下放专业版V1.0.0的全部功能
This commit is contained in:
62
README.md
62
README.md
@@ -9,33 +9,65 @@
|
|||||||
* Copyright (c) 2018-2024 by 于小丘Yuerchu, All Rights Reserved.
|
* 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 you’re 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
|
## 安装 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:
|
你需要安装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:
|
Run the following command to start Findreve:
|
||||||
|
> Python main.py
|
||||||
|
|
||||||
```shell
|
启动后, Findreve 会在程序的根目录自动创建 SQLite 数据库,并在
|
||||||
python3 main.py
|
终端显示管理员账号密码。请注意,账号密码仅显示一次,请注意保管。
|
||||||
```
|
|
||||||
|
|
||||||
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
|
## 技术栈 Stacks
|
||||||
Findreve is available in two versions:
|
- Nicegui
|
||||||
|
|
||||||
|
## 许可证 License
|
||||||
|
此仓库的 Findreve 是社区版,完全免费,基于 GPLv3 协议。
|
||||||
|
|
||||||
Open Source Free Version: Licensed under the `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.
|
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.
|
||||||
196
admin.py
196
admin.py
@@ -2,7 +2,7 @@
|
|||||||
Author: 于小丘 海枫
|
Author: 于小丘 海枫
|
||||||
Date: 2024-10-02 15:23:34
|
Date: 2024-10-02 15:23:34
|
||||||
LastEditors: Yuerchu admin@yuxiaoqiu.cn
|
LastEditors: Yuerchu admin@yuxiaoqiu.cn
|
||||||
LastEditTime: 2024-11-29 20:43:28
|
LastEditTime: 2024-12-14 20:03:49
|
||||||
FilePath: /Findreve/admin.py
|
FilePath: /Findreve/admin.py
|
||||||
Description: Findreve 后台管理 admin
|
Description: Findreve 后台管理 admin
|
||||||
|
|
||||||
@@ -12,12 +12,18 @@ Copyright (c) 2018-2024 by 于小丘Yuerchu, All Rights Reserved.
|
|||||||
from nicegui import ui, app
|
from nicegui import ui, app
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
import traceback
|
||||||
import model
|
import model
|
||||||
|
import asyncio
|
||||||
import qrcode
|
import qrcode
|
||||||
import base64
|
import base64
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
from tool import *
|
from tool import *
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
|
||||||
|
|
||||||
def create(unitTest: bool = False):
|
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(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')
|
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:
|
with ui.left_drawer() as left_drawer:
|
||||||
ui.image('https://bing.img.run/1366x768.php').classes('w-full')
|
ui.image('https://bing.img.run/1366x768.php').classes('w-full')
|
||||||
with ui.row(align_items='center').classes('w-full'):
|
with ui.row(align_items='center').classes('w-full'):
|
||||||
ui.label('Findreve').classes('text-2xl text-bold')
|
ui.label('Findreve').classes('text-2xl text-bold')
|
||||||
ui.chip('Pro').classes('text-xs -left-3').props('floating outline')
|
ui.chip('Pro').classes('text-xs -left-3').props('floating outline')
|
||||||
ui.label("本地模式无需授权").classes('text-gray-600 -mt-3')
|
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')) \
|
ui.button('物品 & 库存', icon='settings', on_click=lambda: tabs.set_value('item')) \
|
||||||
.classes('w-full').props('flat no-caps')
|
.classes('w-full').props('flat no-caps')
|
||||||
ui.button('产品 & 授权', icon='settings', on_click=lambda: tabs.set_value('auth')) \
|
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')) \
|
ui.button('关于 & 反馈', icon='settings', on_click=lambda: tabs.set_value('about')) \
|
||||||
.classes('w-full').props('flat no-caps')
|
.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'):
|
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():
|
async def addObject():
|
||||||
dialogAddObjectIcon.disable()
|
dialogAddObjectIcon.disable()
|
||||||
@@ -146,10 +193,85 @@ def create(unitTest: bool = False):
|
|||||||
ui.button("返回", on_click=addObjectDialog.close) \
|
ui.button("返回", on_click=addObjectDialog.close) \
|
||||||
.classes('w-full').props('flat rounded')
|
.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',
|
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()]
|
'find_ip', 'create_at', 'lost_at'], obj)) for obj in await model.Database().get_object()]
|
||||||
status_map = {'ok': '正常', 'lost': '丢失'}
|
status_map = {'ok': '正常', 'lost': '丢失'}
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
obj['status'] = status_map.get(obj['status'], obj['status'])
|
obj['status'] = status_map.get(obj['status'], obj['status'])
|
||||||
@@ -157,37 +279,46 @@ def create(unitTest: bool = False):
|
|||||||
obj['create_at'] = format_time_diff(obj['create_at'])
|
obj['create_at'] = format_time_diff(obj['create_at'])
|
||||||
if obj['lost_at']:
|
if obj['lost_at']:
|
||||||
obj['lost_at'] = format_time_diff(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)
|
object_table.update_rows(objects)
|
||||||
if tips:
|
if tips:
|
||||||
ui.notify('刷新成功')
|
ui.notify('刷新成功')
|
||||||
|
|
||||||
# 获取所有物品
|
object_columns = [
|
||||||
objects = [dict(zip(['id', 'key', 'name', 'icon', 'status', 'phone', 'context',
|
{'name': 'id', 'label': '内部ID', 'field': 'id', 'required': True, 'align': 'left'},
|
||||||
'find_ip', 'create_at', 'lost_at'], obj)) for obj in await model.Database().get_object()]
|
{'name': 'key', 'label': '物品Key', 'field': 'key', 'required': True, 'align': 'left'},
|
||||||
status_map = {'ok': '正常', 'lost': '丢失'}
|
{'name': 'name', 'label': '物品名称', 'field': 'name', 'required': True, 'align': 'left'},
|
||||||
for obj in objects:
|
{'name': 'icon', 'label': '物品图标', 'field': 'icon', 'required': True, 'align': 'left'},
|
||||||
obj['status'] = status_map.get(obj['status'], obj['status'])
|
{'name': 'status', 'label': '物品状态', 'field': 'status', 'required': True, 'align': 'left'},
|
||||||
if obj['create_at']:
|
{'name': 'phone', 'label': '物品绑定手机', 'field': 'phone', 'required': True, 'align': 'left'},
|
||||||
obj['create_at'] = format_time_diff(obj['create_at'])
|
{'name': 'context', 'label': '丢失描述', 'field': 'context', 'required': True, 'align': 'left'},
|
||||||
if obj['lost_at']:
|
{'name': 'find_ip', 'label': '物品发现IP', 'field': 'find_ip', 'required': True, 'align': 'left'},
|
||||||
obj['lost_at'] = format_time_diff(obj['lost_at'])
|
{'name': 'create_at', 'label': '物品创建时间', 'field': 'create_at', 'required': True, 'align': 'left'},
|
||||||
object_columns=[
|
{'name': 'lost_at', 'label': '物品丢失时间', 'field': 'lost_at', 'required': True, 'align': 'left'}
|
||||||
{'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'},
|
objects = await fetch_and_process_objects()
|
||||||
{'name': 'icon', 'label': '物品图标', 'field': 'icon', 'required': True, 'align': 'left'},
|
|
||||||
{'name': 'phone', 'label': '物品绑定手机', 'field': 'phone', 'required': True, 'align': 'left'},
|
|
||||||
{'name': 'create_at', 'label': '物品创建时间', 'field': 'create_at', 'required': True, 'align': 'left'}
|
|
||||||
]
|
|
||||||
object_table = ui.table(
|
object_table = ui.table(
|
||||||
title='物品 & 库存',
|
title='物品 & 库存',
|
||||||
row_key='id',
|
row_key='id',
|
||||||
pagination=10,
|
pagination=10,
|
||||||
selection='single',
|
selection='single',
|
||||||
columns=object_columns,
|
columns=object_columns,
|
||||||
rows=objects
|
rows=objects,
|
||||||
|
on_select=lambda: objectTableOnClick()
|
||||||
).classes('w-full').props('flat')
|
).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'):
|
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:
|
with ui.page_sticky(x_offset=24, y_offset=24) as addObjectFAB:
|
||||||
ui.button(icon='add', on_click=addObjectDialog.open) \
|
ui.button(icon='add', on_click=addObjectDialog.open) \
|
||||||
.props('fab')
|
.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 授权
|
# Findreve 授权
|
||||||
with ui.tab_panel('auth'):
|
with ui.tab_panel('auth'):
|
||||||
@@ -231,9 +372,10 @@ def create(unitTest: bool = False):
|
|||||||
|
|
||||||
# 关于 Findreve
|
# 关于 Findreve
|
||||||
with ui.tab_panel('about'):
|
with ui.tab_panel('about'):
|
||||||
ui.label('关于 Findreve').classes('text-2xl text-bold')
|
ui.label('关于 Findreve')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ui.label('还在做')
|
|
||||||
|
|
||||||
if __name__ in {"__main__", "__mp_main__"}:
|
if __name__ in {"__main__", "__mp_main__"}:
|
||||||
create(unitTest=True)
|
create(unitTest=True)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
BIN
static/Findreve.png
Normal file
BIN
static/Findreve.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Reference in New Issue
Block a user