Browse Source

合并目前代码

yangtaodemon 7 months ago
parent
commit
6ae7b4de2f
100 changed files with 2693 additions and 578 deletions
  1. 1 1
      .idea/.name
  2. 2 0
      README.md
  3. BIN
      api/SoilAcidification.db
  4. 6 1
      api/app/__init__.py
  5. 64 40
      api/app/database_models.py
  6. 206 15
      api/app/model.py
  7. 230 83
      api/app/routes.py
  8. BIN
      api/pkl/rf_model_1222_1922.pkl
  9. BIN
      api/pkl/rf_model_1222_1952.pkl
  10. BIN
      api/pkl/rf_model_1222_1954.pkl
  11. BIN
      api/pkl/rf_model_1222_1959.pkl
  12. BIN
      api/pkl/rf_model_1222_2012.pkl
  13. BIN
      api/uploads/datasets/dataset_2.xlsx
  14. BIN
      api/uploads/datasets/dataset_29.xlsx
  15. BIN
      api/uploads/datasets/dataset_30.xlsx
  16. BIN
      api/uploads/datasets/dataset_31.xlsx
  17. BIN
      api/uploads/datasets/dataset_32.xlsx
  18. BIN
      api/uploads/datasets/dataset_33.xlsx
  19. BIN
      api/uploads/datasets/dataset_34.xlsx
  20. BIN
      api/uploads/datasets/dataset_35.xlsx
  21. BIN
      api/uploads/datasets/dataset_36.xlsx
  22. BIN
      api/uploads/datasets/dataset_37.xlsx
  23. BIN
      api/uploads/datasets/dataset_38.xlsx
  24. BIN
      api/uploads/datasets/dataset_39.xlsx
  25. BIN
      api/uploads/datasets/dataset_40.xlsx
  26. BIN
      api/uploads/datasets/dataset_41.xlsx
  27. BIN
      api/uploads/datasets/dataset_42.xlsx
  28. BIN
      api/uploads/datasets/dataset_6.xlsx
  29. BIN
      api/uploads/datasets/dataset_8.xlsx
  30. 24 4
      app.js
  31. 43 33
      app.json
  32. 0 12
      app.wxss
  33. BIN
      assets/taddar/12.png
  34. BIN
      assets/taddar/QQ.png
  35. BIN
      assets/taddar/微信.png
  36. BIN
      assets/taddar/微信图片_20241209174643.png
  37. BIN
      assets/taddar/微信图片_20241209174946.png
  38. BIN
      assets/taddar/我的 (1).png
  39. BIN
      assets/taddar/我的.png
  40. BIN
      assets/taddar/模型数据表 (1).png
  41. BIN
      assets/taddar/模型数据表.png
  42. BIN
      assets/taddar/模型计算 (1).png
  43. BIN
      assets/taddar/模型计算.png
  44. BIN
      assets/taddar/设置 (1).png
  45. BIN
      assets/taddar/设置.png
  46. BIN
      assets/taddar/首页 (1).png
  47. BIN
      assets/taddar/首页 (2).png
  48. BIN
      assets/taddar/首页 (3).png
  49. 181 0
      components/loading/loading.js
  50. 4 0
      components/loading/loading.json
  51. 9 0
      components/loading/loading.wxml
  52. 0 0
      components/loading/loading.wxss
  53. 61 0
      components/shoping-tabbar/index.js
  54. 4 0
      components/shoping-tabbar/index.json
  55. 7 0
      components/shoping-tabbar/index.wxml
  56. 38 0
      components/shoping-tabbar/index.wxss
  57. 42 0
      custom-tab-bar/index.js
  58. 3 0
      custom-tab-bar/index.json
  59. 8 0
      custom-tab-bar/index.wxml
  60. 42 0
      custom-tab-bar/index.wxss
  61. BIN
      images/help.png
  62. BIN
      images/history_b.png
  63. BIN
      images/home.png
  64. BIN
      images/home_b.png
  65. BIN
      images/more2.png
  66. BIN
      images/more2_b.png
  67. BIN
      images/my.png
  68. BIN
      images/my_b.png
  69. 62 0
      pages/Model Selection/Model Selection.js
  70. 6 0
      pages/Model Selection/Model Selection.json
  71. 13 0
      pages/Model Selection/Model Selection.wxml
  72. 32 0
      pages/Model Selection/Model Selection.wxss
  73. 50 0
      pages/Register/Register.js
  74. 6 0
      pages/Register/Register.json
  75. 5 0
      pages/Register/Register.wxml
  76. 23 0
      pages/Register/Register.wxss
  77. 36 0
      pages/RoleSelectionPage/RoleSelectionPage.js
  78. 6 0
      pages/RoleSelectionPage/RoleSelectionPage.json
  79. 7 0
      pages/RoleSelectionPage/RoleSelectionPage.wxml
  80. 27 0
      pages/RoleSelectionPage/RoleSelectionPage.wxss
  81. 55 23
      pages/Staff/Staff.js
  82. 1 1
      pages/Staff/Staff.json
  83. 20 20
      pages/Staff/Staff.wxml
  84. 3 2
      pages/Staff/Staff.wxss
  85. 409 0
      pages/Visualizatio/Visualizatio.js
  86. 6 0
      pages/Visualizatio/Visualizatio.json
  87. 105 0
      pages/Visualizatio/Visualizatio.wxml
  88. 205 0
      pages/Visualizatio/Visualizatio.wxss
  89. 25 13
      pages/Visualization/Visualization.js
  90. 3 3
      pages/Visualization/Visualization.json
  91. 47 54
      pages/Visualization/Visualization.wxml
  92. 28 105
      pages/Visualization/Visualization.wxss
  93. 259 0
      pages/admin/admin.js
  94. 6 0
      pages/admin/admin.json
  95. 44 0
      pages/admin/admin.wxml
  96. 208 0
      pages/admin/admin.wxss
  97. 10 17
      pages/logs/logs.js
  98. 2 3
      pages/logs/logs.json
  99. 5 32
      pages/logs/logs.wxml
  100. 4 116
      pages/logs/logs.wxss

+ 1 - 1
.idea/.name

@@ -1 +1 @@
-__init__.py
+run.py

+ 2 - 0
README.md

@@ -0,0 +1,2 @@
+# tabBar
+小程序自定义底部菜单栏

BIN
api/SoilAcidification.db


+ 6 - 1
api/app/__init__.py

@@ -2,6 +2,8 @@ from flask import Flask
 from flask_cors import CORS
 from . import config
 from flask_sqlalchemy import SQLAlchemy
+from flask_migrate import Migrate
+import logging
 
 # 创建 SQLAlchemy 全局实例
 db = SQLAlchemy()
@@ -12,10 +14,13 @@ def create_app():
     CORS(app)
     # 进行初始配置,加载配置文件等
     app.config.from_object(config.Configs)
-
+    app.logger.setLevel(logging.DEBUG)
     # 初始化 SQLAlchemy
     db.init_app(app)
 
+    # 初始化 Flask-Migrate
+    migrate = Migrate(app, db)
+
     # 导入路由
     from . import routes
     app.register_blueprint(routes.bp)

+ 64 - 40
api/app/database_models.py

@@ -1,48 +1,72 @@
-from . import db
-from datetime import datetime
+from typing import List, Optional
 
-class Model(db.Model):
+from sqlalchemy import Column, Float, ForeignKey, Integer, String, TIMESTAMP, Table, Text, text
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
+import datetime
+
+class Base(DeclarativeBase):
+    pass
+
+
+class Datasets(Base):
+    __tablename__ = 'Datasets'
+
+    Dataset_name: Mapped[str] = mapped_column(Text)
+    Row_count: Mapped[int] = mapped_column(Integer)
+    Dataset_type: Mapped[str] = mapped_column(Text)
+    Dataset_ID: Mapped[Optional[int]] = mapped_column(Integer, primary_key=True)
+    Dataset_description: Mapped[Optional[str]] = mapped_column(Text)
+    Uploaded_at: Mapped[Optional[datetime.datetime]] = mapped_column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'))
+    Status: Mapped[Optional[str]] = mapped_column(Text, server_default=text("'pending'"))
+
+
+class Models(Base):
     __tablename__ = 'Models'
 
-    ModelID = db.Column(db.Integer, primary_key=True)
-    ModelName = db.Column(db.Text, nullable=False)
-    ModelType = db.Column(db.Text, nullable=False)
-    CreatedAt = db.Column(db.TIMESTAMP, default=db.func.current_timestamp())
-    Description = db.Column(db.Text)
-    
+    ModelID: Mapped[Optional[int]] = mapped_column(Integer, primary_key=True)
+    Model_name: Mapped[str] = mapped_column(Text, nullable=False)
+    Model_type: Mapped[str] = mapped_column(Text, nullable=False)
+    Created_at: Mapped[Optional[datetime.datetime]] = mapped_column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'))
+    Description: Mapped[Optional[str]] = mapped_column(Text)
+    DatasetID: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('Datasets.Dataset_ID'))
+    ModelFilePath: Mapped[Optional[str]] = mapped_column(Text)
+    Data_type: Mapped[Optional[str]] = mapped_column(Text)  # 新增字段
 
-class ModelParameters(db.Model):
-    __tablename__ = 'ModelParameters'  # 指定表名
+    ModelParameters: Mapped[List['ModelParameters']] = relationship('ModelParameters', back_populates='Models_')
 
-    ParamID = db.Column(db.Integer, primary_key=True, autoincrement=True)  # 主键
-    ModelID = db.Column(db.Integer, db.ForeignKey('Models.ModelID'), nullable=False)  # 外键,指向 Models 表的 ModelID
-    ParamName = db.Column(db.Text, nullable=False)  # 参数名
-    ParamValue = db.Column(db.Text, nullable=False)  # 参数值
+class CurrentReduce(Base):
+    __tablename__ = 'current_reduce'
 
-    # 定义反向关系
-    model = db.relationship('Model', backref=db.backref('parameters', lazy=True))
+    id: Mapped[int] = mapped_column(Integer, primary_key=True)
+    Q_over_b: Mapped[float] = mapped_column(Float)
+    pH: Mapped[float] = mapped_column(Float)
+    OM: Mapped[float] = mapped_column(Float)
+    CL: Mapped[float] = mapped_column(Float)
+    H: Mapped[float] = mapped_column(Float)
+    Al: Mapped[float] = mapped_column(Float)
 
-class Dataset(db.Model):
-    __tablename__ = 'Datasets'
 
-    DatasetID = db.Column(db.Integer, primary_key=True)  # 数据集ID
-    DatasetName = db.Column(db.String(255), nullable=False)  # 数据集名称
-    DatasetDescription = db.Column(db.Text, nullable=True)  # 数据集描述 (可选)
-    UploadedAt = db.Column(db.TIMESTAMP, default=datetime.utcnow)  # 上传时间
-    RowCount = db.Column(db.Integer, nullable=False)  # 数据集行数(条数)
-    Status = db.Column(db.String(50), default='pending')  # 数据集状态 (pending, processed)
-    DatasetType = db.Column(db.String(50), nullable=False)  # 数据集类型 (反酸模型训练, 降酸模型训练等)
-
-    def __repr__(self):
-        return f'<Dataset {self.DatasetName}>'
-
-    def to_dict(self):
-        return {
-            'DatasetID': self.DatasetID,
-            'DatasetName': self.DatasetName,
-            'DatasetDescription': self.DatasetDescription,
-            'UploadedAt': self.UploadedAt.strftime('%Y-%m-%d %H:%M:%S'),
-            'RowCount': self.RowCount,
-            'Status': self.Status,
-            'DatasetType': self.DatasetType
-        }
+class CurrentReflux(Base):
+    __tablename__ = 'current_reflux'
+
+    id: Mapped[int] = mapped_column(Integer, primary_key=True)
+    OM: Mapped[float] = mapped_column(Float)
+    CL: Mapped[float] = mapped_column(Float)
+    CEC: Mapped[float] = mapped_column(Float)
+    H_plus: Mapped[float] = mapped_column(Float)
+    HN: Mapped[float] = mapped_column(Float)
+    Al3_plus: Mapped[float] = mapped_column(Float)
+    Free_alumina: Mapped[float] = mapped_column(Float)
+    Free_iron_oxides: Mapped[float] = mapped_column(Float)
+    Delta_pH: Mapped[float] = mapped_column(Float)
+
+
+class ModelParameters(Base):
+    __tablename__ = 'ModelParameters'
+
+    ParamID: Mapped[int] = mapped_column(Integer, primary_key=True)
+    ModelID: Mapped[int] = mapped_column(ForeignKey('Models.ModelID'))
+    ParamName: Mapped[str] = mapped_column(Text)
+    ParamValue: Mapped[str] = mapped_column(Text)
+
+    Models_: Mapped['Models'] = relationship('Models', back_populates='ModelParameters')

+ 206 - 15
api/app/model.py

@@ -1,5 +1,14 @@
+import datetime
+import os
 import pickle
 import pandas as pd
+from flask_sqlalchemy.session import Session
+from sklearn.ensemble import RandomForestRegressor
+from sklearn.model_selection import train_test_split, cross_val_score
+from sqlalchemy import text
+
+from .database_models import Models, Datasets
+
 
 
 # 加载模型
@@ -17,30 +26,212 @@ def predict(input_data: pd.DataFrame, model_name):
     return predictions.tolist()
 
 
-def train_and_save_model(dataset_id, model_type, model_name, model_description):
-    dataset = get_dataset_by_id(dataset_id)
+
+def train_and_save_model(session, model_type, model_name, model_description, data_type, dataset_id=None):
+    if not dataset_id:
+        # 直接创建新的数据集并复制数据
+        dataset_id = save_current_dataset(session, data_type)
+
+    # 从新复制的数据集表中加载数据
+    dataset_table_name = f"dataset_{dataset_id}"
+    dataset = pd.read_sql_table(dataset_table_name, session.bind)
+
     if dataset.empty:
         raise ValueError(f"Dataset {dataset_id} is empty or not found.")
 
-    # Step 1: 数据准备
-    X = dataset.iloc[:, :-1]  # 特征数据
-    y = dataset.iloc[:, -1]  # 目标变量
+    # 数据准备
+    X = dataset.iloc[:, :-1]
+    y = dataset.iloc[:, -1]
 
-    # Step 2: 训练模型
+    # 训练模型
     model = train_model_by_type(X, y, model_type)
 
-    # Step 3: 保存模型到数据库
-    # 使用提供的 model_name 和 model_description
-    saved_model = save_model(model_name, model_type, model_description)
+    # 保存模型到数据库
+    save_model(session, model, model_name, model_type, model_description, dataset_id, data_type)
+
+
+# # 保存模型参数
+    # save_model_parameters(model, saved_model.ModelID)
+
+    # # 计算评估指标(如MSE)
+    # y_pred = model.predict(X)
+    # mse = mean_squared_error(y, y_pred)
+    #
+    # return saved_model, mse
+
+def save_current_dataset(session, data_type):
+    """
+    创建一个新的数据集条目,并复制对应的数据类型表的数据。
+
+    Args:
+    session (Session): SQLAlchemy session对象。
+    data_type (str): 数据集的类型,如 'reduce' 或 'reflux'。
+
+    Returns:
+    int: 新保存的数据集的ID。
+    """
+    # 创建一个新的数据集条目
+    new_dataset = Datasets(
+        Dataset_name=f"{data_type}_dataset_{datetime.datetime.now():%Y%m%d_%H%M%S}",  # 使用当前时间戳生成独特的名称
+        Dataset_description=f"Automatically generated dataset for type {data_type}",
+        Row_count=0,  # 初始行数为0,将在复制数据后更新
+        Status='pending',  # 初始状态为pending
+        Dataset_type=data_type
+    )
+
+    # 添加到数据库并提交以获取ID
+    session.add(new_dataset)
+    session.flush()  # flush用于立即执行SQL并获取ID,但不提交事务
+
+    # 获取新数据集的ID
+    dataset_id = new_dataset.Dataset_ID
+
+    # 复制数据到新表
+    source_table = data_type_table_mapping(data_type)  # 假设有函数映射数据类型到表名
+    new_table_name = f"dataset_{dataset_id}"
+    copy_table_sql = f"CREATE TABLE {new_table_name} AS SELECT * FROM {source_table};"
+    session.execute(text(copy_table_sql))
+
+    # 更新新数据集的状态和行数
+    update_sql = f"UPDATE datasets SET status='processed', row_count=(SELECT count(*) FROM {new_table_name}) WHERE dataset_id={dataset_id};"
+    session.execute(text(update_sql))
+
+    session.commit()
+
+    return dataset_id
+
+def data_type_table_mapping(data_type):
+    """映射数据类型到对应的数据库表名"""
+    if data_type == 'reduce':
+        return 'current_reduce'
+    elif data_type == 'reflux':
+        return 'current_reflux'
+    else:
+        raise ValueError("Invalid data type provided.")
+
+
+def train_model_by_type(X, y, model_type):
+    # 划分数据集
+    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+    if model_type == 'RandomForest':
+        # 随机森林的参数优化
+        return train_random_forest(X_train, y_train)
+    elif model_type == 'XGBR':
+        # XGBoost的参数优化
+        return train_xgboost(X_train, y_train)
+    elif model_type == 'GBSTR':
+        # 梯度提升树的参数优化
+        return train_gradient_boosting(X_train, y_train)
+    else:
+        raise ValueError(f"Unsupported model type: {model_type}")
+
+
+def train_random_forest(X_train, y_train):
+    best_score = 0
+    best_n_estimators = None
+    best_max_depth = None
+    random_state = 43
+
+    # 筛选最佳的树的数量
+    for n_estimators in range(1, 20, 1):
+        model = RandomForestRegressor(n_estimators=n_estimators, random_state=random_state)
+        score = cross_val_score(model, X_train, y_train, cv=5).mean()
+        if score > best_score:
+            best_score = score
+            best_n_estimators = n_estimators
+
+    print(f"Best number of trees: {best_n_estimators}, Score: {best_score}")
+
+    # 在找到的最佳树的数量基础上,筛选最佳的最大深度
+    best_score = 0  # 重置最佳得分,为最大深度优化做准备
+    for max_depth in range(1, 30, 1):
+        model = RandomForestRegressor(n_estimators=best_n_estimators, max_depth=max_depth, random_state=random_state)
+        score = cross_val_score(model, X_train, y_train, cv=5).mean()
+        if score > best_score:
+            best_score = score
+            best_max_depth = max_depth
+
+    print(f"Best max depth: {best_max_depth}, Score: {best_score}")
+
+    # 使用最佳的树的数量和最大深度训练最终模型
+    best_model = RandomForestRegressor(n_estimators=best_n_estimators, max_depth=best_max_depth,
+                                       random_state=random_state)
+    best_model.fit(X_train, y_train)
+
+    return best_model
+
+
+def train_xgboost(X_train, y_train, X_test, y_test):
+    # XGBoost训练过程
+    # (将类似上面的代码添加到这里)
+    pass
+
+
+def train_gradient_boosting(X_train, y_train, X_test, y_test):
+    # 梯度提升树训练过程
+    # (将类似上面的代码添加到这里)
+    pass
+
+def save_model(session, model, model_name, model_type, model_description, dataset_id, data_type, custom_path='pkl'):
+    """
+    保存模型到数据库,并将模型文件保存到磁盘。
+
+    :param session: 数据库会话
+    :param model: 要保存的模型对象
+    :param model_name: 模型的名称
+    :param model_type: 模型的类型
+    :param model_description: 模型的描述信息
+    :param dataset_id: 数据集ID
+    :param custom_path: 保存模型的路径
+    :return: 返回保存的模型文件路径
+    """
+    # 根据模型类型设置文件名前缀
+    prefix_dict = {
+        'RandomForest': 'rf_model_',
+        'XGBRegressor': 'xgbr_model_',
+        'GBSTRegressor': 'gbstr_model_'
+    }
+    prefix = prefix_dict.get(model_type, 'default_model_')  # 如果model_type不在字典中,默认前缀
+
+    try:
+        # 确保路径存在
+        os.makedirs(custom_path, exist_ok=True)
+
+        # 获取当前时间戳(格式:月日时分)
+        timestamp = datetime.datetime.now().strftime('%m%d_%H%M')
+
+        # 拼接完整的文件名
+        file_name = os.path.join(custom_path, f'{prefix}{timestamp}.pkl')
+
+        # 保存模型到文件
+        with open(file_name, 'wb') as f:
+            pickle.dump(model, f)
+        print(f"模型已保存为: {file_name}")
+
+        # 创建模型数据库记录
+        new_model = Models(
+            Model_name=model_name,
+            Model_type=model_type,
+            Description=model_description,
+            DatasetID=dataset_id,
+            Created_at=datetime.datetime.now(),
+            ModelFilePath=file_name,
+            Data_type=data_type
+        )
+
+        # 添加记录到数据库
+        session.add(new_model)
+        session.commit()
 
-    # Step 4: 保存模型参数
-    save_model_parameters(model, saved_model.ModelID)
+        # 返回文件路径
+        return file_name
 
-    # Step 5: 计算评估指标(比如MSE)
-    y_pred = model.predict(X)
-    mse = mean_squared_error(y, y_pred)
+    except Exception as e:
+        session.rollback()
+        print(f"Error saving model: {str(e)}")
+        raise e  # 显式抛出异常供调用者处理
 
-    return saved_model, mse
 
 
 if __name__ == '__main__':

+ 230 - 83
api/app/routes.py

@@ -5,106 +5,253 @@ from .model import predict, train_and_save_model
 import pandas as pd
 from . import db  # 从 app 包导入 db 实例
 from sqlalchemy.engine.reflection import Inspector
-from .database_models import Model, ModelParameters, Dataset
+from .database_models import Models, ModelParameters, Datasets, CurrentReduce, CurrentReflux
 import os
 from .utils import create_dynamic_table, allowed_file
 from sqlalchemy.orm import sessionmaker
+from sqlalchemy.schema import MetaData, Table
+import logging
+from sqlalchemy import text, select
 
+# 配置日志
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger(__name__)
 # 创建蓝图 (Blueprint),用于分离路由
 bp = Blueprint('routes', __name__)
 
+def infer_column_types(df):
+    type_map = {
+        'object': 'str',
+        'int64': 'int',
+        'float64': 'float',
+        'datetime64[ns]': 'datetime'  # 适应Pandas datetime类型
+    }
+    # 提取列和其数据类型
+    return {col: type_map.get(str(df[col].dtype), 'str') for col in df.columns}
+
 
 @bp.route('/upload-dataset', methods=['POST'])
 def upload_dataset():
     try:
-        # 检查是否包含文件
         if 'file' not in request.files:
             return jsonify({'error': 'No file part'}), 400
         file = request.files['file']
+        if file.filename == '' or not allowed_file(file.filename):
+            return jsonify({'error': 'No selected file or invalid file type'}), 400
+
+        dataset_name = request.form.get('dataset_name')
+        dataset_description = request.form.get('dataset_description', 'No description provided')
+        dataset_type = request.form.get('dataset_type')
+        if not dataset_type:
+            return jsonify({'error': 'Dataset type is required'}), 400
+
+        # 创建 sessionmaker 实例
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
+        new_dataset = Datasets(
+            Dataset_name=dataset_name,
+            Dataset_description=dataset_description,
+            Row_count=0,
+            Status='pending',
+            Dataset_type=dataset_type
+        )
+        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)
+        dynamic_table_class = create_dynamic_table(new_dataset.Dataset_ID, column_types)
+        insert_data_into_dynamic_table(session, dataset_df, dynamic_table_class)
+
+        # 根据 dataset_type 决定插入到哪个已有表
+        if dataset_type == 'reduce':
+            insert_data_into_existing_table(session, dataset_df, CurrentReduce)
+        elif dataset_type == 'reflux':
+            insert_data_into_existing_table(session, dataset_df, CurrentReflux)
+
+        session.commit()
 
-        # 如果没有文件或者文件名为空
-        if file.filename == '':
-            return jsonify({'error': 'No selected file'}), 400
-
-        # 检查文件类型是否允许
-        if file and allowed_file(file.filename):
-            # 获取数据集的元数据
-            dataset_name = request.form.get('dataset_name')
-            dataset_description = request.form.get('dataset_description', 'No description provided')
-            dataset_type = request.form.get('dataset_type')  # 新增字段:数据集类型
-
-            # 校验 dataset_type 是否存在
-            if not dataset_type:
-                return jsonify({'error': 'Dataset type is required'}), 400
-
-            # 创建 Dataset 实体并保存到数据库
-            new_dataset = Dataset(
-                DatasetName=dataset_name,
-                DatasetDescription=dataset_description,
-                RowCount=0,  # 初步创建数据集时,行数先置为0
-                Status='pending',  # 状态默认为 'pending'
-                DatasetType=dataset_type  # 保存数据集类型
-            )
-            db.session.add(new_dataset)
-            db.session.commit()
-
-            # 获取数据集的 ID
-            dataset_id = new_dataset.DatasetID
-
-            # 保存文件时使用数据库的 DatasetID 作为文件名
-            unique_filename = f"dataset_{dataset_id}.xlsx"
-            upload_folder = current_app.config['UPLOAD_FOLDER']
-            file_path = os.path.join(upload_folder, unique_filename)
-
-            # 保存文件
-            file.save(file_path)
-
-            # 读取 Excel 文件内容
-            dataset_df = pd.read_excel(file_path)
-
-            # 更新数据集的行数
-            row_count = len(dataset_df)
-            new_dataset.RowCount = row_count
-            new_dataset.Status = 'processed'  # 状态更新为 processed
-            db.session.commit()
-
-            # 动态创建数据表
-            columns = {}
-            for col in dataset_df.columns:
-                if dataset_df[col].dtype == 'int64':
-                    columns[col] = 'int'
-                elif dataset_df[col].dtype == 'float64':
-                    columns[col] = 'float'
-                else:
-                    columns[col] = 'str'
-
-            # 创建新表格(动态表格)
-            dynamic_table_class = create_dynamic_table(dataset_id, columns)
-
-            # 创建新的数据库会话
-            Session = sessionmaker(bind=db.engine)
-            session = Session()
-
-            # 将每一行数据插入到动态创建的表格中
-            for _, row in dataset_df.iterrows():
-                record_data = row.to_dict()
-                # 将数据插入到新表格中
-                session.execute(dynamic_table_class.__table__.insert(), [record_data])
-
-            session.commit()
-            session.close()
+        return jsonify({
+            'message': f'Dataset {dataset_name} uploaded successfully!',
+            'dataset_id': new_dataset.Dataset_ID,
+            'filename': unique_filename
+        }), 201
 
-            return jsonify({
-                'message': f'Dataset {dataset_name} uploaded successfully!',
-                'dataset_id': new_dataset.DatasetID,
-                'filename': unique_filename
-            }), 201
+    except Exception as e:
+        if session:
+            session.rollback()
+        logging.error('Failed to process the dataset upload:', exc_info=True)
+        return jsonify({'error': str(e)}), 500
+    finally:
+        session.close()
 
-        else:
-            return jsonify({'error': 'Invalid file type'}), 400
+@bp.route('/train-and-save-model', methods=['POST'])
+def train_and_save_model_endpoint():
+    # 创建 sessionmaker 实例
+    Session = sessionmaker(bind=db.engine)
+    session = Session()
+
+    # 从请求中解析参数
+    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:
+        # 调用训练和保存模型的函数
+        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)
+        return jsonify({'error': 'Failed to train and save model', 'message': str(e)}), 500
+    finally:
+        session.close()
+
+
+def clean_column_names(dataframe):
+    # Strip whitespace and replace non-breaking spaces and other non-printable characters
+    dataframe.columns = [col.strip().replace('\xa0', '') for col in dataframe.columns]
+    return dataframe
+
+
+def rename_columns_for_model(dataframe, dataset_type):
+    if dataset_type == 'reduce':
+        rename_map = {
+            '1/b': 'Q_over_b',
+            'pH': 'pH',
+            'OM': 'OM',
+            'CL': 'CL',
+            'H': 'H',
+            'Al': 'Al'
+        }
+    elif dataset_type == 'reflux':
+        rename_map = {
+            'OM g/kg': 'OM',
+            'CL g/kg': 'CL',
+            'CEC cmol/kg': 'CEC',
+            'H+ cmol/kg': 'H_plus',
+            'HN mg/kg': 'HN',
+            'Al3+cmol/kg': 'Al3_plus',
+            'Free alumina g/kg': 'Free_alumina',
+            'Free iron oxides g/kg': 'Free_iron_oxides',
+            'ΔpH': 'Delta_pH'
+        }
+
+    # 使用 rename() 方法更新列名
+    dataframe = dataframe.rename(columns=rename_map)
+    return dataframe
+
+
+def insert_data_into_existing_table(session, dataframe, model_class):
+    """Insert data from a DataFrame into an existing SQLAlchemy model table."""
+    for index, row in dataframe.iterrows():
+        record = model_class(**row.to_dict())
+        session.add(record)
+
+def insert_data_into_dynamic_table(session, dataset_df, dynamic_table_class):
+    for _, row in dataset_df.iterrows():
+        record_data = row.to_dict()
+        session.execute(dynamic_table_class.__table__.insert(), [record_data])
+
+def insert_data_by_type(session, dataset_df, dataset_type):
+    if dataset_type == 'reduce':
+        for _, row in dataset_df.iterrows():
+            record = CurrentReduce(**row.to_dict())
+            session.add(record)
+    elif dataset_type == 'reflux':
+        for _, row in dataset_df.iterrows():
+            record = CurrentReflux(**row.to_dict())
+            session.add(record)
+
+
+def get_current_data(session, data_type):
+    # 根据数据类型选择相应的表模型
+    if data_type == 'reduce':
+        model = CurrentReduce
+    elif data_type == 'reflux':
+        model = CurrentReflux
+    else:
+        raise ValueError("Invalid data type provided. Choose 'reduce' or 'reflux'.")
+
+    # 从数据库中查询所有记录
+    result = session.execute(select(model))
+
+    # 将结果转换为DataFrame
+    dataframe = pd.DataFrame([dict(row) for row in result])
+    return dataframe
+
+def get_dataset_by_id(session, dataset_id):
+    # 动态获取表的元数据
+    metadata = MetaData(bind=session.bind)
+    dataset_table = Table(dataset_id, metadata, autoload=True, autoload_with=session.bind)
+
+    # 从数据库中查询整个表的数据
+    query = select(dataset_table)
+    result = session.execute(query).fetchall()
+
+    # 检查是否有数据返回
+    if not result:
+        raise ValueError(f"No data found for dataset {dataset_id}.")
+
+    # 将结果转换为DataFrame
+    dataframe = pd.DataFrame(result, columns=[column.name for column in dataset_table.columns])
+
+    return dataframe
+
+
+@bp.route('/delete-dataset/<int:dataset_id>', methods=['DELETE'])
+def delete_dataset(dataset_id):
+    # 创建 sessionmaker 实例
+    Session = sessionmaker(bind=db.engine)
+    session = Session()
+    try:
+        # 查询数据集
+        dataset = session.query(Datasets).filter_by(Dataset_ID=dataset_id).first()
+        if not dataset:
+            return jsonify({'error': 'Dataset not found'}), 404
+
+        # 删除文件
+        filename = f"dataset_{dataset.Dataset_ID}.xlsx"
+        file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
+        if os.path.exists(file_path):
+            os.remove(file_path)
+
+        # 删除数据表
+        table_name = f"dataset_{dataset.Dataset_ID}"
+        session.execute(text(f"DROP TABLE IF EXISTS {table_name}"))
+
+        # 删除数据集记录
+        session.delete(dataset)
+        session.commit()
+
+        return jsonify({'message': 'Dataset deleted successfully'}), 200
 
     except Exception as e:
+        session.rollback()
+        logging.error(f'Failed to delete dataset {dataset_id}:', exc_info=True)
         return jsonify({'error': str(e)}), 500
+    finally:
+        session.close()
 
 
 @bp.route('/tables', methods=['GET'])
@@ -118,7 +265,7 @@ def list_tables():
 @bp.route('/models/<int:model_id>', methods=['GET'])
 def get_model(model_id):
     try:
-        model = Model.query.filter_by(ModelID=model_id).first()
+        model = Models.query.filter_by(ModelID=model_id).first()
         if model:
             return jsonify({
                 'ModelID': model.ModelID,
@@ -136,7 +283,7 @@ def get_model(model_id):
 @bp.route('/models', methods=['GET'])
 def get_all_models():
     try:
-        models = Model.query.all()  # 获取所有模型数据
+        models = Models.query.all()  # 获取所有模型数据
         if models:
             result = [
                 {
@@ -179,7 +326,7 @@ def get_all_model_parameters():
 @bp.route('/models/<int:model_id>/parameters', methods=['GET'])
 def get_model_parameters(model_id):
     try:
-        model = Model.query.filter_by(ModelID=model_id).first()
+        model = Models.query.filter_by(ModelID=model_id).first()
         if model:
             # 获取该模型的所有参数
             parameters = [

BIN
api/pkl/rf_model_1222_1922.pkl


BIN
api/pkl/rf_model_1222_1952.pkl


BIN
api/pkl/rf_model_1222_1954.pkl


BIN
api/pkl/rf_model_1222_1959.pkl


BIN
api/pkl/rf_model_1222_2012.pkl


BIN
api/uploads/datasets/dataset_2.xlsx


BIN
api/uploads/datasets/dataset_29.xlsx


BIN
api/uploads/datasets/dataset_30.xlsx


BIN
api/uploads/datasets/dataset_31.xlsx


BIN
api/uploads/datasets/dataset_32.xlsx


BIN
api/uploads/datasets/dataset_33.xlsx


BIN
api/uploads/datasets/dataset_34.xlsx


BIN
api/uploads/datasets/dataset_35.xlsx


BIN
api/uploads/datasets/dataset_36.xlsx


BIN
api/uploads/datasets/dataset_37.xlsx


BIN
api/uploads/datasets/dataset_38.xlsx


BIN
api/uploads/datasets/dataset_39.xlsx


BIN
api/uploads/datasets/dataset_40.xlsx


BIN
api/uploads/datasets/dataset_41.xlsx


BIN
api/uploads/datasets/dataset_42.xlsx


BIN
api/uploads/datasets/dataset_6.xlsx


BIN
api/uploads/datasets/dataset_8.xlsx


+ 24 - 4
app.js

@@ -1,8 +1,8 @@
-// app.js
+//app.js
 App({
-  onLaunch() {
+  onLaunch: function () {
     // 展示本地存储能力
-    const logs = wx.getStorageSync('logs') || []
+    var logs = wx.getStorageSync('logs') || []
     logs.unshift(Date.now())
     wx.setStorageSync('logs', logs)
 
@@ -12,8 +12,28 @@ App({
         // 发送 res.code 到后台换取 openId, sessionKey, unionId
       }
     })
+    // 获取用户信息
+    wx.getSetting({
+      success: res => {
+        if (res.authSetting['scope.userInfo']) {
+          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
+          wx.getUserInfo({
+            success: res => {
+              // 可以将 res 发送给后台解码出 unionId
+              this.globalData.userInfo = res.userInfo
+
+              // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
+              // 所以此处加入 callback 以防止这种情况
+              if (this.userInfoReadyCallback) {
+                this.userInfoReadyCallback(res)
+              }
+            }
+          })
+        }
+      }
+    })
   },
   globalData: {
     userInfo: null
   }
-})
+})

+ 43 - 33
app.json

@@ -1,54 +1,64 @@
 {
   "pages": [
-    
-    "pages/b/b",
-    "pages/index/index",
-    "pages/demo/demo",
-    "pages/Data Visualization/Data Visualization",
-    "pages/logs/logs",
+    "pages/admin/admin",
+    "pages/Register/Register",
+    "pages/RoleSelectionPage/RoleSelectionPage",
+    "pages/threshold/threshold",
+    "pages/Model Selection/Model Selection",
+    "pages/thres/thres",
     "pages/Visualization/Visualization",
-    "pages/Calculate/Calculate",
+    "pages/Visualizatio/Visualizatio",
     "pages/Staff/Staff",
-    "pages/Calculation/Calculation",
-    "pages/AcidNeutralizationModel/AcidNeutralizationModel",
+    "shoping/Home/Home",
+    "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"
   ],
   "window": {
-    "navigationBarTitleText": "登入页面",
-    "navigationBarBackgroundColor": "#00AF92",  
-    "navigationBarTextStyle": "black"  ,
-    "enablePullDownRefresh": true,
-    "backgroundColor": "#efefef",
-    "backgroundTextStyle": "dark"
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationBarTitleText": "WeChat",
+    "navigationBarTextStyle": "black"
   },
   "tabBar": {
-    "selectedColor": "#F3514F",
-    "color": "#666666",
-    "backgroundColor": "#ffffff",
+    "color": "#7A7E83",
+    "selectedColor": "#3cc51f",
+    "borderStyle": "black",
+    "backgroundColor": "#FFFFFF",
     "list": [
       {
-        "text": "计算数据",
-        "pagePath": "pages/Calculate/Calculate",
-        "iconPath": "/assets/taddar/Calculate_Data-active.png",
-        "selectedIconPath": "/assets/taddar/Calculate_Data.png"
+        "pagePath": "pages/threshold/threshold",
+        "text": "设置",
+        "iconPath": "/assets/taddar/设置 (1).png",
+        "selectedIconPath": "/assets/taddar/设置.png"
       },
       {
-        "text": "数据图像展示",
-        "pagePath": "pages/Data Visualization/Data Visualization",
-        "iconPath": "/assets/taddar/chart-histogram (1).png",
-        "selectedIconPath": "/assets/taddar/chart-histogram.png"
+        "pagePath": "pages/Visualization/Visualization",
+        "text": "反酸模型数据管理",
+        "iconPath": "/assets/taddar/模型数据表 (1).png",
+        "selectedIconPath": "/assets/taddar/模型数据表.png"
       },
       {
-        "text": "数据展示",
-        "pagePath": "pages/Visualization/Visualization",
-        "iconPath": "/assets/taddar/Data_Visualization-active.png",
-        "selectedIconPath": "/assets/taddar/Data_Visualization.png"
+        "pagePath": "pages/Visualizatio/Visualizatio",
+        "text": "降酸模型数据管理",
+        "iconPath": "/assets/taddar/模型数据表 (1).png",
+        "selectedIconPath": "/assets/taddar/模型数据表.png"
       },
       {
-        "text": "人员中心",
         "pagePath": "pages/Staff/Staff",
-        "iconPath": "/assets/taddar/users-copy.png",
-        "selectedIconPath": "/assets/taddar/copy.png"
+        "text": "我的",
+        "iconPath": "/assets/taddar/我的 (1).png",
+        "selectedIconPath": "/assets/taddar/我的.png"
       }
     ]
   },

+ 0 - 12
app.wxss

@@ -1,12 +0,0 @@
-/**app.wxss**/
-.container {
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: space-between;
-  padding: 200rpx 0;
-  box-sizing: border-box;
-} 
-
-

BIN
assets/taddar/12.png


BIN
assets/taddar/QQ.png


BIN
assets/taddar/微信.png


BIN
assets/taddar/微信图片_20241209174643.png


BIN
assets/taddar/微信图片_20241209174946.png


BIN
assets/taddar/我的 (1).png


BIN
assets/taddar/我的.png


BIN
assets/taddar/模型数据表 (1).png


BIN
assets/taddar/模型数据表.png


BIN
assets/taddar/模型计算 (1).png


BIN
assets/taddar/模型计算.png


BIN
assets/taddar/设置 (1).png


BIN
assets/taddar/设置.png


BIN
assets/taddar/首页 (1).png


BIN
assets/taddar/首页 (2).png


BIN
assets/taddar/首页 (3).png


+ 181 - 0
components/loading/loading.js

@@ -0,0 +1,181 @@
+module.exports =
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// define __esModule on exports
+/******/ 	__webpack_require__.r = function(exports) {
+/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 		}
+/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
+/******/ 	};
+/******/
+/******/ 	// create a fake namespace object
+/******/ 	// mode & 1: value is a module id, require it
+/******/ 	// mode & 2: merge all properties of value into the ns
+/******/ 	// mode & 4: return value when already ns object
+/******/ 	// mode & 8|1: behave like require
+/******/ 	__webpack_require__.t = function(value, mode) {
+/******/ 		if(mode & 1) value = __webpack_require__(value);
+/******/ 		if(mode & 8) return value;
+/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
+/******/ 		var ns = Object.create(null);
+/******/ 		__webpack_require__.r(ns);
+/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
+/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
+/******/ 		return ns;
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 14);
+/******/ })
+/************************************************************************/
+/******/ ({
+
+/***/ 14:
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+Component({
+    options: {
+        addGlobalClass: true
+    },
+    properties: {
+        extClass: {
+            type: String,
+            value: ''
+        },
+        show: {
+            type: Boolean,
+            value: true,
+            observer: function observer(newValue) {
+                this._computedStyle(newValue, this.data.animated);
+            }
+        },
+        animated: {
+            type: Boolean,
+            value: false,
+            observer: function observer(newValue) {
+                this._computedStyle(this.data.show, newValue);
+            }
+        },
+        duration: {
+            type: Number,
+            value: 350
+        },
+        type: {
+            type: String,
+            value: 'dot-gray'
+        },
+        tips: {
+            type: String,
+            value: '加载中'
+        }
+    },
+    data: {
+        animationData: {},
+        animationInstance: {},
+        displayStyle: 'none'
+    },
+    methods: {
+        _computedStyle: function _computedStyle(show, animated) {
+            if (!show) {
+                if (!animated) {
+                    this.setData({
+                        displayStyle: 'none'
+                    });
+                } else {
+                    this._startAnimation();
+                }
+            } else {
+                this.setData({
+                    displayStyle: ''
+                });
+            }
+        },
+        _startAnimation: function _startAnimation() {
+            var _this = this;
+
+            setTimeout(function () {
+                var data = _this.data;
+                var animation = data.animationInstance;
+                animation.height(0).step();
+                _this.setData({
+                    animationData: animation.export()
+                });
+            }, 0);
+        }
+    },
+    lifetimes: {
+        attached: function attached() {
+            var data = this.data;
+            var animationInstance = wx.createAnimation({
+                duration: data.duration,
+                timingFunction: 'ease'
+            });
+            this.setData({ animationInstance: animationInstance });
+            this._computedStyle(this.data.show, this.data.animated);
+        }
+    }
+});
+
+/***/ })
+
+/******/ });

+ 4 - 0
components/loading/loading.json

@@ -0,0 +1,4 @@
+{
+    "component": true,
+    "usingComponents": {}
+}

+ 9 - 0
components/loading/loading.wxml

@@ -0,0 +1,9 @@
+<view style="display:{{displayStyle}};" class="wx_loading_view {{extClass}}" animation="{{animationData}}" id="wx_loading_view">
+    <view wx:if="{{type==='dot-white'}}" class="loading wx_dot_loading wx_dot_loading_white">
+    </view>
+    <view wx:elif="{{type==='dot-gray'}}" class="loading wx_dot_loading"></view>
+    <view wx:elif="{{type==='circle'}}" class="weui-loadmore">
+        <view class="weui-loading"></view>
+        <view class="weui-loadmore__tips">{{tips}}</view>
+    </view>
+</view>

File diff suppressed because it is too large
+ 0 - 0
components/loading/loading.wxss


+ 61 - 0
components/shoping-tabbar/index.js

@@ -0,0 +1,61 @@
+// components/shoping-tabbar/index.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    selected: {
+      type: Number,
+      default:0
+    }
+  },  
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    selected:0,
+    color: "#7A7E83",
+    selectedColor: "#3cc51f",
+    list: [
+      {
+        pagePath: "/shoping/Home/Home",
+        text: "首页",
+        iconPath: "/assets/taddar/首页 (1).png",
+        selectedIconPath: "/assets/taddar/首页 (2).png"
+      },
+      {
+        pagePath: "/shoping/Soil Acidification/Soil Acidification",
+        text: "反酸模型",
+        iconPath: "/assets/taddar/模型计算 (1).png",
+        selectedIconPath: "/assets/taddar/模型计算.png"
+      },
+      {
+        pagePath: "/shoping/Soil Deacidification/Soil Deacidification",
+        text: "降酸模型",
+        iconPath: "/assets/taddar/模型计算 (1).png",
+        selectedIconPath: "/assets/taddar/模型计算.png"
+      },
+      {
+        pagePath: "/shoping/Staff/Staff",
+        text: "我的",
+        iconPath: "/assets/taddar/我的 (1).png",
+        selectedIconPath: "/assets/taddar/我的.png"
+      }
+    ]
+  },
+
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    switchTab(e) {
+      let data = e.currentTarget.dataset
+      let url = data.path
+      wx.redirectTo({
+        url
+      })
+    }
+  }
+})

+ 4 - 0
components/shoping-tabbar/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 7 - 0
components/shoping-tabbar/index.wxml

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

+ 38 - 0
components/shoping-tabbar/index.wxss

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

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

@@ -0,0 +1,42 @@
+Component({
+  data: {
+    selected: 0,
+    color: "#7A7E83",
+    selectedColor: "#3cc51f",
+    list: [
+      {
+        pagePath: "/pages/index/index",
+        text: "首页",
+        iconPath: "/images/home.png",
+        selectedIconPath: "/images/home_b.png"
+      },
+      {
+        pagePath: "/pages/indexcate/indexcate",
+        text: "发现",
+        iconPath: "/images/more2.png",
+        selectedIconPath: "/images/more2_b.png"
+      },
+      {
+        pagePath: "/pages/mycenter/mycenter",
+        text: "我的",
+        iconPath: "/images/my.png",
+        selectedIconPath: "/images/my_b.png"
+      }
+    ]
+  },
+  methods: {
+    switchTab(e) {
+      console.log(e)
+      let data = e.currentTarget.dataset
+      let url = data.path
+      wx.switchTab({
+        url
+      })
+      this.setData({
+        selected: data.index
+      })
+      console.log(url, this.data.selected, '111')
+
+    }
+  }
+})

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

@@ -0,0 +1,3 @@
+{
+  "component": true
+}

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

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

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

@@ -0,0 +1,42 @@
+.tab-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 48px;
+  background: white;
+  display: flex;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+
+.tab-bar-border {
+  background-color: rgba(0, 0, 0, 0.33);
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 1px;
+  transform: scaleY(0.5);
+}
+
+.tab-bar-item {
+  flex: 1;
+  text-align: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+
+.tab-bar-item cover-image {
+  width: 27px;
+  height: 27px;
+}
+
+.tab-bar-item cover-view {
+  font-size: 100px;
+}
+
+.page-content {
+  padding-bottom: 48px; /* 给页面内容底部添加与底部栏相等的间距 */
+}

BIN
images/help.png


BIN
images/history_b.png


BIN
images/home.png


BIN
images/home_b.png


BIN
images/more2.png


BIN
images/more2_b.png


BIN
images/my.png


BIN
images/my_b.png


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

@@ -0,0 +1,62 @@
+Page({
+  data: {
+    selectedModel: '',  // 存储选择的模型
+    models: [
+      { name: '降酸模型', value: 'jiang_suan' },  // 降酸模型
+      { name: '反酸模型', value: 'fan_suan' },   // 反酸模型
+    ]
+  },
+
+  // 页面加载时获取缓存中的选择模型
+  onLoad() {
+    this.loadSelectedModel();  // 加载缓存中的选择模型
+  },
+
+  // 页面显示时确保选择的模型刷新
+  onShow() {
+    this.loadSelectedModel();  // 每次进入页面时刷新选择的模型
+  },
+
+  // 加载选择的模型并更新页面
+  loadSelectedModel() {
+    const storedModel = wx.getStorageSync('selectedModel');  // 获取缓存中的选择模型
+    if (storedModel) {
+      this.setData({
+        selectedModel: storedModel,  // 更新页面上的选择模型
+      });
+    }
+  },
+
+  // 选择模型并更新缓存
+  onModelChange(e) {
+    const selectedModel = e.detail.value;  // 获取选中的模型
+    this.setData({
+      selectedModel: selectedModel,  // 更新页面上的选择模型
+    });
+
+    // 将选择的模型保存到本地缓存
+    wx.setStorageSync('selectedModel', selectedModel);
+  },
+
+  // 提交选择
+  submitSelection() {
+    const { selectedModel } = this.data;
+
+    // 如果没有选择模型,提示用户
+    if (!selectedModel) {
+      wx.showToast({
+        title: '请选择一个模型',
+        icon: 'none',
+      });
+      return;
+    }
+
+    // 显示选择的模型
+    wx.showToast({
+      title: `已选择:${selectedModel === 'jiang_suan' ? '降酸模型' : '反酸模型'}`,
+      icon: 'success',
+    });
+
+    // 你可以在这里处理提交逻辑,比如跳转页面等
+  },
+});

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

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

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

@@ -0,0 +1,13 @@
+<view class="container">
+  <view class="radio-group">
+    <radio-group bindchange="onModelChange">
+      <label wx:for="{{models}}" wx:key="value" class="radio-item">
+        <radio value="{{item.value}}" checked="{{item.value === selectedModel}}">{{item.name}}</radio>
+      </label>
+    </radio-group>
+  </view>
+
+  <view class="submit-container">
+    <button bindtap="submitSelection" class="submit-btn">提交选择</button>
+  </view>
+</view>

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

@@ -0,0 +1,32 @@
+.container {
+  padding: 20px;
+  margin-bottom: 200px;
+}
+
+.radio-group {
+  display: flex;
+  justify-content: space-between;  /* 让选择框左右分开 */
+  margin-bottom: 20px;
+}
+
+.radio-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.submit-container {
+  display: flex;
+  justify-content: center;  /* 居中按钮 */
+  margin-top: 30px;  /* 给按钮增加上边距 */
+}
+
+.submit-btn {
+  width: 100px;  /* 设置按钮宽度 */
+  padding: 1px;  /* 给按钮添加内边距 */
+  background-color: #3EC01E;
+  color: white;
+  border-radius: 5px;
+  text-align: center;
+  font-size: 16px;
+}

+ 50 - 0
pages/Register/Register.js

@@ -0,0 +1,50 @@
+Page({
+  data: {
+    userInfo: {
+      username: '', // 用户名
+      password: '', // 密码
+    },
+  },
+
+  // 获取用户名输入
+  inputUsername(e) {
+    this.setData({
+      'userInfo.username': e.detail.value, // 更新用户名
+    });
+  },
+
+  // 获取密码输入
+  inputPassword(e) {
+    this.setData({
+      'userInfo.password': e.detail.value, // 更新密码
+    });
+  },
+
+  // 注册方法
+  register() {
+    const { username, password } = this.data.userInfo;
+
+    // 检查用户名和密码是否为空
+    if (!username || !password) {
+      wx.showToast({
+        title: '用户名和密码不能为空',
+        icon: 'none',
+      });
+      return;
+    }
+
+    // 保存用户信息(用户名和密码)到缓存
+    wx.setStorageSync('userInfo', this.data.userInfo);
+
+    // 显示注册成功的提示
+    wx.showToast({
+      title: '注册成功',
+      icon: 'success',
+    });
+
+    // 注册成功后跳转到登录页面
+    wx.reLaunch({
+      url: '/pages/admin/admin',  // 登录页面路径
+    });
+  },
+});

+ 6 - 0
pages/Register/Register.json

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

+ 5 - 0
pages/Register/Register.wxml

@@ -0,0 +1,5 @@
+<view class="container">
+    <input class="input" bindinput="inputUsername" placeholder="请输入用户名" />
+    <input class="input" bindinput="inputPassword" placeholder="请输入密码" type="password" />
+    <button class="gologin" bindtap="register">注册</button>
+</view>

+ 23 - 0
pages/Register/Register.wxss

@@ -0,0 +1,23 @@
+.container {
+  margin-top: 100rpx;
+  padding: 100rpx;
+}
+
+.input {
+  width: 90%;
+  padding: 12rpx;
+  margin-bottom: 20rpx;
+  border: 1px solid #ccc;
+  border-radius: 8rpx;
+  font-size: 28rpx;
+}
+
+.gologin {
+  width: 50%;
+  padding: 12rpx;
+  background-color: #28a745;
+  color: #fff;
+  border-radius: 8rpx;
+  font-size: 32rpx;
+  text-align: center;
+}

+ 36 - 0
pages/RoleSelectionPage/RoleSelectionPage.js

@@ -0,0 +1,36 @@
+Component({
+  data: {
+  },
+  methods: {
+    //普通用户
+    selectRegularUser(e) {
+      let data = e.currentTarget.dataset;
+      let url = data.path;
+        // 存储普通用户信息到本地缓存
+        wx.setStorageSync('userRole', 'regular');  // 存储角色为普通用户
+        wx.setStorageSync('rolePath', url); // 存储路径(可选)
+      // 可能还需要进行页面跳转或其他逻辑
+      wx.reLaunch({
+        url: '/shoping/Home/Home',
+      });
+    },
+    //管理员
+    selectAdmin(e) {
+      let data = e.currentTarget.dataset;
+      let url = data.path;
+      // 存储管理员信息到本地缓存
+      wx.setStorageSync('userRole', 'admin');  // 存储角色为管理员
+      wx.setStorageSync('rolePath', url); // 存储路径(可选)
+      // 可能还需要进行页面跳转或其他逻辑
+      wx.reLaunch({
+        url: '/pages/threshold/threshold',
+      });
+    },
+    // 隐藏返回首页按钮
+    onShow: function() {
+      if(wx.canIUse('hideHomeButton')) {
+        wx.hideHomeButton();
+      }
+    }
+  },
+});

+ 6 - 0
pages/RoleSelectionPage/RoleSelectionPage.json

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

+ 7 - 0
pages/RoleSelectionPage/RoleSelectionPage.wxml

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

+ 27 - 0
pages/RoleSelectionPage/RoleSelectionPage.wxss

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

+ 55 - 23
pages/Staff/Staff.js

@@ -1,13 +1,31 @@
 Page({
   data: {
     isLogin: false,  // 登录状态
-    userInfo: {},    // 存储用户信息(头像和昵称)
+    userInfo: {},    // 存储用户信息(头像、昵称、用户名、密码)
+    isHidden: true,  // 登录弹窗状态
   },
 
-  // 页面加载时获取缓存的昵称和头像
+  // 页面加载时获取缓存的昵称、头像、用户名、密码
   onLoad() {
-    const storedUserInfo = wx.getStorageSync('userinfo'); // 获取缓存中的用户信息
+    this.loadUserInfo();  // 调用加载用户信息的方法
+  },
 
+  // 页面显示时确保用户信息刷新
+  onShow() {
+    this.loadUserInfo();  // 每次进入页面时刷新用户信息
+    if (typeof this.getTabBar === 'function' && this.getTabBar()) {
+      this.getTabBar().setData({
+      });
+    }
+    // 隐藏返回首页按钮
+    if (wx.canIUse('hideHomeButton')) {
+      wx.hideHomeButton();
+    }
+  },
+
+  // 加载用户信息并更新页面
+  loadUserInfo() {
+    const storedUserInfo = wx.getStorageSync('userInfo');  // 获取缓存中的用户信息
     if (storedUserInfo) {
       // 如果缓存中有用户信息,更新页面上的数据
       this.setData({
@@ -25,9 +43,9 @@ Page({
     });
 
     // 保存头像到缓存
-    let userInfo = wx.getStorageSync('userinfo') || {};
+    let userInfo = wx.getStorageSync('userInfo') || {};
     userInfo.avatarUrl = avatarUrl;
-    wx.setStorageSync('userinfo', userInfo);
+    wx.setStorageSync('userInfo', userInfo);
   },
 
   // 获取用户昵称
@@ -38,18 +56,25 @@ Page({
     });
 
     // 保存昵称到缓存
-    let userInfo = wx.getStorageSync('userinfo') || {};
+    let userInfo = wx.getStorageSync('userInfo') || {};
     userInfo.nickName = nickName;
-    wx.setStorageSync('userinfo', userInfo);
+    wx.setStorageSync('userInfo', userInfo);
   },
 
   // 显示登录弹窗
-  gologin() {
+  goLogin() {
     this.setData({
       isHidden: false
     });
   },
 
+  // 编辑个人资料
+  EditProfile() {
+    wx.navigateTo({
+      url: '/shoping/EditProfile/EditProfile'
+    });
+  },
+
   // 取消登录弹窗
   potNo() {
     this.setData({
@@ -59,37 +84,44 @@ Page({
 
   // 确认登录弹窗
   popYes() {
-    const { avatarUrl, nickName } = this.data.userInfo;
-    if (!avatarUrl || !nickName) {
+    const { avatarUrl, nickName, username, password } = this.data.userInfo;
+    if (!avatarUrl || !nickName || !username || !password) {
       wx.showToast({
         icon: 'error',
-        title: '请获取头像和昵称',
+        title: '请填写头像、昵称、用户名和密码',
       });
       return;
     }
 
-    // 保存头像和昵称到缓存
-    wx.setStorageSync('userinfo', this.data.userInfo);
-
+    // 保存头像、昵称、用户名和密码到缓存
+    wx.setStorageSync('userInfo', this.data.userInfo);
     this.setData({
       isLogin: true,  // 设置登录状态为 true
       isHidden: true, // 隐藏弹窗
     });
   },
 
-  // 退出登录
+  // 跳转到阈值页面
+  goToThreshold() {
+    wx.navigateTo({
+      url: '/pages/threshold/threshold'
+    });
+  },
+
+  // 点击退出登录
   tuichu() {
+    // 清除缓存
+    wx.clearStorageSync();
+
+    // 重置登录状态
     this.setData({
-      isLogin: false,  // 设置登录状态为 false
-      userInfo: {},    // 清除用户信息
+      isLogin: false,
+      userInfo: {}
     });
 
-    // 清除缓存中的用户信息
-    wx.removeStorageSync('userinfo');
-    
-    // 跳转到 "pages/b/b" 页面
-    wx.navigateTo({
-      url: '/pages/b/b', // 路径可以根据实际情况修改
+    // 跳转到登录页面
+    wx.reLaunch({
+      url: '/pages/admin/admin' // 登录页面的路径
     });
   }
 });

+ 1 - 1
pages/Staff/Staff.json

@@ -1,6 +1,6 @@
 {
   "usingComponents": {},
   "navigationBarTitleText": "个人中心",
-  "navigationBarBackgroundColor": "#ffffff",  
+  "navigationBarBackgroundColor": "#dbdbdb",  
   "navigationBarTextStyle": "black"  
 }

+ 20 - 20
pages/Staff/Staff.wxml

@@ -1,36 +1,36 @@
 <view class="center">
-  <!-- 处理点击事件和hover类 -->
+  <!-- 点击头像区域,判断是否登录 -->
   <view class="logo" bindtap="goLogin" hover-class="{{!isLogin ? 'logo-hover' : ''}}">
-  <!-- 如果已登录并且有头像,则显示用户头像 -->
-  <image class="logo-img" wx:if="{{isLogin && userInfo && userInfo.avatarUrl}}" src="{{ userInfo.avatarUrl }}" />
-  <!-- 如果未登录或没有头像,则显示默认头像 -->
-  <image class="logo-img" wx:if="{{!isLogin || !userInfo.avatarUrl}}" src="/assets/taddar/me.png" />
-  <view class="logo-title">
-    <!-- 显示昵称,如果已登录,则显示昵称 -->
-    <text class="uer-name">Hi,{{ isLogin ? userInfo.nickName : '未登录' }}</text>
-    <!-- 未登录时显示登录图标 -->
-    <image class="go-login" wx:if="{{!isLogin}}" src="/assets/taddar/right (1).png" />
+    <!-- 显示用户头像(如果已登录并且有头像) -->
+    <image class="logo-img" wx:if="{{isLogin && userInfo && userInfo.avatarUrl}}" src="{{ userInfo.avatarUrl }}" />
+    <!-- 未登录或没有头像时,显示默认头像 -->
+    <image class="logo-img" wx:if="{{!isLogin || !userInfo.avatarUrl}}" src="/assets/taddar/me.png" />
+    
+    <!-- 显示用户昵称,如果昵称存在,则显示昵称,若没有则显示用户名 -->
+    <view class="logo-title">
+      <text class="uer-name">
+        Hi,{{ isLogin ? (userInfo.nickName || userInfo.username || '未登录') : '未登录' }}
+      </text>
+      <!-- 未登录时显示登录图标 -->
+      <image class="go-login" wx:if="{{!isLogin}}" src="/assets/taddar/right (1).png" />
+    </view>
   </view>
-</view>
-
 
+  <!-- 用户中心列表项 -->
   <view class="center-list">
-    <view class="center-list-item">
-      <image class="list-icon" src="/assets/taddar/lock.png" />
-      <text class="list-text">帐号管理</text>
-      <image class="navigat-arrow" src="/assets/taddar/right.png" />
-    </view>
-    <view class="center-list-item">
+    <!-- 编辑个人信息项 -->
+    <view class="center-list-item" bindtap="EditProfile">
       <image class="list-icon" src="/assets/taddar/edit.png" />
       <text class="list-text">编辑个人信息</text>
       <image class="navigat-arrow" src="/assets/taddar/right.png" />
     </view>
   </view>
 
+  <!-- 退出登录项 -->
   <view class="center-list">
-    <view class="center-list-item border-bottom">
+    <view class="center-list-item border-bottom" bindtap="tuichu">
       <image class="list-icon" src="/assets/taddar/logout.png" />
-      <text class="list-text" bindtap="tuichu">退出登录</text>
+      <text class="list-text">退出登录</text>
       <image class="navigat-arrow" src="/assets/taddar/right.png" />
     </view>
   </view>

+ 3 - 2
pages/Staff/Staff.wxss

@@ -41,14 +41,15 @@ page {
   justify-content: space-between;
   flex-direction: row;
   margin-left: 20rpx;
+  text-align: center;  /* 确保昵称水平居中 */
 }
 
 /* 用户名样式 */
 .uer-name {
-  height: 60rpx;
-  line-height: 60rpx;
   font-size: 38rpx;
   color: #FFFFFF;
+  line-height: 10rpx;  /* 让文本垂直居中,但不会影响水平居中 */
+  margin: 0;  /* 移除上下间距 */
 }
 
 .go-login {

+ 409 - 0
pages/Visualizatio/Visualizatio.js

@@ -0,0 +1,409 @@
+Page({
+  data: {
+    isEditing: false,
+    shoopingtext: "", // 搜索框内容
+    filteredRows: [], // 表格过滤后的数据
+    rows: [], // 所有表格数据
+    showModal: false, // 控制底部编辑删除弹窗的显示与否
+    showAddModal: false, // 控制新增数据弹窗的显示与否
+    currentRow: null, // 当前选中的表格行
+    // 新增数据弹窗的输入框内容
+    newData: {
+      Sample_ID: "",
+      OM: "",
+      CL: "",
+      CEC: "",
+      H: "",
+      HN: "",
+      Al: "",
+      free_alumina: "",
+      free_iron_oxides: "",
+      pH: "",
+      final_pH: "",
+      Collection_location: "",
+      Collection_date: ""
+    },
+    // 中文表头内容,动态生成
+    tableHeaders: [
+      "序号", "0天 pH", "有机质含量 (g/kg)", "土壤粘粒重量 (g/kg)", "阳离子交换量 (cmol/kg)", 
+      "氢离子含量 (cmol/kg)", "铵离子含量 (mg/kg)", "铝离子含量 (cmol/kg)", "游离氧化铝 (g/kg)", 
+      "游离氧化铁 (g/kg)", "105天 pH", "采集位置", "采集日期"
+    ]
+  },
+
+  // 页面加载时获取表格数据
+  onLoad: function() {
+    this.LoadData();
+  },  
+
+  LoadData: function() {
+    wx.request({
+      url: 'http://localhost:5000/tables',
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json'
+      },
+      data: {
+        table: 'Soil_samples'
+    },
+    success: (res) => {
+      console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
+      
+      if (res.data && Array.isArray(res.data.rows)) {
+        const rows = res.data.rows.map(row => {
+          return {
+            'Sample_ID': row[0],
+            'pH': row[1],
+            'OM': row[2],
+            'CL': row[3],
+            'CEC': row[4],
+            'H': row[5],
+            'HN': row[6],
+            'Al': row[7],
+            'free_alumina': row[8],
+            'free_iron_oxides': row[9],
+            'final_pH': row[10],
+            'Collection_location': row[11],
+            'Collection_date': row[12]
+          };
+        });
+
+        this.setData({
+          rows: rows,
+          filteredRows: rows
+        });
+      } else {
+        wx.showToast({
+          title: '获取数据失败',
+          icon: 'none'
+        });
+      }
+    },
+    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
+    });
+  },
+
+  // 新增按钮点击,显示新增弹窗
+  onAdd: function() {
+    console.log("新增按钮被点击了");  // 调试日志
+    this.setData({
+      showAddModal: true,
+      newData: { // 重置新增数据
+        OM: "",
+        CL: "",
+        CEC: "",
+        H: "",
+        HN: "",
+        Al: "",
+        free_alumina: "",
+        free_iron_oxides: "",
+        pH: "",
+        final_pH: "",
+        Collection_location: "",
+        Collection_date: ""
+      }
+    });
+    console.log("showAddModal: ", this.data.showAddModal);  // 确认数据更新
+  },
+
+  // 输入框绑定:更新新增数据的对应字段
+  onInputOM: function(e) {
+    this.setData({
+      'newData.OM': e.detail.value
+    });
+  },
+  onInputCL: function(e) {
+    this.setData({
+      'newData.CL': e.detail.value
+    });
+  },
+  onInputCEC: function(e) {
+    this.setData({
+      'newData.CEC': e.detail.value
+    });
+  },
+  onInputHplus: function(e) {
+    this.setData({
+      'newData.H': e.detail.value
+    });
+  },
+  onInputHN: function(e) {
+    this.setData({
+      'newData.HN': e.detail.value
+    });
+  },
+  onInputAl3plus: function(e) {
+    this.setData({
+      'newData.Al': e.detail.value
+    });
+  },
+  onInputAlumina: function(e) {
+    this.setData({
+      'newData.free_alumina': e.detail.value
+    });
+  },
+  onInputIronOxides: function(e) {
+    this.setData({
+      'newData.free_iron_oxides': e.detail.value
+    });
+  },
+  onInputinitPH: function(e) {
+    this.setData({
+      'newData.pH': e.detail.value
+    });
+  },
+  onInputfinalPH: function(e) {
+    this.setData({
+      'newData.final_pH': e.detail.value
+    });
+  },
+  onInputlocation: function(e) {
+    this.setData({
+      'newData.Collection_location': e.detail.value
+    });
+  },
+  onBindDateChange: function(e) {
+    this.setData({
+      'newData.Collection_date': e.detail.value
+    });
+  },
+
+  // 提交新增数据
+  onSubmitAdd: function() {
+    const newRow = this.data.newData;
+    if (Object.values(newRow).some(value => !value)) {
+      wx.showToast({
+        title: '所有字段都必须填写!',
+        icon: 'none'
+      });
+      return;
+    }
+
+    wx.request({
+      url: 'http://localhost:5000/add_item',
+      method: 'POST',
+      header: {
+        'Content-Type': 'application/json'
+      },
+      data: {
+        table: 'Soil_samples',
+        item: newRow
+      },
+      success: (res) => {
+        if (res.data.success) {
+          this.setData({
+            rows: [...this.data.rows, newRow],
+            showAddModal: false
+          });
+          wx.showToast({
+            title: '新增成功',
+            icon: 'success'
+          });
+          this.LoadData();
+        } else {
+          wx.showToast({
+            title: '新增失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        wx.showToast({
+          title: '请求失败,请重试',
+          icon: 'none'
+        });
+        console.error('请求失败:', err);
+      }
+    });
+  },
+
+  // 取消新增数据
+  onCancel: function() {
+    this.setData({
+      showAddModal: false
+    });
+  },
+
+  // 编辑按钮点击
+onEdit: function(e) {
+  const { index, row } = this.data.currentRow;
+  console.log(row);
+
+  // 检查 currentRow 是否有定义并且包含数据
+  if (row) {
+    this.setData({
+      isEditing: true,
+      showModal: false,
+      showAddModal: true, // 显示编辑弹窗
+      newData: row, // 将当前行的数据复制到 newData 中,以便在表单中显示
+      currentRow: { index: index, row: row } // 保存当前行的信息,以便在提交时使用
+    });
+  } else {
+    wx.showToast({
+      title: '数据加载失败,请重试',
+      icon: 'none'
+    });
+  }
+},
+
+  // 提交编辑数据
+onSubmitEdit: function() {
+  const updatedRow = this.data.newData;
+  const { index } = this.data.currentRow;
+
+  if (Object.values(updatedRow).some(value => !value)) {
+    wx.showToast({
+      title: '所有字段都必须填写!',
+      icon: 'none'
+    });
+    return;
+  }
+
+  wx.request({
+    url: 'http://localhost:5000/update_item',
+    method: 'PUT',
+    header: {
+      'Content-Type': 'application/json'
+    },
+    data: {
+      table: 'Soil_samples',
+      item: updatedRow
+    },
+    success: (res) => {
+      if (res.data.success) {
+        // 更新成功后,更新前端数据
+        let rows = this.data.rows;
+        rows[index] = updatedRow; // 用更新后的数据替换旧数据
+        this.setData({
+          rows: rows,
+          showAddModal: false // 关闭编辑弹窗
+        });
+        wx.showToast({
+          title: '编辑成功',
+          icon: 'success'
+        });
+        this.LoadData();
+      } else {
+        wx.showToast({
+          title: '编辑失败',
+          icon: 'none'
+        });
+      }
+    },
+    fail: (err) => {
+      wx.showToast({
+        title: '请求失败,请重试',
+        icon: 'none'
+      });
+      console.error('请求失败:', err);
+    }
+  });
+},
+
+  // 删除按钮点击
+  onDelete: function() {
+    const { index, row } = this.data.currentRow;
+    console.log("当前选中行的数据:", this.data.currentRow);
+  
+    const condition = `Sample_ID=${row.Sample_ID}`;  // 使用条件进行删除
+  
+    // 发送 DELETE 请求
+    wx.request({
+      url: 'http://127.0.0.1:5000/delete_item',  // 确保使用正确的URL
+      method: 'DELETE',  // 使用 DELETE 请求方法
+      header: {
+        'Content-Type': 'application/json'  // 请求类型是 JSON
+      },
+      data: {
+        table: 'Soil_samples',  // 目标表
+        condition: condition  // 删除条件
+      },
+      success: (res) => {
+        if (res.data.success) {
+          // 删除成功后,更新前端数据
+          let rows = this.data.rows;
+          rows.splice(index, 1);  // 从数据中删除选中的行
+          this.setData({
+            rows: rows,
+            showModal: false  // 隐藏编辑删除弹窗
+          });
+          wx.showToast({
+            title: '删除成功',
+            icon: 'success'
+          });
+  
+          // 重新获取数据
+          this.LoadData();  // 重新加载数据
+        } else {
+          wx.showToast({
+            title: '删除失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        wx.showToast({
+          title: '请求失败,请重试',
+          icon: 'none'
+        });
+        console.error('请求失败:', err);
+      }
+    });
+  },  
+
+  onSubmit: function() {
+    if (this.data.isEditing) {
+      this.onSubmitEdit();
+    } else {
+      this.onSubmitAdd();
+    }
+  },
+  // 取消编辑删除弹窗
+  onCancel: function() {
+    if (this.data.showModal) {
+      this.setData({
+        showModal: false
+      });
+    } else {
+      this.setData({
+        showAddModal: false
+      });
+    }
+  },
+
+  // 行点击事件:显示编辑和删除弹窗
+  onRowClick: function(e) {
+    const index = e.currentTarget.dataset.index;
+    const row = e.currentTarget.dataset.row;
+
+    this.setData({
+      currentRow: { index: index, row: row },
+      showModal: true  // 显示编辑删除弹窗
+    });
+  }
+});

+ 6 - 0
pages/Visualizatio/Visualizatio.json

@@ -0,0 +1,6 @@
+{
+  "usingComponents": { },
+  "navigationBarTitleText": "数据展示",
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"
+}

+ 105 - 0
pages/Visualizatio/Visualizatio.wxml

@@ -0,0 +1,105 @@
+<!-- 固定顶部区域 -->
+<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="onAdd">新增</button>
+  <button class="add-btn" bindtap="onImport">导入</button>
+  <button class="add-btn" bindtap="onExport">导出</button>
+
+  </view>
+</view>
+
+<!-- 滚动区域 -->
+<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" 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}}" data-row="{{item}}">
+        <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">{{item.pH}}</view>
+        <view class="table-cell">{{item.OM}}</view>
+        <view class="table-cell">{{item.CL}}</view>
+        <view class="table-cell">{{item.CEC}}</view>
+        <view class="table-cell">{{item.H}}</view>
+        <view class="table-cell">{{item.HN}}</view>
+        <view class="table-cell">{{item.Al}}</view>
+        <view class="table-cell">{{item.free_alumina}}</view>
+        <view class="table-cell">{{item.free_iron_oxides}}</view>
+        <view class="table-cell">{{item.final_pH}}</view>
+        <view class="table-cell">{{Collection_location}}</view>
+        <view class="table-cell">{{Collection_date}}</view>
+      </view>
+    </block>
+  </view>
+</scroll-view>
+
+<!-- 新增数据弹窗 -->
+<view class="modal" wx:if="{{showAddModal}}">
+  <view class="modal-content">
+    <view class="page-section-title">0 day pH:</view>
+    <input class="input-field" placeholder="请输入0 day pH" data-field="pH" bindinput="onInputinitPH" value="{{newData.pH}}"/>
+    <view class="page-section-title">有机质含量 (OM g/kg):</view>
+    <input class="input-field" placeholder="请输入OM含量" data-field="OM" bindinput="onInputOM" value="{{newData.OM}}"/>
+    <view class="page-section-title">土壤粘粒重量 (CL g/kg):</view>
+    <input class="input-field" placeholder="请输入CL含量" data-field="CL" bindinput="onInputCL" value="{{newData.CL}}"/>
+    <view class="page-section-title">阳离子交换量 (CEC cmol/kg):</view>
+    <input class="input-field" placeholder="请输入CEC含量" data-field="CEC" bindinput="onInputCEC" value="{{newData.CEC}}"/>
+    <view class="page-section-title">氢离子含量 (H+ cmol/kg):</view>
+    <input class="input-field" placeholder="请输入H+含量" data-field="H" bindinput="onInputHplus" value="{{newData.H}}"/>
+    <view class="page-section-title">铵离子含量 (HN cmol/kg):</view>
+    <input class="input-field" placeholder="请输入HN含量" data-field="HN" bindinput="onInputHN" value="{{newData.HN}}"/>
+    <view class="page-section-title">铝离子含量 (Al3+ cmol/kg):</view>
+    <input class="input-field" placeholder="请输入Al3+含量" data-field="Al" bindinput="onInputAl3plus" value="{{newData.Al}}"/>
+    <view class="page-section-title">游离氧化铝 (free alumina g/kg):</view>
+    <input class="input-field" placeholder="请输入游离氧化铝含量" data-field="free_alumina" bindinput="onInputAlumina" value="{{newData.free_alumina}}"/>
+    <view class="page-section-title">游离氧化铁 (free iron oxides g/kg):</view>
+    <input class="input-field" placeholder="请输入游离氧化铁含量" data-field="free_iron_oxides" bindinput="onInputIronOxides"value="{{newData.free_iron_oxides}}" />
+    <view class="page-section-title">105 day pH:</view>
+    <input class="input-field" placeholder="请输入105 day pH" data-field="final_pH" bindinput="onInputfinalPH"value="{{newData.final_pH}}" />
+    <view class="page-section-title">采样地点: </view>
+<input class="input-field" placeholder="请输入采样地点" data-field="collection_location" bindinput="onInputlocation" value="{{newData.Collection_location}}"/>
+
+<!-- 日期选择器 -->
+<picker mode="date" value="{{newData.Collection_date}}" bindchange="onBindDateChange">
+  <view class="picker-container">
+    <text class="page-section-title">采样时间:</text>
+    <text class="color6">{{newData.Collection_date || '请选择日期'}}</text>
+  </view>
+    </picker>
+
+    <view class="button-container">
+      <button class="submit-btn" bindtap="onSubmit">确认</button>
+      <button class="cancel-btn" bindtap="onCancel">取消</button>
+    </view>
+  </view>
+</view>
+
+<!-- 底部编辑删除弹窗 -->
+<view class="modal" wx:if="{{showModal}}">
+  <view class="modal-content">
+    <view class="modal-item" bindtap="onEdit">编辑</view>
+    <view class="modal-item delete" bindtap="onDelete">删除</view>
+    <view class="modal-item cancel" bindtap="onCancel">取消</view>
+  </view>
+</view>

+ 205 - 0
pages/Visualizatio/Visualizatio.wxss

@@ -0,0 +1,205 @@
+/* 顶部固定区域 */
+.top-container {
+  position: sticky;
+  top: 0;
+  background-color: white;
+  z-index: 100; /* 保证顶部元素位于表格之上 */
+}
+
+.topsearch {
+  width: 90%;
+  margin-left: 5%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between; /* 确保搜索框和按钮分开 */
+}
+
+.frame {
+  background-color: white;
+  width: 75%;
+  border-radius: 1px;
+  padding: 0 3%;
+}
+
+.frame > input {
+  font-size: 24rpx;
+  margin: 6rpx 0;
+}
+
+.topsearch > text {
+  width: 10%;
+  margin-left: 5%;
+  color: #a8a7a7fa;
+}
+
+/* 新增按钮容器 */
+.add-btn-container {
+  position: sticky;
+  top: 0;
+  background-color: #fff; /* 背景色为白色 */
+  z-index: 101; /* 确保新增按钮高于表格 */
+  padding: 1rpx; /* 增加按钮的上内边距 */
+  display: flex;
+  justify-content: center;
+  margin-top: 1rpx; /* 新增按钮的上边距 */
+}
+
+/* 新增按钮 */
+.add-btn {
+  padding: 1px 12px;
+  background-color: #3EC01E;
+  color: #fff;
+  border-radius: 4px;
+  font-size: 14px;
+}
+
+/* 表格容器 */
+.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: 250rpx; /* 固定宽度 */
+  padding: 10rpx;
+  text-align: center;
+  font-size: 28rpx;
+  color: #030803; /* 表头文字颜色 */
+  border-right: 1rpx solid #ddd;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 表格行样式 */
+.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;
+}
+
+/* 模态框背景 */
+.modal {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5); /* 半透明背景 */
+  z-index: 1000; /* 确保弹窗在最上层 */
+}
+
+/* 模态框内容 */
+.modal-content {
+  background-color: #fff; /* 白色背景 */
+  padding: 20px;
+  border-radius: 8px;
+  width: 90%;
+  max-width: 400px; /* 最大宽度 */
+  max-height: 80vh; /* 最大高度,留出一些空间以便滚动条显示 */
+  overflow-y: auto; /* 垂直方向溢出时显示滚动条 */
+  overflow-x: hidden; /* 隐藏水平滚动条 */
+}
+
+/* 每个选项项 */
+.modal-item {
+  text-align: center; /* 每个项的文本居中 */
+  font-size: 32rpx; /* 字体大小 */
+  padding: 20rpx 0; /* 内边距 */
+  border-top: 1rpx solid #eee; /* 上边框为浅灰色 */
+  cursor: pointer; /* 鼠标悬停时显示为手指 */
+  transition: background-color 0.2s; /* 添加鼠标悬停的平滑过渡 */
+}
+
+/* 第一项不显示上边框 */
+.modal-item:first-child {
+  border-top: none;
+}
+
+/* 提交按钮样式 */
+.submit-btn {
+  background-color: #4CAF50; /* 绿色背景 */
+  color: white; /* 白色字体 */
+  border: none; /* 去掉边框 */
+  padding: 10rpx 20rpx; /* 内边距 */
+  font-size: 32rpx; /* 字体大小 */
+  border-radius: 8rpx; /* 圆角 */
+  cursor: pointer; /* 鼠标悬停时显示为手指 */
+  transition: background-color 0.3s ease; /* 背景色变化平滑过渡 */
+}
+
+/* 提交按钮悬停效果 */
+.submit-btn:hover {
+  background-color: #45a049; /* 悬停时变为更深的绿色 */
+}
+
+/* 取消按钮样式 */
+.cancel-btn {
+  background-color: #e74c3c; /* 红色背景 */
+  color: white; /* 白色字体 */
+  border: none; /* 去掉边框 */
+  padding: 10rpx 20rpx; /* 内边距 */
+  font-size: 32rpx; /* 字体大小 */
+  border-radius: 8rpx; /* 圆角 */
+  cursor: pointer; /* 鼠标悬停时显示为手指 */
+  transition: background-color 0.3s ease; /* 背景色变化平滑过渡 */
+}
+
+/* 取消按钮悬停效果 */
+.cancel-btn:hover {
+  background-color: #c0392b; /* 悬停时变为更深的红色 */
+}
+
+/* 新增数据弹窗的按钮容器 */
+.modal-content {
+  display: flex;
+  flex-direction: column; /* 默认垂直方向布局 */
+  align-items: center; /* 居中对齐内容 */
+}
+
+/* 按钮容器 */
+.button-container {
+  display: flex; /* 使用 flexbox 布局 */
+  justify-content: space-between; /* 按钮之间均匀分布 */
+  width: 100%; /* 按钮容器宽度为 100% */
+  margin-top: 20rpx; /* 上边距 */
+}
+
+/* 每个按钮样式 */
+.submit-btn, .cancel-btn {
+  flex: 1; /* 使按钮宽度相等 */
+  margin: 0 10rpx; /* 按钮间隔 */
+}

+ 25 - 13
pages/Visualization/Visualization.js

@@ -22,7 +22,13 @@ Page({
       final_pH: "",
       Collection_location: "",
       Collection_date: ""
-    }
+    },
+    // 中文表头内容,动态生成
+    tableHeaders: [
+      "序号", "0天 pH", "有机质含量 (g/kg)", "土壤粘粒重量 (g/kg)", "阳离子交换量 (cmol/kg)", 
+      "氢离子含量 (cmol/kg)", "铵离子含量 (mg/kg)", "铝离子含量 (cmol/kg)", "游离氧化铝 (g/kg)", 
+      "游离氧化铁 (g/kg)", "105天 pH", "采集位置", "采集日期"
+    ]
   },
 
   // 页面加载时获取表格数据
@@ -322,32 +328,37 @@ onSubmitEdit: function() {
   // 删除按钮点击
   onDelete: function() {
     const { index, row } = this.data.currentRow;
-    console.log(this.data.currentRow);
-    const condition = `Sample_ID=${row.Sample_ID}`;
-
+    console.log("当前选中行的数据:", this.data.currentRow);
+  
+    const condition = `Sample_ID=${row.Sample_ID}`;  // 使用条件进行删除
+  
+    // 发送 DELETE 请求
     wx.request({
-      url: 'http://127.0.0.1:5000/delete_item',
-      method: 'POST',
+      url: 'http://127.0.0.1:5000/delete_item',  // 确保使用正确的URL
+      method: 'DELETE',  // 使用 DELETE 请求方法
       header: {
-        'Content-Type': 'application/json'
+        'Content-Type': 'application/json'  // 请求类型是 JSON
       },
       data: {
-        table: 'Soil_samples',
-        condition: condition
+        table: 'Soil_samples',  // 目标表
+        condition: condition  // 删除条件
       },
       success: (res) => {
         if (res.data.success) {
+          // 删除成功后,更新前端数据
           let rows = this.data.rows;
-          rows.splice(index, 1);  // 删除选中的行
+          rows.splice(index, 1);  // 从数据中删除选中的行
           this.setData({
             rows: rows,
-            showModal: false
+            showModal: false  // 隐藏编辑删除弹窗
           });
           wx.showToast({
             title: '删除成功',
             icon: 'success'
           });
-          this.LoadData();
+  
+          // 重新获取数据
+          this.LoadData();  // 重新加载数据
         } else {
           wx.showToast({
             title: '删除失败',
@@ -363,7 +374,8 @@ onSubmitEdit: function() {
         console.error('请求失败:', err);
       }
     });
-  },
+  },  
+
   onSubmit: function() {
     if (this.data.isEditing) {
       this.onSubmitEdit();

+ 3 - 3
pages/Visualization/Visualization.json

@@ -1,6 +1,6 @@
 {
-  "usingComponents": {},
+  "usingComponents": { },
   "navigationBarTitleText": "数据展示",
-  "navigationBarBackgroundColor": "#ffffff",  
-  "navigationBarTextStyle": "black"  
+  "navigationBarBackgroundColor": "#dbdbdb",  
+  "navigationBarTextStyle": "black"
 }

+ 47 - 54
pages/Visualization/Visualization.wxml

@@ -1,5 +1,5 @@
-<scroll-view class="table-container" scroll-x="true" scroll-with-animation="true">
-  <!-- 顶部搜索 -->
+<!-- 固定顶部区域 -->
+<view class="top-container">
   <view class="top">
     <view class="topsearch">
       <view class="frame">
@@ -9,63 +9,56 @@
     </view>
   </view>
 
-  <!-- 新增按钮 -->
   <view class="add-btn-container">
-    <button class="add-btn" bindtap="onAdd">新增</button>
-    <button class="add-btn" bindtap="onAdd">导入</button>
-    <button class="add-btn" bindtap="onAdd">导出</button>
+  <button class="add-btn" bindtap="onAdd">新增</button>
+  <button class="add-btn" bindtap="onImport">导入</button>
+  <button class="add-btn" bindtap="onExport">导出</button>
+
   </view>
+</view>
 
-  <!-- 表头 -->
-  <view class="table">
-    <view class="table-header">
-      <view class="table-cell">序号</view>
-      <view class="table-cell">有机质含量</view>
-      <view class="table-cell">土壤粘粒重量</view>
-      <view class="table-cell">阳离子交换量</view>
-      <view class="table-cell">氢离子含量</view>
-      <view class="table-cell">铵离子含量</view>
-      <view class="table-cell">铝离子含量</view>
-      <view class="table-cell">游离氧化铝</view>
-      <view class="table-cell">游离氧化铁</view>
-      <view class="table-cell">0 day pH</view>
-      <view class="table-cell">105 day pH</view>
-      <view class="table-cell">采集位置</view>
-      <view class="table-cell">采集日期</view>
-    </view>
+<!-- 滚动区域 -->
+<scroll-view class="table-container" scroll-x="true" scroll-with-animation="true">
+  <!-- 数据加载中 -->
+  <view wx:if="{{loading}}" class="loading">数据加载中,请稍候...</view>
 
-    <!-- 数据加载中 -->
-    <view wx:if="{{loading}}" class="loading">数据加载中,请稍候...</view>
+  <!-- 无数据提示 -->
+  <view wx:if="{{!loading && filteredRows.length === 0}}" class="no-data">暂无数据</view>
 
-    <!-- 无数据提示 -->
-    <view wx:if="{{!loading && filteredRows.length === 0}}" class="no-data">暂无数据</view>
+  <!-- 表格 -->
+  <view class="table-body">
+    <view class="table-header">
+      <view class="table-cell" wx:for="{{tableHeaders}}" wx:key="index">{{item}}</view>
+    </view>
 
-    <!-- 表格内容 -->
-    <scroll-view wx:else scroll-y="true" class="table-body">
-      <block wx:for="{{filteredRows}}" wx:key="index">
-        <view class="table-row" bindtap="onRowClick" data-index="{{index}}" data-row="{{item}}">
-          <view class="table-cell">{{item['Sample_ID']}}</view>
-          <view class="table-cell">{{item['pH']}}</view>
-          <view class="table-cell">{{item['OM']}}</view>
-          <view class="table-cell">{{item['CL']}}</view>
-          <view class="table-cell">{{item['CEC']}}</view>
-          <view class="table-cell">{{item['H']}}</view>
-          <view class="table-cell">{{item['HN']}}</view>
-          <view class="table-cell">{{item['Al']}}</view>
-          <view class="table-cell">{{item['free_alumina']}}</view>
-          <view class="table-cell">{{item['free_iron_oxides']}}</view>
-          <view class="table-cell">{{item['final_pH']}}</view>
-          <view class="table-cell">{{item['Collection_location']}}</view>
-          <view class="table-cell">{{item['Collection_date']}}</view>
+    <block wx:for="{{filteredRows}}" wx:key="index">
+      <view class="table-row" bindtap="onRowClick" data-index="{{index}}" data-row="{{item}}">
+        <view class='table-cell' wx:for="{{list}}" wx:key="item">
+          <view class='item'>{{item}}</view>
         </view>
-      </block>
-    </scroll-view>
+        <view class="table-cell">{{index + 1}}</view> <!-- 序号 -->
+        <view class="table-cell">{{item.pH}}</view>
+        <view class="table-cell">{{item.OM}}</view>
+        <view class="table-cell">{{item.CL}}</view>
+        <view class="table-cell">{{item.CEC}}</view>
+        <view class="table-cell">{{item.H}}</view>
+        <view class="table-cell">{{item.HN}}</view>
+        <view class="table-cell">{{item.Al}}</view>
+        <view class="table-cell">{{item.free_alumina}}</view>
+        <view class="table-cell">{{item.free_iron_oxides}}</view>
+        <view class="table-cell">{{item.final_pH}}</view>
+        <view class="table-cell">{{Collection_location}}</view>
+        <view class="table-cell">{{Collection_date}}</view>
+      </view>
+    </block>
   </view>
 </scroll-view>
 
 <!-- 新增数据弹窗 -->
 <view class="modal" wx:if="{{showAddModal}}">
   <view class="modal-content">
+    <view class="page-section-title">0 day pH:</view>
+    <input class="input-field" placeholder="请输入0 day pH" data-field="pH" bindinput="onInputinitPH" value="{{newData.pH}}"/>
     <view class="page-section-title">有机质含量 (OM g/kg):</view>
     <input class="input-field" placeholder="请输入OM含量" data-field="OM" bindinput="onInputOM" value="{{newData.OM}}"/>
     <view class="page-section-title">土壤粘粒重量 (CL g/kg):</view>
@@ -82,17 +75,17 @@
     <input class="input-field" placeholder="请输入游离氧化铝含量" data-field="free_alumina" bindinput="onInputAlumina" value="{{newData.free_alumina}}"/>
     <view class="page-section-title">游离氧化铁 (free iron oxides g/kg):</view>
     <input class="input-field" placeholder="请输入游离氧化铁含量" data-field="free_iron_oxides" bindinput="onInputIronOxides"value="{{newData.free_iron_oxides}}" />
-    <view class="page-section-title">0 day pH:</view>
-    <input class="input-field" placeholder="请输入0 day pH" data-field="pH" bindinput="onInputinitPH" value="{{newData.pH}}"/>
     <view class="page-section-title">105 day pH:</view>
     <input class="input-field" placeholder="请输入105 day pH" data-field="final_pH" bindinput="onInputfinalPH"value="{{newData.final_pH}}" />
     <view class="page-section-title">采样地点: </view>
-    <input class="input-field" placeholder="请输入采样地点" data-field="collection_location" bindinput="onInputlocation" value="{{newData.Collection_location}}"/>
-    <picker mode="date"value="{{newData.Collection_date}}" bindchange="onBindDateChange">
-      <view class="picker-container">
-        <text class="page-section-title">采样时间:</text>
-        <text class="color6">{{newData.Collection_date || '请选择日期'}}</text>
-      </view>
+<input class="input-field" placeholder="请输入采样地点" data-field="collection_location" bindinput="onInputlocation" value="{{newData.Collection_location}}"/>
+
+<!-- 日期选择器 -->
+<picker mode="date" value="{{newData.Collection_date}}" bindchange="onBindDateChange">
+  <view class="picker-container">
+    <text class="page-section-title">采样时间:</text>
+    <text class="color6">{{newData.Collection_date || '请选择日期'}}</text>
+  </view>
     </picker>
 
     <view class="button-container">

+ 28 - 105
pages/Visualization/Visualization.wxss

@@ -1,10 +1,9 @@
-/* 确保搜索框和新增按钮固定在顶部 */
-.top {
-  position: sticky; /* 使搜索框在页面滚动时固定 */
-  top: 0; /* 固定在顶部 */
-  z-index: 100; /* 确保在表格之上 */
-  background-color: #f7f7f7; /* 背景色 */
-  padding: 2% 0; /* 上下内边距 */
+/* 顶部固定区域 */
+.top-container {
+  position: sticky;
+  top: 0;
+  background-color: white;
+  z-index: 100; /* 保证顶部元素位于表格之上 */
 }
 
 .topsearch {
@@ -18,7 +17,7 @@
 .frame {
   background-color: white;
   width: 75%;
-  border-radius: 20rpx;
+  border-radius: 1px;
   padding: 0 3%;
 }
 
@@ -39,16 +38,16 @@
   top: 0;
   background-color: #fff; /* 背景色为白色 */
   z-index: 101; /* 确保新增按钮高于表格 */
-  padding: 10rpx; /* 增加按钮的上内边距 */
+  padding: 1rpx; /* 增加按钮的上内边距 */
   display: flex;
   justify-content: center;
-  margin-top: 10rpx; /* 新增按钮的上边距 */
+  margin-top: 1rpx; /* 新增按钮的上边距 */
 }
 
 /* 新增按钮 */
 .add-btn {
-  padding: 5px 12px;
-  background-color: #007aff;
+  padding: 1px 12px;
+  background-color: #3EC01E;
   color: #fff;
   border-radius: 4px;
   font-size: 14px;
@@ -58,36 +57,44 @@
 .table-container {
   width: 100%;
   overflow-x: auto; /* 启用横向滚动 */
+  white-space: nowrap; /* 禁止换行 */
   background-color: #fff;
   border: 1rpx solid #ddd;
-  margin-top: 20rpx; /* 添加上边距,确保不会和顶部元素重叠 */
+  margin-top: 10rpx; /* 添加上边距,确保不会和顶部元素重叠 */
   white-space: nowrap; /* 禁止换行 */
 }
 
 /* 表头样式 */
 .table-header {
   display: flex;
-  width: max-content;
-  background-color: #FFD700;
+  background-color: #61E054; /* 表头背景色 */
   font-weight: bold;
-  border-bottom: 1rpx solid #ddd;
-  z-index: 99; /* 确保表头高于表格内容 */
+  text-align: center;
 }
 
 /* 表格单元格样式 */
 .table-cell {
   flex: none;
-  width: 250rpx;
+  width: 250rpx; /* 固定宽度 */
   padding: 10rpx;
   text-align: center;
   font-size: 28rpx;
-  color: #333;
+  color: #030803; /* 表头文字颜色 */
   border-right: 1rpx solid #ddd;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
 
+/* 表格行样式 */
+.table-row:nth-child(odd) {
+  background-color: #E4FBE5; /* 奇数行背景色 */
+}
+
+.table-row:nth-child(even) {
+  background-color: #fff; /* 偶数行背景色 */
+}
+
 /* 表格内容样式 */
 .table-body {
   display: flex;
@@ -97,14 +104,8 @@
 
 .table-row {
   display: flex;
-}
-
-.table-row:nth-child(odd) {
-  background-color: #f9f9f9;
-}
-
-.table-row:nth-child(even) {
-  background-color: #ffffff;
+  flex-direction: row;
+  border-bottom: 1rpx solid #ddd;
 }
 
 /* 模态框背景 */
@@ -202,81 +203,3 @@
   flex: 1; /* 使按钮宽度相等 */
   margin: 0 10rpx; /* 按钮间隔 */
 }
-.picker-placeholder {
-  flex-grow: 1;
-  color: #888;
-  margin-right: 10px;
-}
-
-.picker {
-  flex-shrink: 0;
-  width: 100px; /* 根据需要调整宽度 */
-}
-
-/* 选择器容器样式 */
-.picker-container {
-  display: flex;
-  align-items: center;
-  margin-bottom: 15px;
-}
-
-/* 选择器文本样式 */
-.picker-container text {
-  margin-right: 10px;
-}
-
-/* 按钮悬停效果 */
-.submit-btn:hover,
-.cancel-btn:hover {
-  opacity: 0.8;
-}
-
-/* 输入字段样式 */
-.input-field {
-  width: 100%;
-  padding: 10rpx;
-  font-size: 28rpx;
-  color: #333;
-  border: none;
-  border-bottom: 1px solid #ddd;
-}
-
-/* 输入字段聚焦时的样式 */
-.input-field:focus {
-  border-color: #007bff; /* 聚焦时边框颜色 */
-  outline: none; /* 去除默认的聚焦轮廓 */
-  box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); /* 聚焦时的阴影效果 */
-}
-
-/* 输入字段禁用时的样式 */
-.input-field:disabled {
-  background-color: #e9ecef; /* 背景色 */
-  opacity: 0.65; /* 不透明度 */
-  cursor: not-allowed; /* 禁用时的鼠标样式 */
-}
-
-/* 输入字段的占位符样式 */
-.input-field::placeholder {
-  color: #6c757d; /* 占位符文本颜色 */
-  font-style: italic; /* 斜体 */
-}
-
-/* 输入字段的动画效果 */
-.input-field:active, .input-field:focus {
-  transition: all 0.15s ease-in-out;
-}
-
-/* 输入字段的响应式设计 */
-@media (max-width: 768px) {
-  .input-field {
-    font-size: 14px; /* 在小屏幕上减小字体大小 */
-  }
-}
-
-/* 页面标题样式 */
-.page-section-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 10rpx;
-}

+ 259 - 0
pages/admin/admin.js

@@ -0,0 +1,259 @@
+Page({
+  data: {
+    isLogin: false, // 登录状态
+    isHidden: true, // 弹窗状态
+    avatarUrl: '', // 用户头像
+    nickName: '', // 用户昵称
+    username: '', // 用户名(用户名密码登录)
+    password: '', // 密码(用户名密码登录)
+    errorMessage: '', // 错误信息
+  },
+
+  // 获取用户头像
+  getAvatar(e) {
+    const avatarUrl = e.detail.avatarUrl; // 从事件中获取头像URL
+
+    if (avatarUrl) {
+      this.setData({
+        avatarUrl: avatarUrl
+      });
+
+      // 创建userInfo对象,保存头像、昵称、用户名和密码到缓存
+      let userInfo = {
+        avatarUrl: avatarUrl,
+        nickName: this.data.nickName,
+        username: this.data.username, // 保存用户名
+        password: this.data.password  // 保存密码
+      };
+      wx.setStorageSync('userInfo', userInfo); // 保存到缓存
+    } else {
+      wx.showToast({
+        title: '头像获取失败',
+        icon: 'error',
+        duration: 2000
+      });
+    }
+  },
+
+  // 获取用户昵称
+  getName(e) {
+    const nickName = e.detail.value; // 获取昵称
+    this.setData({
+      nickName: nickName
+    });
+
+    // 更新缓存中的昵称
+    let userInfo = wx.getStorageSync('userInfo') || {};
+    userInfo.nickName = nickName;
+    wx.setStorageSync('userInfo', userInfo); // 更新缓存
+  },
+
+  // 显示登录弹窗
+  gologin() {
+    this.setData({
+      isHidden: false
+    });
+  },
+
+  // 取消登录弹窗
+  potNo() {
+    this.setData({
+      isHidden: true
+    });
+  },
+
+  // 确认登录弹窗
+  popYes() {
+    const { avatarUrl, nickName, username, password } = this.data;
+
+    // 检查头像、昵称、用户名和密码是否存在
+    if ((!avatarUrl || !nickName) && (!username || !password)) {
+      wx.showToast({
+        icon: 'error',
+        title: '请获取头像、昵称、用户名和密码',
+      });
+      return;
+    }
+
+    // 如果头像和昵称存在,保存到缓存
+    if (avatarUrl && nickName) {
+      this.setData({
+        isLogin: true,
+        isHidden: true,
+      });
+
+      // 创建/更新 userInfo 对象并存储到缓存
+      let userInfo = {
+        avatarUrl: avatarUrl,
+        nickName: nickName,
+        username: this.data.username, // 保存用户名
+        password: this.data.password, // 保存密码
+      };
+      wx.setStorageSync('userInfo', userInfo); // 更新缓存
+
+      // 登录成功后跳转到指定页面
+      wx.navigateTo({
+        url: '/pages/RoleSelectionPage/RoleSelectionPage',
+      });
+
+      return;
+    }
+
+    // 检查用户名和密码是否一致登录
+    const storedUserInfo = wx.getStorageSync('userInfo');
+    if (storedUserInfo) {
+      const storedUsername = storedUserInfo.username;
+      const storedPassword = storedUserInfo.password;
+
+      // 验证用户名和密码
+      if (username === storedUsername && password === storedPassword) {
+        wx.showToast({
+          title: '登录成功',
+          icon: 'success',
+          duration: 2000
+        });
+
+        this.setData({
+          isLogin: true,
+          errorMessage: '',
+        });
+
+        // 登录成功后跳转到指定页面
+        wx.navigateTo({
+          url: '/pages/RoleSelectionPage/RoleSelectionPage',
+        });
+      } else {
+        this.setData({
+          errorMessage: '用户名或密码错误'
+        });
+        wx.showToast({
+          title: '用户名或密码错误',
+          icon: 'none',
+          duration: 2000
+        });
+      }
+    }
+  },
+
+  Register() {
+    wx.navigateTo({
+      url: '/pages/Register/Register',
+    });
+  },
+
+  // 用户名密码登录
+  inputUsername(e) {
+    this.setData({
+      username: e.detail.value
+    });
+  },
+
+  inputPassword(e) {
+    this.setData({
+      password: e.detail.value
+    });
+  },
+
+  // 登录验证
+  login() {
+    const { username, password } = this.data;
+
+    // 获取缓存的用户信息
+    const storedUserInfo = wx.getStorageSync('userInfo');
+
+    // 检查用户信息是否存在
+    if (!storedUserInfo) {
+      this.setData({
+        errorMessage: '没有找到用户信息,请先注册'
+      });
+      return;
+    }
+
+    const storedUsername = storedUserInfo.username;
+    const storedPassword = storedUserInfo.password;
+
+    // 检查用户名和密码是否为空
+    if (!username || !password) {
+      this.setData({
+        errorMessage: '用户名和密码不能为空'
+      });
+      return;
+    }
+
+    wx.showLoading({
+      title: '登录中...'
+    });
+
+    setTimeout(() => {
+      wx.hideLoading();
+
+      // 验证输入的用户名和密码与缓存中的是否一致
+      if (username === storedUsername && password === storedPassword) {
+        wx.showToast({
+          title: '登录成功',
+          icon: 'success',
+          duration: 2000
+        });
+        this.setData({
+          isLogin: true,
+          errorMessage: '',
+        });
+
+        // 登录成功后跳转到指定页面
+        wx.navigateTo({
+          url: '/pages/RoleSelectionPage/RoleSelectionPage',
+        });
+      } else {
+        this.setData({
+          errorMessage: '用户名或密码错误'
+        });
+        wx.showToast({
+          title: '用户名或密码错误',
+          icon: 'none',
+          duration: 2000
+        });
+      }
+    }, 1500);
+  },
+
+  // 页面加载时,检查是否有缓存的用户信息
+  onLoad() {
+    const storedUserInfo = wx.getStorageSync('userInfo');
+
+    // 如果有缓存的用户信息,自动填充头像、昵称、用户名和密码
+    if (storedUserInfo) {
+      this.setData({
+        avatarUrl: storedUserInfo.avatarUrl || '/assets/taddar/授权管理.png', // 默认头像
+        nickName: storedUserInfo.nickName || '未登录', // 默认昵称
+        username: storedUserInfo.username || '', // 默认用户名
+        password: storedUserInfo.password || '', // 默认密码
+        isLogin: true, // 设置已登录状态
+      });
+    }
+
+    // 如果没有设置头像,使用默认头像
+    if (!this.data.avatarUrl) {
+      this.setData({
+        avatarUrl: '/assets/taddar/授权管理.png' // 默认头像
+      });
+    }
+
+    // 获取存储的用户角色
+    const storedUserRole = wx.getStorageSync('userRole');  // 获取缓存中的用户角色
+
+    // 根据角色跳转页面
+    if (storedUserRole) {
+      if (storedUserRole === 'admin') {
+        // 如果是管理员,跳转到管理员页面
+        wx.reLaunch({
+          url: '/pages/threshold/threshold', // 管理员页面路径
+        });
+      } else if (storedUserRole === 'regular') {
+        // 如果是普通用户,跳转到普通用户页面
+        wx.reLaunch({
+          url: '/shoping/Home/Home', // 普通用户页面路径
+        });
+      }
+    }
+  }
+});

+ 6 - 0
pages/admin/admin.json

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

+ 44 - 0
pages/admin/admin.wxml

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

+ 208 - 0
pages/admin/admin.wxss

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

+ 10 - 17
pages/logs/logs.js

@@ -1,22 +1,15 @@
+//logs.js
+const util = require('../../utils/util.js')
+
 Page({
   data: {
-    login: false,
-    uerInfo: {}
+    logs: []
   },
-
-  // 退出登录
-  tuichu() {
+  onLoad: function () {
     this.setData({
-      avatarUrl: '',
-      nickName: '',
-      isLogin: false
-    });
-  },
-  
-  goLogin() {
-    if (!this.data.login) {
-      console.log("点击前往登录");
-      // 这里你可以加入逻辑去处理登录操作
-    }
+      logs: (wx.getStorageSync('logs') || []).map(log => {
+        return util.formatTime(new Date(log))
+      })
+    })
   }
-});
+})

+ 2 - 3
pages/logs/logs.json

@@ -1,5 +1,4 @@
 {
-  "usingComponents": {
-    "navigation-bar": "/components/navigation-bar/navigation-bar"
-  }
+  "navigationBarTitleText": "查看启动日志",
+  "usingComponents": {}
 }

+ 5 - 32
pages/logs/logs.wxml

@@ -1,33 +1,6 @@
-<view class="center">
-  <!-- 处理点击事件和hover类 -->
-  <view class="logo" bindtap="goLogin" hover-class="{{!login ? 'logo-hover' : ''}}">
-    <image class="logo-img" src="/assets/taddar/me.png"></image>
-    <view class="logo-title">
-      <text class="uer-name">Hi,{{ login ? uerInfo.name : '未登录' }}</text>
-      <!-- 未登录时显示登录图标 -->
-      <image class="go-login" wx:if="{{!login}}" src="/assets/taddar/right (1).png"></image>
-    </view>
-  </view>
-
-  <view class="center-list">
-    <view class="center-list-item">
-      <!-- 使用图片替代 iconfont 字体图标 -->
-      <image class="list-icon" src="/assets/taddar/lock.png"></image>
-      <text class="list-text">帐号管理</text>
-      <image class="navigat-arrow" src="/assets/taddar/right.png"></image>
-    </view>
-    <view class="center-list-item">
-      <image class="list-icon" src="/assets/taddar/edit.png"></image>
-      <text class="list-text">编辑个人信息</text>
-      <image class="navigat-arrow" src="/assets/taddar/right.png"></image>
-    </view>
-  </view>
-
-  <view class="center-list">
-    <view class="center-list-item border-bottom">
-      <image class="list-icon" src="/assets/taddar/logout.png"></image>
-      <button class="list-text" bind:tap="tuichu">退出登录</button> 
-      <image class="navigat-arrow" src="/assets/taddar/right.png"></image>
-    </view>
-  </view>
+<!--logs.wxml-->
+<view class="container log-list">
+  <block wx:for="{{logs}}" wx:for-item="log">
+    <text class="log-item">{{index + 1}}. {{log}}</text>
+  </block>
 </view>

+ 4 - 116
pages/logs/logs.wxss

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

Some files were not shown because too many files changed in this diff