This commit is contained in:
2026-08-24 13:45:14 +08:00
parent 75d3e93e6e
commit 7e9b32b379
12 changed files with 244 additions and 11 deletions
-7
View File
@@ -13,12 +13,5 @@ training/runs/
*.pt
*.tflite
# 后端运行时数据(SQLite 库,删除即丢授权/订单)
server/data/
server/biz/service/testdata/*.db
# 管理端构建产物(server_admin 构建生成,由后端托管)
server/admin_dist/
# 文生图配置(含API key,不入库)
#training/gen_images_config.json
+9
View File
@@ -6,6 +6,7 @@ import 'auth/session_store.dart';
import 'camera/camera_screen.dart';
import 'container.dart';
import 'home/home_screen.dart';
import 'legal/terms_screen.dart';
import 'payment/paywall_screen.dart';
class ObserverApp extends StatelessWidget {
@@ -31,6 +32,7 @@ class ObserverApp extends StatelessWidget {
initialRoute: '/gate',
routes: {
'/gate': (_) => const StartupGate(),
'/terms': (_) => const TermsScreen(),
'/login': (_) => const AuthScreen(),
'/home': (_) => const HomeScreen(),
'/paywall': (_) => const PaywallScreen(),
@@ -57,6 +59,13 @@ class _StartupGateState extends State<StartupGate> {
}
Future<void> _check() async {
// 首次启动强制协议;同意后不再展示(拒绝则退出应用)
final accepted = await TermsScreen.readAccepted();
if (!mounted) return;
if (accepted != '1') {
Navigator.of(context).pushReplacementNamed('/terms');
return;
}
final session = context.read<SessionStore>();
final token = await session.readToken();
if (!mounted) return;
+3 -4
View File
@@ -1,10 +1,9 @@
/// 全局配置占位:接入真实支付前需替换以下值。
class AppConfig {
/// 后端服务器地址(订单创建/授权查询/套餐价格)
/// 默认 Android 模拟器 10.0.2.2iOS 模拟器构建时传
/// --dart-define=API_BASE_URL=http://127.0.0.1:8080
static const String apiBaseUrl =
String.fromEnvironment('API_BASE_URL', defaultValue: 'http://10.0.2.2:8080');
/// 默认指向线上域名;本地联调构建时用 --dart-define=API_BASE_URL=http://127.0.0.1:8080 覆盖
static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL',
defaultValue: 'http://observer.redpowerfuture.com');
/// 微信开放平台 AppID(需在微信开放平台注册包名+签名)
static const String wechatAppId = 'wx0000000000000000';
+126
View File
@@ -0,0 +1,126 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show SystemNavigator;
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:provider/provider.dart';
import '../auth/session_store.dart';
/// 使用协议页:首次启动强制展示,同意后记录(secure storage),拒绝则退出应用。
/// 用途声明:仅限野生动物观察,禁止近距离打扰与非法使用。
class TermsScreen extends StatefulWidget {
const TermsScreen({super.key});
static const _storage = FlutterSecureStorage();
static const acceptedKey = 'terms_accepted';
static Future<String?> readAccepted() => _storage.read(key: acceptedKey);
@override
State<TermsScreen> createState() => _TermsScreenState();
}
class _TermsScreenState extends State<TermsScreen> {
bool _saving = false;
static const String _content = '''
《视野》使用协议
感谢您使用「视野」动物观察应用。使用本应用前,请仔细阅读并同意以下条款:
一、用途限制
本应用仅用于野生动物观察、记录与科普学习,严禁用于任何非法用途。
二、禁止行为
1. 禁止近距离打扰、追逐、驱赶、恐吓野生动物;
2. 禁止投喂、诱捕、猎捕、伤害野生动物;
3. 禁止利用本应用从事盗猎、野生动物交易或其他违法犯罪活动;
4. 禁止进入自然保护区禁区、私人领地等未经许可的区域进行观察。
三、法律合规
您须遵守《中华人民共和国野生动物保护法》及相关法律法规。
因违法违规使用产生的一切后果由使用者自行承担。
四、安全提示
野外观察存在风险,请结伴而行、注意自身安全。因使用本应用产生的安全事故,本应用不承担责任。
五、识别结果说明
本应用的动物识别结果基于人工智能模型,可能存在误差或漏检,识别结果仅供参考,不构成科学鉴定或法律依据。
六、其他
本协议内容可能适时更新,更新后您继续使用即视为接受。''';
Future<void> _accept() async {
if (_saving) return;
setState(() => _saving = true);
await TermsScreen._storage.write(key: TermsScreen.acceptedKey, value: '1');
if (!mounted) return;
final token = await context.read<SessionStore>().readToken();
if (!mounted) return;
Navigator.of(context)
.pushReplacementNamed(token == null ? '/login' : '/home');
}
Future<void> _decline() async {
if (_saving) return;
final ok = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('需要同意协议'),
content: const Text('不同意《使用协议》将无法使用本应用。'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('再看看'),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(true),
child: const Text('退出'),
),
],
),
);
if (ok == true && mounted) await SystemNavigator.pop();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('使用协议')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Text(
_content,
style: const TextStyle(fontSize: 15, height: 1.7),
),
),
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _saving ? null : _decline,
child: const Text('不同意'),
),
),
const SizedBox(width: 16),
Expanded(
child: FilledButton(
onPressed: _saving ? null : _accept,
child: _saving
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('同意并继续'),
),
),
],
),
),
),
);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="bluesky-icon" viewBox="0 0 16 17">
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
</symbol>
<symbol id="discord-icon" viewBox="0 0 20 19">
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
</symbol>
<symbol id="documentation-icon" viewBox="0 0 21 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
</symbol>
<symbol id="github-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
</symbol>
<symbol id="social-icon" viewBox="0 0 20 20">
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
</symbol>
<symbol id="x-icon" viewBox="0 0 19 19">
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>视野管理端</title>
<script type="module" crossorigin src="/admin/assets/index-gy5ysx_E.js"></script>
<link rel="stylesheet" crossorigin href="/admin/assets/index-DsTNKZnX.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
Binary file not shown.
Binary file not shown.
+8
View File
@@ -1,4 +1,10 @@
# 视野后端单机部署:前后端一体单端口,./data 挂载持久化(容器重建不丢授权/订单数据)
name: redfuture-app
networks:
redfuture-net:
driver: bridge
services:
observer-server:
build:
@@ -13,3 +19,5 @@ services:
- ./data:/app/data
environment:
TZ: Asia/Shanghai
networks:
- redfuture-net