Quellcode durchsuchen

Merge branch 'ding' of Ding/AcidificationModel into master

Ding vor 2 Monaten
Ursprung
Commit
19ae0dc981
100 geänderte Dateien mit 3047 neuen und 1102 gelöschten Zeilen
  1. 1 2
      api/.gitignore
  2. 29 2
      api/app/__init__.py
  3. 1 1
      api/app/celery_app.py
  4. 0 116
      api/app/data_cleaner.py
  5. 1 6
      api/app/database_models.py
  6. 780 0
      api/app/frontend.py
  7. 16 138
      api/app/model.py
  8. 66 833
      api/app/routes.py
  9. 1 1
      api/app/tasks.py
  10. BIN
      api/model_optimize/data/Acidity_reduce_new - 1.xlsx
  11. BIN
      api/model_optimize/data/Acidity_reduce_new - 2.xlsx
  12. BIN
      api/model_optimize/data/Acidity_reduce_new - 3.xlsx
  13. BIN
      api/pkl/rf_model_0104_0932.pkl
  14. BIN
      api/pkl/rf_model_0104_1145.pkl
  15. BIN
      api/pkl/rf_model_0104_1149.pkl
  16. BIN
      api/pkl/rf_model_0104_1237.pkl
  17. BIN
      api/pkl/rf_model_0104_1259.pkl
  18. BIN
      api/pkl/rf_model_0104_1415.pkl
  19. BIN
      api/pkl/rf_model_0104_1418.pkl
  20. BIN
      api/pkl/rf_model_0104_1420.pkl
  21. BIN
      api/pkl/rf_model_0107_0123.pkl
  22. BIN
      api/pkl/rf_model_0111_1755.pkl
  23. BIN
      api/pkl/rf_model_0308_1550.pkl
  24. BIN
      api/pkl/rf_model_0308_1619.pkl
  25. BIN
      api/pkl/rf_model_0308_1632.pkl
  26. 11 3
      api/run.py
  27. BIN
      api/software_intro.db
  28. BIN
      api/uploads/datasets/dataset_33.xlsx
  29. BIN
      api/uploads/datasets/dataset_6.xlsx
  30. BIN
      api/uploads/datasets/dataset_7.xlsx
  31. BIN
      api/uploads/datasets/dataset_8.xlsx
  32. BIN
      assets/taddar/W020241210635035429857_ORIGIN.jpg
  33. BIN
      assets/taddar/首页.png
  34. 48 0
      custom-tab-bar/index.js
  35. 6 0
      custom-tab-bar/index.json
  36. 7 0
      custom-tab-bar/index.wxml
  37. 38 0
      custom-tab-bar/index.wxss
  38. 66 0
      pages/Overview/Overview.js
  39. 6 0
      pages/Overview/Overview.json
  40. 3 0
      pages/Overview/Overview.wxml
  41. 21 0
      pages/Overview/Overview.wxss
  42. 146 0
      pages/Regular/Regular.js
  43. 6 0
      pages/Regular/Regular.json
  44. 6 0
      pages/Regular/Regular.wxml
  45. 42 0
      pages/Regular/Regular.wxss
  46. 66 0
      pages/ResearchFindings/ResearchFindings.js
  47. 6 0
      pages/ResearchFindings/ResearchFindings.json
  48. 8 0
      pages/ResearchFindings/ResearchFindings.wxml
  49. 27 0
      pages/ResearchFindings/ResearchFindings.wxss
  50. 21 0
      pages/RoleSelectionPage/RoleSelectionPage.js
  51. 6 0
      pages/RoleSelectionPage/RoleSelectionPage.json
  52. 7 0
      pages/RoleSelectionPage/RoleSelectionPage.wxml
  53. 27 0
      pages/RoleSelectionPage/RoleSelectionPage.wxss
  54. 66 0
      pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.js
  55. 6 0
      pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.json
  56. 2 0
      pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxml
  57. 1 0
      pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxss
  58. 66 0
      pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js
  59. 6 0
      pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.json
  60. 2 0
      pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml
  61. 1 0
      pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxss
  62. 26 0
      pages/Soil Acidification/Soil Acidification.js
  63. 13 0
      pages/Soil Acidification/Soil Acidification.json
  64. 21 0
      pages/Soil Acidification/Soil Acidification.wxml
  65. 46 0
      pages/Soil Acidification/Soil Acidification.wxss
  66. 21 0
      pages/Soil Deacidification/Soil Deacidification.js
  67. 6 0
      pages/Soil Deacidification/Soil Deacidification.json
  68. 20 0
      pages/Soil Deacidification/Soil Deacidification.wxml
  69. 45 0
      pages/Soil Deacidification/Soil Deacidification.wxss
  70. 66 0
      pages/SoilPro/SoilPro.js
  71. 6 0
      pages/SoilPro/SoilPro.json
  72. 49 0
      pages/SoilPro/SoilPro.wxml
  73. 49 0
      pages/SoilPro/SoilPro.wxss
  74. 66 0
      pages/Unit Team Profile/Unit Team Profile.js
  75. 6 0
      pages/Unit Team Profile/Unit Team Profile.json
  76. 14 0
      pages/Unit Team Profile/Unit Team Profile.wxml
  77. 48 0
      pages/Unit Team Profile/Unit Team Profile.wxss
  78. 66 0
      shoping/Data Visualization/Data Visualization.js
  79. 3 0
      shoping/Data Visualization/Data Visualization.json
  80. 2 0
      shoping/Data Visualization/Data Visualization.wxml
  81. 1 0
      shoping/Data Visualization/Data Visualization.wxss
  82. 101 0
      shoping/Staffl/Staff.js
  83. 8 0
      shoping/Staffl/Staff.json
  84. 33 0
      shoping/Staffl/Staff.wxml
  85. 108 0
      shoping/Staffl/Staff.wxss
  86. 66 0
      shoping/Visualizatio/Visualizatio.js
  87. 5 0
      shoping/Visualizatio/Visualizatio.json
  88. 3 0
      shoping/Visualizatio/Visualizatio.wxml
  89. 1 0
      shoping/Visualizatio/Visualizatio.wxss
  90. 66 0
      shoping/Visualization/Visualization.js
  91. 5 0
      shoping/Visualization/Visualization.json
  92. 3 0
      shoping/Visualization/Visualization.wxml
  93. 1 0
      shoping/Visualization/Visualization.wxss
  94. 176 0
      shoping/admin/admin.js
  95. 6 0
      shoping/admin/admin.json
  96. 37 0
      shoping/admin/admin.wxml
  97. 177 0
      shoping/admin/admin.wxss
  98. 41 0
      shoping/threshold/threshold.js
  99. 5 0
      shoping/threshold/threshold.json
  100. 12 0
      shoping/threshold/threshold.wxml

+ 1 - 2
api/.gitignore

@@ -1,5 +1,4 @@
 app/__pycache__
 .idea
 model_optimize/__pycache__
-__pycache__
-.vscode
+__pycache__

+ 29 - 2
api/app/__init__.py

@@ -1,6 +1,9 @@
+import os
+import sqlite3
+
 from flask import Flask
 from flask_cors import CORS
-from . import config
+from . import config, frontend
 from flask_sqlalchemy import SQLAlchemy
 from flask_migrate import Migrate
 import logging
@@ -8,20 +11,44 @@ import logging
 # 创建 SQLAlchemy 全局实例
 db = SQLAlchemy()
 
+
 # 创建并配置 Flask 应用
 def create_app():
     app = Flask(__name__)
     CORS(app)
-    # 进行初始配置,加载配置文件等
     app.config.from_object(config.Config)
     app.logger.setLevel(logging.DEBUG)
+
+    # 图片上传目录
+    UPLOAD_FOLDER = 'uploads'
+    if not os.path.exists(UPLOAD_FOLDER):
+        os.makedirs(UPLOAD_FOLDER)
+    app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
     # 初始化 SQLAlchemy
     db.init_app(app)
 
     # 初始化 Flask-Migrate
     migrate = Migrate(app, db)
 
+    # 初始化数据库表
+    def init_db():
+        conn = sqlite3.connect('software_intro.db')
+        cursor = conn.cursor()
+        cursor.execute('''
+            CREATE TABLE IF NOT EXISTS software_intro (
+                id INTEGER PRIMARY KEY AUTOINCREMENT,
+                title TEXT NOT NULL,
+                intro TEXT
+            )
+        ''')
+        conn.commit()
+        conn.close()
+
+    # 调用初始化数据库
+    init_db()
+
     # 导入路由
     from . import routes
     app.register_blueprint(routes.bp)
+    app.register_blueprint(frontend.bp)
     return app

+ 1 - 1
api/app/celery_app.py

@@ -2,7 +2,7 @@
 Celery配置文件
 """
 from celery import Celery
-from app.config import Config
+from api.app.config import Config
 
 # 创建Celery实例
 celery = Celery('app',

+ 0 - 116
api/app/data_cleaner.py

@@ -1,116 +0,0 @@
-"""
-数据清理模块,提供各种数据清理和预处理功能
-"""
-import pandas as pd
-import numpy as np
-from sklearn.preprocessing import StandardScaler
-import logging
-
-logger = logging.getLogger(__name__)
-
-def remove_duplicates(df):
-    """
-    移除数据框中的重复行
-    
-    Args:
-        df: 输入数据框
-        
-    Returns:
-        tuple: (清理后的数据框, 移除的重复项数量)
-    """
-    original_count = len(df)
-    df_clean = df.drop_duplicates()
-    duplicates_removed = original_count - len(df_clean)
-    logger.info(f"移除了 {duplicates_removed} 个重复样本")
-    return df_clean, duplicates_removed
-
-def remove_outliers(df, method='iqr', threshold=1.5):
-    """
-    使用指定方法检测和移除异常值
-    
-    Args:
-        df: 输入数据框
-        method: 异常值检测方法 ('iqr', 'zscore')
-        threshold: 异常值判定阈值
-        
-    Returns:
-        tuple: (清理后的数据框, 移除的异常值数量)
-    """
-    original_count = len(df)
-    
-    if method == 'iqr':
-        Q1 = df.quantile(0.25)
-        Q3 = df.quantile(0.75)
-        IQR = Q3 - Q1
-        outlier_mask = ~((df < (Q1 - threshold * IQR)) | (df > (Q3 + threshold * IQR))).any(axis=1)
-        df_clean = df[outlier_mask]
-    
-    elif method == 'zscore':
-        from scipy import stats
-        z_scores = stats.zscore(df)
-        outlier_mask = ~(np.abs(z_scores) > threshold).any(axis=1)
-        df_clean = df[outlier_mask]
-    
-    outliers_removed = original_count - len(df_clean)
-    logger.info(f"使用 {method} 方法移除了 {outliers_removed} 个异常值")
-    return df_clean, outliers_removed
-
-def clean_dataset(df, target_column=None, remove_dups=False, handle_outliers=False, 
-                 outlier_method='iqr', outlier_threshold=1.5, normalize=False):
-    """
-    综合数据清理函数
-    
-    Args:
-        df: 输入数据框
-        target_column: 目标变量列名或索引
-        remove_dups: 是否移除重复项
-        handle_outliers: 是否处理异常值
-        outlier_method: 异常值检测方法
-        outlier_threshold: 异常值判定阈值
-        normalize: 是否标准化特征
-        
-    Returns:
-        tuple: (特征数据框, 目标变量, 清理统计信息)
-    """
-    stats = {'original_count': len(df)}
-    
-    # 分离特征和目标变量
-    if target_column is not None:
-        if isinstance(target_column, str):
-            X = df.drop(columns=[target_column])
-            y = df[target_column]
-        else:
-            X = df.drop(df.columns[target_column], axis=1)
-            y = df.iloc[:, target_column]
-    else:
-        X = df
-        y = None
-    
-    # 移除重复项
-    if remove_dups:
-        if y is not None:
-            combined = pd.concat([X, y], axis=1)
-            combined, stats['duplicates_removed'] = remove_duplicates(combined)
-            X = combined.iloc[:, :-1] if isinstance(target_column, int) else combined.drop(columns=[target_column])
-            y = combined.iloc[:, -1] if isinstance(target_column, int) else combined[target_column]
-        else:
-            X, stats['duplicates_removed'] = remove_duplicates(X)
-    
-    # 处理异常值
-    if handle_outliers:
-        if y is not None:
-            combined = pd.concat([X, y], axis=1)
-            combined, stats['outliers_removed'] = remove_outliers(combined, method=outlier_method, threshold=outlier_threshold)
-            X = combined.iloc[:, :-1] if isinstance(target_column, int) else combined.drop(columns=[target_column])
-            y = combined.iloc[:, -1] if isinstance(target_column, int) else combined[target_column]
-        else:
-            X, stats['outliers_removed'] = remove_outliers(X, method=outlier_method, threshold=outlier_threshold)
-    
-    # 标准化特征
-    if normalize:
-        scaler = StandardScaler()
-        X = pd.DataFrame(scaler.fit_transform(X), columns=X.columns, index=X.index)
-        stats['normalized'] = True
-    
-    stats['final_count'] = len(X)
-    return X, y, stats 

+ 1 - 6
api/app/database_models.py

@@ -31,12 +31,7 @@ class Models(Base):
     DatasetID: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('Datasets.Dataset_ID'))
     ModelFilePath: Mapped[Optional[str]] = mapped_column(Text)
     Data_type: Mapped[Optional[str]] = mapped_column(Text)
-    Performance_score: Mapped[Optional[float]] = mapped_column(Float)
-
-    # 新增评分指标字段
-    MAE: Mapped[Optional[float]] = mapped_column(Float)
-    RMSE: Mapped[Optional[float]] = mapped_column(Float)
-    CV_score: Mapped[Optional[float]] = mapped_column(Float)
+    Performance_score: Mapped[Optional[float]] = mapped_column(Text)
 
     ModelParameters: Mapped[List['ModelParameters']] = relationship('ModelParameters', back_populates='Models_')
 

+ 780 - 0
api/app/frontend.py

@@ -0,0 +1,780 @@
+import logging
+import os
+import sqlite3
+import uuid
+from io import BytesIO
+
+import pandas as pd
+from flask import Blueprint, request, jsonify, send_from_directory, current_app, send_file, session
+from sqlalchemy import text, select, MetaData, Table
+from sqlalchemy.orm import sessionmaker
+from werkzeug.security import check_password_hash, generate_password_hash
+from werkzeug.utils import secure_filename
+
+from .database_models import Models
+
+# 配置日志
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger(__name__)
+# 创建蓝图 (Blueprint),用于分离路由
+bp = Blueprint('frontend', __name__)
+
+
+# 封装数据库连接函数
+def get_db_connection():
+    return sqlite3.connect('software_intro.db')
+
+
+# 密码加密
+def hash_password(password):
+    return generate_password_hash(password)
+
+
+def get_db():
+    """ 获取数据库连接 """
+    return sqlite3.connect(current_app.config['DATABASE'])
+
+
+# 定义添加数据库记录的 API 接口
+@bp.route('/add_item', methods=['POST'])
+def add_item():
+    """
+    接收 JSON 格式的请求体,包含表名和要插入的数据。
+    尝试将数据插入到指定的表中,并进行字段查重。
+    :return:
+    """
+    try:
+        # 确保请求体是 JSON 格式
+        data = request.get_json()
+        if not data:
+            raise ValueError("No JSON data provided")
+
+        table_name = data.get('table')
+        item_data = data.get('item')
+
+        if not table_name or not item_data:
+            return jsonify({'error': 'Missing table name or item data'}), 400
+
+        # 定义各个表的字段查重规则
+        duplicate_check_rules = {
+            'users': ['email', 'username'],
+            'products': ['product_code'],
+            'current_reduce': ['Q_over_b', 'pH', 'OM', 'CL', 'H', 'Al'],
+            'current_reflux': ['OM', 'CL', 'CEC', 'H_plus', 'N', 'Al3_plus', 'Delta_pH'],
+            # 其他表和规则
+        }
+
+        # 获取该表的查重字段
+        duplicate_columns = duplicate_check_rules.get(table_name)
+
+        if not duplicate_columns:
+            return jsonify({'error': 'No duplicate check rule for this table'}), 400
+
+        # 动态构建查询条件,逐一检查是否有重复数据
+        condition = ' AND '.join([f"{column} = :{column}" for column in duplicate_columns])
+        duplicate_query = f"SELECT 1 FROM {table_name} WHERE {condition} LIMIT 1"
+
+        result = db.session.execute(text(duplicate_query), item_data).fetchone()
+
+        if result:
+            return jsonify({'error': '重复数据,已有相同的数据项存在。'}), 409
+
+        # 动态构建 SQL 语句,进行插入操作
+        columns = ', '.join(item_data.keys())
+        placeholders = ', '.join([f":{key}" for key in item_data.keys()])
+        sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
+
+        # 直接执行插入操作,无需显式的事务管理
+        db.session.execute(text(sql), item_data)
+
+        # 提交事务
+        db.session.commit()
+
+        # 返回成功响应
+        return jsonify({'success': True, 'message': 'Item added successfully'}), 201
+
+    except ValueError as e:
+        return jsonify({'error': str(e)}), 400
+    except KeyError as e:
+        return jsonify({'error': f'Missing data field: {e}'}), 400
+    except sqlite3.IntegrityError as e:
+        return jsonify({'error': '数据库完整性错误', 'details': str(e)}), 409
+    except sqlite3.Error as e:
+        return jsonify({'error': '数据库错误', 'details': str(e)}), 500
+
+
+@bp.route('/delete_item', methods=['POST'])
+def delete_item():
+    """
+    删除数据库记录的 API 接口
+    """
+    data = request.get_json()
+    table_name = data.get('table')
+    condition = data.get('condition')
+
+    # 检查表名和条件是否提供
+    if not table_name or not condition:
+        return jsonify({
+            "success": False,
+            "message": "缺少表名或条件参数"
+        }), 400
+
+    # 尝试从条件字符串中解析键和值
+    try:
+        key, value = condition.split('=')
+        key = key.strip()  # 去除多余的空格
+        value = value.strip().strip("'\"")  # 去除多余的空格和引号
+    except ValueError:
+        return jsonify({
+            "success": False,
+            "message": "条件格式错误,应为 'key=value'"
+        }), 400
+
+    # 准备 SQL 删除语句
+    sql = f"DELETE FROM {table_name} WHERE {key} = :value"
+
+    try:
+        # 使用 SQLAlchemy 执行删除
+        with db.session.begin():
+            result = db.session.execute(text(sql), {"value": value})
+
+        # 检查是否有记录被删除
+        if result.rowcount == 0:
+            return jsonify({
+                "success": False,
+                "message": "未找到符合条件的记录"
+            }), 404
+
+        return jsonify({
+            "success": True,
+            "message": "记录删除成功"
+        }), 200
+
+    except Exception as e:
+        return jsonify({
+            "success": False,
+            "message": f"删除失败: {e}"
+        }), 500
+
+
+# 定义修改数据库记录的 API 接口
+@bp.route('/update_item', methods=['PUT'])
+def update_record():
+    """
+    接收 JSON 格式的请求体,包含表名和更新的数据。
+    尝试更新指定的记录。
+    """
+    data = request.get_json()
+
+    # 检查必要的数据是否提供
+    if not data or 'table' not in data or 'item' not in data:
+        return jsonify({
+            "success": False,
+            "message": "请求数据不完整"
+        }), 400
+
+    table_name = data['table']
+    item = data['item']
+
+    # 假设 item 的第一个键是 ID
+    id_key = next(iter(item.keys()))  # 获取第一个键
+    record_id = item.get(id_key)
+
+    if not record_id:
+        return jsonify({
+            "success": False,
+            "message": "缺少记录 ID"
+        }), 400
+
+    # 获取更新的字段和值
+    updates = {key: value for key, value in item.items() if key != id_key}
+
+    if not updates:
+        return jsonify({
+            "success": False,
+            "message": "没有提供需要更新的字段"
+        }), 400
+
+    # 动态构建 SQL
+    set_clause = ', '.join([f"{key} = :{key}" for key in updates.keys()])
+    sql = f"UPDATE {table_name} SET {set_clause} WHERE {id_key} = :id_value"
+
+    # 添加 ID 到参数
+    updates['id_value'] = record_id
+
+    try:
+        # 使用 SQLAlchemy 执行更新
+        with db.session.begin():
+            result = db.session.execute(text(sql), updates)
+
+        # 检查是否有更新的记录
+        if result.rowcount == 0:
+            return jsonify({
+                "success": False,
+                "message": "未找到要更新的记录"
+            }), 404
+
+        return jsonify({
+            "success": True,
+            "message": "数据更新成功"
+        }), 200
+
+    except Exception as e:
+        # 捕获所有异常并返回
+        return jsonify({
+            "success": False,
+            "message": f"更新失败: {str(e)}"
+        }), 500
+
+
+# 定义查询数据库记录的 API 接口
+@bp.route('/search/record', methods=['GET'])
+def sql_search():
+    """
+    接收 JSON 格式的请求体,包含表名和要查询的 ID。
+    尝试查询指定 ID 的记录并返回结果。
+    :return:
+    """
+    try:
+        data = request.get_json()
+
+        # 表名
+        sql_table = data['table']
+
+        # 要搜索的 ID
+        Id = data['id']
+
+        # 连接到数据库
+        cur = db.cursor()
+
+        # 构造查询语句
+        sql = f"SELECT * FROM {sql_table} WHERE id = ?"
+
+        # 执行查询
+        cur.execute(sql, (Id,))
+
+        # 获取查询结果
+        rows = cur.fetchall()
+        column_names = [desc[0] for desc in cur.description]
+
+        # 检查是否有结果
+        if not rows:
+            return jsonify({'error': '未查找到对应数据。'}), 400
+
+        # 构造响应数据
+        results = []
+        for row in rows:
+            result = {column_names[i]: row[i] for i in range(len(row))}
+            results.append(result)
+
+        # 关闭游标和数据库连接
+        cur.close()
+        db.close()
+
+        # 返回 JSON 响应
+        return jsonify(results), 200
+
+    except sqlite3.Error as e:
+        # 如果发生数据库错误,返回错误信息
+        return jsonify({'error': str(e)}), 400
+    except KeyError as e:
+        # 如果请求数据中缺少必要的键,返回错误信息
+        return jsonify({'error': f'缺少必要的数据字段: {e}'}), 400
+
+
+# 定义提供数据库列表,用于展示表格的 API 接口
+@bp.route('/table', methods=['POST'])
+def get_table():
+    data = request.get_json()
+    table_name = data.get('table')
+    if not table_name:
+        return jsonify({'error': '需要表名'}), 400
+
+    try:
+        # 创建 sessionmaker 实例
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
+
+        # 动态获取表的元数据
+        metadata = MetaData()
+        table = Table(table_name, metadata, autoload_with=db.engine)
+
+        # 从数据库中查询所有记录
+        query = select(table)
+        result = session.execute(query).fetchall()
+
+        # 将结果转换为列表字典形式
+        rows = [dict(zip([column.name for column in table.columns], row)) for row in result]
+
+        # 获取列名
+        headers = [column.name for column in table.columns]
+
+        return jsonify(rows=rows, headers=headers), 200
+
+    except Exception as e:
+        return jsonify({'error': str(e)}), 400
+    finally:
+        # 关闭 session
+        session.close()
+
+
+# 更新用户信息接口
+@bp.route('/update_user', methods=['POST'])
+def update_user():
+    # 获取前端传来的数据
+    data = request.get_json()
+
+    # 打印收到的请求数据
+    current_app.logger.info(f"Received data: {data}")
+
+    user_id = data.get('userId')  # 用户ID
+    name = data.get('name')  # 用户名
+    old_password = data.get('oldPassword')  # 旧密码
+    new_password = data.get('newPassword')  # 新密码
+
+    logger.info(f"Update request received: user_id={user_id}, name={name}")
+
+    # 校验传入的用户名和密码是否为空
+    if not name or not old_password:
+        logger.warning("用户名和旧密码不能为空")
+        return jsonify({"success": False, "message": "用户名和旧密码不能为空"}), 400
+
+    # 新密码和旧密码不能相同
+    if new_password and old_password == new_password:
+        logger.warning(f"新密码与旧密码相同:{name}")
+        return jsonify({"success": False, "message": "新密码与旧密码不能相同"}), 400
+
+    try:
+        # 查询数据库验证用户ID
+        query = "SELECT * FROM users WHERE id = :user_id"
+        conn = get_db()
+        user = conn.execute(query, {"user_id": user_id}).fetchone()
+
+        if not user:
+            logger.warning(f"用户ID '{user_id}' 不存在")
+            return jsonify({"success": False, "message": "用户不存在"}), 400
+
+        # 获取数据库中存储的密码(假设密码是哈希存储的)
+        stored_password = user[2]  # 假设密码存储在数据库的第三列
+
+        # 校验旧密码是否正确
+        if not check_password_hash(stored_password, old_password):
+            logger.warning(f"旧密码错误:{name}")
+            return jsonify({"success": False, "message": "旧密码错误"}), 400
+
+        # 如果新密码非空,则更新新密码
+        if new_password:
+            hashed_new_password = hash_password(new_password)
+            update_query = "UPDATE users SET password = :new_password WHERE id = :user_id"
+            conn.execute(update_query, {"new_password": hashed_new_password, "user_id": user_id})
+            conn.commit()
+            logger.info(f"User ID '{user_id}' password updated successfully.")
+
+        # 如果用户名发生更改,则更新用户名
+        if name != user[1]:
+            update_name_query = "UPDATE users SET name = :new_name WHERE id = :user_id"
+            conn.execute(update_name_query, {"new_name": name, "user_id": user_id})
+            conn.commit()
+            logger.info(f"User ID '{user_id}' name updated to '{name}' successfully.")
+
+        return jsonify({"success": True, "message": "用户信息更新成功"})
+
+    except Exception as e:
+        # 记录错误日志并返回错误信息
+        logger.error(f"Error updating user: {e}", exc_info=True)
+        return jsonify({"success": False, "message": "更新失败"}), 500
+
+
+# 注册用户
+@bp.route('/register', methods=['POST'])
+def register_user():
+    # 获取前端传来的数据
+    data = request.get_json()
+    name = data.get('name')  # 用户名
+    password = data.get('password')  # 密码
+
+    logger.info(f"Register request received: name={name}")
+
+    # 检查用户名和密码是否为空
+    if not name or not password:
+        logger.warning("用户名和密码不能为空")
+        return jsonify({"success": False, "message": "用户名和密码不能为空"}), 400
+
+    # 动态获取数据库表的列名
+    columns = get_column_names('users')
+    logger.info(f"Database columns for 'users' table: {columns}")
+
+    # 检查前端传来的数据是否包含数据库表中所有的必填字段
+    for column in ['name', 'password']:
+        if column not in columns:
+            logger.error(f"缺少必填字段:{column}")
+            return jsonify({"success": False, "message": f"缺少必填字段:{column}"}), 400
+
+    # 对密码进行哈希处理
+    hashed_password = hash_password(password)
+    logger.info(f"Password hashed for user: {name}")
+
+    # 插入到数据库
+    try:
+        # 检查用户是否已经存在
+        query = "SELECT * FROM users WHERE name = :name"
+        conn = get_db()
+        user = conn.execute(query, {"name": name}).fetchone()
+
+        if user:
+            logger.warning(f"用户名 '{name}' 已存在")
+            return jsonify({"success": False, "message": "用户名已存在"}), 400
+
+        # 向数据库插入数据
+        query = "INSERT INTO users (name, password) VALUES (:name, :password)"
+        conn.execute(query, {"name": name, "password": hashed_password})
+        conn.commit()
+
+        logger.info(f"User '{name}' registered successfully.")
+        return jsonify({"success": True, "message": "注册成功"})
+
+    except Exception as e:
+        # 记录错误日志并返回错误信息
+        logger.error(f"Error registering user: {e}", exc_info=True)
+        return jsonify({"success": False, "message": "注册失败"}), 500
+
+
+def get_column_names(table_name):
+    """
+    动态获取数据库表的列名。
+    """
+    try:
+        conn = get_db()
+        query = f"PRAGMA table_info({table_name});"
+        result = conn.execute(query).fetchall()
+        conn.close()
+        return [row[1] for row in result]  # 第二列是列名
+    except Exception as e:
+        logger.error(f"Error getting column names for table {table_name}: {e}", exc_info=True)
+        return []
+
+
+# 导出数据
+@bp.route('/export_data', methods=['GET'])
+def export_data():
+    table_name = request.args.get('table')
+    file_format = request.args.get('format', 'excel').lower()
+
+    if not table_name:
+        return jsonify({'error': '缺少表名参数'}), 400
+    if not table_name.isidentifier():
+        return jsonify({'error': '无效的表名'}), 400
+
+    try:
+        conn = get_db()
+        query = "SELECT name FROM sqlite_master WHERE type='table' AND name=?;"
+        table_exists = conn.execute(query, (table_name,)).fetchone()
+        if not table_exists:
+            return jsonify({'error': f"表 {table_name} 不存在"}), 404
+
+        query = f"SELECT * FROM {table_name};"
+        df = pd.read_sql(query, conn)
+
+        output = BytesIO()
+        if file_format == 'csv':
+            df.to_csv(output, index=False, encoding='utf-8')
+            output.seek(0)
+            return send_file(output, as_attachment=True, download_name=f'{table_name}_data.csv', mimetype='text/csv')
+        elif file_format == 'excel':
+            df.to_excel(output, index=False, engine='openpyxl')
+            output.seek(0)
+            return send_file(output, as_attachment=True, download_name=f'{table_name}_data.xlsx',
+                             mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
+        else:
+            return jsonify({'error': '不支持的文件格式,仅支持 CSV 和 Excel'}), 400
+
+    except Exception as e:
+        logger.error(f"Error in export_data: {e}", exc_info=True)
+        return jsonify({'error': str(e)}), 500
+
+
+# 导入数据接口
+@bp.route('/import_data', methods=['POST'])
+def import_data():
+    logger.debug("Import data endpoint accessed.")
+    if 'file' not in request.files:
+        logger.error("No file in request.")
+        return jsonify({'success': False, 'message': '文件缺失'}), 400
+
+    file = request.files['file']
+    table_name = request.form.get('table')
+
+    if not table_name:
+        logger.error("Missing table name parameter.")
+        return jsonify({'success': False, 'message': '缺少表名参数'}), 400
+
+    if file.filename == '':
+        logger.error("No file selected.")
+        return jsonify({'success': False, 'message': '未选择文件'}), 400
+
+    try:
+        # 保存文件到临时路径
+        temp_path = os.path.join(current_app.config['UPLOAD_FOLDER'], secure_filename(file.filename))
+        file.save(temp_path)
+        logger.debug(f"File saved to temporary path: {temp_path}")
+
+        # 根据文件类型读取文件
+        if file.filename.endswith('.xlsx'):
+            df = pd.read_excel(temp_path)
+        elif file.filename.endswith('.csv'):
+            df = pd.read_csv(temp_path)
+        else:
+            logger.error("Unsupported file format.")
+            return jsonify({'success': False, 'message': '仅支持 Excel 和 CSV 文件'}), 400
+
+        # 获取数据库列名
+        db_columns = get_column_names(table_name)
+        if 'id' in db_columns:
+            db_columns.remove('id')  # 假设 id 列是自增的,不需要处理
+
+        if not set(db_columns).issubset(set(df.columns)):
+            logger.error(f"File columns do not match database columns. File columns: {df.columns.tolist()}, Expected: {db_columns}")
+            return jsonify({'success': False, 'message': '文件列名与数据库表不匹配'}), 400
+
+        # 清洗数据并删除空值行
+        df_cleaned = df[db_columns].dropna()
+
+        # 统一数据类型,避免 int 和 float 合并问题
+        df_cleaned[db_columns] = df_cleaned[db_columns].apply(pd.to_numeric, errors='coerce')
+
+        # 获取现有的数据
+        conn = get_db()
+        with conn:
+            existing_data = pd.read_sql(f"SELECT * FROM {table_name}", conn)
+
+            # 查找重复数据
+            duplicates = df_cleaned.merge(existing_data, on=db_columns, how='inner')
+
+            # 如果有重复数据,删除它们
+            df_cleaned = df_cleaned[~df_cleaned.index.isin(duplicates.index)]
+            logger.warning(f"Duplicate data detected and removed: {duplicates}")
+
+            # 获取导入前后的数据量
+            total_data = len(df_cleaned) + len(duplicates)
+            new_data = len(df_cleaned)
+            duplicate_data = len(duplicates)
+
+            # 导入不重复的数据
+            df_cleaned.to_sql(table_name, conn, if_exists='append', index=False)
+            logger.debug(f"Imported {new_data} new records into the database.")
+
+        # 删除临时文件
+        os.remove(temp_path)
+        logger.debug(f"Temporary file removed: {temp_path}")
+
+        # 返回结果
+        return jsonify({
+            'success': True,
+            'message': '数据导入成功',
+            'total_data': total_data,
+            'new_data': new_data,
+            'duplicate_data': duplicate_data
+        }), 200
+
+    except Exception as e:
+        logger.error(f"Import failed: {e}", exc_info=True)
+        return jsonify({'success': False, 'message': f'导入失败: {str(e)}'}), 500
+
+
+# 模板下载接口
+@bp.route('/download_template', methods=['GET'])
+def download_template():
+    """
+    根据给定的表名,下载表的模板(如 CSV 或 Excel 格式)。
+    """
+    table_name = request.args.get('table')
+    if not table_name:
+        return jsonify({'error': '表名参数缺失'}), 400
+
+    columns = get_column_names(table_name)
+    if not columns:
+        return jsonify({'error': f"Table '{table_name}' not found or empty."}), 404
+
+    # 不包括 ID 列
+    if 'id' in columns:
+        columns.remove('id')
+
+    df = pd.DataFrame(columns=columns)
+
+    file_format = request.args.get('format', 'excel').lower()
+    try:
+        if file_format == 'csv':
+            output = BytesIO()
+            df.to_csv(output, index=False, encoding='utf-8')
+            output.seek(0)
+            return send_file(output, as_attachment=True, download_name=f'{table_name}_template.csv',
+                             mimetype='text/csv')
+        else:
+            output = BytesIO()
+            df.to_excel(output, index=False, engine='openpyxl')
+            output.seek(0)
+            return send_file(output, as_attachment=True, download_name=f'{table_name}_template.xlsx',
+                             mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
+    except Exception as e:
+        logger.error(f"Failed to generate template: {e}", exc_info=True)
+        return jsonify({'error': '生成模板文件失败'}), 500
+
+
+# 切换模型接口
+@bp.route('/switch-model', methods=['POST'])
+def switch_model():
+    session = None
+    try:
+        data = request.get_json()
+        model_id = data.get('model_id')
+        model_name = data.get('model_name')
+
+        # 创建 session
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
+
+        # 查找模型
+        model = session.query(Models).filter_by(ModelID=model_id).first()
+        if not model:
+            return jsonify({'error': 'Model not found'}), 404
+
+        # 更新模型状态(或其他切换逻辑)
+        # 假设此处是更新模型的某些字段来进行切换
+        model.status = 'active'  # 假设有一个字段记录模型状态
+        session.commit()
+
+        # 记录切换日志
+        logger.info(f'Model {model_name} (ID: {model_id}) switched successfully.')
+
+        return jsonify({'success': True, 'message': f'Model {model_name} switched successfully!'}), 200
+
+    except Exception as e:
+        logger.error('Failed to switch model:', exc_info=True)
+        return jsonify({'error': str(e)}), 400
+    finally:
+        if session:
+            session.close()
+
+
+# 修改了一下登录·接口
+@bp.route('/login', methods=['POST'])
+def login_user():
+    data = request.get_json()
+    logger.debug(f"Received login data: {data}")  # 增加调试日志
+    name = data.get('name')
+    password = data.get('password')
+
+    logger.info(f"Login request received for user: {name}")
+
+    if not isinstance(name, str) or not isinstance(password, str):
+        logger.warning("Username and password must be strings")
+        return jsonify({"success": False, "message": "用户名和密码必须为字符串"}), 400
+
+    if not name or not password:
+        logger.warning("Username and password cannot be empty")
+        return jsonify({"success": False, "message": "用户名和密码不能为空"}), 400
+
+    query = "SELECT id, name, password FROM users WHERE name = ?"
+
+    try:
+        with get_db() as conn:
+            user = conn.execute(query, (name,)).fetchone()
+
+            if not user:
+                logger.warning(f"User '{name}' does not exist")
+                return jsonify({"success": False, "message": "用户名不存在"}), 400
+
+            stored_password = user[2]  # 假设 'password' 是第三个字段
+            user_id = user[0]  # 假设 'id' 是第一个字段
+
+            if check_password_hash(stored_password, password):
+                session['name'] = name
+                logger.info(f"User '{name}' logged in successfully.")
+                return jsonify({
+                    "success": True,
+                    "message": "登录成功",
+                    "userId": user_id,
+                    "name": name
+                })
+            else:
+                logger.warning(f"Incorrect password for user '{name}'")
+                return jsonify({"success": False, "message": "用户名或密码错误"}), 400
+
+    except sqlite3.DatabaseError as db_err:
+        logger.error(f"Database error during login process: {db_err}", exc_info=True)
+        return jsonify({"success": False, "message": "数据库错误"}), 500
+    except Exception as e:
+        logger.error(f"Unexpected error during login process: {e}", exc_info=True)
+        return jsonify({"success": False, "message": "登录失败"}), 500
+
+
+# 添加退出登录状态接口
+@bp.route('/logout', methods=['GET', 'POST'])
+def logout_user():
+    try:
+        session.clear()
+        return jsonify({"msg": "退出成功"}), 200
+    except Exception as e:
+        logger.error(f"Error during logout process: {e}", exc_info=True)
+        return jsonify({"msg": "退出失败"}), 500
+
+
+# 获取软件介绍信息的路由
+@bp.route('/software-intro/<int:id>', methods=['GET'])
+def get_software_intro(id):
+    try:
+        conn = get_db_connection()
+        cursor = conn.cursor()
+        cursor.execute('SELECT title, intro FROM software_intro WHERE id = ?', (id,))
+        result = cursor.fetchone()
+        conn.close()
+
+        if result:
+            title, intro = result
+            return jsonify({
+                'title': title,
+                'intro': intro
+            })
+        return jsonify({}), 404
+    except sqlite3.Error as e:
+        print(f"数据库错误: {e}")
+        return jsonify({"error": f"数据库错误: {str(e)}"}), 500
+
+
+# 更新软件介绍信息的路由
+@bp.route('/software-intro/<int:id>', methods=['PUT'])
+def update_software_intro(id):
+    try:
+        data = request.get_json()
+        title = data.get('title')
+        intro = data.get('intro')
+
+        conn = get_db_connection()
+        cursor = conn.cursor()
+        cursor.execute('UPDATE software_intro SET title =?, intro =? WHERE id = ?', (title, intro, id))
+        conn.commit()
+        conn.close()
+
+        return jsonify({'message': '软件介绍更新成功'})
+    except sqlite3.Error as e:
+        print(f"数据库错误: {e}")
+        return jsonify({"error": f"数据库错误: {str(e)}"}), 500
+
+
+# 处理图片上传的路由
+@bp.route('/upload-image', methods=['POST'])
+def upload_image():
+    file = request.files['image']
+    if file:
+        filename = str(uuid.uuid4()) + '.' + file.filename.rsplit('.', 1)[1].lower()
+        file.save(os.path.join(os.getcwd(), 'uploads', filename))
+        imageUrl = f'http://127.0.0.1:5000/uploads/{filename}'
+        return jsonify({'imageUrl': imageUrl})
+    return jsonify({'error': '未找到图片文件'}), 400
+
+
+# 配置静态资源服务
+@bp.route('/uploads/<path:filename>')
+def serve_image(filename):
+    uploads_folder = os.path.join(os.getcwd(), 'uploads')
+    return send_from_directory(uploads_folder, filename)

+ 16 - 138
api/app/model.py

@@ -4,16 +4,13 @@ import pickle
 import pandas as pd
 from flask_sqlalchemy.session import Session
 from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
-from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
+from sklearn.metrics import r2_score
 from sklearn.model_selection import train_test_split, cross_val_score
 from sqlalchemy import text
 from xgboost import XGBRegressor
-import logging
-import numpy as np
 
 from .database_models import Models, Datasets
 from .config import Config
-from .data_cleaner import clean_dataset
 
 
 # 加载模型
@@ -34,72 +31,8 @@ def predict(session, input_data: pd.DataFrame, model_id):
     predictions = ML_model.predict(input_data)
     return predictions.tolist()
 
-def check_dataset_overlap_with_test(dataset_df, data_type):
-    """
-    检查数据集是否与测试集有重叠
-    
-    Args:
-        dataset_df (DataFrame): 要检查的数据集
-        data_type (str): 数据集类型 ('reflux' 或 'reduce')
-        
-    Returns:
-        tuple: (重叠的行数, 重叠的行索引)
-    """
-    # 加载测试集
-    if data_type == 'reflux':
-        X_test = pd.read_csv('uploads/data/X_test_reflux.csv')
-        Y_test = pd.read_csv('uploads/data/Y_test_reflux.csv')
-    elif data_type == 'reduce':
-        X_test = pd.read_csv('uploads/data/X_test_reduce.csv')
-        Y_test = pd.read_csv('uploads/data/Y_test_reduce.csv')
-    else:
-        raise ValueError(f"不支持的数据类型: {data_type}")
-    
-    # 合并X_test和Y_test
-    if data_type == 'reflux':
-        test_df = pd.concat([X_test, Y_test], axis=1)
-    else:
-        test_df = pd.concat([X_test, Y_test], axis=1)
-    
-    # 确定用于比较的列
-    compare_columns = [col for col in dataset_df.columns if col in test_df.columns]
-    
-    if not compare_columns:
-        return 0, []
-    
-    # 查找重叠的行
-    merged = dataset_df[compare_columns].merge(test_df[compare_columns], how='inner', indicator=True)
-    overlapping_rows = merged[merged['_merge'] == 'both']
-    
-    # 获取重叠行在原始数据集中的索引
-    if not overlapping_rows.empty:
-        # 使用合并后的数据找回原始索引
-        overlap_indices = []
-        for _, row in overlapping_rows.iterrows():
-            # 创建一个布尔掩码,用于在原始数据集中查找匹配的行
-            mask = True
-            for col in compare_columns:
-                mask = mask & (dataset_df[col] == row[col])
-            
-            # 获取匹配行的索引
-            matching_indices = dataset_df[mask].index.tolist()
-            overlap_indices.extend(matching_indices)
-        
-        return len(set(overlap_indices)), list(set(overlap_indices))
-    
-    return 0, []
-
 # 计算模型评分
 def calculate_model_score(model_info):
-    """
-    计算模型评分
-    
-    Args:
-        model_info: 模型信息对象
-        
-    Returns:
-        dict: 包含多种评分指标的字典
-    """
     # 加载模型
     with open(model_info.ModelFilePath, 'rb') as f:
         ML_model = pickle.load(f)
@@ -109,55 +42,22 @@ def calculate_model_score(model_info):
         # 加载保存的 X_test 和 Y_test
         X_test = pd.read_csv('uploads/data/X_test_reflux.csv')
         Y_test = pd.read_csv('uploads/data/Y_test_reflux.csv')
-        
-        # 预测测试集
+        print(X_test.columns)  # 在测试时使用的数据的列名
         y_pred = ML_model.predict(X_test)
-        
-        # 计算各种评分指标
-        r2 = r2_score(Y_test, y_pred)
-        mae = mean_absolute_error(Y_test, y_pred)
-        rmse = np.sqrt(mean_squared_error(Y_test, y_pred))
-        
     elif model_info.Data_type == 'reduce':  # 降酸数据集
         # 加载保存的 X_test 和 Y_test
         X_test = pd.read_csv('uploads/data/X_test_reduce.csv')
         Y_test = pd.read_csv('uploads/data/Y_test_reduce.csv')
-        
-        # 预测测试集
+        print(X_test.columns)  # 在测试时使用的数据的列名
         y_pred = ML_model.predict(X_test)
-        
-        # 计算各种评分指标
-        r2 = r2_score(Y_test, y_pred)
-        mae = mean_absolute_error(Y_test, y_pred)
-        rmse = np.sqrt(mean_squared_error(Y_test, y_pred))
-        
-    else:
-        # 不支持的数据类型
-        return {'r2': 0, 'mae': 0, 'rmse': 0}
-    
-    # 返回所有评分指标(不包括交叉验证得分)
-    return {
-        'r2': float(r2),
-        'mae': float(mae),
-        'rmse': float(rmse)
-    }
+
+
+    # 计算 R² 分数
+    r2 = r2_score(Y_test, y_pred)
+    return r2
 
 
 def train_and_save_model(session, model_type, model_name, model_description, data_type, dataset_id=None):
-    """
-    训练并保存模型
-    
-    Args:
-        session: 数据库会话
-        model_type: 模型类型
-        model_name: 模型名称
-        model_description: 模型描述
-        data_type: 数据类型 ('reflux' 或 'reduce')
-        dataset_id: 数据集ID
-        
-    Returns:
-        tuple: (模型名称, 模型ID, 数据集ID)
-    """
     try:
         if not dataset_id:
             # 创建新的数据集并复制数据,此过程将不立即提交
@@ -179,45 +79,26 @@ def train_and_save_model(session, model_type, model_name, model_description, dat
             if dataset.empty:
                 raise ValueError(f"Dataset {dataset_id} is empty or not found.")
 
-        # 使用数据清理模块
         if data_type == 'reflux':
             X = dataset.iloc[:, 1:-1]
             y = dataset.iloc[:, -1]
-
-            # target_column = -1  # 假设目标变量在最后一列
-            # X, y, clean_stats = clean_dataset(dataset, target_column=target_column)
         elif data_type == 'reduce':
             X = dataset.iloc[:, 2:]
             y = dataset.iloc[:, 1]
-            # target_column = 1  # 假设目标变量在第二列
-            # X, y, clean_stats = clean_dataset(dataset, target_column=target_column)
-        
-        # 记录清理统计信息
-        # logging.info(f"数据清理统计: {clean_stats}")
-        
+
         # 训练模型
         model = train_model_by_type(X, y, model_type)
-        
-        # 计算交叉验证得分
-        cv_score = cross_val_score(model, X, y, cv=5).mean()
-        
+
         # 保存模型到数据库
         model_id = save_model(session, model, model_name, model_type, model_description, dataset_id, data_type)
-        
-        # 更新模型的交叉验证得分
-        model_info = session.query(Models).filter(Models.ModelID == model_id).first()
-        if model_info:
-            model_info.CV_score = float(cv_score)
-            session.commit()
-        
+
         # 所有操作成功后,手动提交事务
         session.commit()
-        return model_name, model_id, dataset_id, cv_score
-        
+        return model_name, model_id, dataset_id
     except Exception as e:
+        # 如果在任何阶段出现异常,回滚事务
         session.rollback()
-        logging.error(f"训练和保存模型时发生错误: {str(e)}", exc_info=True)
-        raise
+        raise e  # 可选择重新抛出异常或处理异常
 
 
 
@@ -271,11 +152,8 @@ def data_type_table_mapping(data_type):
 
 def train_model_by_type(X, y, model_type):
     # 划分数据集
-    # X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
-    
-    # 使用全部数据作为训练集
-    X_train, y_train = X, y
-    
+    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
     if model_type == 'RandomForest':
         # 随机森林的参数优化
         return train_random_forest(X_train, y_train)

+ 66 - 833
api/app/routes.py

@@ -1,12 +1,9 @@
 import sqlite3
-from io import BytesIO
-import pickle
 
-from flask import Blueprint, request, jsonify, current_app, send_file
-from werkzeug.security import check_password_hash, generate_password_hash
-from werkzeug.utils import secure_filename
+from flask import Blueprint, request, jsonify,current_app
+from werkzeug.security import generate_password_hash
 
-from .model import predict, train_and_save_model, calculate_model_score, check_dataset_overlap_with_test
+from .model import predict, train_and_save_model, calculate_model_score
 import pandas as pd
 from . import db  # 从 app 包导入 db 实例
 from sqlalchemy.engine.reflection import Inspector
@@ -17,10 +14,10 @@ from .utils import create_dynamic_table, allowed_file, infer_column_types, renam
     predict_to_Q, Q_to_t_ha, create_kriging
 from sqlalchemy.orm import sessionmaker
 import logging
-from sqlalchemy import text, select, MetaData, Table, func
+from sqlalchemy import text, func
 from .tasks import train_model_task
 from datetime import datetime
-from sklearn.metrics import r2_score
+
 
 # 配置日志
 logging.basicConfig(level=logging.DEBUG)
@@ -28,6 +25,9 @@ logger = logging.getLogger(__name__)
 # 创建蓝图 (Blueprint),用于分离路由
 bp = Blueprint('routes', __name__)
 
+# 封装数据库连接函数
+def get_db_connection():
+    return sqlite3.connect('software_intro.db')
 
 # 密码加密
 def hash_password(password):
@@ -139,49 +139,6 @@ def upload_dataset():
         dynamic_table_class = create_dynamic_table(new_dataset.Dataset_ID, column_types)
         insert_data_into_dynamic_table(session, dataset_df, dynamic_table_class)
 
-        # 去除上传数据集内部的重复项
-        original_count = len(dataset_df)
-        dataset_df = dataset_df.drop_duplicates()
-        duplicates_in_file = original_count - len(dataset_df)
-
-        # 检查与现有数据的重复
-        duplicates_with_existing = 0
-        if dataset_type in ['reduce', 'reflux']:
-            # 确定表名
-            table_name = 'current_reduce' if dataset_type == 'reduce' else 'current_reflux'
-            
-            # 从表加载现有数据
-            existing_data = pd.read_sql_table(table_name, session.bind)
-            if 'id' in existing_data.columns:
-                existing_data = existing_data.drop('id', axis=1)
-            
-            # 确定用于比较的列
-            compare_columns = [col for col in dataset_df.columns if col in existing_data.columns]
-            
-            # 计算重复行数
-            original_df_len = len(dataset_df)
-            
-            # 使用concat和drop_duplicates找出非重复行
-            all_data = pd.concat([existing_data[compare_columns], dataset_df[compare_columns]])
-            duplicates_mask = all_data.duplicated(keep='first')
-            duplicates_with_existing = sum(duplicates_mask[len(existing_data):])
-            
-            # 保留非重复行
-            dataset_df = dataset_df[~duplicates_mask[len(existing_data):].values]
-            
-            logger.info(f"原始数据: {original_df_len}, 与现有数据重复: {duplicates_with_existing}, 保留: {len(dataset_df)}")
-
-        # 检查与测试集的重叠
-        test_overlap_count, test_overlap_indices = check_dataset_overlap_with_test(dataset_df, dataset_type)
-        
-        # 如果有与测试集重叠的数据,从数据集中移除
-        if test_overlap_count > 0:
-            # 创建一个布尔掩码,标记不在重叠索引中的行
-            mask = ~dataset_df.index.isin(test_overlap_indices)
-            # 应用掩码,只保留不重叠的行
-            dataset_df = dataset_df[mask]
-            logger.warning(f"移除了 {test_overlap_count} 行与测试集重叠的数据")
-
         # 根据 dataset_type 决定插入到哪个已有表
         if dataset_type == 'reduce':
             insert_data_into_existing_table(session, dataset_df, CurrentReduce)
@@ -194,30 +151,15 @@ def upload_dataset():
         training_triggered, task_id = check_and_trigger_training(session, dataset_type, dataset_df)
 
         response_data = {
-            'message': f'数据集 {dataset_name} 上传成功!',
+            'message': f'Dataset {dataset_name} uploaded successfully!',
             'dataset_id': new_dataset.Dataset_ID,
             'filename': unique_filename,
-            'training_triggered': training_triggered,
-            'data_stats': {
-                'original_count': original_count,
-                'duplicates_in_file': duplicates_in_file,
-                'duplicates_with_existing': duplicates_with_existing,
-                'test_overlap_count': test_overlap_count,
-                'final_count': len(dataset_df)
-            }
+            'training_triggered': training_triggered
         }
         
         if training_triggered:
             response_data['task_id'] = task_id
-            response_data['message'] += ' 自动训练已触发。'
-
-        # 添加去重信息到消息中
-        if duplicates_with_existing > 0:
-            response_data['message'] += f' 已移除 {duplicates_with_existing} 个与现有数据重复的项。'
-            
-        # 添加测试集重叠信息到消息中
-        if test_overlap_count > 0:
-            response_data['message'] += f' 已移除 {test_overlap_count} 个与测试集重叠的项。'
+            response_data['message'] += ' Auto-training has been triggered.'
 
         return jsonify(response_data), 201
 
@@ -256,22 +198,11 @@ def train_and_save_model_endpoint():
         if model_id:
             model_info = session.query(Models).filter(Models.ModelID == model_id).first()
             if model_info:
-                # 计算多种评分指标
-                score_metrics = calculate_model_score(model_info)
+                score = calculate_model_score(model_info)
                 # 更新模型评分
-                model_info.Performance_score = score_metrics['r2']
-                # 添加新的评分指标到数据库
-                model_info.MAE = score_metrics['mae']
-                model_info.RMSE = score_metrics['rmse']
-                # CV_score 已在 train_and_save_model 中设置,此处不再更新
+                model_info.Performance_score = score
                 session.commit()
-                result = {
-                    'model_id': model_id, 
-                    'model_score': score_metrics['r2'],
-                    'mae': score_metrics['mae'],
-                    'rmse': score_metrics['rmse'],
-                    'cv_score': result[3]
-                }
+                result = {'model_id': model_id, 'model_score': score}
 
         # 返回成功响应
         return jsonify({
@@ -333,7 +264,7 @@ def predict_route():
         return jsonify({'error': str(e)}), 400
 
 
-# 为指定模型计算指标评分,需要提供model_id
+# 为指定模型计算评分Performance_score,需要提供model_id
 @bp.route('/score-model/<int:model_id>', methods=['POST'])
 def score_model(model_id):
     # 创建 sessionmaker 实例
@@ -345,23 +276,15 @@ def score_model(model_id):
             return jsonify({'error': 'Model not found'}), 404
 
         # 计算模型评分
-        score_metrics = calculate_model_score(model_info)
-
-        # 更新模型记录中的评分(不包括交叉验证得分)
-        model_info.Performance_score = score_metrics['r2']
-        model_info.MAE = score_metrics['mae']
-        model_info.RMSE = score_metrics['rmse']
+        score = calculate_model_score(model_info)
 
+        # 更新模型记录中的评分
+        model_info.Performance_score = score
         session.commit()
 
-        return jsonify({
-            'message': 'Model scored successfully', 
-            'r2_score': score_metrics['r2'],
-            'mae': score_metrics['mae'],
-            'rmse': score_metrics['rmse'],
-        }), 200
+        return jsonify({'message': 'Model scored successfully', 'score': score}), 200
     except Exception as e:
-        logging.error('Failed to process model scoring:', exc_info=True)
+        logging.error('Failed to process the dataset upload:', exc_info=True)
         return jsonify({'error': str(e)}), 400
     finally:
         session.close()
@@ -466,43 +389,6 @@ def get_model(model_id):
         session.close()
 
 
-@bp.route('/models', methods=['GET'])
-def get_all_models():
-    """
-    获取所有模型信息的API接口
-    
-    @return: JSON响应
-    """
-    Session = sessionmaker(bind=db.engine)
-    session = Session()
-    
-    try:
-        models = session.query(Models).all()
-        if models:
-            result = [
-                {
-                    'ModelID': model.ModelID,
-                    'Model_name': model.Model_name,
-                    'Model_type': model.Model_type,
-                    'Created_at': model.Created_at.strftime('%Y-%m-%d %H:%M:%S'),
-                    'Description': model.Description,
-                    'Performance_score': float(model.Performance_score) if model.Performance_score else None,
-                    'Data_type': model.Data_type
-                }
-                for model in models
-            ]
-            return jsonify(result)
-        else:
-            return jsonify({'message': '未找到任何模型'}), 404
-            
-    except Exception as e:
-        logger.error(f'获取所有模型信息失败: {str(e)}')
-        return jsonify({'error': '服务器内部错误', 'message': str(e)}), 500
-        
-    finally:
-        session.close()
-
-
 @bp.route('/model-parameters', methods=['GET'])
 def get_all_model_parameters():
     """
@@ -567,288 +453,6 @@ def get_model_parameters(model_id):
         return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
 
 
-# 定义添加数据库记录的 API 接口
-@bp.route('/add_item', methods=['POST'])
-def add_item():
-    """
-    接收 JSON 格式的请求体,包含表名和要插入的数据。
-    尝试将数据插入到指定的表中,并进行字段查重。
-    :return:
-    """
-    try:
-        # 确保请求体是 JSON 格式
-        data = request.get_json()
-        if not data:
-            raise ValueError("No JSON data provided")
-
-        table_name = data.get('table')
-        item_data = data.get('item')
-
-        if not table_name or not item_data:
-            return jsonify({'error': 'Missing table name or item data'}), 400
-
-        # 定义各个表的字段查重规则
-        duplicate_check_rules = {
-            'users': ['email', 'username'],
-            'products': ['product_code'],
-            'current_reduce': [ 'Q_over_b', 'pH', 'OM', 'CL', 'H', 'Al'],
-            'current_reflux': ['OM', 'CL', 'CEC', 'H_plus', 'N', 'Al3_plus', 'Delta_pH'],
-            # 其他表和规则
-        }
-
-        # 获取该表的查重字段
-        duplicate_columns = duplicate_check_rules.get(table_name)
-
-        if not duplicate_columns:
-            return jsonify({'error': 'No duplicate check rule for this table'}), 400
-
-        # 动态构建查询条件,逐一检查是否有重复数据
-        condition = ' AND '.join([f"{column} = :{column}" for column in duplicate_columns])
-        duplicate_query = f"SELECT 1 FROM {table_name} WHERE {condition} LIMIT 1"
-
-        result = db.session.execute(text(duplicate_query), item_data).fetchone()
-
-        if result:
-            return jsonify({'error': '重复数据,已有相同的数据项存在。'}), 409
-
-        # 动态构建 SQL 语句,进行插入操作
-        columns = ', '.join(item_data.keys())
-        placeholders = ', '.join([f":{key}" for key in item_data.keys()])
-        sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
-
-        # 直接执行插入操作,无需显式的事务管理
-        db.session.execute(text(sql), item_data)
-
-        # 提交事务
-        db.session.commit()
-
-        # 返回成功响应
-        return jsonify({'success': True, 'message': 'Item added successfully'}), 201
-
-    except ValueError as e:
-        return jsonify({'error': str(e)}), 400
-    except KeyError as e:
-        return jsonify({'error': f'Missing data field: {e}'}), 400
-    except sqlite3.IntegrityError as e:
-        return jsonify({'error': '数据库完整性错误', 'details': str(e)}), 409
-    except sqlite3.Error as e:
-        return jsonify({'error': '数据库错误', 'details': str(e)}), 500
-
-
-@bp.route('/delete_item', methods=['POST'])
-def delete_item():
-    """
-    删除数据库记录的 API 接口
-    """
-    data = request.get_json()
-    table_name = data.get('table')
-    condition = data.get('condition')
-
-    # 检查表名和条件是否提供
-    if not table_name or not condition:
-        return jsonify({
-            "success": False,
-            "message": "缺少表名或条件参数"
-        }), 400
-
-    # 尝试从条件字符串中解析键和值
-    try:
-        key, value = condition.split('=')
-        key = key.strip()  # 去除多余的空格
-        value = value.strip().strip("'\"")  # 去除多余的空格和引号
-    except ValueError:
-        return jsonify({
-            "success": False,
-            "message": "条件格式错误,应为 'key=value'"
-        }), 400
-
-    # 准备 SQL 删除语句
-    sql = f"DELETE FROM {table_name} WHERE {key} = :value"
-
-    try:
-        # 使用 SQLAlchemy 执行删除
-        with db.session.begin():
-            result = db.session.execute(text(sql), {"value": value})
-
-        # 检查是否有记录被删除
-        if result.rowcount == 0:
-            return jsonify({
-                "success": False,
-                "message": "未找到符合条件的记录"
-            }), 404
-
-        return jsonify({
-            "success": True,
-            "message": "记录删除成功"
-        }), 200
-
-    except Exception as e:
-        return jsonify({
-            "success": False,
-            "message": f"删除失败: {e}"
-        }), 500
-
-# 定义修改数据库记录的 API 接口
-@bp.route('/update_item', methods=['PUT'])
-def update_record():
-    """
-    接收 JSON 格式的请求体,包含表名和更新的数据。
-    尝试更新指定的记录。
-    """
-    data = request.get_json()
-
-    # 检查必要的数据是否提供
-    if not data or 'table' not in data or 'item' not in data:
-        return jsonify({
-            "success": False,
-            "message": "请求数据不完整"
-        }), 400
-
-    table_name = data['table']
-    item = data['item']
-
-    # 假设 item 的第一个键是 ID
-    id_key = next(iter(item.keys()))  # 获取第一个键
-    record_id = item.get(id_key)
-
-    if not record_id:
-        return jsonify({
-            "success": False,
-            "message": "缺少记录 ID"
-        }), 400
-
-    # 获取更新的字段和值
-    updates = {key: value for key, value in item.items() if key != id_key}
-
-    if not updates:
-        return jsonify({
-            "success": False,
-            "message": "没有提供需要更新的字段"
-        }), 400
-
-    # 动态构建 SQL
-    set_clause = ', '.join([f"{key} = :{key}" for key in updates.keys()])
-    sql = f"UPDATE {table_name} SET {set_clause} WHERE {id_key} = :id_value"
-
-    # 添加 ID 到参数
-    updates['id_value'] = record_id
-
-    try:
-        # 使用 SQLAlchemy 执行更新
-        with db.session.begin():
-            result = db.session.execute(text(sql), updates)
-
-        # 检查是否有更新的记录
-        if result.rowcount == 0:
-            return jsonify({
-                "success": False,
-                "message": "未找到要更新的记录"
-            }), 404
-
-        return jsonify({
-            "success": True,
-            "message": "数据更新成功"
-        }), 200
-
-    except Exception as e:
-        # 捕获所有异常并返回
-        return jsonify({
-            "success": False,
-            "message": f"更新失败: {str(e)}"
-        }), 500
-
-
-# 定义查询数据库记录的 API 接口
-@bp.route('/search/record', methods=['GET'])
-def sql_search():
-    """
-    接收 JSON 格式的请求体,包含表名和要查询的 ID。
-    尝试查询指定 ID 的记录并返回结果。
-    :return:
-    """
-    try:
-        data = request.get_json()
-
-        # 表名
-        sql_table = data['table']
-
-        # 要搜索的 ID
-        Id = data['id']
-
-        # 连接到数据库
-        cur = db.cursor()
-
-        # 构造查询语句
-        sql = f"SELECT * FROM {sql_table} WHERE id = ?"
-
-        # 执行查询
-        cur.execute(sql, (Id,))
-
-        # 获取查询结果
-        rows = cur.fetchall()
-        column_names = [desc[0] for desc in cur.description]
-
-        # 检查是否有结果
-        if not rows:
-            return jsonify({'error': '未查找到对应数据。'}), 400
-
-        # 构造响应数据
-        results = []
-        for row in rows:
-            result = {column_names[i]: row[i] for i in range(len(row))}
-            results.append(result)
-
-        # 关闭游标和数据库连接
-        cur.close()
-        db.close()
-
-        # 返回 JSON 响应
-        return jsonify(results), 200
-
-    except sqlite3.Error as e:
-        # 如果发生数据库错误,返回错误信息
-        return jsonify({'error': str(e)}), 400
-    except KeyError as e:
-        # 如果请求数据中缺少必要的键,返回错误信息
-        return jsonify({'error': f'缺少必要的数据字段: {e}'}), 400
-
-
-# 定义提供数据库列表,用于展示表格的 API 接口
-@bp.route('/table', methods=['POST'])
-def get_table():
-    data = request.get_json()
-    table_name = data.get('table')
-    if not table_name:
-        return jsonify({'error': '需要表名'}), 400
-
-    try:
-        # 创建 sessionmaker 实例
-        Session = sessionmaker(bind=db.engine)
-        session = Session()
-
-        # 动态获取表的元数据
-        metadata = MetaData()
-        table = Table(table_name, metadata, autoload_with=db.engine)
-
-        # 从数据库中查询所有记录
-        query = select(table)
-        result = session.execute(query).fetchall()
-
-        # 将结果转换为列表字典形式
-        rows = [dict(zip([column.name for column in table.columns], row)) for row in result]
-
-        # 获取列名
-        headers = [column.name for column in table.columns]
-
-        return jsonify(rows=rows, headers=headers), 200
-
-    except Exception as e:
-        return jsonify({'error': str(e)}), 400
-    finally:
-        # 关闭 session
-        session.close()
-
-
 @bp.route('/train-model-async', methods=['POST'])
 def train_model_async():
     """
@@ -976,19 +580,16 @@ def delete_model(model_id, delete_dataset=False):
         session.commit()
         
         # 2. 删除模型文件
-        model_path = model.ModelFilePath
-        try:
-            if os.path.exists(model_path):
+        model_file = f"rf_model_{model_id}.pkl"
+        model_path = os.path.join(current_app.config['MODEL_SAVE_PATH'], model_file)
+        if os.path.exists(model_path):
+            try:
                 os.remove(model_path)
-            else:
+            except OSError as e:
                 # 如果删除文件失败,回滚数据库操作
-                session.rollback() 
-                logger.warning(f'模型文件不存在: {model_path}')
-        except OSError as e:
-            # 如果删除文件失败,回滚数据库操作
-            session.rollback() 
-            logger.error(f'删除模型文件失败: {str(e)}')
-            return jsonify({'error': f'删除模型文件失败: {str(e)}'}), 500
+                session.rollback()
+                logger.error(f'删除模型文件失败: {str(e)}')
+                return jsonify({'error': f'删除模型文件失败: {str(e)}'}), 500
 
         # 3. 如果需要删除关联的数据集
         if delete_dataset and dataset_id:
@@ -1008,7 +609,7 @@ def delete_model(model_id, delete_dataset=False):
 
         response_data = {
             'message': '模型删除成功',
-            'deleted_files': [model_path]
+            'deleted_files': [model_file]
         }
         
         if delete_dataset:
@@ -1068,354 +669,6 @@ def clear_dataset(data_type):
         session.close()
 
 
-@bp.route('/login', methods=['POST'])
-def login_user():
-    # 获取前端传来的数据
-    data = request.get_json()
-    name = data.get('name')  # 用户名
-    password = data.get('password')  # 密码
-
-    logger.info(f"Login request received: name={name}")
-
-    # 检查用户名和密码是否为空
-    if not name or not password:
-        logger.warning("用户名和密码不能为空")
-        return jsonify({"success": False, "message": "用户名和密码不能为空"}), 400
-
-    try:
-        # 查询数据库验证用户名
-        query = "SELECT * FROM users WHERE name = :name"
-        conn = get_db()
-        user = conn.execute(query, {"name": name}).fetchone()
-
-        if not user:
-            logger.warning(f"用户名 '{name}' 不存在")
-            return jsonify({"success": False, "message": "用户名不存在"}), 400
-
-        # 获取数据库中存储的密码(假设密码是哈希存储的)
-        stored_password = user[2]  # 假设密码存储在数据库的第三列
-        user_id = user[0]  # 假设 id 存储在数据库的第一列
-
-        # 校验密码是否正确
-        if check_password_hash(stored_password, password):
-            logger.info(f"User '{name}' logged in successfully.")
-            return jsonify({
-                "success": True,
-                "message": "登录成功",
-                "userId": user_id  # 返回用户 ID
-            })
-        else:
-            logger.warning(f"Invalid password for user '{name}'")
-            return jsonify({"success": False, "message": "用户名或密码错误"}), 400
-
-    except Exception as e:
-        # 记录错误日志并返回错误信息
-        logger.error(f"Error during login: {e}", exc_info=True)
-        return jsonify({"success": False, "message": "登录失败"}), 500
-
-# 更新用户信息接口
-
-
-@bp.route('/update_user', methods=['POST'])
-def update_user():
-    # 获取前端传来的数据
-    data = request.get_json()
-
-    # 打印收到的请求数据
-    current_app.logger.info(f"Received data: {data}")
-
-    user_id = data.get('userId')  # 用户ID
-    name = data.get('name')  # 用户名
-    old_password = data.get('oldPassword')  # 旧密码
-    new_password = data.get('newPassword')  # 新密码
-
-    logger.info(f"Update request received: user_id={user_id}, name={name}")
-
-    # 校验传入的用户名和密码是否为空
-    if not name or not old_password:
-        logger.warning("用户名和旧密码不能为空")
-        return jsonify({"success": False, "message": "用户名和旧密码不能为空"}), 400
-
-    # 新密码和旧密码不能相同
-    if new_password and old_password == new_password:
-        logger.warning(f"新密码与旧密码相同:{name}")
-        return jsonify({"success": False, "message": "新密码与旧密码不能相同"}), 400
-
-    try:
-        # 查询数据库验证用户ID
-        query = "SELECT * FROM users WHERE id = :user_id"
-        conn = get_db()
-        user = conn.execute(query, {"user_id": user_id}).fetchone()
-
-        if not user:
-            logger.warning(f"用户ID '{user_id}' 不存在")
-            return jsonify({"success": False, "message": "用户不存在"}), 400
-
-        # 获取数据库中存储的密码(假设密码是哈希存储的)
-        stored_password = user[2]  # 假设密码存储在数据库的第三列
-
-        # 校验旧密码是否正确
-        if not check_password_hash(stored_password, old_password):
-            logger.warning(f"旧密码错误:{name}")
-            return jsonify({"success": False, "message": "旧密码错误"}), 400
-
-        # 如果新密码非空,则更新新密码
-        if new_password:
-            hashed_new_password = hash_password(new_password)
-            update_query = "UPDATE users SET password = :new_password WHERE id = :user_id"
-            conn.execute(update_query, {"new_password": hashed_new_password, "user_id": user_id})
-            conn.commit()
-            logger.info(f"User ID '{user_id}' password updated successfully.")
-
-        # 如果用户名发生更改,则更新用户名
-        if name != user[1]:
-            update_name_query = "UPDATE users SET name = :new_name WHERE id = :user_id"
-            conn.execute(update_name_query, {"new_name": name, "user_id": user_id})
-            conn.commit()
-            logger.info(f"User ID '{user_id}' name updated to '{name}' successfully.")
-
-        return jsonify({"success": True, "message": "用户信息更新成功"})
-
-    except Exception as e:
-        # 记录错误日志并返回错误信息
-        logger.error(f"Error updating user: {e}", exc_info=True)
-        return jsonify({"success": False, "message": "更新失败"}), 500
-
-
-# 注册用户
-@bp.route('/register', methods=['POST'])
-def register_user():
-    # 获取前端传来的数据
-    data = request.get_json()
-    name = data.get('name')  # 用户名
-    password = data.get('password')  # 密码
-
-    logger.info(f"Register request received: name={name}")
-
-    # 检查用户名和密码是否为空
-    if not name or not password:
-        logger.warning("用户名和密码不能为空")
-        return jsonify({"success": False, "message": "用户名和密码不能为空"}), 400
-
-    # 动态获取数据库表的列名
-    columns = get_column_names('users')
-    logger.info(f"Database columns for 'users' table: {columns}")
-
-    # 检查前端传来的数据是否包含数据库表中所有的必填字段
-    for column in ['name', 'password']:
-        if column not in columns:
-            logger.error(f"缺少必填字段:{column}")
-            return jsonify({"success": False, "message": f"缺少必填字段:{column}"}), 400
-
-    # 对密码进行哈希处理
-    hashed_password = hash_password(password)
-    logger.info(f"Password hashed for user: {name}")
-
-    # 插入到数据库
-    try:
-        # 检查用户是否已经存在
-        query = "SELECT * FROM users WHERE name = :name"
-        conn = get_db()
-        user = conn.execute(query, {"name": name}).fetchone()
-
-        if user:
-            logger.warning(f"用户名 '{name}' 已存在")
-            return jsonify({"success": False, "message": "用户名已存在"}), 400
-
-        # 向数据库插入数据
-        query = "INSERT INTO users (name, password) VALUES (:name, :password)"
-        conn.execute(query, {"name": name, "password": hashed_password})
-        conn.commit()
-
-        logger.info(f"User '{name}' registered successfully.")
-        return jsonify({"success": True, "message": "注册成功"})
-
-    except Exception as e:
-        # 记录错误日志并返回错误信息
-        logger.error(f"Error registering user: {e}", exc_info=True)
-        return jsonify({"success": False, "message": "注册失败"}), 500
-
-
-def get_column_names(table_name):
-    """
-    动态获取数据库表的列名。
-    """
-    try:
-        conn = get_db()
-        query = f"PRAGMA table_info({table_name});"
-        result = conn.execute(query).fetchall()
-        conn.close()
-        return [row[1] for row in result]  # 第二列是列名
-    except Exception as e:
-        logger.error(f"Error getting column names for table {table_name}: {e}", exc_info=True)
-        return []
-
-
-# 导出数据
-@bp.route('/export_data', methods=['GET'])
-def export_data():
-    table_name = request.args.get('table')
-    file_format = request.args.get('format', 'excel').lower()
-
-    if not table_name:
-        return jsonify({'error': '缺少表名参数'}), 400
-    if not table_name.isidentifier():
-        return jsonify({'error': '无效的表名'}), 400
-
-    try:
-        conn = get_db()
-        query = "SELECT name FROM sqlite_master WHERE type='table' AND name=?;"
-        table_exists = conn.execute(query, (table_name,)).fetchone()
-        if not table_exists:
-            return jsonify({'error': f"表 {table_name} 不存在"}), 404
-
-        query = f"SELECT * FROM {table_name};"
-        df = pd.read_sql(query, conn)
-
-        output = BytesIO()
-        if file_format == 'csv':
-            df.to_csv(output, index=False, encoding='utf-8')
-            output.seek(0)
-            return send_file(output, as_attachment=True, download_name=f'{table_name}_data.csv', mimetype='text/csv')
-        elif file_format == 'excel':
-            df.to_excel(output, index=False, engine='openpyxl')
-            output.seek(0)
-            return send_file(output, as_attachment=True, download_name=f'{table_name}_data.xlsx',
-                             mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
-        else:
-            return jsonify({'error': '不支持的文件格式,仅支持 CSV 和 Excel'}), 400
-
-    except Exception as e:
-        logger.error(f"Error in export_data: {e}", exc_info=True)
-        return jsonify({'error': str(e)}), 500
-
-
-# 导入数据接口
-@bp.route('/import_data', methods=['POST'])
-def import_data():
-    logger.debug("Import data endpoint accessed.")
-    if 'file' not in request.files:
-        logger.error("No file in request.")
-        return jsonify({'success': False, 'message': '文件缺失'}), 400
-
-    file = request.files['file']
-    table_name = request.form.get('table')
-
-    if not table_name:
-        logger.error("Missing table name parameter.")
-        return jsonify({'success': False, 'message': '缺少表名参数'}), 400
-
-    if file.filename == '':
-        logger.error("No file selected.")
-        return jsonify({'success': False, 'message': '未选择文件'}), 400
-
-    try:
-        # 保存文件到临时路径
-        temp_path = os.path.join(current_app.config['UPLOAD_FOLDER'], secure_filename(file.filename))
-        file.save(temp_path)
-        logger.debug(f"File saved to temporary path: {temp_path}")
-
-        # 根据文件类型读取文件
-        if file.filename.endswith('.xlsx'):
-            df = pd.read_excel(temp_path)
-        elif file.filename.endswith('.csv'):
-            df = pd.read_csv(temp_path)
-        else:
-            logger.error("Unsupported file format.")
-            return jsonify({'success': False, 'message': '仅支持 Excel 和 CSV 文件'}), 400
-
-        # 获取数据库列名
-        db_columns = get_column_names(table_name)
-        if 'id' in db_columns:
-            db_columns.remove('id')  # 假设 id 列是自增的,不需要处理
-
-        if not set(db_columns).issubset(set(df.columns)):
-            logger.error(f"File columns do not match database columns. File columns: {df.columns.tolist()}, Expected: {db_columns}")
-            return jsonify({'success': False, 'message': '文件列名与数据库表不匹配'}), 400
-
-        # 清洗数据并删除空值行
-        df_cleaned = df[db_columns].dropna()
-
-        # 统一数据类型,避免 int 和 float 合并问题
-        df_cleaned[db_columns] = df_cleaned[db_columns].apply(pd.to_numeric, errors='coerce')
-
-        # 获取现有的数据
-        conn = get_db()
-        with conn:
-            existing_data = pd.read_sql(f"SELECT * FROM {table_name}", conn)
-
-            # 查找重复数据
-            duplicates = df_cleaned.merge(existing_data, on=db_columns, how='inner')
-
-            # 如果有重复数据,删除它们
-            df_cleaned = df_cleaned[~df_cleaned.index.isin(duplicates.index)]
-            logger.warning(f"Duplicate data detected and removed: {duplicates}")
-
-            # 获取导入前后的数据量
-            total_data = len(df_cleaned) + len(duplicates)
-            new_data = len(df_cleaned)
-            duplicate_data = len(duplicates)
-
-            # 导入不重复的数据
-            df_cleaned.to_sql(table_name, conn, if_exists='append', index=False)
-            logger.debug(f"Imported {new_data} new records into the database.")
-
-        # 删除临时文件
-        os.remove(temp_path)
-        logger.debug(f"Temporary file removed: {temp_path}")
-
-        # 返回结果
-        return jsonify({
-            'success': True,
-            'message': '数据导入成功',
-            'total_data': total_data,
-            'new_data': new_data,
-            'duplicate_data': duplicate_data
-        }), 200
-
-    except Exception as e:
-        logger.error(f"Import failed: {e}", exc_info=True)
-        return jsonify({'success': False, 'message': f'导入失败: {str(e)}'}), 500
-
-
-# 模板下载接口
-@bp.route('/download_template', methods=['GET'])
-def download_template():
-    """
-    根据给定的表名,下载表的模板(如 CSV 或 Excel 格式)。
-    """
-    table_name = request.args.get('table')
-    if not table_name:
-        return jsonify({'error': '表名参数缺失'}), 400
-
-    columns = get_column_names(table_name)
-    if not columns:
-        return jsonify({'error': f"Table '{table_name}' not found or empty."}), 404
-
-    # 不包括 ID 列
-    if 'id' in columns:
-        columns.remove('id')
-
-    df = pd.DataFrame(columns=columns)
-
-    file_format = request.args.get('format', 'excel').lower()
-    try:
-        if file_format == 'csv':
-            output = BytesIO()
-            df.to_csv(output, index=False, encoding='utf-8')
-            output.seek(0)
-            return send_file(output, as_attachment=True, download_name=f'{table_name}_template.csv',
-                             mimetype='text/csv')
-        else:
-            output = BytesIO()
-            df.to_excel(output, index=False, engine='openpyxl')
-            output.seek(0)
-            return send_file(output, as_attachment=True, download_name=f'{table_name}_template.xlsx',
-                             mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
-    except Exception as e:
-        logger.error(f"Failed to generate template: {e}", exc_info=True)
-        return jsonify({'error': '生成模板文件失败'}), 500
-
 @bp.route('/update-threshold', methods=['POST'])
 def update_threshold():
     """
@@ -1744,64 +997,44 @@ def kriging_interpolation():
     except Exception as e:
         return jsonify({"error": str(e)}), 500
 
-@bp.route('/model-scatter-data/<int:model_id>', methods=['GET'])
-def get_model_scatter_data(model_id):
-    """
-    获取指定模型的散点图数据(真实值vs预测值)
-    
-    @param model_id: 模型ID
-    @return: JSON响应,包含散点图数据
-    """
-    Session = sessionmaker(bind=db.engine)
-    session = Session()
-    
+# 显示切换模型
+@bp.route('/models', methods=['GET'])
+def get_models():
+    session = None
     try:
-        # 查询模型信息
-        model = session.query(Models).filter_by(ModelID=model_id).first()
-        if not model:
-            return jsonify({'error': '未找到指定模型'}), 404
-            
-        # 加载模型
-        with open(model.ModelFilePath, 'rb') as f:
-            ML_model = pickle.load(f)
-            
-        # 根据数据类型加载测试数据
-        if model.Data_type == 'reflux':
-            X_test = pd.read_csv('uploads/data/X_test_reflux.csv')
-            Y_test = pd.read_csv('uploads/data/Y_test_reflux.csv')
-        elif model.Data_type == 'reduce':
-            X_test = pd.read_csv('uploads/data/X_test_reduce.csv')
-            Y_test = pd.read_csv('uploads/data/Y_test_reduce.csv')
-        else:
-            return jsonify({'error': '不支持的数据类型'}), 400
-            
-        # 获取预测值
-        y_pred = ML_model.predict(X_test)
-        
-        # 生成散点图数据
-        scatter_data = [
-            [float(true), float(pred)] 
-            for true, pred in zip(Y_test.iloc[:, 0], y_pred)
+        # 创建 session
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
+
+        # 查询所有模型
+        models = session.query(Models).all()
+
+        logger.debug(f"Models found: {models}")  # 打印查询的模型数据
+
+        if not models:
+            return jsonify({'message': 'No models found'}), 404
+
+        # 将模型数据转换为字典列表
+        models_list = [
+            {
+                'ModelID': model.ModelID,
+                'ModelName': model.Model_name,
+                'ModelType': model.Model_type,
+                'CreatedAt': model.Created_at.strftime('%Y-%m-%d %H:%M:%S'),
+                'Description': model.Description,
+                'DatasetID': model.DatasetID,
+                'ModelFilePath': model.ModelFilePath,
+                'DataType': model.Data_type,
+                'PerformanceScore': model.Performance_score
+            }
+            for model in models
         ]
-        
-        # 计算R²分数
-        r2 = r2_score(Y_test, y_pred)
-        
-        # 获取数据范围,用于绘制对角线
-        y_min = min(min(Y_test.iloc[:, 0]), min(y_pred))
-        y_max = max(max(Y_test.iloc[:, 0]), max(y_pred))
-        
-        return jsonify({
-            'scatter_data': scatter_data,
-            'r2_score': float(r2),
-            'y_range': [float(y_min), float(y_max)],
-            'model_name': model.Model_name,
-            'model_type': model.Model_type
-        }), 200
-        
+
+        return jsonify(models_list), 200
+
     except Exception as e:
-        logger.error(f'获取模型散点图数据失败: {str(e)}', exc_info=True)
-        return jsonify({'error': f'获取数据失败: {str(e)}'}), 500
-        
+        return jsonify({'error': str(e)}), 400
     finally:
-        session.close()
+        if session:
+            session.close()
+

+ 1 - 1
api/app/tasks.py

@@ -2,7 +2,7 @@
 Celery任务定义文件
 """
 from celery import current_task
-from app.celery_app import celery
+from api.app.celery_app import celery
 import time
 from .database_models import Models, Datasets
 from .model import train_and_save_model, calculate_model_score

BIN
api/model_optimize/data/Acidity_reduce_new - 1.xlsx


BIN
api/model_optimize/data/Acidity_reduce_new - 2.xlsx


BIN
api/model_optimize/data/Acidity_reduce_new - 3.xlsx


BIN
api/pkl/rf_model_0104_0932.pkl


BIN
api/pkl/rf_model_0104_1145.pkl


BIN
api/pkl/rf_model_0104_1149.pkl


BIN
api/pkl/rf_model_0104_1237.pkl


BIN
api/pkl/rf_model_0104_1259.pkl


BIN
api/pkl/rf_model_0104_1415.pkl


BIN
api/pkl/rf_model_0104_1418.pkl


BIN
api/pkl/rf_model_0104_1420.pkl


BIN
api/pkl/rf_model_0107_0123.pkl


BIN
api/pkl/rf_model_0111_1755.pkl


BIN
api/pkl/rf_model_0308_1550.pkl


BIN
api/pkl/rf_model_0308_1619.pkl


BIN
api/pkl/rf_model_0308_1632.pkl


+ 11 - 3
api/run.py

@@ -1,10 +1,9 @@
-from flask import request
 
+from flask import request
 from app import create_app
 import os
 # 创建 Flask 应用
 app = create_app()
-
 # 使用 HTTPS
 context = ('ssl/cert.crt', 'ssl/cert.key')
 @app.before_request
@@ -13,7 +12,16 @@ def force_https():
         url = request.url.replace('http://', 'https://', 1)
         from flask import redirect
         return redirect(url, code=301)
-
+# 设置 secret_key
+app.secret_key = os.urandom(24)  # 使用随机生成的安全密钥
+# 使用 HTTPS
+context = ('ssl/cert.crt', 'ssl/cert.key')
+#@app.before_request
+#def force_https():
+#    if not request.is_secure:
+#        url = request.url.replace('http://', 'https://', 1)
+#       from flask import redirect
+#        return redirect(url, code=301)
 
 # 启动服务器
 if __name__ == '__main__':

BIN
api/software_intro.db


BIN
api/uploads/datasets/dataset_33.xlsx


BIN
api/uploads/datasets/dataset_6.xlsx


BIN
api/uploads/datasets/dataset_7.xlsx


BIN
api/uploads/datasets/dataset_8.xlsx


BIN
assets/taddar/W020241210635035429857_ORIGIN.jpg


BIN
assets/taddar/首页.png


+ 48 - 0
custom-tab-bar/index.js

@@ -0,0 +1,48 @@
+// custom-tab-bar/index.js
+Component({
+  data: {
+    selected: 0, // 当前选中的 tab 索引
+    color: "#999999",
+    selectedColor: "#F7393F",
+    list: [
+      { 
+        text: "首页", 
+        pagePath: "/pages/Home/Home", 
+        iconPath: "/assets/taddar/首页.png", 
+        selectedIconPath: "/assets/taddar/首页 (1).png" 
+      },
+      { 
+        text: "反酸模型", 
+        pagePath: "/pages/Soil Acidification/Soil Acidification", 
+        iconPath: "/assets/taddar/chart-histogram (1).png", 
+        selectedIconPath: "/assets/taddar/chart-histogram.png" 
+      },
+      { 
+        text: "降酸模型", 
+        pagePath: "/pages/Soil Deacidification/Soil Deacidification", 
+        iconPath: "/assets/taddar/Data_Visualization-active.png", 
+        selectedIconPath: "/assets/taddar/Data_Visualization.png" 
+      },
+      { 
+        text: "人员中心", 
+        pagePath: "/pages/Staff/Staff", 
+        iconPath: "/assets/taddar/users-copy.png", 
+        selectedIconPath: "/assets/taddar/copy.png" 
+      }
+    ]
+  },
+  attached() {
+    // 可在此设置初始选中项
+  },
+  methods: {
+    switchTab(e) {
+      const { index, path } = e.currentTarget.dataset;
+      wx.switchTab({
+        url: path,
+      });
+      this.setData({
+        selected: index, // 更新当前选中的 tab 索引
+      });
+    },
+  },
+});

+ 6 - 0
custom-tab-bar/index.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "注册",
+  "navigationBarBackgroundColor": "#f8f8f8",  
+  "navigationBarTextStyle": "black"  
+}

+ 7 - 0
custom-tab-bar/index.wxml

@@ -0,0 +1,7 @@
+<cover-view class="tab-bar">
+  <cover-view class="tab-bar-border"></cover-view>
+  <cover-view wx:for="{{list}}" wx:key="index" class="tab-bar-item" data-path="{{item.pagePath}}" data-index="{{index}}" bindtap="switchTab">
+    <cover-image src="{{selected === index ? item.selectedIconPath : item.iconPath}}"></cover-image>
+    <cover-view style="color: {{selected === index ? selectedColor : color}}">{{item.text}}</cover-view>
+  </cover-view>
+</cover-view>

+ 38 - 0
custom-tab-bar/index.wxss

@@ -0,0 +1,38 @@
+.tab-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 48px;
+  background: white;
+  display: flex;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+ 
+.tab-bar-border {
+  background-color: rgba(0, 0, 0, 0.33);
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 1px;
+  transform: scaleY(0.5);
+}
+ 
+.tab-bar-item {
+  flex: 1;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+ 
+.tab-bar-item cover-image {
+  width: 27px;
+  height: 27px;
+}
+ 
+.tab-bar-item cover-view {
+  font-size: 10px;
+}

+ 66 - 0
pages/Overview/Overview.js

@@ -0,0 +1,66 @@
+// pages/Regular User/Overview/Overview.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 6 - 0
pages/Overview/Overview.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "项目简介",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black"  
+}

+ 3 - 0
pages/Overview/Overview.wxml

@@ -0,0 +1,3 @@
+<view class="green-box">
+  <text class="title-en">项目简介</text>
+</view>

+ 21 - 0
pages/Overview/Overview.wxss

@@ -0,0 +1,21 @@
+/* 页面整体背景 */
+.green-box {
+  background-color: #4caf50; /* 绿色背景 */
+  padding: 10px 20px;  /* 减小上下内边距,控制高度 */
+  text-align: center;
+  display: flex;
+  flex-direction: column; /* 设置垂直方向布局 */
+  justify-content: center;
+  align-items: center;
+  max-height: 100rpx; /* 限制最大高度 */
+  max-width: calc(100% - 10px); /* 宽度最大100%,两边各留5px */
+  margin-left: 15px;  /* 左边留5px */
+  margin-right: 15px; /* 右边留5px */
+  box-sizing: border-box; /* 确保 padding 和 margin 不影响宽度计算 */
+}
+
+.title-en {
+  font-size: 16px;
+  color: #fff;
+  display: block;
+}

+ 146 - 0
pages/Regular/Regular.js

@@ -0,0 +1,146 @@
+Page({
+  data: {
+    name: '',     // 用户名
+    password: '', // 密码
+    errorMessage: '', // 错误提示
+  },
+
+  // 输入用户名
+  inputName: function (e) {
+    this.setData({
+      name: e.detail.value
+    });
+  },
+
+<<<<<<< HEAD:pages/b/b.js
+  // 输入密码
+  inputPassword: function (e) {
+=======
+  // 取消登录弹窗
+  potNo() {
+    this.setData({
+      isHidden: true
+    });
+  },
+
+  // 确认登录弹窗
+  popYes() {
+    const { avatarUrl, nickName } = this.data;
+    if (!avatarUrl || !nickName) {
+      wx.showToast({
+        icon: 'error',
+        title: '请获取头像和昵称',
+      });
+      return;
+    }
+
+    this.setData({
+      isLogin: true,
+      isHidden: true,
+    });
+
+    // 登录成功后跳转到指定页面
+    wx.switchTab({
+      url: '/pages/Regular User/Home/Home',
+    });
+  },
+
+  // 用户名密码登录
+  inputUsername(e) {
+    this.setData({
+      username: e.detail.value
+    });
+  },
+
+  inputPassword(e) {
+>>>>>>> origin/soligd:pages/Regular/Regular.js
+    this.setData({
+      password: e.detail.value
+    });
+  },
+
+  // 注册逻辑
+  register: function () {
+    const { name, password } = this.data;
+
+    // 检查用户名和密码是否为空
+    if (!name || !password) {
+      wx.showToast({
+        title: '用户名和密码不能为空',
+        icon: 'none',
+        duration: 2000
+      });
+      return;
+    }
+
+    wx.showLoading({
+      title: '注册中...'
+    });
+
+    setTimeout(() => {
+      // 发送请求到后端,进行用户注册
+      wx.request({
+        url: 'https://soilgd.com:5000/register', // 后端注册接口
+        method: 'POST',
+        data: {
+          name: name,        // 修改为 name
+          password: password,
+        },
+        success: (res) => {
+          wx.hideLoading();
+          if (res.data.success) {
+            wx.showToast({
+              title: '注册成功',
+              icon: 'success',
+              duration: 2000
+            });
+
+            // 清空输入框
+            this.setData({
+              name: '',
+              password: '',
+              errorMessage: ''
+            });
+
+<<<<<<< HEAD:pages/b/b.js
+            // 跳转到登录页面
+            wx.switchTab({
+              url: '/pages/Home/Home' 
+            });
+          } else {
+            wx.showToast({
+              title: res.data.message || '注册失败',
+              icon: 'none',
+              duration: 2000
+            });
+          }
+        },
+        fail: (err) => {
+          wx.hideLoading();
+          wx.showToast({
+            title: '网络错误,请重试',
+            icon: 'none',
+            duration: 2000
+          });
+          console.error('注册失败:', err);
+        }
+      });
+=======
+        // 登录成功后跳转到指定页面
+        wx.switchTab({
+          url: '/pages/Regular User/Home/Home',
+        });
+      } else {
+        this.setData({
+          errorMessage: '用户名或密码错误'
+        });
+        wx.showToast({
+          title: '用户名或密码错误',
+          icon: 'none',
+          duration: 2000
+        });
+      }
+>>>>>>> origin/soligd:pages/Regular/Regular.js
+    }, 1500);
+  },
+});

+ 6 - 0
pages/Regular/Regular.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "普通用户登录",
+  "navigationBarBackgroundColor": "#f8f8f8",  
+  "navigationBarTextStyle": "black"  
+}

+ 6 - 0
pages/Regular/Regular.wxml

@@ -0,0 +1,6 @@
+<view class="container">
+  <input class="input" bindinput="inputName" placeholder="请输入用户名" />
+  <input class="input" bindinput="inputPassword" placeholder="请输入密码" type="password" />
+  <button class="btn" bindtap="register">注册</button>
+  <text class="error">{{errorMessage}}</text>
+</view>

+ 42 - 0
pages/Regular/Regular.wxss

@@ -0,0 +1,42 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 50px 40px;
+  height: 50vh; /* 使容器占据整个屏幕高度 */
+}
+
+.input {
+  width: 100%;
+  height: 30px;
+  margin-bottom: 10px;
+  padding: 0 20px;
+  border: 1px solid #ddd;
+  border-radius: var(--border-radius, 10px);
+  box-sizing: border-box;
+}
+
+.btn {
+  width: 30%;            /* 按钮占满宽度 */
+  height: 40px;
+  padding: 1px 0;        /* 按钮内边距,控制高度 */
+  margin: 1px 0;         /* 按钮间距 */
+  background-color: #3EC01E; /* 按钮背景颜色 */
+  color: white;           /* 按钮文字颜色 */
+  font-size: 16px;        /* 按钮文字大小 */
+  border: none;           /* 去除按钮边框 */
+  outline: none;          /* 去除焦点边框 */
+  text-align: center;     /* 文字居中 */
+  border-radius: 5px;     /* 圆角效果 */
+}
+
+.btn:active {
+  background-color: var(--active-color, #128c13);
+  box-shadow: 0 2rpx 5rpx rgba(0, 0, 0, 0.2);  /* 按钮点击时阴影变化 */
+}
+
+.btn:hover {
+  background-color: var(--hover-color, #16b818);
+  cursor: pointer;  /* 鼠标悬浮时显示手型 */
+}

+ 66 - 0
pages/ResearchFindings/ResearchFindings.js

@@ -0,0 +1,66 @@
+// pages/Regular User/ResearchFindings/ResearchFindings.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 6 - 0
pages/ResearchFindings/ResearchFindings.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "研究成果",
+  "navigationBarBackgroundColor": "#ffffff",
+  "navigationBarTextStyle": "black"  
+}

+ 8 - 0
pages/ResearchFindings/ResearchFindings.wxml

@@ -0,0 +1,8 @@
+<view class="container">
+  <view class="section">
+    <text class="content">⼴东省典型区域⼟壤环境质量及农产品安全研究,在⼴东省典型区域率先系统地开展了⼟壤环境质量的探查研究,为新的国家⼟壤环境质量标准修订稿中有关有机污染物指标限值的确定提供了科⼴东省典型区域⼟壤环境质量及农产品安全研究,在⼴东省典型区域率先系统地开展了⼟壤环境质量的探查研究,为新的国⼟壤环境质量标准修订稿中有关有机污染物指标限值的确定提供了科学依据,并为全国开展⼟壤有机污染物调查提供了重要参考。相关成果获得2009年度⼴东省科学技术⼀等奖。</text>
+  </view>
+</view>
+
+<image class="navigat-arrow" src="/assets/taddar/12.png" />
+

+ 27 - 0
pages/ResearchFindings/ResearchFindings.wxss

@@ -0,0 +1,27 @@
+/* 父容器样式 */
+.container {
+  padding: 15px;
+  background-color: #f4f4f4;
+  display: block;
+}
+
+/* 标题样式 */
+.title {
+  font-size: 22px;
+  font-weight: bold;
+}
+
+.content {
+  font-size: 16px;
+}
+
+/* 使每个段落独立 */
+.content {
+  text-indent: 2em;  /* 段落首行缩进 */
+}
+
+.navigat-arrow {
+  padding: 10px;
+  width: 700rpx;  /* 设置图片的大小 */
+  height: 400rpx;  /* 设置图片的大小 */
+}

+ 21 - 0
pages/RoleSelectionPage/RoleSelectionPage.js

@@ -0,0 +1,21 @@
+Page({
+  selectRegularUser() {
+    // 设置用户角色为普通用户
+    wx.setStorageSync('userRole', 'RegularUser');
+
+    // 跳转到普通用户首页
+    wx.switchTab({
+      url: '/pages/Home/Home', // 普通用户首页
+    });
+  },
+
+  selectAdmin() {
+    // 设置用户角色为管理员
+    wx.setStorageSync('userRole', 'Admin');
+
+    // 跳转到管理员首页
+    wx.navigateTo({
+      url: '/shoping/threshold/threshold', // 管理员首页
+    });
+  }
+});

+ 6 - 0
pages/RoleSelectionPage/RoleSelectionPage.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "选择权限", 
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black"  
+}

+ 7 - 0
pages/RoleSelectionPage/RoleSelectionPage.wxml

@@ -0,0 +1,7 @@
+<!-- pages/role-selection/role-selection.wxml -->
+<view class="container">
+  <button class="full-width-button" bindtap="selectRegularUser">普通用户</button>
+  <button class="full-width-button" bindtap="selectAdmin">管理员</button>
+</view>
+
+<custom-tab-bar></custom-tab-bar>

+ 27 - 0
pages/RoleSelectionPage/RoleSelectionPage.wxss

@@ -0,0 +1,27 @@
+/* 父容器样式 */
+.container {
+  display: flex;
+  flex-direction: column; /* 垂直排列 */
+  align-items: center;    /* 居中对齐 */
+  padding: 20px 0;        /* 增加上下内边距 */
+  margin: 150px  20px 20px  20px;     /* 增加顶部外边距 */
+}
+
+/* 按钮样式 */
+.full-width-button {
+  width: 100%;            /* 按钮占满宽度 */
+  padding: 15px 0;        /* 按钮内边距,控制高度 */
+  margin: 15px 0;         /* 按钮间距 */
+  background-color: #3EC01E; /* 按钮背景颜色 */
+  color: white;           /* 按钮文字颜色 */
+  font-size: 18px;        /* 按钮文字大小 */
+  border: none;           /* 去除按钮边框 */
+  outline: none;          /* 去除焦点边框 */
+  text-align: center;     /* 文字居中 */
+  border-radius: 5px;     /* 圆角效果 */
+}
+
+/* 按钮点击效果 */
+.full-width-button:active {
+  background-color: #299A0C; /* 按下按钮时的颜色变化 */
+}

+ 66 - 0
pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.js

@@ -0,0 +1,66 @@
+// pages/Regular User/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 6 - 0
pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "降酸模型迭代进化",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black" 
+}

+ 2 - 0
pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxml

@@ -0,0 +1,2 @@
+<!--pages/Regular User/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxml-->
+<text>这里可以放"降酸模型"计算图片</text>

+ 1 - 0
pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxss

@@ -0,0 +1 @@
+/* pages/Regular User/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxss */

+ 66 - 0
pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js

@@ -0,0 +1,66 @@
+// pages/Regular User/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 6 - 0
pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "反酸模型迭代进化",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black" 
+}

+ 2 - 0
pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml

@@ -0,0 +1,2 @@
+<!--pages/Regular User/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml-->
+<text>这里可以放"反酸模型"计算图片</text>

+ 1 - 0
pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxss

@@ -0,0 +1 @@
+/* pages/Regular User/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxss */

+ 26 - 0
pages/Soil Acidification/Soil Acidification.js

@@ -0,0 +1,26 @@
+// pages/Home/Home.js
+Page({
+  SoilPro() {
+    wx.navigateTo({
+      url: '/pages/SoilPro/SoilPro'
+    });
+  },
+  onShow: function() {
+ 
+    if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+      this.getTabBar().setData({
+        selected: 1  //这个数字是当前页面在tabBar中list数组的索引
+      })
+    }
+},
+  Calculation() {
+    wx.navigateTo({
+      url: '/pages/Calculation/Calculation'
+    });
+  },
+  Soil_Acidification_Iterative_Evolution() {
+    wx.navigateTo({
+      url: '/pages/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution'
+    });
+  },
+})

+ 13 - 0
pages/Soil Acidification/Soil Acidification.json

@@ -0,0 +1,13 @@
+{
+<<<<<<< HEAD:pages/Visualization/Visualization.json
+  "usingComponents": { },
+  "navigationBarTitleText": "反酸模型数据管理",
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"
+=======
+  "usingComponents": {},
+  "navigationBarTitleText": "反酸模型",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black"  
+>>>>>>> origin/soligd:pages/Soil Acidification/Soil Acidification.json
+}

+ 21 - 0
pages/Soil Acidification/Soil Acidification.wxml

@@ -0,0 +1,21 @@
+<view class="containe">
+  <text class="regular-text">
+    注意事项如下:
+    1. 本模型是基于我国南方 23 份酸性土壤样品开发的;
+    2. 土壤起始 pH 和目标 pH 是指在土水比为 1:2.5 下的 pH 值;\n3. 施用最佳时期为翻耕前,将农用石灰质物料均匀撒加到土壤表面,然后与耕作层土壤(0-20 cm)混匀;\n4. 本软件同时可以计算不同类型碱性物料的施用量,包括石灰石粉、生石灰、熟石灰、白云石、以及已知 CaO 和 MgO 含量的碱性材料。
+    </text>
+</view>
+
+<view class="containe">
+<text class="regular-text">
+    从重金属污染土壤安全利用的目的,还需注意:\n1. 土壤目标 pH 为 6.5 时能有效降低土壤镉的活性和稻米镉的积累;\n2. 农用石灰质物料重金属含量不能超过农用物料重金属含量标准;\n3. 一次施用后,稻米降镉效果可持续多年,每隔 3-5 年通过监测土壤 pH 变化,根据土壤 pH 的变化,可适当追加少量石灰质物料。
+  </text>
+</view>
+
+<image class="navigat-arrow" src="/assets/taddar/微信图片_20241209174643.png" />
+
+<view class="container">
+  <button class="full-width-button" bindtap="Calculation">反酸模型计算</button>
+  <button class="full-width-button" bindtap="Soil_Acidification_Iterative_Evolution">反酸模型迭代进化</button>
+</view>
+

+ 46 - 0
pages/Soil Acidification/Soil Acidification.wxss

@@ -0,0 +1,46 @@
+/* 普通文本样式 */
+.containe {
+  padding: 5px 5px;  /* 调整容器的内边距,减小文字与容器的距离 */
+  line-height: 1;  /* 调整行高,控制文字的垂直间距 */
+  margin-bottom: 2px;
+}
+
+.regular-text {
+  font-size: 14px;  /* 设置字体大小 */
+  color: #333333;
+}
+
+
+/* 父容器样式 */
+.container {
+  display: flex;
+  flex-direction: column; /* 垂直排列 */
+  align-items: center;    /* 居中对齐 */
+  padding: 10px 0;        /* 增加上下内边距 */
+  margin: 1px 20px 20px  20px;     /* 增加顶部外边距 */
+}
+
+/* 按钮样式 */
+.full-width-button {
+  width: 100%;            /* 按钮占满宽度 */
+  padding: 5px 0;        /* 按钮内边距,控制高度 */
+  margin: 15px 0;         /* 按钮间距 */
+  background-color: #3EC01E; /* 按钮背景颜色 */
+  color: white;           /* 按钮文字颜色 */
+  font-size: 18px;        /* 按钮文字大小 */
+  border: none;           /* 去除按钮边框 */
+  outline: none;          /* 去除焦点边框 */
+  text-align: center;     /* 文字居中 */
+  border-radius: 5px;     /* 圆角效果 */
+}
+
+/* 按钮点击效果 */
+.full-width-button:active {
+  background-color: #299A0C; /* 按下按钮时的颜色变化 */
+}
+
+.navigat-arrow {
+  padding: 5px;
+  width: 700rpx;
+  height: 400rpx;
+}

+ 21 - 0
pages/Soil Deacidification/Soil Deacidification.js

@@ -0,0 +1,21 @@
+// pages/Home/Home.js
+Page({
+  AcidNeutralizationModel() {
+    wx.navigateTo({
+      url: '/pages/AcidNeutralizationModel/AcidNeutralizationModel'
+    });
+  },
+  onShow: function() {
+ 
+    if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+      this.getTabBar().setData({
+        selected: 2  //这个数字是当前页面在tabBar中list数组的索引
+      })
+    }
+},
+  Soil_Acid_Reduction_Iterative_Evolution() {
+    wx.navigateTo({
+      url: '/pages/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution'
+    });
+  },
+})

+ 6 - 0
pages/Soil Deacidification/Soil Deacidification.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "降酸模型",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black"
+}

+ 20 - 0
pages/Soil Deacidification/Soil Deacidification.wxml

@@ -0,0 +1,20 @@
+<view class="containe">
+  <text class="regular-text">
+    注意事项如下:
+    1. 本模型是基于我国南方 23 份酸性土壤样品开发的;
+    2. 土壤起始 pH 和目标 pH 是指在土水比为 1:2.5 下的 pH 值;\n3. 施用最佳时期为翻耕前,将农用石灰质物料均匀撒加到土壤表面,然后与耕作层土壤(0-20 cm)混匀;\n4. 本软件同时可以计算不同类型碱性物料的施用量,包括石灰石粉、生石灰、熟石灰、白云石、以及已知 CaO 和 MgO 含量的碱性材料。
+    </text>
+</view>
+
+<view class="containe">
+<text class="regular-text">
+    从重金属污染土壤安全利用的目的,还需注意:\n1. 土壤目标 pH 为 6.5 时能有效降低土壤镉的活性和稻米镉的积累;\n2. 农用石灰质物料重金属含量不能超过农用物料重金属含量标准;\n3. 一次施用后,稻米降镉效果可持续多年,每隔 3-5 年通过监测土壤 pH 变化,根据土壤 pH 的变化,可适当追加少量石灰质物料。
+  </text>
+</view>
+
+<image class="navigat-arrow" src="/assets/taddar/微信图片_20241209174946.png" />
+
+<view class="container">
+  <button class="full-width-button" bindtap="AcidNeutralizationModel">降酸模型计算</button>
+  <button class="full-width-button" bindtap="Soil_Acid_Reduction_Iterative_Evolution">降酸模型迭代进化</button>
+</view>

+ 45 - 0
pages/Soil Deacidification/Soil Deacidification.wxss

@@ -0,0 +1,45 @@
+/* 普通文本样式 */
+.containe {
+  padding: 5px 5px;  /* 调整容器的内边距,减小文字与容器的距离 */
+  line-height: 1;  /* 调整行高,控制文字的垂直间距 */
+  margin-bottom: 2px;
+}
+
+.regular-text {
+  font-size: 14px;  /* 设置字体大小 */
+  color: #333333;
+}
+
+/* 父容器样式 */
+.container {
+  display: flex;
+  flex-direction: column; /* 垂直排列 */
+  align-items: center;    /* 居中对齐 */
+  padding: 10px 0;        /* 增加上下内边距 */
+  margin: 1px 20px 20px  20px;     /* 增加顶部外边距 */
+}
+
+/* 按钮样式 */
+.full-width-button {
+  width: 100%;            /* 按钮占满宽度 */
+  padding: 5px 0;        /* 按钮内边距,控制高度 */
+  margin: 15px 0;         /* 按钮间距 */
+  background-color: #3EC01E; /* 按钮背景颜色 */
+  color: white;           /* 按钮文字颜色 */
+  font-size: 18px;        /* 按钮文字大小 */
+  border: none;           /* 去除按钮边框 */
+  outline: none;          /* 去除焦点边框 */
+  text-align: center;     /* 文字居中 */
+  border-radius: 5px;     /* 圆角效果 */
+}
+
+/* 按钮点击效果 */
+.full-width-button:active {
+  background-color: #299A0C; /* 按下按钮时的颜色变化 */
+}
+
+.navigat-arrow {
+  padding: 5px;
+  width: 700rpx;
+  height: 400rpx;
+}

+ 66 - 0
pages/SoilPro/SoilPro.js

@@ -0,0 +1,66 @@
+// pages/SoilPro/SoilPro.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 6 - 0
pages/SoilPro/SoilPro.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "软件简介",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black"  
+}

+ 49 - 0
pages/SoilPro/SoilPro.wxml

@@ -0,0 +1,49 @@
+<view class="green-box">
+  <text class="title-en">算法模型计算及优化软件简介</text>
+</view>
+
+<view class="container">
+  <view class="section">
+    <text class="sub-title">⼴东省⽣态环境与⼟壤研究所</text>
+    <text class="content">依托</text>
+    <text class="sub-title">国家⾃然科学基⾦重点项⽬</text>
+    <text class="content">⽀持,开发了</text>
+    <text class="sub-title">算法模型计算及优化</text>
+    <text class="content">软件。该软件集成了</text>
+    <text class="sub-title">反酸模型</text> 和 
+    <text class="sub-title">降酸模型,</text>
+    <text class="content">以科学化、智能化的⽅式提供⼟壤酸性调控解决⽅案,同时通过模型迭代优化不断提升预测精度。以下为软件的综述性简介:</text>
+    <text class="sub-title">\n1. 软件功能概述</text>
+    <text class="content">反酸模型:输入土壤理化指标(如粘粒、交换性铝、有机质、游离氧化铝等),预测土壤反酸后的 pH 值变化。</text>
+    <text class="content">降酸模型:根据土壤 pH 和目标值,计算不同碱性物料(如石灰石粉、生石灰等)的施用量。</text>
+
+    <text class="sub-title">\n2. 核心技术特点</text>
+    <text class="content">迭代进化模型,反酸模型与降酸模型通过数据驱动实现迭代优化:</text>
+    <text class="content">\n1. 初始模型训练:使用小规模的初始数据集训练初始模型 Aa,获得初步预测能力,准确度为 Ka。</text>
+    <text class="content">\n2. 新数据迭代优化:当新数据集加入后,模型 Aa 更新为 Ab,准确度提升至 Kb。</text>
+    <text class="content">\n3. 长期优化与路线图:随着数据量的不断积累,模型通过多轮迭代进化逐步完善,可绘制性能提升的可视化路线图,展示准确度提升的动态过程。</text>
+
+    <text class="sub-title">\n机器学习赋能</text>
+    <text class="content">\n使用算法(如随机森林、XGBoost)挖掘理化指标与 pH 变化的复杂关系。</text>
+
+    <text class="sub-title">\n动态数据支持</text>
+    <text class="content">\n模型支持多种数据类型的输入,结合实验室数据和田间采样,逐步扩展模型的适用范围。</text>
+
+    <text class="sub-title">\n3. 应用场景</text>
+    <text class="content">农业管理:为农户和农业技术人员提供优化土壤酸性治理的科学依据,提升作物产量与品质。</text>
+    <text class="content">生态修复:支持酸性土壤生态修复的决策与方案优化,助力退化土地的恢复与改良。</text>
+    <text class="content">科学研究:为土壤学者和研究机构提供高效、便捷的土壤酸性调控工具,助力科研进展。</text>
+
+    <text class="sub-title">\n4. 使用优势</text>
+    <text class="content">精准性:基于高质量数据和模型优化,预测结果具备科学性与实践指导价值。</text>
+    <text class="content">可视化路线图:提供模型进化路线图和性能提升的可视化展示,让用户清晰了解模型迭代优化过程和效果。</text>
+
+    <text class="sub-title">\n5. 项目意义</text>
+    <text class="content">算法模型计算及优化软件是国家自然科学基金重点项目的创新成果,系统集成了反酸与降酸模型,采用数据驱动的方式动态优化,为土壤酸性调控提供精准、高效的解决方案。模型的迭代进化及可视化性能展示,不仅提升了科学研究的深度,也为农业和生态保护的实际应用提供了强有力支持。</text>
+  </view>
+</view>
+
+<view class="container">
+  <text class="title-e">广东省生态环境与土壤研究所期待与社会各界共同努力,为土壤科学技术进步和生态文明建设作出积极贡献!</text>
+</view>
+

+ 49 - 0
pages/SoilPro/SoilPro.wxss

@@ -0,0 +1,49 @@
+/* 页面整体背景 */
+.green-box {
+  background-color: #4caf50; /* 绿色背景 */
+  padding: 10px 20px;  /* 减小上下内边距,控制高度 */
+  text-align: center;
+  display: flex;
+  flex-direction: column; /* 设置垂直方向布局 */
+  justify-content: center;
+  align-items: center;
+  max-height: 100rpx; /* 限制最大高度 */
+  max-width: calc(100% - 10px); /* 宽度最大100%,两边各留5px */
+  margin-left: 15px;  /* 左边留5px */
+  margin-right: 15px; /* 右边留5px */
+  box-sizing: border-box; /* 确保 padding 和 margin 不影响宽度计算 */
+}
+
+.title-en {
+  font-size: 16px;
+  color: #fff;
+  display: block;
+}
+
+/* 父容器样式 */
+.container {
+  padding: 15px;
+  background-color: #f4f4f4;
+  display: block;
+}
+
+/* 标题样式 */
+.title {
+  font-size: 22px;
+  font-weight: bold;
+}
+
+.sub-title {
+  font-size: 18px;
+  font-weight:700;
+}
+
+.content {
+  font-size: 16px;
+}
+
+/* 使每个段落独立 */
+.content {
+  text-indent: 2em;  /* 段落首行缩进 */
+}
+

+ 66 - 0
pages/Unit Team Profile/Unit Team Profile.js

@@ -0,0 +1,66 @@
+// pages/Regular User/Unit Team Profile/Unit Team Profile.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 6 - 0
pages/Unit Team Profile/Unit Team Profile.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "承担单位团队信息",
+  "navigationBarBackgroundColor": "#ffffff",
+  "navigationBarTextStyle": "black" 
+}

+ 14 - 0
pages/Unit Team Profile/Unit Team Profile.wxml

@@ -0,0 +1,14 @@
+<view class="green-box">
+  <text class="title-en">广东省⼟壤与⽣态环境研究所</text>
+</view>
+<view class="container">
+  <view class="section">
+    <text class="content">⼴东省科学院⽣态环境与⼟壤研究所,成⽴于1958年,是我国红壤区唯⼀的⼟壤学专业研究机构。前⾝为中科院中南⼟壤研究室,曾⽤名中国科学院⼴州⼟壤研究所、⼴东省⼟壤研究所、⼴东省⽣态环境与⼟壤研究所、⼴东省⽣态环境技术研究所。现有在职职⼯255⼈,科研⼈员218⼈,其中国家级⼈才10⼈,包括国家杰出⻘年基⾦获得者3⼈,国家级优秀⻘年⼈才7⼈;省部级优秀⼈才27⼈次,包括⼴东省杰出⼈才1⼈、⼴东省领军⼈才3⼈、⼴东省杰⻘7⼈、⼴东省⻘拔8⼈等;已形成了⼀⽀⾼⽔平的中⻘年⼈才队伍。建所以来,累积获得省部级奖励超130项,近五年,获国家科技进步⼆等奖1项、省部级⼀等奖 4项。主办国家核⼼学术期刊《⽣态环境学报》、英⽂期刊Carbon Research。建有华南⼟壤污染控制与修复国家地⽅联合⼯程研究中⼼、农业农村部亚热带农⽥⼟环境保护及资源可持续利⽤重点实验室 (部省共建) 、⼴东省农业环境综合治理重点实验室等创新平台。</text>
+  </view>
+</view>
+
+<image class="navigat-arrow" src="/assets/taddar/W020241210635035429857_ORIGIN.jpg" />
+
+<view class="green-box">
+  <text class="title-en">项目团队成员简介</text>
+</view>

+ 48 - 0
pages/Unit Team Profile/Unit Team Profile.wxss

@@ -0,0 +1,48 @@
+/* 页面整体背景 */
+.green-box {
+  background-color: #4caf50; /* 绿色背景 */
+  padding: 10px 20px;  /* 减小上下内边距,控制高度 */
+  text-align: center;
+  display: flex;
+  flex-direction: column; /* 设置垂直方向布局 */
+  justify-content: center;
+  align-items: center;
+  max-height: 100rpx; /* 限制最大高度 */
+  max-width: calc(100% - 10px); /* 宽度最大100%,两边各留5px */
+  margin-left: 15px;  /* 左边留5px */
+  margin-right: 15px; /* 右边留5px */
+  box-sizing: border-box; /* 确保 padding 和 margin 不影响宽度计算 */
+}
+
+.title-en {
+  font-size: 16px;
+  color: #fff;
+  display: block;
+}
+/* 父容器样式 */
+.container {
+  padding: 15px;
+  background-color: #f4f4f4;
+  display: block;
+}
+
+/* 标题样式 */
+.title {
+  font-size: 22px;
+  font-weight: bold;
+}
+
+.content {
+  font-size: 16px;
+}
+
+/* 使每个段落独立 */
+.content {
+  text-indent: 2em;  /* 段落首行缩进 */
+}
+
+.navigat-arrow {
+  padding: 5px;
+  width: 730rpx;
+  height: 400rpx;
+}

+ 66 - 0
shoping/Data Visualization/Data Visualization.js

@@ -0,0 +1,66 @@
+// pages/Data Visualization/Data Visualization.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 3 - 0
shoping/Data Visualization/Data Visualization.json

@@ -0,0 +1,3 @@
+{
+  "usingComponents": {}
+}

+ 2 - 0
shoping/Data Visualization/Data Visualization.wxml

@@ -0,0 +1,2 @@
+<!--pages/Data Visualization/Data Visualization.wxml-->
+<text>pages/Data Visualization/Data Visualization.wxml</text>

+ 1 - 0
shoping/Data Visualization/Data Visualization.wxss

@@ -0,0 +1 @@
+/* pages/Data Visualization/Data Visualization.wxss */

+ 101 - 0
shoping/Staffl/Staff.js

@@ -0,0 +1,101 @@
+Page({
+  data: {
+    isLogin: false,  // 登录状态
+    userInfo: {},    // 存储用户信息(头像和昵称)
+  },
+
+  // 页面加载时获取缓存的昵称和头像
+  onLoad() {
+    const storedUserInfo = wx.getStorageSync('userinfo'); // 获取缓存中的用户信息
+
+    if (storedUserInfo) {
+      // 如果缓存中有用户信息,更新页面上的数据
+      this.setData({
+        userInfo: storedUserInfo,  // 更新用户信息
+        isLogin: true, // 设置登录状态为 true
+      });
+    }
+  },
+
+  // 获取用户头像
+  getAvatar(e) {
+    const avatarUrl = e.detail.avatarUrl;
+    this.setData({
+      'userInfo.avatarUrl': avatarUrl
+    });
+
+    // 保存头像到缓存
+    let userInfo = wx.getStorageSync('userinfo') || {};
+    userInfo.avatarUrl = avatarUrl;
+    wx.setStorageSync('userinfo', userInfo);
+  },
+
+  // 获取用户昵称
+  getName(e) {
+    const nickName = e.detail.value;
+    this.setData({
+      'userInfo.nickName': nickName
+    });
+
+    // 保存昵称到缓存
+    let userInfo = wx.getStorageSync('userinfo') || {};
+    userInfo.nickName = nickName;
+    wx.setStorageSync('userinfo', userInfo);
+  },
+
+  // 显示登录弹窗
+  gologin() {
+    this.setData({
+      isHidden: false
+    });
+  },
+
+  // 取消登录弹窗
+  potNo() {
+    this.setData({
+      isHidden: true
+    });
+  },
+
+  // 确认登录弹窗
+  popYes() {
+    const { avatarUrl, nickName } = this.data.userInfo;
+    if (!avatarUrl || !nickName) {
+      wx.showToast({
+        icon: 'error',
+        title: '请获取头像和昵称',
+      });
+      return;
+    }
+
+    // 保存头像和昵称到缓存
+    wx.setStorageSync('userinfo', this.data.userInfo);
+
+    this.setData({
+      isLogin: true,  // 设置登录状态为 true
+      isHidden: true, // 隐藏弹窗
+    });
+  },
+
+  goToThreshold() {
+    wx.navigateTo({
+      url: '/pages/threshold/threshold'
+    });
+  },
+
+  // 退出登录
+  tuichu() {
+    this.setData({
+      isLogin: false,  // 设置登录状态为 false
+      userInfo: {},    // 清除用户信息
+    });
+
+    // 清除缓存中的用户信息
+    wx.removeStorageSync('userinfo');
+    
+    // 跳转到 "pages/b/b" 页面
+    wx.navigateTo({
+      url: '/pages/Regular User/b/b', // 路径可以根据实际情况修改
+    });
+  }
+});

+ 8 - 0
shoping/Staffl/Staff.json

@@ -0,0 +1,8 @@
+{
+  "usingComponents": {
+    "custom-tab-bar": "/custom-tab-bar/index"
+  },
+  "navigationBarTitleText": "个人中心",
+  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarTextStyle": "black"  
+}

+ 33 - 0
shoping/Staffl/Staff.wxml

@@ -0,0 +1,33 @@
+<view class="center">
+  <!-- 处理点击事件和hover类 -->
+  <view class="logo" bindtap="goLogin" hover-class="{{!isLogin ? 'logo-hover' : ''}}">
+  <!-- 如果已登录并且有头像,则显示用户头像 -->
+  <image class="logo-img" wx:if="{{isLogin && userInfo && userInfo.avatarUrl}}" src="{{ userInfo.avatarUrl }}" />
+  <!-- 如果未登录或没有头像,则显示默认头像 -->
+  <image class="logo-img" wx:if="{{!isLogin || !userInfo.avatarUrl}}" src="/assets/taddar/me.png" />
+  <view class="logo-title">
+    <!-- 显示昵称,如果已登录,则显示昵称 -->
+    <text class="uer-name">Hi,{{ isLogin ? userInfo.nickName : '未登录' }}</text>
+    <!-- 未登录时显示登录图标 -->
+    <image class="go-login" wx:if="{{!isLogin}}" src="/assets/taddar/right (1).png" />
+  </view>
+</view>
+
+
+  <view class="center-list">
+    <view class="center-list-item">
+      <image class="list-icon" src="/assets/taddar/edit.png" />
+      <text class="list-text">编辑个人信息</text>
+      <image class="navigat-arrow" src="/assets/taddar/right.png" />
+    </view>
+  </view>
+
+  <view class="center-list">
+    <view class="center-list-item border-bottom">
+      <image class="list-icon" src="/assets/taddar/logout.png" />
+      <text class="list-text" bindtap="tuichu">退出登录</text>
+      <image class="navigat-arrow" src="/assets/taddar/right.png" />
+    </view>
+  </view>
+</view>
+<custom-tab-bar></custom-tab-bar>

+ 108 - 0
shoping/Staffl/Staff.wxss

@@ -0,0 +1,108 @@
+.log-list {
+  display: flex;
+  flex-direction: column;
+  padding: 40rpx;
+}
+.log-item {
+  margin: 10rpx;
+}
+<<<<<<< HEAD:pages/logs/logs.wxss
+=======
+
+.logo-hover {
+  opacity: 0.8;
+}
+
+.logo-img {
+  width: 150rpx;  /* 设置图片的大小 */
+  height: 150rpx;  /* 设置图片的大小 */
+  border-radius: 150rpx; /* 圆形边框 */
+}
+
+/* Logo标题样式 */
+.logo-title {
+  height: 150rpx;
+  flex: 1;
+  align-items: center;
+  justify-content: space-between;
+  flex-direction: row;
+  margin-left: 20rpx;
+}
+
+/* 用户名样式 */
+.uer-name {
+  height: 60rpx;
+  line-height: 60rpx;
+  font-size: 38rpx;
+  color: #FFFFFF;
+}
+
+.go-login {
+    height: 40rpx; /* 设置箭头图片的高度 */
+    width: 40rpx;  /* 设置箭头图片的宽度 */
+    line-height: 90rpx; /* 图片垂直居中 */
+    margin-left: 10rpx; /* 设置箭头和文本之间的间距 */
+    object-fit: contain; /* 保证图片不被拉伸 */
+}
+
+.login-title {
+  height: 150rpx;
+  align-items: self-start;
+  justify-content: center;
+  flex-direction: column;
+  margin-left: 20rpx;
+}
+
+/* 中心列表样式 */
+.center-list {
+  background-color: #FFFFFF;
+  margin-top: 20rpx;
+  width: 750rpx;
+  flex-direction: column;
+}
+
+/* 列表项样式 */
+.center-list-item {
+  height: 90rpx;
+  width: 750rpx;
+  box-sizing: border-box;
+  flex-direction: row;
+  padding: 0 20rpx;
+  display: flex;
+  justify-content: center; /* 图片和文本水平居中 */
+  align-items: center;     /* 图片和文本垂直居中 */
+}
+
+/* 边框样式 */
+.border-bottom {
+  border-bottom-width: 1rpx;
+  border-color: #c8c7cc;
+  border-bottom-style: solid;
+}
+
+/* 图片图标样式 */
+.list-icon {
+  width: 40rpx;  /* 设置图片宽度 */
+  height: 40rpx;  /* 设置图片高度 */
+  margin-right: 10rpx; /* 图标和文本之间的间距 */
+  object-fit: contain; /* 保证图片不被拉伸 */
+}
+
+/* 列表文字样式 */
+.list-text {
+  height: 90rpx;
+  line-height: 90rpx;
+  font-size: 34rpx;
+  color: #555;
+  flex: 1;
+  text-align: left;  /* 使文字水平居中 */
+}
+
+/* 导航箭头样式 */
+.navigat-arrow {
+  width: 40rpx;  /* 设置箭头图片的宽度 */
+  height: 40rpx; /* 设置箭头图片的高度 */
+  margin-left: 10rpx; /* 设置箭头和文本之间的间距 */
+  object-fit: contain; /* 保证图片不被拉伸 */
+}
+>>>>>>> origin/soligd:shoping/Staffl/Staff.wxss

+ 66 - 0
shoping/Visualizatio/Visualizatio.js

@@ -0,0 +1,66 @@
+// pages/admin/Visualizatio/Visualizatio.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 5 - 0
shoping/Visualizatio/Visualizatio.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "custom-tab-bar": "/custom-tab-bar/index"
+  }
+}

+ 3 - 0
shoping/Visualizatio/Visualizatio.wxml

@@ -0,0 +1,3 @@
+<!--pages/admin/Visualizatio/Visualizatio.wxml-->
+<text>pages/admin/Visualizatio/Visualizatio.wxml</text>
+<custom-tab-bar></custom-tab-bar>

+ 1 - 0
shoping/Visualizatio/Visualizatio.wxss

@@ -0,0 +1 @@
+/* pages/admin/Visualizatio/Visualizatio.wxss */

+ 66 - 0
shoping/Visualization/Visualization.js

@@ -0,0 +1,66 @@
+// pages/admin/Visualization/Visualization.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 5 - 0
shoping/Visualization/Visualization.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "custom-tab-bar": "/custom-tab-bar/index"
+  }
+}

+ 3 - 0
shoping/Visualization/Visualization.wxml

@@ -0,0 +1,3 @@
+<!--pages/admin/Visualization/Visualization.wxml-->
+<text>pages/admin/Visualization/Visualization.wxml</text>
+<custom-tab-bar></custom-tab-bar>

+ 1 - 0
shoping/Visualization/Visualization.wxss

@@ -0,0 +1 @@
+/* pages/admin/Visualization/Visualization.wxss */

+ 176 - 0
shoping/admin/admin.js

@@ -0,0 +1,176 @@
+Page({
+  data: {
+    isLogin: false, // 登录状态
+    isHidden: true, // 弹窗状态
+    avatarUrl: '', // 用户头像
+    nickName: '', // 用户昵称
+    username: '', // 用户名(用户名密码登录)
+    password: '', // 密码(用户名密码登录)
+    errorMessage: '', // 错误信息
+  },
+
+  // 获取用户头像
+  getAvatar(e) {
+    const avatarUrl = e.detail.avatarUrl; // 从事件中获取头像URL
+
+    if (avatarUrl) {
+      this.setData({
+        avatarUrl: avatarUrl
+      });
+
+      // 授权登录后,自动设置默认密码到缓存
+      wx.setStorageSync('password', '123');
+
+      // 创建userInfo对象,保存头像和昵称到缓存
+      let userInfo = {
+        avatarUrl: avatarUrl,
+        nickName: this.data.nickName
+      };
+      wx.setStorageSync('userinfo', userInfo); // 保存到缓存
+    } else {
+      wx.showToast({
+        title: '头像获取失败',
+        icon: 'error',
+        duration: 2000
+      });
+    }
+  },
+
+  // 获取用户昵称
+  getName(e) {
+    const nickName = e.detail.value; // 获取昵称
+    this.setData({
+      nickName: nickName
+    });
+
+    // 在授权登录时,将昵称更新到缓存的 userInfo 对象中
+    let userInfo = wx.getStorageSync('userinfo') || {};
+    userInfo.nickName = nickName;
+    wx.setStorageSync('userinfo', userInfo); // 更新缓存
+  },
+
+  // 显示登录弹窗
+  gologin() {
+    this.setData({
+      isHidden: false
+    });
+  },
+
+  // 取消登录弹窗
+  potNo() {
+    this.setData({
+      isHidden: true
+    });
+  },
+
+  // 确认登录弹窗
+  popYes() {
+    const { avatarUrl, nickName } = this.data;
+    if (!avatarUrl || !nickName) {
+      wx.showToast({
+        icon: 'error',
+        title: '请获取头像和昵称',
+      });
+      return;
+    }
+
+    this.setData({
+      isLogin: true,
+      isHidden: true,
+    });
+
+    // 登录成功后跳转到指定页面
+    wx.switchTab({
+      url: '/pages/Regular User/Home/Home',
+    });
+  },
+
+  // 用户名密码登录
+  inputUsername(e) {
+    this.setData({
+      username: e.detail.value
+    });
+  },
+
+  inputPassword(e) {
+    this.setData({
+      password: e.detail.value
+    });
+  },
+
+  // 登录验证
+  login() {
+    const { username, password } = this.data;
+
+    if (!username || !password) {
+      this.setData({
+        errorMessage: '用户名和密码不能为空'
+      });
+      return;
+    }
+
+    wx.showLoading({
+      title: '登录中...'
+    });
+
+    setTimeout(() => {
+      wx.hideLoading();
+
+      // 假设用户名和密码为123时登录成功
+      if (username === '123' && password === '123') {
+        wx.showToast({
+          title: '登录成功',
+          icon: 'success',
+          duration: 2000
+        });
+        this.setData({
+          isLogin: true,
+          errorMessage: '',
+        });
+
+        // 登录成功后跳转到指定页面
+        wx.switchTab({
+          url: '/pages/Regular User/Home/Home',
+        });
+      } else {
+        this.setData({
+          errorMessage: '用户名或密码错误'
+        });
+        wx.showToast({
+          title: '用户名或密码错误',
+          icon: 'none',
+          duration: 2000
+        });
+      }
+    }, 1500);
+  },
+
+  // 页面加载时,检查是否有缓存的密码
+  onLoad() {
+    const storedPassword = wx.getStorageSync('password');
+    const storedUserInfo = wx.getStorageSync('userinfo');
+
+    // 如果有缓存的密码,则自动填充
+    if (storedPassword) {
+      this.setData({
+        password: storedPassword // 自动填充缓存的密码
+      });
+    }
+
+    // 如果有缓存的用户信息,自动填充头像和昵称
+    if (storedUserInfo) {
+      this.setData({
+        avatarUrl: storedUserInfo.avatarUrl || '/assets/taddar/授权管理.png', // 默认头像
+        nickName: storedUserInfo.nickName || '未登录', // 默认昵称
+        isLogin: true, // 设置已登录状态
+      });
+    }
+
+    // 如果没有设置头像,使用默认头像
+    if (!this.data.avatarUrl) {
+      this.setData({
+        avatarUrl: '/assets/taddar/授权管理.png' // 默认头像
+      });
+    }
+  }
+});

+ 6 - 0
shoping/admin/admin.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "管理员登录",
+  "navigationBarBackgroundColor": "#f8f8f8",  
+  "navigationBarTextStyle": "black"  
+}

+ 37 - 0
shoping/admin/admin.wxml

@@ -0,0 +1,37 @@
+<view class="container">
+   <!-- 用户名密码登录 -->
+   <view >
+    <input class="input" bindinput="inputUsername" placeholder="请输入用户名" />
+    <input class="input" bindinput="inputPassword" placeholder="请输入密码" type="password" />
+    <button class="gologin" bindtap="login">登录</button>
+    <text class="error">{{errorMessage}}</text>
+  </view>
+  <view class="container">
+<button bindtap="gologin">微信登录</button>
+</view>
+
+  <!-- 登录弹窗 -->
+  <view class="pop_root" hidden="{{isHidden}}">
+    <view class="pop_content">
+      <view class="pot_tip">授权登录弹窗</view>
+      
+      <!-- 获取头像 -->
+      <button class="pop_avatar" open-type="chooseAvatar" bindchooseavatar="getAvatar">
+        <image class="pop_img" wx:if="{{avatarUrl}}" src="{{avatarUrl}}" />
+        <view wx:else>获取头像</view>
+      </button>
+      
+      <!-- 获取昵称 -->
+      <input 
+    class="pop_name" 
+    type="nickname" 
+    bindblur="getName"
+    placeholder="获取昵称"/>
+      
+      <view class="pop_bot">
+        <view class="btn" bind:tap="potNo">取消</view>
+        <view class="btn1" bind:tap="popYes">确定</view>
+      </view>
+    </view>
+  </view>
+</view>

+ 177 - 0
shoping/admin/admin.wxss

@@ -0,0 +1,177 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 20rpx 40rpx;
+  height: 50vh; /* 使容器占据整个屏幕高度 */
+}
+
+.input {
+  width: 100%;
+  height: 80rpx;
+  margin-bottom: 20rpx;
+  padding: 0 20rpx;
+  border: 1px solid #ddd;
+  border-radius: var(--border-radius, 10rpx);
+  box-sizing: border-box;
+}
+
+.btn {
+  width: 100%;
+  height: 80rpx;
+  background-color: var(--primary-color, #1aad19);
+  color: #fff;
+  border: none;
+  border-radius: var(--border-radius, 10rpx);
+  text-align: center;
+  line-height: 45rpx;
+  font-size: 36rpx;
+  box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.1);  /* 添加阴影 */
+  transition: background-color 0.3s ease, box-shadow 0.3s ease;  /* 背景颜色和阴影过渡效果 */
+}
+
+.btn:active {
+  background-color: var(--active-color, #128c13);
+  box-shadow: 0 2rpx 5rpx rgba(0, 0, 0, 0.2);  /* 按钮点击时阴影变化 */
+}
+
+.btn:hover {
+  background-color: var(--hover-color, #16b818);
+  cursor: pointer;  /* 鼠标悬浮时显示手型 */
+}
+
+.error {
+  color: red;
+  margin-top: 10rpx;
+  animation: fadeIn 0.5s ease;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+  }
+  to {
+    opacity: 1;
+  }
+}
+
+.avatar {
+  width: 150rpx;
+  height: 150rpx;
+  border-radius: 50%;
+  margin-top: 20rpx;
+  object-fit: cover;
+}
+
+@media screen and (max-width: 375px) {
+  .container {
+    padding: 100rpx 20rpx;
+  }
+  .btn {
+    font-size: 28rpx;
+    height: 60rpx;
+    line-height: 60rpx;
+  }
+  .input {
+    height: 60rpx;
+  }
+}
+
+/* pages/demo/demo.wxss */
+.root {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+
+.touxiang {
+  width: 300rpx;
+  height: 300rpx;
+  border-radius: 50%;
+  margin-top: 30rpx;
+}
+
+.name {
+  margin-top: 30rpx;
+}
+
+/* 弹窗 */
+.pop_root {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background: rgb(0, 0, 0, 0.6);
+  z-index: 1000;
+}
+
+.pop_content {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: white;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  border-radius: 30rpx 30rpx 0 0;
+}
+
+.pot_tip {
+  font-size: 48rpx;
+  margin-top: 30rpx;
+}
+
+.pop_avatar {
+  width: 200rpx;
+  height: 200rpx;
+  border-radius: 50%;
+  background: gainsboro;
+  font-size: 35rpx;
+  display: flex;
+  flex-direction: center;
+  align-items: center;
+  margin: 30rpx;
+  padding: 0;
+}
+
+.pop_img {
+  width: 100%;
+  height: 100%;
+}
+
+.pop_name {
+  width: 300rpx;
+  bottom: 1px solid gray;
+  border-radius: 25epx;
+  padding-left: 160rpx;
+  margin-bottom: 50rpx;
+}
+
+.pop_bot {
+  display: flex;
+  margin-bottom: 50rpx;
+
+}
+
+.btn {
+  width: 150rpx;
+  border: 1px solid gray;
+  border-radius: 20rpx;
+  text-align: center;
+  background: red;
+  color: white;
+}
+
+.btn1 {
+  width: 150rpx;
+  border: 1px solid gray;
+  border-radius: 20rpx;
+  text-align: center;
+  background: green;
+  color: white;
+  margin-left: 90rpx;
+}

+ 41 - 0
shoping/threshold/threshold.js

@@ -0,0 +1,41 @@
+Page({
+  data: {
+    countThreshold: 3, // 条数阈值
+    percentageThreshold: 70 // 百分比阈值
+  },
+
+  // 修改条数阈值
+  updateCountThreshold(e) {
+    this.setData({
+      countThreshold: Number(e.detail.value)
+    });
+  },
+
+  // 修改百分比阈值
+  updatePercentageThreshold(e) {
+    this.setData({
+      percentageThreshold: Number(e.detail.value)
+    });
+  },
+
+  // 保存设置
+  saveSettings() {
+    const { countThreshold, percentageThreshold } = this.data;
+    const app = getApp();
+
+    // 更新全局变量
+    app.globalData.triggerThreshold = {
+      count: countThreshold,
+      percentage: percentageThreshold
+    };
+
+    // 持久化存储
+    wx.setStorageSync('triggerThreshold', app.globalData.triggerThreshold);
+
+    wx.showToast({
+      title: '设置已保存',
+      icon: 'success',
+      duration: 2000
+    });
+  }
+});

+ 5 - 0
shoping/threshold/threshold.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "custom-tab-bar": "/custom-tab-bar/index"
+  }
+}

+ 12 - 0
shoping/threshold/threshold.wxml

@@ -0,0 +1,12 @@
+<view class="container">
+  <view>
+    <text>条数阈值:</text>
+    <input type="number" value="{{countThreshold}}" bindinput="updateCountThreshold" />
+  </view>
+  <view>
+    <text>百分比阈值:</text>
+    <input type="number" value="{{percentageThreshold}}" bindinput="updatePercentageThreshold" />
+  </view>
+  <button bindtap="saveSettings">保存</button>
+</view>
+<custom-tab-bar></custom-tab-bar>

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.