web端修复
This commit is contained in:
24
CHANGELOG.md
24
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
|
||||||
|
|
||||||
### 新增
|
### 修复
|
||||||
- 💡 **屏幕常亮功能**
|
- 🐛 **修复 Web 平台兼容性问题**
|
||||||
- 在个人设置页面添加了屏幕常亮开关
|
- 修复了 `wakelock_plus` 库在 Web 平台上不可用的问题(`dart:ffi` 在 Web 平台上不可用)
|
||||||
- 实现了开关状态的管理
|
- 在 Web 平台上禁用了屏幕常亮功能,并显示相应的提示信息
|
||||||
- 添加了 OLED 屏幕提示对话框,告知用户可能的屏幕老化风险
|
- 在 Web 平台上隐藏了屏幕常亮设置项
|
||||||
- 支持 OpenHarmony 平台
|
|
||||||
- 涉及文件:
|
- 涉及文件:
|
||||||
- `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` - 修改弹出菜单项
|
|
||||||
|
|
||||||
### 修复
|
### 修复
|
||||||
- 优化了屏幕常亮功能的错误处理
|
- 优化了屏幕常亮功能的错误处理
|
||||||
|
|||||||
421
README.md
421
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
|
***
|
||||||
|
|
||||||
|
## 🌟 主要功能
|
||||||
|
|
||||||
|
### <20> 诗词阅读
|
||||||
|
|
||||||
|
- **精美卡片展示**:采用经典、现代、毛玻璃三种主题风格,支持自定义颜色和圆角
|
||||||
|
- **智能推荐**:根据时间和情景推荐合适的诗词
|
||||||
|
- **诗词详情**:显示诗词标题、作者、朝代、正文、注释和赏析
|
||||||
|
- **关键词标签**:展示诗词主题标签,方便分类浏览
|
||||||
|
- **精选诗句**:突出显示诗词中的经典名句
|
||||||
|
|
||||||
|
### ❤️ 收藏功能
|
||||||
|
|
||||||
|
- **收藏诗词**:一键收藏喜欢的诗词
|
||||||
|
- **收藏管理**:在收藏页面查看和管理所有收藏的诗词
|
||||||
|
- **收藏统计**:显示收藏数量和收藏时间
|
||||||
|
|
||||||
|
### 🔍 搜索功能
|
||||||
|
|
||||||
|
- **关键词搜索**:通过关键词搜索诗词
|
||||||
|
- **分类浏览**:按朝代、作者、主题等分类浏览
|
||||||
|
- **热门诗词**:查看最受欢迎的诗词
|
||||||
|
- **活跃排行**:查看活跃度最高的诗词
|
||||||
|
|
||||||
|
### 📝 足迹记录
|
||||||
|
|
||||||
|
- **阅读历史**:自动记录阅读过的诗词
|
||||||
|
- **浏览统计**:统计今日浏览、本周浏览、累计浏览等数据
|
||||||
|
- **使用天数**:记录首次使用时间和累计使用天数
|
||||||
|
- **数据占用**:显示应用数据占用的存储空间
|
||||||
|
|
||||||
|
### 🎮 诗词答题
|
||||||
|
|
||||||
|
- **答题挑战**:参与诗词答题挑战,测试诗词知识
|
||||||
|
- **题目随机化**:使用 Fisher-Yates 算法随机打乱题目顺序
|
||||||
|
- **答题记录**:记录答题历史和成绩
|
||||||
|
- **答题统计**:统计今日答题、累计答题、正确率等数据
|
||||||
|
- **提示功能**:遇到困难时可以获取提示
|
||||||
|
|
||||||
|
### 🌐 离线模式
|
||||||
|
|
||||||
|
- **离线数据下载**:支持下载诗词和答题数据到本地
|
||||||
|
- **下载选项**:可选择下载 20/30/60/100 条诗词或答题
|
||||||
|
- **后台下载**:返回上一页后继续后台下载
|
||||||
|
- **缓存管理**:清空缓存时可选择清空内容
|
||||||
|
- **自动切换**:无网络时自动切换到离线模式
|
||||||
|
|
||||||
|
### 🎨 个性化设置
|
||||||
|
|
||||||
|
- **主题切换**:支持浅色/深色主题,跟随系统设置
|
||||||
|
- **卡片样式**:经典、现代、毛玻璃三种样式可选
|
||||||
|
- **颜色自定义**:自定义主题颜色和背景颜色
|
||||||
|
- **圆角调整**:调整卡片圆角大小
|
||||||
|
- **字体大小**:调整字体大小
|
||||||
|
|
||||||
|
### ⚙️ 功能设置
|
||||||
|
|
||||||
|
- **自动刷新**:开启后自动刷新诗词内容
|
||||||
|
- **预加载**:预加载下一条诗词,提升加载速度
|
||||||
|
- **声音反馈**:开启后播放配音
|
||||||
|
- **震动反馈**:开启后提供震动反馈
|
||||||
|
- **全局Tips**:显示/隐藏提示信息
|
||||||
|
- **隐藏次要按钮**:隐藏主页的"上一条"和"分享"按钮
|
||||||
|
- **屏幕常亮**:保持屏幕常亮,方便阅读
|
||||||
|
|
||||||
|
### 👤 个人中心
|
||||||
|
|
||||||
|
- **个人信息**:编辑昵称、头像等个人信息
|
||||||
|
- **统计数据**:显示今日浏览、本周浏览、累计浏览、今日点赞、今日答题等统计
|
||||||
|
- **数据隐藏**:可隐藏统计和答题数据
|
||||||
|
- **用户计划**:加入用户体验计划,享受更多功能
|
||||||
|
- **应用信息**:查看应用版本、设备信息、UDID 等
|
||||||
|
|
||||||
|
### 📸 分享功能
|
||||||
|
|
||||||
|
- **诗词分享**:将诗词生成精美图片分享给朋友
|
||||||
|
- **软件分享**:分享软件给朋友
|
||||||
|
- **复制功能**:复制诗词内容、UDID、QQ群号等
|
||||||
|
|
||||||
|
### 🗳️ 投票功能
|
||||||
|
|
||||||
|
- **用户投票**:参与软件功能投票,影响软件发展方向
|
||||||
|
- **投票结果**:查看投票结果和统计
|
||||||
|
- **登录注册**:支持用户登录和注册
|
||||||
|
|
||||||
|
### <20> 投稿功能
|
||||||
|
|
||||||
|
- **诗词投稿**:向软件投稿诗词,帮助软件完善内容
|
||||||
|
- **投稿记录**:查看历史投稿记录
|
||||||
|
- **相似度检测**:防止重复投稿
|
||||||
|
|
||||||
|
### 🌤️ 天气功能
|
||||||
|
|
||||||
|
- **天气显示**:在卡片上显示当前天气信息
|
||||||
|
- **城市显示**:显示所在城市名称
|
||||||
|
- **十二时辰**:使用中国十二时辰制显示时间
|
||||||
|
|
||||||
|
### 🐛 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
|
||||||
|
- 官方网站:请查看应用内的官方网站链接
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
## 🙏 致谢
|
||||||
|
|
||||||
|
感谢所有为情景诗词应用做出贡献的开发者和用户,是你们的支持让这个应用不断完善和发展。
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
*情景诗词,诗意生活,触手可及*
|
||||||
|
|||||||
1071
index.html
Normal file
1071
index.html
Normal file
File diff suppressed because it is too large
Load Diff
545
lib/services/API使用文档.md
Normal file
545
lib/services/API使用文档.md
Normal file
@@ -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<Category> {
|
||||||
|
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<Category>()
|
||||||
|
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<List<String>> 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<dynamic> categories = data['categories'];
|
||||||
|
return categories.map((cat) => cat['catename'] as String).toList();
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load categories');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查名称
|
||||||
|
Future<CheckResult> 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<bool> 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**: 添加检测按钮和提交前确认
|
||||||
66
lib/services/stats.php
Normal file
66
lib/services/stats.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
require_once('../../includes/common.php');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stats = [];
|
||||||
|
|
||||||
|
$stats['count_category'] = $DB->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);
|
||||||
|
}
|
||||||
11
lib/services/wakelock_service.dart
Normal file
11
lib/services/wakelock_service.dart
Normal file
@@ -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<void> enable();
|
||||||
|
Future<void> disable();
|
||||||
|
Future<bool> isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
WakelockService getWakelockService() => getWakelockServiceImpl();
|
||||||
21
lib/services/wakelock_service_io.dart
Normal file
21
lib/services/wakelock_service_io.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
import 'wakelock_service.dart';
|
||||||
|
|
||||||
|
class WakelockServiceIO implements WakelockService {
|
||||||
|
@override
|
||||||
|
Future<void> enable() async {
|
||||||
|
await WakelockPlus.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> disable() async {
|
||||||
|
await WakelockPlus.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isEnabled() async {
|
||||||
|
return await WakelockPlus.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WakelockService getWakelockServiceImpl() => WakelockServiceIO();
|
||||||
20
lib/services/wakelock_service_web.dart
Normal file
20
lib/services/wakelock_service_web.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'wakelock_service.dart';
|
||||||
|
|
||||||
|
class WakelockServiceWeb implements WakelockService {
|
||||||
|
@override
|
||||||
|
Future<void> enable() async {
|
||||||
|
// Web 平台不支持屏幕常亮功能
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> disable() async {
|
||||||
|
// Web 平台不支持屏幕常亮功能
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isEnabled() async {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WakelockService getWakelockServiceImpl() => WakelockServiceWeb();
|
||||||
216
lib/services/统计API文档.md
Normal file
216
lib/services/统计API文档.md
Normal file
@@ -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<StatsData?> 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<String, dynamic> 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<String, dynamic> 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 接口
|
||||||
0
lib/views/profile/components/entire -page.dart
Normal file
0
lib/views/profile/components/entire -page.dart
Normal file
@@ -1,8 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import '../../../constants/app_constants.dart';
|
import '../../../constants/app_constants.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import '../../../services/wakelock_service.dart';
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
|
|
||||||
class PopMenu extends StatelessWidget {
|
class PopMenu extends StatelessWidget {
|
||||||
@@ -44,6 +45,16 @@ class PopMenu extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> toggleScreenWake(BuildContext context) async {
|
static Future<void> toggleScreenWake(BuildContext context) async {
|
||||||
|
// Web 平台不支持 wakelock_plus
|
||||||
|
if (kIsWeb) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(const SnackBar(content: Text('Web 平台不支持屏幕常亮功能')));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用 io.Platform 检测平台
|
// 使用 io.Platform 检测平台
|
||||||
final String osName = io.Platform.operatingSystem;
|
final String osName = io.Platform.operatingSystem;
|
||||||
@@ -51,7 +62,7 @@ class PopMenu extends StatelessWidget {
|
|||||||
print('Current platform: $osName, version: $osVersion');
|
print('Current platform: $osName, version: $osVersion');
|
||||||
|
|
||||||
// 直接尝试启用屏幕常亮
|
// 直接尝试启用屏幕常亮
|
||||||
await WakelockPlus.enable();
|
await WakelockService.instance.enable();
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
@@ -59,7 +70,7 @@ class PopMenu extends StatelessWidget {
|
|||||||
).showSnackBar(const SnackBar(content: Text('屏幕常亮已开启')));
|
).showSnackBar(const SnackBar(content: Text('屏幕常亮已开启')));
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('WakelockPlus error: $e');
|
print('WakelockService error: $e');
|
||||||
print('Stack trace: $stackTrace');
|
print('Stack trace: $stackTrace');
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
// 检查错误类型,判断是否是设备不支持
|
// 检查错误类型,判断是否是设备不支持
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import 'dart:io' as io;
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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 '../../constants/app_constants.dart';
|
||||||
import '../../controllers/history_controller.dart';
|
import '../../controllers/history_controller.dart';
|
||||||
import '../../controllers/shared_preferences_storage_controller.dart';
|
import '../../controllers/shared_preferences_storage_controller.dart';
|
||||||
|
import '../../services/wakelock_service.dart';
|
||||||
import 'history_page.dart';
|
import 'history_page.dart';
|
||||||
import 'per_card.dart';
|
import 'per_card.dart';
|
||||||
import 'settings/app_fun.dart';
|
import 'settings/app_fun.dart';
|
||||||
@@ -802,6 +803,11 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildScreenWakeItem() {
|
Widget _buildScreenWakeItem() {
|
||||||
|
// Web 平台不支持屏幕常亮功能,不显示该项
|
||||||
|
if (kIsWeb) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
// === 屏幕常亮设置项:显示图标、标题和开关 ===
|
// === 屏幕常亮设置项:显示图标、标题和开关 ===
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
@@ -912,6 +918,16 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toggleScreenWake(bool enable) async {
|
Future<void> _toggleScreenWake(bool enable) async {
|
||||||
|
// Web 平台不支持 wakelock_plus
|
||||||
|
if (kIsWeb) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(const SnackBar(content: Text('Web 平台不支持屏幕常亮功能')));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用 io.Platform 检测平台
|
// 使用 io.Platform 检测平台
|
||||||
final String osName = io.Platform.operatingSystem;
|
final String osName = io.Platform.operatingSystem;
|
||||||
@@ -919,14 +935,14 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
print('Current platform: $osName, version: $osVersion');
|
print('Current platform: $osName, version: $osVersion');
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
await WakelockPlus.enable();
|
await WakelockService.instance.enable();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('屏幕常亮已开启')));
|
).showSnackBar(const SnackBar(content: Text('屏幕常亮已开启')));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await WakelockPlus.disable();
|
await WakelockService.instance.disable();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
@@ -938,7 +954,7 @@ class _ProfilePageState extends State<ProfilePage>
|
|||||||
_isScreenWakeEnabled = enable;
|
_isScreenWakeEnabled = enable;
|
||||||
});
|
});
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
print('WakelockPlus error: $e');
|
print('WakelockService error: $e');
|
||||||
print('Stack trace: $stackTrace');
|
print('Stack trace: $stackTrace');
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// 检查错误类型,判断是否是设备不支持
|
// 检查错误类型,判断是否是设备不支持
|
||||||
|
|||||||
Reference in New Issue
Block a user