Browse Source

添加散点图,调整部分页面,添加模型选择功能

DIng 6 months ago
parent
commit
cf21823184
72 changed files with 2385 additions and 1371 deletions
  1. BIN
      api/SoilAcidification.db
  2. 0 442
      api/api_db.py
  3. 152 134
      api/app/routes.py
  4. BIN
      api/environment.yml
  5. BIN
      api/pkl/rf_model_0111_1755.pkl
  6. 11 1
      api/run.py
  7. 90 0
      api/ssl/cert.crt
  8. 27 0
      api/ssl/cert.key
  9. BIN
      api/uploads/4cI7d3IK2Hb56df564d5dc154b33617d559170859584.xlsx
  10. 31 22
      app.json
  11. BIN
      assets/taddar/介绍.png
  12. BIN
      assets/taddar/团队介绍@1x.png
  13. BIN
      assets/taddar/成果1.png
  14. BIN
      assets/taddar/模型训练.png
  15. BIN
      assets/taddar/模型选择.png
  16. BIN
      assets/taddar/阈值.png
  17. BIN
      assets/taddar/项目.png
  18. 1 1
      components/shoping-tabbar/index.js
  19. 57 0
      pages/Home/Home.js
  20. 8 0
      pages/Home/Home.json
  21. 21 0
      pages/Home/Home.wxml
  22. 58 0
      pages/Home/Home.wxss
  23. 66 0
      pages/Model/Model Selection.js
  24. 3 0
      pages/Model/Model Selection.json
  25. 2 0
      pages/Model/Model Selection.wxml
  26. 1 0
      pages/Model/Model Selection.wxss
  27. 66 0
      pages/Model/ModelTrain.js
  28. 3 0
      pages/Model/ModelTrain.json
  29. 2 0
      pages/Model/ModelTrain.wxml
  30. 1 0
      pages/Model/ModelTrain.wxss
  31. 1 1
      pages/Staff/Staff.js
  32. 71 75
      pages/Visualizatio/Visualizatio.js
  33. 1 1
      pages/Visualizatio/Visualizatio.json
  34. 6 15
      pages/Visualizatio/Visualizatio.wxml
  35. 72 46
      pages/Visualizatio/Visualizatio.wxss
  36. 66 73
      pages/Visualization/Visualization.js
  37. 1 1
      pages/Visualization/Visualization.json
  38. 24 60
      pages/Visualization/Visualization.wxml
  39. 72 46
      pages/Visualization/Visualization.wxss
  40. 3 3
      pages/threshold/threshold.js
  41. 1 1
      pages/threshold/threshold.json
  42. 14 7
      pages/threshold/threshold.wxml
  43. 52 25
      pages/threshold/threshold.wxss
  44. 3 2
      project.config.json
  45. 1 1
      project.private.config.json
  46. 48 41
      shoping/AcidNeutralizationModel/AcidNeutralizationModel.js
  47. 14 21
      shoping/AcidNeutralizationModel/AcidNeutralizationModel.wxml
  48. 2 2
      shoping/Calculation/Calculation.js
  49. 13 13
      shoping/Calculation/Calculation.wxml
  50. 104 0
      shoping/Model Selection/Model Selection.js
  51. 6 0
      shoping/Model Selection/Model Selection.json
  52. 21 0
      shoping/Model Selection/Model Selection.wxml
  53. 21 0
      shoping/Model Selection/Model Selection.wxss
  54. 125 0
      shoping/ModelTrain/ModelTrain.js
  55. 6 0
      shoping/ModelTrain/ModelTrain.json
  56. 50 0
      shoping/ModelTrain/ModelTrain.wxml
  57. 141 0
      shoping/ModelTrain/ModelTrain.wxss
  58. 1 1
      shoping/ResearchFindings/ResearchFindings.wxml
  59. 331 46
      shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.js
  60. 3 1
      shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.json
  61. 17 2
      shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxml
  62. 24 1
      shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxss
  63. 244 208
      shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js
  64. 10 3
      shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml
  65. 4 1
      shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxss
  66. 10 33
      shoping/Soil Acidification/Soil Acidification.wxml
  67. 12 35
      shoping/Soil Deacidification/Soil Deacidification.wxml
  68. 1 6
      shoping/Unit Team Profile/Unit Team Profile.wxml
  69. 55 0
      shoping/thres/thres.js
  70. 6 0
      shoping/thres/thres.json
  71. 53 0
      shoping/thres/thres.wxml
  72. 75 0
      shoping/thres/thres.wxss

BIN
api/SoilAcidification.db


+ 0 - 442
api/api_db.py

@@ -1,442 +0,0 @@
-import os
-import sqlite3
-from flask import Flask, jsonify, request, send_file, g
-from werkzeug.utils import secure_filename
-from flask_cors import CORS
-import pandas as pd
-from io import BytesIO
-import logging
-
-app = Flask(__name__)
-
-# 设置数据库文件和上传文件夹的路径
-DATABASE = 'SoilAcidification.db'
-UPLOAD_FOLDER = 'uploads'
-app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
-os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
-
-# 跨源资源共享(CORS)配置,允许跨域请求
-CORS(app)
-
-# 设置日志
-logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-# 创建一个数据库连接池
-def get_db():
-    db = getattr(g, '_database', None)
-    if db is None:
-        db = g._database = sqlite3.connect(DATABASE)
-    return db
-
-# 获取列名的函数
-def get_column_names(table_name):
-    db = get_db()
-    cur = db.cursor()
-    cur.execute(f"PRAGMA table_info({table_name});")
-    columns = [column[1] for column in cur.fetchall()]
-    db.close()
-    return columns
-
-@app.after_request
-def add_headers(response):
-    response.headers['Content-Type'] = 'application/json; charset=utf-8'
-    return response
-
-@app.route('/')
-def index():
-    return 'Hello, World!'
-
-# 有这个函数来获取列名
-def get_column_names(table_name):
-    """
-    根据表名获取对应的列名(字段)。
-    """
-    if table_name == 'current_reflux':
-        # 返回列名映射
-        return ['OM', 'CL', 'CEC', 'H_plus', 'HN', 'Al3_plus', 'Free_alumina', 'Free_iron_oxides', 'Delta_pH'], \
-               ["有机质含量", "土壤粘粒重量", "阳离子交换量", "氢离子含量", "硝态氮含量", "铝离子含量", "游离氧化铝", "游离氧化铁", "酸碱差值"]
-    elif table_name == 'current_reduce':
-        # 返回列名映射
-        return ['Q_over_b', 'pH', 'OM', 'CL', 'H', 'Al'], \
-               ["比值", "酸碱度", "有机质含量", "氯离子含量", "氢离子含量", "铝离子含量"]
-    else:
-        return [], []  # 不支持的表名,返回空列表
-
-# 模板下载接口
-@app.route('/download_template', methods=['GET'])
-def download_template():
-        """
-        根据表名生成模板文件(Excel 或 CSV),并返回下载。
-        :return: 返回模板文件
-        """
-        table_name = request.args.get('table')  # 从请求参数中获取表名
-        if not table_name:
-            return jsonify({'error': '表名参数缺失'}), 400
-
-        # 获取表的列名
-        try:
-            column_names = get_column_names(table_name)
-        except sqlite3.Error as e:
-            return jsonify({'error': f'获取列名失败: {str(e)}'}), 400
-
-        if not column_names:
-            return jsonify({'error': f'未找到表 {table_name} 或列名为空'}), 400
-
-        # 根据表名映射列名(可自定义标题)
-        if table_name == 'current_reflux':
-            column_titles = ["有机质含量", "土壤粘粒重量", "阳离子交换量", "氢离子含量", "硝态氮含量", "铝离子含量",
-                             "游离氧化铝", "游离氧化铁", "酸碱差值"]
-        elif table_name == 'current_reduce':
-            column_titles = ["比值", "酸碱度", "有机质含量", "氯离子含量", "氢离子含量1", "铝离子含量1"]
-        else:
-            return jsonify({'error': f'不支持的表名 {table_name}'}), 400
-
-        # 使用 pandas 创建一个空的 DataFrame,列名为自定义的标题
-        df = pd.DataFrame(columns=column_titles)
-
-        # 根据需求选择模板格式:Excel 或 CSV
-        file_format = request.args.get('format', 'excel').lower()
-        if file_format == 'csv':
-            # 将 DataFrame 写入内存中的 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:
-            # 默认生成 Excel 文件
-            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')
-
-# 模板导入接口
-@app.route('/import_data', methods=['POST'])
-def import_data():
-    """
-    导入数据接口,根据表名将 Excel 或 CSV 数据导入到对应的数据库表中。
-    """
-    # 检查是否有文件
-    if 'file' not in request.files:
-        logging.error("请求中没有文件")
-        return jsonify({'success': False, 'message': '文件缺失'}), 400
-
-    file = request.files['file']
-    table_name = request.form.get('table')
-
-    # 检查表名参数
-    if not table_name:
-        logging.error("缺少表名参数")
-        return jsonify({'success': False, 'message': '缺少表名参数'}), 400
-
-    # 检查文件是否为空
-    if file.filename == '':
-        logging.error("文件为空")
-        return jsonify({'success': False, 'message': '未选择文件'}), 400
-
-    # 获取表的字段映射
-    db_columns, display_titles = get_column_names(table_name)
-
-    # 校验是否支持该表名
-    if not db_columns:
-        logging.error(f"不支持的表名: {table_name}")
-        return jsonify({'success': False, 'message': f'不支持的表名: {table_name}'}), 400
-
-    logging.info(f"表名 {table_name} 对应的字段: {db_columns}")
-
-    # 中文列名到数据库列名的映射
-    column_name_mapping = {
-        "有机质含量": "OM",
-        "土壤粘粒重量": "CL",
-        "阳离子交换量": "CEC",
-        "氢离子含量": "H_plus",
-        "硝态氮含量": "HN",
-        "铝离子含量": "Al3_plus",
-        "游离氧化铝": "Free_alumina",
-        "游离氧化铁": "Free_iron_oxides",
-        "酸碱差值": "Delta_pH",
-        "比值": "Q_over_b",
-        "酸碱度": "pH",
-        "氯离子含量": "CL",
-        "氢离子含量1": "H",
-        "铝离子含量1": "Al"
-    }
-
-    try:
-        # 保存上传文件到临时目录
-        temp_path = os.path.join(app.config['UPLOAD_FOLDER'], secure_filename(file.filename))
-        logging.info(f"正在保存文件到临时路径: {temp_path}")
-        file.save(temp_path)
-
-        # 读取文件内容(支持 Excel 和 CSV 格式)
-        if file.filename.endswith('.xlsx'):
-            logging.info(f"读取 Excel 文件: {file.filename}")
-            df = pd.read_excel(temp_path)
-        elif file.filename.endswith('.csv'):
-            logging.info(f"读取 CSV 文件: {file.filename}")
-            df = pd.read_csv(temp_path)
-        else:
-            logging.error("仅支持 Excel 和 CSV 文件")
-            return jsonify({'success': False, 'message': '仅支持 Excel 和 CSV 文件'}), 400
-
-        logging.info(f"文件读取成功,列名: {df.columns.tolist()}")
-
-        # 1. 移除“序号”列(如果存在)
-        if '序号' in df.columns:
-            df = df.drop(columns=['序号'])
-
-        # 2. 将文件中的列名(中文)转换为数据库列名(英文)
-        df.columns = [column_name_mapping.get(col, col) for col in df.columns]
-
-        logging.info(f"转换后的列名: {df.columns.tolist()}")
-
-        # 校验文件的列是否与数据库表字段匹配
-        if not set(db_columns).issubset(set(df.columns)):
-            logging.error(f"文件列名与数据库表不匹配. 文件列: {df.columns.tolist()}, 期望列: {db_columns}")
-            return jsonify({'success': False, 'message': '文件列名与数据库表不匹配'}), 400
-
-        logging.info("开始清洗数据:移除空行并按需要格式化")
-        # 数据清洗:移除空行并按需要格式化
-        df_cleaned = df[db_columns].dropna()
-        logging.info(f"清洗后数据行数: {len(df_cleaned)}")
-
-        # 插入数据到数据库
-        conn = get_db()
-        logging.info(f"开始将数据插入到表 {table_name} 中")
-
-        # 使用事务确保操作的一致性
-        with conn:
-            df_cleaned.to_sql(table_name, conn, if_exists='append', index=False)
-
-        logging.info(f"数据成功插入到表 {table_name} 中,插入行数: {len(df_cleaned)}")
-
-        # 删除临时文件
-        os.remove(temp_path)
-        logging.info(f"临时文件 {temp_path} 删除成功")
-
-        return jsonify({'success': True, 'message': '数据导入成功'}), 200
-
-    except Exception as e:
-        logging.error(f"导入失败: {e}", exc_info=True)  # 捕获堆栈信息,方便调试
-        return jsonify({'success': False, 'message': f'导入失败: {str(e)}'}), 500
-
-
-# 导出数据接口
-@app.route('/export_data', methods=['GET'])
-def export_data():
-    """
-    根据表名从数据库导出数据,并返回 Excel 或 CSV 格式的文件
-    :return: 返回文件
-    """
-    # 获取表名和文件格式
-    table_name = request.args.get('table')
-    if not table_name:
-        return jsonify({'error': '缺少表名参数'}), 400
-
-    file_format = request.args.get('format', 'excel').lower()  # 默认生成 Excel 文件
-
-    # 获取数据
-    conn = get_db()
-    query = f"SELECT * FROM {table_name};"  # 查询表中的所有数据
-    df = pd.read_sql(query, conn)
-    conn.close()
-
-    if file_format == 'csv':
-        # 将数据保存为 CSV 格式
-        output = BytesIO()
-        df.to_csv(output, index=False)
-        output.seek(0)
-        return send_file(output, as_attachment=True, download_name=f'{table_name}_data.csv',
-                         mimetype='text/csv')
-
-    else:
-        # 默认生成 Excel 格式
-        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}_data.xlsx',
-                         mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
-
-# 添加记录
-@app.route('/add_item', methods=['POST'])
-def add_item():
-    db = get_db()
-    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
-
-        cur = db.cursor()
-
-        # 动态构建 SQL 语句
-        columns = ', '.join(item_data.keys())
-        placeholders = ', '.join(['?'] * len(item_data))
-        sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
-        cur.execute(sql, tuple(item_data.values()))
-        db.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': 'Database integrity error', 'details': str(e)}), 409
-    except sqlite3.Error as e:
-        return jsonify({'error': 'Database error', 'details': str(e)}), 500
-    finally:
-        db.close()
-
-# 删除记录
-@app.route('/delete_item', methods=['POST'])
-def delete_item():
-    data = request.get_json()
-    table_name = data.get('table')
-    condition = data.get('condition')
-
-    # 日志:检查是否收到正确的参数
-    logging.debug(f"Received data: table={table_name}, condition={condition}")
-
-    if not table_name or not condition:
-        logging.error("缺少表名或条件参数")
-        return jsonify({"success": False, "message": "缺少表名或条件参数"}), 400
-
-    try:
-        key, value = condition.split('=')
-    except ValueError:
-        logging.error(f"条件格式错误,received condition: {condition}")
-        return jsonify({"success": False, "message": "条件格式错误,应为 'key=value'"}), 400
-
-    db = get_db()
-    cur = db.cursor()
-
-    # 日志:确认开始删除操作
-    logging.debug(f"Attempting to delete from {table_name} where {key} = {value}")
-
-    try:
-        cur.execute(f"DELETE FROM {table_name} WHERE {key} = ?", (value,))
-        db.commit()
-
-        # 日志:删除成功
-        logging.info(f"Record deleted from {table_name} where {key} = {value}")
-        return jsonify({"success": True, "message": "记录删除成功"}), 200
-    except sqlite3.Error as e:
-        db.rollback()
-        # 日志:捕获删除失败的错误
-        logging.error(f"Failed to delete from {table_name} where {key} = {value}. Error: {e}")
-        return jsonify({"success": False, "message": f"删除失败: {e}"}), 400
-
-# 更新记录
-@app.route('/update_item', methods=['PUT'])
-def update_record():
-    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']
-
-    if not item or next(iter(item.keys())) is None:
-        return jsonify({"success": False, "message": "记录数据为空"}), 400
-
-    id_key = next(iter(item.keys()))
-    record_id = item[id_key]
-    updates = {key: value for key, value in item.items() if key != id_key}
-
-    db = get_db()
-    cur = db.cursor()
-
-    try:
-        record_id = int(record_id)
-    except ValueError:
-        return jsonify({"success": False, "message": "ID 必须是整数"}), 400
-
-    parameters = list(updates.values()) + [record_id]
-    set_clause = ','.join([f"{k} = ?" for k in updates.keys()])
-    sql = f"UPDATE {table_name} SET {set_clause} WHERE {id_key} = ?"
-
-    try:
-        cur.execute(sql, parameters)
-        db.commit()
-        if cur.rowcount == 0:
-            return jsonify({"success": False, "message": "未找到要更新的记录"}), 404
-        return jsonify({"success": True, "message": "数据更新成功"}), 200
-    except sqlite3.Error as e:
-        db.rollback()
-        return jsonify({"success": False, "message": f"更新失败: {e}"}), 400
-
-# 查询记录
-@app.route('/search/record', methods=['GET'])
-def sql_search():
-    try:
-        data = request.get_json()
-        sql_table = data['table']
-        record_id = data['id']
-
-        db = get_db()
-        cur = db.cursor()
-
-        sql = f"SELECT * FROM {sql_table} WHERE id = ?"
-        cur.execute(sql, (record_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()
-
-        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
-
-# 提供表格数据
-@app.route('/tables', methods=['POST'])
-def get_table():
-    data = request.get_json()
-    table_name = data.get('table')
-
-    if not table_name:
-        return jsonify({'error': '需要表名'}), 400
-
-    db = get_db()
-    try:
-        cur = db.cursor()
-        cur.execute(f"SELECT * FROM {table_name}")
-        rows = cur.fetchall()
-
-        if not rows:
-            return jsonify({'error': f'表 {table_name} 为空或不存在'}), 400
-
-        headers = [description[0] for description in cur.description]
-        return jsonify(rows=rows, headers=headers), 200
-    except sqlite3.Error as e:
-        return jsonify({'error': str(e)}), 400
-    finally:
-        db.close()
-
-if __name__ == '__main__':
-    app.run(debug=True)

+ 152 - 134
api/app/routes.py

@@ -1,27 +1,53 @@
+import os
+import logging
+import pickle
 import sqlite3
+
+import pandas as pd
 from flask import Blueprint, request, jsonify, current_app
+from sqlalchemy.orm import sessionmaker
 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
 from .database_models import Models, ModelParameters, Datasets, CurrentReduce, CurrentReflux
-import os
+from . import db
 from .utils import create_dynamic_table, allowed_file, infer_column_types, rename_columns_for_model_predict, \
     clean_column_names, rename_columns_for_model, insert_data_into_dynamic_table, insert_data_into_existing_table, \
     predict_to_Q
-from sqlalchemy.orm import sessionmaker
-import logging
-from sqlalchemy import text, select, MetaData, Table
+from sqlalchemy import text
+from sqlalchemy.engine.reflection import Inspector
+from sqlalchemy import MetaData, Table, select
 
 # 配置日志
 logging.basicConfig(level=logging.DEBUG)
 logger = logging.getLogger(__name__)
-# 创建蓝图 (Blueprint),用于分离路由
+
+# 创建蓝图 (Blueprint)
 bp = Blueprint('routes', __name__)
 
 
+def complex_reflux_calculation(parameters):
+    """
+    假设的复杂 'reflux' 计算函数
+    :param parameters: 输入的参数
+    :return: 计算结果
+    """
+    try:
+        # 加载已训练好的模型
+        with open('path_to_your_reflux_model.pkl', 'rb') as model_file:
+            model = pickle.load(model_file)
+
+        input_data = pd.DataFrame([parameters])  # 转换参数为DataFrame(假设是这种格式)
+        reflux_result = model.predict(input_data)  # 根据实际情况调用模型的 predict 方法
+
+        # 返回预测结果
+        return reflux_result.tolist()
+    except Exception as e:
+        logger.error('Failed to calculate reflux model prediction:', exc_info=True)
+        return {'error': str(e)}
+
+
 @bp.route('/upload-dataset', methods=['POST'])
 def upload_dataset():
+    session = None
     try:
         if 'file' not in request.files:
             return jsonify({'error': 'No file part'}), 400
@@ -38,6 +64,8 @@ def upload_dataset():
         # 创建 sessionmaker 实例
         Session = sessionmaker(bind=db.engine)
         session = Session()
+
+        # 创建新的数据集记录
         new_dataset = Datasets(
             Dataset_name=dataset_name,
             Dataset_description=dataset_description,
@@ -48,19 +76,20 @@ def upload_dataset():
         session.add(new_dataset)
         session.commit()
 
+        # 文件保存
         unique_filename = f"dataset_{new_dataset.Dataset_ID}.xlsx"
         upload_folder = current_app.config['UPLOAD_FOLDER']
         file_path = os.path.join(upload_folder, unique_filename)
         file.save(file_path)
 
+        # 处理数据集
         dataset_df = pd.read_excel(file_path)
         new_dataset.Row_count = len(dataset_df)
         new_dataset.Status = 'processed'
         session.commit()
 
-        # 清理列名
+        # 清理列名和数据处理
         dataset_df = clean_column_names(dataset_df)
-        # 重命名 DataFrame 列以匹配模型字段
         dataset_df = rename_columns_for_model(dataset_df, dataset_type)
 
         column_types = infer_column_types(dataset_df)
@@ -82,106 +111,168 @@ def upload_dataset():
         }), 201
 
     except Exception as e:
-        session.rollback()
-        logging.error('Failed to process the dataset upload:', exc_info=True)
+        if session:
+            session.rollback()
+        logger.error('Failed to process the dataset upload:', exc_info=True)
         return jsonify({'error': str(e)}), 500
     finally:
-        session.close()
+        if session:
+            session.close()
 
 
 @bp.route('/train-and-save-model', methods=['POST'])
 def train_and_save_model_endpoint():
-    # 创建 sessionmaker 实例
-    Session = sessionmaker(bind=db.engine)
-    session = Session()
-
+    session = None
     data = request.get_json()
-    # 从请求中解析参数
-    model_type = data.get('model_type')
-    model_name = data.get('model_name')
-    model_description = data.get('model_description')
-    data_type = data.get('data_type')
-    dataset_id = data.get('dataset_id', None)  # 默认为 None,如果未提供
 
     try:
+        # 获取请求中的数据
+        model_type = data.get('model_type')
+        model_name = data.get('model_name')
+        model_description = data.get('model_description')
+        data_type = data.get('data_type')
+        dataset_id = data.get('dataset_id', None)
+
+        # 创建 session
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
+
         # 调用训练和保存模型的函数
         result = train_and_save_model(session, model_type, model_name, model_description, data_type, dataset_id)
 
-        # 返回成功响应
         return jsonify({'message': 'Model trained and saved successfully', 'result': result}), 200
 
     except Exception as e:
-        session.rollback()
-        logging.error('Failed to process the dataset upload:', exc_info=True)
+        if session:
+            session.rollback()
+        logger.error('Failed to train and save model:', exc_info=True)
         return jsonify({'error': 'Failed to train and save model', 'message': str(e)}), 500
     finally:
-        session.close()
+        if session:
+            session.close()
 
+
+# 预测接口
 @bp.route('/predict', methods=['POST'])
 def predict_route():
-    # 创建 sessionmaker 实例
-    Session = sessionmaker(bind=db.engine)
-    session = Session()
+    session = None
     try:
         data = request.get_json()
-        model_id = data.get('model_id')          # 提取模型名称
-        parameters = data.get('parameters', {})  # 提取所有变量
+        model_id = data.get('model_id')
+        parameters = data.get('parameters', {})
+
+        # 创建 session
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
 
-        # 根据model_id获取模型Data_type
+        # 查询模型信息
         model_info = session.query(Models).filter(Models.ModelID == model_id).first()
         if not model_info:
             return jsonify({'error': 'Model not found'}), 404
+
         data_type = model_info.Data_type
+        input_data = pd.DataFrame([parameters])
 
-        input_data = pd.DataFrame([parameters])  # 转换参数为DataFrame
-        # 如果为reduce,则不需要传入target_ph
+        # 处理 'reduce' 类型模型
         if data_type == 'reduce':
-            # 获取传入的init_ph、target_ph参数
-            init_ph = float(parameters.get('init_pH', 0.0))  # 默认值为0.0,防止None导致错误
-            target_ph = float(parameters.get('target_pH', 0.0))  # 默认值为0.0,防止None导致错误
+            init_ph = float(parameters.get('init_pH', 0.0))
+            target_ph = float(parameters.get('target_pH', 0.0))
+            input_data = input_data.drop('target_pH', axis=1, errors='ignore')
 
-            # 从输入数据中删除'target_pH'列
-            input_data = input_data.drop('target_pH', axis=1, errors='ignore')  # 使用errors='ignore'防止列不存在时出错
+        # 通过模型类型重命名列以适配模型
+        input_data_rename = rename_columns_for_model_predict(input_data, data_type)
 
-        input_data_rename = rename_columns_for_model_predict(input_data, data_type)  # 重命名列名以匹配模型字段
-        predictions = predict(session, input_data_rename, model_id)  # 调用预测函数
+        # 使用当前活动的模型进行预测(假设根据 model_id 获取当前模型)
+        predictions = predict(session, input_data_rename, model_id)
 
+        # 如果是 reduce 类型,进行特定的转换
         if data_type == 'reduce':
             predictions = predictions[0]
-            # 将预测结果转换为Q
             predictions = predict_to_Q(predictions, init_ph, target_ph)
-            print(predictions)
 
         return jsonify({'result': predictions}), 200
+
     except Exception as e:
-        logging.error('Failed to predict:', exc_info=True)
+        logger.error('Failed to predict:', exc_info=True)
         return jsonify({'error': str(e)}), 400
+    finally:
+        if session:
+            session.close()
 
-# 为指定模型计算评分Performance_score,需要提供model_id
-@bp.route('/score-model/<int:model_id>', methods=['POST'])
-def score_model(model_id):
-    # 创建 sessionmaker 实例
-    Session = sessionmaker(bind=db.engine)
-    session = Session()
+# 切换模型接口
+@bp.route('/switch-model', methods=['POST'])
+def switch_model():
+    session = None
     try:
-        model_info = session.query(Models).filter(Models.ModelID == model_id).first()
-        if not model_info:
-            return jsonify({'error': 'Model not found'}), 404
+        data = request.get_json()
+        model_id = data.get('model_id')
+        model_name = data.get('model_name')
 
-        # 计算模型评分
-        score = calculate_model_score(model_info)
+        # 创建 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_info.Performance_score = score
+        # 更新模型状态(或其他切换逻辑)
+        # 假设此处是更新模型的某些字段来进行切换
+        model.status = 'active'  # 假设有一个字段记录模型状态
         session.commit()
 
-        return jsonify({'message': 'Model scored successfully', 'score': score}), 200
+        # 记录切换日志
+        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:
-        logging.error('Failed to process the dataset upload:', exc_info=True)
+        logger.error('Failed to switch model:', exc_info=True)
         return jsonify({'error': str(e)}), 400
     finally:
-        session.close()
+        if session:
+            session.close()
 
+@bp.route('/models', methods=['GET'])
+def get_models():
+    session = None
+    try:
+        # 创建 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
+        ]
+
+        return jsonify(models_list), 200
+
+    except Exception as e:
+        return jsonify({'error': str(e)}), 400
+    finally:
+        if session:
+            session.close()
 
 @bp.route('/delete-dataset/<int:dataset_id>', methods=['DELETE'])
 def delete_dataset(dataset_id):
@@ -225,47 +316,6 @@ def list_tables():
     table_names = inspector.get_table_names()  # 获取所有表名
     return jsonify(table_names)  # 以 JSON 形式返回表名列表
 
-
-@bp.route('/models/<int:model_id>', methods=['GET'])
-def get_model(model_id):
-    try:
-        model = Models.query.filter_by(ModelID=model_id).first()
-        if model:
-            return jsonify({
-                'ModelID': model.ModelID,
-                'ModelName': model.ModelName,
-                'ModelType': model.ModelType,
-                'CreatedAt': model.CreatedAt.strftime('%Y-%m-%d %H:%M:%S'),
-                'Description': model.Description
-            })
-        else:
-            return jsonify({'message': 'Model not found'}), 404
-    except Exception as e:
-        return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
-
-
-@bp.route('/models', methods=['GET'])
-def get_all_models():
-    try:
-        models = Models.query.all()  # 获取所有模型数据
-        if models:
-            result = [
-                {
-                    'ModelID': model.ModelID,
-                    'ModelName': model.ModelName,
-                    'ModelType': model.ModelType,
-                    'CreatedAt': model.CreatedAt.strftime('%Y-%m-%d %H:%M:%S'),
-                    'Description': model.Description
-                }
-                for model in models
-            ]
-            return jsonify(result)
-        else:
-            return jsonify({'message': 'No models found'}), 404
-    except Exception as e:
-        return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
-
-
 @bp.route('/model-parameters', methods=['GET'])
 def get_all_model_parameters():
     try:
@@ -286,38 +336,6 @@ def get_all_model_parameters():
     except Exception as e:
         return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
 
-
-@bp.route('/models/<int:model_id>/parameters', methods=['GET'])
-def get_model_parameters(model_id):
-    try:
-        model = Models.query.filter_by(ModelID=model_id).first()
-        if model:
-            # 获取该模型的所有参数
-            parameters = [
-                {
-                    'ParamID': param.ParamID,
-                    'ParamName': param.ParamName,
-                    'ParamValue': param.ParamValue
-                }
-                for param in model.parameters
-            ]
-            
-            # 返回模型参数信息
-            return jsonify({
-                'ModelID': model.ModelID,
-                'ModelName': model.ModelName,
-                'ModelType': model.ModelType,
-                'CreatedAt': model.CreatedAt.strftime('%Y-%m-%d %H:%M:%S'),
-                'Description': model.Description,
-                'Parameters': parameters
-            })
-        else:
-            return jsonify({'message': 'Model not found'}), 404
-    except Exception as e:
-        return jsonify({'error': 'Internal server error', 'message': str(e)}), 500
-
-
-
 # 定义添加数据库记录的 API 接口
 @bp.route('/add_item', methods=['POST'])
 def add_item():

BIN
api/environment.yml


BIN
api/pkl/rf_model_0111_1755.pkl


+ 11 - 1
api/run.py

@@ -1,8 +1,18 @@
+from flask import request
+
 from app import create_app
 import os
 # 创建 Flask 应用
 app = create_app()
+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__':
-    app.run(debug=True)
+    app.run(host="0.0.0.0", port=5000, debug=True, ssl_context=context)

+ 90 - 0
api/ssl/cert.crt

@@ -0,0 +1,90 @@
+-----BEGIN CERTIFICATE-----
+MIIGKTCCBRGgAwIBAgIQB9I4IYgi/DoHq8pW9zttPDANBgkqhkiG9w0BAQsFADBg
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMR8wHQYDVQQDExZSYXBpZFNTTCBUTFMgUlNBIENBIEcx
+MB4XDTI0MTIxNjAwMDAwMFoXDTI1MTIxNTIzNTk1OVowGTEXMBUGA1UEAxMOd3d3
+LnNvaWxnZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTLGyZ
+3aWgscSdeNFSdhR9U2trusoUbME5l0IkkI5isxu9CzCVk2WFz6S83wmKODyBOvEA
+koFz9nWwuVkt5gscyeT/VwlZ35O8PZC4JgoCO3+pMcCcMsZG8BQAPdpUL7wZ+MAL
+QHH+rvG3M9YoXF1WjyeR6i5YkduP4MQAPlTAuKkw6POFkKyeoER3rctFwrMESgku
+A1Ou2pb3b1rTNHQSYdqfrLSyuQji6c8vkf5kHC1SnX51bZ73OYAiqqcSaLyEHdVp
+I8j3BzRi8TnpUT0LQ2dFwy34P6gbRJ1bh2DdVVtOcb8UXYtZ3hGqGjxgmcuhnPp9
+gLntl8J5U5T2bqR9AgMBAAGjggMkMIIDIDAfBgNVHSMEGDAWgBQM22yCSQ9KZwq4
+FO56xEhSiOtWODAdBgNVHQ4EFgQUYOFvCobAc+ZrLcFoWlNjMamdX3QwJQYDVR0R
+BB4wHIIOd3d3LnNvaWxnZC5jb22CCnNvaWxnZC5jb20wPgYDVR0gBDcwNTAzBgZn
+gQwBAgEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BT
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+PwYDVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NkcC5yYXBpZHNzbC5jb20vUmFwaWRT
+U0xUTFNSU0FDQUcxLmNybDB2BggrBgEFBQcBAQRqMGgwJgYIKwYBBQUHMAGGGmh0
+dHA6Ly9zdGF0dXMucmFwaWRzc2wuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vY2Fj
+ZXJ0cy5yYXBpZHNzbC5jb20vUmFwaWRTU0xUTFNSU0FDQUcxLmNydDAMBgNVHRMB
+Af8EAjAAMIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdQAS8U40vVNyTIQGGcOP
+P3oT+Oe1YoeInG0wBYTr5YYmOgAAAZPPcnsuAAAEAwBGMEQCIDqXUfCVJlEtmrK8
+8tvUIwbMySU06nq+wyQFiAOI1Sg1AiARVyEOEG3/oszKNKVwLRvXzOui0KX2gpT/
+9JXVF/MNfAB3AO08S9boBsKkogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABk89y
+e1AAAAQDAEgwRgIhAOk1S1sKdkPJ69YpEHHO+y6gMlJGzesKKXCpDO1cI7DlAiEA
+1nHguQLPEPl7mFGV5NEO5/Jffxacy78VNYx/0DamwqYAdwDm0jFjQHeMwRBBBtdx
+uc7B0kD2loSG+7qHMh39HjeOUAAAAZPPcnthAAAEAwBIMEYCIQCoLOF4iNFYvWRk
+gK5t4ixr/VUdKhzLBkH/bj1E5g1c1AIhAMaoG7W6gT5aWcggnkeSS7r8HGKa5RX0
+HsZyREciflYTMA0GCSqGSIb3DQEBCwUAA4IBAQCHElF8j9l9mzkOoBGBqOY4aBYk
+578kAvR/TYAZ7/wes0k2fxoAZKGOUT/SOKLsnr5xFrDzSwVrKWhSUhr6wCzcW91/
+6f19fcmlLFmu7TJrEmau6F6aKGyUEKwb/q9OPDFC3qMQ5LYypbIHVG4Bfc1HmIHh
+aS97u8rFbwqbAWBUtQSKk9YYhA/EknTIB14QIGPrle3LZSIbPQiA4oh14ia7rfe+
+D0ZXmY9zAXqCaSr7wOxvVQqzJXnVQQ39w91vuS9RmBp7OkOysxWexFx5ij98qL2J
+VndSYjYLDYkxzLEFB4gNM2Hnum81uLhT4zyeJX6z8kBCAnojReIFuRXq5VAO
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEszCCA5ugAwIBAgIQCyWUIs7ZgSoVoE6ZUooO+jANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xNzExMDIxMjI0MzNaFw0yNzExMDIxMjI0MzNaMGAxCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xHzAdBgNVBAMTFlJhcGlkU1NMIFRMUyBSU0EgQ0EgRzEwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQC/uVklRBI1FuJdUEkFCuDL/I3aJQiaZ6aibRHj
+ap/ap9zy1aYNrphe7YcaNwMoPsZvXDR+hNJOo9gbgOYVTPq8gXc84I75YKOHiVA4
+NrJJQZ6p2sJQyqx60HkEIjzIN+1LQLfXTlpuznToOa1hyTD0yyitFyOYwURM+/CI
+8FNFMpBhw22hpeAQkOOLmsqT5QZJYeik7qlvn8gfD+XdDnk3kkuuu0eG+vuyrSGr
+5uX5LRhFWlv1zFQDch/EKmd163m6z/ycx/qLa9zyvILc7cQpb+k7TLra9WE17YPS
+n9ANjG+ECo9PDW3N9lwhKQCNvw1gGoguyCQu7HE7BnW8eSSFAgMBAAGjggFmMIIB
+YjAdBgNVHQ4EFgQUDNtsgkkPSmcKuBTuesRIUojrVjgwHwYDVR0jBBgwFoAUTiJU
+IBiV5uNu5g/6+rkS7QYXjzkwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsG
+AQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMDQGCCsGAQUFBwEB
+BCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEIGA1Ud
+HwQ7MDkwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEds
+b2JhbFJvb3RHMi5jcmwwYwYDVR0gBFwwWjA3BglghkgBhv1sAQEwKjAoBggrBgEF
+BQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzALBglghkgBhv1sAQIw
+CAYGZ4EMAQIBMAgGBmeBDAECAjANBgkqhkiG9w0BAQsFAAOCAQEAGUSlOb4K3Wtm
+SlbmE50UYBHXM0SKXPqHMzk6XQUpCheF/4qU8aOhajsyRQFDV1ih/uPIg7YHRtFi
+CTq4G+zb43X1T77nJgSOI9pq/TqCwtukZ7u9VLL3JAq3Wdy2moKLvvC8tVmRzkAe
+0xQCkRKIjbBG80MSyDX/R4uYgj6ZiNT/Zg6GI6RofgqgpDdssLc0XIRQEotxIZcK
+zP3pGJ9FCbMHmMLLyuBd+uCWvVcF2ogYAawufChS/PT61D9rqzPRS5I2uqa3tmIT
+44JhJgWhBnFMb7AGQkvNq9KNS9dd3GWc17H/dXa1enoxzWjE0hBdFjxPhUb0W3wi
+8o34/m8Fxw==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEfjCCA2agAwIBAgIQD+Ayq4RNAzEGxQyOE8iwaDANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
+QTAeFw0yNDAxMTgwMDAwMDBaFw0zMTExMDkyMzU5NTlaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo4IBMDCC
+ASwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUTiJUIBiV5uNu5g/6+rkS7QYX
+jzkwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQD
+AgGGMHQGCCsGAQUFBwEBBGgwZjAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZGln
+aWNlcnQuY24wPwYIKwYBBQUHMAKGM2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNu
+L0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBABgNVHR8EOTA3MDWgM6Axhi9odHRw
+Oi8vY3JsLmRpZ2ljZXJ0LmNuL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDARBgNV
+HSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAHRBl3jN7+XHBUK0dZnu
+hMdoNwD1nCROU3BTIh1TNzRI0bQ0m5+C/dCRzzlqoSAFHUlOi+OiDltWkXTzmQn6
+Z8bH5PFBy5sYpc/8cNPoSzhyqcpvvEZvv/Ivc0Up+dzma7vBDJC9WrMRUUlSFSQp
+kdXSmphDNkXJsgARmxzc18IN6LYMRiOWlY7RE2F900pPW60BvJHHNCX0bbSRj/Ql
+bmVq8wuftBD++D+RS8K++ujpMjFBROyWfBX+woQDGsMazkmgulQdnZrdj476elOL
+axRvrSgEorju1kJM7M65z2RUZrfzQYW/1rs8mRUXin6iEtad/Rv1ZI1WGYmWPyBm
+pbo=
+-----END CERTIFICATE-----

+ 27 - 0
api/ssl/cert.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAkyxsmd2loLHEnXjRUnYUfVNra7rKFGzBOZdCJJCOYrMbvQsw
+lZNlhc+kvN8Jijg8gTrxAJKBc/Z1sLlZLeYLHMnk/1cJWd+TvD2QuCYKAjt/qTHA
+nDLGRvAUAD3aVC+8GfjAC0Bx/q7xtzPWKFxdVo8nkeouWJHbj+DEAD5UwLipMOjz
+hZCsnqBEd63LRcKzBEoJLgNTrtqW929a0zR0EmHan6y0srkI4unPL5H+ZBwtUp1+
+dW2e9zmAIqqnEmi8hB3VaSPI9wc0YvE56VE9C0NnRcMt+D+oG0SdW4dg3VVbTnG/
+FF2LWd4Rqho8YJnLoZz6fYC57ZfCeVOU9m6kfQIDAQABAoIBACzs6yve5o/SpB+j
+wVYIOIH3RHvKtaQdfLBdVXH+9I/5d7ax4z9NkriRpYqIFQvt1b5nJSlVD5I8AjoT
+oj7qEatUkARH59FY9+qG6pXj+rsquXIG/3JO54rA/4IBX5PCTIurDMiCgCnL8npH
+pxCD/XQOV/hpQYOE5r6ZUIt4e3umjDKgCGBwwNM622HDA2urhs/UWUNRWHVqI73E
+fY3MZjmliOvcVS0e8UIogKCu9d16tZm58tk8nhgO+WrOoXSLQPG82CrjBNu+zcFL
+R8uOX726MORav2CxLOdzKQqdeB3eNVhF5sgKUbMNcDKIkbpiDpg8z65odmBDiJO8
+Ks+Fd2sCgYEAtfRYNaxkHE2Rncb/RiTFktGucCsx2LJHXzRyMToZylxN3JNdQRI7
+qStAMh1mskROJr3sW5VSk8MJIsrGF5MEuwrpC91lWu5dDdcZey9BjukGeZ2zPME+
+uQapa0e0Gt/S4zYMSdy2odA6VbcP8XONF07HNf/eEjTk6YSU4x7h/kMCgYEAzxCr
+pw7iuYI7VcOBtfh+t0mexsTjaUD28FU5YO0lSKFRodTis0b2y3tRQbBdT9EZXSzp
+bvK38zIGg5hFc78IAW9oJAolJoLMcFEUsy4Y+r0Th7tgdGlbbEk7Hqe+/pGcqSnk
+cWyJ37y4rCPCZ7Sz/JaCqxFX41mlw97SR3S+hj8CgYAWmuqykF5HneNvZJ+mST86
+hE5VpSgmMc/oJg3hy8QUfe82biBxyyAaXkM1dq2hjBbhfUr1/dEqhlqGNgbyOtOl
+oS9ex/yllWN+KrEButc8N/sT9OGltKRkPE2kBF590DzU+YwBSShvZf05VOroJmDq
++mCJduZSALxHvq74oIeXHQKBgQDFe3i6k4/YrUQ5v4RuMNlppWUw3YamU7cv75gR
+wRrV8kUQuyCbKwsldi9BHxtfjGVMYBEiJ4sY/pUH3Kogggj4lXAVLzfPSuAAHtRH
+L6fTO5Ds2uBbJkBzPkSMMCAQWn+3NqURKBs2r57RTimTDty6AjINRTU1N8LI1DJo
+xAcRsQKBgEqCjoKFFHsH97tdtZeUXwbv9BZMzS/qUa+AvazqzpexhnOzlLVbKJzI
+HXLvAsm9VpzoRr7HKBLO3bOfmULhuO5r9Plu2L05tEPjJ8fkQ3YmBUKFGPTFKosA
+q0qdZLhofebh+HFlc5cBqU0MC9+JOgp2wdOpZGrshqKIua8PUxNc
+-----END RSA PRIVATE KEY-----

BIN
api/uploads/4cI7d3IK2Hb56df564d5dc154b33617d559170859584.xlsx


+ 31 - 22
app.json

@@ -1,28 +1,37 @@
 {
   "pages": [
-    "shoping/Home/Home",
-    "pages/admin/admin",
-    "pages/Register/Register",
+    "pages/Home/Home",
     "pages/threshold/threshold",
-    "pages/ModelTrain/ModelTrain",
-    "pages/Model Selection/Model Selection",
-    "pages/thres/thres",
-    "pages/Visualization/Visualization",
+    "pages/Register/Register",
+    "pages/admin/admin",
     "pages/Visualizatio/Visualizatio",
-    "pages/Staff/Staff",
-    "shoping/SoilPro/SoilPro",
-    "shoping/Overview/Overview",
-    "shoping/ResearchFindings/ResearchFindings",
-    "shoping/Unit Team Profile/Unit Team Profile",
-    "shoping/Soil Acidification/Soil Acidification",
-    "shoping/Calculation/Calculation",
-    "shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution",
-    "shoping/Soil Deacidification/Soil Deacidification",
-    "shoping/AcidNeutralizationModel/AcidNeutralizationModel",
-    "shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution",
-    "shoping/Staff/Staff",
-    "shoping/EditProfile/EditProfile",
-    "pages/Result/Result"
+    "pages/Visualization/Visualization",
+    "pages/Staff/Staff"
+  ],
+  "subPackages": [
+    {
+      "root": "shoping",
+      "pages": [
+        "thres/thres",
+        "SoilPro/SoilPro",
+        "ModelTrain/ModelTrain",
+        "Model Selection/Model Selection",
+        "Overview/Overview",
+        "ResearchFindings/ResearchFindings",
+        "Unit Team Profile/Unit Team Profile",
+        "Soil Acidification/Soil Acidification",
+        "Calculation/Calculation",
+        "Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution",
+        "Soil Deacidification/Soil Deacidification",
+        "AcidNeutralizationModel/AcidNeutralizationModel",
+        "Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution",
+        "EditProfile/EditProfile"
+      ]
+    },
+    {
+      "root": "pages/Result",
+      "pages": ["Result"]
+    }
   ],
   "window": {
     "backgroundTextStyle": "light",
@@ -63,4 +72,4 @@
     ]
   },
   "sitemapLocation": "sitemap.json"
-}
+}

BIN
assets/taddar/介绍.png


BIN
assets/taddar/团队介绍@1x.png


BIN
assets/taddar/成果1.png


BIN
assets/taddar/模型训练.png


BIN
assets/taddar/模型选择.png


BIN
assets/taddar/阈值.png


BIN
assets/taddar/项目.png


+ 1 - 1
components/shoping-tabbar/index.js

@@ -18,7 +18,7 @@ Component({
     selectedColor: "#3cc51f",
     list: [
       {
-        pagePath: "/shoping/Home/Home",
+        pagePath: "/pages/Home/Home",
         text: "首页",
         iconPath: "/assets/taddar/首页 (1).png",
         selectedIconPath: "/assets/taddar/首页 (2).png"

+ 57 - 0
pages/Home/Home.js

@@ -0,0 +1,57 @@
+// pages/Home/Home.js
+Page({
+  data: {
+    selected: 0
+  },
+
+  // 页面显示时的逻辑
+  onShow: function() {
+    // 检查缓存中的登录用户
+    const currentUser = wx.getStorageSync('currentUser');
+    
+    // 如果用户已登录,跳转到 /pages/threshold/threshold 页面
+    if (currentUser) {
+      wx.switchTab({
+        url: '/pages/threshold/threshold'
+      });
+      return;  // 停止后续代码执行
+    }
+
+    // 如果没有登录,则正常显示页面内容
+    if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+      this.getTabBar().setData({
+        selected: 0  // 当前页面在tabBar中的索引
+      });
+    }
+
+    // 隐藏返回首页按钮
+    if (wx.hideHomeButton) {
+      wx.hideHomeButton();
+    }
+  },
+
+  // 其他跳转方法
+  SoilPro() {
+    wx.navigateTo({
+      url: '/shoping/SoilPro/SoilPro'
+    });
+  },
+
+  Overview: function() {
+    wx.navigateTo({
+      url: '/shoping/Overview/Overview'
+    });
+  },
+
+  ResearchFindings() {
+    wx.navigateTo({
+      url: '/shoping/ResearchFindings/ResearchFindings'
+    });
+  },
+
+  Unit_Team_Profile() {
+    wx.navigateTo({
+      url: '/shoping/Unit Team Profile/Unit Team Profile'
+    });
+  }
+});

+ 8 - 0
pages/Home/Home.json

@@ -0,0 +1,8 @@
+{
+  "usingComponents": {
+    "nav-tabar": "/components/shoping-tabbar/index"
+  },
+  "navigationBarTitleText": "首页",
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"  
+}

+ 21 - 0
pages/Home/Home.wxml

@@ -0,0 +1,21 @@
+<view class="container">
+  <view class="card" bindtap="SoilPro">
+    <image class="card-icon" src="/assets/taddar//介绍.png" />
+    <text class="card-title">软件简介</text>
+  </view>
+  <view class="card" bindtap="Overview">
+    <image class="card-icon" src="/assets/taddar//项目.png" />
+    <text class="card-title">项目简介</text>
+  </view>
+  <view class="card" bindtap="ResearchFindings">
+    <image class="card-icon" src="/assets/taddar//成果1.png" />
+    <text class="card-title">研究成果</text>
+  </view>
+  <view class="card" bindtap="Unit_Team_Profile">
+    <image class="card-icon" src="/assets/taddar//团队介绍@1x.png" />
+    <text class="card-title">承担单位团队信息</text>
+  </view>
+</view>
+
+<!-- 底部导航栏 -->
+<nav-tabar selected="{{selected}}"></nav-tabar>

+ 58 - 0
pages/Home/Home.wxss

@@ -0,0 +1,58 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  padding: 20px;
+  background-color: #f5f5f5;
+  margin-top: 60px;
+}
+
+.card {
+  display: flex; /* 使用 flexbox 布局 */
+  align-items: center;
+  width: 90%;
+  background-color: #ffffff;
+  padding: 20px;
+  border-radius: 12px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  transition: transform 0.2s ease;
+  cursor: pointer;
+}
+
+.card:hover {
+  transform: scale(1.05);
+}
+
+.card-icon {
+  width: 40px;
+  height: 40px;
+  margin-bottom: 20px;
+  margin-right: 90px; /* 与内容保持间距 */
+}
+
+.card-content {
+  flex: 1; /* 占据剩余空间,保证布局一致 */
+  text-align: center; /* 内容水平居中 */
+}
+
+.card-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+}
+
+.card:nth-child(1) {
+  background-color: #FFDDC1; /* 软件简介卡片颜色 */
+}
+
+.card:nth-child(2) {
+  background-color: #C1E1FF; /* 项目简介卡片颜色 */
+}
+
+.card:nth-child(3) {
+  background-color: #D4F4DD; /* 研究成果卡片颜色 */
+}
+
+.card:nth-child(4) {
+  background-color: #FFD700; /* 团队信息卡片颜色 */
+}

+ 66 - 0
pages/Model/Model Selection.js

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

+ 3 - 0
pages/Model/Model Selection.json

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

+ 2 - 0
pages/Model/Model Selection.wxml

@@ -0,0 +1,2 @@
+<!--pages/Model/Model Selection.wxml-->
+<text>pages/Model/Model Selection.wxml</text>

+ 1 - 0
pages/Model/Model Selection.wxss

@@ -0,0 +1 @@
+/* pages/Model/Model Selection.wxss */

+ 66 - 0
pages/Model/ModelTrain.js

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

+ 3 - 0
pages/Model/ModelTrain.json

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

+ 2 - 0
pages/Model/ModelTrain.wxml

@@ -0,0 +1,2 @@
+<!--pages/Model/ModelTrain.wxml-->
+<text>pages/Model/ModelTrain.wxml</text>

+ 1 - 0
pages/Model/ModelTrain.wxss

@@ -0,0 +1 @@
+/* pages/Model/ModelTrain.wxss */

+ 1 - 1
pages/Staff/Staff.js

@@ -124,7 +124,7 @@ Page({
 
     // 跳转到登录页面
     wx.reLaunch({
-      url: '/shoping/Home/Home' // 登录页面的路径
+      url: '/pages/Home/Home' // 登录页面的路径
     });
   }
 });

+ 71 - 75
pages/Visualizatio/Visualizatio.js

@@ -12,16 +12,17 @@ Page({
     // 新增数据弹窗的输入框内容
     newData: {
       id: "",
-      Q_over_b: "",
-      pH: "",
       OM: "",
       CL: "",
-      H: "",
-      Al: ""
+      CEC: "",
+      H_plus: "",
+      HN: "",
+      Al3_plus: "",
+      Delta_pH: ""
     },
     // 中文表头内容,动态生成
     tableHeaders: [
-      "序号", "比值", "酸碱度", "有机质含量", "氯离子含量",
+      "序号", "Q/ΔpH", "初始pH", "有机质含量", "氯离子含量",
       "氢离子含量","铝离子含量"
     ]
   },
@@ -33,67 +34,48 @@ Page({
 
   LoadData: function() {
     wx.request({
-      url: 'http://localhost:5000/table',
+      url: 'https://soilgd.com:5000/table',
       method: 'POST',
       header: {
         'Content-Type': 'application/json'
       },
       data: {
         table: 'current_reduce'
-    },
-    success: (res) => {
-      console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
-      
-      if (res.data && Array.isArray(res.data.rows)) {
-        const rows = res.data.rows.map(row => {
-          return {
+      },
+      success: (res) => {
+        console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
+        
+        if (res.data && Array.isArray(res.data.rows)) {
+          const rows = res.data.rows.map(row => {
+            return {
             'id': row.id,
-            'Q_over_b':  row.Q_over_b,
-            'pH': row.pH,
-            'OM': row.OM,
-            'CL': row.CL,
-            'H': row.H,
-            'Al': row.Al
-          };
-        });
-
-        this.setData({
-          rows: rows,
-          filteredRows: rows
-        });
-      } else {
+            'Q_over_b':  parseFloat(row.Q_over_b).toFixed(2),
+            'pH': parseFloat(row.pH).toFixed(2),
+            'OM': parseFloat(row.OM).toFixed(2),
+            'CL': parseFloat(row.CL).toFixed(2),
+            'H': parseFloat(row.H).toFixed(2),
+            'Al': parseFloat(row.Al).toFixed(2)
+            };
+          });
+  
+          this.setData({
+            rows: rows,
+            filteredRows: rows
+          });
+        } else {
+          wx.showToast({
+            title: '获取数据失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
         wx.showToast({
-          title: '获取数据失败',
+          title: '请求失败,请重试',
           icon: 'none'
         });
+        console.error('请求失败:', err);
       }
-    },
-    fail: (err) => {
-      wx.showToast({
-        title: '请求失败,请重试',
-        icon: 'none'
-      });
-      console.error('请求失败:', err);
-    }
-    });
-  },
-  // 搜索框绑定
-  shoppinginput: function(e) {
-    this.setData({
-      shoopingtext: e.detail.value
-    });
-  },
-
-  // 搜索功能:根据输入内容过滤表格数据
-  search: function() {
-    const searchText = this.data.shoopingtext.toLowerCase();
-    const filteredRows = this.data.rows.filter(item => {
-      return Object.values(item).some(value =>
-        String(value).toLowerCase().includes(searchText)
-      );
-    });
-    this.setData({
-      filteredRows: filteredRows
     });
   },
 
@@ -107,7 +89,7 @@ Page({
     const format = 'excel';  // 或者 'csv' 作为模板格式
   
     wx.request({
-      url: `http://localhost:5000/download_template?table=${tableName}&format=${format}`,
+      url: `https://soilgd.com:5000/download_template?table=${tableName}&format=${format}`,
       method: 'GET',
       responseType: 'arraybuffer',  // 处理二进制文件
       success: (res) => {
@@ -186,7 +168,7 @@ Page({
 
     // 向后端发送请求,获取表格数据并导出
     wx.request({
-      url: `http://localhost:5000/export_data?table=${tableName}&format=${fileFormat}`,
+      url: `https://soilgd.com:5000/export_data?table=${tableName}&format=${fileFormat}`,
       method: 'GET',
       responseType: 'arraybuffer',  // 处理二进制文件
       success: (res) => {
@@ -257,37 +239,51 @@ Page({
       type: 'file',
       extension: ['xlsx', 'csv'], // 允许上传的文件类型
       success: (res) => {
-        const filePath = res.tempFiles[0].path; // 获取文件临时路径
+        const filePath = res.tempFiles[0].path;
         const fileName = res.tempFiles[0].name;
-
-        wx.showLoading({
-          title: '上传中...',
-        });
-
+  
+        wx.showLoading({ title: '上传中...' });
+  
         // 上传文件到后端
         wx.uploadFile({
-          url: 'http://localhost:5000/import_data', // 后端处理导入的接口
+          url: 'https://soilgd.com:5000/import_data', // 后端接口
           filePath: filePath,
           name: 'file',
           formData: {
-            table: this.data.tableName, // 表名,后端根据需要解析数据
+            table: this.data.tableName, // 表名
             fileName: fileName
           },
           success: (res) => {
             wx.hideLoading();
             const responseData = JSON.parse(res.data);
-
+  
             if (responseData.success) {
-              wx.showToast({
-                title: '导入成功',
-                icon: 'success'
+              // 获取后端返回的结果
+              const { total_data, new_data, duplicate_data, duplicate_details } = responseData;
+  
+              // 显示导入结果
+              wx.showModal({
+                title: '导入结果',
+                content: `总数据: ${total_data} 条\n新增: ${new_data} 条\n重复: ${duplicate_data} 条`,
+                success: () => {
+                  // 如果有重复数据,跳转到重复数据详情页面
+                  if (duplicate_data > 0) {
+                    wx.navigateTo({
+                      url: '/pages/duplicateDetails/duplicateDetails', // 重复数据详情页面
+                      success: (page) => {
+                        // 将重复数据传递给详情页面
+                        page.setData({ duplicateDetails: duplicate_details });
+                      }
+                    });
+                  }
+                }
               });
-
+  
               // 重新加载数据
               this.LoadData();
             } else {
               wx.showToast({
-                title: '导入失败,请检查文件格式',
+                title: responseData.message || '导入失败,请检查文件格式',
                 icon: 'none'
               });
             }
@@ -306,7 +302,7 @@ Page({
         console.error('文件选择失败:', err);
       }
     });
-  },
+  },  
 
   // 新增按钮点击,显示新增弹窗
   onAdd: function() {
@@ -369,7 +365,7 @@ Page({
     }
 
     wx.request({
-      url: 'http://localhost:5000/add_item',
+      url: 'https://soilgd.com:5000/add_item',
       method: 'POST',
       header: {
         'Content-Type': 'application/json'
@@ -449,7 +445,7 @@ onSubmitEdit: function() {
   }
 
   wx.request({
-    url: 'http://localhost:5000/update_item',
+    url: 'https://soilgd.com:5000/update_item',
     method: 'PUT',
     header: {
       'Content-Type': 'application/json'
@@ -498,7 +494,7 @@ onSubmitEdit: function() {
   
     // 发送 DELETE 请求
     wx.request({
-      url: 'http://127.0.0.1:5000/delete_item',  // 后端接口地址
+      url: 'https://soilgd.com:5000/delete_item',  // 后端接口地址
       method: 'POST',  // 使用 POST 请求
       data: {
         table: 'current_reduce',  // 目标表

+ 1 - 1
pages/Visualizatio/Visualizatio.json

@@ -1,6 +1,6 @@
 {
   "usingComponents": { },
-  "navigationBarTitleText": "数据展示",
+  "navigationBarTitleText": "降酸模型数据管理",
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"
 }

+ 6 - 15
pages/Visualizatio/Visualizatio.wxml

@@ -1,14 +1,5 @@
 <!-- 固定顶部区域 -->
 <view class="top-container">
-  <view class="top">
-    <view class="topsearch">
-      <view class="frame">
-        <input value="{{shoopingtext}}" placeholder="请输入搜索内容" bindinput="shoppinginput" />
-      </view>
-      <text bindtap="search">搜索</text>
-    </view>
-  </view>
-
   <view class="add-btn-container">
   <button class="add-btn" bindtap="onImport">导入</button>
   <button class="add-btn" bindtap="onExport">导出</button>
@@ -53,31 +44,31 @@
   <view class="modal-content">
 
     <view class="modal-item">
-    <view class="item-label">比值:</view>
+    <view class="item-label">土壤提升单位pH的生石灰施用量%:</view>
     <input class="input-field" placeholder="比值" data-field="Q_over_b" bindinput="onInputQ_over_b" value="{{newData.Q_over_b}}"/>
   </view>
     <view class="modal-item">
-    <view class="item-label">酸碱度:</view>
+    <view class="item-label">土壤初始含量:</view>
     <input class="input-field" placeholder="请输入酸碱度" data-field="pH" bindinput="onInputpH" value="{{newData.pH}}"/>
   </view>
 
     <view class="modal-item">
-    <view class="item-label">有机含量:</view>
+    <view class="item-label">土壤有机含量:</view>
     <input class="input-field" placeholder="请输入有机质含量" data-field="OM" bindinput="onInputOM" value="{{newData.OM}}"/>
   </view>
 
     <view class="modal-item">
-    <view class="item-label">氯离子含量:</view>
+    <view class="item-label">土壤粘粒含量:</view>
     <input class="input-field" placeholder="请输入氯离子含量" data-field="CL" bindinput="onInputCL" value="{{newData.CL}}"/>
   </view>
 
     <view class="modal-item">
-    <view class="item-label">氢离子含量:</view>
+    <view class="item-label">交换性氢含量:</view>
     <input class="input-field" placeholder="请输入H含量" data-field="H" bindinput="onInputH" value="{{newData.H}}"/>
   </view>
 
     <view class="modal-item">
-    <view class="item-label">铝离子含量:</view>
+    <view class="item-label">交换性铝含量:</view>
     <input class="input-field" placeholder="请输入铝离子含量" data-field="Al" bindinput="onInputAl" value="{{newData.Al}}"/>
   </view>
 

+ 72 - 46
pages/Visualizatio/Visualizatio.wxss

@@ -75,7 +75,7 @@
 /* 表格单元格样式 */
 .table-cell {
   flex: none;
-  width: 250rpx; /* 固定宽度 */
+  width: 140rpx; /* 固定宽度 */
   padding: 10rpx;
   text-align: center;
   font-size: 28rpx;
@@ -123,81 +123,107 @@
 }
 
 /* 模态框内容 */
+/* 编辑删除弹窗样式 */
 .modal-content {
   background-color: #fff; /* 白色背景 */
   padding: 20px;
-  border-radius: 8px;
-  width: 90%;
-  max-width: 400px; /* 最大宽度 */
-  max-height: 80vh; /* 最大高度,留出一些空间以便滚动条显示 */
-  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
-  overflow-x: hidden; /* 隐藏水平滚动条 */
-  display: flex;
-  flex-direction: column; /* 默认垂直方向布局 */
-  align-items: flex-start; /* 左对齐 */
+  border-radius: 12rpx;
+  width: 80%; /* 调整宽度,居中效果更好 */
+  max-width: 400px; /* 限制最大宽度 */
+  text-align: center; /* 居中对齐内容 */
+  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.3); /* 添加阴影效果 */
+  animation: fadeIn 0.3s ease-in-out; /* 添加淡入动画 */
 }
 
-/* 每个输入项 */
+/* 弹窗选项样式 */
 .modal-item {
-  display: flex; /* 使用 flex 布局 */
-  align-items: center; /* 垂直居中 */
-  width: 100%;
-  margin-bottom: 15rpx; /* 每个输入项之间的间隔 */
+  padding: 20rpx;
+  font-size: 16px;
+  border-bottom: 1px solid #f1f1f1; /* 添加下边框分隔 */
+  color: #333; /* 设置字体颜色 */
+  cursor: pointer; /* 鼠标变成手型 */
 }
 
-/* 输入框标签 */
-.item-label {
-  width: 30%; /* 标签宽度 */
-  font-size: 16px;
-  margin-right: 15rpx; /* 标签与输入框之间的间隔 */
-  text-align: right; /* 标签文本右对齐 */
+.modal-item:last-child {
+  border-bottom: none; /* 去掉最后一项的下边框 */
 }
 
-/* 输入框样式 */
-.input-field {
-  flex: 1; /* 输入框占据剩余空间 */
-  padding: 8px;
-  font-size: 16px;
-  border: 1px solid #ccc;
-  border-radius: 4px;
+.modal-item:hover {
+  background-color: #f9f9f9; /* 悬停时背景变浅 */
 }
 
-/* 按钮容器 */
-.button-container {
-  display: flex; /* 使用 flexbox 布局 */
-  justify-content: space-between; /* 按钮之间均匀分布 */
-  width: 100%;
-  margin-top: 20rpx; /* 上边距 */
+/* 删除按钮特殊样式 */
+.modal-item.delete {
+  color: #e74c3c; /* 删除按钮红色字体 */
+  font-weight: bold; /* 加粗字体 */
 }
 
-/* 每个按钮样式 */
-.submit-btn, .cancel-btn {
-  flex: 1; /* 使按钮宽度相等 */
-  margin: 0 10rpx; /* 按钮间隔 */
-  padding: 10px;
-  border-radius: 5px;
-  font-size: 16px;
-  cursor: pointer;
+.modal-item.delete:hover {
+  background-color: #fceaea; /* 删除按钮悬停时红色背景 */
+}
+
+/* 取消按钮特殊样式 */
+.modal-item.cancel {
+  color: #666; /* 取消按钮灰色字体 */
+}
+
+.modal-item.cancel:hover {
+  background-color: #f4f4f4; /* 取消按钮悬停时浅灰背景 */
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px); /* 从顶部滑入 */
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 按钮容器样式 */
+.button-container {
+  display: flex; /* 使用 Flexbox 布局 */
+  justify-content: space-between; /* 按钮两端对齐 */
+  align-items: center; /* 垂直居中对齐 */
+  gap: 20rpx; /* 按钮之间的间距 */
+  margin-top: 20rpx; /* 上边距 */
 }
 
 /* 提交按钮样式 */
 .submit-btn {
+  flex: 1; /* 均分空间 */
   background-color: #4CAF50; /* 绿色背景 */
   color: white;
+  padding: 12rpx 0;
+  font-size: 16px;
+  text-align: center;
+  border-radius: 8rpx; /* 圆角 */
+  cursor: pointer;
+  border: none; /* 去除边框 */
 }
 
 /* 取消按钮样式 */
 .cancel-btn {
+  flex: 1; /* 均分空间 */
   background-color: #e74c3c; /* 红色背景 */
   color: white;
+  padding: 12rpx 0;
+  font-size: 16px;
+  text-align: center;
+  border-radius: 8rpx; /* 圆角 */
+  cursor: pointer;
+  border: none; /* 去除边框 */
 }
 
-/* 提交按钮悬停效果 */
+/* 按钮悬停效果 */
 .submit-btn:hover {
-  background-color: #45a049;
+  background-color: #45a049; /* 提交按钮悬停颜色 */
 }
 
-/* 取消按钮悬停效果 */
 .cancel-btn:hover {
-  background-color: #c0392b;
+  background-color: #c0392b; /* 取消按钮悬停颜色 */
 }
+

+ 66 - 73
pages/Visualization/Visualization.js

@@ -18,8 +18,6 @@ Page({
       H_plus: "",
       HN: "",
       Al3_plus: "",
-      free_alumina: "",
-      free_iron_oxides: "",
       Delta_pH: ""
     },
     // 中文表头内容,动态生成
@@ -36,68 +34,49 @@ Page({
 
   LoadData: function() {
     wx.request({
-      url: 'http://localhost:5000/table',
+      url: 'https://soilgd.com:5000/table',
       method: 'POST',
       header: {
         'Content-Type': 'application/json'
       },
       data: {
         table: 'current_reflux'
-    },
-    success: (res) => {
-      console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
-      
-      if (res.data && Array.isArray(res.data.rows)) {
-        const rows = res.data.rows.map(row => {
-          return {
+      },
+      success: (res) => {
+        console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
+        
+        if (res.data && Array.isArray(res.data.rows)) {
+          const rows = res.data.rows.map(row => {
+            return {
             'id': row.id,
-            'OM': row.OM,
-            'CL': row.CL,
-            'CEC': row.CEC,
-            'H_plus': row.H_plus,
-            'HN': row.N,
-            'Al3_plus': row.Al3_plus,
-            'Delta_pH': row.Delta_pH,
-          };
-        });
-
-        this.setData({
-          rows: rows,
-          filteredRows: rows
-        });
-      } else {
+            'OM': parseFloat(row.OM).toFixed(2),
+            'CL': parseFloat(row.CL).toFixed(2),
+            'CEC': parseFloat(row.CEC).toFixed(2),
+            'H_plus': parseFloat(row.H_plus).toFixed(2),
+            'HN': parseFloat(row.N).toFixed(2),
+            'Al3_plus': parseFloat(row.Al3_plus).toFixed(2),
+            'Delta_pH': parseFloat(row.Delta_pH).toFixed(2),
+            };
+          });
+  
+          this.setData({
+            rows: rows,
+            filteredRows: rows
+          });
+        } else {
+          wx.showToast({
+            title: '获取数据失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
         wx.showToast({
-          title: '获取数据失败',
+          title: '请求失败,请重试',
           icon: 'none'
         });
+        console.error('请求失败:', err);
       }
-    },
-    fail: (err) => {
-      wx.showToast({
-        title: '请求失败,请重试',
-        icon: 'none'
-      });
-      console.error('请求失败:', err);
-    }
-    });
-  },
-  // 搜索框绑定
-  shoppinginput: function(e) {
-    this.setData({
-      shoopingtext: e.detail.value
-    });
-  },
-
-  // 搜索功能:根据输入内容过滤表格数据
-  search: function() {
-    const searchText = this.data.shoopingtext.toLowerCase();
-    const filteredRows = this.data.rows.filter(item => {
-      return Object.values(item).some(value =>
-        String(value).toLowerCase().includes(searchText)
-      );
-    });
-    this.setData({
-      filteredRows: filteredRows
     });
   },
 
@@ -111,7 +90,7 @@ Page({
   const format = 'excel';  // 或者 'csv' 作为模板格式
 
   wx.request({
-    url: `http://localhost:5000/download_template?table=${tableName}&format=${format}`,
+    url: `https://soilgd.com:5000/download_template?table=${tableName}&format=${format}`,
     method: 'GET',
     responseType: 'arraybuffer',  // 处理二进制文件
     success: (res) => {
@@ -190,7 +169,7 @@ Page({
 
     // 向后端发送请求,获取表格数据并导出
     wx.request({
-      url: `http://localhost:5000/export_data?table=${tableName}&format=${fileFormat}`,
+      url: `https://soilgd.com:5000/export_data?table=${tableName}&format=${fileFormat}`,
       method: 'GET',
       responseType: 'arraybuffer',  // 处理二进制文件
       success: (res) => {
@@ -261,37 +240,51 @@ Page({
       type: 'file',
       extension: ['xlsx', 'csv'], // 允许上传的文件类型
       success: (res) => {
-        const filePath = res.tempFiles[0].path; // 获取文件临时路径
+        const filePath = res.tempFiles[0].path;
         const fileName = res.tempFiles[0].name;
-
-        wx.showLoading({
-          title: '上传中...',
-        });
-
+  
+        wx.showLoading({ title: '上传中...' });
+  
         // 上传文件到后端
         wx.uploadFile({
-          url: 'http://localhost:5000/import_data', // 后端处理导入的接口
+          url: 'https://soilgd.com:5000/import_data', // 后端接口
           filePath: filePath,
           name: 'file',
           formData: {
-            table: this.data.tableName, // 表名,后端根据需要解析数据
+            table: this.data.tableName, // 表名
             fileName: fileName
           },
           success: (res) => {
             wx.hideLoading();
             const responseData = JSON.parse(res.data);
-
+  
             if (responseData.success) {
-              wx.showToast({
-                title: '导入成功',
-                icon: 'success'
+              // 获取后端返回的结果
+              const { total_data, new_data, duplicate_data, duplicate_details } = responseData;
+  
+              // 显示导入结果
+              wx.showModal({
+                title: '导入结果',
+                content: `总数据: ${total_data} 条\n新增: ${new_data} 条\n重复: ${duplicate_data} 条`,
+                success: () => {
+                  // 如果有重复数据,跳转到重复数据详情页面
+                  if (duplicate_data > 0) {
+                    wx.navigateTo({
+                      url: '/pages/duplicateDetails/duplicateDetails', // 重复数据详情页面
+                      success: (page) => {
+                        // 将重复数据传递给详情页面
+                        page.setData({ duplicateDetails: duplicate_details });
+                      }
+                    });
+                  }
+                }
               });
-
+  
               // 重新加载数据
               this.LoadData();
             } else {
               wx.showToast({
-                title: '导入失败,请检查文件格式',
+                title: responseData.message || '导入失败,请检查文件格式',
                 icon: 'none'
               });
             }
@@ -310,7 +303,7 @@ Page({
         console.error('文件选择失败:', err);
       }
     });
-  },
+  },  
 
   // 新增按钮点击,显示新增弹窗
   onAdd: function() {
@@ -391,7 +384,7 @@ Page({
     }
 
     wx.request({
-      url: 'http://localhost:5000/add_item',
+      url: 'https://soilgd.com:5000/add_item',
       method: 'POST',
       header: {
         'Content-Type': 'application/json'
@@ -471,7 +464,7 @@ onSubmitEdit: function() {
   }
 
   wx.request({
-    url: 'http://localhost:5000/update_item',
+    url: 'https://soilgd.com:5000/update_item',
     method: 'PUT',
     header: {
       'Content-Type': 'application/json'
@@ -520,7 +513,7 @@ onSubmitEdit: function() {
   
     // 发送 DELETE 请求
     wx.request({
-      url: 'http://127.0.0.1:5000/delete_item',  // 后端接口地址
+      url: 'https://soilgd.com:5000/delete_item',  // 后端接口地址
       method: 'POST',  // 使用 POST 请求
       data: {
         table: 'current_reflux',  // 目标表

+ 1 - 1
pages/Visualization/Visualization.json

@@ -1,6 +1,6 @@
 {
   "usingComponents": { },
-  "navigationBarTitleText": "数据展示",
+  "navigationBarTitleText": "反酸模型数据管理",
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"
 }

+ 24 - 60
pages/Visualization/Visualization.wxml

@@ -1,24 +1,15 @@
 <!-- 固定顶部区域 -->
 <view class="top-container">
-  <view class="top">
-    <view class="topsearch">
-      <view class="frame">
-        <input value="{{shoopingtext}}" placeholder="请输入搜索内容" bindinput="shoppinginput" />
-      </view>
-      <text bindtap="search">搜索</text>
-    </view>
-  </view>
-
   <view class="add-btn-container">
-  <button class="add-btn" bindtap="onImport">导入</button>
-  <button class="add-btn" bindtap="onExport">导出</button>
-  <button class="add-btn" bindtap="onDownloadTemplate">下载模板</button>
-  <button class="add-btn" bindtap="onAdd">新增</button>
+    <button class="add-btn" bindtap="onImport">导入</button>
+    <button class="add-btn" bindtap="onExport">导出</button>
+    <button class="add-btn" bindtap="onDownloadTemplate">下载模板</button>
+    <button class="add-btn" bindtap="onAdd">新增</button>
   </view>
 </view>
 
 <!-- 滚动区域 -->
-<scroll-view class="table-container" scroll-x="true" scroll-with-animation="true">
+<scroll-view class="table-container" scroll-x="true" scroll-y="true" scroll-with-animation="true">
   <!-- 数据加载中 -->
   <view wx:if="{{loading}}" class="loading">数据加载中,请稍候...</view>
 
@@ -36,7 +27,7 @@
         <view class='table-cell' wx:for="{{list}}" wx:key="item">
           <view class='item'>{{item}}</view>
         </view>
-        <view class="table-cell">{{index + 1}}</view> <!-- 序号 -->
+        <view class="table-cell">{{index + 1}}</view>
         <view class="table-cell">{{item.OM}}</view>
         <view class="table-cell">{{item.CL}}</view>
         <view class="table-cell">{{item.CEC}}</view>
@@ -52,61 +43,36 @@
 <!-- 新增数据弹窗 -->
 <view class="modal" wx:if="{{showAddModal}}">
   <view class="modal-content">
-    <!-- 有机质含量 -->
-    <view class="modal-item">
-      <text class="item-label">有机质含量:</text>
-      <input class="input-field" placeholder="请输入有机质含量" data-field="OM" bindinput="onInputOM" value="{{newData.OM}}"/>
-    </view>
-
-    <!-- 土壤粘粒重量 -->
-    <view class="modal-item">
-      <text class="item-label">土壤粘粒重量:</text>
-      <input class="input-field" placeholder="请输入土壤粘粒重量" data-field="CL" bindinput="onInputCL" value="{{newData.CL}}"/>
-    </view>
 
-    <!-- 阳离子交换量 -->
     <view class="modal-item">
-      <text class="item-label">阳离子交换量:</text>
-      <input class="input-field" placeholder="请输入阳离子含量" data-field="CEC" bindinput="onInputCEC" value="{{newData.CEC}}"/>
-    </view>
-
-    <!-- 氢离子含量 -->
-    <view class="modal-item">
-      <text class="item-label">氢离子含量:</text>
-      <input class="input-field" placeholder="请输入氢离子含量" data-field="H_plus" bindinput="onInputH_plus" value="{{newData.H_plus}}"/>
-    </view>
-
-    <!-- 硝态氮含量 -->
+    <view class="item-label">土壤提升单位pH的生石灰施用量%:</view>
+    <input class="input-field" placeholder="比值" data-field="Q_over_b" bindinput="onInputQ_over_b" value="{{newData.Q_over_b}}"/>
+  </view>
     <view class="modal-item">
-      <text class="item-label">硝态氮含量:</text>
-      <input class="input-field" placeholder="请输入硝态氮含量" data-field="HN" bindinput="onInputHN" value="{{newData.HN}}"/>
-    </view>
+    <view class="item-label">土壤初始含量:</view>
+    <input class="input-field" placeholder="请输入酸碱度" data-field="pH" bindinput="onInputpH" value="{{newData.pH}}"/>
+  </view>
 
-    <!-- 铝离子含量 -->
     <view class="modal-item">
-      <text class="item-label">铝离子含量:</text>
-      <input class="input-field" placeholder="请输入铝离子含量" data-field="Al3_plus" bindinput="onInputAl3_plus" value="{{newData.Al3_plus}}"/>
-    </view>
+    <view class="item-label">土壤有机含量:</view>
+    <input class="input-field" placeholder="请输入有机质含量" data-field="OM" bindinput="onInputOM" value="{{newData.OM}}"/>
+  </view>
 
-    <!-- 游离氧化铝 -->
     <view class="modal-item">
-      <text class="item-label">游离氧化铝:</text>
-      <input class="input-field" placeholder="请输入游离氧化铝含量" data-field="free_alumina" bindinput="onInputfree_alumina" value="{{newData.free_alumina}}"/>
-    </view>
+    <view class="item-label">土壤粘粒含量:</view>
+    <input class="input-field" placeholder="请输入氯离子含量" data-field="CL" bindinput="onInputCL" value="{{newData.CL}}"/>
+  </view>
 
-    <!-- 游离氧化铁 -->
     <view class="modal-item">
-      <text class="item-label">游离氧化铁:</text>
-      <input class="input-field" placeholder="请输入游离氧化铁含量" data-field="free_iron_oxides" bindinput="onInputfree_iron_oxides" value="{{newData.free_iron_oxides}}"/>
-    </view>
+    <view class="item-label">交换性氢含量:</view>
+    <input class="input-field" placeholder="请输入H含量" data-field="H" bindinput="onInputH" value="{{newData.H}}"/>
+  </view>
 
-    <!-- 酸碱差值 -->
     <view class="modal-item">
-      <text class="item-label">酸碱差值:</text>
-      <input class="input-field" placeholder="请输入酸碱差值" data-field="Delta_pH" bindinput="onInputDelta_pH" value="{{newData.Delta_pH}}"/>
-    </view>
+    <view class="item-label">交换性铝含量:</view>
+    <input class="input-field" placeholder="请输入铝离子含量" data-field="Al" bindinput="onInputAl" value="{{newData.Al}}"/>
+  </view>
 
-    <!-- 按钮容器 -->
     <view class="button-container">
       <button class="submit-btn" bindtap="onSubmit">确认</button>
       <button class="cancel-btn" bindtap="onCancel">取消</button>
@@ -114,7 +80,6 @@
   </view>
 </view>
 
-
 <!-- 底部编辑删除弹窗 -->
 <view class="modal" wx:if="{{showModal}}">
   <view class="modal-content">
@@ -123,4 +88,3 @@
     <view class="modal-item cancel" bindtap="onCancel">取消</view>
   </view>
 </view>
-

+ 72 - 46
pages/Visualization/Visualization.wxss

@@ -75,7 +75,7 @@
 /* 表格单元格样式 */
 .table-cell {
   flex: none;
-  width: 250rpx; /* 固定宽度 */
+  width: 140rpx; /* 固定宽度 */
   padding: 10rpx;
   text-align: center;
   font-size: 28rpx;
@@ -123,81 +123,107 @@
 }
 
 /* 模态框内容 */
+/* 编辑删除弹窗样式 */
 .modal-content {
   background-color: #fff; /* 白色背景 */
   padding: 20px;
-  border-radius: 8px;
-  width: 90%;
-  max-width: 400px; /* 最大宽度 */
-  max-height: 80vh; /* 最大高度,留出一些空间以便滚动条显示 */
-  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
-  overflow-x: hidden; /* 隐藏水平滚动条 */
-  display: flex;
-  flex-direction: column; /* 默认垂直方向布局 */
-  align-items: flex-start; /* 左对齐 */
+  border-radius: 12rpx;
+  width: 80%; /* 调整宽度,居中效果更好 */
+  max-width: 400px; /* 限制最大宽度 */
+  text-align: center; /* 居中对齐内容 */
+  box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.3); /* 添加阴影效果 */
+  animation: fadeIn 0.3s ease-in-out; /* 添加淡入动画 */
 }
 
-/* 每个输入项 */
+/* 弹窗选项样式 */
 .modal-item {
-  display: flex; /* 使用 flex 布局 */
-  align-items: center; /* 垂直居中 */
-  width: 100%;
-  margin-bottom: 15rpx; /* 每个输入项之间的间隔 */
+  padding: 20rpx;
+  font-size: 16px;
+  border-bottom: 1px solid #f1f1f1; /* 添加下边框分隔 */
+  color: #333; /* 设置字体颜色 */
+  cursor: pointer; /* 鼠标变成手型 */
 }
 
-/* 输入框标签 */
-.item-label {
-  width: 30%; /* 标签宽度 */
-  font-size: 16px;
-  margin-right: 15rpx; /* 标签与输入框之间的间隔 */
-  text-align: right; /* 标签文本右对齐 */
+.modal-item:last-child {
+  border-bottom: none; /* 去掉最后一项的下边框 */
 }
 
-/* 输入框样式 */
-.input-field {
-  flex: 1; /* 输入框占据剩余空间 */
-  padding: 8px;
-  font-size: 16px;
-  border: 1px solid #ccc;
-  border-radius: 4px;
+.modal-item:hover {
+  background-color: #f9f9f9; /* 悬停时背景变浅 */
 }
 
-/* 按钮容器 */
-.button-container {
-  display: flex; /* 使用 flexbox 布局 */
-  justify-content: space-between; /* 按钮之间均匀分布 */
-  width: 100%;
-  margin-top: 20rpx; /* 上边距 */
+/* 删除按钮特殊样式 */
+.modal-item.delete {
+  color: #e74c3c; /* 删除按钮红色字体 */
+  font-weight: bold; /* 加粗字体 */
 }
 
-/* 每个按钮样式 */
-.submit-btn, .cancel-btn {
-  flex: 1; /* 使按钮宽度相等 */
-  margin: 0 10rpx; /* 按钮间隔 */
-  padding: 10px;
-  border-radius: 5px;
-  font-size: 16px;
-  cursor: pointer;
+.modal-item.delete:hover {
+  background-color: #fceaea; /* 删除按钮悬停时红色背景 */
+}
+
+/* 取消按钮特殊样式 */
+.modal-item.cancel {
+  color: #666; /* 取消按钮灰色字体 */
+}
+
+.modal-item.cancel:hover {
+  background-color: #f4f4f4; /* 取消按钮悬停时浅灰背景 */
+}
+
+/* 动画效果 */
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(-20px); /* 从顶部滑入 */
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 按钮容器样式 */
+.button-container {
+  display: flex; /* 使用 Flexbox 布局 */
+  justify-content: space-between; /* 按钮两端对齐 */
+  align-items: center; /* 垂直居中对齐 */
+  gap: 20rpx; /* 按钮之间的间距 */
+  margin-top: 20rpx; /* 上边距 */
 }
 
 /* 提交按钮样式 */
 .submit-btn {
+  flex: 1; /* 均分空间 */
   background-color: #4CAF50; /* 绿色背景 */
   color: white;
+  padding: 12rpx 0;
+  font-size: 16px;
+  text-align: center;
+  border-radius: 8rpx; /* 圆角 */
+  cursor: pointer;
+  border: none; /* 去除边框 */
 }
 
 /* 取消按钮样式 */
 .cancel-btn {
+  flex: 1; /* 均分空间 */
   background-color: #e74c3c; /* 红色背景 */
   color: white;
+  padding: 12rpx 0;
+  font-size: 16px;
+  text-align: center;
+  border-radius: 8rpx; /* 圆角 */
+  cursor: pointer;
+  border: none; /* 去除边框 */
 }
 
-/* 提交按钮悬停效果 */
+/* 按钮悬停效果 */
 .submit-btn:hover {
-  background-color: #45a049;
+  background-color: #45a049; /* 提交按钮悬停颜色 */
 }
 
-/* 取消按钮悬停效果 */
 .cancel-btn:hover {
-  background-color: #c0392b;
+  background-color: #c0392b; /* 取消按钮悬停颜色 */
 }
+

+ 3 - 3
pages/threshold/threshold.js

@@ -15,17 +15,17 @@ Page({
   },
   Model_Selection() {
     wx.navigateTo({
-      url: '/pages/Model Selection/Model Selection',
+      url: '/shoping/Model Selection/Model Selection',
     });
   },
   thres() {
     wx.navigateTo({
-      url: '/pages/thres/thres',
+      url: '/shoping/thres/thres',
     });
   },
   modelTrain() {
     wx.navigateTo({
-      url: '/pages/ModelTrain/ModelTrain',
+      url: '/shoping/ModelTrain/ModelTrain',
     })
   }
 });

+ 1 - 1
pages/threshold/threshold.json

@@ -2,7 +2,7 @@
   "usingComponents": {
     "nav-tabar": "/components/shoping-tabbar/index"
   },
-  "navigationBarTitleText": "数据展示",
+  "navigationBarTitleText": "模型配置",
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"  
 }

+ 14 - 7
pages/threshold/threshold.wxml

@@ -1,7 +1,14 @@
-<view class="container">
-  <button class="full-width-button" bindtap="Model_Selection">模型选择</button>
-
-  <button class="full-width-button" bindtap="thres">阈值选择</button>
-
-  <button class="full-width-button" bindtap="modelTrain">模型训练</button>
-</view>
+<view class="card-container">
+  <view class="card" bindtap="Model_Selection">
+    <image class="card-icon" src="/assets/taddar/模型选择.png" />
+    <text class="card-title">模型选择</text>
+  </view>
+  <view class="card" bindtap="thres">
+    <image class="card-icon" src="/assets/taddar/阈值.png" />
+    <text class="card-title">阈值选择</text>
+  </view>
+  <view class="card" bindtap="modelTrain">
+    <image class="card-icon" src="/assets/taddar/模型训练.png" />
+    <text class="card-title">模型训练</text>
+  </view>
+</view>

+ 52 - 25
pages/threshold/threshold.wxss

@@ -1,27 +1,54 @@
-/* 父容器样式 */
-.container {
+.card-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; /* 按下按钮时的颜色变化 */
+  flex-direction: column;
+  gap: 20px;
+  padding: 20px;
+  background-color: #f5f5f5;
+  margin-top: 60px;
 }
+
+.card {
+  display: flex; /* 使用 flexbox 布局 */
+  align-items: center;
+  width: 90%;
+  background-color: #ffffff;
+  padding: 20px;
+  border-radius: 12px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  transition: transform 0.2s ease;
+  cursor: pointer;
+}
+
+.card:hover {
+  transform: scale(1.05);
+}
+
+.card-icon {
+  width: 40px;
+  height: 40px;
+  margin-bottom: 20px;
+  margin-right: 90px; /* 与内容保持间距 */
+}
+
+.card-content {
+  flex: 1; /* 占据剩余空间,保证布局一致 */
+  text-align: center; /* 内容水平居中 */
+}
+
+.card-title {
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+}
+
+.card:nth-child(1) {
+  background-color: #FFDDC1;
+}
+
+.card:nth-child(2) {
+  background-color: #C1E1FF;
+}
+
+.card:nth-child(3) {
+  background-color: #D4F4DD;
+}

+ 3 - 2
project.config.json

@@ -22,11 +22,12 @@
     "enhance": true,
     "compileWorklet": false,
     "swc": false,
-    "condition": false
+    "condition": false,
+    "packNpmRelationList": []
   },
   "compileType": "miniprogram",
   "libVersion": "3.7.1",
-  "appid": "wx9714bd9b9d4a11c9",
+  "appid": "wxa4088d00b5849cba",
   "projectname": "tabBar",
   "simulatorType": "wechat",
   "simulatorPluginLibVersion": {},

+ 1 - 1
project.private.config.json

@@ -1,6 +1,6 @@
 {
   "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
-  "projectname": "tabBar3",
+  "projectname": "土壤酸化模型计算",
   "setting": {
     "compileHotReLoad": true,
     "urlCheck": false

+ 48 - 41
shoping/AcidNeutralizationModel/AcidNeutralizationModel.js

@@ -49,49 +49,56 @@ Page({
   },
 
   // 点击按钮后进行计算并提示结果
-  calculate: function () {
-    console.log('开始计算...');
-    const data = {
-      model_id: 6,
-      parameters: {
-        init_pH: this.data.init_pH,
-        target_pH: this.data.target_pH,
-        OM: this.data.OM,
-        CL: this.data.CL,
-        H: this.data.H,
-        Al: this.data.Al,
-      },
-    };
+ // 点击按钮后进行计算并提示结果
+calculate: function () {
+  console.log('开始计算...');
+  const data = {
+    model_id: 6,
+    parameters: {
+      init_pH: this.data.init_pH,
+      target_pH: this.data.target_pH,
+      OM: this.data.OM,
+      CL: this.data.CL,
+      H: this.data.H,
+      Al: this.data.Al,
+    },
+  };
 
-    wx.request({
-      url: 'http://localhost:5000/predict',
-      method: 'POST',
-      data: JSON.stringify(data),
-      header: {
-        'content-type': 'application/json',
-      },
-      success: (res) => {
-        console.log('预测结果:', res.data.result);
+  wx.request({
+    url: 'https://soilgd.com:5000/predict',
+    method: 'POST',
+    data: JSON.stringify(data),
+    header: {
+      'content-type': 'application/json',
+    },
+    success: (res) => {
+      console.log('预测结果:', res.data.result);
 
-        // 更新计算结果
-          this.setData({
-            result: res.data.result.toString(),
-            showModal: true, // 显示弹窗
-          });
-          wx.showToast({
-            title: '计算完成!结果已更新',
-            icon: 'success',
-          });
-      },
-      fail: (error) => {
-        console.error('请求失败:', error);
-        wx.showToast({
-          title: '计算失败,请重试',
-          icon: 'none',
-        });
-      },
-    });
-  },
+      // 确保结果显示两位小数
+      let result = res.data.result;
+      if (result !== null && !isNaN(result)) {
+        result = parseFloat(result).toFixed(2);  // 将结果格式化为两位小数
+      }
+
+      // 更新计算结果
+      this.setData({
+        result: result,
+        showModal: true, // 显示弹窗
+      });
+      wx.showToast({
+        title: '计算完成!结果已更新',
+        icon: 'success',
+      });
+    },
+    fail: (error) => {
+      console.error('请求失败:', error);
+      wx.showToast({
+        title: '计算失败,请重试',
+        icon: 'none',
+      });
+    },
+  });
+},
 
   // 关闭弹窗
   closeModal: function () {

+ 14 - 21
shoping/AcidNeutralizationModel/AcidNeutralizationModel.wxml

@@ -1,11 +1,11 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">初始 pH:
+      <view class="page-section-title">土壤初始 pH:
       </view>
       <input 
         class="input-field"  
-        placeholder="4~6" 
+        placeholder="3~6" 
         value="{{init_pH}}" 
         bindinput="onInitPhChange" 
       />
@@ -14,11 +14,11 @@
 
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">目标 pH:
+      <view class="page-section-title">土壤目标 pH:
       </view>
       <input 
         class="input-field"  
-        placeholder="4~6" 
+        placeholder="3~6" 
         value="{{target_pH}}" 
         bindinput="onTargetPhChange" 
       />
@@ -27,11 +27,11 @@
 
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">有机质含量 OM:</view>
+      <view class="page-section-title">土壤有机质(g/kg) OM:</view>
       <input 
         class="input-field" 
         type="text" 
-        placeholder="4~6" 
+        placeholder="10~30" 
         value="{{OM}}" 
         bindinput="onOMChange" 
       />
@@ -40,11 +40,11 @@
 
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">土壤粘粒重量 CL:</view>
+      <view class="page-section-title">土壤粘粒(g/kg) CL:</view>
       <input 
         class="input-field" 
         type="text" 
-        placeholder="4~6" 
+        placeholder="50~600" 
         value="{{CL}}" 
         bindinput="onCLChange" 
       />
@@ -53,11 +53,11 @@
 
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">氢离子含量 H:</view>
+      <view class="page-section-title">氢离子含量(cmol/kg) H:</view>
       <input 
         class="input-field" 
         type="text" 
-        placeholder="4~6" 
+        placeholder="0~4" 
         value="{{H}}" 
         bindinput="onHChange" 
       />
@@ -66,11 +66,11 @@
 
   <view class="white-box">
     <view class="input-row">
-      <view class="page-section-title">铝离子含量 Al:</view>
+      <view class="page-section-title">铝离子含量(cmol/kg) Al:</view>
       <input 
         class="input-field" 
         type="text" 
-        placeholder="4~6" 
+        placeholder="0~4" 
         value="{{Al}}" 
         bindinput="onAlChange" 
       />
@@ -81,11 +81,11 @@
   <view wx:if="{{showModal}}" class="modal-overlay">
     <view class="modal">
       <view class="modal-header">
-        <text>计算结果</text>
+        <text>100克土壤撒{{result}}克生石灰</text>
         <button class="close-btn" bindtap="closeModal">×</button>
       </view>
       <view class="modal-body">
-        <text>{{result}}</text>
+        <text>{{result}}</text>
       </view>
     </view>
   </view>
@@ -94,11 +94,4 @@
   <view class="green-btn">
     <button class="btn" bindtap="calculate">计算</button>
   </view>
-
-  <!-- 建议 -->
-  <view class="description-box">
-    <text class="bold-text">
-      建议:\n1. 选用Ca0(生石灰)作为碱性物料,每季施用量不宜超过150 kg/亩,当计算用量超过150 kg/亩时,建议分多季施用;建议翻耕时施用,施加后与耕作层土壤混匀,避免苗期施用导致烧苗;施加过程注意防护,避免灼烧眼睛和皮肤;\n 2. 在改良时间富裕,石灰质材料充足的条件下,建议优先选用石灰石粉末、白云石等温和型碱性物料;\n3. 当其它碱性物料计算用量超过500 kg/亩,建议分2季施用;\n 4. 建议每隔3-5年,追踪土壤pH变化,可根据土壤pH变化,追施少量石灰质材料;
-    </text>
-  </view>
 </view>

+ 2 - 2
shoping/Calculation/Calculation.js

@@ -91,7 +91,7 @@ Page({
     };
 
     wx.request({
-      url: 'http://127.0.0.1:5000/predict',
+      url: 'https://soilgd.com:5000/predict',
       method: 'POST',
       data: JSON.stringify(data),
       header: {
@@ -104,7 +104,7 @@ Page({
         if (res.data.result && Array.isArray(res.data.result)) {
           const result = res.data.result[0] ? res.data.result[0].toString() : '无结果';
           this.setData({
-            result: result,
+            result: parseFloat(result).toFixed(5),
             showResultPopup: true, // 显示弹窗
           });
         } else {

+ 13 - 13
shoping/Calculation/Calculation.wxml

@@ -2,11 +2,11 @@
 <view class="page-body">
   <view class="white-box">
   <view class="input-row">
-    <view class="page-section-title">有机质含量 OM:</view>
+    <view class="page-section-title">土壤有机质(g/kg) OM:</view>
     <input 
       class="input-field" 
       type="text" 
-      placeholder="4~6" 
+      placeholder="0~30" 
       value="{{OM}}" 
       bindinput="onOMChange" 
     />
@@ -17,11 +17,11 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-    <view class="page-section-title">土壤粘粒重量 CL:</view>
+    <view class="page-section-title">土壤粘粒(g/kg) CL:</view>
     <input 
       class="input-field" 
       type="text" 
-      placeholder="4~6" 
+      placeholder="50~400" 
       value="{{CL}}" 
       bindinput="onCLChange" 
     />
@@ -32,11 +32,11 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-    <view class="page-section-title">阳离子交换量 CEC:</view>
+    <view class="page-section-title">阳离子交换量(cmol/kg) CEC:</view>
     <input 
       class="input-field" 
       type="text" 
-      placeholder="4~6" 
+      placeholder="0~15" 
       value="{{CEC}}" 
       bindinput="onCECChange" 
     />
@@ -47,11 +47,11 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-    <view class="page-section-title">氢离子含量 H:</view>
+    <view class="page-section-title">氢离子含量(cmol/kg) H:</view>
     <input 
       class="input-field" 
       type="text" 
-      placeholder="4~6" 
+      placeholder="0~1" 
       value="{{H}}" 
       bindinput="onHChange" 
     />
@@ -62,11 +62,11 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-    <view class="page-section-title">铵离子含量 HN:</view>
+    <view class="page-section-title">铵离子含量(cmol/kg) HN:</view>
     <input 
       class="input-field" 
       type="text" 
-      placeholder="4~6" 
+      placeholder="0~0.2" 
       value="{{HN}}" 
       bindinput="onHNChange" 
     />
@@ -77,11 +77,11 @@
 <view class="page-body">
   <view class="white-box">
     <view class="input-row">
-    <view class="page-section-title">铝离子含量 Al:</view>
+    <view class="page-section-title">铝离子含量(cmol/kg) Al:</view>
     <input 
       class="input-field" 
       type="text" 
-      placeholder="4~6" 
+      placeholder="0~6" 
       value="{{Al}}" 
       bindinput="onAlChange" 
     />
@@ -94,7 +94,7 @@
   <view wx:if="{{showResultPopup}}" class="modal-overlay">
     <view class="modal">
       <view class="modal-header">
-        <text>计算结果</text>
+        <text>ΔPH值</text>
         <button class="close-btn" bindtap="closePopup">×</button>
       </view>
       <view class="modal-body">

+ 104 - 0
shoping/Model Selection/Model Selection.js

@@ -0,0 +1,104 @@
+Page({
+  data: {
+    modelList: [],         // 可用的模型列表
+    selectedModelName: '', // 选择的模型名称
+    selectedModelId: null, // 选择的模型ID
+    selectedPerformanceScore: '' // 选择的模型性能分数
+  },
+
+  onLoad: function() {
+    // 页面加载时获取可用模型列表
+    this.fetchModelList();
+  },
+
+  // 获取可用的模型列表
+  fetchModelList: function() {
+    wx.request({
+      url: 'https://soilgd.com:5000/models',  // 后端接口返回模型列表
+      method: 'GET',  // 获取模型列表应该是 GET 请求
+      success: (res) => {
+        if (res.data) {
+          // 只保留 ModelType 和 PerformanceScore,并处理 PerformanceScore 为 null 的情况
+          const filteredModels = res.data.map(model => ({
+            ModelType: model.ModelType,  // 模型类型
+            PerformanceScore: (Number(model.PerformanceScore) || 0).toFixed(2),  // 确保是数字并保留两位小数
+            ModelID: model.ModelID  // 模型ID
+          }));
+
+          this.setData({
+            modelList: filteredModels  // 更新模型列表数据
+          });
+        } else {
+          wx.showToast({
+            title: '获取模型列表失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        console.error("获取模型列表失败", err);
+        wx.showToast({
+          title: '获取模型列表失败',
+          icon: 'none'
+        });
+      }
+    });
+  },
+
+  // 选择模型时更新 selectedModelId 和 selectedModelName
+  onModelChange: function(e) {
+    const selectedIndex = e.detail.value;
+    const selectedModel = this.data.modelList[selectedIndex];
+    this.setData({
+      selectedModelName: selectedModel.ModelType,  // 显示 ModelType
+      selectedModelId: selectedModel.ModelID,  // 使用 ModelID 作为唯一标识
+      selectedPerformanceScore: selectedModel.PerformanceScore  // 显示性能分数
+    });
+  },
+
+  // 提交选择的模型
+  onSubmitModel: function() {
+    const { selectedModelId, selectedModelName } = this.data;
+
+    if (!selectedModelId || !selectedModelName) {
+      wx.showToast({
+        title: '请选择一个模型',
+        icon: 'none'
+      });
+      return;
+    }
+
+    // 发送请求切换模型
+    wx.request({
+      url: 'https://soilgd.com:5000/switch-model',  // 后端切换模型的接口
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json'  // 确保发送的是 JSON 数据
+      },
+      data: {
+        model_id: selectedModelId,  // 选择的模型ID
+        model_name: selectedModelName  // 选择的模型名称
+      },
+      success: (res) => {
+        if (res.data && res.data.success) {
+          wx.showToast({
+            title: '模型切换成功',
+            icon: 'success'
+          });
+        } else {
+          wx.showToast({
+            title: '切换模型失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        console.error("切换模型失败", err);
+        wx.showToast({
+          title: '切换模型失败',
+          icon: 'none'
+        });
+      }
+    });
+  }
+});

+ 6 - 0
shoping/Model Selection/Model Selection.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "模型选择", 
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"  
+}

+ 21 - 0
shoping/Model Selection/Model Selection.wxml

@@ -0,0 +1,21 @@
+<view> 
+  <!-- picker 用于选择模型 -->
+  <picker bindchange="onModelChange" value="{{selectedModelId}}" range="{{modelList}}" range-key="ModelType">
+    <view class="picker">
+      选择模型:{{selectedModelName}} - 性能分数:{{selectedPerformanceScore}}
+    </view>
+  </picker>
+
+  <!-- 模型列表显示 
+  <view class="model-list">
+    <block wx:for="{{modelList}}" wx:key="index">
+      <view class="model-item">
+        <text>模型类型:{{item.ModelType}}</text>
+        <text>性能分数:{{item.PerformanceScore}}</text>
+      </view>
+    </block>
+  </view>
+-->
+  <!-- 提交按钮,用于提交选择的模型 -->
+  <button bindtap="onSubmitModel" class="submit-btn">切换模型</button>
+</view>

+ 21 - 0
shoping/Model Selection/Model Selection.wxss

@@ -0,0 +1,21 @@
+.picker {
+  font-size: 16px;
+  margin: 10px;
+  padding: 10px;
+  border: 1px solid #ccc;
+}
+
+.model-list {
+  margin-top: 20px;
+}
+
+.model-item {
+  padding: 10px;
+  margin: 5px 0;
+  border: 1px solid #f1f1f1;
+}
+
+.model-item text {
+  display: block;
+  margin-bottom: 5px;
+}

+ 125 - 0
shoping/ModelTrain/ModelTrain.js

@@ -0,0 +1,125 @@
+Page({
+  data: {
+    rows: [], // 所有表格数据
+    currentRow: null, // 当前选中的表格行
+    filteredRows: null, // 过滤后的表格数据
+    tableHeaders: [
+      "数据集名称","数据条数","更新时间"
+    ],
+    types: [{ name: 'all' }, { name: 'reduce' }, { name: 'reflux' }], // 数据种类
+    currentType: 'all', // 当前选中的数据种类
+  },
+
+  // 页面加载时获取表格数据
+  onLoad: function() {
+    this.LoadData();
+  },
+
+  LoadData: function() {
+    wx.request({
+      url: 'https://soilgd.com:5000/table',
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json'
+      },
+      data: {
+        table: 'Datasets'
+      },
+      success: (res) => {
+        console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
+        
+        if (res.data && Array.isArray(res.data.rows)) {
+          const rows = res.data.rows.map(row => {
+            return {
+              'id': row.Dataset_ID,
+              'name': row.Dataset_name,
+              'description': row.Dataset_description,
+              'type': row.Dataset_type,
+              'count': row.Row_count,
+              'status': row.Status,
+              'uploadTime': row.Uploaded_at,
+            };
+          });
+          console.log(rows);
+          this.setData({
+            rows: rows,
+            filteredRows: rows,
+          });
+        } else {
+          wx.showToast({
+            title: '获取数据失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        wx.showToast({
+          title: '请求失败,请重试',
+          icon: 'none'
+        });
+        console.error('请求失败:', err);
+      }
+    });
+  },
+
+  // 处理行点击事件
+  onRowClick: function(e) {
+    const index = e.currentTarget.dataset.index;
+    const selectedRow = this.data.filteredRows[index];
+    this.setData({
+      currentRow: this.data.filteredRows[index] ? {...this.data.filteredRows[index], index} : null
+    });
+    console.log('选中的行信息:', selectedRow); // 打印当前选中行的信息
+  },
+  
+  // 处理数据种类改变事件
+  onTypeChange: function(e) {
+    const type = this.data.types[e.detail.value].name;
+    this.setData({
+      currentType: type,
+      filteredRows: type === 'all' ? this.data.rows : this.data.rows.filter(row => row.type === type)
+    });
+  },
+
+  // 处理训练模型按钮点击事件
+  trainModel: function() {
+    const { currentRow } = this.data;
+    if (!currentRow) {
+      wx.showToast({
+        title: '请先选择一行数据',
+        icon: 'none'
+      });
+      return;
+    }
+    const { id, type } = currentRow;
+    const trainData = {
+      model_type: "RandomForest",
+      model_name: "ForestModel1",
+      model_description: "A random forest model trained on current data.",
+      data_type: type,
+      dataset_id: id
+    };
+    wx.request({
+      url: 'https://soilgd.com:5000/train-and-save-model', // 假设这是你的接口地址
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json'
+      },
+      data: trainData,
+      success: (res) => {
+        console.log('模型训练成功:', res);
+        wx.showToast({
+          title: '模型训练完成',
+          icon: 'success'
+        });
+      },
+      fail: (err) => {
+        console.error('模型训练失败:', err);
+        wx.showToast({
+          title: '模型训练失败',
+          icon: 'none'
+        });
+      }
+    });
+  }
+})

+ 6 - 0
shoping/ModelTrain/ModelTrain.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": { },
+  "navigationBarTitleText": "模型训练",
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"
+}

+ 50 - 0
shoping/ModelTrain/ModelTrain.wxml

@@ -0,0 +1,50 @@
+<!-- 数据种类选择标题 -->
+<view class="picker-title">数据集种类:</view>
+
+<!-- 数据种类选择下拉框 -->
+<picker mode="selector" range="{{types}}" range-key="name" bindchange="onTypeChange">
+  <view class="picker-container">
+    <view class="picker">
+      {{currentType === 'all' ? '全部数据' : currentType}}
+    </view>
+    <view class="picker-arrow"></view> <!-- 三角形样式 -->
+  </view>
+</picker>
+
+<!-- 滚动区域 -->
+<scroll-view class="table-container" scroll-x="true" scroll-with-animation="true">
+  <!-- 数据加载中 -->
+  <view wx:if="{{loading}}" class="loading">数据加载中,请稍候...</view>
+
+  <!-- 无数据提示 -->
+  <view wx:if="{{!loading && filteredRows.length === 0}}" class="no-data">暂无数据</view>
+
+  <!-- 表格 -->
+  <view class="table-body">
+    <view class="table-header">
+      <!-- 添加单选框 -->
+      <view class="table-cell"></view>
+      <view class="table-cell" wx:for="{{tableHeaders}}" wx:key="index">{{item}}</view>
+    </view>
+
+    <block wx:for="{{filteredRows}}" wx:key="index">
+      <view class="table-row" bindtap="onRowClick" data-index="{{index}}">
+        <!-- 单选框列 -->
+        <view class="table-cell">
+          <radio 
+            value="{{index}}" checked="{{currentRow && currentRow.index === index}}" 
+            class="{{currentRow && currentRow.index === index ? 'radio-checked' : 'radio-unchecked'}}"
+            />
+        </view>
+        <!-- 数据列 -->
+        <view class="table-cell">{{item.name}}</view>
+        <view class="table-cell">{{item.count}}</view>
+        <view class="table-cell">{{item.uploadTime}}</view>
+      </view>
+    </block>
+  </view>
+</scroll-view>
+
+<view class="button-container">
+  <button bindtap="trainModel">训练</button>
+</view>

+ 141 - 0
shoping/ModelTrain/ModelTrain.wxss

@@ -0,0 +1,141 @@
+
+/* 表格容器 */
+.table-container {
+  width: 100%;
+  overflow-x: auto; /* 启用横向滚动 */
+  white-space: nowrap; /* 禁止换行 */
+  background-color: #fff;
+  border: 1rpx solid #ddd;
+  margin-top: 10rpx; /* 添加上边距,确保不会和顶部元素重叠 */
+  white-space: nowrap; /* 禁止换行 */
+}
+
+/* 表头样式 */
+.table-header {
+  display: flex;
+  background-color: #61E054; /* 表头背景色 */
+  font-weight: bold;
+  text-align: center;
+}
+
+/* 表格单元格样式 */
+.table-cell {
+  flex: none;
+  width: 170rpx; /* 固定宽度 */
+  height: 100rpx;
+  padding: 10rpx;
+  text-align: center;
+  font-size: 28rpx;
+  color: #030803; /* 表头文字颜色 */
+  border-right: 1rpx solid #ddd;
+  white-space: normal;
+  overflow: hidden;
+  word-wrap: break-word;
+  word-break: break-all;
+}
+
+/* 表格行样式 */
+.table-row:nth-child(odd) {
+  background-color: #E4FBE5; /* 奇数行背景色 */
+}
+
+.table-row:nth-child(even) {
+  background-color: #fff; /* 偶数行背景色 */
+}
+
+/* 表格内容样式 */
+.table-body {
+  display: flex;
+  flex-direction: column;
+  width: max-content;
+}
+
+.table-row {
+  display: flex;
+  flex-direction: row;
+  border-bottom: 1rpx solid #ddd;
+}
+
+/* 单选框样式 */
+.radio {
+  display: inline-block;
+  margin-right: 10px; /* 单选框之间的间隔 */
+}
+
+/* 单选框选中时的样式 */
+.radio-checked {
+  color: #1AAD19; /* 微信小程序主题色 */
+}
+
+/* 单选框未选中时的样式 */
+.radio-unchecked {
+  color: #ccc; /* 灰色表示未选中 */
+}
+
+/* 按钮通用样式 */
+.button-container button {
+  width: 50%; /* 按钮宽度100%,占满容器宽度 */
+  height: 50px; /* 按钮高度 */
+  line-height: 50px; /* 行高与按钮高度一致,使文本垂直居中 */
+  background-color: #1AAD19; /* 微信绿 */
+  color: white; /* 文字颜色为白色 */
+  border-radius: 5px; /* 圆角边框 */
+  font-size: 16px; /* 字体大小 */
+  margin-top: 20px;
+}
+
+/* 按钮点击效果 */
+.button-container button:active {
+  background-color: #179B16; /* 点击时背景颜色变深 */
+}
+
+
+/* 下拉框容器样式 */
+.picker-container {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 16px; /* 字体大小 */
+  color: #333; /* 字体颜色 */
+  padding: 8px 12px; /* 内边距 */
+  background-color: #fff; /* 背景颜色 */
+  border: 1px solid #ddd; /* 边框 */
+  border-radius: 4px; /* 圆角边框 */
+  width: 200px; /* 固定宽度 */
+  margin-left: auto; /* 自动左边距 */
+  margin-right: auto; /* 自动右边距 */
+  box-sizing: border-box; /* 确保边框和内边距包含在宽度内 */
+}
+
+/* 下拉框样式 */
+.picker {
+  flex-grow: 1; /* 使下拉框占据剩余空间 */
+  text-align: left; /* 文字左对齐 */
+}
+
+/* 三角形样式 */
+.picker-arrow {
+  width: 0;
+  height: 0;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-top: 5px solid #333; /* 三角形颜色 */
+  margin-left: 10px; /* 与下拉框文本的间隔 */
+}
+
+/* 下拉框标题样式 */
+.picker-title {
+  font-size: 16px; /* 字体大小 */
+  font-weight: bold; /* 字体加粗 */
+  color: #333; /* 字体颜色 */
+  margin-bottom: 10px; /* 与下拉框之间的间隔 */
+  text-align: center; /* 文字居中 */
+  padding: 8px 0; /* 上下内边距 */
+  background-color: #f0f0f0; /* 背景颜色 */
+  border: 1px solid #ddd; /* 边框 */
+  border-radius: 4px; /* 圆角边框 */
+  width: 200px; /* 固定宽度 */
+  margin-left: auto; /* 自动左边距 */
+  margin-right: auto; /* 自动右边距 */
+  box-sizing: border-box; /* 确保边框和内边距包含在宽度内 */
+}

+ 1 - 1
shoping/ResearchFindings/ResearchFindings.wxml

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

+ 331 - 46
shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.js

@@ -1,66 +1,351 @@
-// pages/Regular User/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.js
-Page({
+import * as echarts from '../../components/ec-canvas/echarts';
 
-  /**
-   * 页面的初始数据
-   */
+Page({
   data: {
+    ecLine: {
+      onInit: function (canvas, width, height, dpr) {
+        const lineChart = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(lineChart);
+        lineChart.setOption(getLineOption());
 
-  },
+        return lineChart;
+      }
+    },
+    ecInitScatter: {
+      onInit: function (canvas, width, height, dpr) {
+        const initScatter = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(initScatter);
+        initScatter.setOption(getInitScatterOption());
 
-  /**
-   * 生命周期函数--监听页面加载
-   */
-  onLoad(options) {
+        return initScatter;
+      }
+    },
+  ecMidScatter: {
+    onInit: function (canvas, width, height, dpr) {
+      const midScatter = echarts.init(canvas, null, {
+        width: width,
+        height: height,
+        devicePixelRatio: dpr // new
+      });
+      canvas.setChart(midScatter);
+      midScatter.setOption(getMidScatterOption());
 
+      return midScatter;
+    }
   },
+    ecFinalScatter: {
+      onInit: function (canvas, width, height, dpr) {
+        const finalScatter = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(finalScatter);
+        finalScatter.setOption(getFinalScatterOption());
 
-  /**
-   * 生命周期函数--监听页面初次渲染完成
-   */
-  onReady() {
-
+        return finalScatter;
+      }
+    },
   },
 
-  /**
-   * 生命周期函数--监听页面显示
-   */
-  onShow() {
+  onReady() {
+    // You can add any additional logic here if needed
+  }
+});
 
-  },
+// 降酸模型  初代  散点图
+function getInitScatterOption() {
+  const calculateDataRange = (data) => {
+    let xValues = data.map(item => item[0]);
+    let yValues = data.map(item => item[1]);
+    
+    return {
+      xMin: Math.min(...xValues),
+      xMax: Math.max(...xValues),
+      yMin: Math.min(...yValues),
+      yMax: Math.max(...yValues)
+    };
+  };
 
-  /**
-   * 生命周期函数--监听页面隐藏
-   */
-  onHide() {
+  const scatterData = [[0.12931, 0.10315928999999999], [0.066298, 0.10226514000000003], [0.057692, 0.10226514000000003], [0.072464, 0.10339078000000003], [0.146414, 0.10000317000000003], [0.087263, 0.09199080000000004], [0.096983, 0.09947644], [0.070505, 0.10142657000000004], [0.052497, 0.09956722000000007], [0.166515, 0.10179712], [0.063291, 0.10339078000000003]];
 
-  },
+  const range = calculateDataRange(scatterData);
+  const padding = 0.1;
+  const xMin = range.xMin - Math.abs(range.xMin * padding);
+  const xMax = range.xMax + Math.abs(range.xMax * padding);
+  const yMin = range.yMin - Math.abs(range.yMin * padding);
+  const yMax = range.yMax + Math.abs(range.yMax * padding);
+  const min = Math.min(xMin, yMin)
+  const max = Math.max(xMax, yMax)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross'
+      }
+    },
+    legend: {
+      data: ['True vs Predicted']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      name: 'True Values',
+      type: 'value',
+      min: min,
+      max: max
+    },
+    yAxis: {
+      name: 'Predicted Values',
+      type: 'value',
+      min: parseFloat(min).toFixed(2),
+      max: parseFloat(max).toFixed(2)
+    },
+    series: [
+      {
+        name: 'True vs Predicted',
+        type: 'scatter',
+        data: scatterData,
+        symbolSize: 10,
+        itemStyle: {
+          color: '#1f77b4',
+          opacity: 0.7
+        }
+      },
+      {
+        name: 'Trendline',
+        type: 'line',
+        data: [
+          [min, min],
+          [max, max]
+        ],
+        lineStyle: {
+          type: 'dashed',
+          color: '#ff7f0e',
+          width: 2
+        }
+      }
+    ]
+  };
+}
 
-  /**
-   * 生命周期函数--监听页面卸载
-   */
-  onUnload() {
+// 降酸模型  中间代  散点图
+function getMidScatterOption() {
+  const calculateDataRange = (data) => {
+    let xValues = data.map(item => item[0]);
+    let yValues = data.map(item => item[1]);
+    
+    return {
+      xMin: Math.min(...xValues),
+      xMax: Math.max(...xValues),
+      yMin: Math.min(...yValues),
+      yMax: Math.max(...yValues)
+    };
+  };
 
-  },
+  const scatterData = [[0.12931, 0.09595878000000002], [0.066298, 0.10344700000000003], [0.057692, 0.10344700000000003], [0.072464, 0.09011803], [0.146414, 0.0853504500000001], [0.087263, 0.07407179000000012], [0.096983, 0.10581049999999995], [0.070505, 0.09876380000000004], [0.052497, 0.08568465000000008], [0.166515, 0.13348440999999997], [0.063291, 0.09011803]];
 
-  /**
-   * 页面相关事件处理函数--监听用户下拉动作
-   */
-  onPullDownRefresh() {
+  const range = calculateDataRange(scatterData);
+  const padding = 0.1;
+  const xMin = range.xMin - Math.abs(range.xMin * padding);
+  const xMax = range.xMax + Math.abs(range.xMax * padding);
+  const yMin = range.yMin - Math.abs(range.yMin * padding);
+  const yMax = range.yMax + Math.abs(range.yMax * padding);
+  const min = Math.min(xMin, yMin)
+  const max = Math.max(xMax, yMax)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross'
+      }
+    },
+    legend: {
+      data: ['True vs Predicted']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      name: 'True Values',
+      type: 'value',
+      min: min,
+      max: max
+    },
+    yAxis: {
+      name: 'Predicted Values',
+      type: 'value',
+      min: parseFloat(min).toFixed(2),
+      max: parseFloat(max).toFixed(2)
+    },
+    series: [
+      {
+        name: 'True vs Predicted',
+        type: 'scatter',
+        data: scatterData,
+        symbolSize: 10,
+        itemStyle: {
+          color: '#1f77b4',
+          opacity: 0.7
+        }
+      },
+      {
+        name: 'Trendline',
+        type: 'line',
+        data: [
+          [min, min],
+          [max, max]
+        ],
+        lineStyle: {
+          type: 'dashed',
+          color: '#ff7f0e',
+          width: 2
+        }
+      }
+    ]
+  };
+}
 
-  },
+// 降酸模型散点图
+function getFinalScatterOption() {
+  const calculateDataRange = (data) => {
+    let xValues = data.map(item => item[0]);
+    let yValues = data.map(item => item[1]);
+    
+    return {
+      xMin: Math.min(...xValues),
+      xMax: Math.max(...xValues),
+      yMin: Math.min(...yValues),
+      yMax: Math.max(...yValues)
+    };
+  };
 
-  /**
-   * 页面上拉触底事件的处理函数
-   */
-  onReachBottom() {
+  const scatterData = [[0.12931, 0.1144982841836412], [0.066298, 0.07733075000000009], 
+  [0.057692, 0.07784833000000009], [0.072464, 0.09429906104591025], 
+  [0.146414, 0.11774272000000004], [0.087263, 0.07587641627546172], 
+  [0.096983, 0.12344755999999983], [0.070505, 0.06424743000000006], 
+  [0.052497, 0.07665224568865422], [0.166515, 0.15591779999999988], 
+  [0.063291, 0.09275175104591024]];
 
-  },
+  const range = calculateDataRange(scatterData);
+  const padding = 0.1;
+  const xMin = range.xMin - Math.abs(range.xMin * padding);
+  const xMax = range.xMax + Math.abs(range.xMax * padding);
+  const yMin = range.yMin - Math.abs(range.yMin * padding);
+  const yMax = range.yMax + Math.abs(range.yMax * padding);
+  const min = Math.min(xMin, yMin)
+  const max = Math.max(xMax, yMax)
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross'
+      }
+    },
+    legend: {
+      data: ['True vs Predicted']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      name: 'True Values',
+      type: 'value',
+      min: min,
+      max: max
+    },
+    yAxis: {
+      name: 'Predicted Values',
+      type: 'value',
+      min: parseFloat(min).toFixed(2),
+      max: parseFloat(max).toFixed(2)
+    },
+    series: [
+      {
+        name: 'True vs Predicted',
+        type: 'scatter',
+        data: scatterData,
+        symbolSize: 10,
+        itemStyle: {
+          color: '#1f77b4',
+          opacity: 0.7
+        }
+      },
+      {
+        name: 'Trendline',
+        type: 'line',
+        data: [
+          [min, min],
+          [max, max]
+        ],
+        lineStyle: {
+          type: 'dashed',
+          color: '#ff7f0e',
+          width: 2
+        }
+      }
+    ]
+  };
+}
 
-  /**
-   * 用户点击右上角分享
-   */
-  onShareAppMessage() {
 
-  }
-})
+// 降酸模型折线图
+function getLineOption() {
+  return {
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: ['Random Forest', 'XGBoost', 'Gradient Boosting']  // 模型名称
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: ['10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%']  // train_sizes按10%递增
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [
+      {
+        name: 'Random Forest',
+        type: 'line',
+        data: [-0.07014332070467688, 0.16174446067644666, 0.45539363923645115, 0.42496898634299696, 0.4538427686692541, 0.418902219699232, 0.3944331740214546, 0.5036773554206873, 0.6231911298905934, 0.7017514238881619]
+      },
+      {
+        name: 'XGBoost',
+        type: 'line',
+        data: [-0.5854699949971149, 0.12632204933742897, 0.5552415957727412, 0.36024885147666674, 0.3983302490476677, 0.11693728875627551, 0.41317321044147026, 0.7833174829404705, 0.6626050730438451, 0.8168196535959388]
+      },
+      {
+        name: 'Gradient Boosting',
+        type: 'line',
+        data: [-0.2099996080229254, 0.2604127444757308, 0.5544068626708099, 0.37487088504748367, 0.32309657868722486, 0.24740671740183884, 0.4949107161199925, 0.694806526591159, 0.6719430144475025, 0.7889677514137288]
+      }
+    ]
+  };
+}

+ 3 - 1
shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.json

@@ -1,5 +1,7 @@
 {
-  "usingComponents": {},
+  "usingComponents": {
+    "ec-canvas": "/components/ec-canvas/ec-canvas"
+  },
   "navigationBarTitleText": "降酸模型迭代进化",
   "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"  

+ 17 - 2
shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxml

@@ -1,2 +1,17 @@
-<!--pages/Regular User/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxml-->
-<text>这里可以放"降酸模型"计算图片</text>
+<view class="container">
+  <view class="chart-container">
+    <ec-canvas id="lineChart" canvas-id="lineChart" ec="{{ecLine}}"></ec-canvas>
+  </view>
+  <text class="sub-title">初代散点图</text>
+  <view class="chart-container">
+    <ec-canvas id="scatterChart" canvas-id="initScatter" ec="{{ecInitScatter}}"></ec-canvas>
+  </view>
+  <text class="sub-title">中间代散点图</text>
+  <view class="chart-container">
+    <ec-canvas id="scatterChart" canvas-id="midScatter" ec="{{ecMidScatter}}"></ec-canvas>
+  </view>
+  <text class="sub-title">最终代散点图</text>
+  <view class="chart-container">
+    <ec-canvas id="scatterChart" canvas-id="finalScatter" ec="{{ecFinalScatter}}"></ec-canvas>
+  </view>
+</view>

+ 24 - 1
shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxss

@@ -1 +1,24 @@
-/* pages/Regular User/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution.wxss */
+.container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  gap: 20px; /* 添加容器之间的间隔 */
+}
+
+.chart-container {
+  width: 100%;
+  height: 300px;
+  margin-bottom: 20px; /* 添加底部间隔 */
+}
+.sub-title {
+  font-size: 14px;
+  font-weight:700;
+}
+.ec-canvas {
+  width: 100%;
+  height: 100%;
+  margin: 0 10px; /* 添加左右间隔 */
+}

+ 244 - 208
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js

@@ -2,19 +2,6 @@ import * as echarts from '../../components/ec-canvas/echarts';
 
 Page({
   data: {
-    ecBox: {
-      onInit: function (canvas, width, height, dpr) {
-        const boxChart = echarts.init(canvas, null, {
-          width: width,
-          height: height,
-          devicePixelRatio: dpr // new
-        });
-        canvas.setChart(boxChart);
-        boxChart.setOption(getBoxOption());
-
-        return boxChart;
-      }
-    },
     ecLine: {
       onInit: function (canvas, width, height, dpr) {
         const lineChart = echarts.init(canvas, null, {
@@ -28,44 +15,45 @@ Page({
         return lineChart;
       }
     },
-    ecScatter: {
+    ecInitScatter: {
       onInit: function (canvas, width, height, dpr) {
-        const scatterChart = echarts.init(canvas, null, {
+        const initScatter = echarts.init(canvas, null, {
           width: width,
           height: height,
           devicePixelRatio: dpr // new
         });
-        canvas.setChart(scatterChart);
-        scatterChart.setOption(getScatterOption());
+        canvas.setChart(initScatter);
+        initScatter.setOption(getInitScatterOption());
 
-        return scatterChart;
+        return initScatter;
       }
     },
-    ecBar: {
-      onInit: function (canvas, width, height, dpr) {
-        const barChart = echarts.init(canvas, null, {
-          width: width,
-          height: height,
-          devicePixelRatio: dpr
-        });
-        canvas.setChart(barChart);
-        barChart.setOption(getBarOption());
-        return barChart;
-      }
-    },
-    ecPie: {
+  ecMidScatter: {
+    onInit: function (canvas, width, height, dpr) {
+      const midScatter = echarts.init(canvas, null, {
+        width: width,
+        height: height,
+        devicePixelRatio: dpr // new
+      });
+      canvas.setChart(midScatter);
+      midScatter.setOption(getMidScatterOption());
+
+      return midScatter;
+    }
+  },
+    ecFinalScatter: {
       onInit: function (canvas, width, height, dpr) {
-        const pieChart = echarts.init(canvas, null, {
+        const finalScatter = echarts.init(canvas, null, {
           width: width,
           height: height,
           devicePixelRatio: dpr // new
         });
-        canvas.setChart(pieChart);
-        pieChart.setOption(getPieOption());
+        canvas.setChart(finalScatter);
+        finalScatter.setOption(getFinalScatterOption());
 
-        return pieChart;
+        return finalScatter;
       }
-    }
+    },
   },
 
   onReady() {
@@ -73,19 +61,45 @@ Page({
   }
 });
 
-function getBarOption() {
+// 反酸模型  初代  散点图
+function getInitScatterOption() {
+  /**
+   * 计算数据的最大最小值
+   * @param {Array} data - 散点数据数组
+   * @returns {Object} 包含 xMin, xMax, yMin, yMax 的对象
+   */
+  const calculateDataRange = (data) => {
+    let xValues = data.map(item => item[0]);
+    let yValues = data.map(item => item[1]);
+    
+    return {
+      xMin: Math.min(...xValues),
+      xMax: Math.max(...xValues),
+      yMin: Math.min(...yValues),
+      yMax: Math.max(...yValues)
+    };
+  };
+
+  const scatterData = [[-0.003333333333333854, -0.4181333333333324], [-0.1733333333333329, -0.26733333333333265], [-0.6233333333333331, -0.3718666666666661], [-0.7088888888888892, -0.3854666666666661], [-0.3366666666666669, -0.3998666666666657], [-0.8888888888888887, -0.36439999999999934], [-0.5633333333333326, -0.6207999999999997], [-0.7333333333333325, -0.36026666666666607], [-0.3366666666666663, -0.29213333333333275], [-1.176666666666666, -0.6095999999999993], [-0.7122222222222225, -0.3807999999999989], [-0.7699999999999996, -0.3718666666666661]];
+
+  const range = calculateDataRange(scatterData);
+  const padding = 0.1;
+  const xMin = range.xMin - Math.abs(range.xMin * padding);
+  const xMax = range.xMax + Math.abs(range.xMax * padding);
+  const yMin = range.yMin - Math.abs(range.yMin * padding);
+  const yMax = range.yMax + Math.abs(range.yMax * padding);
+  const min = Math.min(xMin, yMin)
+  const max = Math.max(xMax, yMax)
+
   return {
-    title: {
-      text: 'Basic Bar Chart'
-    },
     tooltip: {
       trigger: 'axis',
       axisPointer: {
-        type: 'shadow'
+        type: 'cross'
       }
     },
     legend: {
-      data: ['Sales']
+      data: ['True vs Predicted']
     },
     grid: {
       left: '3%',
@@ -94,162 +108,218 @@ function getBarOption() {
       containLabel: true
     },
     xAxis: {
-      type: 'category',
-      data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']
+      name: 'True Values',
+      type: 'value',
+      min: min,
+      max: max
     },
     yAxis: {
-      type: 'value'
+      name: 'Predicted Values',
+      type: 'value',
+      min: parseFloat(min).toFixed(2),
+      max: parseFloat(max).toFixed(2)
     },
     series: [
       {
-        name: 'Sales',
-        type: 'bar',
-        data: [5, 20, 36, 10, 10, 20, 30],
+        name: 'True vs Predicted',
+        type: 'scatter',
+        data: scatterData,
+        symbolSize: 10,
         itemStyle: {
-          color: '#c23531'
+          color: '#1f77b4',
+          opacity: 0.7
+        }
+      },
+      {
+        name: 'Trendline',
+        type: 'line',
+        data: [
+          [min, min],
+          [max, max]
+        ],
+        lineStyle: {
+          type: 'dashed',
+          color: '#ff7f0e',
+          width: 2
         }
       }
     ]
-  }
+  };
 }
-function getBoxOption() {
+
+// 反酸模型  中间代  散点图
+function getMidScatterOption() {
+  /**
+   * 计算数据的最大最小值
+   * @param {Array} data - 散点数据数组
+   * @returns {Object} 包含 xMin, xMax, yMin, yMax 的对象
+   */
+  const calculateDataRange = (data) => {
+    let xValues = data.map(item => item[0]);
+    let yValues = data.map(item => item[1]);
+    
+    return {
+      xMin: Math.min(...xValues),
+      xMax: Math.max(...xValues),
+      yMin: Math.min(...yValues),
+      yMax: Math.max(...yValues)
+    };
+  };
+
+  const scatterData = [[-0.003333333333333854, -0.5483999999999993], [-0.1733333333333329, -0.2093333333333331], [-0.6233333333333331, -0.5090000000000002], [-0.7088888888888892, -0.4281333333333331], [-0.3366666666666669, -0.4691333333333336], [-0.8888888888888887, -0.49643333333333267], [-0.5633333333333326, -0.7191999999999996], [-0.7333333333333325, -0.5024666666666666], [-0.3366666666666663, -0.37796666666666623], [-1.176666666666666, -0.6415666666666656], [-0.7122222222222225, -0.43299999999999966], [-0.7699999999999996, -0.499966666666667]];
+
+  const range = calculateDataRange(scatterData);
+  const padding = 0.1;
+  const xMin = range.xMin - Math.abs(range.xMin * padding);
+  const xMax = range.xMax + Math.abs(range.xMax * padding);
+  const yMin = range.yMin - Math.abs(range.yMin * padding);
+  const yMax = range.yMax + Math.abs(range.yMax * padding);
+  const min = Math.min(xMin, yMin)
+  const max = Math.max(xMax, yMax)
   return {
-    title: {
-      text: 'Sample ΔpH'
-  },
-  tooltip: {
-      trigger: 'item',
+    tooltip: {
+      trigger: 'axis',
       axisPointer: {
-          type: 'shadow'
+        type: 'cross'
       }
-  },
-  grid: {
+    },
+    legend: {
+      data: ['True vs Predicted']
+    },
+    grid: {
       left: '3%',
       right: '4%',
       bottom: '3%',
       containLabel: true
-  },
-  xAxis: {
-      type: 'category',
-      data: Array.from({length: 27}, (_, i) => i + 1) // 生成 1 到 27 的样本编号
-  },
-  yAxis: {
+    },
+    xAxis: {
+      name: 'True Values',
       type: 'value',
-      name: 'ΔpH',
-      min: -1.5,
-      max: 0.5
-  },
-  series: [
+      min: min,
+      max: max
+    },
+    yAxis: {
+      name: 'Predicted Values',
+      type: 'value',
+      min: parseFloat(min).toFixed(2),
+      max: parseFloat(max).toFixed(2)
+    },
+    series: [
       {
-          name: 'ΔpH',
-          type: 'boxplot',
-          data: [
-              // 每个数组代表一个样本的箱型图数据
-              // 格式为 [min, Q1, median, Q3, max],可以包含异常值作为单独的数组元素
-                [-0.8, -0.6, -0.5, -0.3, 0.0], 
-                [-0.7, -0.5, -0.4, -0.2, 0.1], 
-                [-0.9, -0.7, -0.6, -0.4, -0.1], 
-                [-1.0, -0.8, -0.7, -0.5, -0.3], 
-                [-1.1, -0.9, -0.8, -0.6, -0.4], 
-                [-1.2, -1.0, -0.9, -0.7, -0.5], 
-                [-1.3, -1.1, -1.0, -0.8, -0.6], 
-                [-1.4, -1.2, -1.1, -0.9, -0.7], 
-                [-1.5, -1.3, -1.2, -1.0, -0.8], 
-                [-1.4, -1.2, -1.1, -0.9, -0.7], 
-                [-1.3, -1.1, -1.0, -0.8, -0.6], 
-                [-1.2, -1.0, -0.9, -0.7, -0.5], 
-                [-1.1, -0.9, -0.8, -0.6, -0.4], 
-                [-1.0, -0.8, -0.7, -0.5, -0.3], 
-                [-0.9, -0.7, -0.6, -0.4, -0.1], 
-                [-0.8, -0.6, -0.5, -0.3, 0.0], 
-                [-0.7, -0.5, -0.4, -0.2, 0.1], 
-                [-0.6, -0.4, -0.3, -0.1, 0.2], 
-                [-0.5, -0.3, -0.2, 0.0, 0.3], 
-                [-0.4, -0.2, -0.1, 0.1, 0.4], 
-                [-0.3, -0.1, 0.0, 0.2, 0.5], 
-                [-0.2, 0.0, 0.1, 0.3, 0.6], 
-                [-0.1, 0.1, 0.2, 0.4, 0.7], 
-                [0.0, 0.2, -0.3, 0.5, 0.8], 
-                [-0.1, 0.3, -0.4, 0.6, 0.9], 
-                [-0.2, 0.4, -0.5, 0.7, 1.0], 
-                [-0.3, 0.5, -0.6, 0.8, 1.1]  
-          ],
-          tooltip: {
-              formatter: function (param) {
-                  return [
-                      'Experiment ' + param.name + ': ',
-                      'upper: ' + param.data[5],
-                      'Q3: ' + param.data[4],
-                      'median: ' + param.data[3],
-                      'Q1: ' + param.data[2],
-                      'lower: ' + param.data[1]
-                  ].join(' ');
-              }
-          }
+        name: 'True vs Predicted',
+        type: 'scatter',
+        data: scatterData,
+        symbolSize: 10,
+        itemStyle: {
+          color: '#1f77b4',
+          opacity: 0.7
+        }
+      },
+      {
+        name: 'Trendline',
+        type: 'line',
+        data: [
+          [min, min],
+          [max, max]
+        ],
+        lineStyle: {
+          type: 'dashed',
+          color: '#ff7f0e',
+          width: 2
+        }
       }
-  ]
+    ]
   };
 }
+// 反酸模型  最终代  散点图
+function getFinalScatterOption() {
+  /**
+   * 计算数据的最大最小值
+   * @param {Array} data - 散点数据数组
+   * @returns {Object} 包含 xMin, xMax, yMin, yMax 的对象
+   */
+  const calculateDataRange = (data) => {
+    let xValues = data.map(item => item[0]);
+    let yValues = data.map(item => item[1]);
+    
+    return {
+      xMin: Math.min(...xValues),
+      xMax: Math.max(...xValues),
+      yMin: Math.min(...yValues),
+      yMax: Math.max(...yValues)
+    };
+  };
+
+  const scatterData = [[-0.003333333333333854, -0.45726666666666654], [-0.1733333333333329, -0.1726333333333331], 
+                    [-0.6233333333333331, -0.5226666666666667], [-0.7088888888888892, -0.4791888888888889], 
+                    [-0.3366666666666669, -0.3630666666666673], [-0.8888888888888887, -0.48272222222222183], 
+                    [-0.5633333333333326, -0.7492444444444444], [-0.7333333333333325, -0.5572666666666672], 
+                    [-0.3366666666666663, -0.29379999999999984], [-1.176666666666666, -0.8544111111111106], 
+                    [-0.7122222222222225, -0.4959777777777775], [-0.7699999999999996, -0.6149666666666669]];
+
+  const range = calculateDataRange(scatterData);
+  const padding = 0.1;
+  const xMin = range.xMin - Math.abs(range.xMin * padding);
+  const xMax = range.xMax + Math.abs(range.xMax * padding);
+  const yMin = range.yMin - Math.abs(range.yMin * padding);
+  const yMax = range.yMax + Math.abs(range.yMax * padding);
+  const min = Math.min(xMin, yMin)
+  const max = Math.max(xMax, yMax)
 
-function getScatterOption() {
   return {
-        tooltip: {
-          trigger: 'axis',
-          axisPointer: {
-            type: 'cross'
-          }
-        },
-        legend: {
-          data: ['True vs Predicted']
-        },
-        grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
-          containLabel: true
-        },
-        xAxis: {
-          name: 'True Values',  // 对应 Python 中的 Ytest
-          type: 'value',
-          boundaryGap: [0, 0.01]
-        },
-        yAxis: {
-          name: 'Predicted Values',  // 对应 Python 中的 y_pred
-          type: 'value',
-          boundaryGap: [0, 0.01]
-        },
-        series: [
-          {
-            name: 'True vs Predicted',
-            type: 'scatter',
-            data: [
-              [-0.003333333333333854, -0.45726666666666654], [-0.1733333333333329, -0.1726333333333331], 
-              [-0.6233333333333331, -0.5226666666666667], [-0.7088888888888892, -0.4791888888888889], 
-              [-0.3366666666666669, -0.3630666666666673], [-0.8888888888888887, -0.48272222222222183], 
-              [-0.5633333333333326, -0.7492444444444444], [-0.7333333333333325, -0.5572666666666672], 
-              [-0.3366666666666663, -0.29379999999999984], [-1.176666666666666, -0.8544111111111106], 
-              [-0.7122222222222225, -0.4959777777777775], [-0.7699999999999996, -0.6149666666666669]
-            ],
-            symbolSize: 10,
-            itemStyle: {
-              
-            }
-          },
-          {
-            name: 'Trendline',
-            type: 'line',
-            data: [
-              // 绘制对角线 y = x (这就是理想情况下 True 和 Predicted 的值相等)
-              [-1.2,-1.2],  // 最小值
-              [-0.0034, -0.0034]   // 最大值
-            ],
-            lineStyle: {
-              type: 'dashed',
-              
-            }
-          }
-        ]
-      };
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'cross'
+      }
+    },
+    legend: {
+      data: ['True vs Predicted']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      name: 'True Values',
+      type: 'value',
+      min: min,
+      max: max
+    },
+    yAxis: {
+      name: 'Predicted Values',
+      type: 'value',
+      min: parseFloat(min).toFixed(2),
+      max: parseFloat(max).toFixed(2)
+    },
+    series: [
+      {
+        name: 'True vs Predicted',
+        type: 'scatter',
+        data: scatterData,
+        symbolSize: 10,
+        itemStyle: {
+          color: '#1f77b4',
+          opacity: 0.7
+        }
+      },
+      {
+        name: 'Trendline',
+        type: 'line',
+        data: [
+          [min, min],
+          [max, max]
+        ],
+        lineStyle: {
+          type: 'dashed',
+          color: '#ff7f0e',
+          width: 2
+        }
+      }
+    ]
+  };
 }
 
 function getLineOption() {
@@ -293,37 +363,3 @@ function getLineOption() {
     ]
   };
 }
-
-function getPieOption() {
-  return {
-    tooltip: {
-      trigger: 'item',
-      formatter: '{a} <br/>{b} : {c} ({d}%)'
-    },
-    legend: {
-      orient: 'vertical',
-      left: 'left',
-      data: ['示例1', '示例2', '示例3']
-    },
-    series: [
-      {
-        name: '访问来源',
-        type: 'pie',
-        radius: '55%',
-        center: ['50%', '60%'],
-        data: [
-          { value: 335, name: '示例1' },
-          { value: 310, name: '示例2' },
-          { value: 234, name: '示例3' },
-        ],
-        emphasis: {
-          itemStyle: {
-            shadowBlur: 10,
-            shadowOffsetX: 0,
-            shadowColor: 'rgba(0, 0, 0, 0.5)'
-          }
-        }
-      }
-    ]
-  };
-}

+ 10 - 3
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml

@@ -1,11 +1,18 @@
 <view class="container">
   <view class="chart-container">
-    <ec-canvas id="boxChart" canvas-id="boxChart" ec="{{ecBox}}"></ec-canvas>
+    <ec-canvas id="lineChart" canvas-id="lineChart" ec="{{ecLine}}"></ec-canvas>
   </view>
+  <text class="sub-title">初代散点图</text>
   <view class="chart-container">
-    <ec-canvas id="lineChart" canvas-id="lineChart" ec="{{ecLine}}"></ec-canvas>
+    <ec-canvas id="scatterChart" canvas-id="initScatter" ec="{{ecInitScatter}}"></ec-canvas>
+  </view>
+  
+  <text class="sub-title">中间代散点图</text>
+  <view class="chart-container">
+    <ec-canvas id="scatterChart" canvas-id="midScatter" ec="{{ecMidScatter}}"></ec-canvas>
   </view>
+  <text class="sub-title">最终代散点图</text>
   <view class="chart-container">
-    <ec-canvas id="scatterChart" canvas-id="scatterChart" ec="{{ecScatter}}"></ec-canvas>
+    <ec-canvas id="scatterChart" canvas-id="finalScatter" ec="{{ecFinalScatter}}"></ec-canvas>
   </view>
 </view>

+ 4 - 1
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxss

@@ -7,7 +7,10 @@
   height: 100%;
   gap: 20px; /* 添加容器之间的间隔 */
 }
-
+.sub-title {
+  font-size: 14px;
+  font-weight:700;
+}
 .chart-container {
   width: 100%;
   height: 300px;

+ 10 - 33
shoping/Soil Acidification/Soil Acidification.wxml

@@ -1,51 +1,28 @@
-<view class="containe">
+
   <text class="regular-text">
     <text class="sub-title">土壤反酸模型使用注意说明</text>
-    感谢您选择使用“广东省生态环境与土壤研究所”研发的反酸模型。该模型基于土壤理化性质和机器学习算法,能够预测土壤反酸后的 pH 值。为确保模型的有效性,请注意以下事项:
-
+    感谢您选择使用“广东省生态环境与土壤研究所”研发的反酸模型。该模型基于土壤理化性质和机器学习算法,能够预测土壤反酸后的 pH 值。请注意以下事项:
 <text class="sub-title">1. 数据输入要求</text>
 <text class="sub-title"> \n土壤理化参数:</text>
 <text class="sub-title">\n土壤粘粒含量(%):</text>请确保输入数据经过实验室精确测量。
 <text class="sub-title">交换性铝离子浓度(cmol/kg):</text>建议使用标准土壤分析方法进行测定。
 <text class="sub-title">有机质含量(%):</text>数据应基于准确的土壤样品测试。
 <text class="sub-title">游离氧化铝(g/kg):</text>确保数据来源可靠,测定方法符合国家或行业标准。
-
 输入值必须在合理范围内,不应有异常数据,避免影响预测结果的准确性。
-<text class="sub-title">\n2. 预测结果解读</text>
-预测结果为模型根据输入数据计算的 pH 值,因土壤特殊性可能会有一定误差。
-结果应与实际土壤管理措施结合使用,不应仅依赖预测值进行决策。
-
-<text class="sub-title">3. 不确定性分析</text>
-本模型适用于强酸性旱地土壤(pH < 5.5),对于其他类型的土壤,预测结果可能偏离实际情况。
-模型基于已筛选的关键指标,输入超出这些范围的数据可能导致不准确的预测结果。
-    </text>
-</view>
-
-<image class="navigat-arrow" src="/assets/taddar/微信图片_20241209174643.png" />
-
+<text class="sub-title">2. 模型进化能力</text>
+<text class="sub-title">\n模型迭代:</text>我们的模型将会随着用户数据量的增加和多样化,逐步迭代进化,提升预测的准确性和泛化能力。
+<text class="sub-title">用户贡献:</text>您提交的真实数据和反馈将为模型的优化和进步提供宝贵的支持。
+</text>
 <view class="container">
   <button class="full-width-button" bindtap="Calculation">反酸模型计算</button>
-  <button class="full-width-button" bindtap="Soil_Acidification_Iterative_Evolution">反酸模型迭代化</button>
+  <button class="full-width-button" bindtap="Soil_Acidification_Iterative_Evolution">反酸模型迭代可视化</button>
 </view>
-
-<view class="containe">
-  <view class="containes">
-  <text class="regular-text">
-    <text class="sub-title">4. 模型进化能力</text>
-<text class="sub-title">\n模型迭代:</text>我们的模型将会随着用户数据量的增加和多样化,逐步迭代进化,提升预测的准确性和泛化能力。
-<text class="sub-title">用户贡献:</text>您提交的真实数据和反馈将为模型的优化和进步提供宝贵的支持。
-
-<text class="sub-title">5. 技术支持</text>
+<text class="regular-text">
+<text class="sub-title">3. 技术支持</text>
 如果在使用过程中遇到问题或对模型预测有疑问,请联系技术支持团队。
 邮箱:support@example.com
 单位:广东省生态环境与土壤研究所
-
-<text class="sub-title">6.免责声明</text>
-本软件仅供科研和教学用途,预测结果需与实际田间数据结合使用。
-因使用本软件导致的任何决策后果,开发团队不承担法律责任。
-    </text>
-  </view>
-</view>
+</text>
 
 <nav-tabar selected="{{selected}}"></nav-tabar> 
 

+ 12 - 35
shoping/Soil Deacidification/Soil Deacidification.wxml

@@ -1,49 +1,26 @@
-<view class="containe">
-  <text class="regular-text">
-    <text class="sub-title">降酸模型使用说明</text>
-    该模型旨在帮助提升土壤 pH 值,通过输入土壤起始 pH 和目标 pH,精准计算不同类型碱性物料的施用量(如石灰石粉、生石灰、熟石灰、白云石等)。为确保模型效果与使用体验,请注意以下事项:
-
+<text class="regular-text">
+  <text class="sub-title">降酸模型使用说明</text>
+    该模型旨在帮助提升土壤 pH 值,通过输入土壤起始 pH 和目标 pH,精准计算石灰石的施用。为确保模型效果与使用体验,请注意以下事项:
 <text class="sub-title">1. 数据输入要求</text>
 <text class="sub-title"> \n必填参数:</text>
 <text class="sub-title">\n土壤起始 pH 值:</text>需通过实验室准确测定。
 <text class="sub-title">目标 pH 值:</text>根据土壤改良目标设置。
 <text class="sub-title">关键理化指标:</text>包括土壤质地、有机质含量等。
-\n输入数据必须符合实际测量值,避免因数据异常或错误导致计算结果偏差。
-
-<text class="sub-title">2. 使用范围</text>
-• 本模型适用于酸性土壤的碱性物料施用量计算,尤其适用于 pH < 5.5 的耕地土壤。
-• 其他类型的土壤(如碱性或中性土壤)可能不适用。
-
-<text class="sub-title">3. 注意事项</text>
-•	输入土壤起始 pH 和目标 pH 差值(ΔpH)不宜过大,避免因物料施用量过多导致次生盐碱化等问题。
-• 在实际田间施用时,应结合当地农业实践和技术指导,确保施用量和操作方式的适用性与安全性。
-    </text>
-</view>
-
-<image class="navigat-arrow" src="/assets/taddar/微信图片_20241209174946.png" />
-
+输入数据必须符合实际测量值,避免因数据异常或错误导致计算结果偏差。
+<text class="sub-title">2. 模型进化能力</text>
+本模型将持续根据用户上传的真实数据优化和进化,提升预测精度和适用范围。
+用户的数据贡献将为改良土壤健康提供宝贵支持。
+</text>
 <view class="container">
   <button class="full-width-button" bindtap="AcidNeutralizationModel">降酸模型计算</button>
-  <button class="full-width-button" bindtap="Soil_Acid_Reduction_Iterative_Evolution">降酸模型迭代化</button>
+  <button class="full-width-button" bindtap="Soil_Acid_Reduction_Iterative_Evolution">降酸模型迭代可视化</button>
 </view>
 
-<view class="containe">
-  <view class="containes">
-  <text class="regular-text">
-    <text class="sub-title">4. 模型进化能力</text>
-•	本模型将持续根据用户上传的真实数据优化和进化,提升预测精度和适用范围。
-•	用户的数据贡献将为改良土壤健康提供宝贵支持。
-
-<text class="sub-title">5. 技术支持</text>
+<text class="regular-text">
+<text class="sub-title">3. 技术支持</text>
 如果在使用过程中遇到问题或对模型预测有疑问,请联系技术支持团队。
 邮箱:support@example.com
 单位:广东省生态环境与土壤研究所
-
-<text class="sub-title">6.免责声明</text>
-本软件仅供科研和教学用途,预测结果需与实际田间数据结合使用。
-因使用本软件导致的任何决策后果,开发团队不承担法律责任。
-    </text>
-  </view>
-</view>
+</text>
 
 <nav-tabar selected="{{selected}}"></nav-tabar> 

+ 1 - 6
shoping/Unit Team Profile/Unit Team Profile.wxml

@@ -3,12 +3,7 @@
 </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>
+    <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="" />
-
-<view class="green-box">
-  <text class="title-en">项目团队成员简介</text>
-</view>

+ 55 - 0
shoping/thres/thres.js

@@ -0,0 +1,55 @@
+Page({
+  data: {
+    countOptions: ['25', '40', '60'], // 条数阈值选项
+    percentageOptions: ['25', '40', '60'], // 百分比阈值选项
+
+    selectedCountIndex: 0, // 条数默认选择
+    selectedPercentageIndex: 0, // 百分比默认选择
+
+    countThresh: 25, // 当前条数阈值
+    percentageThresh: 25, // 当前百分比阈值
+  },
+
+  // 条数选择框变化
+  onCountPickerChange(e) {
+    const selectedValue = this.data.countOptions[e.detail.value];
+    this.setData({
+      selectedCountIndex: e.detail.value,
+      countThresh: selectedValue, // 更新到输入框
+    });
+  },
+
+  // 百分比选择框变化
+  onPercentagePickerChange(e) {
+    const selectedValue = this.data.percentageOptions[e.detail.value];
+    this.setData({
+      selectedPercentageIndex: e.detail.value,
+      percentageThresh: selectedValue, // 更新到输入框
+    });
+  },
+
+  // 更新条数阈值输入框
+  updateCountThreshold(e) {
+    this.setData({
+      countThresh: e.detail.value, // 允许手动输入
+    });
+  },
+
+  // 更新百分比阈值输入框
+  updatePercentageThreshold(e) {
+    this.setData({
+      percentageThresh: e.detail.value, // 允许手动输入
+    });
+  },
+
+  // 保存设置
+  saveSettings() {
+    wx.showToast({
+      title: '保存成功',
+      icon: 'success',
+    });
+
+    console.log('条数阈值:', this.data.countThresh);
+    console.log('百分比阈值:', this.data.percentageThresh);
+  },
+});

+ 6 - 0
shoping/thres/thres.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "阈值选择",
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"  
+}

+ 53 - 0
shoping/thres/thres.wxml

@@ -0,0 +1,53 @@
+<view class="green-box">
+  <text class="title-en">降酸模型</text>
+</view>
+
+<view class="container">
+  <view class="row">
+    <!-- 条数阈值 -->
+    <view class="input-group">
+      <text class="label">条数阈值:</text>
+      <picker mode="selector" range="{{countOptions}}" value="{{selectedCountIndex}}" bindchange="onCountPickerChange">
+        <text class="picker-text">{{countOptions[selectedCountIndex]}} 条</text>
+      </picker>
+      <input type="number" value="{{countThresh}}" bindinput="updateCountThreshold" class="input" />
+    </view>
+
+    <!-- 百分比阈值 -->
+    <view class="input-group">
+      <text class="label">百分比阈值:</text>
+      <picker mode="selector" range="{{percentageOptions}}" value="{{selectedPercentageIndex}}" bindchange="onPercentagePickerChange">
+        <text class="picker-text">{{percentageOptions[selectedPercentageIndex]}}%</text>
+      </picker>
+      <input type="number" value="{{percentageThresh}}" bindinput="updatePercentageThreshold" class="input" />
+    </view>
+  </view>
+  <button class="save-btn" bindtap="saveSettings">保存</button>
+</view>
+
+<view class="green-box">
+  <text class="title-en">反酸模型</text>
+</view>
+
+<view class="container">
+  <view class="row">
+    <!-- 条数阈值 -->
+    <view class="input-group">
+      <text class="label">条数阈值:</text>
+      <picker mode="selector" range="{{countOptions}}" value="{{selectedCountIndex}}" bindchange="onCountPickerChange">
+        <text class="picker-text">{{countOptions[selectedCountIndex]}} 条</text>
+      </picker>
+      <input type="number" value="{{countThresh}}" bindinput="updateCountThreshold" class="input" />
+    </view>
+
+    <!-- 百分比阈值 -->
+    <view class="input-group">
+      <text class="label">百分比阈值:</text>
+      <picker mode="selector" range="{{percentageOptions}}" value="{{selectedPercentageIndex}}" bindchange="onPercentagePickerChange">
+        <text class="picker-text">{{percentageOptions[selectedPercentageIndex]}}%</text>
+      </picker>
+      <input type="number" value="{{percentageThresh}}" bindinput="updatePercentageThreshold" class="input" />
+    </view>
+  </view>
+  <button class="save-btn" bindtap="saveSettings">保存</button>
+</view>

+ 75 - 0
shoping/thres/thres.wxss

@@ -0,0 +1,75 @@
+/* 页面整体背景 */
+.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 {
+  margin: 20rpx;
+  padding: 10rpx;
+}
+
+.row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.input-group {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  margin-right: 20rpx;
+}
+
+.label {
+  font-size: 30rpx;
+  color: #333;
+  margin-right: 10rpx;
+}
+
+.picker-text {
+  font-size: 28rpx;
+  color: #007acc; /* 选择框的文字颜色 */
+  margin-right: 10rpx;
+}
+
+.input {
+  width: 200rpx;
+  padding: 7rpx;
+  font-size: 28rpx;
+  border: 1px solid #ccc;
+  border-radius: 8rpx;
+  color: #333;
+}
+
+.save-btn {
+  width: 50%;
+  padding: 5rpx;
+  background-color: #e6f7ff; /* 保存按钮的背景颜色 */
+  color: #303030;
+  border-radius: 10rpx;
+  font-size: 32rpx;
+  text-align: center;
+}
+
+.countOptions {
+  font-weight: 400;
+}