From 7b4cef5d35cd6a0fbe26b4d74ab2621041cd8df9 Mon Sep 17 00:00:00 2001 From: Yuerchu Date: Tue, 15 Jul 2025 12:00:59 +0800 Subject: [PATCH] V2.0.0-alpha3 2025-07-15 --- CHANGELOG.md | 9 + README.md | 3 + frontend/src/components/admin/AboutSystem.vue | 79 ++ frontend/src/components/admin/Dashboard.vue | 113 +++ .../src/components/admin/ItemsManagement.vue | 609 +++++++++++++++ .../src/components/admin/UserSettings.vue | 93 +++ frontend/src/views/Admin.vue | 708 +----------------- model/database.py | 7 +- 8 files changed, 927 insertions(+), 694 deletions(-) create mode 100644 frontend/src/components/admin/AboutSystem.vue create mode 100644 frontend/src/components/admin/Dashboard.vue create mode 100644 frontend/src/components/admin/ItemsManagement.vue create mode 100644 frontend/src/components/admin/UserSettings.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c7778..a4f55c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Findreve 更新日志 +## V2.0.0-alpha3 2025-07-15 +> 注意,这是一个非常不稳定的早期测试版,可能存在着非常多的问题。请勿在生产环境中使用。 + +- 增强 管理员接口的安全性 + +- 优化 前端后台代码的可维护性 + +- 修复 初次启动时不显示管理员账号信息 + ## V2.0.0-alpha2 2025-05-13 > 注意,这是一个非常不稳定的早期测试版,可能存在着非常多的问题。请勿在生产环境中使用。 diff --git a/README.md b/README.md index 17e9579..0047644 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Findreve 是一款强大且直观的解决方案,旨在帮助您管理个人 每个物品都会被分配一个 `唯一 ID` ,并生成一个 `安全链接` ,可轻松嵌入到 `二维码` 或 `NFC 标签` 中。 当扫描该代码时,会将拾得者引导至一个专门的网页,上面显示物品详情和您的联系信息,既保障隐私又便于沟通。 无论您是在管理个人物品还是专业资产,Findreve 都能以高效、简便的方式弥合丢失与找回之间的距离。 +同时,Findreve 还是一个模板项目,对新人开发者较为友好,你可以通过 Findreve 学习到后端的路由操作、数据与数据库管理、 +JWT 与 OAuth2 认证鉴权,还有基于 Vue 前端的各种支持。它还是一个脚手架,能让你通过这个脚手架二改出功能更加完整甚至丰富的系统。 Findreve is a powerful and intuitive solution, an enhanced version of Findreve, designed to help you manage your personal items and ensure their safe recovery in case of loss. Each @@ -59,6 +61,7 @@ chmod +x ./findreve 启动后, Findreve 会在程序的根目录自动创建 SQLite 数据库,并在 终端显示管理员账号密码。请注意,账号密码仅显示一次,请注意保管。 +账号默认为 `admin@yuxiaoqiu.cn` Upon launch, Findreve will create a SQLite database in the project's root directory and display the administrator's account and password in the console. diff --git a/frontend/src/components/admin/AboutSystem.vue b/frontend/src/components/admin/AboutSystem.vue new file mode 100644 index 0000000..041f7f6 --- /dev/null +++ b/frontend/src/components/admin/AboutSystem.vue @@ -0,0 +1,79 @@ + + + diff --git a/frontend/src/components/admin/Dashboard.vue b/frontend/src/components/admin/Dashboard.vue new file mode 100644 index 0000000..4a0d6c1 --- /dev/null +++ b/frontend/src/components/admin/Dashboard.vue @@ -0,0 +1,113 @@ + + + diff --git a/frontend/src/components/admin/ItemsManagement.vue b/frontend/src/components/admin/ItemsManagement.vue new file mode 100644 index 0000000..831f343 --- /dev/null +++ b/frontend/src/components/admin/ItemsManagement.vue @@ -0,0 +1,609 @@ + + + + + diff --git a/frontend/src/components/admin/UserSettings.vue b/frontend/src/components/admin/UserSettings.vue new file mode 100644 index 0000000..f1ad71d --- /dev/null +++ b/frontend/src/components/admin/UserSettings.vue @@ -0,0 +1,93 @@ + + + diff --git a/frontend/src/views/Admin.vue b/frontend/src/views/Admin.vue index f4031a5..d28c8a1 100644 --- a/frontend/src/views/Admin.vue +++ b/frontend/src/views/Admin.vue @@ -51,340 +51,13 @@ - -
-

仪表盘

- - - - -
所有物品
-
{{ itemStats.total }}
- -
-
-
- - - -
正常物品
-
{{ itemStats.normal }}
- -
-
-
- - - -
丢失物品
-
{{ itemStats.lost }}
- -
-
-
- - - -
扫描次数
-
{{ itemStats.scans }}
- -
-
-
-
-
- - -
-
-

物品管理

- - 添加物品 - -
- - - - - - - - - - - - - 刷新数据 - 重置筛选 - - - - - - - - - - - - - - - - -
- -
-

用户设置

- - - - - - -

其他设置功能正在开发中...

-
- -
-

关于 Findreve

- - -

- Findreve 是一款强大且直观的解决方案,旨在帮助您管理个人物品,并确保丢失后能够安全找回。 - 每个物品都会被分配一个唯一 ID,并生成一个安全链接,可轻松嵌入到二维码或 NFC 标签中。 - 当扫描该代码时,会将拾得者引导至一个专门的网页,上面显示物品详情和您的联系信息,既保障隐私又便于沟通。 -

-

- 无论您是在管理个人物品还是专业资产,Findreve 都能以高效、简便的方式弥合丢失与找回之间的距离。 -

- -
版本: 1.0.0
-
-
-
+ + + + +
- - - - - - {{ editItem.id ? '编辑物品' : '添加新物品' }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 取消 - - 保存 - - - - - - - - - 确认删除 - -

您确定要删除物品 "{{ deleteItem?.name || '' }}" 吗?

-

此操作不可逆,删除后将无法恢复。

-
- - - 取消 - - 确认删除 - - -
-
- - - - - 物品二维码 - -
-

{{ selectedItem.name }}

-

ID: {{ selectedItem.key }}

- -
- QR Code -
-

请使用屏幕截图或保存图片功能保存二维码

-
-
- - - 关闭 - -
-
@@ -398,97 +71,25 @@ * 此组件还包含仪表盘视图,显示物品统计信息和最近活动。 */ import apiService from '@/services/api_service'; -import CacheStatus from '@/components/CacheStatus.vue'; +import Dashboard from '@/components/admin/Dashboard.vue'; +import ItemsManagement from '@/components/admin/ItemsManagement.vue'; +import UserSettings from '@/components/admin/UserSettings.vue'; +import AboutSystem from '@/components/admin/AboutSystem.vue'; export default { name: 'AdminView', components: { - CacheStatus + Dashboard, + ItemsManagement, + UserSettings, + AboutSystem }, data() { return { // 界面控制 drawer: false, currentTab: 'dashboard', - loading: false, - search: '', - statusFilter: 'all', - - // 物品管理 - items: [], - editItem: { - id: null, - key: '', - name: '', - icon: '', - phone: '', - status: 'ok', - context: '' - }, - defaultItem: { - id: null, - key: '', - name: '', - icon: '', - phone: '', - status: 'ok', - context: '' - }, - - // 对话框控制 - itemDialog: false, - deleteDialog: false, - qrDialog: false, - saving: false, - deleting: false, - formValid: false, - - // 选中的物品和删除项 - selectedItem: null, - deleteItem: null, - - // 表格配置 - headers: [ - { title: 'ID', key: 'id', sortable: true }, - { title: '物品名称', key: 'name', sortable: true }, - { title: '标识码', key: 'key', sortable: true }, - { title: '状态', key: 'status', sortable: true }, - { title: '创建时间', key: 'created_at', sortable: true }, - { title: '操作', key: 'actions', sortable: false } - ], - - statusOptions: [ - { title: '全部状态', value: 'all' }, - { title: '正常', value: 'ok' }, - { title: '丢失', value: 'lost' } - ], - - // 仪表盘数据 - itemStats: { - total: 0, - normal: 0, - lost: 0, - scans: 0 - } - } - }, - - computed: { - /** - * 过滤后的物品列表 - * - * 根据搜索文本和状态筛选条件过滤物品列表 - * @returns {Array} 过滤后的物品数组 - */ - filteredItems() { - let result = [...this.items]; - - // 应用状态筛选 - if (this.statusFilter !== 'all') { - result = result.filter(item => item.status === this.statusFilter); - } - - return result; + items: [], // 保存物品数据以便共享给子组件 } }, @@ -518,17 +119,14 @@ export default { /** * 获取物品列表 * - * 从API获取所有物品数据并更新统计信息 + * 从API获取所有物品数据 */ async fetchItems() { try { - this.loading = true; - const data = await apiService.get('/api/admin/items'); if (data.code === 0 && Array.isArray(data.data)) { this.items = data.data; - this.updateStats(); } else { throw new Error(data.msg || '获取物品列表失败'); } @@ -540,278 +138,9 @@ export default { message: error.message || '加载物品数据失败' }); }); - } finally { - this.loading = false; } }, - /** - * 更新统计信息 - * - * 根据物品列表计算各种统计数据 - */ - updateStats() { - // 计算物品总数 - this.itemStats.total = this.items.length; - - // 计算正常和丢失物品数量 - this.itemStats.normal = this.items.filter(item => item.status === 'ok').length; - this.itemStats.lost = this.items.filter(item => item.status === 'lost').length; - - // 假设扫描次数是从物品中累计的一个属性,如果没有可以模拟一个值 - // this.itemStats.scans = this.items.reduce((sum, item) => sum + (item.scans || 0), 0) || 42; - }, - - /** - * 计算百分比 - * - * @param {string} type - 物品类型(normal或lost) - * @returns {number} 百分比值 - */ - getPercentage(type) { - if (this.itemStats.total === 0) return 0; - return Math.round((this.itemStats[type] / this.itemStats.total) * 100); - }, - - /** - * 打开物品对话框 - * - * @param {Object|null} item - 要编辑的物品,为null时表示添加新物品 - */ - openItemDialog(item = null) { - if (item) { - this.editItem = JSON.parse(JSON.stringify(item)); // 深拷贝 - } else { - this.editItem = JSON.parse(JSON.stringify(this.defaultItem)); - // 为新物品生成一个随机标识码 - this.editItem.key = this.generateRandomKey(); - } - this.$nextTick(() => { - if (this.$refs.itemForm) { - this.$refs.itemForm.resetValidation(); - } - }); - this.itemDialog = true; - }, - - /** - * 保存物品 - * - * 根据是否有ID决定是添加新物品还是更新现有物品 - */ - async saveItem() { - if (!this.formValid) return; - - try { - this.saving = true; - let data; - - if (this.editItem.id) { - // 更新现有物品 - const params = new URLSearchParams(); - const { id, key, name, icon, phone, status, context } = this.editItem; - - params.append('id', id); - params.append('key', key); - params.append('name', name); - params.append('icon', icon || ''); - params.append('phone', phone); - params.append('status', status); - - // 只有在状态为lost且有context时,才添加context参数 - if (status === 'lost' && context) { - params.append('context', context); - } - - data = await apiService.patch(`/api/admin/items?${params.toString()}`, ''); - - } else { - // 添加新物品 - const params = new URLSearchParams(); - const { key, name, icon, phone } = this.editItem; - - params.append('key', key); - params.append('name', name); - params.append('icon', icon || ''); - params.append('phone', phone); - - data = await apiService.post(`/api/admin/items?${params.toString()}`, ''); - } - - if (data.code !== 0) { - throw new Error(data.msg || '保存物品失败'); - } - - this.$nextTick(() => { - this.$root.$emit('show-toast', { - color: 'success', - message: this.editItem.id ? '物品更新成功' : '物品添加成功' - }); - }); - - this.itemDialog = false; - this.fetchItems(); // 刷新物品列表 - - } catch (error) { - console.error('保存物品错误:', error); - this.$nextTick(() => { - this.$root.$emit('show-toast', { - color: 'error', - message: error.message || '保存物品失败' - }); - }); - } finally { - this.saving = false; - } - }, - - /** - * 确认删除物品 - * - * @param {Object} item - 要删除的物品 - */ - confirmDelete(item) { - this.deleteItem = item; - this.deleteDialog = true; - }, - - /** - * 确认删除物品 - */ - async deleteItemConfirm() { - if (!this.deleteItem || !this.deleteItem.id) return; - - try { - this.deleting = true; - - const data = await apiService.delete(`/api/admin/items?id=${encodeURIComponent(this.deleteItem.id)}`); - - if (data.code !== 0) { - throw new Error(data.msg || '删除物品失败'); - } - - this.$nextTick(() => { - this.$root.$emit('show-toast', { - color: 'success', - message: '物品已成功删除' - }); - }); - - this.deleteDialog = false; - this.fetchItems(); // 刷新物品列表 - - } catch (error) { - console.error('删除物品错误:', error); - this.$nextTick(() => { - this.$root.$emit('show-toast', { - color: 'error', - message: error.message || '删除物品失败' - }); - }); - } finally { - this.deleting = false; - } - }, - - /** - * 显示二维码 - * - * @param {Object} item - 要显示二维码的物品 - */ - showQRCode(item) { - this.selectedItem = item; - this.qrDialog = true; - }, - - /** - * 获取二维码URL - * - * @param {string} key - 物品标识码 - * @returns {string} 二维码图片URL - */ - getQRCodeUrl(key) { - // 使用QR Server API生成二维码 - const currentUrl = window.location.origin; - const foundUrl = `${currentUrl}/found?key=${encodeURIComponent(key)}`; - return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(foundUrl)}`; - }, - - /** - * 生成随机标识码 - * - * @returns {string} 随机生成的标识码 - */ - generateRandomKey() { - // 生成一个8位的随机字母数字组合 - const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let i = 0; i < 8; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; - }, - - /** - * 获取状态对应的颜色 - * - * @param {string} status - 物品状态 - * @returns {string} 对应的颜色名称 - */ - getStatusColor(status) { - const statusMap = { - ok: "success", - lost: "error", - default: "grey" - }; - return statusMap[status] || statusMap.default; - }, - - /** - * 获取状态对应的文本 - * - * @param {string} status - 物品状态 - * @returns {string} 对应的状态文本 - */ - getStatusText(status) { - const statusMap = { - ok: "正常", - lost: "丢失", - default: "未知" - }; - return statusMap[status] || statusMap.default; - }, - - /** - * 格式化日期显示 - * - * @param {string} create_time - 日期字符串 - * @returns {string} 格式化的日期文本 - */ - formatDate(dateStr) { - if (!dateStr) return "未知时间"; - - try { - const date = new Date(dateStr); - return new Intl.DateTimeFormat('zh-CN', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit' - }).format(date); - } catch (e) { - return dateStr; - } - }, - - /** - * 重置所有筛选条件 - */ - resetFilters() { - this.search = ''; - this.statusFilter = 'all'; - }, - /** * 退出登录 */ @@ -834,11 +163,4 @@ export default { min-height: 100vh; padding: 0; } - -/* 确保数据表格在移动设备上响应式滚动 */ -@media (max-width: 768px) { - .v-data-table { - overflow-x: auto; - } -} \ No newline at end of file diff --git a/model/database.py b/model/database.py index 260719a..ddc7e62 100644 --- a/model/database.py +++ b/model/database.py @@ -17,7 +17,12 @@ from typing import Optional # 数据库类 class Database: - def __init__(self, db_path: str = "data.db"): + + # Database 初始化方法 + def __init__( + self, # self 用于引用类的实例 + db_path: str = "data.db" # db_path 数据库文件路径,默认为 data.db + ): self.db_path = db_path async def init_db(self):