下放专业版V1.0.0的全部功能

This commit is contained in:
2025-01-06 12:54:57 +08:00
parent 3da512d569
commit e7d592639d
4 changed files with 217 additions and 45 deletions

View File

@@ -9,33 +9,65 @@
* Copyright (c) 2018-2024 by 于小丘Yuerchu, All Rights Reserved.
-->
<p style="text-align: center; font-size: 32px;">Findreve</p>
<h1 align="center">
<br>
<a href="https://find.yxqi.cn" alt="logo" ><img src="./static/Findreve.png" width="150"/></a>
<br>
Findreve (Community)
<br>
</h1>
<h4 align="center">标记、追踪与找回 —— 就这么简单。</h4>
<h4 align="center">Track, Tag, and Retrieve Simplifying Item Recovery.</h4>
***
<p align="center">
<a href="https://www.yxqi.cn">Homepage</a> •
<a href="https://find.yxqi.cn">Demo</a> •
<a href="https://findreve.yxqi.cn">Documents</a> •
<a href="https://github.com/Findreve/Findreve/releases">Download</a> •
<a href="#License">License</a>
</p>
<p style="text-align: center;">Track, Tag, and Retrieve Simplifying Item Recovery.</p>
## 介绍 Introduction
Findreve 是一款强大且直观的解决方案,旨在帮助您管理个人物品,并确保丢失后能够安全找回。每个物品都会被分配一个 `唯一 ID` ,并生成一个 `安全链接` ,可轻松嵌入到 `二维码``NFC 标签` 中。当扫描该代码时会将拾得者引导至一个专门的网页上面显示物品详情和您的联系信息既保障隐私又便于沟通。无论您是在管理个人物品还是专业资产Findreve 都能以高效、简便的方式弥合丢失与找回之间的距离。
Findreve is a powerful yet intuitive solution designed to help you manage your belongings and ensure their safe return if lost. Each item is assigned a unique ID and generates a secure link, easily embedded into a QR code or NFC tag. When scanned, the code directs finders to a dedicated webpage displaying item details and your contact information, ensuring privacy and ease of communication. Whether youre managing personal belongings or professional assets, Findreve bridges the gap between lost and found with efficiency and simplicity.
Findreve-Pro 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
item is assigned a "unique ID" and a "secure link" is generated, which can be easily embedded
into a "QR code" or "NFC tag". When this code is scanned, the finder is directed to a
dedicated webpage displaying the item's details and your contact information, ensuring both
privacy and ease of communication. Whether you are managing personal items or professional
assets, Findreve bridges the gap between loss and recovery in an efficient and simple way.
## Install
`Findreve` is a Python-based application. You need to have Python 3.8 or higher installed on your server. Then, clone this repository to your server and install the required dependencies:
## 安装 Install
你需要安装Python 3.8 以上的版本。然后clone 本仓库到您的服务器并解压,然后安装下面的依赖:
NiceGUI: `pip3 install nicegui==2.5.0`
You need to have Python 3.8 or higher installed on your server. Then, clone this repository
to your server and install the required dependencies:
aiosqlite: `pip3 install aiosqlite`
- **NiceGUI**: `pip install nicegui==2.5.0`
- **aiosqlite**: `pip install aiosqlite`
- **QRCode**: `pip install QRCode`
## 启动 Launch
使用下面的命令来启动 Findreve:
## Launch
Run the following command to start Findreve:
> Python main.py
```shell
python3 main.py
```
启动后, Findreve 会在程序的根目录自动创建 SQLite 数据库,并在
终端显示管理员账号密码。请注意,账号密码仅显示一次,请注意保管。
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.
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.
## License
Findreve is available in two versions:
## 技术栈 Stacks
- Nicegui
## 许可证 License
此仓库的 Findreve 是社区版,完全免费,基于 GPLv3 协议。
Open Source Free Version: Licensed under the `GPLv3`.
基于赞助的专业版:基于您的赞助,您可获得附加功能和源代码的版本,允许进一步开发用于个人或内部使用。然而,不允许重新分发修改后的或原始的源代码。
Donation-Based Premium Version: By making a donation, you can access a version with additional features and source code, which allows further development for personal or internal use. However, redistribution of the modified or original source code is not permitted.

178
admin.py
View File

@@ -2,7 +2,7 @@
Author: 于小丘 海枫
Date: 2024-10-02 15:23:34
LastEditors: Yuerchu admin@yuxiaoqiu.cn
LastEditTime: 2024-11-29 20:43:28
LastEditTime: 2024-12-14 20:03:49
FilePath: /Findreve/admin.py
Description: Findreve 后台管理 admin
@@ -12,12 +12,18 @@ Copyright (c) 2018-2024 by 于小丘Yuerchu, All Rights Reserved.
from nicegui import ui, app
from typing import Optional
from typing import Dict
import traceback
import model
import asyncio
import qrcode
import base64
from io import BytesIO
from PIL import Image
from fastapi import Request
import json
import requests
from tool import *
from fastapi.responses import RedirectResponse
def create(unitTest: bool = False):
@@ -39,13 +45,23 @@ def create(unitTest: bool = False):
ui.button(icon='menu', on_click=lambda: left_drawer.toggle()).props('flat color=white round')
ui.button(text="Findreve 仪表盘" if not unitTest else 'Findreve 仪表盘 单测模式').classes('text-lg').props('flat color=white no-caps')
siteDomain = request.base_url.hostname
with ui.left_drawer() as left_drawer:
ui.image('https://bing.img.run/1366x768.php').classes('w-full')
with ui.row(align_items='center').classes('w-full'):
ui.label('Findreve').classes('text-2xl text-bold')
ui.chip('Pro').classes('text-xs -left-3').props('floating outline')
if siteDomain == "127.0.0.1" or siteDomain == "localhost":
ui.label("本地模式无需授权").classes('text-gray-600 -mt-3')
elif not await model.Database().get_setting('License'):
ui.label("未授权,请立即前往授权").classes('text-red-600 -mt-3')
elif await model.Database().get_setting('License'):
ui.label("正版授权,希望是一万年").classes('text-green-600 -mt-3')
else:
ui.label("授权异常,请联系作者").classes('text-red-600 -mt-3')
ui.button('首页 & 信息', icon='fingerprint', on_click=lambda: tabs.set_value('main_page')) \
.classes('w-full').props('flat no-caps')
ui.button('物品 & 库存', icon='settings', on_click=lambda: tabs.set_value('item')) \
.classes('w-full').props('flat no-caps')
ui.button('产品 & 授权', icon='settings', on_click=lambda: tabs.set_value('auth')) \
@@ -53,11 +69,42 @@ def create(unitTest: bool = False):
ui.button('关于 & 反馈', icon='settings', on_click=lambda: tabs.set_value('about')) \
.classes('w-full').props('flat no-caps')
with ui.tab_panels(tabs, value='item').classes('w-full').props('vertical'):
with ui.tab_panels(tabs, value='main_page').classes('w-full').props('vertical'):
# 站点一览
with ui.tab_panel('main_page'):
ui.label('首页配置').classes('text-2xl text-bold')
ui.label('暂不支持请直接修改main_page.py').classes('text-md text-gray-600').classes('w-full')
# 物品一览
with ui.tab_panel('item'):
# 列表选择函数
async def objectTableOnClick():
try:
status = str(object_table.selected[0]['status'])
except:
# 当物品列表未选中,显示添加物品按钮,其他按钮不显示
addObjectFAB.set_visibility(True)
lostObjectFAB.set_visibility(False)
findObjectFAB.set_visibility(False)
return
if status == "正常":
# 选中正常物品,显示丢失按钮
addObjectFAB.set_visibility(False)
lostObjectFAB.set_visibility(True)
findObjectFAB.set_visibility(False)
elif status == "丢失":
# 选中丢失物品,显示发现按钮
addObjectFAB.set_visibility(False)
lostObjectFAB.set_visibility(False)
findObjectFAB.set_visibility(True)
else:
# 选中其他状态,隐藏所有按钮
addObjectFAB.set_visibility(False)
lostObjectFAB.set_visibility(False)
findObjectFAB.set_visibility(False)
# 添加物品
async def addObject():
dialogAddObjectIcon.disable()
@@ -146,7 +193,82 @@ def create(unitTest: bool = False):
ui.button("返回", on_click=addObjectDialog.close) \
.classes('w-full').props('flat rounded')
async def reloadTable(tips: bool = True):
async def lostObject():
try:
# 获取选中物品
object_id = object_table.selected[0]['id']
await model.Database().update_object(id=object_id, status='lost')
# 如果设置了留言,则更新留言
if lostReason.value != "":
await model.Database().update_object(id=object_id, context=lostReason.value)
await model.Database().update_object(id=object_id, lost_at=datetime.now())
except Exception as e:
ui.notify(str(e), color='negative')
else:
ui.notify('设置丢失成功', color='positive')
# 刷新表格
await reloadTable(tips=False)
lostObjectDialog.close()
# 将FAB设置为正常
addObjectFAB.set_visibility(True)
lostObjectFAB.set_visibility(False)
findObjectFAB.set_visibility(False)
# 设置物品丢失对话框
with ui.dialog() as lostObjectDialog, ui.card().style('width: 90%; max-width: 500px'):
ui.button(icon='gpp_bad', color='red').props('outline round').classes('mx-auto w-auto shadow-sm w-fill')
ui.label('设置物品丢失').classes('w-full text-h5 text-center')
ui.label('确定要设置这个物品为丢失吗?')
ui.html('设置为丢失以后,<b>你的电话号码将会被完整地显示在物品页面</b>(不是“*** **** 8888”而是“188 8888 8888”),以供拾到者能够记下你的电话号码。此外,在页面底部将会显示一个按钮,这个按钮能够一键拨打预先设置好的电话。')
lostReason = ui.input('物主留言') \
.classes('block w-full text-gray-900')
lostReasonTips = ui.label('非必填,但建议填写,以方便拾到者联系你').classes('-mt-3')
async def handle_lost_object():
await lostObject()
ui.button("确认提交", color='red', on_click=handle_lost_object) \
.classes('items-center w-full').props('rounded')
ui.button("返回", on_click=lostObjectDialog.close) \
.classes('w-full').props('flat rounded')
async def findObject():
try:
object_id = object_table.selected[0]['id']
await model.Database().update_object(id=object_id, status='ok')
await model.Database().update_object(id=object_id, context=None)
await model.Database().update_object(id=object_id, find_ip=None)
await model.Database().update_object(id=object_id, lost_at=None)
except Exception as e:
ui.notify(str(e), color='negative')
else:
ui.notify('解除丢失成功', color='positive')
# 刷新表格
await reloadTable(tips=False)
findObjectDialog.close()
# 将FAB设置为正常
addObjectFAB.set_visibility(True)
lostObjectFAB.set_visibility(False)
findObjectFAB.set_visibility(False)
# 解除丢失对话框
with ui.dialog() as findObjectDialog, ui.card().style('width: 90%; max-width: 500px'):
ui.button(icon='remove_moderator').props('outline round').classes('mx-auto w-auto shadow-sm w-fill')
ui.label('解除丢失').classes('w-full text-h5 text-center')
ui.label('确定物品已经找回了吗?')
async def handle_find_object():
await findObject()
ui.button("确认提交", on_click=handle_find_object) \
.classes('items-center w-full').props('rounded')
ui.button("返回") \
.classes('w-full').props('flat rounded')
async def fetch_and_process_objects():
# 获取所有物品
objects = [dict(zip(['id', 'key', 'name', 'icon', 'status', 'phone', 'context',
'find_ip', 'create_at', 'lost_at'], obj)) for obj in await model.Database().get_object()]
@@ -157,37 +279,46 @@ def create(unitTest: bool = False):
obj['create_at'] = format_time_diff(obj['create_at'])
if obj['lost_at']:
obj['lost_at'] = format_time_diff(obj['lost_at'])
return objects
async def reloadTable(tips: bool = True):
objects = await fetch_and_process_objects()
object_table.update_rows(objects)
if tips:
ui.notify('刷新成功')
# 获取所有物品
objects = [dict(zip(['id', 'key', 'name', 'icon', 'status', 'phone', 'context',
'find_ip', 'create_at', 'lost_at'], obj)) for obj in await model.Database().get_object()]
status_map = {'ok': '正常', 'lost': '丢失'}
for obj in objects:
obj['status'] = status_map.get(obj['status'], obj['status'])
if obj['create_at']:
obj['create_at'] = format_time_diff(obj['create_at'])
if obj['lost_at']:
obj['lost_at'] = format_time_diff(obj['lost_at'])
object_columns=[
object_columns = [
{'name': 'id', 'label': '内部ID', 'field': 'id', 'required': True, 'align': 'left'},
{'name': 'key', 'label': '物品Key', 'field': 'key', 'required': True, 'align': 'left'},
{'name': 'name', 'label': '物品名称', 'field': 'name', 'required': True, 'align': 'left'},
{'name': 'icon', 'label': '物品图标', 'field': 'icon', 'required': True, 'align': 'left'},
{'name': 'status', 'label': '物品状态', 'field': 'status', 'required': True, 'align': 'left'},
{'name': 'phone', 'label': '物品绑定手机', 'field': 'phone', 'required': True, 'align': 'left'},
{'name': 'create_at', 'label': '物品创建时间', 'field': 'create_at', 'required': True, 'align': 'left'}
{'name': 'context', 'label': '丢失描述', 'field': 'context', 'required': True, 'align': 'left'},
{'name': 'find_ip', 'label': '物品发现IP', 'field': 'find_ip', 'required': True, 'align': 'left'},
{'name': 'create_at', 'label': '物品创建时间', 'field': 'create_at', 'required': True, 'align': 'left'},
{'name': 'lost_at', 'label': '物品丢失时间', 'field': 'lost_at', 'required': True, 'align': 'left'}
]
objects = await fetch_and_process_objects()
object_table = ui.table(
title='物品 & 库存',
row_key='id',
pagination=10,
selection='single',
columns=object_columns,
rows=objects
rows=objects,
on_select=lambda: objectTableOnClick()
).classes('w-full').props('flat')
object_table.add_slot('body-cell-status', '''
<q-td key="status" :props="props">
<q-badge :color="props.value === '正常' ? 'green' : 'red'">
{{ props.value }}
</q-badge>
</q-td>
''')
with object_table.add_slot('top-right'):
@@ -206,6 +337,16 @@ def create(unitTest: bool = False):
with ui.page_sticky(x_offset=24, y_offset=24) as addObjectFAB:
ui.button(icon='add', on_click=addObjectDialog.open) \
.props('fab')
with ui.page_sticky(x_offset=24, y_offset=24) as lostObjectFAB:
ui.button(icon='gpp_bad', color='red', on_click=lostObjectDialog.open) \
.props('fab')
# 单独拉出来默认隐藏,防止无法再设置其显示
lostObjectFAB.set_visibility(False)
with ui.page_sticky(x_offset=24, y_offset=24) as findObjectFAB:
ui.button(icon='remove_moderator', on_click=findObjectDialog.open) \
.props('fab')
# 单独拉出来默认隐藏,防止无法再设置其显示
findObjectFAB.set_visibility(False)
# Findreve 授权
with ui.tab_panel('auth'):
@@ -231,9 +372,10 @@ def create(unitTest: bool = False):
# 关于 Findreve
with ui.tab_panel('about'):
ui.label('关于 Findreve').classes('text-2xl text-bold')
ui.label('关于 Findreve')
ui.label('还在做')
if __name__ in {"__main__", "__mp_main__"}:
create(unitTest=True)

File diff suppressed because one or more lines are too long

BIN
static/Findreve.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB