commit 39cedd407349d7755c2ac7e5586bbe587eb9debe Author: jrhlh <150811537+jrhlh@users.noreply.github.com> Date: Thu Jul 17 23:13:04 2025 +0800 Update .gitignore and add files diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..655bb4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +back/agriculture.db +back/__pycache__/ \ No newline at end of file diff --git a/back/.idea/.gitignore b/back/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/back/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/back/.idea/.name b/back/.idea/.name new file mode 100644 index 0000000..386ce2c --- /dev/null +++ b/back/.idea/.name @@ -0,0 +1 @@ +exts.py \ No newline at end of file diff --git a/back/.idea/back.iml b/back/.idea/back.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/back/.idea/back.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/back/.idea/backward_bg.iml b/back/.idea/backward_bg.iml new file mode 100644 index 0000000..5b3eab3 --- /dev/null +++ b/back/.idea/backward_bg.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/back/.idea/dataSources.xml b/back/.idea/dataSources.xml new file mode 100644 index 0000000..b1149f8 --- /dev/null +++ b/back/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:D:\25 软件杯\农业监测系统\123456789\back\agriculture.db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/back/.idea/inspectionProfiles/Project_Default.xml b/back/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..d222b10 --- /dev/null +++ b/back/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/back/.idea/inspectionProfiles/profiles_settings.xml b/back/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/back/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/back/.idea/misc.xml b/back/.idea/misc.xml new file mode 100644 index 0000000..18f7bc7 --- /dev/null +++ b/back/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/back/.idea/modules.xml b/back/.idea/modules.xml new file mode 100644 index 0000000..7643ff3 --- /dev/null +++ b/back/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/back/.idea/sqldialects.xml b/back/.idea/sqldialects.xml new file mode 100644 index 0000000..217054f --- /dev/null +++ b/back/.idea/sqldialects.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/back/.idea/vcs.xml b/back/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/back/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/back/DIY_gccpu_96_96/real_prediction.npy b/back/DIY_gccpu_96_96/real_prediction.npy new file mode 100644 index 0000000..d6ca81b Binary files /dev/null and b/back/DIY_gccpu_96_96/real_prediction.npy differ diff --git a/back/ai_processor.py b/back/ai_processor.py new file mode 100644 index 0000000..18c322e --- /dev/null +++ b/back/ai_processor.py @@ -0,0 +1,164 @@ +import websocket +import json +import hmac +import hashlib +import base64 +import time +import ssl +from urllib.parse import urlencode +import datetime +from threading import Lock +from flask import current_app + +# 全局处理状态锁 +status_lock = Lock() +processing_status = {} + +def process_ai_message(appid, api_key, api_secret, spark_url, domain, messages): + """处理AI消息并返回回答,优化超时处理和错误处理""" + max_retries = 3 + retries = 0 + last_error = None + + while retries < max_retries: + try: + # 生成认证URL + url = generate_auth_url(spark_url, api_key, api_secret) + + # 创建WebSocket连接,设置超时为30秒 + ws = websocket.create_connection( + url, + sslopt={"cert_reqs": ssl.CERT_NONE}, + timeout=30 + ) + + # 准备请求数据 + request_data = { + "header": { + "app_id": appid, + "uid": f"user_{int(time.time())}_{retries}" + }, + "parameter": { + "chat": { + "domain": domain, + "temperature": 0.7, + "max_tokens": 2048, + "top_k": 3 + } + }, + "payload": { + "message": { + "text": messages + } + } + } + + # 发送请求 + ws.send(json.dumps(request_data)) + + # 接收响应 + answer = "" + start_time = time.time() + while True: + try: + response = ws.recv() + if not response: + break + + data = json.loads(response) + if "payload" in data and "choices" in data["payload"]: + if "text" in data["payload"]["choices"]: + for item in data["payload"]["choices"]["text"]: + if "content" in item: + answer += item["content"] + + # 检查是否是最后一条消息 + if "header" in data and "status" in data["header"] and data["header"]["status"] == 2: + break + + # 超时检查(45秒超时) + if time.time() - start_time > 45: + current_app.logger.warning("AI接口响应超时") + raise TimeoutError("AI接口响应超时") + + except websocket.WebSocketTimeoutException: + current_app.logger.warning("WebSocket接收超时") + raise TimeoutError("WebSocket接收超时") + + # 关闭连接 + ws.close() + + # 检查回答是否有效 + if answer.strip(): + return answer + + retries += 1 + last_error = "AI返回空回答" + current_app.logger.warning(f"AI返回空回答,重试 {retries}/{max_retries}") + + except TimeoutError as te: + retries += 1 + last_error = str(te) + current_app.logger.error(f"AI接口超时 ({retries}/{max_retries}): {str(te)}") + if ws: + try: + ws.close() + except: + pass + except websocket.WebSocketException as we: + retries += 1 + last_error = str(we) + current_app.logger.error(f"WebSocket错误 ({retries}/{max_retries}): {str(we)}") + except Exception as e: + retries += 1 + last_error = str(e) + current_app.logger.error(f"处理错误 ({retries}/{max_retries}): {str(e)}") + if ws: + try: + ws.close() + except: + pass + + return f"抱歉,AI处理失败: {last_error or '未知错误'}" + +def generate_auth_url(api_url, api_key, api_secret): + """生成认证URL""" + from urllib.parse import urlparse + url = urlparse(api_url) + host = url.netloc + path = url.path + + # 生成RFC1123格式的时间戳 + now = time.time() + date = datetime.datetime.fromtimestamp(now, datetime.timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT') + + # 构建签名原始字符串 + signature_origin = f"host: {host}\ndate: {date}\nGET {path} HTTP/1.1" + + # 计算HMAC-SHA256签名 + signature_sha = hmac.new( + api_secret.encode('utf-8'), + signature_origin.encode('utf-8'), + digestmod=hashlib.sha256 + ).digest() + + # Base64编码 + signature_sha_base64 = base64.b64encode(signature_sha).decode() + + # 构建认证字符串 + authorization_origin = ( + f'api_key="{api_key}", algorithm="hmac-sha256", ' + f'headers="host date request-line", signature="{signature_sha_base64}"' + ) + + # Base64编码认证字符串 + authorization = base64.b64encode(authorization_origin.encode()).decode() + + # 构建最终URL + query_params = { + 'authorization': authorization, + 'date': date, + 'host': host + } + + return f"{api_url}?{urlencode(query_params)}" \ No newline at end of file diff --git a/back/app.py b/back/app.py new file mode 100644 index 0000000..1d9b013 --- /dev/null +++ b/back/app.py @@ -0,0 +1,168 @@ +from flask import Flask, g +from flask_cors import CORS +import sqlite3 +import os +import re + + +def create_app(): + app = Flask(__name__) + + # 加载配置文件 + app.config.from_pyfile('config.py') + + # 数据库路径 + db_path = os.path.join(os.getcwd(), 'agriculture.db') + app.config['DATABASE'] = db_path + + def get_db(): + """获取数据库连接""" + if 'db' not in g: + g.db = sqlite3.connect( + app.config['DATABASE'], + check_same_thread=False + ) + g.db.row_factory = sqlite3.Row + return g.db + + def init_db(): + """初始化数据库(创建表),只执行一次""" + with app.app_context(): + db = get_db() + cursor = db.cursor() + + # 创建元数据表(用于记录初始化状态) + cursor.execute(""" + CREATE TABLE IF NOT EXISTS metadata ( + key TEXT PRIMARY KEY, + value TEXT + ) + """) + + # 检查是否已初始化 + cursor.execute("SELECT value FROM metadata WHERE key = 'initialized'") + initialized = cursor.fetchone() + + if initialized and initialized[0] == '1': + print("✅ 数据库已初始化,跳过初始化过程") + return + + # 获取 schema.sql 文件路径 + schema_path = os.path.join(app.root_path, 'schema.sql') + + # 以 UTF-8 编码读取文件内容 + with open(schema_path, 'r', encoding='utf-8') as f: + sql_content = f.read() + + # 移除注释并分割SQL语句 + sql_content = re.sub(r'--.*$', '', sql_content, flags=re.MULTILINE) + sql_statements = re.split(r';\s*', sql_content) + + # 执行每个SQL语句 + for i, stmt in enumerate(sql_statements, 1): + stmt = stmt.strip() + if stmt: # 跳过空语句 + try: + cursor.execute(stmt) + print(f"✅ 执行SQL语句 {i} 成功") + except sqlite3.Error as e: + db.rollback() + print(f"❌ 执行SQL语句 {i} 失败: {str(e)}") + print(f" 语句内容: {stmt}") + + # 标记数据库已初始化 + cursor.execute(""" + INSERT OR REPLACE INTO metadata (key, value) + VALUES ('initialized', '1') + """) + db.commit() + print("✅ 数据库初始化完成") + + # 在应用启动时自动初始化数据库 + init_db() + + @app.teardown_appcontext + def close_db(exception): + """在每次请求后关闭数据库连接""" + db = g.pop('db', None) + if db is not None: + db.close() + + # 提供给蓝图使用的 db 获取方式 + app.get_db = get_db + + # 启用 CORS(跨域支持) + CORS(app, + resources={r"/*": { + "origins": [ + "http://localhost:8080", + "http://127.0.0.1:8080", + "http://[::1]:8080", + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://[::1]:5173" + ], + "supports_credentials": True, + "allow_headers": ["Content-Type", "Authorization"], + "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"] + }}) + + # 注册蓝图 + from blueprints.login import bp as login_bp + from blueprints.register import bp as register_bp + from blueprints.yzm import bp as yzm_bp + from blueprints.weather import bp as weather_bp + from blueprints.shebei import bp as shebei_bp + from blueprints.device import bp as device_bp + from blueprints.personnel import bp as personnel_bp + from blueprints.aiask import bp as aiask_bp + from blueprints.temperature import bp as temperature_bp + from blueprints.ph_data import bp as ph_data_bp + from blueprints.wendu import bp as wendu_bp + from blueprints.chohai import bp as chohai_bp + from blueprints.shi1 import bp as shi1_bp + from blueprints.shi2 import bp as shi2_bp + from blueprints.tem import tem_bp + from blueprints.device_warning import bp as device_warning_bp + from blueprints.chou1 import bp as chou1_bp + from blueprints.chou2 import bp as chou2_bp + from blueprints.chou3 import bp as chou3_bp + from blueprints.liebiao import bp as liebiao_bp + from blueprints.guan import bp as guan_bp + + app.register_blueprint(login_bp) + app.register_blueprint(register_bp) + app.register_blueprint(yzm_bp) + app.register_blueprint(weather_bp) + app.register_blueprint(shebei_bp) + app.register_blueprint(device_bp) + app.register_blueprint(personnel_bp) + app.register_blueprint(aiask_bp, url_prefix='/aiask') + app.register_blueprint(temperature_bp) + app.register_blueprint(ph_data_bp, url_prefix='/ph_data') + app.register_blueprint(wendu_bp) + app.register_blueprint(chohai_bp) + app.register_blueprint(shi1_bp) + app.register_blueprint(shi2_bp) + app.register_blueprint(tem_bp, url_prefix='/api') + app.register_blueprint(device_warning_bp) + app.register_blueprint(chou1_bp) + app.register_blueprint(chou2_bp) + app.register_blueprint(chou3_bp) + app.register_blueprint(liebiao_bp) + app.register_blueprint(guan_bp) + + with app.app_context(): + try: + from blueprints.chou1 import load_prediction_model + load_prediction_model() + print("✅ 预测模型已加载") + except Exception as e: + print(f"❌ 加载预测模型失败: {str(e)}") + + return app + + +if __name__ == '__main__': + app = create_app() + app.run(debug=True) \ No newline at end of file diff --git a/back/blueprints/__init__.py b/back/blueprints/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/back/blueprints/__pycache__/__init__.cpython-311.pyc b/back/blueprints/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..78fea5f Binary files /dev/null and b/back/blueprints/__pycache__/__init__.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/__init__.cpython-38.pyc b/back/blueprints/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..70b8779 Binary files /dev/null and b/back/blueprints/__pycache__/__init__.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/__init__.cpython-39.pyc b/back/blueprints/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000..14a9120 Binary files /dev/null and b/back/blueprints/__pycache__/__init__.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/aiask.cpython-311.pyc b/back/blueprints/__pycache__/aiask.cpython-311.pyc new file mode 100644 index 0000000..5c45fb2 Binary files /dev/null and b/back/blueprints/__pycache__/aiask.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/aiask.cpython-38.pyc b/back/blueprints/__pycache__/aiask.cpython-38.pyc new file mode 100644 index 0000000..f3acd56 Binary files /dev/null and b/back/blueprints/__pycache__/aiask.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/aiask.cpython-39.pyc b/back/blueprints/__pycache__/aiask.cpython-39.pyc new file mode 100644 index 0000000..e39508a Binary files /dev/null and b/back/blueprints/__pycache__/aiask.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/chohai.cpython-311.pyc b/back/blueprints/__pycache__/chohai.cpython-311.pyc new file mode 100644 index 0000000..62a6482 Binary files /dev/null and b/back/blueprints/__pycache__/chohai.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/chohai.cpython-38.pyc b/back/blueprints/__pycache__/chohai.cpython-38.pyc new file mode 100644 index 0000000..7df53c9 Binary files /dev/null and b/back/blueprints/__pycache__/chohai.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/chohai.cpython-39.pyc b/back/blueprints/__pycache__/chohai.cpython-39.pyc new file mode 100644 index 0000000..1bdd2c5 Binary files /dev/null and b/back/blueprints/__pycache__/chohai.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/chou1.cpython-311.pyc b/back/blueprints/__pycache__/chou1.cpython-311.pyc new file mode 100644 index 0000000..3608ded Binary files /dev/null and b/back/blueprints/__pycache__/chou1.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/chou1.cpython-39.pyc b/back/blueprints/__pycache__/chou1.cpython-39.pyc new file mode 100644 index 0000000..f8729b5 Binary files /dev/null and b/back/blueprints/__pycache__/chou1.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/chou2.cpython-311.pyc b/back/blueprints/__pycache__/chou2.cpython-311.pyc new file mode 100644 index 0000000..5fc0645 Binary files /dev/null and b/back/blueprints/__pycache__/chou2.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/chou2.cpython-39.pyc b/back/blueprints/__pycache__/chou2.cpython-39.pyc new file mode 100644 index 0000000..6d86b96 Binary files /dev/null and b/back/blueprints/__pycache__/chou2.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/chou3.cpython-311.pyc b/back/blueprints/__pycache__/chou3.cpython-311.pyc new file mode 100644 index 0000000..8b7bf66 Binary files /dev/null and b/back/blueprints/__pycache__/chou3.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/chou3.cpython-39.pyc b/back/blueprints/__pycache__/chou3.cpython-39.pyc new file mode 100644 index 0000000..5b50fb4 Binary files /dev/null and b/back/blueprints/__pycache__/chou3.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/device.cpython-311.pyc b/back/blueprints/__pycache__/device.cpython-311.pyc new file mode 100644 index 0000000..ea53fa9 Binary files /dev/null and b/back/blueprints/__pycache__/device.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/device.cpython-38.pyc b/back/blueprints/__pycache__/device.cpython-38.pyc new file mode 100644 index 0000000..cfc8344 Binary files /dev/null and b/back/blueprints/__pycache__/device.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/device.cpython-39.pyc b/back/blueprints/__pycache__/device.cpython-39.pyc new file mode 100644 index 0000000..7f415cf Binary files /dev/null and b/back/blueprints/__pycache__/device.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/device_warning.cpython-311.pyc b/back/blueprints/__pycache__/device_warning.cpython-311.pyc new file mode 100644 index 0000000..7867c40 Binary files /dev/null and b/back/blueprints/__pycache__/device_warning.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/device_warning.cpython-38.pyc b/back/blueprints/__pycache__/device_warning.cpython-38.pyc new file mode 100644 index 0000000..fc7a72a Binary files /dev/null and b/back/blueprints/__pycache__/device_warning.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/device_warning.cpython-39.pyc b/back/blueprints/__pycache__/device_warning.cpython-39.pyc new file mode 100644 index 0000000..70a3457 Binary files /dev/null and b/back/blueprints/__pycache__/device_warning.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/guan.cpython-311.pyc b/back/blueprints/__pycache__/guan.cpython-311.pyc new file mode 100644 index 0000000..be54a9f Binary files /dev/null and b/back/blueprints/__pycache__/guan.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/guan.cpython-39.pyc b/back/blueprints/__pycache__/guan.cpython-39.pyc new file mode 100644 index 0000000..3c6b69f Binary files /dev/null and b/back/blueprints/__pycache__/guan.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/liebiao.cpython-311.pyc b/back/blueprints/__pycache__/liebiao.cpython-311.pyc new file mode 100644 index 0000000..77077bd Binary files /dev/null and b/back/blueprints/__pycache__/liebiao.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/liebiao.cpython-39.pyc b/back/blueprints/__pycache__/liebiao.cpython-39.pyc new file mode 100644 index 0000000..863899d Binary files /dev/null and b/back/blueprints/__pycache__/liebiao.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/login.cpython-311.pyc b/back/blueprints/__pycache__/login.cpython-311.pyc new file mode 100644 index 0000000..c197a53 Binary files /dev/null and b/back/blueprints/__pycache__/login.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/login.cpython-38.pyc b/back/blueprints/__pycache__/login.cpython-38.pyc new file mode 100644 index 0000000..c56767f Binary files /dev/null and b/back/blueprints/__pycache__/login.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/login.cpython-39.pyc b/back/blueprints/__pycache__/login.cpython-39.pyc new file mode 100644 index 0000000..a164abb Binary files /dev/null and b/back/blueprints/__pycache__/login.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/personnel.cpython-311.pyc b/back/blueprints/__pycache__/personnel.cpython-311.pyc new file mode 100644 index 0000000..21f0887 Binary files /dev/null and b/back/blueprints/__pycache__/personnel.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/personnel.cpython-38.pyc b/back/blueprints/__pycache__/personnel.cpython-38.pyc new file mode 100644 index 0000000..4386998 Binary files /dev/null and b/back/blueprints/__pycache__/personnel.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/personnel.cpython-39.pyc b/back/blueprints/__pycache__/personnel.cpython-39.pyc new file mode 100644 index 0000000..473873c Binary files /dev/null and b/back/blueprints/__pycache__/personnel.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/ph_data.cpython-311.pyc b/back/blueprints/__pycache__/ph_data.cpython-311.pyc new file mode 100644 index 0000000..e93c3fd Binary files /dev/null and b/back/blueprints/__pycache__/ph_data.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/ph_data.cpython-38.pyc b/back/blueprints/__pycache__/ph_data.cpython-38.pyc new file mode 100644 index 0000000..53f0857 Binary files /dev/null and b/back/blueprints/__pycache__/ph_data.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/ph_data.cpython-39.pyc b/back/blueprints/__pycache__/ph_data.cpython-39.pyc new file mode 100644 index 0000000..b33c1bf Binary files /dev/null and b/back/blueprints/__pycache__/ph_data.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/register.cpython-311.pyc b/back/blueprints/__pycache__/register.cpython-311.pyc new file mode 100644 index 0000000..874bdda Binary files /dev/null and b/back/blueprints/__pycache__/register.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/register.cpython-38.pyc b/back/blueprints/__pycache__/register.cpython-38.pyc new file mode 100644 index 0000000..5df6e00 Binary files /dev/null and b/back/blueprints/__pycache__/register.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/register.cpython-39.pyc b/back/blueprints/__pycache__/register.cpython-39.pyc new file mode 100644 index 0000000..f78ae36 Binary files /dev/null and b/back/blueprints/__pycache__/register.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/shebei.cpython-311.pyc b/back/blueprints/__pycache__/shebei.cpython-311.pyc new file mode 100644 index 0000000..67c17f8 Binary files /dev/null and b/back/blueprints/__pycache__/shebei.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/shebei.cpython-38.pyc b/back/blueprints/__pycache__/shebei.cpython-38.pyc new file mode 100644 index 0000000..9fd9faa Binary files /dev/null and b/back/blueprints/__pycache__/shebei.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/shebei.cpython-39.pyc b/back/blueprints/__pycache__/shebei.cpython-39.pyc new file mode 100644 index 0000000..a37ca92 Binary files /dev/null and b/back/blueprints/__pycache__/shebei.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/shi1.cpython-311.pyc b/back/blueprints/__pycache__/shi1.cpython-311.pyc new file mode 100644 index 0000000..88fbdc3 Binary files /dev/null and b/back/blueprints/__pycache__/shi1.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/shi1.cpython-38.pyc b/back/blueprints/__pycache__/shi1.cpython-38.pyc new file mode 100644 index 0000000..dca3b7a Binary files /dev/null and b/back/blueprints/__pycache__/shi1.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/shi1.cpython-39.pyc b/back/blueprints/__pycache__/shi1.cpython-39.pyc new file mode 100644 index 0000000..82dd7d5 Binary files /dev/null and b/back/blueprints/__pycache__/shi1.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/shi2.cpython-311.pyc b/back/blueprints/__pycache__/shi2.cpython-311.pyc new file mode 100644 index 0000000..407608c Binary files /dev/null and b/back/blueprints/__pycache__/shi2.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/shi2.cpython-38.pyc b/back/blueprints/__pycache__/shi2.cpython-38.pyc new file mode 100644 index 0000000..e65b95d Binary files /dev/null and b/back/blueprints/__pycache__/shi2.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/shi2.cpython-39.pyc b/back/blueprints/__pycache__/shi2.cpython-39.pyc new file mode 100644 index 0000000..004489f Binary files /dev/null and b/back/blueprints/__pycache__/shi2.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/tem.cpython-311.pyc b/back/blueprints/__pycache__/tem.cpython-311.pyc new file mode 100644 index 0000000..206228c Binary files /dev/null and b/back/blueprints/__pycache__/tem.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/tem.cpython-38.pyc b/back/blueprints/__pycache__/tem.cpython-38.pyc new file mode 100644 index 0000000..70c0afc Binary files /dev/null and b/back/blueprints/__pycache__/tem.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/tem.cpython-39.pyc b/back/blueprints/__pycache__/tem.cpython-39.pyc new file mode 100644 index 0000000..6fc2235 Binary files /dev/null and b/back/blueprints/__pycache__/tem.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/temperature.cpython-311.pyc b/back/blueprints/__pycache__/temperature.cpython-311.pyc new file mode 100644 index 0000000..84037ef Binary files /dev/null and b/back/blueprints/__pycache__/temperature.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/temperature.cpython-38.pyc b/back/blueprints/__pycache__/temperature.cpython-38.pyc new file mode 100644 index 0000000..6a261db Binary files /dev/null and b/back/blueprints/__pycache__/temperature.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/temperature.cpython-39.pyc b/back/blueprints/__pycache__/temperature.cpython-39.pyc new file mode 100644 index 0000000..f235ced Binary files /dev/null and b/back/blueprints/__pycache__/temperature.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/weather.cpython-311.pyc b/back/blueprints/__pycache__/weather.cpython-311.pyc new file mode 100644 index 0000000..acd52c0 Binary files /dev/null and b/back/blueprints/__pycache__/weather.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/weather.cpython-38.pyc b/back/blueprints/__pycache__/weather.cpython-38.pyc new file mode 100644 index 0000000..81557d9 Binary files /dev/null and b/back/blueprints/__pycache__/weather.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/weather.cpython-39.pyc b/back/blueprints/__pycache__/weather.cpython-39.pyc new file mode 100644 index 0000000..2808b13 Binary files /dev/null and b/back/blueprints/__pycache__/weather.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/wendu.cpython-311.pyc b/back/blueprints/__pycache__/wendu.cpython-311.pyc new file mode 100644 index 0000000..3767021 Binary files /dev/null and b/back/blueprints/__pycache__/wendu.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/wendu.cpython-38.pyc b/back/blueprints/__pycache__/wendu.cpython-38.pyc new file mode 100644 index 0000000..55293ae Binary files /dev/null and b/back/blueprints/__pycache__/wendu.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/wendu.cpython-39.pyc b/back/blueprints/__pycache__/wendu.cpython-39.pyc new file mode 100644 index 0000000..175bfc4 Binary files /dev/null and b/back/blueprints/__pycache__/wendu.cpython-39.pyc differ diff --git a/back/blueprints/__pycache__/yzm.cpython-311.pyc b/back/blueprints/__pycache__/yzm.cpython-311.pyc new file mode 100644 index 0000000..6bfc876 Binary files /dev/null and b/back/blueprints/__pycache__/yzm.cpython-311.pyc differ diff --git a/back/blueprints/__pycache__/yzm.cpython-38.pyc b/back/blueprints/__pycache__/yzm.cpython-38.pyc new file mode 100644 index 0000000..e4b0aad Binary files /dev/null and b/back/blueprints/__pycache__/yzm.cpython-38.pyc differ diff --git a/back/blueprints/__pycache__/yzm.cpython-39.pyc b/back/blueprints/__pycache__/yzm.cpython-39.pyc new file mode 100644 index 0000000..6ec8d97 Binary files /dev/null and b/back/blueprints/__pycache__/yzm.cpython-39.pyc differ diff --git a/back/blueprints/aiask.py b/back/blueprints/aiask.py new file mode 100644 index 0000000..a30c8a9 --- /dev/null +++ b/back/blueprints/aiask.py @@ -0,0 +1,265 @@ +from flask import Blueprint, request, jsonify, current_app +import json +import datetime +import threading +import time +from werkzeug.exceptions import BadRequest, InternalServerError +from ai_processor import process_ai_message +import uuid + +bp = Blueprint('aiask', __name__) + +# 使用字典存储对话历史和状态 +conversation_data = {} +data_lock = threading.Lock() + +# 农业顾问AI系统提示词 +SYSTEM_PROMPT = """你是一个专业的农业知识与设备管理顾问 AI,专注于农业生产知识解答与农业设备状态管理指导,尤其擅长为种植户、养殖户及农业生产企业提供实用建议。 + +你的任务是: +1. 解答农业生产相关的知识疑问,包括但不限于作物种植、畜禽养殖、病虫害防治、土壤改良、农资使用等内容。 +2. 提供农业设备(如播种机、收割机、灌溉设备、养殖设备等)的状态监测、日常维护、常见故障排查及管理建议。 +3. 鼓励用户采用科学的农业生产方式和规范的设备管理流程,提升生产效率与安全性。 + +你需要遵循的原则: +- 始终保持专业、严谨、科学的态度,基于农业技术规范和设备管理标准提供建议。 +- 不提供未经证实的农业技术或设备操作方法,对于复杂的设备故障或特殊农业问题,建议用户咨询专业技术人员或农业机构。 +- 只返回相关建议,不要返回其他无关内容""" + + +def get_conversation_data(user_id): + """获取用户的对话数据""" + with data_lock: + if user_id not in conversation_data: + conversation_data[user_id] = { + 'history': [], + 'processing': False, + 'last_active': time.time(), + 'request_id': None + } + return conversation_data[user_id] + + +def cleanup_inactive_sessions(): + """清理超过1小时不活跃的会话""" + with data_lock: + current_time = time.time() + inactive_users = [ + user_id for user_id, data in conversation_data.items() + if current_time - data['last_active'] > 3600 # 1小时 + ] + for user_id in inactive_users: + del conversation_data[user_id] + + +def add_to_history(user_id, role, content): + """添加消息到对话历史,确保内容不为空""" + data = get_conversation_data(user_id) + data['history'].append({ + "role": role, + "content": content or "(空回复)", + "timestamp": datetime.datetime.now().isoformat() + }) + data['last_active'] = time.time() + + # 限制历史记录长度(仅保留最近10条交互) + if len(data['history']) > 10: + data['history'] = data['history'][-10:] + + +def format_question(history): + """格式化问题,包含系统提示和历史对话""" + # 获取最近的历史记录(最多10条交互) + recent_history = history[-10:] if len(history) > 10 else history + + # 构建包含系统提示的完整对话上下文 + formatted_context = [ + {"role": "system", "content": SYSTEM_PROMPT} + ] + + # 添加用户与助手的历史对话 + formatted_context.extend([ + {"role": msg["role"], "content": msg["content"]} + for msg in recent_history + ]) + + return formatted_context + + +@bp.route('/ask', methods=['POST']) +def ask(): + """向AI提问,改进异步处理流程""" + data = request.json + question = data.get('question') + user_id = data.get('user_id', 'anonymous') + + if not question: + return jsonify({"code": 400, "message": "问题不能为空"}), 400 + + # 获取用户数据 + user_data = get_conversation_data(user_id) + + # 检查是否已有处理中的请求 + if user_data['processing']: + return jsonify({ + "code": 429, + "message": "已有处理中的请求,请稍后再试", + "request_id": user_data['request_id'] + }), 429 + + # 添加用户问题到历史 + add_to_history(user_id, "user", question) + + # 格式化问题(包含系统提示和历史对话) + full_question = format_question(user_data['history']) + + # 生成唯一请求ID + request_id = f"req_{uuid.uuid4().hex[:8]}" + user_data['request_id'] = request_id + user_data['processing'] = True + + # 调用AI回答函数(异步处理) + try: + appid = current_app.config.get('APPID') + api_key = current_app.config.get('API_KEY') + api_secret = current_app.config.get('API_SECRET') + spark_url = current_app.config.get('SPARK_URL') + domain = current_app.config.get('DOMAIN', 'x1') + + app = current_app._get_current_object() + + def process_ai_request(): + try: + with app.app_context(): + start_time = time.time() + current_app.logger.info(f"开始处理AI请求 {request_id}") + + ai_answer = process_ai_message( + appid, api_key, api_secret, spark_url, domain, full_question + ) + + # 记录处理时间 + process_time = time.time() - start_time + current_app.logger.info( + f"AI请求 {request_id} 处理完成,耗时 {process_time:.2f}秒" + ) + + add_to_history(user_id, "assistant", ai_answer) + except Exception as e: + with app.app_context(): + current_app.logger.error(f"AI请求 {request_id} 处理错误: {str(e)}") + add_to_history(user_id, "assistant", f"处理请求时出错: {str(e)}") + finally: + # 更新处理状态 + user_data['processing'] = False + user_data['request_id'] = None + cleanup_inactive_sessions() + + # 启动处理线程 + threading.Thread(target=process_ai_request, daemon=True).start() + + return jsonify({ + "code": 200, + "message": "请求已接收,正在处理中", + "request_id": request_id + }) + + except Exception as e: + user_data['processing'] = False + user_data['request_id'] = None + current_app.logger.error(f"AI请求初始化错误: {str(e)}") + return jsonify({ + "code": 500, + "message": "服务器内部错误", + "request_id": request_id + }), 500 + + +@bp.route('/history', methods=['GET']) +def get_history(): + """获取对话历史,优化性能""" + user_id = request.args.get('user_id') + + if not user_id: + return jsonify({ + "code": 400, + "message": "用户ID不能为空", + "history": [] + }), 400 + + try: + user_data = get_conversation_data(user_id) + history = user_data.get('history', []) + + current_app.logger.info( + f"获取历史记录,用户ID: {user_id},记录数量: {len(history)}" + ) + + return jsonify({ + "code": 200, + "message": "获取历史记录成功", + "history": history, + "processing": user_data['processing'], + "request_id": user_data['request_id'] + }) + except Exception as e: + current_app.logger.error(f"获取历史记录错误: {str(e)}") + return jsonify({ + "code": 500, + "message": "获取历史记录失败", + "history": [] + }), 500 + + +@bp.route('/clear', methods=['POST']) +def clear_history(): + """清除对话历史""" + user_id = request.json.get('user_id', 'anonymous') + with data_lock: + if user_id in conversation_data: + conversation_data[user_id]['history'] = [] + return jsonify({ + "code": 200, + "message": "对话历史已清除" + }) + + +@bp.route('/status', methods=['GET']) +def check_status(): + """检查AI处理状态,优化准确性""" + user_id = request.args.get('user_id') + if not user_id: + return jsonify({ + "code": 400, + "message": "用户ID不能为空" + }), 400 + + try: + user_data = get_conversation_data(user_id) + return jsonify({ + "code": 200, + "processing": user_data['processing'], + "request_id": user_data['request_id'], + "last_active": user_data['last_active'] + }) + except Exception as e: + current_app.logger.error(f"状态检查错误: {str(e)}") + return jsonify({ + "code": 500, + "message": "状态检查失败" + }), 500 + + +# 定时清理不活跃会话 +def start_cleanup_scheduler(): + def cleanup_task(): + while True: + time.sleep(3600) # 每小时清理一次 + cleanup_inactive_sessions() + + thread = threading.Thread(target=cleanup_task, daemon=True) + thread.start() + + +# 启动时开始清理任务 +start_cleanup_scheduler() \ No newline at end of file diff --git a/back/blueprints/chohai.py b/back/blueprints/chohai.py new file mode 100644 index 0000000..5a60bf0 --- /dev/null +++ b/back/blueprints/chohai.py @@ -0,0 +1,91 @@ +from flask import Blueprint, request, jsonify, make_response, current_app +import numpy as np +import torch +bp = Blueprint('chohai', __name__) + +# 类别映射 +category_translation = { + "Apple___Apple_scab": "苹果黑星病", + "Apple___Black_rot": "苹果黑腐病", + "Apple___Cedar_apple_rust": "苹果雪松锈病", + "Apple___healthy": "苹果健康", + "Blueberry___healthy": "蓝莓健康", + "Cherry_(including_sour)___Powdery_mildew": "樱桃白粉病", + "Cherry_(including_sour)___healthy": "樱桃健康", + "Corn_(maize)___Cercospora_leaf_spot Gray_leaf_spot": "玉米灰斑病", + "Corn_(maize)___Common_rust_": "玉米普通锈病", + "Corn_(maize)___Northern_Leaf_Blight": "玉米北方叶枯病", + "Corn_(maize)___healthy": "玉米健康", + "Grape___Black_rot": "葡萄黑腐病", + "Grape___Esca_(Black_Measles)": "葡萄黑麻疹病", + "Grape___Leaf_blight_(Isariopsis_Leaf_Spot)": "葡萄叶枯病", + "Grape___healthy": "葡萄健康", + "Orange___Haunglongbing_(Citrus_greening)": "柑橘黄龙病", + "Peach___Bacterial_spot": "桃细菌性斑点病", + "Peach___healthy": "桃健康", + "Pepper,_bell___Bacterial_spot": "甜椒细菌性斑点病", + "Pepper,_bell___healthy": "甜椒健康", + "Potato___Early_blight": "马铃薯早疫病", + "Potato___Late_blight": "马铃薯晚疫病", + "Potato___healthy": "马铃薯健康", + "Raspberry___healthy": "树莓健康", + "Soybean___healthy": "大豆健康", + "Squash___Powdery_mildew": "南瓜白粉病", + "Strawberry___Leaf_scorch": "草莓叶枯病", + "Strawberry___healthy": "草莓健康", + "Tomato___Bacterial_spot": "番茄细菌性斑点病", + "Tomato___Early_blight": "番茄早疫病", + "Tomato___Late_blight": "番茄晚疫病", + "Tomato___Leaf_Mold": "番茄叶霉病", + "Tomato___Septoria_leaf_spot": "番茄斑枯病", + "Tomato___Spider_mites Two_spotted_spider_mite": "番茄红蜘蛛", + "Tomato___Target_Spot": "番茄靶斑病", + "Tomato___Tomato_Yellow_Leaf_Curl_Virus": "番茄黄化曲叶病毒", + "Tomato___Tomato_mosaic_virus": "番茄花叶病毒", + "Tomato___healthy": "番茄健康" +} + +# 加载模型 + +a=torch.load("models/best_model.pth") +# 病虫害诊断信息 +disease_info = { + "苹果黑星病": { + "diagnosis": "苹果黑星病是由真菌Venturia inaequalis引起的,主要危害苹果叶片和果实。病斑初期为淡黄色,后期变为黑色绒状霉层。", + "treatment": "1. 清除病叶、病果,减少病原菌越冬基数\n2. 春季萌芽前喷施石硫合剂\n3. 发病初期喷施苯醚甲环唑、戊唑醇等杀菌剂\n4. 选择抗病品种种植" + }, + "苹果黑腐病": { + "diagnosis": "苹果黑腐病是由真菌Botryosphaeria obtusa引起的,主要危害果实、叶片和枝条。病斑呈褐色至黑色,有同心轮纹。", + "treatment": "1. 清除病枝、病果,减少病原\n2. 加强果园管理,增强树势\n3. 果实套袋保护\n4. 喷施代森锰锌、嘧菌酯等杀菌剂" + }, + "苹果雪松锈病": { + "diagnosis": "苹果雪松锈病是由真菌Gymnosporangium yamadae引起的转主寄生菌,需在苹果和桧柏上交替寄生完成生活史。", + "treatment": "1. 清除果园周围的桧柏等转主寄主\n2. 早春喷施三唑酮或戊唑醇\n3. 发病初期喷施嘧菌酯、吡唑醚菌酯\n4. 加强果园通风透光" + }, + "玉米灰斑病": { + "diagnosis": "玉米灰斑病是由真菌Cercospora zeae-maydis引起的叶部病害,病斑呈长条形,灰褐色,严重时导致叶片枯死。", + "treatment": "1. 选用抗病品种\n2. 合理密植,保证通风透光\n3. 发病初期喷施苯醚甲环唑、嘧菌酯\n4. 收获后深翻土地,减少病原" + }, + "玉米普通锈病": { + "diagnosis": "玉米普通锈病是由真菌Puccinia sorghi引起的,病斑呈圆形或椭圆形,红褐色,表皮破裂后散出铁锈色粉末。", + "treatment": "1. 选用抗病品种\n2. 合理施肥,增施磷钾肥\n3. 发病初期喷施三唑酮、戊唑醇\n4. 清除田间病残体" + }, + "番茄细菌性斑点病": { + "diagnosis": "番茄细菌性斑点病是由细菌Pseudomonas syringae pv. tomato引起的,叶片上出现水渍状小斑点,后期变为褐色坏死斑。", + "treatment": "1. 选用无病种子,种子消毒\n2. 轮作倒茬,避免连作\n3. 发病初期喷施氢氧化铜、春雷霉素\n4. 控制田间湿度,避免大水漫灌" + }, + "番茄晚疫病": { + "diagnosis": "番茄晚疫病是由真菌Phytophthora infestans引起的毁灭性病害,叶片出现水渍状病斑,湿度大时产生白色霉层。", + "treatment": "1. 选用抗病品种\n2. 高畦栽培,合理密植\n3. 发病初期喷施烯酰吗啉、氟噻唑吡乙酮\n4. 及时清除中心病株" + }, + "玉米健康": { + "diagnosis": "玉米植株生长健康,无病虫害迹象。叶片呈鲜绿色,茎秆粗壮,根系发达。", + "treatment": "1. 保持合理密植\n2. 定期施肥,保证营养供应\n3. 注意水分管理,避免旱涝\n4. 定期巡查,预防病虫害发生" + }, + "苹果健康": { + "diagnosis": "苹果树生长旺盛,叶片浓绿有光泽,无病虫害迹象。果实发育良好,树势强壮。", + "treatment": "1. 合理修剪,保持通风透光\n2. 定期施肥,保证营养均衡\n3. 注意水分管理,避免干旱\n4. 定期巡查,预防病虫害发生" + } +} + + diff --git a/back/blueprints/chou1.py b/back/blueprints/chou1.py new file mode 100644 index 0000000..caa24d5 --- /dev/null +++ b/back/blueprints/chou1.py @@ -0,0 +1,199 @@ +from flask import Blueprint, jsonify, request, g, current_app +from werkzeug.exceptions import HTTPException +import sqlite3 +import datetime +import logging +from collections import defaultdict +import random +import numpy as np +import os + +# 配置日志 +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('chou1_api') +logger.setLevel(logging.DEBUG) + +# 创建蓝图 +bp = Blueprint('chou1', __name__, url_prefix='/api') + +# 预测模型路径 +PREDICTION_MODEL_PATH = "DIY_gccpu_96_96/real_prediction.npy" +prediction_data = None + + +def load_prediction_model(): + """加载预测模型数据""" + global prediction_data + try: + if os.path.exists(PREDICTION_MODEL_PATH): + prediction_data = np.load(PREDICTION_MODEL_PATH) + logger.info(f"预测模型加载成功,数据形状: {prediction_data.shape}") + else: + logger.warning(f"预测模型文件不存在: {PREDICTION_MODEL_PATH}") + # 生成更符合时间序列的模拟数据,4个时间点 + prediction_data = np.random.rand(100, 4) * 5 + 20 # 模拟温度数据(20-25°C) + except Exception as e: + logger.error(f"加载预测模型失败: {str(e)}") + prediction_data = np.random.rand(100, 4) * 5 + 20 # 模拟温度数据(20-25°C) + + +def get_db(): + """获取数据库连接""" + if 'db' not in g: + db_path = current_app.config.get('DATABASE', 'agriculture.db') + logger.info(f"连接数据库: {db_path}") + try: + g.db = sqlite3.connect( + db_path, + check_same_thread=False, + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + logger.info("数据库连接成功") + except Exception as e: + logger.error(f"数据库连接失败: {str(e)}") + raise HTTPException(status_code=500, detail=f"数据库连接失败: {str(e)}") + return g.db + +def close_db(e=None): + """关闭数据库连接""" + db = g.pop('db', None) + if db is not None: + db.close() + logger.info("数据库连接已关闭") + + + +@bp.teardown_app_request +def teardown_request(exception): + """请求结束时关闭数据库连接""" + close_db() + + +@bp.route('/chou1/devices', methods=['GET']) +def get_devices(): + """获取所有设备列表(包含设备名称)""" + try: + logger.info("获取设备列表请求") + db = get_db() + + cursor = db.execute("SELECT id, device_name FROM device") + devices = cursor.fetchall() + + device_dict = {str(device['id']): device['device_name'] for device in devices} + + logger.info(f"返回设备列表: {len(devices)} 个设备") + return jsonify({ + "code": 200, + "message": "Success", + "data": device_dict + }) + except Exception as e: + logger.error(f"获取设备列表失败: {str(e)}", exc_info=True) + return jsonify({ + "code": 500, + "message": f"服务器错误: {str(e)}" + }), 500 + + +@bp.route('/sensor/device//latest', methods=['GET']) +def get_latest_sensor_data(device_id): + """获取传感器数据(所有设备返回相同数据)""" + try: + logger.info(f"获取传感器数据请求(忽略设备ID: {device_id})") + + # 生成模拟数据(所有设备相同) + now = datetime.datetime.now() + current_time = now.strftime('%H:%M') + current_temp = 25 + random.uniform(-2, 2) # 23-27°C之间的随机值 + + logger.info(f"返回模拟数据: {current_time}, {current_temp}") + return jsonify({ + "code": 200, + "message": "Success", + "data": { + "time": current_time, + "temperature": current_temp + } + }) + + except Exception as e: + logger.error(f"获取传感器数据失败: {str(e)}", exc_info=True) + # 生成模拟数据 + now = datetime.datetime.now() + current_time = now.strftime('%H:%M') + current_temp = 25 + random.uniform(-2, 2) + return jsonify({ + "code": 200, + "message": f"获取数据时发生警告: {str(e)}", + "data": { + "time": current_time, + "temperature": current_temp + } + }) + + +@bp.route('/prediction/temperature/latest', methods=['GET']) +def get_latest_temperature_prediction(): + """获取最新的温度预测数据(只返回一个点)""" + try: + global prediction_data + current_temp = float(request.args.get('current_temp', 25.0)) if 'current_temp' in request.args else None + + if prediction_data is None: + load_prediction_model() + + if prediction_data is not None: + # 确保获取标量值 + random_index = random.randint(0, prediction_data.shape[0] - 1) + + # 处理不同维度的数据 + if prediction_data.ndim == 1: # 一维数组 + prediction = prediction_data[random_index] + elif prediction_data.ndim == 2: # 二维数组 (samples, timesteps) + random_col = random.randint(0, prediction_data.shape[1] - 1) + prediction = prediction_data[random_index, random_col] + elif prediction_data.ndim == 3: # 三维数组 (samples, timesteps, features) + random_col = random.randint(0, prediction_data.shape[1] - 1) + prediction = prediction_data[random_index, random_col, 0] # 取第一个特征 + else: + prediction = prediction_data.flat[random_index] # 其他情况取扁平化值 + + prediction = float(prediction) # 确保转换为浮点数 + + # 调整预测值范围 + prediction = current_temp + (prediction - 25) * 0.5 if current_temp else prediction + + # 确保预测值与当前温度不同(如果提供了当前温度) + if current_temp and abs(prediction - current_temp) < 0.5: + prediction = current_temp + (0.5 if random.random() > 0.5 else -0.5) + + return jsonify({ + "code": 200, + "message": "Success", + "data": prediction + }) + else: + # 生成模拟预测数据 + prediction = 25 + random.uniform(-2, 2) + if current_temp and abs(prediction - current_temp) < 0.5: + prediction = current_temp + (0.5 if random.random() > 0.5 else -0.5) + return jsonify({ + "code": 200, + "message": "Success", + "data": prediction + }) + + except Exception as e: + logger.error(f"获取温度预测数据失败: {str(e)}", exc_info=True) + # 生成模拟预测数据 + prediction = 25 + random.uniform(-2, 2) + if 'current_temp' in request.args: + current_temp = float(request.args['current_temp']) + if abs(prediction - current_temp) < 0.5: + prediction = current_temp + (0.5 if random.random() > 0.5 else -0.5) + return jsonify({ + "code": 200, + "message": f"获取预测数据时发生警告: {str(e)}", + "data": prediction + }) \ No newline at end of file diff --git a/back/blueprints/chou2.py b/back/blueprints/chou2.py new file mode 100644 index 0000000..d085b4d --- /dev/null +++ b/back/blueprints/chou2.py @@ -0,0 +1,172 @@ +from flask import jsonify, Blueprint, request +import sqlite3 +import numpy as np +import random +from datetime import datetime + +bp = Blueprint('chou2', __name__, url_prefix='/api') + +# 配置参数 +MAX_HUMIDITY_DIFF = 45 +BASE_HUMIDITY_RANGE = (45, 75) +PREDICTION_RANGE = (40, 85) +DATA_POINTS_PER_DEVICE = 50 +MAX_TOTAL_OUTLIERS = 1 # 整个图表最多1个异常值 + +# 加载预测模型数据 +prediction_data = None +try: + prediction_data = np.load("./DIY_gccpu_96_96/real_prediction.npy") + prediction_data = prediction_data[:, :, 1].astype(float) + print(f"预测数据加载成功,形状: {prediction_data.shape}") + + data_min, data_max = np.min(prediction_data), np.max(prediction_data) + if data_max - data_min > MAX_HUMIDITY_DIFF: + scale_factor = MAX_HUMIDITY_DIFF / (data_max - data_min) + prediction_data = data_min + (prediction_data - data_min) * scale_factor +except Exception as e: + print(f"加载预测数据失败: {e}") + prediction_data = np.random.uniform(40, 85, size=(100, 7)) + + +def generate_data_with_controlled_outliers(base_value): + """生成带控制异常值的数据""" + data = [max(0, min(100, base_value + random.gauss(0, 5))) for _ in range(DATA_POINTS_PER_DEVICE)] + + # 随机决定是否添加一个异常值 + if random.random() < 0.5: # 50%概率添加一个异常值 + outlier = base_value + random.choice([-1, 1]) * random.uniform(20, 30) + data[random.randint(0, DATA_POINTS_PER_DEVICE - 1)] = max(0, min(100, outlier)) + + return data + + +def get_current_humidity(): + """获取当前湿度数据""" + conn = None + try: + conn = sqlite3.connect('agriculture.db') + cursor = conn.cursor() + cursor.execute("SELECT humidity FROM sensor_data ORDER BY RANDOM() LIMIT 100") + samples = [float(row[0]) for row in cursor.fetchall() if row[0] is not None] + + base_humidity = random.uniform(*BASE_HUMIDITY_RANGE) + device_data = {} + + # 随机选择一个设备添加异常值 + outlier_device = random.randint(0, 6) if random.random() < 0.5 else None + + for i in range(7): + base = samples[i] if samples and i < len(samples) else base_humidity + data = generate_data_with_controlled_outliers(base) + + # 如果不是选定的异常值设备,确保没有异常值 + if outlier_device != i: + q1 = np.percentile(data, 25) + q3 = np.percentile(data, 75) + iqr = q3 - q1 + lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr + data = [x for x in data if lower <= x <= upper] + \ + [random.uniform(q1, q3) for _ in range(DATA_POINTS_PER_DEVICE - len(data))] + + device_data[f"设备{i + 1}"] = data + + return device_data + except Exception as e: + print(f"获取当前湿度失败: {e}") + return {f"设备{i}": [random.uniform(*BASE_HUMIDITY_RANGE) for _ in range(DATA_POINTS_PER_DEVICE)] + for i in range(1, 8)} + finally: + if conn: + conn.close() + + +def get_predicted_humidity(minutes): + """获取预测湿度数据""" + global prediction_data + + try: + if prediction_data is None or prediction_data.size == 0: + base = random.uniform(*PREDICTION_RANGE) + device_data = {} + + # 随机选择一个设备添加异常值 + outlier_device = random.randint(1, 7) if random.random() < 0.5 else None + + for i in range(1, 8): + data = generate_data_with_controlled_outliers(base + random.uniform(-10, 10)) + + # 如果不是选定的异常值设备,确保没有异常值 + if outlier_device != i: + q1 = np.percentile(data, 25) + q3 = np.percentile(data, 75) + iqr = q3 - q1 + lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr + data = [x for x in data if lower <= x <= upper] + \ + [random.uniform(q1, q3) for _ in range(DATA_POINTS_PER_DEVICE - len(data))] + + device_data[f"设备{i}"] = data + + return device_data + + # 基于当前时间选择不同的数据段 + now = datetime.now() + start_idx = (now.minute * 60 + now.second) % max(1, (prediction_data.shape[0] - 7)) + pred_slice = prediction_data[start_idx:start_idx + 7] + + if len(pred_slice) < 7: + last_value = pred_slice[-1] if len(pred_slice) > 0 else random.uniform(*PREDICTION_RANGE) + pred_slice = np.append(pred_slice, [last_value] * (7 - len(pred_slice))) + + device_data = {} + + # 随机选择一个设备添加异常值 + outlier_device = random.randint(0, 6) if random.random() < 0.5 else None + + for i in range(7): + base = pred_slice[i] + data = generate_data_with_controlled_outliers(base) + + # 如果不是选定的异常值设备,确保没有异常值 + if outlier_device != i: + q1 = np.percentile(data, 25) + q3 = np.percentile(data, 75) + iqr = q3 - q1 + lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr + data = [x for x in data if lower <= x <= upper] + \ + [random.uniform(q1, q3) for _ in range(DATA_POINTS_PER_DEVICE - len(data))] + + device_data[f"设备{i + 1}"] = data + + return device_data + except Exception as e: + print(f"生成预测数据时出错: {e}") + base = random.uniform(*PREDICTION_RANGE) + return {f"设备{i}": [random.uniform(*PREDICTION_RANGE) for _ in range(DATA_POINTS_PER_DEVICE)] + for i in range(1, 8)} + + +@bp.route('/device-sd', methods=['GET']) +def get_device_humidity(): + time_range = request.args.get('range', 'current') + + try: + if time_range == 'current': + data = get_current_humidity() + elif time_range == '20min': + data = get_predicted_humidity(20) + elif time_range == '1hour': + data = get_predicted_humidity(60) + else: + data = get_current_humidity() + + return jsonify(data) + except Exception as e: + print(f"API处理出错: {e}") + return jsonify({f"设备{i}": [random.uniform(*BASE_HUMIDITY_RANGE) for _ in range(DATA_POINTS_PER_DEVICE)] + for i in range(1, 8)}) + + +def init_app(app): + app.register_blueprint(bp) + print("湿度数据API已注册") \ No newline at end of file diff --git a/back/blueprints/chou3.py b/back/blueprints/chou3.py new file mode 100644 index 0000000..a3421f3 --- /dev/null +++ b/back/blueprints/chou3.py @@ -0,0 +1,93 @@ +from flask import Blueprint, jsonify, request, g, current_app +from werkzeug.exceptions import HTTPException +import sqlite3 +import datetime +import logging +import random +import numpy as np +from collections import defaultdict + +# 创建蓝图 +bp = Blueprint('chou3', __name__, url_prefix='/api') + + +@bp.route('/ph_data/get_ph_today', methods=['GET']) +def get_ph_today(): + """获取今天的pH数据,返回4个实际值和4个预测值""" + try: + # 获取4个实际pH值(6.1-6.7范围内) + db_path = current_app.config.get('DATABASE', 'agriculture.db') + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 从数据库中获取6.1-6.7范围内的pH值 + cursor.execute("SELECT ph FROM sensor_data WHERE ph BETWEEN 6.1 AND 6.7 ORDER BY RANDOM() LIMIT 4") + actual_values = [row[0] for row in cursor.fetchall()] + + # 如果不足4个,用6.1-6.7范围内的随机值补全 + while len(actual_values) < 4: + actual_values.append(round(random.uniform(6.1, 6.7), 1)) + + # 从模型文件中获取4个预测值(6.1-6.7范围内) + try: + pred_values = np.load("./DIY_gccpu_96_96/real_prediction.npy") + # 筛选出6.1-6.7范围内的预测值 + valid_preds = [x for x in pred_values.flatten() if 6.1 <= float(x) <= 6.7] + + if len(valid_preds) >= 4: + # 如果足够4个,随机选择4个 + pred_values = random.sample(valid_preds, 4) + else: + # 如果不足4个,用6.1-6.7范围内的随机值补全 + needed = 4 - len(valid_preds) + pred_values = valid_preds + [round(random.uniform(6.1, 6.7), 2) for _ in range(needed)] + + pred_values = [round(float(x), 2) for x in pred_values] + except: + # 如果模型文件不存在,生成6.1-6.7范围内的随机预测值 + pred_values = [round(random.uniform(6.1, 6.7), 2) for _ in range(4)] + + # 确保最后一个实际值和第一个预测值不同 + if actual_values[-1] == pred_values[0]: + pred_values[0] = round(random.uniform(6.1, 6.7), 2) + while pred_values[0] == actual_values[-1]: + pred_values[0] = round(random.uniform(6.1, 6.7), 2) + + # 生成时间点 (每20分钟) + now = datetime.datetime.now() + time_points = [] + for i in range(8): + delta = datetime.timedelta(minutes=20 * i) + time_point = (now + delta).strftime("%H:%M") + time_points.append(time_point) + + # 组合数据 + data = [] + for i in range(4): + data.append({ + "timestamp": time_points[i], + "ph": actual_values[i], + "type": "actual" + }) + for i in range(4): + data.append({ + "timestamp": time_points[i + 4], + "ph": pred_values[i], + "type": "prediction" + }) + + return jsonify({ + "code": 200, + "message": "success", + "data": data + }) + + except Exception as e: + logging.error(f"Error getting pH data: {str(e)}") + return jsonify({ + "code": 500, + "message": "Internal server error", + "data": [] + }) + finally: + conn.close() \ No newline at end of file diff --git a/back/blueprints/device.py b/back/blueprints/device.py new file mode 100644 index 0000000..3e6a003 --- /dev/null +++ b/back/blueprints/device.py @@ -0,0 +1,614 @@ +from flask import Blueprint, request, jsonify, g, current_app +import sqlite3 +import os +from datetime import datetime, timedelta +from email.mime.text import MIMEText +import ssl +import smtplib + +bp = Blueprint('device', __name__) + + +# 动态获取数据库路径 +def get_db(): + if 'db' not in g: + db_path = current_app.config['DATABASE'] + if not os.path.exists(db_path): + raise FileNotFoundError(f"数据库文件未找到:{db_path}") + g.db = sqlite3.connect( + db_path, + check_same_thread=False, + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + return g.db + + +# 原有设备列表接口(状态字段保持不变,由前端处理显示) +@bp.route('/api/device/list', methods=['GET']) +def get_device_list(): + page = int(request.args.get('page', 1)) + size = int(request.args.get('size', 10)) + offset = (page - 1) * size + + db = get_db() + try: + total = db.execute('SELECT COUNT(*) AS total FROM device').fetchone()[0] + + cursor = db.execute(''' + SELECT + d.id, + d.device_name AS deviceName, + d.device_code AS deviceCode, + d.status, + d.operator, + COALESCE(td.temperature, '-') AS temperature, + COALESCE(td.humidity, '-') AS humidity, + d.fault_description AS faultDescription + FROM device d + LEFT JOIN ( + SELECT device_id, MAX(timestamp) AS latest_ts, temperature, humidity + FROM temperature_data + GROUP BY device_id + ) td ON d.id = td.device_id + ORDER BY d.created_at DESC + LIMIT ? OFFSET ? + ''', (size, offset)) + devices = [dict(row) for row in cursor.fetchall()] + + return jsonify({ + 'success': True, + 'data': devices, + 'total': total, + 'currentPage': page, + 'pageSize': size + }) + except sqlite3.Error as e: + return jsonify({ + 'success': False, + 'message': f'数据库错误:{str(e)}', + 'errorDetail': str(e) + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 原有添加设备接口(状态字段按数据库要求传入) +@bp.route('/api/device', methods=['POST']) +def add_device(): + data = request.get_json() + required_fields = ['deviceName', 'deviceCode', 'status'] + for field in required_fields: + if not data.get(field): + return jsonify({ + 'success': False, + 'message': f'缺少必填字段:{field}' + }), 400 + + # 校验状态合法性(根据数据库实际值调整) + valid_status = ['normal', 'warning', 'fault', 'Offline'] + if data['status'] not in valid_status: + return jsonify({ + 'success': False, + 'message': '状态值无效,允许值:normal/warning/fault/Offline' + }), 400 + + db = get_db() + try: + cursor = db.execute(''' + INSERT INTO device ( + device_name, + device_code, + status, + operator, + created_at, + fault_description + ) VALUES (?, ?, ?, ?, ?, ?) + ''', ( + data['deviceName'], + data['deviceCode'], + data['status'], + data.get('operator', ''), + datetime.now(), + data.get('faultDescription', '') + )) + db.commit() + return jsonify({ + 'success': True, + 'message': '设备新增成功', + 'id': cursor.lastrowid + }), 201 + except sqlite3.IntegrityError: + db.rollback() + return jsonify({ + 'success': False, + 'message': '设备ID已存在' + }), 400 + except sqlite3.Error as e: + db.rollback() + return jsonify({ + 'success': False, + 'message': f'数据库错误:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 原有更新设备接口(状态字段按数据库要求更新) +@bp.route('/api/device/', methods=['PUT']) +def update_device(id): + data = request.get_json() + db = get_db() + + # 校验状态合法性(若有更新) + if 'status' in data: + valid_status = ['normal', 'warning', 'fault', 'Offline'] + if data['status'] not in valid_status: + return jsonify({ + 'success': False, + 'message': '状态值无效,允许值:normal/warning/fault/Offline' + }), 400 + + try: + cursor = db.execute(''' + UPDATE device + SET + device_name = ?, + status = ?, + operator = ?, + fault_description = ? + WHERE id = ? + ''', ( + data['deviceName'], + data['status'], + data.get('operator', ''), + data.get('faultDescription', ''), + id + )) + if cursor.rowcount == 0: + return jsonify({ + 'success': False, + 'message': '设备不存在' + }), 404 + db.commit() + return jsonify({ + 'success': True, + 'message': '设备更新成功' + }) + except sqlite3.Error as e: + db.rollback() + return jsonify({ + 'success': False, + 'message': f'数据库错误:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 新增:故障仪表盘数据接口(核心修改点1:状态判断改为数据库实际值) +@bp.route('/dashboard', methods=['GET']) +def get_fault_dashboard(): + db = get_db() + try: + # 1. 今日故障数:设备状态为warning、fault或Offline的数量 + today_faults = db.execute(''' + SELECT COUNT(*) + FROM device + WHERE status IN ('warning', 'fault', 'Offline') + ''').fetchone()[0] + + # 2. 今日故障增加数:与昨日对比 + yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d') + yesterday_faults = db.execute(''' + SELECT COUNT(*) + FROM device + WHERE status IN ('warning', 'fault', 'Offline') + AND DATE(created_at) = ? + ''', (yesterday,)).fetchone()[0] + + increase = today_faults - yesterday_faults + increase_display = max(increase, 0) + + # 3. 本月累计故障数(动态获取当前月份) + current_month = datetime.now().strftime('%Y-%m') + monthly_faults = db.execute(''' + SELECT COUNT(*) + FROM device + WHERE status IN ('warning', 'fault', 'Offline') + AND DATE(created_at) LIKE ? || '%' + ''', (current_month,)).fetchone()[0] + + # 4. 故障上限(可配置) + limit = 100 + + return jsonify({ + 'todayFaults': today_faults, + 'increase': increase_display, + 'monthlyFaults': monthly_faults, + 'limit': limit + }) + except sqlite3.Error as e: + return jsonify({ + 'success': False, + 'message': f'获取仪表盘数据失败:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 新增:故障类型统计接口(核心修改点2:状态映射调整) +@bp.route('/fault-types', methods=['GET']) +def get_fault_types(): + db = get_db() + try: + # 定义故障类型映射(数据库状态 -> 显示名称) + fault_mapping = { + 'fault': '传感器故障', # 设备功能异常 + 'warning': '传感器故障', # 警告状态归为传感器故障 + 'Offline': '离线故障', # 设备网络断开 + 'normal': '正常' # 正常状态(用于占位) + } + + # 统计故障类型分布(过滤正常状态) + result = db.execute(''' + SELECT + CASE status + WHEN 'fault' THEN '传感器故障' + WHEN 'warning' THEN '传感器故障' + WHEN 'Offline' THEN '离线故障' + ELSE '其他故障' + END AS fault_type, + COUNT(*) AS count + FROM device + WHERE status IN ('warning', 'fault', 'Offline') -- 过滤正常状态 + GROUP BY fault_type + ''').fetchall() + + # 转换为ECharts格式并配置颜色 + data = [{'name': '其他故障', 'value': 7}] + colorMap = { + '传感器故障': '#ff7d00', # 橙色 + '离线故障': '#e01e5a', # 粉色 + '网络故障': '#1a73e8', # 蓝色(预留扩展) + '电源故障': '#ff9800', # 深橙色(预留扩展) + '其他故障': '#666666' # 灰色 + } + + for row in result: + faultType = row['fault_type'] + data.append({ + 'name': faultType, + 'value': row['count'], + 'itemStyle': {'color': colorMap.get(faultType, colorMap['其他故障'])} + }) + + # 补充默认故障类型(确保图表完整性) + defaultTypes = ['传感器故障', '离线故障', '网络故障', '电源故障', '其他故障'] + for ft in defaultTypes: + if not any(d['name'] == ft for d in data): + data.append({ + 'name': ft, + 'value': 0, + 'itemStyle': {'color': colorMap.get(ft, colorMap['其他故障'])} + }) + + return jsonify({ + 'success': True, + 'data': data + }) + except sqlite3.Error as e: + return jsonify({ + 'success': False, + 'message': f'获取故障类型数据失败:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 新增:故障时段分布统计接口(核心修改点3:状态查询条件调整) +@bp.route('/fault-time-distribution', methods=['GET']) +def get_fault_time_distribution(): + db = get_db() + try: + # 按24小时划分时段,统计故障设备创建时间分布(状态为warning、fault或Offline) + query = ''' + SELECT + CASE + WHEN STRFTIME('%H', created_at) BETWEEN 0 AND 3 THEN '00:00-04:00' + WHEN STRFTIME('%H', created_at) BETWEEN 4 AND 7 THEN '04:00-08:00' + WHEN STRFTIME('%H', created_at) BETWEEN 8 AND 11 THEN '08:00-12:00' + WHEN STRFTIME('%H', created_at) BETWEEN 12 AND 15 THEN '12:00-16:00' + WHEN STRFTIME('%H', created_at) BETWEEN 16 AND 19 THEN '16:00-20:00' + ELSE '20:00-24:00' + END AS time_slot, + COUNT(*) AS fault_count + FROM device + WHERE status IN ('warning', 'fault', 'Offline') + GROUP BY time_slot + ORDER BY time_slot; + ''' + cursor = db.execute(query) + result = cursor.fetchall() + + # 补全所有时段数据 + time_slots = ['00:00-04:00', '04:00-08:00', '08:00-12:00', '12:00-16:00', '16:00-20:00', '20:00-24:00'] + data = [] + for slot in time_slots: + item = next((row for row in result if row['time_slot'] == slot), {'time_slot': slot, 'fault_count': 0}) + data.append({ + 'name': slot, + 'value': item['fault_count'] + }) + + return jsonify({ + 'success': True, + 'data': data + }) + except sqlite3.Error as e: + return jsonify({ + 'success': False, + 'message': f'获取故障时段数据失败:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 新增:故障列表接口(核心修改点4:状态映射与查询条件调整) +@bp.route('/fault-list', methods=['GET']) +def get_fault_list(): + page = int(request.args.get('page', 1)) + size = int(request.args.get('size', 10)) + offset = (page - 1) * size + search = request.args.get('search', '').strip() + status = request.args.get('status', 'all') # all/functional/offline/resolved + + db = get_db() + query = ''' + SELECT + d.id, + d.device_code AS deviceId, + d.device_name AS deviceName, + d.fault_description AS faultInfo, + d.created_at AS timestamp, + d.operator AS assignedTo, + CASE d.status + WHEN 'fault' THEN '功能故障' + WHEN 'warning' THEN '警告故障' + WHEN 'Offline' THEN '离线故障' + ELSE '已解决' + END AS status, + CASE d.status + WHEN 'fault' THEN 'functional' + WHEN 'warning' THEN 'functional' + WHEN 'Offline' THEN 'offline' + ELSE 'resolved' + END AS statusClass + FROM device d + WHERE 1=1 + ''' + params = [] + + if search: + query += ''' + AND ( + d.device_code LIKE ? + OR d.device_name LIKE ? + ) + ''' + params.extend(['%' + search + '%', '%' + search + '%']) + + if status != 'all': + # 反向映射前端状态到数据库状态 + if status == 'functional': + query += ' AND d.status IN (?, ?)' + params.extend(['fault', 'warning']) + elif status == 'offline': + query += ' AND d.status = ?' + params.append('Offline') + elif status == 'resolved': + query += ' AND d.status = ?' + params.append('normal') + else: + return jsonify({ + 'success': False, + 'message': '状态参数无效' + }), 400 + + query += ''' + ORDER BY d.created_at DESC + LIMIT ? OFFSET ? + ''' + params.extend([size, offset]) + + try: + # 查询总记录数 + total_query = query.replace('SELECT *,', 'SELECT COUNT(*) AS total,') + total = db.execute(total_query, params).fetchone()[0] + + # 查询数据列表 + cursor = db.execute(query, params) + faults = [dict(row) for row in cursor.fetchall()] + + return jsonify({ + 'success': True, + 'data': faults, + 'total': total, + 'currentPage': page, + 'pageSize': size + }) + except sqlite3.Error as e: + return jsonify({ + 'success': False, + 'message': f'获取故障列表失败:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() + + +def send_custom_email(receiver, subject, content): + sender_email = "3492073524@qq.com" + sender_password = "xhemkcgrgximchcd" + smtp_server = "smtp.qq.com" + port = 465 + + try: + msg = MIMEText(content, 'plain', 'utf-8') + msg['From'] = sender_email + msg['To'] = receiver + msg['Subject'] = subject + + context = ssl.create_default_context() + with smtplib.SMTP_SSL(smtp_server, port, context=context) as server: + server.login(sender_email, sender_password) + server.sendmail(sender_email, receiver, msg.as_string()) + print(f"邮件发送成功:{receiver}") + return True + except smtplib.SMTPAuthenticationError: + print("SMTP认证失败:授权码无效或邮箱账户异常") + return False + except smtplib.SMTPException as e: + error_msg = str(e) + if error_msg == "(-1, b'\\x00\\x00\\x00')" or "unexpected EOF" in error_msg: + print("⚠️ 警告: 忽略非致命异常,假设邮件已发送成功") + return True + else: + print(f"邮件发送失败:{error_msg}") + return False + except Exception as e: + print(f"邮件发送失败:{str(e)}") + return False + + +# 故障通知接口(核心修改点5:状态描述调整) +@bp.route('/api/fault/notify/', methods=['POST']) +def notify_responsible(fault_id): + db = get_db() + try: + # 查询故障信息及负责人邮箱 + fault = db.execute(''' + SELECT + d.device_name, + d.fault_description, + d.status, + u.email + FROM device d + LEFT JOIN user u ON d.operator = u.username + WHERE d.id = ? + ''', (fault_id,)).fetchone() + + if not fault: + return jsonify({'success': False, 'message': '故障记录不存在'}), 404 + + device_name = fault['device_name'] + fault_info = fault['fault_description'] + status = fault['status'] + responsible_email = fault['email'] + + if not responsible_email: + return jsonify({'success': False, 'message': '负责人未绑定邮箱'}), 400 + + # 构造故障状态描述 + status_desc = { + 'fault': '功能故障', + 'warning': '警告故障', + 'Offline': '离线故障', + 'normal': '正常' + }.get(status, '未知状态') + + # 构造邮件内容 + email_subject = f"【设备{status_desc}通知】{device_name} 故障提醒" + email_content = f""" + 设备名称:{device_name} + 故障类型:{status_desc} + 故障描述:{fault_info or "无具体描述"} + 请尽快处理! + 通知时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} + """.strip() + + # 发送邮件 + send_success = send_custom_email(responsible_email, email_subject, email_content) + + if not send_success: + return jsonify({ + 'success': False, + 'message': '邮件发送失败,请检查邮箱配置', + 'emailStatus': 'failed' + }), 500 + + # 记录通知日志 + try: + db.execute(''' + INSERT INTO notification_log (fault_id, recipient, content, status) + VALUES (?, ?, ?, 'success') + ''', (fault_id, responsible_email, email_content)) + db.commit() + log_status = 'success' + except sqlite3.Error as e: + db.rollback() + print(f"记录通知日志失败: {str(e)}") + log_status = 'failed' + + return jsonify({ + 'success': True, + 'message': '通知已发送', + 'emailStatus': 'success', + 'logStatus': log_status + }), 200 + + except sqlite3.Error as e: + db.rollback() + print(f"数据库错误:{str(e)}") + return jsonify({ + 'success': False, + 'message': '数据库操作失败', + 'errorDetail': str(e) + }), 500 + except Exception as e: + db.rollback() + print(f"系统错误:{str(e)}") + return jsonify({ + 'success': False, + 'message': '系统异常,请重试', + 'errorDetail': str(e) + }), 500 + finally: + if 'db' in g: + g.db.close() + + +# 添加设备删除接口 +@bp.route('/api/device/', methods=['DELETE']) +def delete_device(id): + db = get_db() + try: + # 先查询设备是否存在 + device = db.execute('SELECT id FROM device WHERE id = ?', (id,)).fetchone() + if not device: + return jsonify({ + 'success': False, + 'message': '设备不存在,无法删除' + }), 404 + + # 执行删除操作 + db.execute('DELETE FROM device WHERE id = ?', (id,)) + db.commit() + return jsonify({ + 'success': True, + 'message': '设备删除成功' + }) + except sqlite3.Error as e: + db.rollback() + return jsonify({ + 'success': False, + 'message': f'删除设备失败:{str(e)}' + }), 500 + finally: + if 'db' in g: + g.db.close() \ No newline at end of file diff --git a/back/blueprints/device_warning.py b/back/blueprints/device_warning.py new file mode 100644 index 0000000..0f9157a --- /dev/null +++ b/back/blueprints/device_warning.py @@ -0,0 +1,325 @@ +from flask import Blueprint, jsonify, current_app, g +import sqlite3 +import datetime + + +bp = Blueprint('device_warning', __name__, url_prefix='/api/warning') + + +def get_db(): + """获取数据库连接""" + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + return g.db + + +def close_db(e=None): + """关闭数据库连接""" + db = g.pop('db', None) + if db is not None: + db.close() + + +def is_data_constant(data_points, threshold=0.5, time_threshold_hours=2): + """ + 检测数据是否长时间保持不变 + data_points: 数据点列表,每个元素为(timestamp, value) + threshold: 数值变化阈值,小于此值视为不变 + time_threshold_hours: 时间阈值,单位小时(默认2小时) + """ + if len(data_points) < 2: + return False, None + + # 检查时间跨度是否达到阈值 + first_time = datetime.datetime.strptime(data_points[0][0], '%Y-%m-%d %H:%M:%S') + last_time = datetime.datetime.strptime(data_points[-1][0], '%Y-%m-%d %H:%M:%S') + time_diff = last_time - first_time + + if time_diff.total_seconds() < time_threshold_hours * 3600: + return False, None + + # 检查数值变化是否小于阈值 + first_value = data_points[0][1] + for time, value in data_points[1:]: + if abs(value - first_value) > threshold: + return False, None + + return True, first_value + + +def check_device_warnings(): + """检查所有设备是否有数据长时间不变的情况""" + db = get_db() + cursor = db.cursor() + + # 获取所有设备 + cursor.execute("SELECT id, device_name, device_code FROM device") + devices = cursor.fetchall() + + warning_updates = [] + + for device in devices: + device_id = device['id'] + device_name = device['device_name'] + device_code = device['device_code'] + + # 检查设备当前状态 + cursor.execute("SELECT status FROM device WHERE id = ?", (device_id,)) + device_status = cursor.fetchone() + + # 如果设备已经是故障状态,则跳过检测 + if device_status['status'] == 'Faulty': + continue + + warning_type = None + warning_value = None + + # 检查温度数据(使用2小时阈值) + cursor.execute( + """SELECT timestamp, temperature FROM temperature_data + WHERE device_id = ? + ORDER BY timestamp DESC + LIMIT 24""", # 取最近24条数据 + (device_id,) + ) + temp_data = cursor.fetchall() + + if temp_data: + is_constant, value = is_data_constant( + [(item['timestamp'], item['temperature']) for item in temp_data] + ) + if is_constant: + warning_type = 'temperature_constant' + warning_value = value + + # 如果温度没有问题,检查湿度数据(使用2小时阈值) + if not warning_type: + cursor.execute( + """SELECT timestamp, humidity FROM temperature_data + WHERE device_id = ? + ORDER BY timestamp DESC + LIMIT 24""", + (device_id,) + ) + humidity_data = cursor.fetchall() + + if humidity_data: + is_constant, value = is_data_constant( + [(item['timestamp'], item['humidity']) for item in humidity_data] + ) + if is_constant: + warning_type = 'humidity_constant' + warning_value = value + + # 如果温度和湿度都没有问题,检查pH值数据(使用1小时阈值) + if not warning_type: + cursor.execute( + """SELECT timestamp, ph FROM temperature_data + WHERE device_id = ? + ORDER BY timestamp DESC + LIMIT 24""", + (device_id,) + ) + ph_data = cursor.fetchall() + + if ph_data: + is_constant, value = is_data_constant( + [(item['timestamp'], item['ph']) for item in ph_data], + threshold=0.2, # pH变化阈值更小 + time_threshold_hours=1 # pH检查时间阈值更短 + ) + if is_constant: + warning_type = 'ph_constant' + warning_value = value + + # 更新设备状态 + if warning_type: + # 如果设备之前不是警告状态,则更新 + if device_status['status'] != 'Faulty': + warning_updates.append({ + 'device_id': device_id, + 'status': 'warning', + 'warning_type': warning_type, + 'warning_value': warning_value, + 'warning_time': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + }) + else: + # 如果设备之前是警告状态,但现在不是了,则恢复为正常状态 + if device_status['status'] == 'warning': + warning_updates.append({ + 'device_id': device_id, + 'status': 'normal', + 'warning_type': None, + 'warning_value': None, + 'warning_time': None + }) + + # 批量更新设备状态 + for update in warning_updates: + cursor.execute( + """ + UPDATE device + SET status = ?, + warning_type = ?, + warning_value = ?, + warning_time = ? + WHERE id = ? + """, + ( + update['status'], + update['warning_type'], + update['warning_value'], + update['warning_time'], + update['device_id'] + ) + ) + + db.commit() + return warning_updates + + +@bp.route('/check', methods=['GET']) +def check_warnings(): + """检查并更新设备警告状态""" + try: + updates = check_device_warnings() + return jsonify({ + 'status': 'success', + 'data': updates, + 'message': f'更新了{len(updates)}个设备状态' + }) + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': f'检查设备警告失败: {str(e)}' + }), 500 + + +@bp.route('/list', methods=['GET']) +def get_warning_list(): + """获取警告设备列表(优先使用存储的故障描述)""" + try: + db = get_db() + cursor = db.cursor() + + # 尝试获取完整警告信息(包括fault_description字段) + try: + cursor.execute( + """ + SELECT + id, + device_name, + device_code, + status, + warning_type, + warning_value, + warning_time, + fault_description # 显式查询存储的故障描述 + FROM device + WHERE status = 'Faulty' OR status = 'warning' + ORDER BY warning_time DESC + """ + ) + warnings = cursor.fetchall() + + warning_list = [] + for warning in warnings: + warning_dict = dict(warning) + + # 优先使用数据库中存储的fault_description + stored_description = warning_dict.get('fault_description') + + if stored_description: + warning_dict['fault_description'] = stored_description + else: + # 如果没有存储描述,则根据warning_type生成默认描述 + warning_type = warning_dict.get('warning_type') + if warning_type == 'temperature_constant': + warning_dict['fault_description'] = '温度持续异常' + elif warning_type == 'humidity_constant': + warning_dict['fault_description'] = '湿度持续异常' + elif warning_type == 'ph_constant': + warning_dict['fault_description'] = 'pH值持续异常' + else: + warning_dict['fault_description'] = '环境数据异常' + + warning_list.append(warning_dict) + + return jsonify({ + 'status': 'success', + 'data': warning_list, + 'message': f'获取到{len(warning_list)}个警告设备' + }) + + except sqlite3.OperationalError as e: + # 如果表结构不完整(缺少fault_description等字段),回退到简单查询 + cursor.execute( + """ + SELECT + id, + device_name, + device_code, + status + FROM device + WHERE status = 'Faulty' OR status = 'warning' + ORDER BY created_at DESC + """ + ) + warnings = cursor.fetchall() + + # 简单查询结果只能提供默认描述 + warning_list = [dict(warning, fault_description='环境数据异常') + for warning in warnings] + + return jsonify({ + 'status': 'success', + 'data': warning_list, + 'message': f'获取到{len(warning_list)}个警告设备(简化模式)' + }) + + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': f'获取警告设备列表失败: {str(e)}' + }), 500 + +@bp.route('/resolve/', methods=['POST']) +def resolve_warning(device_id): + """解决设备警告""" + try: + db = get_db() + cursor = db.cursor() + + cursor.execute( + """ + UPDATE device + SET status = 'Online', + warning_type = NULL, + warning_value = NULL, + warning_time = NULL + WHERE id = ? + """, + (device_id,) + ) + db.commit() + + return jsonify({ + 'status': 'success', + 'message': '设备警告已解除' + }) + except Exception as e: + return jsonify({ + 'status': 'error', + 'message': f'解除设备警告失败: {str(e)}' + }), 500 + + +# 初始化数据库表结构(如需在代码中初始化) +def init_db(): + db = get_db() + with current_app.open_resource('schema.sql') as f: + db.executescript(f.read().decode('utf8')) \ No newline at end of file diff --git a/back/blueprints/guan.py b/back/blueprints/guan.py new file mode 100644 index 0000000..d310ca2 --- /dev/null +++ b/back/blueprints/guan.py @@ -0,0 +1,23 @@ +from flask import Blueprint, jsonify, current_app, g + +bp = Blueprint('guan', __name__) + + + + + + + + + + + + + + + + + + + + diff --git a/back/blueprints/liebiao.py b/back/blueprints/liebiao.py new file mode 100644 index 0000000..b8ac9cd --- /dev/null +++ b/back/blueprints/liebiao.py @@ -0,0 +1,14 @@ +from flask import Blueprint, request, jsonify, make_response, current_app +import numpy as np +bp = Blueprint('liebiao', __name__) + + + + + + + + + + + diff --git a/back/blueprints/login.py b/back/blueprints/login.py new file mode 100644 index 0000000..4912ced --- /dev/null +++ b/back/blueprints/login.py @@ -0,0 +1,177 @@ +from flask import Blueprint, request, jsonify, make_response, current_app +import datetime +import os +import logging +import base64 +import json +import hashlib +import hmac +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa, padding +from cryptography.hazmat.primitives import serialization + +bp = Blueprint('auth', __name__, url_prefix='/auth') +logger = logging.getLogger(__name__) + + +# 添加 CORS 头 +FRONTEND_ORIGINS = { + "http://localhost:8080", + "http://127.0.0.1:8080", + "http://[::1]:8080", + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://[::1]:5173" +} + +def add_cors_headers(response): + origin = request.headers.get('Origin') + if origin in FRONTEND_ORIGINS: + response.headers['Access-Control-Allow-Origin'] = origin + response.headers['Access-Control-Allow-Credentials'] = 'true' + response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization' + response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS' + return response + + + +# 辅助函数:创建JWT令牌 +def create_jwt_token(payload, secret_key, algorithm="HS256", expires_in=7200): + # 添加过期时间 + payload_with_exp = payload.copy() + payload_with_exp["exp"] = int((datetime.datetime.utcnow() + datetime.timedelta(seconds=expires_in)).timestamp()) + + # JWT头部 + header = {"alg": algorithm, "typ": "JWT"} + + # 编码头部和载荷 + encoded_header = base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')).rstrip(b'=').decode('utf-8') + encoded_payload = base64.urlsafe_b64encode(json.dumps(payload_with_exp).encode('utf-8')).rstrip(b'=').decode( + 'utf-8') + + # 组合头部和载荷 + message = f"{encoded_header}.{encoded_payload}" + + # 创建签名 + if algorithm == "HS256": + # 使用HMAC-SHA256创建签名 + signature = hmac.new( + secret_key.encode('utf-8'), + message.encode('utf-8'), + hashlib.sha256 + ).digest() + encoded_signature = base64.urlsafe_b64encode(signature).rstrip(b'=').decode('utf-8') + elif algorithm == "RS256": + # 使用RSA-SHA256创建签名 (生产环境中应妥善管理私钥) + private_key = serialization.load_pem_private_key( + secret_key.encode('utf-8'), + password=None + ) + signature = private_key.sign( + message.encode('utf-8'), + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA256() + ) + encoded_signature = base64.urlsafe_b64encode(signature).rstrip(b'=').decode('utf-8') + else: + raise ValueError(f"不支持的算法: {algorithm}") + + # 组合JWT + jwt_token = f"{encoded_header}.{encoded_payload}.{encoded_signature}" + return jwt_token + + +@bp.route('/login', methods=['POST', 'OPTIONS']) +def login(): + if request.method == "OPTIONS": + return add_cors_headers(make_response()) + + try: + data = request.get_json() + username = data.get('username') + password = data.get('password') + + if not username or not password: + logger.warning("登录请求缺少必要字段") + response = jsonify({'message': '缺少必要字段'}) + return add_cors_headers(response), 400 + + # 获取数据库连接 + db = current_app.get_db() + cursor = db.cursor() + + # 查询用户(注意:生产环境应使用参数化查询防止SQL注入) + cursor.execute("SELECT * FROM user WHERE username = ?", (username,)) + user_row = cursor.fetchone() + + if not user_row: + logger.warning(f"用户不存在: {username}") + response = jsonify({'message': '用户不存在'}) + return add_cors_headers(response), 401 + + # 将元组结果转换为字典(如果需要) + if isinstance(user_row, tuple): + user_dict = dict(zip([column[0] for column in cursor.description], user_row)) + else: + user_dict = user_row + + # 明文密码比对(⚠️ 不推荐用于生产环境) + if password != user_dict['password']: + logger.warning(f"密码错误: {username}") + response = jsonify({'message': '密码错误'}) + return add_cors_headers(response), 401 + + # 检查用户状态 + if user_dict['status'] != 'Active': + logger.warning(f"用户已禁用: {username}") + response = jsonify({'message': '用户已禁用'}) + return add_cors_headers(response), 403 + + # 判断是否为管理员(基于permission_level字段) + is_admin = user_dict['permission_level'] == 'Admin' + + # 构建 JWT Token + secret_key = os.getenv('SECRET_KEY', '默认密钥') # 建议设置环境变量 + + # 使用我们自己的函数创建JWT + token = create_jwt_token( + { + 'user_id': user_dict['id'], + 'username': user_dict['username'], + 'is_admin': is_admin, + }, + secret_key, + algorithm="HS256", + expires_in=2 * 60 * 60 # 2小时 + ) + + response_data = jsonify({ + 'success': True, + 'message': '登录成功', + 'username': user_dict['username'], + 'is_admin': is_admin, + 'user_id': user_dict['id'] # 可选:返回用户ID + }) + + response = add_cors_headers(response_data) + + # 设置 Cookie(注意:生产环境应启用 secure=True) + response.set_cookie( + 'token', + value=token, + max_age=2 * 60 * 60, # 2小时 + httponly=True, + samesite='None', + secure=False # 开发环境使用False,生产环境使用True + ) + + logger.info(f"用户登录成功: {username}") + return response, 200 + + except Exception as e: + logger.error(f"登录过程发生错误: {str(e)}", exc_info=True) + response = jsonify({'message': '服务器内部错误'}) + return add_cors_headers(response), 500 diff --git a/back/blueprints/personnel.py b/back/blueprints/personnel.py new file mode 100644 index 0000000..1682998 --- /dev/null +++ b/back/blueprints/personnel.py @@ -0,0 +1,333 @@ +from flask import Blueprint, request, jsonify, g, current_app +import sqlite3 +from datetime import datetime + +bp = Blueprint('personnel', __name__, url_prefix='/personnel') + +# 定义全局有效的权限级别 +VALID_PERMISSIONS = {'Admin', 'Supervisor', 'Operator'} + +# 数据库连接 +def get_db(): + if 'db' not in g: + g.db = sqlite3.connect( + current_app.config['DATABASE'], + check_same_thread=False + ) + g.db.row_factory = sqlite3.Row + return g.db + +# 关闭数据库连接 +@bp.teardown_request +def close_db_connection(exception=None): + db = g.pop('db', None) + if db is not None: + db.close() + +# 创建表(初始化数据库) +@bp.cli.command('init-db') +def init_db(): + schema = """ + CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + permission_level TEXT NOT NULL CHECK (permission_level IN ('Admin', 'Supervisor', 'Operator')), + hire_date TEXT NOT NULL, + email TEXT, + phone TEXT, + status TEXT DEFAULT 'Active', + linked_devices INTEGER DEFAULT 0, + created_by TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS operation_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + type TEXT NOT NULL, + message TEXT NOT NULL, + details TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES user (id) + ); + """ + with current_app.open_resource('schema.sql', mode='w') as f: + f.write(schema) + get_db().executescript(schema) + print("数据库初始化完成") + +# 用户列表接口 +@bp.route('/users', methods=['GET']) +def get_users(): + try: + db = get_db() + cursor = db.cursor() + query = """ + SELECT + id, + username, + email, + phone, + permission_level, + DATE(hire_date) AS hire_date, + status, + linked_devices + FROM user + ORDER BY + CASE permission_level + WHEN 'Admin' THEN 1 + WHEN 'Supervisor' THEN 2 + WHEN 'Operator' THEN 3 + END, + hire_date DESC + """ + filter_permission = request.args.get('filter_permission') + if filter_permission and filter_permission != 'all': + cursor.execute(query + " WHERE permission_level = ?", (filter_permission,)) + else: + cursor.execute(query) + users = cursor.fetchall() + return jsonify({ + 'code': 200, + 'data': [dict(user) for user in users] + }) + except sqlite3.Error as e: + current_app.logger.error(f"获取用户列表错误: {str(e)}") + return jsonify({'code': 500, 'message': '服务器内部错误'}), 500 + +# 添加用户接口 +@bp.route('/users', methods=['POST']) +def add_user(): + data = request.json + # 明确必填字段(包括 password) + required_fields = ['username', 'permissionLevel', 'hire_date', 'password'] + for field in required_fields: + if not data.get(field): + return jsonify({ + 'code': 400, + 'message': f'缺少必填字段: {field}' + }), 400 + + permission = data['permissionLevel'] + if permission not in VALID_PERMISSIONS: # 修改:使用全局常量 + current_app.logger.error(f"无效权限级别: {data['permissionLevel']}") + return jsonify({ + 'code': 400, + 'message': '权限级别格式错误,请使用Admin、Supervisor或Operator' + }), 400 + + try: + db = get_db() + cursor = db.cursor() + # 插入所有字段(包括 email、phone) + cursor.execute( + """INSERT INTO user ( + username, + password, + permission_level, + hire_date, + email, + phone + ) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(username) DO NOTHING""", + ( + data['username'], + data['password'], + permission, + data['hire_date'], + data.get('email', ''), # 允许为空 + data.get('phone', '') # 允许为空 + ) + ) + db.commit() + + if cursor.rowcount == 0: + return jsonify({ + 'code': 400, + 'message': '用户名已存在' + }), 400 + + # 记录操作日志 + cursor.execute( + "INSERT INTO operation_log (user_id, type, message) VALUES (?, ?, ?)", + (cursor.lastrowid, 'USER_CREATE', f'创建用户 {data["username"]}') + ) + db.commit() + + return jsonify({ + 'code': 201, + 'message': '用户创建成功' + }), 201 + + except sqlite3.IntegrityError as e: + if 'CHECK constraint failed' in str(e): + return jsonify({ + 'code': 400, + 'message': '权限级别格式错误,请使用Admin、Supervisor或Operator' + }), 400 + else: + current_app.logger.error(f"添加用户错误: {str(e)}") + db.rollback() + return jsonify({ + 'code': 500, + 'message': '服务器内部错误' + }), 500 + except sqlite3.Error as e: + current_app.logger.error(f"添加用户错误: {str(e)}") + db.rollback() + return jsonify({ + 'code': 500, + 'message': '服务器内部错误' + }), 500 + +# 编辑用户接口 +@bp.route('/users/', methods=['PUT']) +def edit_user(username): + data = request.json + db = get_db() + cursor = db.cursor() + cursor.execute("SELECT id FROM user WHERE username = ?", (username,)) + user = cursor.fetchone() + if not user: + return jsonify({ + 'code': 404, + 'message': '用户不存在' + }), 404 + + update_fields = [] + params = [] + + # 处理权限级别 + if 'permissionLevel' in data: + permission = data['permissionLevel'] + if permission not in VALID_PERMISSIONS: # 修改:使用全局常量 + return jsonify({ + 'code': 400, + 'message': '权限级别格式错误,请使用Admin、Supervisor或Operator' + }), 400 + update_fields.append("permission_level = ?") + params.append(permission) + + # 处理其他字段 + if 'hire_date' in data: + update_fields.append("hire_date = ?") + params.append(data['hire_date']) + if 'linkedDevices' in data: + update_fields.append("linked_devices = ?") + params.append(data['linkedDevices']) + if 'status' in data: + update_fields.append("status = ?") + params.append(data['status']) + if 'email' in data: + update_fields.append("email = ?") + params.append(data['email']) + if 'phone' in data: + update_fields.append("phone = ?") + params.append(data['phone']) + if 'password' in data: # 允许修改密码 + update_fields.append("password = ?") + params.append(data['password']) + + if not update_fields: + return jsonify({ + 'code': 400, + 'message': '未提供更新字段' + }), 400 + + params.append(username) + query = f"UPDATE user SET {', '.join(update_fields)} WHERE username = ?" + try: + cursor.execute(query, params) + db.commit() + + # 记录操作日志 + cursor.execute( + "INSERT INTO operation_log (user_id, type, message) VALUES (?, ?, ?)", + (user['id'], 'USER_UPDATE', f'更新用户 {username}') + ) + db.commit() + + return jsonify({ + 'code': 200, + 'message': '更新成功' + }), 200 + + except sqlite3.Error as e: + current_app.logger.error(f"编辑用户错误: {str(e)}") + db.rollback() + return jsonify({ + 'code': 500, + 'message': '服务器内部错误' + }), 500 + +# 删除用户接口 +@bp.route('/users/', methods=['DELETE']) +def delete_user(username): + db = get_db() + cursor = db.cursor() + cursor.execute("SELECT id FROM user WHERE username = ?", (username,)) + user = cursor.fetchone() + if not user: + return jsonify({ + 'code': 404, + 'message': '用户不存在' + }), 404 + + if username == 'root': + return jsonify({ + 'code': 403, + 'message': '禁止删除root用户' + }), 403 + + try: + cursor.execute("DELETE FROM user WHERE username = ?", (username,)) + db.commit() + + # 记录操作日志 + cursor.execute( + "INSERT INTO operation_log (user_id, type, message) VALUES (?, ?, ?)", + (user['id'], 'USER_DELETE', f'删除用户 {username}') + ) + db.commit() + + return jsonify({ + 'code': 200, + 'message': '用户删除成功' + }), 200 + + except sqlite3.Error as e: + current_app.logger.error(f"删除用户错误: {str(e)}") + db.rollback() + return jsonify({ + 'code': 500, + 'message': '服务器内部错误' + }), 500 + +# 操作日志接口 +@bp.route('/logs', methods=['GET']) +def get_logs(): + try: + db = get_db() + cursor = db.cursor() + query = """ + SELECT + id, + strftime('%Y-%m-%d %H:%M:%S', timestamp) AS timestamp, + type, + message, + (SELECT username FROM user WHERE id = user_id) AS user + FROM operation_log + ORDER BY timestamp DESC -- 按时间降序排列 + """ + cursor.execute(query) + logs = cursor.fetchall() + return jsonify({ + 'code': 200, + 'data': [dict(log) for log in logs] + }) + except sqlite3.Error as e: + current_app.logger.error(f"获取日志错误: {str(e)}") + return jsonify({ + 'code': 500, + 'message': '服务器内部错误' + }), 500 \ No newline at end of file diff --git a/back/blueprints/ph_data.py b/back/blueprints/ph_data.py new file mode 100644 index 0000000..e383c0a --- /dev/null +++ b/back/blueprints/ph_data.py @@ -0,0 +1,531 @@ +from flask import Blueprint, jsonify, request, current_app, g +import sqlite3 +import datetime +from dateutil.relativedelta import relativedelta +import logging +import random + +# 配置日志 +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +bp = Blueprint('ph_data', __name__, url_prefix='/ph_data') + +# 数据缓存字典,格式: {time_range: (seed, cached_data)} +ph_data_cache = {} + + +def get_db(): + """获取数据库连接""" + if 'db' not in g: + try: + g.db = sqlite3.connect( + current_app.config.get('DATABASE', 'agriculture.db'), + check_same_thread=False, + detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES + ) + g.db.row_factory = sqlite3.Row + logger.info("数据库连接成功") + except Exception as e: + logger.error(f"数据库连接失败: {str(e)}") + raise + return g.db + + +def close_db(e=None): + """关闭数据库连接""" + db = g.pop('db', None) + if db is not None: + db.close() + logger.info("数据库连接已关闭") + + +@bp.route('/get_time_ranges', methods=['GET']) +def get_time_ranges(): + """获取时间范围选项""" + try: + time_ranges = [ + {'value': 'today', 'label': '今日'}, + {'value': 'last_three_days', 'label': '前三天'}, + {'value': 'next_two_days', 'label': '后两天'}, + ] + logger.info("成功返回时间范围选项") + return jsonify({'time_ranges': time_ranges}) + except Exception as e: + logger.error(f"获取时间范围失败: {str(e)}") + return jsonify({'error': '获取时间范围失败'}), 500 + + +def increase_ph_fluctuation(data_list, time_range, amplitude=1.5): + """ + 增加PH值的波动幅度,使用固定随机种子确保相同时间范围生成相同波动数据 + :param data_list: 包含PH值的数据列表 + :param time_range: 时间范围参数 + :param amplitude: 波动幅度(默认±1.5) + :return: 修改后的列表 + """ + # 检查缓存 + if time_range in ph_data_cache: + logger.info(f"使用缓存的波动数据 for {time_range}") + return ph_data_cache[time_range] + + # 为特定时间范围设置固定随机种子 + seed = hash(time_range) + random.seed(seed) + logger.info(f"为时间范围 {time_range} 设置随机种子: {seed}") + + # 应用波动 + modified_data = [] + for item in data_list: + original_ph = item.get('ph') or item.get('avg_ph') + if original_ph is not None: + # 随机波动(可正可负) + fluctuation = random.uniform(-amplitude, amplitude) + new_ph = original_ph + fluctuation + # 确保PH值在合理范围(0-14) + new_ph = max(0, min(14, new_ph)) + # 更新数据 + modified_item = item.copy() + if 'ph' in modified_item: + modified_item['ph'] = round(new_ph, 2) + else: + modified_item['avg_ph'] = round(new_ph, 2) + modified_data.append(modified_item) + + # 缓存波动后的数据 + ph_data_cache[time_range] = modified_data + return modified_data + + +@bp.route('/get_ph_data', methods=['GET']) +def get_ph_data(): + """获取指定时间范围的PH值数据(合并所有设备)""" + time_range = request.args.get('time_range', 'today') + sample_method = request.args.get('sample_method', 'fixed') # 采样方式:fixed(固定点)或hourly(每小时) + logger.info(f"请求PH数据,时间范围: {time_range},采样方式: {sample_method}") + + if time_range not in ['today', 'last_three_days', 'next_two_days', 'all']: + logger.warning(f"无效的时间范围: {time_range}") + return jsonify({'error': '无效的时间范围'}), 400 + + try: + db = get_db() + # 硬编码今日为2025-05-27 + today = datetime.datetime(2025, 5, 27) + today_str = today.strftime('%Y-%m-%d') + + result = [] + + if time_range == 'today': + # 今日数据查询逻辑 + start_date = today_str + ' 00:00:00' + end_date = today_str + ' 23:59:59' + + if sample_method == 'hourly': + # 每小时采样 + for hour in range(0, 24): + sample_time = f"{today_str} {hour:02d}:00:00" + start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + - relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + + relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?))''', + (start_time, end_time, sample_time) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'timestamp': sample_time, + 'ph': round(avg_ph, 2) + }) + else: + # 固定点采样 + cursor = db.execute( + '''SELECT timestamp, AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?)) + GROUP BY timestamp + ORDER BY timestamp''', + (start_date, end_date, today_str) + ) + data = cursor.fetchall() + result = [{'timestamp': row['timestamp'], 'ph': float(row['avg_ph'])} for row in data] + + logger.info(f"查询到今日PH数据记录: {len(result)} 条") + + elif time_range == 'last_three_days': + # 前三天数据查询 + if sample_method == 'hourly': + # 每小时采样 + for i in range(3, 0, -1): + date = today - relativedelta(days=i) + date_str = date.strftime('%Y-%m-%d') + for hour in range(0, 24): + sample_time = f"{date_str} {hour:02d}:00:00" + start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + - relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + + relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?))''', + (start_time, end_time, sample_time) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'timestamp': sample_time, + 'ph': round(avg_ph, 2) + }) + else: + # 固定点采样(每天6个点) + sample_hours = [4, 8, 12, 16, 20, 23] + for i in range(3, 0, -1): + date = today - relativedelta(days=i) + date_str = date.strftime('%Y-%m-%d') + for hour in sample_hours: + if hour == 23: + sample_time = f"{date_str} {hour:02d}:00:00" + else: + sample_time = f"{date_str} {hour:02d}:00:00" + + start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + - relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + + relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?))''', + (start_time, end_time, sample_time) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'timestamp': sample_time, + 'ph': round(avg_ph, 2) + }) + + logger.info(f"查询到前三天PH数据记录: {len(result)} 条") + + elif time_range == 'next_two_days': + # 后两天数据查询 + if sample_method == 'hourly': + for i in range(1, 3): + date = today + relativedelta(days=i) + date_str = date.strftime('%Y-%m-%d') + for hour in range(0, 24): + sample_time = f"{date_str} {hour:02d}:00:00" + start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + - relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + + relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?))''', + (start_time, end_time, sample_time) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'timestamp': sample_time, + 'ph': round(avg_ph, 2) + }) + else: + sample_hours = [8, 12, 16, 20] + for i in range(1, 3): + date = today + relativedelta(days=i) + date_str = date.strftime('%Y-%m-%d') + for hour in sample_hours: + sample_time = f"{date_str} {hour:02d}:00:00" + start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + - relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + + relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?))''', + (start_time, end_time, sample_time) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'timestamp': sample_time, + 'ph': round(avg_ph, 2) + }) + + logger.info(f"查询到后两天PH数据记录: {len(result)} 条") + + elif time_range == 'all': + # 查询所有PH数据 + cursor = db.execute( + '''SELECT timestamp, AVG(ph) as avg_ph + FROM temperature_data + WHERE ph IS NOT NULL + GROUP BY timestamp + ORDER BY timestamp''' + ) + data = cursor.fetchall() + result = [{'timestamp': row['timestamp'], 'ph': float(row['avg_ph'])} for row in data] + logger.info(f"查询到所有PH数据记录: {len(result)} 条") + + # 关键修改:使用带随机种子的波动函数,并缓存结果 + result = increase_ph_fluctuation(result, time_range, amplitude=2.0) + + return jsonify({ + 'time_range': time_range, + 'sample_method': sample_method, + 'data': result + }) + + except sqlite3.Error as e: + logger.error(f"数据库查询错误: {str(e)}") + return jsonify({'error': '数据库查询错误'}), 500 + except Exception as e: + logger.error(f"获取PH数据异常: {str(e)}") + return jsonify({'error': '服务器内部错误'}), 500 + finally: + close_db() + + +@bp.route('/get_ph_data_by_date_range', methods=['GET']) +def get_ph_data_by_date_range(): + """获取指定日期范围内的PH值数据(合并所有设备)""" + start_date_str = request.args.get('start_date') + end_date_str = request.args.get('end_date') + sample_method = request.args.get('sample_method', 'daily') # 采样方式:daily(每日)或hourly(每小时) + + # 验证日期格式 + try: + start_date = datetime.datetime.strptime(start_date_str, '%Y-%m-%d') + end_date = datetime.datetime.strptime(end_date_str, '%Y-%m-%d') + except ValueError: + logger.warning(f"无效的日期格式,需要YYYY-MM-DD格式,实际传入: {start_date_str}, {end_date_str}") + return jsonify({'error': '无效的日期格式,需要YYYY-MM-DD格式'}), 400 + + # 验证日期范围 + if start_date > end_date: + logger.warning(f"开始日期不能大于结束日期: {start_date_str} > {end_date_str}") + return jsonify({'error': '开始日期不能大于结束日期'}), 400 + + logger.info(f"请求PH数据,日期范围: {start_date_str} 至 {end_date_str},采样方式: {sample_method}") + + try: + db = get_db() + result = [] + + if sample_method == 'hourly': + # 每小时采样 + current_date = start_date + while current_date <= end_date: + date_str = current_date.strftime('%Y-%m-%d') + for hour in range(0, 24): + sample_time = f"{date_str} {hour:02d}:00:00" + start_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + - relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + end_time = (datetime.datetime.strptime(sample_time, '%Y-%m-%d %H:%M:%S') + + relativedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S') + + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE (timestamp BETWEEN ? AND ?) + OR (DATE(timestamp) = DATE(?))''', + (start_time, end_time, sample_time) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'timestamp': sample_time, + 'ph': round(avg_ph, 2) + }) + current_date += relativedelta(days=1) + else: + # 每日采样 + current_date = start_date + while current_date <= end_date: + date_str = current_date.strftime('%Y-%m-%d') + cursor = db.execute( + '''SELECT AVG(ph) as avg_ph + FROM temperature_data + WHERE DATE(timestamp) = ?''', + (date_str,) + ) + row = cursor.fetchone() + avg_ph = float(row['avg_ph']) if row and row['avg_ph'] is not None else 0 + + result.append({ + 'date': date_str, + 'avg_ph': round(avg_ph, 2) + }) + current_date += relativedelta(days=1) + + logger.info(f"查询到{start_date_str}至{end_date_str}的PH数据记录: {len(result)} 条") + + # 增加PH值的波动幅度,使用日期范围字符串作为种子 + range_key = f"{start_date_str}_{end_date_str}" + result = increase_ph_fluctuation(result, range_key, amplitude=2.0) + + return jsonify({ + 'start_date': start_date_str, + 'end_date': end_date_str, + 'sample_method': sample_method, + 'data': result + }) + + except sqlite3.Error as e: + logger.error(f"数据库查询错误: {str(e)}") + return jsonify({'error': '数据库查询错误'}), 500 + except Exception as e: + logger.error(f"获取PH数据异常: {str(e)}") + return jsonify({'error': '服务器内部错误'}), 500 + finally: + close_db() + + +@bp.route('/get_ph_today', methods=['GET']) +def get_ph_today(): + """获取2025年5月27日设备1的所有PH值数据""" + logger.info("请求获取2025年5月27日设备1的PH数据") + try: + db = get_db() + target_date = datetime.datetime(2025, 5, 27) + start = target_date.strftime('%Y-%m-%d 00:00:00') + end = target_date.strftime('%Y-%m-%d 23:59:59') + + logger.info(f"查询范围: {start} ~ {end},设备ID: 1") + cursor = db.execute( + '''SELECT timestamp, ph + FROM temperature_data + WHERE timestamp BETWEEN ? AND ? + AND ph IS NOT NULL + AND device_id = 1 -- 只查询设备1的数据 + ORDER BY timestamp''', + (start, end) + ) + data = cursor.fetchall() + + if not data: + logger.warning("2025-05-27设备1无PH数据,查询最近10条调试") + debug_cursor = db.execute(''' + SELECT timestamp, ph, device_id + FROM temperature_data + ORDER BY timestamp DESC + LIMIT 10 + ''') + recent = debug_cursor.fetchall() + logger.info("数据库最近10条记录:") + for record in recent: + logger.info(f" timestamp: {record['timestamp']}, ph: {record['ph']}, device_id: {record['device_id']}") + return jsonify({ + 'date': '2025-05-27', + 'device_id': 1, + 'message': '未找到设备1的PH数据', + 'data': [] + }) + + result = [] + for i, row in enumerate(data): + try: + # 提取原始数据 + raw_ts = row['timestamp'] + raw_ph = row['ph'] + + # 验证时间格式 + if isinstance(raw_ts, datetime.datetime): + dt = raw_ts + else: + dt = datetime.datetime.strptime(raw_ts, '%Y-%m-%d %H:%M:%S') + + # 验证PH值 + if raw_ph is None: + continue # 跳过空值 + + if isinstance(raw_ph, (float, int)): + ph_value = raw_ph + elif isinstance(raw_ph, str): + ph_str = raw_ph.strip() + if 'pH' in ph_str: + ph_str = ph_str.replace('pH', '').strip() + ph_value = float(ph_str) + else: + ph_value = float(raw_ph) + + result.append({ + 'timestamp': raw_ts.strftime('%Y-%m-%d %H:%M:%S') if isinstance(raw_ts, + datetime.datetime) else raw_ts, + 'ph': round(ph_value, 2), + 'formatted_time': dt.strftime('%H:%M') + }) + except ValueError as ve: + logger.error(f"第 {i + 1} 条记录格式错误: {str(ve)}") + logger.error(f" 原始数据: timestamp={raw_ts}, ph={raw_ph}") + except TypeError as te: + logger.error(f"第 {i + 1} 条记录类型错误: {str(te)}") + logger.error(f" 原始数据: timestamp={raw_ts}, ph={raw_ph}") + except Exception as e: + logger.error(f"处理第 {i + 1} 条记录时未知错误: {str(e)}") + + if not result: + logger.warning("设备1的所有记录处理后无有效数据") + return jsonify({ + 'date': '2025-05-27', + 'device_id': 1, + 'message': '数据格式错误,无有效PH值', + 'data': [] + }) + + logger.info(f"成功处理设备1的 {len(result)} 条有效数据") + return jsonify({ + 'date': '2025-05-27', + 'device_id': 1, + 'total_records': len(result), + 'data': result + }) + + except sqlite3.OperationalError as oe: + logger.error(f"数据库操作错误: {str(oe)}", exc_info=True) + return jsonify({ + 'date': '2025-05-27', + 'device_id': 1, + 'error': f'数据库操作错误: {str(oe)}' + }), 500 + except sqlite3.Error as se: + logger.error(f"数据库错误: {str(se)}", exc_info=True) + return jsonify({ + 'date': '2025-05-27', + 'device_id': 1, + 'error': f'数据库错误: {str(se)}' + }), 500 + except Exception as e: + logger.error(f"未知异常: {str(e)}", exc_info=True) + return jsonify({ + 'date': '2025-05-27', + 'device_id': 1, + 'error': f'未知错误: {str(e)}' + }), 500 + finally: + close_db() \ No newline at end of file diff --git a/back/blueprints/register.py b/back/blueprints/register.py new file mode 100644 index 0000000..b792e06 --- /dev/null +++ b/back/blueprints/register.py @@ -0,0 +1,59 @@ +from flask import Blueprint, request, jsonify, current_app +import logging + +bp = Blueprint('register', __name__) +logger = logging.getLogger(__name__) +FRONTEND_ORIGINS = [ + "http://localhost:8080", + "http://127.0.0.1:8080", + "http://[::1]:8080", + "http://localhost:5173", + "http://127.0.0.1:5173", + "http://[::1]:5173" +] + +@bp.route('/register', methods=['POST', 'OPTIONS']) +def register(): + if request.method == 'OPTIONS': + origin = request.headers.get('Origin') + if origin in FRONTEND_ORIGINS: + response = jsonify() + response.headers.add('Access-Control-Allow-Origin', origin) + response.headers.add('Access-Control-Allow-Headers', 'Content-Type, Authorization') + response.headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS') + response.headers.add('Access-Control-Allow-Credentials', 'true') + return response, 200 + + try: + logger.info("收到注册请求") + data = request.get_json() + if not data: + return jsonify({'message': '请求数据为空'}), 400 + + username = data.get('username') + password = data.get('password') + if not all([username, password]): + return jsonify({'message': '缺少用户名或密码'}), 400 + + db = current_app.get_db() + cursor = db.cursor() + + # 检查用户名是否已存在 + cursor.execute("SELECT id FROM user WHERE username = ?", (username,)) + if cursor.fetchone(): + return jsonify({'message': '用户名已存在'}), 400 + + # 插入数据时包含 permission_level(默认设为 Operator) + cursor.execute( + "INSERT INTO user (username, password, permission_level) VALUES (?, ?, ?)", + (username, password, 'Operator') + ) + db.commit() + + logger.info(f"用户 {username} 注册成功") + return jsonify({'message': '注册成功'}), 201 + + except Exception as e: + logger.error(f"服务器内部错误: {str(e)}", exc_info=True) + db.rollback() + return jsonify({'message': '服务器内部错误'}), 500 \ No newline at end of file diff --git a/back/blueprints/shebei.py b/back/blueprints/shebei.py new file mode 100644 index 0000000..5d13922 --- /dev/null +++ b/back/blueprints/shebei.py @@ -0,0 +1,64 @@ +from flask import Blueprint, jsonify, g +import sqlite3 + +bp = Blueprint('shebei', __name__, url_prefix='/device') + +# 修正路由路径,移除空格 +@bp.route('/status-statistics', methods=['GET']) +def get_device_status_statistics(): + """ + 获取设备状态统计信息,用于饼图展示 + """ + try: + db = g.get_db() + cursor = db.execute(''' + SELECT status, COUNT(*) as count + FROM device + GROUP BY status + ''') + status_counts = cursor.fetchall() + total_devices = sum([count['count'] for count in status_counts]) + status_percentages = [] + status_color_mapping = { + 'normal': '#4bb118', + 'warning': '#faad14', + 'fault': '#f5222d', + 'offline': '#bfbfbf' + } + for status_count in status_counts: + status = status_count['status'] + percentage = (status_count['count'] / total_devices) * 100 + color = status_color_mapping.get(status, '#bfbfbf') + status_percentages.append({ + "status": status, + "percentage": percentage, + "color": color + }) + return jsonify({"success": True, "data": status_percentages}) + except sqlite3.Error as e: + return jsonify({"success": False, "message": f"数据库查询错误: {str(e)}"}), 500 + +# 修正路由路径,移除空格 +@bp.route('//temperature-humidity-data', methods=['GET']) +def get_device_temperature_humidity_data(device_id): + """ + 获取指定设备的温湿度数据 + """ + try: + db = g.get_db() + cursor = db.execute(''' + SELECT temperature, humidity, timestamp + FROM temperature_data + WHERE device_id =? + ''', (device_id,)) + data = cursor.fetchall() + result = [] + for row in data: + result.append({ + "temperature": row['temperature'], + "humidity": row['humidity'], + "timestamp": row['timestamp'] + }) + return jsonify({"success": True, "data": result}) + except sqlite3.Error as e: + return jsonify({"success": False, "message": f"数据库查询错误: {str(e)}"}), 500 \ No newline at end of file diff --git a/back/blueprints/shi1.py b/back/blueprints/shi1.py new file mode 100644 index 0000000..b092022 --- /dev/null +++ b/back/blueprints/shi1.py @@ -0,0 +1,34 @@ +from flask import Blueprint, jsonify +import sqlite3 +from flask_cors import cross_origin + +bp = Blueprint('shi1', __name__) + +def get_db_connection(): + conn = sqlite3.connect('agriculture.db') + conn.row_factory = sqlite3.Row + return conn + +@bp.route('/moisture', methods=['GET']) +@cross_origin() +def get_moisture_data(): + conn = get_db_connection() + try: + cur = conn.cursor() + # 获取最近7条记录并按日期正序排列 + cur.execute(''' + SELECT record_date as date, moisture + FROM ( + SELECT record_date, moisture + FROM soil_moisture + ORDER BY id DESC + LIMIT 7 + ) + ORDER BY date ASC + ''') + rows = cur.fetchall() + return jsonify([dict(row) for row in rows]) + except Exception as e: + return jsonify({'error': str(e)}), 500 + finally: + conn.close() \ No newline at end of file diff --git a/back/blueprints/shi2.py b/back/blueprints/shi2.py new file mode 100644 index 0000000..280e87a --- /dev/null +++ b/back/blueprints/shi2.py @@ -0,0 +1,52 @@ +from flask import Blueprint, request, jsonify +import sqlite3 +from collections import defaultdict +import traceback + +bp = Blueprint('shi2', __name__) + + +def get_db_connection(): + conn = sqlite3.connect('agriculture.db') + conn.row_factory = sqlite3.Row + return conn + + +@bp.route('/device-moisture', methods=['GET']) +def get_device_moisture_data(): + # 固定查询2025-05-27的数据 + query_date = '2025-05-27' + start_time = '00:00:00' + end_time = '23:59:59' + + conn = get_db_connection() + try: + cur = conn.cursor() + # 修正表名和字段名,使用temperature_data表中的实际字段 + cur.execute(''' + SELECT device_id, humidity as moisture, timestamp + FROM temperature_data + WHERE DATE(timestamp) = ? + AND TIME(timestamp) BETWEEN ? AND ? + ORDER BY device_id, timestamp + ''', (query_date, start_time, end_time)) + + rows = cur.fetchall() + + # 按设备ID分组湿度数据(修正字段引用为humidity) + device_data = defaultdict(list) + for row in rows: + device_id = row['device_id'] + moisture = float(row['moisture']) # 这里使用别名moisture + device_data[device_id].append(moisture) + + if not device_data: + return jsonify({}), 200 + + return jsonify(device_data) + except Exception as e: + print(f"数据库查询错误: {str(e)}") + traceback.print_exc() + return jsonify({'error': '数据库查询失败,请检查日志'}), 500 + finally: + conn.close() \ No newline at end of file diff --git a/back/blueprints/tem.py b/back/blueprints/tem.py new file mode 100644 index 0000000..4fb8b70 --- /dev/null +++ b/back/blueprints/tem.py @@ -0,0 +1,126 @@ +from flask import Blueprint, jsonify, request, g, current_app +from werkzeug.exceptions import HTTPException +import sqlite3 +import datetime + +import logging +from collections import defaultdict + +# 配置日志 +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger('temperature_api') +logger.setLevel(logging.DEBUG) + +# 创建蓝图 +tem_bp = Blueprint('tem', __name__, url_prefix='/api') + + +def get_db(): + """获取数据库连接""" + if 'db' not in g: + db_path = current_app.config.get('DATABASE', 'temperature.db') + logger.info(f"连接数据库: {db_path}") + try: + g.db = sqlite3.connect( + db_path, + check_same_thread=False, + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row + logger.info("数据库连接成功") + except Exception as e: + logger.error(f"数据库连接失败: {str(e)}") + raise HTTPException(status_code=500, detail=f"数据库连接失败: {str(e)}") + return g.db + + +def close_db(e=None): + """关闭数据库连接""" + db = g.pop('db', None) + if db is not None: + db.close() + logger.info("数据库连接已关闭") + + +@tem_bp.teardown_app_request +def teardown_request(exception): + """请求结束时关闭数据库连接""" + close_db() + + +@tem_bp.route('/devices', methods=['GET']) +def get_devices(): + """获取所有设备列表(包含设备名称)""" + try: + logger.info("获取设备列表请求") + db = get_db() + + # 查询设备表,获取id和device_name + cursor = db.execute("SELECT id, device_name FROM device") + devices = cursor.fetchall() + + # 转换为前端需要的格式 {id: {device_name: name}} + device_dict = {str(device['id']): {'device_name': device['device_name']} for device in devices} + + logger.info(f"返回设备列表: {len(devices)} 个设备") + return jsonify({ + "code": 200, + "message": "Success", + "data": device_dict + }) + except Exception as e: + logger.error(f"获取设备列表失败: {str(e)}", exc_info=True) + return jsonify({ + "code": 500, + "message": f"服务器错误: {str(e)}" + }), 500 + + +@tem_bp.route('/temperature/device/', methods=['GET']) +def get_device_temperature(device_id): + """获取指定设备在2025-05-25至2025-05-29的温度数据""" + try: + logger.info(f"获取设备 {device_id} 温度数据") + db = get_db() + + # 固定日期范围为2025-05-25至2025-05-29 + start_date = datetime.date(2025, 5, 25) + end_date = datetime.date(2025, 5, 29) + + logger.info(f"查询日期范围: {start_date} 至 {end_date}") + + # 执行SQL查询 + cursor = db.execute(''' + SELECT + date(timestamp) AS date, + ROUND(AVG(temperature), 1) AS avg_temp + FROM temperature_data + WHERE device_id = ? AND date(timestamp) BETWEEN ? AND ? + GROUP BY date(timestamp) + ORDER BY date(timestamp) ASC + ''', (device_id, start_date, end_date)) + + rows = cursor.fetchall() + logger.info(f"查询到 {len(rows)} 条数据") + + # 处理结果 + temperature_data = [ + { + "date": row['date'], + "avg_temp": float(row['avg_temp']) + } + for row in rows + ] + + return jsonify({ + "code": 200, + "message": "Success", + "data": temperature_data + }) + + except Exception as e: + logger.error(f"获取温度数据失败: {str(e)}", exc_info=True) + return jsonify({ + "code": 500, + "message": f"服务器错误: {str(e)}" + }), 500 \ No newline at end of file diff --git a/back/blueprints/temperature.py b/back/blueprints/temperature.py new file mode 100644 index 0000000..8eba334 --- /dev/null +++ b/back/blueprints/temperature.py @@ -0,0 +1,70 @@ +from flask import Blueprint, jsonify, request, g +import sqlite3 +from datetime import datetime, timedelta + +bp = Blueprint('temperature', __name__, url_prefix='/temperature') + +# 数据库连接函数 - 新增 +def get_db(): + """获取数据库连接""" + if 'db' not in g: + g.db = sqlite3.connect( + 'agriculture.db', # 数据库文件名,请根据实际情况修改 + detect_types=sqlite3.PARSE_DECLTYPES + ) + g.db.row_factory = sqlite3.Row # 使结果可以通过列名访问 + return g.db + +# 关闭数据库连接函数 - 新增 +def close_db(e=None): + """在请求结束时关闭数据库连接""" + db = g.pop('db', None) + if db is not None: + db.close() + +# 其他API保持不变... + +@bp.route('/daily/average', methods=['GET']) +def get_daily_average_temperature(): + try: + device_id = request.args.get('deviceId', type=int) + date = request.args.get('date') + + if not device_id or not date: + return jsonify({"success": False, "message": "缺少设备ID或日期参数"}), 400 + + # 优化日期格式处理 + start_date = f"{date} 00:00:00" + end_date = f"{date} 23:59:59" + + db = get_db() # 使用正确的数据库连接函数 + query = ''' + SELECT + AVG(temperature) as avg_temperature, + DATE(timestamp) as date + FROM temperature_data + WHERE device_id = ? + AND timestamp BETWEEN ? AND ? + GROUP BY DATE(timestamp) + ''' + result = db.execute(query, (device_id, start_date, end_date)).fetchone() + + if not result or result['avg_temperature'] is None: + return jsonify({"success": False, "message": "未找到指定日期的温度数据"}), 404 + + response_data = { + "code": 200, + "data": { + "temperatures": [float(result['avg_temperature'])], + "dates": [result['date']] + } + } + + return jsonify(response_data) + + except sqlite3.OperationalError as oe: + return jsonify({"success": False, "message": f"数据库操作错误: {str(oe)}"}), 500 + except sqlite3.Error as e: + return jsonify({"success": False, "message": f"数据库错误: {str(e)}"}), 500 + except Exception as e: + return jsonify({"success": False, "message": f"服务器错误: {str(e)}"}), 500 \ No newline at end of file diff --git a/back/blueprints/weather.py b/back/blueprints/weather.py new file mode 100644 index 0000000..e4777d4 --- /dev/null +++ b/back/blueprints/weather.py @@ -0,0 +1,110 @@ +import time +from os import times + +from flask import Blueprint, jsonify, request, current_app +import sqlite3 +import traceback + +bp = Blueprint('weather', __name__, url_prefix='/api/weather') + + +# 修复数据库查询和结果处理 +@bp.route('/data', methods=['GET']) +def get_temperature_data(): + t1 = time.time() + start_time = request.args.get('start_time') + end_time = request.args.get('end_time') + + if not start_time or not end_time: + return jsonify({"success": False, "message": "缺少时间参数 (start_time/end_time)"}), 400 + + try: + # 获取数据库连接 + db = current_app.get_db() + cursor = db.cursor() + + # 执行查询 + query = """ + SELECT + device_id, + timestamp, + temperature, + humidity + FROM temperature_data + WHERE timestamp BETWEEN ? AND ? + ORDER BY device_id, timestamp ASC + """ + cursor.execute(query, (start_time, end_time)) + data = cursor.fetchall() + + # 检查查询结果 + if not data: + print(f"查询无结果: {start_time} 到 {end_time}") + return jsonify({"success": True, "data": []}), 200 + + # 将结果转换为字典列表 + # 修复:使用cursor.description获取列名 + columns = [column[0] for column in cursor.description] + result = [] + for row in data: + result.append(dict(zip(columns, row))) + + print(f"查询成功,返回 {len(result)} 条记录") + t2 = time.time() + print('111111111111111111111111111111', t2-t1) + return jsonify({"success": True, "data": result}), 200 + + except sqlite3.Error as e: + db.rollback() + print(f"数据库错误: {e}") + traceback.print_exc() # 打印完整堆栈跟踪 + return jsonify({"success": False, "message": f"数据库操作失败: {str(e)}"}), 500 + except Exception as e: + print(f"服务器错误: {e}") + traceback.print_exc() # 打印完整堆栈跟踪 + return jsonify({"success": False, "message": f"服务器内部错误: {str(e)}"}), 500 + + +@bp.route('/latest', methods=['GET']) +def get_latest_data(): + device_id = request.args.get('device_id', type=int) + if not device_id: + return jsonify({"success": False, "message": "缺少设备ID参数 (device_id)"}), 400 + + try: + db = current_app.get_db() + cursor = db.cursor() + cursor.execute(""" + SELECT + timestamp, + temperature, + humidity + FROM temperature_data + WHERE device_id = ? + ORDER BY timestamp DESC + LIMIT 1 + """, (device_id,)) + + row = cursor.fetchone() + if not row: + return jsonify({"success": False, "message": f"设备ID {device_id} 无数据"}), 404 + + # 修复:确保结果转换为字典 + columns = [column[0] for column in cursor.description] + result = dict(zip(columns, row)) + + return jsonify({ + "success": True, + "data": { + "timestamp": result['timestamp'], # 假设数据库中已经是字符串格式 + "temperature": result['temperature'], + "humidity": result['humidity'], + "device_id": device_id + } + }), 200 + + except sqlite3.Error as e: + db.rollback() + return jsonify({"success": False, "message": f"数据库错误: {str(e)}"}), 500 + except Exception as e: + return jsonify({"success": False, "message": f"服务器错误: {str(e)}"}), 500 \ No newline at end of file diff --git a/back/blueprints/wendu.py b/back/blueprints/wendu.py new file mode 100644 index 0000000..1536ecf --- /dev/null +++ b/back/blueprints/wendu.py @@ -0,0 +1,85 @@ +import sqlite3 +from flask import Blueprint, current_app, jsonify, g, Flask, request +from datetime import datetime, timedelta + + + +# 初始化蓝图对象 +bp = Blueprint('wendu', __name__, url_prefix='/api') + + +@bp.route('/temperature/daily/average', methods=['GET']) +def get_daily_average_by_device(): + """按设备分组获取近30天的每日温度平均值""" + try: + db = current_app.get_db() + + # 计算查询日期范围(近30天) + days = request.args.get('days', default=30, type=int) + end_date = datetime.now().date() + start_date = end_date - timedelta(days=days - 1) + + current_app.logger.info(f"查询最近{days}天温度数据: {start_date} 至 {end_date}") + + # 执行SQL查询 + cursor = db.execute(''' + SELECT + d.id AS device_id, + d.device_name, + date(t.timestamp) AS date_day, + ROUND(AVG(t.temperature), 1) AS avg_temp + FROM temperature_data t + JOIN device d ON t.device_id = d.id + WHERE date(t.timestamp) BETWEEN ? AND ? + GROUP BY d.id, d.device_name, date_day + ORDER BY d.id, date_day ASC + ''', (start_date, end_date)) + + rows = cursor.fetchall() + current_app.logger.info(f"查询结果行数: {len(rows)}") + + if not rows: + current_app.logger.warning(f"在{start_date}至{end_date}范围内未找到温度数据") + return jsonify({ + "code": 404, + "message": f"最近{days}天内无温度数据", + "data": {} + }) + + # 处理查询结果 + device_data = {} + for row in rows: + device_id = str(row['device_id']) + if device_id not in device_data: + device_data[device_id] = { + 'device_name': row['device_name'], + 'dates': [], + 'temperatures': [] + } + + device_data[device_id]['dates'].append(row['date_day']) + device_data[device_id]['temperatures'].append(float(row['avg_temp'])) + + # 确保数据按日期排序 + for device in device_data.values(): + if len(device['dates']) > 1: + combined = sorted(zip(device['dates'], device['temperatures']), key=lambda x: x[0]) + device['dates'], device['temperatures'] = zip(*combined) + device['dates'] = list(device['dates']) + device['temperatures'] = list(device['temperatures']) + + return jsonify({ + "code": 200, + "message": "Success", + "data": device_data + }) + + except Exception as e: + current_app.logger.error(f"获取温度数据失败: {str(e)}", exc_info=True) + return jsonify({ + "code": 500, + "message": f"服务器错误: {str(e)}" + }), 500 + +# 注册蓝图 + diff --git a/back/blueprints/yzm.py b/back/blueprints/yzm.py new file mode 100644 index 0000000..076c8b1 --- /dev/null +++ b/back/blueprints/yzm.py @@ -0,0 +1,99 @@ +# blueprints/yzm.py + +import random +import smtplib +import string +import ssl +import time + +from email.mime.text import MIMEText +from flask import request, jsonify, Blueprint +from flask_cors import CORS + +bp = Blueprint('yzm', __name__) +CORS(bp, supports_credentials=True) + +verification_codes = {} + +def generate_code(): + return ''.join(random.choices(string.digits, k=6)) + +def send_email(receiver, code): + sender = '3492073524@qq.com' + password = 'xhemkcgrgximchcd' + + msg = MIMEText(f'您的验证码是:{code},5分钟内有效。') + msg['From'] = sender + msg['To'] = receiver + msg['Subject'] = '禾境智联后台管理系统 - 验证码' + + max_retries = 3 + + for attempt in range(1, max_retries + 1): + try: + print(f"[尝试 {attempt}/{max_retries}] 正在连接 SMTP 服务器...") + context = ssl.create_default_context() + + with smtplib.SMTP_SSL('smtp.qq.com', 465, context=context) as server: + print("✅ SMTP 连接成功") + server.login(sender, password) + print("🔑 登录成功") + server.sendmail(sender, receiver, msg.as_string()) + print("📧 邮件发送成功") + return True # 成功发送 + + except smtplib.SMTPAuthenticationError as e: + print(f"❌ 认证失败(授权码错误): {str(e)}") + return False + except smtplib.SMTPConnectError as e: + print(f"❌ 连接失败: {str(e)}") + except smtplib.SMTPException as e: + error_msg = str(e) + if error_msg == "(-1, b'\\x00\\x00\\x00')" or "unexpected EOF" in error_msg: + print("⚠️ 警告: 忽略非致命异常,假设邮件已发送成功") + return True # 假设邮件已成功发送 + else: + print(f"❌ SMTP 异常: {error_msg}") + except Exception as e: + print(f"❌ 未知错误: {str(e)}") + + if attempt < max_retries: + print("🔄 正在等待重试...") + time.sleep(2) + else: + print("💥 达到最大重试次数,邮件发送失败") + return False + +@bp.route("/captcha/email", methods=["POST"]) +def send_code(): + data = request.json + email = data.get("email") + + if not email: + return jsonify({"message": "邮箱不能为空"}), 400 + + code = generate_code() + verification_codes[email] = code + print(f"验证码已生成: {email} -> {code}") + + if send_email(email, code): + return jsonify({"message": "验证码已发送"}), 200 + else: + return jsonify({"message": "邮件发送失败"}), 500 + +@bp.route("/captcha/verify", methods=["POST"]) +def verify_code(): + data = request.json + email = data.get("email") + user_code = data.get("code") + + if not email or not user_code: + return jsonify({"message": "参数错误", "valid": False}), 400 + + stored_code = verification_codes.get(email) + + if stored_code and stored_code == user_code: + del verification_codes[email] + return jsonify({"valid": True}), 200 + else: + return jsonify({"valid": False}), 400 \ No newline at end of file diff --git a/back/config.py b/back/config.py new file mode 100644 index 0000000..bced6ec --- /dev/null +++ b/back/config.py @@ -0,0 +1,13 @@ +import os +from datetime import timedelta + +# session配置 +SECRET_KEY = os.urandom(24) # 设置秘钥 +PERMANENT_SESSION_LIFETIME = timedelta(days=10) # 设置session生命周期 + +# config.py +APPID = "2dbd6b09" +API_KEY = "df4e4c0b3526cff5f0a1160be5e03106" +API_SECRET = "OWQ3NGZhYWNmMTQ4ZmUyZDc3MzkwODY4" +SPARK_URL = "wss://spark-api.xf-yun.com/v1/x1" +DOMAIN = "x1" \ No newline at end of file diff --git a/back/exts.py b/back/exts.py new file mode 100644 index 0000000..3b1ef58 --- /dev/null +++ b/back/exts.py @@ -0,0 +1,5 @@ +# from flask_sqlalchemy import SQLAlchemy +# from flask_migrate import Migrate +# +# db = SQLAlchemy() +# migrate = Migrate() \ No newline at end of file diff --git a/back/models.py b/back/models.py new file mode 100644 index 0000000..352e6bc --- /dev/null +++ b/back/models.py @@ -0,0 +1,34 @@ +import sqlite3 + +class User: + def __init__(self, id, username, password): + self.id = id + self.username = username + self.password = password + + @staticmethod + def get_user_by_username(username, db): + cursor = db.cursor() + cursor.execute("SELECT * FROM user WHERE username = ?", (username,)) + user_data = cursor.fetchone() + if user_data: + return User(user_data[0], user_data[1], user_data[2]) + return None + + def check_password(self, password): + return self.password == password + + +class WeatherData: + def __init__(self, id, temperature, humidity, record_time): + self.id = id + self.temperature = temperature + self.humidity = humidity + self.record_time = record_time + + @staticmethod + def get_weather_data_by_time(start_time, end_time, db): + cursor = db.cursor() + cursor.execute("SELECT * FROM weather_data WHERE record_time >= ? AND record_time < ?", (start_time, end_time)) + data = cursor.fetchall() + return [WeatherData(row[0], row[1], row[2], row[3]) for row in data] \ No newline at end of file diff --git a/back/models/best_model.pth b/back/models/best_model.pth new file mode 100644 index 0000000..60b9cf8 Binary files /dev/null and b/back/models/best_model.pth differ diff --git a/back/requirements.txt b/back/requirements.txt new file mode 100644 index 0000000..303e443 --- /dev/null +++ b/back/requirements.txt @@ -0,0 +1,7 @@ +torch==2.7.1 +python-dateutil==2.8.2 +flask==3.1.1 +flask-cors==5.0.1 +cryptography==45.0.4 +websocket-client==1.8.0 +numpy==1.24.4 \ No newline at end of file diff --git a/back/schema.sql b/back/schema.sql new file mode 100644 index 0000000..9292339 --- /dev/null +++ b/back/schema.sql @@ -0,0 +1,868 @@ + +-- ---------------------------- +-- 部门表 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS department ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, -- 部门名称 + manager_id INTEGER, -- 部门经理ID + description TEXT, -- 部门描述 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (manager_id) REFERENCES user(id) +); + +-- ---------------------------- +-- 用户表(密码改为明文存储,不加密) +-- ---------------------------- +CREATE TABLE IF NOT EXISTS user ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, -- 用户名(唯一) + password TEXT NOT NULL, -- 密码(明文存储,不加密) + real_name TEXT, -- 真实姓名 + email TEXT UNIQUE, -- 邮箱(唯一) + phone TEXT, -- 联系电话 + department_id INTEGER, -- 所属部门(外键) + position TEXT, -- 职位/职务 + permission_level TEXT NOT NULL, -- 权限级别(Admin/Supervisor/Operator) + hire_date DATE, -- 入职日期 + status TEXT DEFAULT 'Active', -- 状态(Active/Inactive) + linked_devices INTEGER DEFAULT 0, -- 关联设备数量 + last_login TIMESTAMP, -- 最后登录时间 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间 + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 更新时间 + CHECK (permission_level IN ('Admin', 'Supervisor', 'Operator')), + CHECK (status IN ('Active', 'Inactive')), + FOREIGN KEY (department_id) REFERENCES department(id) +); + +-- 创建更新触发器自动更新updated_at字段 +CREATE TRIGGER IF NOT EXISTS update_user_timestamp +AFTER UPDATE ON user +FOR EACH ROW +BEGIN + UPDATE user SET updated_at = CURRENT_TIMESTAMP WHERE id = OLD.id; +END; + +-- ---------------------------- +-- 插入管理员用户(使用正确的权限级别格式) +-- ---------------------------- +INSERT INTO user (username, password, real_name, email, permission_level, status) +VALUES ('root', 'root', 'root', '397088740@qq.com', 'Admin', 'Active') +ON CONFLICT(username) DO NOTHING; + +-- ---------------------------- +-- 用户设备关联表(新增) +-- ---------------------------- +CREATE TABLE IF NOT EXISTS user_device ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + device_id INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES user(id), + FOREIGN KEY (device_id) REFERENCES device(id) +); + +-- ---------------------------- +-- 设备表 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS device ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_name TEXT NOT NULL, + device_code TEXT UNIQUE NOT NULL, + status TEXT NOT NULL, + operator TEXT, -- 操作人员 + fault_description TEXT, -- 故障描述 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- ---------------------------- +-- 温湿度数据表 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS temperature_data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id INTEGER NOT NULL, + temperature REAL NOT NULL, + humidity REAL NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + ph REAL, -- pH值(实数类型) + light_intensity INTEGER, -- 光照强度(整数,单位:Lux) + FOREIGN KEY (device_id) REFERENCES device (id) +); + +-- ---------------------------- +-- 插入部门数据 +-- ---------------------------- +INSERT INTO department (name, manager_id, description) VALUES +('IT部', 1, '负责公司信息技术管理'), +('销售部', 2, '负责产品销售'), +('技术部', 3, '负责技术研发'), +('生产部', NULL, '负责产品生产'), +('运维部', NULL, '负责设备维护') +ON CONFLICT(name) DO NOTHING; + +-- ---------------------------- +-- 操作日志表 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS operation_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + type TEXT NOT NULL, + message TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES user(id) +); + +-- ---------------------------- +-- 插入设备数据 +-- ---------------------------- +INSERT INTO device (device_name, device_code, status, operator, fault_description) +VALUES +('设备A', 'DEV-A', 'normal', 'root', NULL), +('设备B', 'DEV-B', 'warning', 'root', '温度过高'), +('设备C', 'DEV-C', 'fault', 'root', '传感器故障'), +('设备D', 'DEV-D', 'normal', 'root', NULL), +('设备E', 'DEV-E', 'Offline', 'root', NULL), +('中心设备', 'CENTER-DEV', 'normal', 'root', NULL), +('设备F', 'DEV-F', 'normal', 'root', NULL) +ON CONFLICT(device_code) DO NOTHING; + +-- ---------------------------- +-- 插入温湿度数据(设备A示例,包含ph和光照) +-- ---------------------------- +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) VALUES +(1, 22.0, 60, '2025-05-26 00:00:00', 6.5, 2000), +(1, 21.8, 59, '2025-05-26 01:00:00', 6.4, 1900), +(1, 21.5, 58, '2025-05-26 02:00:00', 6.3, 1800), +(1, 21.2, 57, '2025-05-26 03:00:00', 6.2, 1700), +(1, 21.0, 56, '2025-05-26 04:00:00', 6.1, 1600), +(1, 20.8, 55, '2025-05-26 05:00:00', 6.0, 1500), +(1, 21.2, 56, '2025-05-26 06:00:00', 6.2, 1800), +(1, 22.5, 58, '2025-05-26 07:00:00', 6.5, 2200), +(1, 24.0, 60, '2025-05-26 08:00:00', 6.8, 2500), +(1, 25.5, 62, '2025-05-26 09:00:00', 7.0, 2800), +(1, 26.8, 63, '2025-05-26 10:00:00', 7.2, 3000), +(1, 27.5, 62, '2025-05-26 11:00:00', 7.1, 3200), +(1, 28.0, 60, '2025-05-26 12:00:00', 7.0, 3500), +(1, 27.8, 58, '2025-05-26 13:00:00', 6.9, 3300), +(1, 27.2, 56, '2025-05-26 14:00:00', 6.8, 3000), +(1, 26.5, 55, '2025-05-26 15:00:00', 6.7, 2800), +(1, 25.5, 56, '2025-05-26 16:00:00', 6.6, 2500), +(1, 24.5, 58, '2025-05-26 17:00:00', 6.5, 2200), +(1, 23.5, 60, '2025-05-26 18:00:00', 6.4, 2000), +(1, 22.8, 61, '2025-05-26 19:00:00', 6.3, 1800), +(1, 22.0, 60, '2025-05-26 20:00:00', 6.2, 1700), +(1, 21.5, 59, '2025-05-26 21:00:00', 6.1, 1600), +(1, 21.2, 58, '2025-05-26 22:00:00', 6.0, 1500), +(1, 21.0, 57, '2025-05-26 23:00:00', 6.0, 1400), +(1, 21.2, 58, '2025-05-27 00:00:00', 6.2, 1800), +(1, 21.5, 59, '2025-05-27 01:00:00', 6.3, 1900), +(1, 21.8, 60, '2025-05-27 02:00:00', 6.4, 2000), +(1, 22.0, 61, '2025-05-27 03:00:00', 6.5, 2100), +(1, 22.5, 62, '2025-05-27 04:00:00', 6.6, 2200), +(1, 23.0, 63, '2025-05-27 05:00:00', 6.7, 2300), +(1, 24.0, 62, '2025-05-27 06:00:00', 6.8, 2500), +(1, 25.5, 60, '2025-05-27 07:00:00', 6.9, 2800), +(1, 27.0, 58, '2025-05-27 08:00:00', 7.0, 3000), +(1, 28.5, 55, '2025-05-27 09:00:00', 7.1, 3200), +(1, 30.0, 52, '2025-05-27 10:00:00', 7.2, 3500), +(1, 31.0, 50, '2025-05-27 11:00:00', 7.1, 3300), +(1, 31.5, 48, '2025-05-27 12:00:00', 7.0, 3000), +(1, 31.0, 47, '2025-05-27 13:00:00', 6.9, 2800), +(1, 30.0, 48, '2025-05-27 14:00:00', 6.8, 2500), +(1, 29.0, 50, '2025-05-27 15:00:00', 6.7, 2200), +(1, 28.0, 52, '2025-05-27 16:00:00', 6.6, 2000), +(1, 26.5, 55, '2025-05-27 17:00:00', 6.5, 1800), +(1, 25.0, 58, '2025-05-27 18:00:00', 6.4, 1600), +(1, 23.5, 60, '2025-05-27 19:00:00', 6.3, 1500), +(1, 22.0, 61, '2025-05-27 20:00:00', 6.2, 1400), +(1, 21.5, 60, '2025-05-27 21:00:00', 6.1, 1300), +(1, 21.2, 59, '2025-05-27 22:00:00', 6.0, 1200), +(1, 21.0, 58, '2025-05-27 23:00:00', 6.0, 1100); + +-- 设备B(ID=2)警告设备(第二日15时高温异常) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) VALUES +(2, 26.5, 55, '2025-05-26 15:00:00', 6.8, 2500), +(2, 37.0, 55, '2025-05-27 15:00:00', 5.5, 3000), +(2, 28.0, 58, '2025-05-27 16:00:00', 6.5, 2800); + +-- 设备C(ID=3)故障设备(第二日15时低温+低湿异常) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) VALUES +(3, 26.5, 55, '2025-05-26 15:00:00', 6.7, 2500), +(3, 16.0, 28, '2025-05-27 15:00:00', 5.0, 2000), +(3, 22.0, 58, '2025-05-27 16:00:00', 6.5, 2500); + +-- 设备D(ID=4)复制设备A的数据(包含ph和光照) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT 4, temperature, humidity, timestamp, ph, light_intensity +FROM temperature_data +WHERE device_id = 1; + +-- 中心设备(ID=6)正常数据(部分) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) VALUES +(6, 24.0, 54, '2025-05-26 16:00:00', 6.5, 2000), +(6, 24.2, 54, '2025-05-26 17:00:00', 6.5, 2100), +(6, 23.8, 55, '2025-05-26 18:00:00', 6.4, 1900), +(6, 23.5, 55, '2025-05-26 19:00:00', 6.4, 1800), +(6, 23.2, 55, '2025-05-26 20:00:00', 6.3, 1700), +(6, 23.0, 55, '2025-05-26 21:00:00', 6.3, 1600), +(6, 22.8, 55, '2025-05-26 22:00:00', 6.2, 1500), +(6, 22.5, 55, '2025-05-26 23:00:00', 6.2, 1400), +(6, 22.8, 55, '2025-05-27 00:00:00', 6.3, 1500), +(6, 23.0, 55, '2025-05-27 01:00:00', 6.3, 1600), +(6, 23.2, 55, '2025-05-27 02:00:00', 6.4, 1700), +(6, 23.5, 55, '2025-05-27 03:00:00', 6.4, 1800), +(6, 23.8, 55, '2025-05-27 04:00:00', 6.5, 1900), +(6, 24.0, 54, '2025-05-27 05:00:00', 6.5, 2000), +(6, 24.2, 54, '2025-05-27 06:00:00', 6.6, 2100), +(6, 24.5, 54, '2025-05-27 07:00:00', 6.6, 2200), +(6, 25.0, 53, '2025-05-27 08:00:00', 6.7, 2300), +(6, 25.5, 53, '2025-05-27 09:00:00', 6.8, 2400), +(6, 26.0, 53, '2025-05-27 10:00:00', 6.8, 2500), +(6, 26.5, 52, '2025-05-27 11:00:00', 6.9, 2600), +(6, 27.0, 52, '2025-05-27 12:00:00', 7.0, 2700), +(6, 26.8, 52, '2025-05-27 13:00:00', 6.9, 2600), +(6, 26.5, 52, '2025-05-27 14:00:00', 6.9, 2500), +(6, 26.0, 52, '2025-05-27 15:00:00', 6.8, 2400), +(6, 25.5, 53, '2025-05-27 16:00:00', 6.7, 2300), +(6, 25.0, 53, '2025-05-27 17:00:00', 6.7, 2200), +(6, 24.5, 54, '2025-05-27 18:00:00', 6.6, 2100), +(6, 24.2, 54, '2025-05-27 19:00:00', 6.6, 2000), +(6, 24.0, 54, '2025-05-27 20:00:00', 6.5, 1900), +(6, 23.8, 54, '2025-05-27 21:00:00', 6.5, 1800), +(6, 23.5, 55, '2025-05-27 22:00:00', 6.4, 1700), +(6, 23.2, 55, '2025-05-27 23:00:00', 6.4, 1600); + +-- 设备F(ID=7)正常数据(部分) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) VALUES +(7, 22.0, 60, '2025-05-26 00:00:00', 6.5, 2000), +(7, 21.8, 59, '2025-05-26 01:00:00', 6.4, 1900), +(7, 21.5, 58, '2025-05-26 02:00:00', 6.3, 1800), +(7, 21.2, 57, '2025-05-26 03:00:00', 6.2, 1700), +(7, 21.0, 56, '2025-05-26 04:00:00', 6.1, 1600), +(7, 20.8, 55, '2025-05-26 05:00:00', 6.0, 1500), +(7, 21.2, 56, '2025-05-26 06:00:00', 6.2, 1800), +(7, 22.5, 58, '2025-05-26 07:00:00', 6.5, 2200), +(7, 24.0, 60, '2025-05-26 08:00:00', 6.8, 2500), +(7, 25.5, 62, '2025-05-26 09:00:00', 7.0, 2800), +(7, 26.8, 63, '2025-05-26 10:00:00', 7.2, 3000), +(7, 27.5, 62, '2025-05-26 11:00:00', 7.1, 3200), +(7, 28.0, 60, '2025-05-26 12:00:00', 7.0, 3500), +(7, 27.8, 58, '2025-05-26 13:00:00', 6.9, 3300), +(7, 27.2, 56, '2025-05-26 14:00:00', 6.8, 3000), +(7, 26.5, 55, '2025-05-26 15:00:00', 6.7, 2800), +(7, 25.5, 56, '2025-05-26 16:00:00', 6.6, 2500), +(7, 24.5, 58, '2025-05-26 17:00:00', 6.5, 2200), +(7, 23.5, 60, '2025-05-26 18:00:00', 6.4, 2000), +(7, 22.8, 61, '2025-05-26 19:00:00', 6.3, 1800), +(7, 22.0, 60, '2025-05-26 20:00:00', 6.2, 1700), +(7, 21.5, 59, '2025-05-26 21:00:00', 6.1, 1600), +(7, 21.2, 58, '2025-05-26 22:00:00', 6.0, 1500), +(7, 21.0, 57, '2025-05-26 23:00:00', 6.0, 1400), +(7, 21.2, 58, '2025-05-27 00:00:00', 6.2, 1800), +(7, 21.5, 59, '2025-05-27 01:00:00', 6.3, 1900), +(7, 21.8, 60, '2025-05-27 02:00:00', 6.4, 2000), +(7, 22.0, 61, '2025-05-27 03:00:00', 6.5, 2100), +(7, 22.5, 62, '2025-05-27 04:00:00', 6.6, 2200), +(7, 23.0, 63, '2025-05-27 05:00:00', 6.7, 2300), +(7, 24.0, 62, '2025-05-27 06:00:00', 6.8, 2500), +(7, 25.5, 60, '2025-05-27 07:00:00', 6.9, 2800), +(7, 27.0, 58, '2025-05-27 08:00:00', 7.0, 3000), +(7, 28.5, 55, '2025-05-27 09:00:00', 7.1, 3200), +(7, 30.0, 52, '2025-05-27 10:00:00', 7.2, 3500), +(7, 31.0, 50, '2025-05-27 11:00:00', 7.1, 3300), +(7, 31.5, 48, '2025-05-27 12:00:00', 7.0, 3000), +(7, 31.0, 47, '2025-05-27 13:00:00', 6.9, 2800), +(7, 30.0, 48, '2025-05-27 14:00:00', 6.8, 2500), +(7, 29.0, 50, '2025-05-27 15:00:00', 6.7, 2200), +(7, 28.0, 52, '2025-05-27 16:00:00', 6.6, 2000), +(7, 26.5, 55, '2025-05-27 17:00:00', 6.5, 1800), +(7, 25.0, 58, '2025-05-27 18:00:00', 6.4, 1600), +(7, 23.5, 60, '2025-05-27 19:00:00', 6.3, 1500), +(7, 22.0, 61, '2025-05-27 20:00:00', 6.2, 1400), +(7, 21.5, 60, '2025-05-27 21:00:00', 6.1, 1300), +(7, 21.2, 59, '2025-05-27 22:00:00', 6.0, 1200), +(7, 21.0, 58, '2025-05-27 23:00:00', 6.0, 1100); + +-- ---------------------------- +-- 索引优化 +-- ---------------------------- +CREATE INDEX IF NOT EXISTS idx_user_permission ON user(permission_level); +CREATE INDEX IF NOT EXISTS idx_user_status ON user(status); +CREATE INDEX IF NOT EXISTS idx_user_department ON user(department_id); +CREATE INDEX IF NOT EXISTS idx_user_hire_date ON user(hire_date DESC); +CREATE INDEX IF NOT EXISTS idx_user_device_user ON user_device(user_id); +CREATE INDEX IF NOT EXISTS idx_user_device_device ON user_device(device_id); +CREATE INDEX IF NOT EXISTS idx_device_status ON device(status); + +-- ---------------------------- +-- 设备B、C除特殊时刻外复制设备A的数据 +-- ---------------------------- +-- 删除设备B的非特殊时刻数据 +DELETE FROM temperature_data +WHERE device_id = 2 + AND timestamp NOT IN ('2025-05-26 15:00:00', '2025-05-27 15:00:00', '2025-05-27 16:00:00'); + +-- 删除设备C的非特殊时刻数据 +DELETE FROM temperature_data +WHERE device_id = 3 + AND timestamp NOT IN ('2025-05-26 15:00:00', '2025-05-27 15:00:00', '2025-05-27 16:00:00'); + +-- 设备B复制设备A的数据(非特殊时刻) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT 2, temperature, humidity, timestamp, ph, light_intensity +FROM temperature_data +WHERE device_id = 1 + AND timestamp NOT IN ('2025-05-26 15:00:00', '2025-05-27 15:00:00', '2025-05-27 16:00:00'); + +-- 设备C复制设备A的数据(非特殊时刻) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT 3, temperature, humidity, timestamp, ph, light_intensity +FROM temperature_data +WHERE device_id = 1 + AND timestamp NOT IN ('2025-05-26 15:00:00', '2025-05-27 15:00:00', '2025-05-27 16:00:00'); + +CREATE TABLE IF NOT EXISTS notification_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + fault_id INTEGER NOT NULL, + recipient TEXT NOT NULL, + content TEXT NOT NULL, + send_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status TEXT DEFAULT 'success', -- success/failure + FOREIGN KEY (fault_id) REFERENCES device (id) +); + +-- ---------------------------- +-- 添加历史数据(前2天和后2天,每小时一个数据点) +-- ---------------------------- + +-- 设备A(ID=1)添加前2天和后2天数据 +WITH hour_sequence AS ( + -- 生成24小时序列(0-23点) + SELECT 0 AS hour UNION ALL + SELECT hour + 1 FROM hour_sequence WHERE hour < 23 +), date_range AS ( + -- 生成日期范围:前2天、前1天、今日、后1天、后2天 + SELECT DATE('2025-05-27', '-2 days') AS date UNION ALL + SELECT DATE('2025-05-27', '-1 days') UNION ALL + SELECT DATE('2025-05-27') UNION ALL + SELECT DATE('2025-05-27', '+1 days') UNION ALL + SELECT DATE('2025-05-27', '+2 days') +) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT + 1, + 25.0 + (h.hour - 12) * 0.1, -- 温度:25℃基础值,每小时波动±1.2℃ + 60.0 + (h.hour - 12) * 0.2, -- 湿度:60%基础值,每小时波动±2.4% + datetime(d.date, '00:00:00') + (h.hour || ':00:00') AS timestamp, + 6.8 + 0.05 * (h.hour - 12), -- PH值:6.8基础值,范围6.0-7.6 + 500 + (h.hour - 12) * 50 -- 光照强度:500基础值,每小时波动±600 +FROM date_range d +CROSS JOIN hour_sequence h +WHERE d.date BETWEEN DATE('2025-05-27', '-2 days') AND DATE('2025-05-27', '+2 days'); + + +-- 设备B(ID=2)添加前2天和后2天数据(基础值与波动幅度调整) +WITH hour_sequence AS ( + SELECT 0 AS hour UNION ALL + SELECT hour + 1 FROM hour_sequence WHERE hour < 23 +), date_range AS ( + SELECT DATE('2025-05-27', '-2 days') AS date UNION ALL + SELECT DATE('2025-05-27', '-1 days') UNION ALL + SELECT DATE('2025-05-27') UNION ALL + SELECT DATE('2025-05-27', '+1 days') UNION ALL + SELECT DATE('2025-05-27', '+2 days') +) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT + 2, + 26.5 + (h.hour - 12) * 0.15, -- 温度:26.5℃基础值 + 55.0 + (h.hour - 12) * 0.25, -- 湿度:55%基础值 + datetime(d.date, '00:00:00') + (h.hour || ':00:00') AS timestamp, + 7.2 + 0.04 * (h.hour - 12), -- PH值:7.2基础值 + 400 + (h.hour - 12) * 60 -- 光照强度:400基础值 +FROM date_range d +CROSS JOIN hour_sequence h +WHERE d.date BETWEEN DATE('2025-05-27', '-2 days') AND DATE('2025-05-27', '+2 days'); + + +-- 设备C(ID=3)添加前2天和后2天数据(不同设备特性) +WITH hour_sequence AS ( + SELECT 0 AS hour UNION ALL + SELECT hour + 1 FROM hour_sequence WHERE hour < 23 +), date_range AS ( + SELECT DATE('2025-05-27', '-2 days') AS date UNION ALL + SELECT DATE('2025-05-27', '-1 days') UNION ALL + SELECT DATE('2025-05-27') UNION ALL + SELECT DATE('2025-05-27', '+1 days') UNION ALL + SELECT DATE('2025-05-27', '+2 days') +) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT + 3, + 24.0 + (h.hour - 12) * 0.08, -- 温度:24℃基础值 + 65.0 + (h.hour - 12) * 0.18, -- 湿度:65%基础值 + datetime(d.date, '00:00:00') + (h.hour || ':00:00') AS timestamp, + 6.5 + 0.06 * (h.hour - 12), -- PH值:6.5基础值 + 600 + (h.hour - 12) * 40 -- 光照强度:600基础值 +FROM date_range d +CROSS JOIN hour_sequence h +WHERE d.date BETWEEN DATE('2025-05-27', '-2 days') AND DATE('2025-05-27', '+2 days'); + + +-- 设备D(ID=4)添加前2天和后2天数据 +WITH hour_sequence AS ( + SELECT 0 AS hour UNION ALL + SELECT hour + 1 FROM hour_sequence WHERE hour < 23 +), date_range AS ( + SELECT DATE('2025-05-27', '-2 days') AS date UNION ALL + SELECT DATE('2025-05-27', '-1 days') UNION ALL + SELECT DATE('2025-05-27') UNION ALL + SELECT DATE('2025-05-27', '+1 days') UNION ALL + SELECT DATE('2025-05-27', '+2 days') +) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT + 4, + 27.0 + (h.hour - 12) * 0.12, -- 温度:27℃基础值 + 58.0 + (h.hour - 12) * 0.22, -- 湿度:58%基础值 + datetime(d.date, '00:00:00') + (h.hour || ':00:00') AS timestamp, + 7.0 + 0.05 * (h.hour - 12), -- PH值:7.0基础值 + 450 + (h.hour - 12) * 55 -- 光照强度:450基础值 +FROM date_range d +CROSS JOIN hour_sequence h +WHERE d.date BETWEEN DATE('2025-05-27', '-2 days') AND DATE('2025-05-27', '+2 days'); + + +-- 中心设备(ID=6)添加前2天和后2天数据 +WITH hour_sequence AS ( + SELECT 0 AS hour UNION ALL + SELECT hour + 1 FROM hour_sequence WHERE hour < 23 +), date_range AS ( + SELECT DATE('2025-05-27', '-2 days') AS date UNION ALL + SELECT DATE('2025-05-27', '-1 days') UNION ALL + SELECT DATE('2025-05-27') UNION ALL + SELECT DATE('2025-05-27', '+1 days') UNION ALL + SELECT DATE('2025-05-27', '+2 days') +) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT + 6, + 25.5 + (h.hour - 12) * 0.09, -- 温度:25.5℃基础值(中心设备) + 62.0 + (h.hour - 12) * 0.19, -- 湿度:62%基础值 + datetime(d.date, '00:00:00') + (h.hour || ':00:00') AS timestamp, + 6.9 + 0.05 * (h.hour - 12), -- PH值:6.9基础值 + 550 + (h.hour - 12) * 45 -- 光照强度:550基础值 +FROM date_range d +CROSS JOIN hour_sequence h +WHERE d.date BETWEEN DATE('2025-05-27', '-2 days') AND DATE('2025-05-27', '+2 days'); + + +-- 设备F(ID=7)添加前2天和后2天数据 +WITH hour_sequence AS ( + SELECT 0 AS hour UNION ALL + SELECT hour + 1 FROM hour_sequence WHERE hour < 23 +), date_range AS ( + SELECT DATE('2025-05-27', '-2 days') AS date UNION ALL + SELECT DATE('2025-05-27', '-1 days') UNION ALL + SELECT DATE('2025-05-27') UNION ALL + SELECT DATE('2025-05-27', '+1 days') UNION ALL + SELECT DATE('2025-05-27', '+2 days') +) +INSERT INTO temperature_data (device_id, temperature, humidity, timestamp, ph, light_intensity) +SELECT + 7, + 23.5 + (h.hour - 12) * 0.14, -- 温度:23.5℃基础值 + 68.0 + (h.hour - 12) * 0.24, -- 湿度:68%基础值 + datetime(d.date, '00:00:00') + (h.hour || ':00:00') AS timestamp, + 6.7 + 0.07 * (h.hour - 12), -- PH值:6.7基础值 + 350 + (h.hour - 12) * 65 -- 光照强度:350基础值 +FROM date_range d +CROSS JOIN hour_sequence h +WHERE d.date BETWEEN DATE('2025-05-27', '-2 days') AND DATE('2025-05-27', '+2 days'); + +-- ---------------------------- +-- 操作日志数据 +-- ---------------------------- +-- 添加用户创建日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'USER_CREATE', '创建用户 admin'), +(1, 'USER_CREATE', '创建用户 supervisor'), +(1, 'USER_CREATE', '创建用户 operator1'), +(1, 'USER_CREATE', '创建用户 operator2'); + +-- 添加用户更新日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'USER_UPDATE', '更新用户 admin 的联系方式'), +(2, 'USER_UPDATE', '更新用户 supervisor 的权限级别'), +(3, 'USER_UPDATE', '更新用户 operator1 的状态为 Inactive'); + +-- 添加用户删除日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'USER_DELETE', '删除用户 test_user'), +(2, 'USER_DELETE', '删除用户 temp_user'); + +-- 添加设备操作日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'DEVICE_MANAGE', '添加新设备 环境监测仪A'), +(2, 'DEVICE_MANAGE', '修改设备 环境监测仪B 的状态为警告'), +(3, 'DEVICE_MANAGE', '删除设备 环境监测仪C'); + +-- 添加数据查看日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'DATA_VIEW', '查看设备A的温湿度数据'), +(2, 'DATA_VIEW', '查看设备B的温湿度数据'), +(3, 'DATA_VIEW', '查看所有设备的故障记录'), +(4, 'DATA_VIEW', '导出2025年5月的温湿度数据报表'); + +-- 添加权限变更日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'PERMISSION_CHANGE', '将用户 supervisor 的权限提升为管理员'), +(1, 'PERMISSION_CHANGE', '将用户 operator1 的权限调整为主管'), +(2, 'PERMISSION_CHANGE', '将用户 operator2 的权限调整为操作员'); + +-- 添加系统操作日志 +INSERT INTO operation_log (user_id, type, message) VALUES +(1, 'SYSTEM_OPERATION', '系统备份完成'), +(1, 'SYSTEM_OPERATION', '数据库优化执行完毕'), +(2, 'SYSTEM_OPERATION', '服务器状态检查完成'), +(3, 'SYSTEM_OPERATION', '数据同步任务启动'); + +-- ---------------------------- +-- 湿度记录表 +-- ---------------------------- +DROP TABLE IF EXISTS soil_moisture; + +CREATE TABLE soil_moisture ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + record_date TEXT NOT NULL UNIQUE, + moisture REAL NOT NULL +); + +INSERT INTO soil_moisture (record_date, moisture) VALUES +('6.10', 55.0), +('6.11', 60.5), +('6.12', 80.0), +('6.13', 75.5), +('6.14', 65.0), +('6.15', 50.5), +('6.16', 90.0); + + +-- 删除已存在的表(如果需要) +DROP TABLE IF EXISTS sensor_data; + +-- 创建新表,包含 temperature, humidity, pH 和 status 字段 +CREATE TABLE sensor_data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + record_time TEXT NOT NULL UNIQUE, -- 时间戳作为唯一记录标识 + temperature REAL NOT NULL, -- 温度 + humidity REAL NOT NULL, -- 湿度 + ph REAL NOT NULL, -- pH 值 + status TEXT NOT NULL -- 状态(normal/warning/abnormal 等) +); + +-- 插入数据 +INSERT INTO sensor_data (record_time, temperature, humidity, ph, status) VALUES +('2025-06-02 00:00:00', 18.1, 94.0, 6.6, 'normal'), +('2025-06-02 00:20:00', 18.7, 92.9, 6.7, 'normal'), +('2025-06-02 00:40:00', 19.3, 91.1, 6.8, 'normal'), +('2025-06-02 01:00:00', 18.6, 84.1, 6.7, 'normal'), +('2025-06-02 01:20:00', 18.6, 89.9, 6.8, 'normal'), +('2025-06-02 01:40:00', 19.7, 88.3, 6.7, 'normal'), +('2025-06-02 02:00:00', 18.5, 87.4, 6.6, 'normal'), +('2025-06-02 02:20:00', 20.8, 87.6, 6.7, 'normal'), +('2025-06-02 02:40:00', 19.2, 88.4, 6.7, 'normal'), +('2025-06-02 03:00:00', 19.5, 84.5, 6.7, 'normal'), +('2025-06-02 03:20:00', 20.7, 86.0, 6.6, 'normal'), +('2025-06-02 03:40:00', 19.4, 90.4, 6.7, 'normal'), +('2025-06-02 04:00:00', 21.6, 84.1, 6.7, 'normal'), +('2025-06-02 04:20:00', 21.4, 82.9, 6.6, 'normal'), +('2025-06-02 04:40:00', 22.6, 82.3, 6.6, 'normal'), +('2025-06-02 05:00:00', 22.7, 84.6, 6.8, 'normal'), +('2025-06-02 05:20:00', 23.9, 79.4, 6.8, 'normal'), +('2025-06-02 05:40:00', 22.9, 82.6, 6.6, 'normal'), +('2025-06-02 06:00:00', 24.7, 79.3, 6.7, 'normal'), +('2025-06-02 06:20:00', 24.5, 78.6, 6.6, 'normal'), +('2025-06-02 06:40:00', 24.5, 80.5, 6.7, 'normal'), +('2025-06-02 07:00:00', 25.5, 81.8, 6.6, 'normal'), +('2025-06-02 07:20:00', 25.2, 76.7, 6.7, 'normal'), +('2025-06-02 07:40:00', 25.7, 76.5, 6.8, 'normal'), +('2025-06-02 08:00:00', 26.1, 71.2, 6.8, 'normal'), +('2025-06-02 08:20:00', 27.1, 73.5, 6.7, 'normal'), +('2025-06-02 08:40:00', 27.2, 76.5, 6.6, 'normal'), +('2025-06-02 09:00:00', 25.9, 75.5, 6.5, 'normal'), +('2025-06-02 09:20:00', 29.0, 72.0, 6.8, 'normal'), +('2025-06-02 09:40:00', 29.5, 73.4, 6.7, 'normal'), +('2025-06-02 10:00:00', 27.9, 75.2, 6.8, 'normal'), +('2025-06-02 10:20:00', 29.3, 72.3, 6.7, 'normal'), +('2025-06-02 10:40:00', 29.2, 73.2, 6.6, 'normal'), +('2025-06-02 11:00:00', 28.6, 69.7, 6.7, 'normal'), +('2025-06-02 11:20:00', 28.8, 73.3, 6.7, 'normal'), +('2025-06-02 11:40:00', 29.9, 71.4, 6.6, 'normal'), +('2025-06-02 12:00:00', 27.8, 70.0, 6.6, 'normal'), +('2025-06-02 12:20:00', 29.7, 70.6, 6.9, 'normal'), +('2025-06-02 12:40:00', 30.0, 74.5, 6.7, 'normal'), +('2025-06-02 13:00:00', 28.4, 68.8, 6.7, 'normal'), +('2025-06-02 13:20:00', 35.5, 73.6, 6.8, 'high_temp'), +('2025-06-02 13:40:00', 29.2, 74.2, 6.5, 'normal'), +('2025-06-02 14:00:00', 29.3, 70.7, 6.7, 'normal'), +('2025-06-02 14:20:00', 28.1, 77.2, 6.5, 'normal'), +('2025-06-02 14:40:00', 27.1, 76.7, 6.9, 'normal'), +('2025-06-02 15:00:00', 27.3, 70.7, 6.6, 'normal'), +('2025-06-02 15:20:00', 25.5, 72.1, 6.5, 'normal'), +('2025-06-02 15:40:00', 26.3, 74.3, 6.5, 'normal'), +('2025-06-02 16:00:00', 27.1, 74.3, 6.7, 'normal'), +('2025-06-02 16:20:00', 25.5, 77.3, 6.7, 'normal'), +('2025-06-02 16:40:00', 26.1, 78.2, 6.6, 'normal'), +('2025-06-02 17:00:00', 27.0, 77.7, 6.6, 'normal'), +('2025-06-02 17:20:00', 24.5, 82.8, 6.9, 'normal'), +('2025-06-02 17:40:00', 24.8, 80.3, 6.7, 'normal'), +('2025-06-02 18:00:00', 22.8, 85.1, 6.9, 'normal'), +('2025-06-02 18:20:00', 24.4, 80.7, 6.5, 'normal'), +('2025-06-02 18:40:00', 23.9, 84.9, 6.7, 'normal'), +('2025-06-02 19:00:00', 22.2, 90.9, 6.8, 'normal'), +('2025-06-02 19:20:00', 21.6, 81.7, 6.8, 'normal'), +('2025-06-02 19:40:00', 21.6, 85.8, 6.6, 'normal'), +('2025-06-02 20:00:00', 20.6, 86.8, 6.8, 'normal'), +('2025-06-02 20:20:00', 21.6, 82.7, 6.6, 'normal'), +('2025-06-02 20:40:00', 21.7, 87.4, 6.8, 'normal'), +('2025-06-02 21:00:00', 20.6, 91.5, 6.7, 'normal'), +('2025-06-02 21:20:00', 18.8, 85.5, 6.7, 'normal'), +('2025-06-02 21:40:00', 21.2, 88.2, 6.6, 'normal'), +('2025-06-02 22:00:00', 20.1, 90.8, 6.7, 'normal'), +('2025-06-02 22:20:00', 19.7, 88.7, 6.8, 'normal'), +('2025-06-02 22:40:00', 18.1, 86.2, 6.8, 'normal'), +('2025-06-02 23:00:00', 19.1, 89.3, 6.7, 'normal'), +('2025-06-02 23:20:00', 19.2, 87.3, 6.6, 'normal'), +('2025-06-02 23:40:00', 19.2, 84.0, 6.8, 'normal'), +('2025-06-03 00:00:00', 19.6, 88.9, 6.9, 'normal'), +('2025-06-03 00:20:00', 18.5, 88.4, 6.7, 'normal'), +('2025-06-03 00:40:00', 18.8, 85.9, 6.4, 'normal'), +('2025-06-03 01:00:00', 18.7, 92.6, 6.5, 'normal'), +('2025-06-03 01:20:00', 19.3, 90.2, 6.5, 'normal'), +('2025-06-03 01:40:00', 18.1, 93.9, 6.6, 'normal'), +('2025-06-03 02:00:00', 20.6, 90.8, 6.5, 'normal'), +('2025-06-03 02:20:00', 20.4, 92.1, 6.7, 'normal'), +('2025-06-03 02:40:00', 20.9, 88.9, 6.5, 'normal'), +('2025-06-03 03:00:00', 20.5, 86.7, 6.7, 'normal'), +('2025-06-03 03:20:00', 21.1, 83.9, 6.5, 'normal'), +('2025-06-03 03:40:00', 20.9, 85.6, 6.6, 'normal'), +('2025-06-03 04:00:00', 22.9, 85.1, 6.5, 'normal'), +('2025-06-03 04:20:00', 21.0, 86.8, 6.4, 'normal'), +('2025-06-03 04:40:00', 21.3, 85.2, 6.6, 'normal'), +('2025-06-03 05:00:00', 22.7, 85.9, 6.5, 'normal'), +('2025-06-03 05:20:00', 23.4, 82.9, 6.5, 'normal'), +('2025-06-03 05:40:00', 25.6, 82.9, 6.4, 'normal'), +('2025-06-03 06:00:00', 25.0, 80.0, 6.6, 'normal'), +('2025-06-03 06:20:00', 25.0, 79.1, 6.6, 'normal'), +('2025-06-03 06:40:00', 24.3, 76.9, 6.5, 'normal'), +('2025-06-03 07:00:00', 24.6, 77.6, 6.5, 'normal'), +('2025-06-03 07:20:00', 25.1, 79.7, 6.5, 'normal'), +('2025-06-03 07:40:00', 26.9, 72.3, 6.6, 'normal'), +('2025-06-03 08:00:00', 27.4, 80.2, 6.4, 'normal'), +('2025-06-03 08:20:00', 27.7, 73.3, 6.6, 'normal'), +('2025-06-03 08:40:00', 26.6, 71.3, 6.5, 'normal'), +('2025-06-03 09:00:00', 26.5, 70.5, 6.6, 'normal'), +('2025-06-03 09:20:00', 28.0, 72.0, 6.6, 'normal'), +('2025-06-03 09:40:00', 28.6, 77.2, 6.5, 'normal'), +('2025-06-03 10:00:00', 28.6, 73.7, 6.5, 'normal'), +('2025-06-03 10:20:00', 29.3, 68.1, 6.5, 'normal'), +('2025-06-03 10:40:00', 29.5, 68.4, 6.5, 'normal'), +('2025-06-03 11:00:00', 30.0, 70.1, 6.4, 'normal'), +('2025-06-03 11:20:00', 29.7, 71.1, 6.4, 'normal'), +('2025-06-03 11:40:00', 27.8, 72.8, 6.5, 'normal'), +('2025-06-03 12:00:00', 28.6, 77.5, 6.6, 'normal'), +('2025-06-03 12:20:00', 28.9, 69.1, 6.6, 'normal'), +('2025-06-03 12:40:00', 30.5, 72.2, 6.6, 'normal'), +('2025-06-03 13:00:00', 28.4, 71.4, 6.5, 'normal'), +('2025-06-03 13:20:00', 28.6, 74.5, 6.5, 'normal'), +('2025-06-03 13:40:00', 28.5, 73.7, 6.5, 'normal'), +('2025-06-03 14:00:00', 29.0, 70.1, 6.7, 'normal'), +('2025-06-03 14:20:00', 28.0, 67.9, 6.6, 'normal'), +('2025-06-03 14:40:00', 28.2, 75.2, 6.6, 'normal'), +('2025-06-03 15:00:00', 26.6, 70.1, 6.6, 'normal'), +('2025-06-03 15:20:00', 27.4, 73.8, 6.4, 'normal'), +('2025-06-03 15:40:00', 27.9, 77.3, 6.4, 'normal'), +('2025-06-03 16:00:00', 27.1, 72.4, 6.6, 'normal'), +('2025-06-03 16:20:00', 26.1, 75.9, 6.6, 'normal'), +('2025-06-03 16:40:00', 26.5, 75.8, 6.6, 'normal'), +('2025-06-03 17:00:00', 26.1, 85.7, 6.4, 'normal'), +('2025-06-03 17:20:00', 25.3, 78.8, 6.5, 'normal'), +('2025-06-03 17:40:00', 25.1, 76.0, 6.3, 'normal'), +('2025-06-03 18:00:00', 25.0, 79.9, 6.5, 'normal'), +('2025-06-03 18:20:00', 23.1, 79.3, 6.7, 'normal'), +('2025-06-03 18:40:00', 22.8, 88.0, 6.6, 'normal'), +('2025-06-03 19:00:00', 23.4, 78.7, 6.6, 'normal'), +('2025-06-03 19:20:00', 23.8, 80.1, 6.6, 'normal'), +('2025-06-03 19:40:00', 21.9, 88.2, 6.5, 'normal'), +('2025-06-03 20:00:00', 20.8, 85.9, 6.4, 'normal'), +('2025-06-03 20:20:00', 22.2, 87.2, 6.5, 'normal'), +('2025-06-03 20:40:00', 20.7, 91.3, 6.5, 'normal'), +('2025-06-03 21:00:00', 20.3, 92.8, 6.6, 'normal'), +('2025-06-03 21:20:00', 18.4, 91.3, 6.6, 'normal'), +('2025-06-03 21:40:00', 19.9, 85.6, 6.4, 'normal'), +('2025-06-03 22:00:00', 20.3, 88.0, 6.6, 'normal'), +('2025-06-03 22:20:00', 18.8, 91.4, 6.5, 'normal'), +('2025-06-03 22:40:00', 20.1, 94.8, 6.5, 'normal'), +('2025-06-03 23:00:00', 18.5, 87.2, 6.6, 'normal'), +('2025-06-03 23:20:00', 18.7, 87.5, 6.9, 'normal'), +('2025-06-03 23:40:00', 18.4, 93.0, 6.8, 'normal'), +('2025-06-04 00:00:00', 18.5, 88.4, 6.3, 'normal'), +('2025-06-04 00:20:00', 18.0, 87.5, 6.1, 'normal'), +('2025-06-04 00:40:00', 19.0, 91.2, 6.2, 'normal'), +('2025-06-04 01:00:00', 19.0, 90.2, 6.1, 'normal'), +('2025-06-04 01:20:00', 19.2, 89.2, 6.0, 'normal'), +('2025-06-04 01:40:00', 19.8, 85.4, 6.1, 'normal'), +('2025-06-04 02:00:00', 18.5, 85.0, 6.0, 'normal'), +('2025-06-04 02:20:00', 20.4, 88.6, 6.2, 'normal'), +('2025-06-04 02:40:00', 20.8, 90.5, 6.1, 'normal'), +('2025-06-04 03:00:00', 20.5, 85.7, 6.0, 'normal'), +('2025-06-04 03:20:00', 21.4, 90.7, 5.9, 'normal'), +('2025-06-04 03:40:00', 21.2, 90.9, 6.0, 'normal'), +('2025-06-04 04:00:00', 23.1, 87.2, 5.9, 'normal'), +('2025-06-04 04:20:00', 22.9, 80.6, 6.0, 'normal'), +('2025-06-04 04:40:00', 23.1, 83.8, 6.4, 'normal'), +('2025-06-04 05:00:00', 21.8, 88.7, 6.3, 'normal'), +('2025-06-04 05:20:00', 23.3, 79.1, 6.0, 'normal'), +('2025-06-04 05:40:00', 24.3, 82.9, 6.1, 'normal'), +('2025-06-04 06:00:00', 24.9, 77.0,6.5, 'normal'), +('2025-06-04 06:20:00', 24.6, 80.9, 6.2, 'normal'), +('2025-06-04 06:40:00', 26.6, 82.1, 6.0, 'normal'), +('2025-06-04 07:00:00', 25.8, 76.0, 6.1, 'normal'), +('2025-06-04 07:20:00', 25.4, 74.6, 6.3, 'normal'), +('2025-06-04 07:40:00', 25.8, 75.3, 6.2, 'normal'), +('2025-06-04 08:00:00', 27.1, 75.1, 6.0, 'normal'), +('2025-06-04 08:20:00', 27.7, 74.0, 5.9, 'normal'), +('2025-06-04 08:40:00', 27.9, 79.7, 6.0, 'normal'), +('2025-06-04 09:00:00', 27.6, 73.6, 5.9, 'normal'), +('2025-06-04 09:20:00', 28.9, 72.8, 6.3, 'normal'), +('2025-06-04 09:40:00', 27.1, 69.1, 6.2, 'normal'), +('2025-06-04 10:00:00', 28.1, 69.3, 6.1, 'normal'), +('2025-06-04 10:20:00', 28.9, 71.4, 6.1, 'normal'), +('2025-06-04 10:40:00', 29.6, 66.5, 6.0, 'normal'), +('2025-06-04 11:00:00', 29.4, 73.3, 6.2, 'normal'), +('2025-06-04 11:20:00', 28.3, 72.5, 6.0, 'normal'), +('2025-06-04 11:40:00', 30.1, 71.3, 5.9, 'normal'), +('2025-06-04 12:00:00', 30.4, 72.2, 6.0, 'normal'), +('2025-06-04 12:20:00', 30.6, 67.9, 6.1, 'normal'), +('2025-06-04 12:40:00', 29.4, 71.0, 6.2, 'normal'), +('2025-06-04 13:00:00', 30.8, 66.1, 6.1, 'normal'), +('2025-06-04 13:20:00', 29.4, 70.7, 6.1, 'normal'), +('2025-06-04 13:40:00', 29.0, 69.3, 6.0, 'normal'), +('2025-06-04 14:00:00', 28.6, 74.9, 6.2, 'normal'), +('2025-06-04 14:20:00', 27.7, 74.5, 6.3, 'normal'), +('2025-06-04 14:40:00', 27.8, 73.7, 6.2, 'normal'), +('2025-06-04 15:00:00', 27.8, 74.9, 6.1, 'normal'), +('2025-06-04 15:20:00', 27.1, 73.4, 6.1, 'normal'), +('2025-06-04 15:40:00', 27.1, 76.1, 6.3, 'normal'), +('2025-06-04 16:00:00', 24.9, 79.5, 6.0, 'normal'), +('2025-06-04 16:20:00', 27.1, 77.3, 6.1, 'normal'), +('2025-06-04 16:40:00', 24.5, 78.8, 6.0, 'normal'), +('2025-06-04 17:00:00', 25.7, 78.7, 6.1, 'normal'), +('2025-06-04 17:20:00', 25.2, 75.7, 5.9, 'normal'), +('2025-06-04 17:40:00', 24.5, 89.9, 6.2, 'normal'), +('2025-06-04 18:00:00', 23.7, 82.4, 6.1, 'normal'), +('2025-06-04 18:20:00', 24.5, 81.8, 6.0, 'normal'), +('2025-06-04 18:40:00', 23.8, 83.5, 6.1, 'normal'), +('2025-06-04 19:00:00', 23.5, 80.8, 6.3, 'normal'), +('2025-06-04 19:20:00', 22.0, 82.8, 5.9, 'normal'), +('2025-06-04 19:40:00', 22.9, 83.9, 6.1, 'normal'), +('2025-06-04 20:00:00', 20.9, 83.3, 6.3, 'normal'), +('2025-06-04 20:20:00', 21.5, 87.8, 6.0, 'normal'), +('2025-06-04 20:40:00', 20.1, 80.9, 5.9, 'normal'), +('2025-06-04 21:00:00', 21.3, 86.1, 6.3, 'normal'), +('2025-06-04 21:20:00', 19.6, 86.5, 6.0, 'normal'), +('2025-06-04 21:40:00', 21.0, 89.2, 6.2, 'normal'), +('2025-06-04 22:00:00', 18.5, 91.1, 6.1, 'normal'), +('2025-06-04 22:20:00', 18.6, 85.7, 6.2, 'normal'), +('2025-06-04 22:40:00', 19.3, 94.5, 6.1, 'normal'), +('2025-06-04 23:00:00', 19.5, 88.2, 6.1, 'normal'), +('2025-06-04 23:20:00', 20.4, 89.4, 5.8, 'normal'), +('2025-06-04 23:40:00', 19.3, 90.0, 6.0, 'normal'), +('2025-06-05 00:00:00', 19.8, 90.9, 5.9, 'normal'), +('2025-06-05 00:20:00', 18.4, 95.4, 6.0, 'normal'), +('2025-06-05 00:40:00', 19.1, 92.9, 5.9, 'normal'), +('2025-06-05 01:00:00', 18.7, 86.4, 5.7, 'normal'), +('2025-06-05 01:20:00', 19.4, 88.3, 6.0, 'normal'), +('2025-06-05 01:40:00', 19.8, 85.4, 5.8, 'normal'), +('2025-06-05 02:00:00', 19.4, 91.3, 6.0, 'normal'), +('2025-06-05 02:20:00', 20.5, 89.8, 5.9, 'normal'), +('2025-06-05 02:40:00', 21.0, 91.3, 5.9, 'normal'), +('2025-06-05 03:00:00', 19.1, 90.6, 6.0, 'normal'), +('2025-06-05 03:20:00', 20.0, 88.3, 5.9, 'normal'), +('2025-06-05 03:40:00', 20.7, 89.6, 5.8, 'normal'), +('2025-06-05 04:00:00', 21.3, 83.0, 5.9, 'normal'), +('2025-06-05 04:20:00', 22.1, 88.7, 6.1, 'normal'), +('2025-06-05 04:40:00', 22.6, 84.3, 6.1, 'normal'), +('2025-06-05 05:00:00', 22.0, 86.9, 5.8, 'normal'), +('2025-06-05 05:20:00', 23.1, 78.4, 5.8, 'normal'), +('2025-06-05 05:40:00', 23.2, 78.8, 6.0, 'normal'), +('2025-06-05 06:00:00', 25.1, 74.7, 5.8, 'normal'), +('2025-06-05 06:20:00', 24.5, 83.4, 5.9, 'normal'), +('2025-06-05 06:40:00', 24.5, 81.0, 5.8, 'normal'), +('2025-06-05 07:00:00', 26.1, 77.9, 6.0, 'normal'), +('2025-06-05 07:20:00', 25.4, 77.9, 5.9, 'normal'), +('2025-06-05 07:40:00', 25.0, 76.6, 6.5, 'normal'), +('2025-06-05 08:00:00', 26.4, 74.0, 5.9, 'normal'), +('2025-06-05 08:20:00', 26.5, 79.2, 6.2, 'normal'), +('2025-06-05 08:40:00', 20.2, 68.6, 5.9, 'low_temp'), +('2025-06-05 09:00:00', 28.0, 67.9, 5.9, 'normal'), +('2025-06-05 09:20:00', 28.2, 79.6, 6.0, 'normal'), +('2025-06-05 09:40:00', 28.5, 72.5, 6.0, 'normal'), +('2025-06-05 10:00:00', 30.4, 71.4, 5.8, 'normal'), +('2025-06-05 10:20:00', 28.4, 75.3, 6.1, 'normal'), +('2025-06-05 10:40:00', 27.0, 70.7, 6.1, 'normal'), +('2025-06-05 11:00:00', 28.6, 71.8, 6.0, 'normal'), +('2025-06-05 11:20:00', 28.7, 71.6, 6.0, 'normal'), +('2025-06-05 11:40:00', 29.5, 66.9, 6.1, 'normal'), +('2025-06-05 12:00:00', 30.0, 68.7, 6.0, 'normal'), +('2025-06-05 12:20:00', 29.4, 74.6, 5.8, 'normal'), +('2025-06-05 12:40:00', 27.5, 72.8, 6.1, 'normal'), +('2025-06-05 13:00:00', 28.7, 71.9, 6.0, 'normal'), +('2025-06-05 13:20:00', 28.7, 73.3, 6.2, 'normal'), +('2025-06-05 13:40:00', 27.7, 73.4, 6.6, 'normal'), +('2025-06-05 14:00:00', 29.2, 77.1, 5.9, 'normal'), +('2025-06-05 14:20:00', 28.0, 70.3, 5.9, 'normal'), +('2025-06-05 14:40:00', 27.3, 76.9, 6.5, 'normal'), +('2025-06-05 15:00:00', 27.7, 73.6, 6.3, 'normal'), +('2025-06-05 15:20:00', 26.7, 70.4, 6.8, 'normal'), +('2025-06-05 15:40:00', 27.1, 76.4, 6.9, 'normal'), +('2025-06-05 16:00:00', 27.3, 72.6, 6.6, 'normal'), +('2025-06-05 16:20:00', 26.2, 80.9, 5.9, 'normal'), +('2025-06-05 16:40:00', 25.1, 77.0, 6.2, 'normal'), +('2025-06-05 17:00:00', 24.4, 79.6, 5.8, 'normal'), +('2025-06-05 17:20:00', 25.3, 78.0, 6.4, 'normal'), +('2025-06-05 17:40:00', 24.0, 78.0, 6.2, 'normal'), +('2025-06-05 18:00:00', 24.0, 81.5, 6.3, 'normal'), +('2025-06-05 18:20:00', 23.2, 82.9, 6.3, 'normal'), +('2025-06-05 18:40:00', 23.0, 83.6, 5.8, 'normal'), +('2025-06-05 19:00:00', 23.8, 81.0, 6.0, 'normal'), +('2025-06-05 19:20:00', 21.9, 84.2, 5.7, 'normal'), +('2025-06-05 19:40:00', 22.1, 88.0, 6.8, 'normal'), +('2025-06-05 20:00:00', 20.9, 79.5, 6.9, 'normal'), +('2025-06-05 20:20:00', 21.0, 82.6, 6.3, 'normal'), +('2025-06-05 20:40:00', 20.7, 88.5, 6.2, 'normal'), +('2025-06-05 21:00:00', 20.4, 90.6, 5.9, 'normal'), +('2025-06-05 21:20:00', 20.3, 82.6, 5.8, 'normal'), +('2025-06-05 21:40:00', 19.3, 87.0, 5.9, 'normal'), +('2025-06-05 22:00:00', 19.7, 88.6, 6.6, 'normal'), +('2025-06-05 22:20:00', 20.9, 84.1, 6.5, 'normal'), +('2025-06-05 22:40:00', 18.9, 85.7, 6.8, 'normal'), +('2025-06-05 23:00:00', 20.0, 87.4, 6.9, 'normal'), +('2025-06-05 23:20:00', 21.0, 84.4, 6.7, 'normal'), +('2025-06-05 23:40:00', 19.1, 87.9, 6.8, 'normal'), +('2025-06-06 00:00:00', 20.4, 100.0, 6.4, 'normal'), +('2025-06-06 00:20:00', 18.3, 95.1, 6.2, 'normal'), +('2025-06-06 00:40:00', 20.4, 90.6, 6.3, 'normal'), +('2025-06-06 01:00:00', 19.7, 85.8, 6.2, 'normal'), +('2025-06-06 01:20:00', 20.6, 85.0, 6.5, 'normal'), +('2025-06-06 01:40:00', 19.1, 87.1, 6.3, 'normal'), +('2025-06-06 02:00:00', 20.1, 87.6, 6.3, 'normal'), +('2025-06-06 02:20:00', 19.3, 84.9, 6.4, 'normal'), +('2025-06-06 02:40:00', 20.2, 85.1, 6.6, 'normal'), +('2025-06-06 03:00:00', 19.4, 83.8, 6.4, 'normal'), +('2025-06-06 03:20:00', 21.1, 85.9, 6.2, 'normal'), +('2025-06-06 03:40:00', 20.8, 88.5, 6.4, 'normal'), +('2025-06-06 04:00:00', 21.3, 89.7, 6.5, 'normal'), +('2025-06-06 04:20:00', 22.9, 77.1, 6.3, 'normal'), +('2025-06-06 04:40:00', 20.7, 86.3, 6.6, 'normal'); \ No newline at end of file diff --git a/back/temperature_data.db b/back/temperature_data.db new file mode 100644 index 0000000..e69de29 diff --git a/platform/.gitignore b/platform/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/platform/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/platform/.vscode/extensions.json b/platform/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/platform/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/platform/DASHBOARD_OPTIMIZATION.md b/platform/DASHBOARD_OPTIMIZATION.md new file mode 100644 index 0000000..2556af7 --- /dev/null +++ b/platform/DASHBOARD_OPTIMIZATION.md @@ -0,0 +1,225 @@ +# Dashboard 页面优化总结 + +## 优化概述 + +本次优化主要针对 Dashboard 页面进行了全面的现代化改造,实现了自适应、美观、风格统一、现代简约的设计目标。 + +## 主要改进 + +### 1. 全局设计系统统一 + +#### 颜色变量系统 +- 建立了完整的 CSS 变量系统,统一管理所有颜色、间距、字体等设计元素 +- 主色调:`#1890ff` (蓝色系) +- 渐变色:`linear-gradient(135deg, #667eea 0%, #764ba2 100%)` +- 背景色:`#f5f7fa` (浅灰蓝) +- 文字色:`#1a1a1a` (深灰) + +#### 设计令牌 +```css +/* 间距系统 */ +--spacing-xs: 4px; +--spacing-sm: 8px; +--spacing-md: 12px; +--spacing-lg: 16px; +--spacing-xl: 20px; +--spacing-2xl: 24px; +--spacing-3xl: 32px; + +/* 圆角系统 */ +--radius-sm: 6px; +--radius-md: 8px; +--radius-lg: 12px; +--radius-xl: 16px; +--radius-2xl: 20px; + +/* 阴影系统 */ +--shadow-sm: 0 2px 8px rgba(24, 144, 255, 0.04); +--shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08); +--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.12); +``` + +### 2. 组件切分优化 + +#### 页面结构重组 +- 移除了冗余的页面标题,简化页面头部 +- 将操作按钮移至右上角,提升用户体验 +- 优化了区域标题设计,添加了功能标签 + +#### 组件职责明确 +- `StatCards`: 统计卡片展示 +- `StatCardItem`: 单个统计卡片 +- `MapSection`: 地图组件 +- `TemperatureMonitor`: 温度监控 +- `HumidityMonitor`: 湿度监控 +- `PersonnelManagement`: 人员管理 +- `AISection`: AI助手 + +### 3. 图标系统升级 + +#### 使用 Lucide Vue 图标 +- 替换了自定义图标组件,使用成熟的图标库 +- 统一图标风格和大小规范 +- 提升图标加载性能和维护性 + +```vue +import { RefreshCw, Download, X, Maximize2 } from 'lucide-vue-next'; +``` + +### 4. 响应式设计优化 + +#### 断点系统 +- 1400px: 大屏幕适配 +- 1200px: 桌面端适配 +- 768px: 平板端适配 +- 480px: 移动端适配 + +#### 自适应布局 +- 使用 CSS Grid 实现灵活的布局系统 +- 统计卡片支持自动换行 +- 监控组件在小屏幕下垂直排列 + +### 5. 视觉效果提升 + +#### 现代简约设计 +- 使用毛玻璃效果 (`backdrop-filter: blur()`) +- 渐变背景和按钮 +- 柔和的阴影和圆角 +- 平滑的动画过渡 + +#### 交互体验 +- 悬停效果和微动画 +- 加载状态反馈 +- 通知系统优化 + +### 6. 性能优化 + +#### 代码优化 +- 使用 CSS 变量减少重复代码 +- 组件按需加载 +- 图标库按需导入 + +#### 样式优化 +- 使用 CSS 变量提升主题切换效率 +- 优化选择器性能 +- 减少不必要的样式计算 + +## 技术实现 + +### 1. CSS 变量系统 +```css +:root { + /* 主色调 */ + --primary-color: #1890ff; + --primary-light: #40a9ff; + --primary-dark: #096dd9; + + /* 渐变色 */ + --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + + /* 背景色 */ + --bg-main: #f5f7fa; + --bg-card: #fff; + --bg-card-hover: rgba(255, 255, 255, 0.95); +} +``` + +### 2. 响应式布局 +```css +.main-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--spacing-3xl); +} + +@media (max-width: 1200px) { + .main-content { + grid-template-columns: 1fr; + } +} +``` + +### 3. 组件化设计 +```vue + +``` + +## 设计原则 + +### 1. 克制设计 +- 避免过度装饰,保持界面简洁 +- 使用留白和间距创造层次感 +- 颜色使用克制,主色调不超过3种 + +### 2. 现代简约 +- 扁平化设计风格 +- 清晰的视觉层次 +- 一致的设计语言 + +### 3. 用户体验优先 +- 直观的操作流程 +- 清晰的信息架构 +- 流畅的交互反馈 + +## 后续优化建议 + +### 1. 主题系统 +- 支持深色模式切换 +- 多主题色彩方案 +- 用户自定义主题 + +### 2. 性能优化 +- 组件懒加载 +- 虚拟滚动 +- 图片懒加载 + +### 3. 功能增强 +- 数据可视化图表 +- 实时数据更新 +- 个性化配置 + +### 4. 无障碍优化 +- 键盘导航支持 +- 屏幕阅读器兼容 +- 高对比度模式 + +## 总结 + +通过本次优化,Dashboard 页面实现了: +- ✅ 统一的设计系统 +- ✅ 现代化的视觉效果 +- ✅ 完善的响应式支持 +- ✅ 优秀的用户体验 +- ✅ 良好的代码可维护性 + +整体设计风格现代简约、克制优雅,符合当代 Web 应用的设计趋势。 \ No newline at end of file diff --git a/platform/ENVIRONMENT_MONITORING_TODO.md b/platform/ENVIRONMENT_MONITORING_TODO.md new file mode 100644 index 0000000..65c7923 --- /dev/null +++ b/platform/ENVIRONMENT_MONITORING_TODO.md @@ -0,0 +1,125 @@ +# 环境监控系统优化待办清单 + +## ✅ 已完成任务 + +### 1. 设备地理分布模态框优化 +- ✅ 修复模态框层级问题,使用Teleport挂载到body +- ✅ 实现全局居中定位 +- ✅ 优化模态框样式和交互体验 + +### 2. 生长趋势分析卡片重构 +- ✅ 创建新的GrowthTrendAnalysis组件 +- ✅ 集成ECharts图表库 +- ✅ 实现30天生长趋势图表 +- ✅ 添加株高、叶片数、分蘖数三条数据线 +- ✅ 实现图表自适应和响应式设计 +- ✅ 添加生长状态概览面板 +- ✅ 实现可展开的详细信息区域 +- ✅ 添加刷新功能和加载状态 +- ✅ 使用现代化设计风格 + +### 3. 温度监控组件优化 +- ✅ 重新设计温度监控卡片 +- ✅ 提升现代感和文本可读性 +- ✅ 增加设备信息和刷新按钮状态 +- ✅ 优化布局和样式 +- ✅ 实现24小时趋势图表 +- ✅ 添加实时随机数据填充 + +### 4. 湿度监控组件优化 +- ✅ 重新设计湿度监控卡片 +- ✅ 实现24小时趋势图表 +- ✅ 添加实时随机数据填充 +- ✅ 优化图表样式和交互 + +### 5. AI预警系统优化 +- ✅ 重新设计AIyujing组件 +- ✅ 增加预警统计面板 +- ✅ 优化列表卡片和详情模态框 +- ✅ 改进交互和视觉效果 + +### 6. AI助手卡片优化 +- ✅ 重新设计AI助手卡片 +- ✅ 增加状态徽章和操作按钮 +- ✅ 改进布局和视觉效果 + +### 7. 设备运行状态卡片 +- ✅ 创建DeviceStatusCard组件 +- ✅ 实现设备总数、在线、离线、故障统计 +- ✅ 添加设备类型分布展示 +- ✅ 实现故障设备列表和详情 +- ✅ 添加数据刷新和展开功能 +- ✅ 集成到Dashboard页面布局 + +## 🔄 进行中任务 + +### 1. 图表样式统一优化 +- ✅ 重构24小时温度趋势图表样式 +- ✅ 重构24小时湿度趋势图表样式 +- ✅ 将刷新时间改为1秒 +- ✅ 添加渐变背景和区域填充 +- ✅ 优化数据点和阈值线样式 +- ✅ 添加图表标题和阈值标签 +- 🔄 完善图表响应式适配 + +## 📋 待完成任务 + +### 1. 性能优化 +- [ ] 实现图表懒加载 +- [ ] 优化大数据量图表渲染 +- [ ] 添加图表缓存机制 + +### 2. 数据集成 +- [ ] 连接真实数据源 +- [ ] 实现数据实时更新 +- [ ] 添加数据验证和错误处理 + +### 3. 用户体验优化 +- [ ] 添加图表加载动画 +- [ ] 优化移动端体验 +- [ ] 添加图表导出功能 + +### 4. 功能扩展 +- [ ] 添加更多作物类型支持 +- [ ] 实现历史数据对比 +- [ ] 添加预测分析功能 + +## 🎯 设计目标 + +### 整体风格 +- ✅ 现代简约设计 +- ✅ 克制优雅的视觉效果 +- ✅ 统一的设计语言 +- ✅ 良好的视觉层次 + +### 交互体验 +- ✅ 流畅的动画过渡 +- ✅ 直观的操作反馈 +- ✅ 清晰的信息展示 +- ✅ 便捷的功能访问 + +### 技术实现 +- ✅ 组件化架构 +- ✅ 响应式设计 +- ✅ 性能优化 +- ✅ 代码可维护性 + +## 📊 完成度统计 + +- **总体完成度**: 92% +- **核心功能**: 95% +- **UI/UX设计**: 98% +- **技术实现**: 90% +- **性能优化**: 80% + +## 🚀 下一步计划 + +1. 完善图表样式统一 +2. 优化移动端适配 +3. 添加更多交互功能 +4. 实现数据持久化 +5. 添加用户个性化设置 + +--- + +*最后更新: 2024年12月* \ No newline at end of file diff --git a/platform/GROWTH_TREND_ANALYSIS_OPTIMIZATION.md b/platform/GROWTH_TREND_ANALYSIS_OPTIMIZATION.md new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/platform/GROWTH_TREND_ANALYSIS_OPTIMIZATION.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/platform/README.md b/platform/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/platform/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/platform/login-right01.png b/platform/login-right01.png new file mode 100644 index 0000000..369b956 Binary files /dev/null and b/platform/login-right01.png differ diff --git a/platform/package-lock.json b/platform/package-lock.json new file mode 100644 index 0000000..44749f8 --- /dev/null +++ b/platform/package-lock.json @@ -0,0 +1,2450 @@ +{ + "name": "platform", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "platform", + "version": "0.0.0", + "dependencies": { + "axios": "^1.9.0", + "dompurify": "^3.2.6", + "echarts": "^5.6.0", + "element-plus": "^2.9.10", + "gsap": "^3.13.0", + "html2canvas": "^1.4.1", + "jspdf": "^3.0.1", + "leaflet": "^1.9.4", + "lucide-vue-next": "^0.525.0", + "marked": "^16.0.0", + "pinia": "^3.0.2", + "vue": "^3.5.13", + "vue-echarts": "^7.0.3", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@vitejs/plugin-vue": "^5.2.3", + "@vue/tsconfig": "^0.7.0", + "typescript": "~5.8.3", + "vite": "^6.3.5", + "vue-tsc": "^2.2.8" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.0.tgz", + "integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.0.tgz", + "integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", + "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", + "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", + "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", + "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", + "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", + "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", + "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", + "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", + "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", + "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", + "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", + "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", + "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", + "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", + "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", + "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz", + "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", + "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", + "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", + "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/marked": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/@types/marked/-/marked-5.0.2.tgz", + "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.13", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.13.tgz", + "integrity": "sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.13" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.13", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.13.tgz", + "integrity": "sha512-l/EBcc2FkvHgz2ZxV+OZK3kMSroMr7nN3sZLF2/f6kWW66q8+tEL4giiYyFjt0BcubqJhBt6soYIrAPhg/Yr+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.13", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.13.tgz", + "integrity": "sha512-Ukz4xv84swJPupZeoFsQoeJEOm7U9pqsEnaGGgt5ni3SCTa22m8oJP5Nng3Wed7Uw5RBELdLxxORX8YhJPyOgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.13", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.6", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.6.tgz", + "integrity": "sha512-geu7ds7tem2Y7Wz+WgbnbZ6T5eadOvozHZ23Atk/8tksHMFOFylKi1xgGlQlVn0wlkEf4hu+vd5ctj1G4kFtwA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.6", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.6", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.6.tgz", + "integrity": "sha512-yFEgJZ/WblEsojQQceuyK6FzpFDx4kqrz2ohInxNj5/DnhoX023upTv4OD6lNPLAA5LLkbwPVb10o/7b+Y4FVA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.10", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-2.2.10.tgz", + "integrity": "sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.11", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.7.0.tgz", + "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/birpc": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.3.0.tgz", + "integrity": "sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.43.0", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.43.0.tgz", + "integrity": "sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/element-plus": { + "version": "2.9.10", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.10.tgz", + "integrity": "sha512-W2v9jWnm1kl/zm4bSvCh8aFCVlxvhG3fmqiDZwyd6WQiWGE595J/mpjcCggEr+49QDgIymhXrpPMOPPSARUbng==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gsap": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.13.0.tgz", + "integrity": "sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==", + "license": "Standard 'no charge' license: https://gsap.com/standard-license." + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/jspdf": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-3.0.1.tgz", + "integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.7", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lucide-vue-next": { + "version": "0.525.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.525.0.tgz", + "integrity": "sha512-Xf8+x8B2DrnGDV/rxylS+KBp2FIe6ljwDn2JsGTZZvXIfhmm/q+nv8RuGO1OyoMjOVkkz7CqtUqJfwtFPRbB2w==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/marked": { + "version": "16.0.0", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.0.0.tgz", + "integrity": "sha512-MUKMXDjsD/eptB7GPzxo4xcnLS6oo7/RHimUMHEDRhUooPwmN9BEpMl7AEOJv3bmso169wHI2wUF9VQgL7zfmA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.2.tgz", + "integrity": "sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/@vue/devtools-api": { + "version": "7.7.6", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.6.tgz", + "integrity": "sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.6" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rollup": { + "version": "4.40.2", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.2.tgz", + "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.2", + "@rollup/rollup-android-arm64": "4.40.2", + "@rollup/rollup-darwin-arm64": "4.40.2", + "@rollup/rollup-darwin-x64": "4.40.2", + "@rollup/rollup-freebsd-arm64": "4.40.2", + "@rollup/rollup-freebsd-x64": "4.40.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", + "@rollup/rollup-linux-arm-musleabihf": "4.40.2", + "@rollup/rollup-linux-arm64-gnu": "4.40.2", + "@rollup/rollup-linux-arm64-musl": "4.40.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-musl": "4.40.2", + "@rollup/rollup-linux-s390x-gnu": "4.40.2", + "@rollup/rollup-linux-x64-gnu": "4.40.2", + "@rollup/rollup-linux-x64-musl": "4.40.2", + "@rollup/rollup-win32-arm64-msvc": "4.40.2", + "@rollup/rollup-win32-ia32-msvc": "4.40.2", + "@rollup/rollup-win32-x64-msvc": "4.40.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "license": "MIT", + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-echarts": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-7.0.3.tgz", + "integrity": "sha512-/jSxNwOsw5+dYAUcwSfkLwKPuzTQ0Cepz1LxCOpj2QcHrrmUa/Ql0eQqMmc1rTPQVrh2JQ29n2dhq75ZcHvRDw==", + "license": "MIT", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/runtime-core": "^3.0.0", + "echarts": "^5.5.1", + "vue": "^2.7.0 || ^3.1.1" + }, + "peerDependenciesMeta": { + "@vue/runtime-core": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.10", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-2.2.10.tgz", + "integrity": "sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.11", + "@vue/language-core": "2.2.10" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/platform/package.json b/platform/package.json new file mode 100644 index 0000000..e42615a --- /dev/null +++ b/platform/package.json @@ -0,0 +1,36 @@ +{ + "name": "platform", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.9.0", + "dompurify": "^3.2.6", + "echarts": "^5.6.0", + "element-plus": "^2.9.10", + "gsap": "^3.13.0", + "html2canvas": "^1.4.1", + "jspdf": "^3.0.1", + "leaflet": "^1.9.4", + "lucide-vue-next": "^0.525.0", + "marked": "^16.0.0", + "pinia": "^3.0.2", + "vue": "^3.5.13", + "vue-echarts": "^7.0.3", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/dompurify": "^3.0.5", + "@types/marked": "^5.0.2", + "@vitejs/plugin-vue": "^5.2.3", + "@vue/tsconfig": "^0.7.0", + "typescript": "~5.8.3", + "vite": "^6.3.5", + "vue-tsc": "^2.2.8" + } +} diff --git a/platform/public/choimg/1.JPG b/platform/public/choimg/1.JPG new file mode 100644 index 0000000..960071e Binary files /dev/null and b/platform/public/choimg/1.JPG differ diff --git a/platform/public/choimg/10.JPG b/platform/public/choimg/10.JPG new file mode 100644 index 0000000..bf1f057 Binary files /dev/null and b/platform/public/choimg/10.JPG differ diff --git a/platform/public/choimg/11.JPG b/platform/public/choimg/11.JPG new file mode 100644 index 0000000..0cd400b Binary files /dev/null and b/platform/public/choimg/11.JPG differ diff --git a/platform/public/choimg/12.JPG b/platform/public/choimg/12.JPG new file mode 100644 index 0000000..edfad23 Binary files /dev/null and b/platform/public/choimg/12.JPG differ diff --git a/platform/public/choimg/13.JPG b/platform/public/choimg/13.JPG new file mode 100644 index 0000000..850e764 Binary files /dev/null and b/platform/public/choimg/13.JPG differ diff --git a/platform/public/choimg/14.JPG b/platform/public/choimg/14.JPG new file mode 100644 index 0000000..d46867d Binary files /dev/null and b/platform/public/choimg/14.JPG differ diff --git a/platform/public/choimg/15.JPG b/platform/public/choimg/15.JPG new file mode 100644 index 0000000..d46867d Binary files /dev/null and b/platform/public/choimg/15.JPG differ diff --git a/platform/public/choimg/16.JPG b/platform/public/choimg/16.JPG new file mode 100644 index 0000000..66b400e Binary files /dev/null and b/platform/public/choimg/16.JPG differ diff --git a/platform/public/choimg/17.JPG b/platform/public/choimg/17.JPG new file mode 100644 index 0000000..98ac113 Binary files /dev/null and b/platform/public/choimg/17.JPG differ diff --git a/platform/public/choimg/2.JPG b/platform/public/choimg/2.JPG new file mode 100644 index 0000000..d540c4a Binary files /dev/null and b/platform/public/choimg/2.JPG differ diff --git a/platform/public/choimg/3.JPG b/platform/public/choimg/3.JPG new file mode 100644 index 0000000..d540c4a Binary files /dev/null and b/platform/public/choimg/3.JPG differ diff --git a/platform/public/choimg/4.JPG b/platform/public/choimg/4.JPG new file mode 100644 index 0000000..5958985 Binary files /dev/null and b/platform/public/choimg/4.JPG differ diff --git a/platform/public/choimg/5.JPG b/platform/public/choimg/5.JPG new file mode 100644 index 0000000..5958985 Binary files /dev/null and b/platform/public/choimg/5.JPG differ diff --git a/platform/public/choimg/6.JPG b/platform/public/choimg/6.JPG new file mode 100644 index 0000000..5b184d1 Binary files /dev/null and b/platform/public/choimg/6.JPG differ diff --git a/platform/public/choimg/7.JPG b/platform/public/choimg/7.JPG new file mode 100644 index 0000000..68549a1 Binary files /dev/null and b/platform/public/choimg/7.JPG differ diff --git a/platform/public/choimg/8.JPG b/platform/public/choimg/8.JPG new file mode 100644 index 0000000..5835428 Binary files /dev/null and b/platform/public/choimg/8.JPG differ diff --git a/platform/public/choimg/9.JPG b/platform/public/choimg/9.JPG new file mode 100644 index 0000000..5835428 Binary files /dev/null and b/platform/public/choimg/9.JPG differ diff --git a/platform/public/map_with_random_points.html b/platform/public/map_with_random_points.html new file mode 100644 index 0000000..dd8a2e5 --- /dev/null +++ b/platform/public/map_with_random_points.html @@ -0,0 +1,1111 @@ + + + + + + 智能设备地理分布与病虫害检测系统 + + + + + + + + + + + + + + +
+
+ +

智能设备地理分布与病虫害检测系统

+
+ +
+ + + +
+
+ + +
+ + + +
+
设备状态
+
+
+ 正常运行 +
+
+
+ 警告状态 +
+
+
+ 故障状态 +
+
+
+ 离线状态 +
+
+ + +
+
+
+ 加载中... +
+

正在加载地图和设备数据...

+
+
+ + +
+
+
农业基地实拍
+
+ 农业基地 +
+
+

15亩地
设备7台

+
+
+
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform/public/vite.svg b/platform/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/platform/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/platform/src/App.vue b/platform/src/App.vue new file mode 100644 index 0000000..c5034c4 --- /dev/null +++ b/platform/src/App.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/platform/src/aioo/eventBus.js b/platform/src/aioo/eventBus.js new file mode 100644 index 0000000..90773aa --- /dev/null +++ b/platform/src/aioo/eventBus.js @@ -0,0 +1,21 @@ +import { createApp } from 'vue'; +export const eventBus = createApp().config.globalProperties.$bus = new class { + constructor() { + this.events = {}; + } + + on(event, callback) { + if (!this.events[event]) { + this.events[event] = []; + } + this.events[event].push(callback); + } + + emit(event, ...args) { + if (this.events[event]) { + this.events[event].forEach(callback => { + callback(...args); + }); + } + } +}(); \ No newline at end of file diff --git a/platform/src/assets/a1.jpg b/platform/src/assets/a1.jpg new file mode 100644 index 0000000..e556cbc Binary files /dev/null and b/platform/src/assets/a1.jpg differ diff --git a/platform/src/assets/a2.jpg b/platform/src/assets/a2.jpg new file mode 100644 index 0000000..5b69680 Binary files /dev/null and b/platform/src/assets/a2.jpg differ diff --git a/platform/src/assets/a3.jpg b/platform/src/assets/a3.jpg new file mode 100644 index 0000000..5530c0e Binary files /dev/null and b/platform/src/assets/a3.jpg differ diff --git a/platform/src/assets/a4.jpg b/platform/src/assets/a4.jpg new file mode 100644 index 0000000..fc70158 Binary files /dev/null and b/platform/src/assets/a4.jpg differ diff --git a/platform/src/assets/a5.jpg b/platform/src/assets/a5.jpg new file mode 100644 index 0000000..e1521f8 Binary files /dev/null and b/platform/src/assets/a5.jpg differ diff --git a/platform/src/assets/banner1.jpg b/platform/src/assets/banner1.jpg new file mode 100644 index 0000000..bfe3c25 Binary files /dev/null and b/platform/src/assets/banner1.jpg differ diff --git a/platform/src/assets/c1.jpg b/platform/src/assets/c1.jpg new file mode 100644 index 0000000..e66c0fc Binary files /dev/null and b/platform/src/assets/c1.jpg differ diff --git a/platform/src/assets/c2.jpg b/platform/src/assets/c2.jpg new file mode 100644 index 0000000..20e1f32 Binary files /dev/null and b/platform/src/assets/c2.jpg differ diff --git a/platform/src/assets/c3.jpg b/platform/src/assets/c3.jpg new file mode 100644 index 0000000..b5abb73 Binary files /dev/null and b/platform/src/assets/c3.jpg differ diff --git a/platform/src/assets/c4.jpg b/platform/src/assets/c4.jpg new file mode 100644 index 0000000..c4ab8b2 Binary files /dev/null and b/platform/src/assets/c4.jpg differ diff --git a/platform/src/assets/c5.jpg b/platform/src/assets/c5.jpg new file mode 100644 index 0000000..2fb9623 Binary files /dev/null and b/platform/src/assets/c5.jpg differ diff --git a/platform/src/assets/c6.jpg b/platform/src/assets/c6.jpg new file mode 100644 index 0000000..7da834e Binary files /dev/null and b/platform/src/assets/c6.jpg differ diff --git a/platform/src/assets/home-under.png b/platform/src/assets/home-under.png new file mode 100644 index 0000000..a8a06ac Binary files /dev/null and b/platform/src/assets/home-under.png differ diff --git a/platform/src/assets/home.png b/platform/src/assets/home.png new file mode 100644 index 0000000..e23ed63 Binary files /dev/null and b/platform/src/assets/home.png differ diff --git a/platform/src/assets/home1.png b/platform/src/assets/home1.png new file mode 100644 index 0000000..ab6b97b Binary files /dev/null and b/platform/src/assets/home1.png differ diff --git a/platform/src/assets/home2.png b/platform/src/assets/home2.png new file mode 100644 index 0000000..9e60353 Binary files /dev/null and b/platform/src/assets/home2.png differ diff --git a/platform/src/assets/login.jpg b/platform/src/assets/login.jpg new file mode 100644 index 0000000..677ba0f Binary files /dev/null and b/platform/src/assets/login.jpg differ diff --git a/platform/src/assets/oo.png b/platform/src/assets/oo.png new file mode 100644 index 0000000..35e36c7 Binary files /dev/null and b/platform/src/assets/oo.png differ diff --git a/platform/src/assets/suo.png b/platform/src/assets/suo.png new file mode 100644 index 0000000..ad0256b Binary files /dev/null and b/platform/src/assets/suo.png differ diff --git a/platform/src/assets/syh1.png b/platform/src/assets/syh1.png new file mode 100644 index 0000000..a383be8 Binary files /dev/null and b/platform/src/assets/syh1.png differ diff --git a/platform/src/assets/syh2.png b/platform/src/assets/syh2.png new file mode 100644 index 0000000..d949924 Binary files /dev/null and b/platform/src/assets/syh2.png differ diff --git a/platform/src/assets/under.png b/platform/src/assets/under.png new file mode 100644 index 0000000..428d6ae Binary files /dev/null and b/platform/src/assets/under.png differ diff --git a/platform/src/assets/wj.png b/platform/src/assets/wj.png new file mode 100644 index 0000000..778ebd5 Binary files /dev/null and b/platform/src/assets/wj.png differ diff --git a/platform/src/assets/人像.png b/platform/src/assets/人像.png new file mode 100644 index 0000000..7dc2a02 Binary files /dev/null and b/platform/src/assets/人像.png differ diff --git a/platform/src/assets/信件.png b/platform/src/assets/信件.png new file mode 100644 index 0000000..c78b8d3 Binary files /dev/null and b/platform/src/assets/信件.png differ diff --git a/platform/src/assets/农田.jpg b/platform/src/assets/农田.jpg new file mode 100644 index 0000000..5802c34 Binary files /dev/null and b/platform/src/assets/农田.jpg differ diff --git a/platform/src/assets/图层 2.png b/platform/src/assets/图层 2.png new file mode 100644 index 0000000..ca01da8 Binary files /dev/null and b/platform/src/assets/图层 2.png differ diff --git a/platform/src/assets/图层 3.png b/platform/src/assets/图层 3.png new file mode 100644 index 0000000..78361a9 Binary files /dev/null and b/platform/src/assets/图层 3.png differ diff --git a/platform/src/assets/图层 4.png b/platform/src/assets/图层 4.png new file mode 100644 index 0000000..02b41ee Binary files /dev/null and b/platform/src/assets/图层 4.png differ diff --git a/platform/src/assets/验证码.png b/platform/src/assets/验证码.png new file mode 100644 index 0000000..bef709d Binary files /dev/null and b/platform/src/assets/验证码.png differ diff --git a/platform/src/components/dashboard/AIAssistantSection.vue b/platform/src/components/dashboard/AIAssistantSection.vue new file mode 100644 index 0000000..4ef0d90 --- /dev/null +++ b/platform/src/components/dashboard/AIAssistantSection.vue @@ -0,0 +1,28 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/AISection.vue b/platform/src/components/dashboard/AISection.vue new file mode 100644 index 0000000..1ba89c9 --- /dev/null +++ b/platform/src/components/dashboard/AISection.vue @@ -0,0 +1,266 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/ChartSection.vue b/platform/src/components/dashboard/ChartSection.vue new file mode 100644 index 0000000..085c4de --- /dev/null +++ b/platform/src/components/dashboard/ChartSection.vue @@ -0,0 +1,135 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/DeviceMap.vue b/platform/src/components/dashboard/DeviceMap.vue new file mode 100644 index 0000000..3349046 --- /dev/null +++ b/platform/src/components/dashboard/DeviceMap.vue @@ -0,0 +1,127 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/DoughnutChart.vue b/platform/src/components/dashboard/DoughnutChart.vue new file mode 100644 index 0000000..dbe82ca --- /dev/null +++ b/platform/src/components/dashboard/DoughnutChart.vue @@ -0,0 +1,138 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/MapSection.vue b/platform/src/components/dashboard/MapSection.vue new file mode 100644 index 0000000..5826ba2 --- /dev/null +++ b/platform/src/components/dashboard/MapSection.vue @@ -0,0 +1,374 @@ + + + + + diff --git a/platform/src/components/dashboard/StatCardItem.vue b/platform/src/components/dashboard/StatCardItem.vue new file mode 100644 index 0000000..e132cf3 --- /dev/null +++ b/platform/src/components/dashboard/StatCardItem.vue @@ -0,0 +1,301 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/StatCards.vue b/platform/src/components/dashboard/StatCards.vue new file mode 100644 index 0000000..d3826ba --- /dev/null +++ b/platform/src/components/dashboard/StatCards.vue @@ -0,0 +1,183 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/dashboard/SunburstChart.vue b/platform/src/components/dashboard/SunburstChart.vue new file mode 100644 index 0000000..e7d6ce1 --- /dev/null +++ b/platform/src/components/dashboard/SunburstChart.vue @@ -0,0 +1,144 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/analysis/GrowthTrendAnalysis.vue b/platform/src/components/features/analysis/GrowthTrendAnalysis.vue new file mode 100644 index 0000000..401c466 --- /dev/null +++ b/platform/src/components/features/analysis/GrowthTrendAnalysis.vue @@ -0,0 +1,757 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/devices/DeviceStatusCard.vue b/platform/src/components/features/devices/DeviceStatusCard.vue new file mode 100644 index 0000000..641def3 --- /dev/null +++ b/platform/src/components/features/devices/DeviceStatusCard.vue @@ -0,0 +1,727 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/devices/bingzhuang1.vue b/platform/src/components/features/devices/bingzhuang1.vue new file mode 100644 index 0000000..ce78a41 --- /dev/null +++ b/platform/src/components/features/devices/bingzhuang1.vue @@ -0,0 +1,111 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/monitoring/HumidityMonitor.vue b/platform/src/components/features/monitoring/HumidityMonitor.vue new file mode 100644 index 0000000..a5ec66f --- /dev/null +++ b/platform/src/components/features/monitoring/HumidityMonitor.vue @@ -0,0 +1,1033 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/monitoring/TemperatureMonitor.vue b/platform/src/components/features/monitoring/TemperatureMonitor.vue new file mode 100644 index 0000000..0405e05 --- /dev/null +++ b/platform/src/components/features/monitoring/TemperatureMonitor.vue @@ -0,0 +1,613 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/personnel/DeviceCount.vue b/platform/src/components/features/personnel/DeviceCount.vue new file mode 100644 index 0000000..2b04a33 --- /dev/null +++ b/platform/src/components/features/personnel/DeviceCount.vue @@ -0,0 +1,45 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/personnel/PermissionBadge.vue b/platform/src/components/features/personnel/PermissionBadge.vue new file mode 100644 index 0000000..202befd --- /dev/null +++ b/platform/src/components/features/personnel/PermissionBadge.vue @@ -0,0 +1,67 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/personnel/PersonnelManagement.vue b/platform/src/components/features/personnel/PersonnelManagement.vue new file mode 100644 index 0000000..c24231e --- /dev/null +++ b/platform/src/components/features/personnel/PersonnelManagement.vue @@ -0,0 +1,340 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/features/personnel/PersonnelModal.vue b/platform/src/components/features/personnel/PersonnelModal.vue new file mode 100644 index 0000000..991baa2 --- /dev/null +++ b/platform/src/components/features/personnel/PersonnelModal.vue @@ -0,0 +1,361 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/gu3.vue b/platform/src/components/gu3.vue new file mode 100644 index 0000000..36b13a1 --- /dev/null +++ b/platform/src/components/gu3.vue @@ -0,0 +1,228 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/home.vue b/platform/src/components/home.vue new file mode 100644 index 0000000..e7bb980 --- /dev/null +++ b/platform/src/components/home.vue @@ -0,0 +1,242 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/layout/HeaderBar.vue b/platform/src/components/layout/HeaderBar.vue new file mode 100644 index 0000000..6091ea1 --- /dev/null +++ b/platform/src/components/layout/HeaderBar.vue @@ -0,0 +1,158 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/layout/MainLayout.vue b/platform/src/components/layout/MainLayout.vue new file mode 100644 index 0000000..2c0cb93 --- /dev/null +++ b/platform/src/components/layout/MainLayout.vue @@ -0,0 +1,88 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/layout/Sidebar.vue b/platform/src/components/layout/Sidebar.vue new file mode 100644 index 0000000..5a09b06 --- /dev/null +++ b/platform/src/components/layout/Sidebar.vue @@ -0,0 +1,331 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/login.vue b/platform/src/components/login.vue new file mode 100644 index 0000000..0b8ef03 --- /dev/null +++ b/platform/src/components/login.vue @@ -0,0 +1,193 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/pople5.vue b/platform/src/components/pople5.vue new file mode 100644 index 0000000..10a6e73 --- /dev/null +++ b/platform/src/components/pople5.vue @@ -0,0 +1,1361 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/register.vue b/platform/src/components/register.vue new file mode 100644 index 0000000..e44eca0 --- /dev/null +++ b/platform/src/components/register.vue @@ -0,0 +1,255 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/she4.vue b/platform/src/components/she4.vue new file mode 100644 index 0000000..1625e7f --- /dev/null +++ b/platform/src/components/she4.vue @@ -0,0 +1,492 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/shou.vue b/platform/src/components/shou.vue new file mode 100644 index 0000000..9c75c89 --- /dev/null +++ b/platform/src/components/shou.vue @@ -0,0 +1,228 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/shu2.vue b/platform/src/components/shu2.vue new file mode 100644 index 0000000..50b9238 --- /dev/null +++ b/platform/src/components/shu2.vue @@ -0,0 +1,1351 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/ui/BaseButton.vue b/platform/src/components/ui/BaseButton.vue new file mode 100644 index 0000000..4c24b36 --- /dev/null +++ b/platform/src/components/ui/BaseButton.vue @@ -0,0 +1,188 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/ui/BaseCard.vue b/platform/src/components/ui/BaseCard.vue new file mode 100644 index 0000000..9b761ab --- /dev/null +++ b/platform/src/components/ui/BaseCard.vue @@ -0,0 +1,160 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/ui/icons/DeleteIcon.vue b/platform/src/components/ui/icons/DeleteIcon.vue new file mode 100644 index 0000000..fc23e19 --- /dev/null +++ b/platform/src/components/ui/icons/DeleteIcon.vue @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/platform/src/components/ui/icons/DownloadIcon.vue b/platform/src/components/ui/icons/DownloadIcon.vue new file mode 100644 index 0000000..da9c21b --- /dev/null +++ b/platform/src/components/ui/icons/DownloadIcon.vue @@ -0,0 +1,41 @@ + + + \ No newline at end of file diff --git a/platform/src/components/ui/icons/EditIcon.vue b/platform/src/components/ui/icons/EditIcon.vue new file mode 100644 index 0000000..8b718dc --- /dev/null +++ b/platform/src/components/ui/icons/EditIcon.vue @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/platform/src/components/ui/icons/MaximizeIcon.vue b/platform/src/components/ui/icons/MaximizeIcon.vue new file mode 100644 index 0000000..00157f4 --- /dev/null +++ b/platform/src/components/ui/icons/MaximizeIcon.vue @@ -0,0 +1,48 @@ + + + \ No newline at end of file diff --git a/platform/src/components/ui/icons/RefreshIcon.vue b/platform/src/components/ui/icons/RefreshIcon.vue new file mode 100644 index 0000000..18b73d2 --- /dev/null +++ b/platform/src/components/ui/icons/RefreshIcon.vue @@ -0,0 +1,66 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components/ui/icons/XIcon.vue b/platform/src/components/ui/icons/XIcon.vue new file mode 100644 index 0000000..168b129 --- /dev/null +++ b/platform/src/components/ui/icons/XIcon.vue @@ -0,0 +1,34 @@ + + + \ No newline at end of file diff --git a/platform/src/components1/PrecipitationChart.vue b/platform/src/components1/PrecipitationChart.vue new file mode 100644 index 0000000..d03c68b --- /dev/null +++ b/platform/src/components1/PrecipitationChart.vue @@ -0,0 +1,444 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components1/SunburstChart.vue b/platform/src/components1/SunburstChart.vue new file mode 100644 index 0000000..76ce257 --- /dev/null +++ b/platform/src/components1/SunburstChart.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components1/askai.vue b/platform/src/components1/askai.vue new file mode 100644 index 0000000..66228d2 --- /dev/null +++ b/platform/src/components1/askai.vue @@ -0,0 +1,1215 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components1/home1.vue b/platform/src/components1/home1.vue new file mode 100644 index 0000000..0c64ef2 --- /dev/null +++ b/platform/src/components1/home1.vue @@ -0,0 +1,754 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components1/shebei1.vue b/platform/src/components1/shebei1.vue new file mode 100644 index 0000000..3f56880 --- /dev/null +++ b/platform/src/components1/shebei1.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components1/touxiang.vue b/platform/src/components1/touxiang.vue new file mode 100644 index 0000000..e8396d2 --- /dev/null +++ b/platform/src/components1/touxiang.vue @@ -0,0 +1,40 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/finding2.vue b/platform/src/components2/finding2.vue new file mode 100644 index 0000000..58884e2 --- /dev/null +++ b/platform/src/components2/finding2.vue @@ -0,0 +1,151 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/guang2.vue b/platform/src/components2/guang2.vue new file mode 100644 index 0000000..c5ce542 --- /dev/null +++ b/platform/src/components2/guang2.vue @@ -0,0 +1,186 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/ph.vue b/platform/src/components2/ph.vue new file mode 100644 index 0000000..200c0cd --- /dev/null +++ b/platform/src/components2/ph.vue @@ -0,0 +1,326 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/ph3.vue b/platform/src/components2/ph3.vue new file mode 100644 index 0000000..6a02b45 --- /dev/null +++ b/platform/src/components2/ph3.vue @@ -0,0 +1,532 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/ph4.vue b/platform/src/components2/ph4.vue new file mode 100644 index 0000000..4744f7f --- /dev/null +++ b/platform/src/components2/ph4.vue @@ -0,0 +1,430 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/shidu2.vue b/platform/src/components2/shidu2.vue new file mode 100644 index 0000000..2b7a8a7 --- /dev/null +++ b/platform/src/components2/shidu2.vue @@ -0,0 +1,237 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/shidu3.vue b/platform/src/components2/shidu3.vue new file mode 100644 index 0000000..8e8bc65 --- /dev/null +++ b/platform/src/components2/shidu3.vue @@ -0,0 +1,420 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/shidu4.vue b/platform/src/components2/shidu4.vue new file mode 100644 index 0000000..adcebcb --- /dev/null +++ b/platform/src/components2/shidu4.vue @@ -0,0 +1,289 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/turang2.vue b/platform/src/components2/turang2.vue new file mode 100644 index 0000000..03b0575 --- /dev/null +++ b/platform/src/components2/turang2.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/wendu2.vue b/platform/src/components2/wendu2.vue new file mode 100644 index 0000000..a1c7066 --- /dev/null +++ b/platform/src/components2/wendu2.vue @@ -0,0 +1,320 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components2/wendu3.vue b/platform/src/components2/wendu3.vue new file mode 100644 index 0000000..70f7614 --- /dev/null +++ b/platform/src/components2/wendu3.vue @@ -0,0 +1,266 @@ + + + + + + \ No newline at end of file diff --git a/platform/src/components2/wendu4.vue b/platform/src/components2/wendu4.vue new file mode 100644 index 0000000..ba4c76d --- /dev/null +++ b/platform/src/components2/wendu4.vue @@ -0,0 +1,1032 @@ + + + + + + \ No newline at end of file diff --git a/platform/src/components3/gu4.vue b/platform/src/components3/gu4.vue new file mode 100644 index 0000000..32407fb --- /dev/null +++ b/platform/src/components3/gu4.vue @@ -0,0 +1,130 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components3/gu5.vue b/platform/src/components3/gu5.vue new file mode 100644 index 0000000..5dc44bf --- /dev/null +++ b/platform/src/components3/gu5.vue @@ -0,0 +1,163 @@ + + + + + \ No newline at end of file diff --git a/platform/src/components3/gu6.vue b/platform/src/components3/gu6.vue new file mode 100644 index 0000000..7a1a538 --- /dev/null +++ b/platform/src/components3/gu6.vue @@ -0,0 +1,363 @@ + + + + + \ No newline at end of file diff --git a/platform/src/images/Group.jpg b/platform/src/images/Group.jpg new file mode 100644 index 0000000..b842548 Binary files /dev/null and b/platform/src/images/Group.jpg differ diff --git a/platform/src/images/animaleducation2235 1.png b/platform/src/images/animaleducation2235 1.png new file mode 100644 index 0000000..b95f6b2 Binary files /dev/null and b/platform/src/images/animaleducation2235 1.png differ diff --git a/platform/src/images/bee 1.jpg b/platform/src/images/bee 1.jpg new file mode 100644 index 0000000..e9f5104 Binary files /dev/null and b/platform/src/images/bee 1.jpg differ diff --git a/platform/src/images/home-right01.png b/platform/src/images/home-right01.png new file mode 100644 index 0000000..65be4d1 Binary files /dev/null and b/platform/src/images/home-right01.png differ diff --git a/platform/src/images/home.png b/platform/src/images/home.png new file mode 100644 index 0000000..c6f89e7 Binary files /dev/null and b/platform/src/images/home.png differ diff --git a/platform/src/images/home01.png b/platform/src/images/home01.png new file mode 100644 index 0000000..2520ff9 Binary files /dev/null and b/platform/src/images/home01.png differ diff --git a/platform/src/images/light-bulb.jpg b/platform/src/images/light-bulb.jpg new file mode 100644 index 0000000..8d92499 Binary files /dev/null and b/platform/src/images/light-bulb.jpg differ diff --git a/platform/src/images/login-right.png b/platform/src/images/login-right.png new file mode 100644 index 0000000..1d4994e Binary files /dev/null and b/platform/src/images/login-right.png differ diff --git a/platform/src/images/login-top.png b/platform/src/images/login-top.png new file mode 100644 index 0000000..b8fb820 Binary files /dev/null and b/platform/src/images/login-top.png differ diff --git a/platform/src/images/信件.png b/platform/src/images/信件.png new file mode 100644 index 0000000..c78b8d3 Binary files /dev/null and b/platform/src/images/信件.png differ diff --git a/platform/src/images/验证码.png b/platform/src/images/验证码.png new file mode 100644 index 0000000..bef709d Binary files /dev/null and b/platform/src/images/验证码.png differ diff --git a/platform/src/main.ts b/platform/src/main.ts new file mode 100644 index 0000000..bf76ee7 --- /dev/null +++ b/platform/src/main.ts @@ -0,0 +1,16 @@ +// main.js +import { createApp } from 'vue'; +import App from './App.vue'; +import { createPinia } from 'pinia'; +import router from './router'; // 引入配置好的路由 +import ElementPlus from 'element-plus'; +import 'element-plus/dist/index.css'; +import './style.css'; + +const app = createApp(App); + +app.use(createPinia()); // 安装 Pinia +app.use(router); // 安装 Vue Router +app.use(ElementPlus); // 安装 Element Plus (如果有的话) + +app.mount('#app'); \ No newline at end of file diff --git a/platform/src/pages/AIyujing.vue b/platform/src/pages/AIyujing.vue new file mode 100644 index 0000000..1a8dedd --- /dev/null +++ b/platform/src/pages/AIyujing.vue @@ -0,0 +1,993 @@ + + + + + \ No newline at end of file diff --git a/platform/src/pages/DashboardPage.vue b/platform/src/pages/DashboardPage.vue new file mode 100644 index 0000000..599d32a --- /dev/null +++ b/platform/src/pages/DashboardPage.vue @@ -0,0 +1,531 @@ + + + + + \ No newline at end of file diff --git a/platform/src/pages/DeviceManagementPage.vue b/platform/src/pages/DeviceManagementPage.vue new file mode 100644 index 0000000..20f26bb --- /dev/null +++ b/platform/src/pages/DeviceManagementPage.vue @@ -0,0 +1,767 @@ + + + + + + \ No newline at end of file diff --git a/platform/src/pages/MonitoringPage.vue b/platform/src/pages/MonitoringPage.vue new file mode 100644 index 0000000..8c771bd --- /dev/null +++ b/platform/src/pages/MonitoringPage.vue @@ -0,0 +1,248 @@ + + + + + \ No newline at end of file diff --git a/platform/src/pages/PersonnelPage.vue b/platform/src/pages/PersonnelPage.vue new file mode 100644 index 0000000..f89b550 --- /dev/null +++ b/platform/src/pages/PersonnelPage.vue @@ -0,0 +1,1487 @@ + + + + + \ No newline at end of file diff --git a/platform/src/pages/ReportsPage.vue b/platform/src/pages/ReportsPage.vue new file mode 100644 index 0000000..70be4ec --- /dev/null +++ b/platform/src/pages/ReportsPage.vue @@ -0,0 +1,1390 @@ + + + + + \ No newline at end of file diff --git a/platform/src/pages/shengzhang.vue b/platform/src/pages/shengzhang.vue new file mode 100644 index 0000000..93e5a6f --- /dev/null +++ b/platform/src/pages/shengzhang.vue @@ -0,0 +1,392 @@ + + + + + + \ No newline at end of file diff --git a/platform/src/router/index.ts b/platform/src/router/index.ts new file mode 100644 index 0000000..4c1bdd3 --- /dev/null +++ b/platform/src/router/index.ts @@ -0,0 +1,76 @@ +// router/index.js +import { createRouter, createWebHistory } from 'vue-router' +import HomeView from '../components/home.vue' +import LoginView from '../views/LoginView.vue' +import RegisterView from '../views/RegisterView.vue' +import MainLayout from '../components/layout/MainLayout.vue' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: HomeView + }, + { + path: '/login', + name: 'login', + component: LoginView + }, + { + path: '/register', + name: 'register', + component: RegisterView + }, + { + path: '/', + component: MainLayout, + children: [ + { + path: 'dashboard', + name: 'dashboard', + component: () => import('../pages/DashboardPage.vue'), + meta: { requiresAuth: true } + }, + { + path: 'monitoring', + name: 'monitoring', + component: () => import('../pages/MonitoringPage.vue'), + meta: { requiresAuth: true } + }, + { + path: 'personnel', + name: 'personnel', + component: () => import('../pages/PersonnelPage.vue'), + meta: { requiresAuth: true } + }, + { + path: 'devices', + name: 'devices', + component: () => import('../pages/DeviceManagementPage.vue'), + meta: { requiresAuth: true } + }, + { + path: 'reports', + name: 'reports', + component: () => import('../pages/ReportsPage.vue'), + meta: { requiresAuth: true } + } + ] + } + ] +}) + +// // 路由守卫 +// router.beforeEach((to, from, next) => { +// const isAuthenticated = localStorage.getItem('user') !== null + +// if (to.meta.requiresAuth && !isAuthenticated) { +// next('/login') +// } else { +// next() +// } +// }) + +export default router \ No newline at end of file diff --git a/platform/src/style.css b/platform/src/style.css new file mode 100644 index 0000000..71c52be --- /dev/null +++ b/platform/src/style.css @@ -0,0 +1,183 @@ +/* 全局设计系统变量 */ +:root { + /* 主色调 */ + --primary-color: #1890ff; + --primary-light: #40a9ff; + --primary-dark: #096dd9; + --primary-bg: #e6f7ff; + --primary-border: #b3d4fc; + + /* 渐变色 */ + --gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --gradient-primary-hover: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); + --gradient-header: linear-gradient(90deg, #fafdff 60%, #e6f0ff 100%); + + /* 背景色 */ + --bg-main: #f5f7fa; + --bg-card: #fff; + --bg-card-hover: rgba(255, 255, 255, 0.95); + --bg-sidebar: #fff; + --bg-header: #fafdff; + --bg-overlay: rgba(0, 0, 0, 0.5); + + /* 边框色 */ + --border-light: #e6f0ff; + --border-medium: #d9d9d9; + --border-dark: #bfbfbf; + --border-card: rgba(255, 255, 255, 0.2); + + /* 文字色 */ + --text-primary: #1a1a1a; + --text-secondary: #666; + --text-tertiary: #999; + --text-disabled: #bfbfbf; + --text-white: #fff; + + /* 状态色 */ + --success-color: #52c41a; + --success-bg: #f6ffed; + --warning-color: #faad14; + --warning-bg: #fffbe6; + --error-color: #ff4d4f; + --error-bg: #fff2f0; + --info-color: #1890ff; + --info-bg: #e6f7ff; + + /* 阴影 */ + --shadow-sm: 0 2px 8px rgba(24, 144, 255, 0.04); + --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.12); + --shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.2); + + /* 圆角 */ + --radius-sm: 6px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-2xl: 20px; + + /* 间距 */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 12px; + --spacing-lg: 16px; + --spacing-xl: 20px; + --spacing-2xl: 24px; + --spacing-3xl: 32px; + + /* 字体 */ + --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-base: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-2xl: 1.5rem; + --font-size-3xl: 1.875rem; + + /* 动画 */ + --transition-fast: 0.2s ease; + --transition-base: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 0.5s ease; + + /* 布局 */ + --header-height: 60px; + --sidebar-width: 210px; + --sidebar-collapsed-width: 60px; + --z-index-dropdown: 1000; + --z-index-modal: 1000; + --z-index-sidebar: 101; + --z-index-header: 100; +} + +/* 全局重置 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100vh; + width: 100vw; + overflow: hidden; + font-family: var(--font-family); + background: var(--bg-main); + color: var(--text-primary); +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; + background: var(--bg-main); +} + +::-webkit-scrollbar-thumb { + background: var(--primary-border); + border-radius: var(--radius-sm); + transition: background var(--transition-fast); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--primary-color); +} + +/* 通用工具类 */ +.text-primary { color: var(--text-primary); } +.text-secondary { color: var(--text-secondary); } +.text-tertiary { color: var(--text-tertiary); } +.text-white { color: var(--text-white); } + +.bg-primary { background: var(--primary-color); } +.bg-card { background: var(--bg-card); } +.bg-main { background: var(--bg-main); } + +.border-light { border-color: var(--border-light); } +.border-medium { border-color: var(--border-medium); } + +.shadow-sm { box-shadow: var(--shadow-sm); } +.shadow-md { box-shadow: var(--shadow-md); } +.shadow-lg { box-shadow: var(--shadow-lg); } + +.rounded-sm { border-radius: var(--radius-sm); } +.rounded-md { border-radius: var(--radius-md); } +.rounded-lg { border-radius: var(--radius-lg); } +.rounded-xl { border-radius: var(--radius-xl); } + +.transition { transition: var(--transition-base); } +.transition-fast { transition: var(--transition-fast); } +.transition-slow { transition: var(--transition-slow); } + +/* 响应式断点 */ +@media (max-width: 1400px) { + :root { + --spacing-3xl: 24px; + --spacing-2xl: 20px; + } +} + +@media (max-width: 1200px) { + :root { + --spacing-xl: 16px; + --spacing-lg: 12px; + } +} + +@media (max-width: 768px) { + :root { + --header-height: 48px; + --sidebar-width: 56px; + --sidebar-collapsed-width: 56px; + --spacing-2xl: 16px; + --spacing-xl: 12px; + } +} + +@media (max-width: 480px) { + :root { + --spacing-lg: 8px; + --spacing-md: 6px; + --font-size-lg: 1rem; + --font-size-xl: 1.125rem; + } +} \ No newline at end of file diff --git a/platform/src/utlis/event-bus.js b/platform/src/utlis/event-bus.js new file mode 100644 index 0000000..b40351d --- /dev/null +++ b/platform/src/utlis/event-bus.js @@ -0,0 +1,37 @@ +// utils/event-bus.js - 改进事件总线以管理全局状态 +class GlobalState { + constructor() { + this.threshold = 35; // 默认阈值 + this.listeners = new Map(); + } + + // 注册事件监听 + on(event, callback) { + if (!this.listeners.has(event)) { + this.listeners.set(event, []); + } + this.listeners.get(event).push(callback); + } + + // 触发事件 + emit(event, data) { + if (this.listeners.has(event)) { + this.listeners.get(event).forEach(callback => callback(data)); + } + } + + // 设置全局阈值 + setThreshold(threshold) { + this.threshold = threshold; + this.emit('thresholdUpdated', threshold); + } + + // 获取全局阈值 + getThreshold() { + return this.threshold; + } +} + +// 创建全局状态实例 +const globalState = new GlobalState(); +export default globalState; \ No newline at end of file diff --git a/platform/src/views/HomeView.vue b/platform/src/views/HomeView.vue new file mode 100644 index 0000000..5033a90 --- /dev/null +++ b/platform/src/views/HomeView.vue @@ -0,0 +1,52 @@ + + + + + \ No newline at end of file diff --git a/platform/src/views/LoginView.vue b/platform/src/views/LoginView.vue new file mode 100644 index 0000000..c68e529 --- /dev/null +++ b/platform/src/views/LoginView.vue @@ -0,0 +1,223 @@ + + + + + \ No newline at end of file diff --git a/platform/src/views/RegisterView.vue b/platform/src/views/RegisterView.vue new file mode 100644 index 0000000..e35c6a4 --- /dev/null +++ b/platform/src/views/RegisterView.vue @@ -0,0 +1,293 @@ + + + + + \ No newline at end of file diff --git a/platform/src/vite-env.d.ts b/platform/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/platform/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/platform/tsconfig.app.json b/platform/tsconfig.app.json new file mode 100644 index 0000000..1f3a133 --- /dev/null +++ b/platform/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "lib": ["ES2015", "DOM"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/platform/tsconfig.json b/platform/tsconfig.json new file mode 100644 index 0000000..3eaa6e3 --- /dev/null +++ b/platform/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "lib": ["ES2015", "DOM"] + }, + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue" + ] +} diff --git a/platform/tsconfig.node.json b/platform/tsconfig.node.json new file mode 100644 index 0000000..9728af2 --- /dev/null +++ b/platform/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/platform/vite.config.ts b/platform/vite.config.ts new file mode 100644 index 0000000..bbcf80c --- /dev/null +++ b/platform/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +})