From 6517a78c7e82b29b79df6eb110b022b2d4293eea Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 1 Apr 2026 01:34:22 +0800 Subject: [PATCH] =?UTF-8?q?web=E7=AB=AF=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 24 +- README.md | 421 ++++++- index.html | 1071 +++++++++++++++++ lib/services/API使用文档.md | 545 +++++++++ lib/services/stats.php | 66 + lib/services/wakelock_service.dart | 11 + lib/services/wakelock_service_io.dart | 21 + lib/services/wakelock_service_web.dart | 20 + lib/services/统计API文档.md | 216 ++++ .../profile/components/entire -page.dart | 0 lib/views/profile/components/pop-menu.dart | 17 +- lib/views/profile/profile_page.dart | 24 +- 12 files changed, 2390 insertions(+), 46 deletions(-) create mode 100644 index.html create mode 100644 lib/services/API使用文档.md create mode 100644 lib/services/stats.php create mode 100644 lib/services/wakelock_service.dart create mode 100644 lib/services/wakelock_service_io.dart create mode 100644 lib/services/wakelock_service_web.dart create mode 100644 lib/services/统计API文档.md create mode 100644 lib/views/profile/components/entire -page.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f8ca4ac..7b25fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,24 +4,18 @@ All notable changes to this project will be documented in this file. --- -## [1.3.10] - 2026-03-31 +## [1.3.11] - 2026-03-31 -### 新增 -- 💡 **屏幕常亮功能** - - 在个人设置页面添加了屏幕常亮开关 - - 实现了开关状态的管理 - - 添加了 OLED 屏幕提示对话框,告知用户可能的屏幕老化风险 - - 支持 OpenHarmony 平台 +### 修复 +- 🐛 **修复 Web 平台兼容性问题** + - 修复了 `wakelock_plus` 库在 Web 平台上不可用的问题(`dart:ffi` 在 Web 平台上不可用) + - 在 Web 平台上禁用了屏幕常亮功能,并显示相应的提示信息 + - 在 Web 平台上隐藏了屏幕常亮设置项 - 涉及文件: - - `lib/views/profile/profile_page.dart` - 添加屏幕常亮开关和功能实现 + - `lib/views/profile/profile_page.dart` - 添加 Web 平台检查 + - `lib/views/profile/components/pop-menu.dart` - 添加 Web 平台检查 -### 变更 -- 📱 **修改弹出菜单按钮** - - 将弹出菜单中的 "设置" 按钮修改为 "取消" 按钮 - - 将 "退出软件" 按钮修改为 "返回桌面" 按钮 - - 优化了 "刷新数据" 按钮功能,点击时会刷新所有统计数据 - - 涉及文件: - - `lib/views/profile/components/pop-menu.dart` - 修改弹出菜单项 +--- ### 修复 - 优化了屏幕常亮功能的错误处理 diff --git a/README.md b/README.md index b881088..4f5318e 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,410 @@ -# 无书 (wushu) +# 情景诗词 -一款 Flutter 跨平台诗词阅读应用。 +一款优雅的 Flutter 跨平台诗词阅读应用,融合中国传统文化与现代科技,让诗词之美触手可及。 -## 功能特点 +*** -- 📖 诗词阅读与欣赏 -- ❤️ 收藏喜欢的诗词 -- 📝 足迹记录阅读历史 -- 🔍 搜索诗词功能 -- 📱 支持多平台 (Android, iOS, Web, Windows, macOS, 鸿蒙) +## 📖 软件介绍 -## 开始使用 +情景诗词是一款专注于中国古典诗词阅读与欣赏的移动应用,采用 Flutter 框架开发,支持 Android、iOS、Web、Windows、macOS 和鸿蒙多平台。应用以"情景"为核心,通过精美的卡片设计、智能推荐和丰富的互动功能,让用户在碎片化时间中感受诗词的魅力。 -本项目是一个 Flutter 应用。 +## 🎯 软件 Slogan -### 环境要求 +**诗意生活,触手可及** -- Flutter SDK -- Dart SDK +## ✨ 一句话介绍 -### 运行项目 +一款融合中国传统文化与现代科技的诗词阅读应用,让诗词之美触手可及。 -```bash -flutter pub get -flutter run -``` +## 📝 一段话介绍 -## 项目结构 +情景诗词是一款优雅的 Flutter 跨平台诗词阅读应用,致力于将中国古典诗词以现代方式呈现给用户。应用采用精美的卡片式设计,支持多种主题风格,提供诗词阅读、收藏、搜索、答题等丰富功能。无论是晨起时的一句"床前明月光",还是午后的一首"春眠不觉晓",都能让您在快节奏的生活中找到片刻的诗意与宁静。应用支持离线模式,让您随时随地都能享受诗词之美,真正实现诗意生活,触手可及。 -- `lib/` - 主要源代码 -- `assets/` - 资源文件 -- `android/`, `ios/`, `web/`, `windows/`, `macos/`, `ohos/` - 各平台配置 +## 🔑 关键字 -## 许可证 +诗词、古典文学、中国文化、阅读、收藏、答题、离线、跨平台、Flutter、鸿蒙、iOS、Android、传统文化、诗词欣赏、诗词学习 -MIT License +*** + +## 🌟 主要功能 + +### � 诗词阅读 + +- **精美卡片展示**:采用经典、现代、毛玻璃三种主题风格,支持自定义颜色和圆角 +- **智能推荐**:根据时间和情景推荐合适的诗词 +- **诗词详情**:显示诗词标题、作者、朝代、正文、注释和赏析 +- **关键词标签**:展示诗词主题标签,方便分类浏览 +- **精选诗句**:突出显示诗词中的经典名句 + +### ❤️ 收藏功能 + +- **收藏诗词**:一键收藏喜欢的诗词 +- **收藏管理**:在收藏页面查看和管理所有收藏的诗词 +- **收藏统计**:显示收藏数量和收藏时间 + +### 🔍 搜索功能 + +- **关键词搜索**:通过关键词搜索诗词 +- **分类浏览**:按朝代、作者、主题等分类浏览 +- **热门诗词**:查看最受欢迎的诗词 +- **活跃排行**:查看活跃度最高的诗词 + +### 📝 足迹记录 + +- **阅读历史**:自动记录阅读过的诗词 +- **浏览统计**:统计今日浏览、本周浏览、累计浏览等数据 +- **使用天数**:记录首次使用时间和累计使用天数 +- **数据占用**:显示应用数据占用的存储空间 + +### 🎮 诗词答题 + +- **答题挑战**:参与诗词答题挑战,测试诗词知识 +- **题目随机化**:使用 Fisher-Yates 算法随机打乱题目顺序 +- **答题记录**:记录答题历史和成绩 +- **答题统计**:统计今日答题、累计答题、正确率等数据 +- **提示功能**:遇到困难时可以获取提示 + +### 🌐 离线模式 + +- **离线数据下载**:支持下载诗词和答题数据到本地 +- **下载选项**:可选择下载 20/30/60/100 条诗词或答题 +- **后台下载**:返回上一页后继续后台下载 +- **缓存管理**:清空缓存时可选择清空内容 +- **自动切换**:无网络时自动切换到离线模式 + +### 🎨 个性化设置 + +- **主题切换**:支持浅色/深色主题,跟随系统设置 +- **卡片样式**:经典、现代、毛玻璃三种样式可选 +- **颜色自定义**:自定义主题颜色和背景颜色 +- **圆角调整**:调整卡片圆角大小 +- **字体大小**:调整字体大小 + +### ⚙️ 功能设置 + +- **自动刷新**:开启后自动刷新诗词内容 +- **预加载**:预加载下一条诗词,提升加载速度 +- **声音反馈**:开启后播放配音 +- **震动反馈**:开启后提供震动反馈 +- **全局Tips**:显示/隐藏提示信息 +- **隐藏次要按钮**:隐藏主页的"上一条"和"分享"按钮 +- **屏幕常亮**:保持屏幕常亮,方便阅读 + +### 👤 个人中心 + +- **个人信息**:编辑昵称、头像等个人信息 +- **统计数据**:显示今日浏览、本周浏览、累计浏览、今日点赞、今日答题等统计 +- **数据隐藏**:可隐藏统计和答题数据 +- **用户计划**:加入用户体验计划,享受更多功能 +- **应用信息**:查看应用版本、设备信息、UDID 等 + +### 📸 分享功能 + +- **诗词分享**:将诗词生成精美图片分享给朋友 +- **软件分享**:分享软件给朋友 +- **复制功能**:复制诗词内容、UDID、QQ群号等 + +### 🗳️ 投票功能 + +- **用户投票**:参与软件功能投票,影响软件发展方向 +- **投票结果**:查看投票结果和统计 +- **登录注册**:支持用户登录和注册 + +### � 投稿功能 + +- **诗词投稿**:向软件投稿诗词,帮助软件完善内容 +- **投稿记录**:查看历史投稿记录 +- **相似度检测**:防止重复投稿 + +### 🌤️ 天气功能 + +- **天气显示**:在卡片上显示当前天气信息 +- **城市显示**:显示所在城市名称 +- **十二时辰**:使用中国十二时辰制显示时间 + +### 🐛 Bug 反馈 + +- **已知 Bug 列表**:查看已知 Bug 和解决方案 +- **用户反馈**:向开发者反馈问题和建议 +- **功能建议**:提交功能建议 + +### 📱 多平台支持 + +- **Android**:支持 Android 8.0+ 系统 +- **iOS**:支持 iOS 16.0+ 系统 +- **Web**:支持现代浏览器 +- **Windows**:支持 Windows 11 系统 +- **macOS**:支持 macOS 10.15+ 系统 +- **鸿蒙**:支持 HarmonyOS 5.0+ 系统 + +*** + +## 🗺️ 页面导航 + +### 主页 + +- **诗词卡片**:显示当前诗词,支持点赞、分享、上一条、下一条操作 +- **悬浮按钮**:分享按钮(生成图片分享)、上一条按钮 +- **下拉刷新**:下拉刷新诗词内容 +- **长按复制**:长按诗词卡片复制内容 +- **提示信息**:显示"点击任意区域加载下一条,长按复制,下拉刷新" + +### 发现页 + +- **分类浏览**:按朝代、作者、主题等分类浏览诗词 +- **热门诗词**:查看最受欢迎的诗词 +- **活跃排行**:查看活跃度最高的诗词 +- **搜索功能**:搜索诗词 + +### 收藏页 + +- **收藏列表**:显示所有收藏的诗词 +- **取消收藏**:取消收藏诗词 +- **收藏统计**:显示收藏数量 + +### 个人页 + +- **个人卡片**:显示头像、昵称、等级、UEP 标识 +- **统计卡片**:显示今日浏览、本周浏览、累计浏览、今日点赞、今日答题等统计 +- **功能入口**:设置、了解我们、权限管理、应用信息、数据管理、帮助中心等 + +### 设置页 + +- **功能设置**:自动刷新、调试信息、预加载、隐藏次要按钮、声音反馈、震动反馈、全局Tips、屏幕常亮 +- **卡片设置**:卡片样式、主题颜色、背景颜色、圆角大小、字体大小 +- **离线数据**:下载离线数据、清空缓存、查看缓存状态 +- **用户计划**:加入用户体验计划 + +### 了解我们页 + +- **官方网站**:访问官方网站 +- **QQ交流群**:复制 QQ 群号加入交流群 +- **开源协议**:查看开源协议 +- **开发团队**:了解开发团队 + +### 权限管理页 + +- **权限说明**:说明应用需要的权限 +- **沙盒机制**:说明应用的沙盒机制 +- **用户反馈**:反馈问题和建议 +- **功能建议**:提交功能建议 + +### 应用信息页 + +- **应用信息**:应用名称、版本号、包名等 +- **设备信息**:设备型号、系统版本、UDID 等 +- **技术栈**:显示使用的技术栈 +- **开源协议**:查看开源协议 + +### 数据管理页 + +- **数据统计**:显示数据占用情况 +- **清空数据**:清空应用数据 +- **数据备份**:备份应用数据 + +### 帮助中心页 + +- **使用指南**:查看使用指南 +- **常见问题**:查看常见问题解答 +- **联系我们**:联系开发者 + +### 投票页 + +- **投票列表**:查看可参与的投票 +- **投票详情**:查看投票详情和选项 +- **提交投票**:提交投票 + +### 投稿页 + +- **投稿表单**:填写诗词投稿信息 +- **投稿记录**:查看历史投稿记录 +- **清空记录**:清空投稿记录 + +### Bug 列表页 + +- **Bug 列表**:查看已知 Bug +- **解决方案**:查看 Bug 的解决方案 +- **Bug 状态**:查看 Bug 的修复状态 + +*** + +## 📖 使用说明 + +### 首次使用 + +1. 下载并安装应用 +2. 打开应用,阅读并同意用户协议 +3. 进入主页,开始浏览诗词 + +### 浏览诗词 + +1. **加载诗词**:点击诗词卡片任意区域加载下一条诗词 +2. **查看详情**:诗词卡片显示标题、作者、朝代、正文、注释和赏析 +3. **点赞诗词**:点击心形图标点赞诗词 +4. **分享诗词**:点击分享按钮生成图片并分享 +5. **切换诗词**:点击"上一条"或"下一条"按钮切换诗词 + +### 收藏诗词 + +1. 在诗词卡片上点击收藏图标 +2. 在收藏页查看所有收藏的诗词 +3. 点击收藏图标取消收藏 + +### 搜索诗词 + +1. 进入发现页 +2. 点击搜索框输入关键词 +3. 选择分类浏览诗词 +4. 查看热门诗词和活跃排行 + +### 参与答题 + +1. 在诗词卡片上点击答题按钮 +2. 查看题目和选项 +3. 选择答案并提交 +4. 查看答题结果和提示 + +### 下载离线数据 + +1. 进入设置页 +2. 点击"离线数据" +3. 选择下载类型和数量 +4. 点击"开始下载" +5. 等待下载完成 + +### 个性化设置 + +1. 进入设置页 +2. 点击"卡片设置" +3. 选择卡片样式、主题颜色、背景颜色、圆角大小、字体大小 +4. 点击"功能设置"调整功能开关 + +### 查看统计数据 + +1. 进入个人页 +2. 查看统计卡片中的数据 +3. 点击隐藏按钮隐藏统计数据 + +### 投稿诗词 + +1. 进入个人页 +2. 点击"诗词投稿" +3. 填写投稿表单 +4. 点击"提交投稿" + +### 参与投票 + +1. 进入个人页 +2. 点击"功能投票" +3. 选择投票并提交 + +### 反馈问题 + +1. 进入个人页 +2. 点击"权限管理" +3. 点击"用户反馈"或"功能建议" +4. 填写反馈内容 + +*** + +## 🚀 未来开发功能 + +### 🏗️ HarmonyOS 桌面小组件 + +- 2x2 布局小组件 +- 天气显示 +- 诗词展示 +- 优先级:3 + +### 🎯 GetX 状态管理 + +- 引入 GetX 框架进行状态管理 +- 优化应用性能 +- 优先级:4 + +### 📱 二维码能力 + +- 生成二维码分享应用 +- 扫描二维码添加好友 +- 优先级:3 + +### 🌟 HarmonyOS HongMeng Kernel + +- 深度适配鸿蒙内核 +- 优化鸿蒙平台性能 +- 优先级:4 + +### 🎨 更多主题样式 + +- 新增更多卡片主题样式 +- 支持自定义主题 +- 优先级:3 + +### 📊 数据可视化 + +- 统计数据可视化展示 +- 阅读习惯分析 +- 优先级:4 + +### 🤖 AI 智能推荐 + +- 基于 AI 的诗词智能推荐 +- 个性化内容推荐 +- 优先级:5 + +### 🎵 诗词朗诵 + +- 添加诗词朗诵音频 +- 支持多种朗诵风格 +- 优先级:4 + +### 📚 诗词学习 + +- 添加诗词学习课程 +- 诗词知识问答 +- 优先级:4 + +### 👥 社交功能 + +- 添加好友功能 +- 诗词分享到社交平台 +- 优先级:5 + +*** + +## 📖 一篇小文章 + +### 诗意生活,触手可及——情景诗词,让古典诗词走进现代生活 + +在这个快节奏的数字时代,我们常常被各种信息轰炸,很少有时间静下心来品味古典诗词的韵味。然而,中国古典诗词作为中华文化的瑰宝,承载着千年的智慧和情感,值得我们去细细品味。 + +情景诗词应用应运而生,它将中国古典诗词与现代科技完美融合,让诗词之美触手可及。无论您是在清晨的地铁上,还是在午后的咖啡馆里,只需打开应用,就能欣赏到精美的诗词卡片,感受诗人的情感与智慧。 + +应用采用 Flutter 框架开发,支持 Android、iOS、Web、Windows、macOS 和鸿蒙多平台,让用户在任何设备上都能享受诗词之美。精美的卡片设计,经典、现代、毛玻璃三种主题风格,让诗词阅读成为一种视觉享受。 + +应用不仅提供诗词阅读功能,还支持收藏、搜索、答题等丰富功能。用户可以收藏喜欢的诗词,通过关键词搜索诗词,参与诗词答题挑战,测试自己的诗词知识。离线模式让用户随时随地都能享受诗词之美,真正实现诗意生活,触手可及。 + +情景诗词应用致力于将中国古典诗词以现代方式呈现给用户,让更多人在碎片化时间中感受诗词的魅力。无论是晨起时的一句"床前明月光",还是午后的一首"春眠不觉晓",都能让您在快节奏的生活中找到片刻的诗意与宁静。 + +让我们一起,用情景诗词应用,让古典诗词走进现代生活,让诗意生活,触手可及。 + +*** + +## 📄 开源协议 + +本项目采用开源协议,具体信息请查看应用内的开源协议页面。 + +## 📞 联系我们 + +- QQ 交流群:271129018 +- 官方网站:请查看应用内的官方网站链接 + +*** + +## 🙏 致谢 + +感谢所有为情景诗词应用做出贡献的开发者和用户,是你们的支持让这个应用不断完善和发展。 + +*** + +*情景诗词,诗意生活,触手可及* diff --git a/index.html b/index.html new file mode 100644 index 0000000..0df6d17 --- /dev/null +++ b/index.html @@ -0,0 +1,1071 @@ + + + + + + 情景诗词 - 诗意生活,触手可及 + + + + + + + + + + + + +
+
+
+
+
+ + Flutter 跨平台应用 +
+

诗意生活
触手可及

+

一款专注于中国古典诗词阅读与欣赏的移动应用,融合传统文化与现代科技,让诗词之美触手可及。

+ +
+
+
+
+
+
+
静夜思
+
唐 · 李白
+
+ 床前明月光,
+ 疑是地上霜。
+ 举头望明月,
+ 低头思故乡。 +
+
+
+
春晓
+
唐 · 孟浩然
+
+ 春眠不觉晓,
+ 处处闻啼鸟。 +
+
+
+
+
+ 🏠 + 主页 +
+
+ 🔍 + 发现 +
+
+ ❤️ + 收藏 +
+
+ 👤 + 我的 +
+
+
+
+
+
+ + +
+
+ +

丰富的诗词体验

+

精心设计的每一个功能,只为让您更好地感受诗词之美

+
+
+
+
📚
+

精美诗词阅读

+

经典、现代、毛玻璃三种主题风格,支持自定义颜色和圆角,打造专属阅读体验

+
+
+
❤️
+

收藏与管理

+

一键收藏喜欢的诗词,随时查看和管理您的诗词收藏夹

+
+
+
🎮
+

诗词答题挑战

+

参与诗词答题,测试您的诗词知识,记录答题历史和成绩

+
+
+
🌐
+

离线模式支持

+

下载诗词和答题数据到本地,随时随地享受诗词之美

+
+
+
🎨
+

个性化设置

+

主题切换、卡片样式、颜色自定义,打造独一无二的诗词应用

+
+
+
📸
+

诗词分享

+

将诗词生成精美图片,与朋友分享诗词之美

+
+
+
+ + +
+
+ +

持续成长中

+
+
+
+
6+
+
支持平台
+
+
+
1000+
+
诗词收录
+
+
+
3
+
主题风格
+
+
+
+
诗词之美
+
+
+
+ + +
+
+
+ +

选择您的平台

+

支持 Android、iOS、鸿蒙、Web、Windows、macOS 多平台

+
+
+
+
🤖
+
Android
+
Android 8.0+
+ 下载 APK +
+
+
🍎
+
iOS
+
iOS 16.0+
+ App Store +
+
+
🌸
+
鸿蒙
+
HarmonyOS 5.0+
+ 应用市场 +
+
+
🌐
+
Web
+
现代浏览器
+ 在线使用 +
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/lib/services/API使用文档.md b/lib/services/API使用文档.md new file mode 100644 index 0000000..38ecc8b --- /dev/null +++ b/lib/services/API使用文档.md @@ -0,0 +1,545 @@ +# 诗词收录系统 - API 使用文档 + +## 概述 + +本文档描述诗词收录系统的 API 接口使用方法。 + +## 基础信息 + +- **API 地址**: `api.php` +- **请求方式**: GET/POST +- **返回格式**: JSON +- **字符编码**: UTF-8 + +## API 接口 + +### 1. 获取分类列表 + +获取所有可用的诗词分类。 + +**接口地址**: `api.php?api=categories` + +**请求方式**: GET + +**请求参数**: 无 + +**返回示例**: + +```json +{ + "ok": true, + "categories": [ + { + "id": "1", + "sid": "1", + "icon": "fa-paper-plane", + "catename": "诗词句", + "alias": null, + "create_time": "2026-03-12 04:17:50", + "update_time": "2026-03-13 02:12:54" + } + ], + "debug": { + "current_dir": "/www/wwwroot/yy.vogov.cn/api/app", + "categories_count": 3 + } +} +``` + +--- + +### 2. 检查诗词名称是否存在 + +检查指定的诗词名称是否已存在于数据库中(支持相似度检查)。 + +**接口地址**: `api.php?api=check-name` + +**请求方式**: POST + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| name | string | 是 | 诗词名称/参考语句 | +| threshold | int | 否 | 相似度阈值(0-100),默认 80 | + +**请求示例**: + +```javascript +const formData = new FormData(); +formData.append('name', '盈盈一水间,脉脉不得语'); +formData.append('threshold', 80); + +const response = await fetch('api.php?api=check-name', { + method: 'POST', + body: formData +}); + +const data = await response.json(); +``` + +**返回示例**: + +**无相似内容**: +```json +{ + "ok": true, + "exists": false, + "similar_count": 0, + "max_similarity": 0, + "threshold": 80 +} +``` + +**发现相似内容**: +```json +{ + "ok": true, + "exists": true, + "similar_count": 2, + "max_similarity": 95, + "threshold": 80 +} +``` + +**返回字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| ok | boolean | 请求是否成功 | +| exists | boolean | 是否存在相似内容,true=存在,false=不存在 | +| similar_count | int | 相似内容条数 | +| max_similarity | float | 最高相似度百分比(0-100) | +| threshold | int | 使用的相似度阈值 | + +--- + +### 3. 提交诗词收录申请 + +提交诗词收录申请到数据库。 + +**接口地址**: `api.php?api=submit` + +**请求方式**: POST + +**请求参数**: + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| name | string | 是 | 诗词名称/参考语句 | +| catename | string | 是 | 分类名称 | +| url | string | 是 | 诗人和标题 | +| keywords | string | 是 | 关键词,多个用逗号分隔 | +| introduce | string | 是 | 诗词介绍 | +| img | string | 否 | 平台/配图,默认值: 'default' | +| captcha | string | 是 | 人机验证码 | +| threshold | int | 否 | 相似度阈值(0-100),默认 80 | + +**请求示例**: + +```javascript +const formData = new FormData(); +formData.append('name', '盈盈一水间,脉脉不得语'); +formData.append('catename', '诗词句'); +formData.append('url', '古诗十九首'); +formData.append('keywords', '爱情,古诗,离别'); +formData.append('introduce', '《迢迢牵牛星》是产生于汉代的一首文人五言诗...'); +formData.append('img', 'iOS Swift'); +formData.append('captcha', '1234'); +formData.append('threshold', 80); + +const response = await fetch('api.php?api=submit', { + method: 'POST', + body: formData +}); + +const data = await response.json(); +``` + +**返回示例**: + +**成功**: +```json +{ + "ok": true, + "message": "✅ 提交成功!等待审核", + "debug": { + "input_data": {...}, + "insert_result": true, + "last_insert_id": "123" + } +} +``` + +**失败**: +```json +{ + "ok": false, + "error": "该诗词已存在!", + "debug": {...} +} +``` + +**返回字段说明**: + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| ok | boolean | 请求是否成功 | +| message | string | 成功消息(仅成功时返回) | +| error | string | 错误消息(仅失败时返回) | +| debug | object | 调试信息 | + +--- + +## 在 App 中的使用方法 + +### Android (Kotlin) + +```kotlin +// 获取分类 +suspend fun getCategories(): List { + val response = OkHttpClient().newCall( + Request.Builder() + .url("https://your-domain.com/api.php?api=categories") + .build() + ).execute() + + val json = JSONObject(response.body?.string()) + val categoriesArray = json.getJSONArray("categories") + + val categories = mutableListOf() + for (i in 0 until categoriesArray.length()) { + val cat = categoriesArray.getJSONObject(i) + categories.add(Category(cat.getString("catename"))) + } + + return categories +} + +// 检查名称 +suspend fun checkName(name: String, threshold: Int = 80): CheckResult { + val formBody = FormBody.Builder() + .add("name", name) + .add("threshold", threshold.toString()) + .build() + + val response = OkHttpClient().newCall( + Request.Builder() + .url("https://your-domain.com/api.php?api=check-name") + .post(formBody) + .build() + ).execute() + + val json = JSONObject(response.body?.string()) + return CheckResult( + exists = json.getBoolean("exists"), + similarCount = json.getInt("similar_count"), + maxSimilarity = json.getDouble("max_similarity"), + threshold = json.getInt("threshold") + ) +} + +// 提交收录 +suspend fun submitPoem(data: PoemData): Boolean { + val formBody = FormBody.Builder() + .add("name", data.name) + .add("catename", data.catename) + .add("url", data.url) + .add("keywords", data.keywords) + .add("introduce", data.introduce) + .add("img", data.img ?: "default") + .add("captcha", data.captcha) + .add("threshold", data.threshold?.toString() ?: "80") + .build() + + val response = OkHttpClient().newCall( + Request.Builder() + .url("https://your-domain.com/api.php?api=submit") + .post(formBody) + .build() + ).execute() + + val json = JSONObject(response.body?.string()) + return json.getBoolean("ok") +} +``` + +### iOS (Swift) + +```swift +// 获取分类 +func getCategories(completion: @escaping ([String]?, Error?) -> Void) { + guard let url = URL(string: "https://your-domain.com/api.php?api=categories") else { + completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])) + return + } + + URLSession.shared.dataTask(with: url) { data, response, error in + if let error = error { + completion(nil, error) + return + } + + guard let data = data else { + completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data"])) + return + } + + do { + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let categories = json["categories"] as? [[String: Any]] { + let categoryNames = categories.compactMap { $0["catename"] as? String } + completion(categoryNames, nil) + } + } catch { + completion(nil, error) + } + }.resume() +} + +// 检查名称 +func checkName(name: String, threshold: Int = 80, completion: @escaping (CheckResult?, Error?) -> Void) { + guard let apiUrl = URL(string: "https://your-domain.com/api.php?api=check-name") else { + completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])) + return + } + + var request = URLRequest(url: apiUrl) + request.httpMethod = "POST" + + let parameters = [ + "name": name, + "threshold": "\(threshold)" + ] + + request.httpBody = parameters.percentEncoded() + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(nil, error) + return + } + + guard let data = data else { + completion(nil, NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data"])) + return + } + + do { + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let exists = json["exists"] as? Bool, + let similarCount = json["similar_count"] as? Int, + let maxSimilarity = json["max_similarity"] as? Double, + let threshold = json["threshold"] as? Int { + let result = CheckResult( + exists: exists, + similarCount: similarCount, + maxSimilarity: maxSimilarity, + threshold: threshold + ) + completion(result, nil) + } + } catch { + completion(nil, error) + } + }.resume() +} + +// 提交收录 +func submitPoem(name: String, catename: String, url: String, keywords: String, introduce: String, img: String?, captcha: String, threshold: Int = 80, completion: @escaping (Bool, String?) -> Void) { + guard let apiUrl = URL(string: "https://your-domain.com/api.php?api=submit") else { + completion(false, "Invalid URL") + return + } + + var request = URLRequest(url: apiUrl) + request.httpMethod = "POST" + + let parameters = [ + "name": name, + "catename": catename, + "url": url, + "keywords": keywords, + "introduce": introduce, + "img": img ?? "default", + "captcha": captcha, + "threshold": "\(threshold)" + ] + + request.httpBody = parameters.percentEncoded() + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(false, error.localizedDescription) + return + } + + guard let data = data else { + completion(false, "No data") + return + } + + do { + if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let ok = json["ok"] as? Bool { + let message = json["message"] as? String ?? json["error"] as? String + completion(ok, message) + } + } catch { + completion(false, error.localizedDescription) + } + }.resume() +} + +extension Dictionary { + func percentEncoded() -> Data? { + return map { key, value in + let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" + return escapedKey + "=" + escapedValue + } + .joined(separator: "&") + .data(using: .utf8) + } +} + +extension CharacterSet { + static let urlQueryValueAllowed: CharacterSet = { + let generalDelimitersToEncode = ":#[]@" + let subDelimitersToEncode = "!$&'()*+,;=" + + var allowed = CharacterSet.urlQueryAllowed + allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") + return allowed + }() +} +``` + +### Flutter (Dart) + +```dart +import 'package:http/http.dart' as http; +import 'dart:convert'; + +// 获取分类 +Future> getCategories() async { + final response = await http.get( + Uri.parse('https://your-domain.com/api.php?api=categories'), + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + final List categories = data['categories']; + return categories.map((cat) => cat['catename'] as String).toList(); + } else { + throw Exception('Failed to load categories'); + } +} + +// 检查名称 +Future checkName({ + required String name, + int threshold = 80, +}) async { + final response = await http.post( + Uri.parse('https://your-domain.com/api.php?api=check-name'), + body: { + 'name': name, + 'threshold': threshold.toString(), + }, + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + return CheckResult( + exists: data['exists'] as bool, + similarCount: data['similar_count'] as int, + maxSimilarity: (data['max_similarity'] as num).toDouble(), + threshold: data['threshold'] as int, + ); + } else { + throw Exception('Failed to check name'); + } +} + +// 提交收录 +Future submitPoem({ + required String name, + required String catename, + required String url, + required String keywords, + required String introduce, + String? img, + required String captcha, + int threshold = 80, +}) async { + final response = await http.post( + Uri.parse('https://your-domain.com/api.php?api=submit'), + body: { + 'name': name, + 'catename': catename, + 'url': url, + 'keywords': keywords, + 'introduce': introduce, + 'img': img ?? 'default', + 'captcha': captcha, + 'threshold': threshold.toString(), + }, + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + return data['ok'] as bool; + } else { + throw Exception('Failed to submit'); + } +} +``` + +--- + +## 错误码说明 + +| 错误信息 | 说明 | +|----------|------| +| 缺少必填字段:xxx | 必填字段未填写 | +| 该诗词已存在! | 诗词名称已在数据库中,或相似度超过阈值 | +| ❌ 数据库写入失败:无法插入数据 | 数据库插入失败 | +| 验证码错误,请重新输入 | 人机验证码错误 | +| 提交过于频繁,请稍后再试 | 频率限制,1分钟内只能提交3次 | + +--- + +## 相似度说明 + +系统使用 **Levenshtein 距离算法** 计算文本相似度: + +1. **文本清理**:自动去除标点符号和空格后比较 +2. **阈值设置**:0-100%,默认 80% +3. **判断规则**:相似度 ≥ 阈值 则认为是重复内容 + +**示例**: +- "盈盈一水间,脉脉不得语" +- "盈盈一水间,脉脉不得语。"(相似度约 95%) +- "盈盈一水间,脉脉不得"(相似度约 85%) + +--- + +## 注意事项 + +1. **字符编码**: 所有请求和响应都使用 UTF-8 编码 +2. **人机验证**: 提交接口必须提供正确的验证码 +3. **频率限制**: 同一 IP 1分钟内最多提交 3 次 +4. **相似度检查**: check-name 和 submit 接口都会进行相似度检查 +5. **数据安全**: 所有用户输入都会经过安全处理 +6. **调试信息**: API 返回包含 debug 字段,方便开发调试,生产环境可忽略 + +--- + +## 更新日志 + +- **v1.0.12**: 添加相似度验证功能,支持可配置阈值 +- **v1.0.11**: 修改验证表为 pre_site +- **v1.0.10**: 添加人机验证功能和频率限制 +- **v1.0.9**: 添加结果Modal对话框 +- **v1.0.8**: 添加检测按钮和提交前确认 diff --git a/lib/services/stats.php b/lib/services/stats.php new file mode 100644 index 0000000..099013e --- /dev/null +++ b/lib/services/stats.php @@ -0,0 +1,66 @@ +count('category'); + $stats['count_site'] = $DB->count('site'); + $stats['count_apply'] = $DB->count('apply', array('reject' => 0)); + $stats['count_apply_reject'] = $DB->count('apply', array('reject' => 1)); + $stats['count_article'] = $DB->count('article'); + $stats['count_article_category'] = $DB->count('article_category'); + $stats['count_notice'] = $DB->count('notice'); + $stats['count_link'] = $DB->count('link'); + $stats['count_tags'] = 131; + + $top_hits_day = $DB->find('site', 'id, name', array('date' => date("Y-m-d", time())), '`hits_day` desc'); + $top_hits_month = $DB->find('site', 'id, name', array('datem' => date("Y-m", time())), '`hits_month` desc'); + $top_hits_total = $DB->find('site', 'id, name', null, '`hits_total` desc'); + $top_like = $DB->find('site', 'id, name', null, '`like` desc'); + + $cumulative_hits_result = $DB->query("SELECT SUM(hits_total) as total FROM pre_site")->fetch(); + $cumulative_likes_result = $DB->query("SELECT SUM(`like`) as total FROM pre_site")->fetch(); + + $stats['cumulative_hits'] = $cumulative_hits_result['total'] ?? 0; + $stats['cumulative_likes'] = $cumulative_likes_result['total'] ?? 0; + + $stats['top_hits_day'] = $top_hits_day ? [ + 'id' => $top_hits_day['id'], + 'name' => $top_hits_day['name'] + ] : null; + + $stats['top_hits_month'] = $top_hits_month ? [ + 'id' => $top_hits_month['id'], + 'name' => $top_hits_month['name'] + ] : null; + + $stats['top_hits_total'] = $top_hits_total ? [ + 'id' => $top_hits_total['id'], + 'name' => $top_hits_total['name'] + ] : null; + + $stats['top_like'] = $top_like ? [ + 'id' => $top_like['id'], + 'name' => $top_like['name'] + ] : null; + + $stats['build_time'] = $conf['build_time'] ?? ''; + + echo json_encode([ + 'ok' => true, + 'data' => $stats, + 'timestamp' => time() + ], JSON_UNESCAPED_UNICODE); + +} catch (Exception $e) { + echo json_encode([ + 'ok' => false, + 'error' => $e->getMessage() + ], JSON_UNESCAPED_UNICODE); +} diff --git a/lib/services/wakelock_service.dart b/lib/services/wakelock_service.dart new file mode 100644 index 0000000..7283aae --- /dev/null +++ b/lib/services/wakelock_service.dart @@ -0,0 +1,11 @@ +import 'wakelock_service_web.dart' if (dart.library.io) 'wakelock_service_io.dart'; + +abstract class WakelockService { + static final WakelockService instance = getWakelockService(); + + Future enable(); + Future disable(); + Future isEnabled(); +} + +WakelockService getWakelockService() => getWakelockServiceImpl(); diff --git a/lib/services/wakelock_service_io.dart b/lib/services/wakelock_service_io.dart new file mode 100644 index 0000000..17ba873 --- /dev/null +++ b/lib/services/wakelock_service_io.dart @@ -0,0 +1,21 @@ +import 'package:wakelock_plus/wakelock_plus.dart'; +import 'wakelock_service.dart'; + +class WakelockServiceIO implements WakelockService { + @override + Future enable() async { + await WakelockPlus.enable(); + } + + @override + Future disable() async { + await WakelockPlus.disable(); + } + + @override + Future isEnabled() async { + return await WakelockPlus.enabled; + } +} + +WakelockService getWakelockServiceImpl() => WakelockServiceIO(); diff --git a/lib/services/wakelock_service_web.dart b/lib/services/wakelock_service_web.dart new file mode 100644 index 0000000..b818254 --- /dev/null +++ b/lib/services/wakelock_service_web.dart @@ -0,0 +1,20 @@ +import 'wakelock_service.dart'; + +class WakelockServiceWeb implements WakelockService { + @override + Future enable() async { + // Web 平台不支持屏幕常亮功能 + } + + @override + Future disable() async { + // Web 平台不支持屏幕常亮功能 + } + + @override + Future isEnabled() async { + return false; + } +} + +WakelockService getWakelockServiceImpl() => WakelockServiceWeb(); diff --git a/lib/services/统计API文档.md b/lib/services/统计API文档.md new file mode 100644 index 0000000..47f61e1 --- /dev/null +++ b/lib/services/统计API文档.md @@ -0,0 +1,216 @@ +# 统计 API 接口文档 + +## 概述 + +获取网站统计信息的 API 接口,返回分类数量、收录数量、热度统计等数据。 + +## 基础信息 + +- **接口地址**: `https://yy.vogov.cn/api/app/stats.php` +- **请求方式**: GET +- **返回格式**: JSON +- **字符编码**: UTF-8 +- **支持跨域**: 是(`Access-Control-Allow-Origin: *`) + +## 请求参数 + +无需任何参数,直接 GET 请求即可。 + +## 返回示例 + +```json +{ + "ok": true, + "data": { + "count_category": 3, + "count_site": 15807, + "count_apply": 14, + "count_apply_reject": 3, + "count_article": 2, + "count_article_category": 4, + "count_notice": 2, + "count_link": 3, + "count_tags": 131, + "cumulative_hits": "16887", + "cumulative_likes": "272", + "top_hits_day": { + "id": "7559", + "name": "除却天边月,没人知。人有悲欢离合,月有阴晴圆缺,此事古难全。" + }, + "top_hits_month": { + "id": "461", + "name": "人有悲欢离合,月有阴晴圆缺,此事古难全。" + }, + "top_hits_total": { + "id": "1", + "name": "井鱼焉知身在渊,错把方寸作世间‌" + }, + "top_like": { + "id": "5876", + "name": "世间无比酒,天下有名楼。" + }, + "build_time": "2026-03-04" + }, + "timestamp": 1774977191 +} +``` + +## 返回字段说明 + +### 基础字段 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| ok | boolean | 请求是否成功 | +| data | object | 统计数据对象 | +| timestamp | int | 服务器时间戳 | + +### data 对象字段 + +#### 数量统计 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| count_category | int | 已开设分类数量 | +| count_site | int | 已收录诗句数量 | +| count_apply | int | 审核中的申请数量 | +| count_apply_reject | int | 已拒绝的申请数量 | +| count_article | int | 文章数量 | +| count_article_category | int | 文章分类数量 | +| count_notice | int | 已发布公告数量 | +| count_link | int | 开发者人数 | +| count_tags | int | 分类标签数量 | + +#### 热度统计 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| cumulative_hits | string | 累计热度次数 | +| cumulative_likes | string | 累计点赞数量 | + +#### 热门内容 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| top_hits_day | object/null | 当天热门诗句 | +| top_hits_month | object/null | 本月热门诗句 | +| top_hits_total | object/null | 历史最热诗句 | +| top_like | object/null | 最高点赞诗句 | + +#### 热门内容对象 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | string | 诗句 ID | +| name | string | 诗句内容 | + +#### 其他 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| build_time | string | 建站时间(格式:YYYY-MM-DD) | + +## 错误返回 + +```json +{ + "ok": false, + "error": "错误信息" +} +``` + +## 调用示例 + +### JavaScript (Fetch) + +```javascript +fetch('https://yy.vogov.cn/api/app/stats.php') + .then(response => response.json()) + .then(data => { + if (data.ok) { + console.log('已收录诗句:', data.data.count_site); + console.log('当天热门:', data.data.top_hits_day.name); + } + }) + .catch(error => console.error('Error:', error)); +``` + + +### Flutter (Dart) + +```dart +import 'package:http/http.dart' as http; +import 'dart:convert'; + +Future getStats() async { + final response = await http.get( + Uri.parse('https://yy.vogov.cn/api/app/stats.php'), + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + if (data['ok'] == true) { + return StatsData.fromJson(data['data']); + } + } + return null; +} + +class StatsData { + final int countSite; + final String cumulativeHits; + final TopContent? topHitsDay; + + StatsData({ + required this.countSite, + required this.cumulativeHits, + this.topHitsDay, + }); + + factory StatsData.fromJson(Map json) { + return StatsData( + countSite: json['count_site'], + cumulativeHits: json['cumulative_hits'], + topHitsDay: json['top_hits_day'] != null + ? TopContent.fromJson(json['top_hits_day']) + : null, + ); + } +} + +class TopContent { + final String id; + final String name; + + TopContent({required this.id, required this.name}); + + factory TopContent.fromJson(Map json) { + return TopContent(id: json['id'], name: json['name']); + } +} +``` + +## 数据来源 + +数据来源于以下数据库表: + +| 表名 | 说明 | +|------|------| +| pre_category | 分类表 | +| pre_site | 收录诗句表 | +| pre_apply | 申请收录表 | +| pre_article | 文章表 | +| pre_article_category | 文章分类表 | +| pre_notice | 公告表 | +| pre_link | 友情链接表 | + +## 注意事项 + +1. **缓存建议**:统计数据变化频率较低,建议客户端缓存 5-10 分钟 +2. **空值处理**:热门内容字段可能为 `null`,请做好空值判断 +3. **跨域支持**:接口已配置 CORS,支持前端直接调用 +4. **数据类型**:`cumulative_hits` 和 `cumulative_likes` 返回的是字符串类型 + +## 更新日志 + +- **v1.0.14** (2026-03-30): 新增统计 API 接口 diff --git a/lib/views/profile/components/entire -page.dart b/lib/views/profile/components/entire -page.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/views/profile/components/pop-menu.dart b/lib/views/profile/components/pop-menu.dart index 4432594..46270ee 100644 --- a/lib/views/profile/components/pop-menu.dart +++ b/lib/views/profile/components/pop-menu.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:share_plus/share_plus.dart'; import '../../../constants/app_constants.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; +import '../../../services/wakelock_service.dart'; import 'dart:io' as io; class PopMenu extends StatelessWidget { @@ -44,6 +45,16 @@ class PopMenu extends StatelessWidget { } static Future toggleScreenWake(BuildContext context) async { + // Web 平台不支持 wakelock_plus + if (kIsWeb) { + if (context.mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Web 平台不支持屏幕常亮功能'))); + } + return; + } + try { // 使用 io.Platform 检测平台 final String osName = io.Platform.operatingSystem; @@ -51,7 +62,7 @@ class PopMenu extends StatelessWidget { print('Current platform: $osName, version: $osVersion'); // 直接尝试启用屏幕常亮 - await WakelockPlus.enable(); + await WakelockService.instance.enable(); if (context.mounted) { ScaffoldMessenger.of( @@ -59,7 +70,7 @@ class PopMenu extends StatelessWidget { ).showSnackBar(const SnackBar(content: Text('屏幕常亮已开启'))); } } catch (e, stackTrace) { - print('WakelockPlus error: $e'); + print('WakelockService error: $e'); print('Stack trace: $stackTrace'); if (context.mounted) { // 检查错误类型,判断是否是设备不支持 diff --git a/lib/views/profile/profile_page.dart b/lib/views/profile/profile_page.dart index e0bc285..200aa2e 100644 --- a/lib/views/profile/profile_page.dart +++ b/lib/views/profile/profile_page.dart @@ -9,11 +9,12 @@ import 'dart:io' as io; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import '../../constants/app_constants.dart'; import '../../controllers/history_controller.dart'; import '../../controllers/shared_preferences_storage_controller.dart'; +import '../../services/wakelock_service.dart'; import 'history_page.dart'; import 'per_card.dart'; import 'settings/app_fun.dart'; @@ -802,6 +803,11 @@ class _ProfilePageState extends State } Widget _buildScreenWakeItem() { + // Web 平台不支持屏幕常亮功能,不显示该项 + if (kIsWeb) { + return const SizedBox.shrink(); + } + // === 屏幕常亮设置项:显示图标、标题和开关 === return ListTile( leading: Icon( @@ -912,6 +918,16 @@ class _ProfilePageState extends State } Future _toggleScreenWake(bool enable) async { + // Web 平台不支持 wakelock_plus + if (kIsWeb) { + if (mounted) { + ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Web 平台不支持屏幕常亮功能'))); + } + return; + } + try { // 使用 io.Platform 检测平台 final String osName = io.Platform.operatingSystem; @@ -919,14 +935,14 @@ class _ProfilePageState extends State print('Current platform: $osName, version: $osVersion'); if (enable) { - await WakelockPlus.enable(); + await WakelockService.instance.enable(); if (mounted) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('屏幕常亮已开启'))); } } else { - await WakelockPlus.disable(); + await WakelockService.instance.disable(); if (mounted) { ScaffoldMessenger.of( context, @@ -938,7 +954,7 @@ class _ProfilePageState extends State _isScreenWakeEnabled = enable; }); } catch (e, stackTrace) { - print('WakelockPlus error: $e'); + print('WakelockService error: $e'); print('Stack trace: $stackTrace'); if (mounted) { // 检查错误类型,判断是否是设备不支持