Эх сурвалжийг харах

设计了简单的微信小程序界面,构建了数据库和部分接口

yangtaodemon 6 сар өмнө
parent
commit
f5339ca473
52 өөрчлөгдсөн 2191 нэмэгдсэн , 0 устгасан
  1. 31 0
      .eslintrc.js
  2. 14 0
      .gitignore
  3. 3 0
      .idea/.gitignore
  4. 8 0
      .idea/AcidificationModel.iml
  5. 12 0
      .idea/inspectionProfiles/Project_Default.xml
  6. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  7. 10 0
      .idea/misc.xml
  8. 8 0
      .idea/modules.xml
  9. 6 0
      .idea/vcs.xml
  10. BIN
      SoilAcidification.db
  11. 286 0
      api_db.py
  12. 19 0
      app.js
  13. 44 0
      app.json
  14. 10 0
      app.wxss
  15. BIN
      assets/taddar/Calculate_Data-active.png
  16. BIN
      assets/taddar/Calculate_Data.png
  17. BIN
      assets/taddar/Data_Visualization-active.png
  18. BIN
      assets/taddar/Data_Visualization.png
  19. BIN
      assets/taddar/copy.png
  20. BIN
      assets/taddar/users-copy.png
  21. 105 0
      components/navigation-bar/navigation-bar.js
  22. 5 0
      components/navigation-bar/navigation-bar.json
  23. 64 0
      components/navigation-bar/navigation-bar.wxml
  24. 96 0
      components/navigation-bar/navigation-bar.wxss
  25. 12 0
      pages/Calculate/Calculate.js
  26. 5 0
      pages/Calculate/Calculate.json
  27. 46 0
      pages/Calculate/Calculate.wxml
  28. 65 0
      pages/Calculate/Calculate.wxss
  29. 128 0
      pages/Calculation/Calculation.js
  30. 5 0
      pages/Calculation/Calculation.json
  31. 170 0
      pages/Calculation/Calculation.wxml
  32. 65 0
      pages/Calculation/Calculation.wxss
  33. 66 0
      pages/Staff/Staff.js
  34. 5 0
      pages/Staff/Staff.json
  35. 2 0
      pages/Staff/Staff.wxml
  36. 1 0
      pages/Staff/Staff.wxss
  37. 328 0
      pages/Visualization/Visualization.js
  38. 5 0
      pages/Visualization/Visualization.json
  39. 98 0
      pages/Visualization/Visualization.wxml
  40. 203 0
      pages/Visualization/Visualization.wxss
  41. 59 0
      pages/index/index.js
  42. 5 0
      pages/index/index.json
  43. 6 0
      pages/index/index.wxml
  44. 79 0
      pages/index/index.wxss
  45. 20 0
      pages/logs/logs.js
  46. 5 0
      pages/logs/logs.json
  47. 7 0
      pages/logs/logs.wxml
  48. 16 0
      pages/logs/logs.wxss
  49. 28 0
      project.config.json
  50. 9 0
      project.private.config.json
  51. 7 0
      sitemap.json
  52. 19 0
      utils/util.js

+ 31 - 0
.eslintrc.js

@@ -0,0 +1,31 @@
+/*
+ * Eslint config file
+ * Documentation: https://eslint.org/docs/user-guide/configuring/
+ * Install the Eslint extension before using this feature.
+ */
+module.exports = {
+  env: {
+    es6: true,
+    browser: true,
+    node: true,
+  },
+  ecmaFeatures: {
+    modules: true,
+  },
+  parserOptions: {
+    ecmaVersion: 2018,
+    sourceType: 'module',
+  },
+  globals: {
+    wx: true,
+    App: true,
+    Page: true,
+    getCurrentPages: true,
+    getApp: true,
+    Component: true,
+    requirePlugin: true,
+    requireMiniProgram: true,
+  },
+  // extends: 'eslint:recommended',
+  rules: {},
+}

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+# Windows
+[Dd]esktop.ini
+Thumbs.db
+$RECYCLE.BIN/
+
+# macOS
+.DS_Store
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+
+# Node.js
+node_modules/

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml

+ 8 - 0
.idea/AcidificationModel.iml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="jdk" jdkName="pythonProject1" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 12 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -0,0 +1,12 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
+      <option name="ignoredErrors">
+        <list>
+          <option value="N806" />
+        </list>
+      </option>
+    </inspection_tool>
+  </profile>
+</component>

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 10 - 0
.idea/misc.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="pythonProject1" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="pythonProject1" project-jdk-type="Python SDK" />
+  <component name="PyCharmProfessionalAdvertiser">
+    <option name="shown" value="true" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/AcidificationModel.iml" filepath="$PROJECT_DIR$/.idea/AcidificationModel.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

BIN
SoilAcidification.db


+ 286 - 0
api_db.py

@@ -0,0 +1,286 @@
+import os
+import sqlite3
+
+from flask import Flask, jsonify, request
+from flask import g
+from flask_cors import CORS
+
+# 定义应用的状态码文档
+"""
+状态码 200 成功
+状态码 400 失败
+"""
+
+app = Flask(__name__)
+
+# 设置数据库文件和上传文件夹的路径
+DATABASE = 'SoilAcidification.db'
+UPLOAD_FOLDER = 'uploads'
+app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
+
+# 确保上传文件夹存在,如果不存在则创建
+os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
+
+# 跨源资源共享(CORS)配置,允许跨域请求
+CORS(app)
+
+
+# 创建一个数据库连接池
+def get_db():
+    db = getattr(g, '_database', None)
+    if db is None:
+        db = g._database = sqlite3.connect(DATABASE)
+    return db
+
+
+# 请求后钩子,为响应添加内容类型头
+@app.after_request
+def add_headers(response):
+    response.headers['Content-Type'] = 'application/json; charset=utf-8'
+    return response
+
+
+@app.route('/')
+def index():
+    return 'Hello, World!'
+
+
+# 定义添加数据库记录的 API 接口
+@app.route('/add_item', methods=['POST'])
+def add_item():
+    """
+    接收 JSON 格式的请求体,包含表名和要插入的数据。
+    尝试将数据插入到指定的表中。
+    :return:
+    """
+    db = get_db()
+    try:
+        # 确保请求体是JSON格式
+        data = request.get_json()
+        if not data:
+            raise ValueError("No JSON data provided")
+
+        table_name = data.get('table')
+        item_data = data.get('item')
+
+        if not table_name or not item_data:
+            return jsonify({'error': 'Missing table name or item data'}), 400
+        cur = db.cursor()
+
+        # 动态构建 SQL 语句
+        columns = ', '.join(item_data.keys())
+        placeholders = ', '.join(['?'] * len(item_data))
+        sql = f"INSERT INTO {table_name} ({columns}) VALUES ({placeholders})"
+        cur.execute(sql, tuple(item_data.values()))
+        db.commit()
+
+        # 返回更详细的成功响应
+        return jsonify({'success': True, 'message': 'Item added successfully'}), 201
+
+    except ValueError as e:
+        return jsonify({'error': str(e)}), 400
+    except KeyError as e:
+        return jsonify({'error': f'Missing data field: {e}'}), 400
+    except sqlite3.IntegrityError as e:
+        # 处理例如唯一性约束违反等数据库完整性错误
+        return jsonify({'error': 'Database integrity error', 'details': str(e)}), 409
+    except sqlite3.Error as e:
+        # 处理其他数据库错误
+        return jsonify({'error': 'Database error', 'details': str(e)}), 500
+    finally:
+        db.close()
+
+
+# 定义删除数据库记录的 API 接口
+@app.route('/delete_item', methods=['DELETE'])
+def delete_item():
+    data = request.get_json()
+    table_name = data.get('table')
+    condition = data.get('condition')
+
+    # 检查表名和条件是否提供
+    if not table_name or not condition:
+        return jsonify({
+            "success": False,
+            "message": "缺少表名或条件参数"
+        }), 400
+
+    # 尝试从条件字符串中分离键和值
+    try:
+        key, value = condition.split('=')
+    except ValueError:
+        return jsonify({
+            "success": False,
+            "message": "条件格式错误,应为 'key=value'"
+        }), 400
+
+    db = get_db()
+    cur = db.cursor()
+
+    try:
+        # 执行删除操作
+        cur.execute(f"DELETE FROM {table_name} WHERE {key} = ?", (value,))
+        db.commit()
+        # 如果没有错误发生,返回成功响应
+        return jsonify({
+            "success": True,
+            "message": "记录删除成功"
+        }), 200
+    except sqlite3.Error as e:
+        # 发生错误,回滚事务
+        db.rollback()
+        # 返回失败响应,并包含错误信息
+        return jsonify({
+            "success": False,
+            "message": f"删除失败: {e}"
+        }), 400
+
+
+# 定义修改数据库记录的 API 接口
+@app.route('/update_item', methods=['PUT'])
+def update_record():
+    data = request.get_json()
+
+    # 检查必要的数据是否提供
+    if not data or 'table' not in data or 'item' not in data:
+        return jsonify({
+            "success": False,
+            "message": "请求数据不完整"
+        }), 400
+
+    table_name = data['table']
+    item = data['item']
+
+    # 假设 item 的第一个元素是 ID
+    if not item or next(iter(item.keys())) is None:
+        return jsonify({
+            "success": False,
+            "message": "记录数据为空"
+        }), 400
+
+    # 获取 ID 和其他字段值
+    id_key = next(iter(item.keys()))
+    record_id = item[id_key]
+    updates = {key: value for key, value in item.items() if key != id_key}  # 排除 ID
+
+    db = get_db()
+    cur = db.cursor()
+
+    try:
+        record_id = int(record_id)  # 确保 ID 是整数
+    except ValueError:
+        return jsonify({
+            "success": False,
+            "message": "ID 必须是整数"
+        }), 400
+
+    # 准备参数列表,包括更新的值和 ID
+    parameters = list(updates.values()) + [record_id]
+
+    # 执行更新操作
+    set_clause = ','.join([f"{k} = ?" for k in updates.keys()])
+    sql = f"UPDATE {table_name} SET {set_clause} WHERE {id_key} = ?"
+    try:
+        cur.execute(sql, parameters)
+        db.commit()
+        if cur.rowcount == 0:
+            return jsonify({
+                "success": False,
+                "message": "未找到要更新的记录"
+            }), 404
+        return jsonify({
+            "success": True,
+            "message": "数据更新成功"
+        }), 200
+    except sqlite3.Error as e:
+        db.rollback()
+        return jsonify({
+            "success": False,
+            "message": f"更新失败: {e}"
+        }), 400
+
+
+# 定义查询数据库记录的 API 接口
+@app.route('/search/record', methods=['GET'])
+def sql_search():
+    """
+    接收 JSON 格式的请求体,包含表名和要查询的 ID。
+    尝试查询指定 ID 的记录并返回结果。
+    :return:
+    """
+    try:
+        data = request.get_json()
+
+        # 表名
+        sql_table = data['table']
+
+        # 要搜索的 ID
+        Id = data['id']
+
+        # 连接到数据库
+        db = get_db()
+        cur = db.cursor()
+
+        # 构造查询语句
+        sql = f"SELECT * FROM {sql_table} WHERE id = ?"
+
+        # 执行查询
+        cur.execute(sql, (Id,))
+
+        # 获取查询结果
+        rows = cur.fetchall()
+        column_names = [desc[0] for desc in cur.description]
+
+        # 检查是否有结果
+        if not rows:
+            return jsonify({'error': '未查找到对应数据。'}), 400
+
+        # 构造响应数据
+        results = []
+        for row in rows:
+            result = {column_names[i]: row[i] for i in range(len(row))}
+            results.append(result)
+
+        # 关闭游标和数据库连接
+        cur.close()
+        db.close()
+
+        # 返回 JSON 响应
+        return jsonify(results), 200
+
+    except sqlite3.Error as e:
+        # 如果发生数据库错误,返回错误信息
+        return jsonify({'error': str(e)}), 400
+    except KeyError as e:
+        # 如果请求数据中缺少必要的键,返回错误信息
+        return jsonify({'error': f'缺少必要的数据字段: {e}'}), 400
+
+
+# 定义提供数据库列表,用于展示表格的 API 接口
+@app.route('/tables', methods=['POST'])
+def get_table():
+    data = request.get_json()
+    table_name = data.get('table')
+    if not table_name:
+        return jsonify({'error': '需要表名'}), 400
+
+    db = get_db()
+    try:
+        cur = db.cursor()
+        cur.execute(f"SELECT * FROM {table_name}")
+        rows = cur.fetchall()
+
+        if not rows:
+            return jsonify({'error': '表为空或不存在'}), 400
+
+        headers = [description[0] for description in cur.description]
+
+        return jsonify(rows=rows, headers=headers), 200
+    except sqlite3.Error as e:
+        return jsonify({'error': str(e)}), 400
+    finally:
+        db.close()
+
+
+if __name__ == '__main__':
+    app.run(debug=True)

+ 19 - 0
app.js

@@ -0,0 +1,19 @@
+// app.js
+App({
+  onLaunch() {
+    // 展示本地存储能力
+    const logs = wx.getStorageSync('logs') || []
+    logs.unshift(Date.now())
+    wx.setStorageSync('logs', logs)
+
+    // 登录
+    wx.login({
+      success: res => {
+        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+      }
+    })
+  },
+  globalData: {
+    userInfo: null
+  }
+})

+ 44 - 0
app.json

@@ -0,0 +1,44 @@
+{
+  "pages": [
+    "pages/index/index",
+    "pages/logs/logs",
+    "pages/Visualization/Visualization",
+    "pages/Calculate/Calculate",
+    "pages/Staff/Staff",
+    "pages/Calculation/Calculation"
+  ],
+  "window": {
+    "navigationBarTitleText": "登入页面",
+    "navigationBarBackgroundColor": "#f3514f",
+    "enablePullDownRefresh": true,
+    "backgroundColor": "#efefef",
+    "backgroundTextStyle": "dark"
+  },
+  "tabBar": {
+    "selectedColor": "#F3514F",
+    "color": "#666666",
+    "backgroundColor": "#ffffff",
+    "list": [
+      {
+        "text": "计算数据",
+        "pagePath": "pages/Calculate/Calculate",
+        "iconPath": "/assets/taddar/Calculate_Data-active.png",
+        "selectedIconPath": "/assets/taddar/Calculate_Data.png"
+      },
+      {
+        "text": "数据展示",
+        "pagePath": "pages/Visualization/Visualization",
+        "iconPath": "/assets/taddar/Data_Visualization-active.png",
+        "selectedIconPath": "/assets/taddar/Data_Visualization.png"
+      },
+      {
+        "text": "人员中心",
+        "pagePath": "pages/Staff/Staff",
+        "iconPath": "/assets/taddar/users-copy.png",
+        "selectedIconPath": "/assets/taddar/copy.png"
+      }
+    ]
+  },
+  "style": "v2",
+  "sitemapLocation": "sitemap.json"
+}

+ 10 - 0
app.wxss

@@ -0,0 +1,10 @@
+/**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/Calculate_Data-active.png


BIN
assets/taddar/Calculate_Data.png


BIN
assets/taddar/Data_Visualization-active.png


BIN
assets/taddar/Data_Visualization.png


BIN
assets/taddar/copy.png


BIN
assets/taddar/users-copy.png


+ 105 - 0
components/navigation-bar/navigation-bar.js

@@ -0,0 +1,105 @@
+Component({
+  options: {
+    multipleSlots: true // 在组件定义时的选项中启用多slot支持
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    extClass: {
+      type: String,
+      value: ''
+    },
+    title: {
+      type: String,
+      value: ''
+    },
+    background: {
+      type: String,
+      value: ''
+    },
+    color: {
+      type: String,
+      value: ''
+    },
+    back: {
+      type: Boolean,
+      value: true
+    },
+    loading: {
+      type: Boolean,
+      value: false
+    },
+    homeButton: {
+      type: Boolean,
+      value: false,
+    },
+    animated: {
+      // 显示隐藏的时候opacity动画效果
+      type: Boolean,
+      value: true
+    },
+    show: {
+      // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在
+      type: Boolean,
+      value: true,
+      observer: '_showChange'
+    },
+    // back为true的时候,返回的页面深度
+    delta: {
+      type: Number,
+      value: 1
+    },
+  },
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    displayStyle: ''
+  },
+  lifetimes: {
+    attached() {
+      const rect = wx.getMenuButtonBoundingClientRect()
+      wx.getSystemInfo({
+        success: (res) => {
+          const isAndroid = res.platform === 'android'
+          const isDevtools = res.platform === 'devtools'
+          this.setData({
+            ios: !isAndroid,
+            innerPaddingRight: `padding-right: ${res.windowWidth - rect.left}px`,
+            leftWidth: `width: ${res.windowWidth - rect.left }px`,
+            safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${res.safeArea.top}px); padding-top: ${res.safeArea.top}px` : ``
+          })
+        }
+      })
+    },
+  },
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    _showChange(show) {
+      const animated = this.data.animated
+      let displayStyle = ''
+      if (animated) {
+        displayStyle = `opacity: ${
+          show ? '1' : '0'
+        };transition:opacity 0.5s;`
+      } else {
+        displayStyle = `display: ${show ? '' : 'none'}`
+      }
+      this.setData({
+        displayStyle
+      })
+    },
+    back() {
+      const data = this.data
+      if (data.delta) {
+        wx.navigateBack({
+          delta: data.delta
+        })
+      }
+      this.triggerEvent('back', { delta: data.delta }, {})
+    }
+  },
+})

+ 5 - 0
components/navigation-bar/navigation-bar.json

@@ -0,0 +1,5 @@
+{
+  "component": true,
+  "styleIsolation": "apply-shared",
+  "usingComponents": {}
+}

+ 64 - 0
components/navigation-bar/navigation-bar.wxml

@@ -0,0 +1,64 @@
+<view class="weui-navigation-bar {{extClass}}">
+  <view class="weui-navigation-bar__inner {{ios ? 'ios' : 'android'}}" style="color: {{color}}; background: {{background}}; {{displayStyle}}; {{innerPaddingRight}}; {{safeAreaTop}};">
+
+    <!-- 左侧按钮 -->
+    <view class='weui-navigation-bar__left' style="{{leftWidth}};">
+      <block wx:if="{{back || homeButton}}">
+        <!-- 返回上一页 -->
+        <block wx:if="{{back}}">
+          <view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_goback">
+            <view
+              bindtap="back"
+              class="weui-navigation-bar__btn_goback_wrapper"
+              hover-class="weui-active"
+              hover-stay-time="100"
+              aria-role="button"
+              aria-label="返回"
+            >
+              <view class="weui-navigation-bar__button weui-navigation-bar__btn_goback"></view>
+            </view>
+          </view>
+        </block>
+        <!-- 返回首页 -->
+        <block wx:if="{{homeButton}}">
+          <view class="weui-navigation-bar__buttons weui-navigation-bar__buttons_home">
+            <view
+              bindtap="home"
+              class="weui-navigation-bar__btn_home_wrapper"
+              hover-class="weui-active"
+              aria-role="button"
+              aria-label="首页"
+            >
+              <view class="weui-navigation-bar__button weui-navigation-bar__btn_home"></view>
+            </view>
+          </view>
+        </block>
+      </block>
+      <block wx:else>
+        <slot name="left"></slot>
+      </block>
+    </view>
+
+    <!-- 标题 -->
+    <view class='weui-navigation-bar__center'>
+      <view wx:if="{{loading}}" class="weui-navigation-bar__loading" aria-role="alert">
+        <view
+          class="weui-loading"
+          aria-role="img"
+          aria-label="加载中"
+        ></view>
+      </view>
+      <block wx:if="{{title}}">
+        <text>{{title}}</text>
+      </block>
+      <block wx:else>
+        <slot name="center"></slot>
+      </block>
+    </view>
+    
+    <!-- 右侧留空 -->
+    <view class='weui-navigation-bar__right'>
+      <slot name="right"></slot>
+    </view>
+  </view>
+</view>

+ 96 - 0
components/navigation-bar/navigation-bar.wxss

@@ -0,0 +1,96 @@
+.weui-navigation-bar {
+  --weui-FG-0:rgba(0,0,0,.9);
+  --height: 44px;
+  --left: 16px;
+}
+.weui-navigation-bar .android {
+  --height: 48px;
+}
+
+.weui-navigation-bar {
+  overflow: hidden;
+  color: var(--weui-FG-0);
+  flex: none;
+}
+
+.weui-navigation-bar__inner {
+  position: relative;
+  top: 0;
+  left: 0;
+  height: calc(var(--height) + env(safe-area-inset-top));
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  padding-top: env(safe-area-inset-top);
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.weui-navigation-bar__left {
+  position: relative;
+  padding-left: var(--left);
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+  height: 100%;
+  box-sizing: border-box;
+}
+
+.weui-navigation-bar__btn_goback_wrapper {
+  padding: 11px 18px 11px 16px;
+  margin: -11px -18px -11px -16px;
+}
+
+.weui-navigation-bar__btn_goback_wrapper.weui-active {
+  opacity: 0.5;
+}
+
+.weui-navigation-bar__btn_goback {
+  font-size: 12px;
+  width: 12px;
+  height: 24px;
+  -webkit-mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E  %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
+  mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='24' viewBox='0 0 12 24'%3E  %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M10 19.438L8.955 20.5l-7.666-7.79a1.02 1.02 0 0 1 0-1.42L8.955 3.5 10 4.563 2.682 12 10 19.438z'/%3E%3C/svg%3E") no-repeat 50% 50%;
+  -webkit-mask-size: cover;
+  mask-size: cover;
+  background-color: var(--weui-FG-0);
+}
+
+.weui-navigation-bar__center {
+  font-size: 17px;
+  text-align: center;
+  position: relative;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  font-weight: bold;
+  flex: 1;
+  height: 100%;
+}
+
+.weui-navigation-bar__loading {
+  margin-right: 4px;
+  align-items: center;
+}
+
+.weui-loading {
+  font-size: 16px;
+  width: 16px;
+  height: 16px;
+  display: block;
+  background: transparent url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='80px' height='80px' viewBox='0 0 80 80' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Eloading%3C/title%3E%3Cdefs%3E%3ClinearGradient x1='94.0869141%25' y1='0%25' x2='94.0869141%25' y2='90.559082%25' id='linearGradient-1'%3E%3Cstop stop-color='%23606060' stop-opacity='0' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3ClinearGradient x1='100%25' y1='8.67370605%25' x2='100%25' y2='90.6286621%25' id='linearGradient-2'%3E%3Cstop stop-color='%23606060' offset='0%25'%3E%3C/stop%3E%3Cstop stop-color='%23606060' stop-opacity='0.3' offset='100%25'%3E%3C/stop%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='none' stroke-width='1' fill='none' fill-rule='evenodd' opacity='0.9'%3E%3Cg%3E%3Cpath d='M40,0 C62.09139,0 80,17.90861 80,40 C80,62.09139 62.09139,80 40,80 L40,73 C58.2253967,73 73,58.2253967 73,40 C73,21.7746033 58.2253967,7 40,7 L40,0 Z' fill='url(%23linearGradient-1)'%3E%3C/path%3E%3Cpath d='M40,0 L40,7 C21.7746033,7 7,21.7746033 7,40 C7,58.2253967 21.7746033,73 40,73 L40,80 C17.90861,80 0,62.09139 0,40 C0,17.90861 17.90861,0 40,0 Z' fill='url(%23linearGradient-2)'%3E%3C/path%3E%3Ccircle id='Oval' fill='%23606060' cx='40.5' cy='3.5' r='3.5'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A") no-repeat;
+  background-size: 100%;
+  margin-left: 0;
+  animation: loading linear infinite 1s;
+}
+
+@keyframes loading {
+  from {
+    transform: rotate(0);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 12 - 0
pages/Calculate/Calculate.js

@@ -0,0 +1,12 @@
+Page({
+  // 页面相关数据
+  data: {},
+
+  // 登录逻辑(跳转页面)
+  login: function () {
+    // 使用 wx.navigateTo 跳转到 "pages/Calculation/Calculation"
+    wx.navigateTo({
+      url: '/pages/Calculation/Calculation'  // 目标页面路径
+    });
+  }
+});

+ 5 - 0
pages/Calculate/Calculate.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "模型",
+  "navigationBarBackgroundColor": "#00AF92"
+}

+ 46 - 0
pages/Calculate/Calculate.wxml

@@ -0,0 +1,46 @@
+<view class="green-box">
+  <text class="title">NAU timeCalculator</text>
+  <text class="subtitle">石灰用量计算横型</text>
+</view>
+
+<view class="description-box">
+  <text class="bold-text">
+    本软件用于我国酸性土壤改良,计算达到目标 pH,单位面积需要石灰质材料的施用量
+  </text>
+</view>
+
+<view class="description-box">
+  <text class="bold-text">
+    注意事项如下:
+  </text>
+  <text class="regular-text">
+    1. 本模型是基于我国南方 23 份酸性土壤样品开发的;
+  </text>
+  <text class="regular-text">
+    2. 土壤起始 pH 和目标 pH 是指在土水比为 1:2.5 下的 pH 值;
+  </text>
+  <text class="regular-text">
+    3. 施用最佳时期为翻耕前,将农用石灰质物料均匀撒加到土壤表面,然后与耕作层土壤(0-20 cm)混匀;
+  </text>
+  <text class="regular-text">
+    4. 本软件同时可以计算不同类型碱性物料的施用量,包括石灰石粉、生石灰、熟石灰、白云石、以及已知 CaO 和 MgO 含量的碱性材料。
+  </text>
+</view>
+<view class="description-box">
+  <text class="bold-text">
+    从重金属污染土壤安全利用的目的,还需注意:
+  </text>
+  <text class="regular-text">
+    1. 土壤目标 pH 为 6.5 时能有效降低土壤镉的活性和稻米镉的积累;
+  </text>
+  <text class="regular-text">
+    2. 农用石灰质物料重金属含量不能超过农用物料重金属含量标准;
+  </text>
+  <text class="regular-text">
+    3. 一次施用后,稻米降镉效果可持续多年,每隔 3-5 年通过监测土壤 pH 变化,根据土壤 pH 的变化,可适当追加少量石灰质物料。
+  </text>
+</view>
+
+<view class="green-btn">
+  <button class="btn" bindtap="login">进入计算</button>
+</view>

+ 65 - 0
pages/Calculate/Calculate.wxss

@@ -0,0 +1,65 @@
+.green-box {
+  width: 95%; /* 绿色框的宽度 */
+  margin: 10rpx auto 0; /* 减少顶部外边距 */
+  padding: 10rpx; /* 内边距 */
+  background-color: #1aad19; /* 背景绿色 */
+  border-radius: 10rpx; /* 圆角 */
+  text-align: center; /* 内容水平居中 */
+  color: #fff; /* 字体白色 */
+}
+
+.title {
+  display: block; /* 强制换行 */
+  font-size: 40rpx; /* 标题字体大小 */
+  font-weight: bold; /* 加粗 */
+}
+
+.subtitle {
+  display: block; /* 强制换行 */
+  font-size: 36rpx; /* 副标题字体大小 */
+  font-weight: bold; /* 加粗 */
+  margin-top: 5rpx; /* 减少上下间距 */
+}
+
+.description-box {
+  width: 90%; /* 白色框的宽度 */
+  margin: 10rpx auto; /* 减少上下间距 */
+  padding: 15rpx; /* 增加文字周围空间 */
+  background-color: #fff; /* 背景为白色 */
+}
+
+.bold-text {
+  font-size: 32rpx; /* 加粗字体大小 */
+  font-weight: bold; /* 加粗 */
+  color: #000; /* 黑色字体 */
+}
+
+.regular-text {
+  font-size: 28rpx; /* 正文字体大小 */
+  color: #333; /* 深灰色字体 */
+}
+
+.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;  /* 鼠标悬浮时显示手型 */
+}
+

+ 128 - 0
pages/Calculation/Calculation.js

@@ -0,0 +1,128 @@
+// pages/Calculation/Calculation.js
+Page({
+  data: {
+    OM: '', // 有机质含量
+    CL: '', // 土壤粘粒重量
+    CEC: '', // 阳离子交换量
+    H: '', // 氢离子含量
+    HN: '', // 铵离子含量
+    Al: '', // 铝离子含量
+    free_alumina: '', // 游离氧化铝含量
+    free_iron_oxides: '', // 游离氧化铁含量
+    collection_location: '', // 采样地点
+    collection_date: '', // 采样时间
+    initialPH: '', // 土壤初始 pH
+    targetPH: '', // 土壤目标 pH
+    materials: ['石灰石(粉)CaCO3', '生石灰(粉)', '纯CaO (粉)', '熟石灰(粉)', '白云石 (粉)', '硅钙钾镁肥', '其他含CaO和MgO材料'],
+    selectedMaterialIndex: 0, // 默认选中的碱性物料索引
+    selectedMaterial: '石灰石(粉)CaCO3', // 默认物料
+  },
+
+  // 更新有机质含量
+  onOMChange: function(e) {
+    this.setData({
+      OM: e.detail.value
+    });
+  },
+  
+  // 更新土壤粘粒重量
+  onCLChange: function(e) {
+    this.setData({
+      CL: e.detail.value
+    });
+  },
+  
+  // 更新阳离子交换量
+  onCECChange: function(e) {
+    this.setData({
+      CEC: e.detail.value
+    });
+  },
+  
+  // 更新氢离子含量
+  onHChange: function(e) {
+    this.setData({
+      H: e.detail.value
+    });
+  },
+  
+  // 更新铵离子含量
+  onHNChange: function(e) {
+    this.setData({
+      HN: e.detail.value
+    });
+  },
+  
+  // 更新铝离子含量
+  onAlChange: function(e) {
+    this.setData({
+      Al: e.detail.value
+    });
+  },
+  
+  // 更新游离氧化铝含量
+  onFreeAluminaChange: function(e) {
+    this.setData({
+      free_alumina: e.detail.value
+    });
+  },
+  
+  // 更新游离氧化铁含量
+  onFreeIronOxidesChange: function(e) {
+    this.setData({
+      free_iron_oxides: e.detail.value
+    });
+  },
+  
+  // 更新采样地点
+  onCollectionLocationChange: function(e) {
+    this.setData({
+      collection_location: e.detail.value
+    });
+  },
+  
+  // 更新采样时间
+  onBindDateChange: function(e) {
+    this.setData({
+      collection_date: e.detail.value
+    });
+  },
+
+  // 处理土壤初始 pH 的输入
+  onInitialPHChange(e) {
+    this.setData({
+      initialPH: e.detail.value,
+    });
+  },
+
+  // 处理土壤目标 pH 的输入
+  onTargetPHChange(e) {
+    this.setData({
+      targetPH: e.detail.value,
+    });
+  },
+
+  // 处理碱性物料选择
+  onSelectMaterial(e) {
+    const index = e.detail.value;
+    this.setData({
+      selectedMaterialIndex: index,
+      selectedMaterial: this.data.materials[index],
+    });
+  },
+
+  // 计算方法
+  calculate: function() {
+    console.log('开始计算...');
+    console.log('有机质含量:', this.data.OM);
+    console.log('土壤粘粒重量:', this.data.CL);
+    console.log('阳离子交换量:', this.data.CEC);
+    console.log('氢离子含量', this.data.H);
+    console.log('铵离子含量:', this.data.HN);
+    console.log('铝离子含量:', this.data.Al);
+    console.log('游离氧化铝含量:', this.data.free_alumina);
+    console.log('游离氧化铁含量:', this.data.free_iron_oxides);
+    console.log('采样地点:', this.data.collection_location);
+    console.log('采样时间:', this.data.collection_date);
+  }
+});

+ 5 - 0
pages/Calculation/Calculation.json

@@ -0,0 +1,5 @@
+{
+  "navigationBarTitleText": "模型", 
+  "navigationBarBackgroundColor": "#00AF92",  
+  "navigationBarTextStyle": "white"  
+}

+ 170 - 0
pages/Calculation/Calculation.wxml

@@ -0,0 +1,170 @@
+<!-- pages/Calculation/Calculation.wxml -->
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">有机质含量 (OM g/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入有机质含量" 
+      value="{{OM}}" 
+      bindinput="onOMChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">土壤粘粒重量 (CL g/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入土壤粘粒重量" 
+      value="{{CL}}" 
+      bindinput="onCLChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">阳离子交换量 (CEC cmol/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入阳离子交换量" 
+      value="{{CEC}}" 
+      bindinput="onCECChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">氢离子含量 (H cmol/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入氢离子含量" 
+      value="{{H}}" 
+      bindinput="onHChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">铵离子含量 (HN mg/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入铵离子含量" 
+      value="{{HN}}" 
+      bindinput="onHNChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">铝离子含量 (Al cmol/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入铝离子含量" 
+      value="{{Al}}" 
+      bindinput="onAlChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">游离氧化铝 (free alumina g/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入游离氧化铝含量" 
+      value="{{free_alumina}}" 
+      bindinput="onFreeAluminaChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">游离氧化铁 (free iron oxides g/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入游离氧化铁含量" 
+      value="{{free_iron_oxides}}" 
+      bindinput="onFreeIronOxidesChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">采样地点:</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入采样地点" 
+      value="{{collection_location}}" 
+      bindinput="onCollectionLocationChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <picker mode="date" header-text="选择时间" value="{{collection_date}}" bindchange="onBindDateChange">
+      <view class="picker-container">
+        <text class="page-section-title">采样时间:</text>
+        <text class="color6">{{collection_date || '请选择日期'}}</text>
+      </view>
+    </picker>
+  </view>
+</view>
+
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">土壤初始 pH:</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="4.5~6.2" 
+      value="{{initialPH}}" 
+      bindinput="onInitialPHChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">土壤目标 pH:</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="4.5~7.0" 
+      value="{{targetPH}}" 
+      bindinput="onTargetPHChange" 
+    />
+  </view>
+</view>
+
+<view class="page-body">
+  <view class="white-box">
+    <view class="page-section-title">碱性物料:</view>
+    <picker mode="selector" range="{{materials}}" value="{{selectedMaterialIndex}}" bindchange="onSelectMaterial">
+      <view class="input-field bold-text">
+        {{selectedMaterial || '石灰石(粉)CaCO3'}}
+      </view>
+    </picker>
+  </view>
+</view>
+
+<view class="green-btn">
+  <button class="btn" bindtap="calculate">计算</button>
+</view>

+ 65 - 0
pages/Calculation/Calculation.wxss

@@ -0,0 +1,65 @@
+/* pages/Calculation/Calculation.wxss */
+.page-body {
+  padding: 20rpx;
+  background-color: #f8f8f8; /* 浅灰色背景 */
+}
+
+.white-box {
+  width: 90%;
+  margin: 10rpx auto;
+  padding: 20rpx;
+  background-color: #fff;
+  border-radius: 10rpx;
+  box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.1);
+}
+
+.page-section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.radio {
+  font-size: 28rpx;
+  color: #666;
+  margin: 5rpx 0;
+}
+
+.input-field {
+  width: 100%;
+  padding: 10rpx;
+  font-size: 28rpx;
+  color: #333;
+  border: none;
+  border-bottom: 1px solid #ddd;
+}
+
+.bold-text {
+  font-weight: bold;
+}
+
+.green-btn {
+  margin-top: 20rpx;
+  text-align: center;
+}
+
+.btn {
+  width: 80%;
+  padding: 15rpx 0;
+  background-color: #00af92;
+  color: #fff;
+  font-size: 28rpx;
+  border-radius: 10rpx;
+}
+
+.picker-placeholder {
+  flex-grow: 1;
+  color: #888;
+  margin-right: 10px;
+}
+
+.picker {
+  flex-shrink: 0;
+  width: 100px; /* 根据需要调整宽度 */
+}

+ 66 - 0
pages/Staff/Staff.js

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

+ 5 - 0
pages/Staff/Staff.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "个人中心",
+  "navigationBarBackgroundColor": "#00AF92"
+}

+ 2 - 0
pages/Staff/Staff.wxml

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

+ 1 - 0
pages/Staff/Staff.wxss

@@ -0,0 +1 @@
+/* pages/Staff/Staff.wxss */

+ 328 - 0
pages/Visualization/Visualization.js

@@ -0,0 +1,328 @@
+Page({
+  data: {
+    shoopingtext: "", // 搜索框内容
+    filteredRows: [], // 表格过滤后的数据
+    rows: [], // 所有表格数据
+    showModal: false, // 控制底部编辑删除弹窗的显示与否
+    showAddModal: false, // 控制新增数据弹窗的显示与否
+    currentRow: null, // 当前选中的表格行
+    // 新增数据弹窗的输入框内容
+    newData: {
+      OM: "",
+      CL: "",
+      CEC: "",
+      Hplus: "",
+      HN: "",
+      Al3plus: "",
+      Alumina: "",
+      IronOxides: "",
+      DeltaPH: "",
+      Day0PH: ""
+    }
+  },
+
+  // 页面加载时获取表格数据
+  onLoad: 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 {
+              '0 day pH': row[0],
+              'OM g/kg': row[1],
+              'CL g/kg': row[2],
+              'CEC cmol/kg': row[3],
+              'H+ cmol/kg': row[4],
+              'HN mg/kg': row[5],
+              'Al3plus': row[6],
+              'Free alumina g/kg': row[7],
+              'Free iron oxides g/kg': row[8],
+              '105 day pH': row[9],
+              'Collection location': row[10],
+              'Collection Date': row[11]
+            };
+          });
+  
+          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: "",
+        Hplus: "",
+        HN: "",
+        Al3plus: "",
+        Alumina: "",
+        IronOxides: "",
+        DeltaPH: "",
+        Day0PH: ""
+      }
+    });
+    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.Hplus': e.detail.value
+    });
+  },
+  onInputHN: function(e) {
+    this.setData({
+      'newData.HN': e.detail.value
+    });
+  },
+  onInputAl3plus: function(e) {
+    this.setData({
+      'newData.Al3plus': e.detail.value
+    });
+  },
+  onInputAlumina: function(e) {
+    this.setData({
+      'newData.Alumina': e.detail.value
+    });
+  },
+  onInputIronOxides: function(e) {
+    this.setData({
+      'newData.IronOxides': e.detail.value
+    });
+  },
+  onInputDeltaPH: function(e) {
+    this.setData({
+      'newData.DeltaPH': e.detail.value
+    });
+  },
+  onInputDay0PH: function(e) {
+    this.setData({
+      'newData.Day0PH': 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: 'your_table_name',
+        item: newRow
+      },
+      success: (res) => {
+        if (res.data.success) {
+          this.setData({
+            rows: [...this.data.rows, newRow],
+            showAddModal: false
+          });
+          wx.showToast({
+            title: '新增成功',
+            icon: 'success'
+          });
+        } else {
+          wx.showToast({
+            title: '新增失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        wx.showToast({
+          title: '请求失败,请重试',
+          icon: 'none'
+        });
+        console.error('请求失败:', err);
+      }
+    });
+  },
+
+  // 取消新增数据
+  onCancelAdd: function() {
+    this.setData({
+      showAddModal: false
+    });
+  },
+
+  // 编辑按钮点击
+  onEdit: function() {
+    const updatedRow = this.data.currentRow.row;  // 获取当前编辑的行数据
+    wx.request({
+      url: 'http://localhost:5000/update_item',
+      method: 'PUT',
+      header: {
+        'Content-Type': 'application/json'
+      },
+      data: {
+        table: 'your_table_name',
+        item: updatedRow
+      },
+      success: (res) => {
+        if (res.data.success) {
+          let rows = [...this.data.rows];
+          rows[this.data.currentRow.index] = updatedRow;  // 更新数据
+          this.setData({
+            rows: rows,
+            showModal: false
+          });
+          wx.showToast({
+            title: '更新成功',
+            icon: 'success'
+          });
+        } else {
+          wx.showToast({
+            title: '更新失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        wx.showToast({
+          title: '请求失败,请重试',
+          icon: 'none'
+        });
+        console.error('请求失败:', err);
+      }
+    });
+  },
+
+  // 删除按钮点击
+  onDelete: function() {
+    const { index, row } = this.data.currentRow;
+    const condition = `id=${row.id}`;
+
+    wx.request({
+      url: 'http://localhost:5000/delete_item',
+      method: 'DELETE',
+      header: {
+        'Content-Type': 'application/json'
+      },
+      data: {
+        table: 'your_table_name',
+        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'
+          });
+        } else {
+          wx.showToast({
+            title: '删除失败',
+            icon: 'none'
+          });
+        }
+      },
+      fail: (err) => {
+        wx.showToast({
+          title: '请求失败,请重试',
+          icon: 'none'
+        });
+        console.error('请求失败:', err);
+      }
+    });
+  },
+
+  // 取消编辑删除弹窗
+  onCancel: function() {
+    this.setData({
+      showModal: 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  // 显示编辑删除弹窗
+    });
+  }
+});

+ 5 - 0
pages/Visualization/Visualization.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "数据展示",
+  "navigationBarBackgroundColor": "#00AF92"
+}

+ 98 - 0
pages/Visualization/Visualization.wxml

@@ -0,0 +1,98 @@
+<scroll-view class="table-container" scroll-x="true" scroll-with-animation="true">
+  <!-- 顶部搜索 -->
+  <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>
+  </view>
+
+  <!-- 表头 -->
+  <view class="table">
+    <view class="table-header">
+      <view class="table-cell">序号</view>
+      <view class="table-cell">0 day pH</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">105 day pH</view>
+      <view class="table-cell">采集位置</view>
+      <view class="table-cell">采集日期</view>
+    </view>
+
+    <!-- 数据加载中 -->
+    <view wx:if="{{loading}}" class="loading">数据加载中,请稍候...</view>
+
+    <!-- 无数据提示 -->
+    <view wx:if="{{!loading && filteredRows.length === 0}}" class="no-data">暂无数据</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['0 day pH']}}</view>
+          <view class="table-cell">{{item['OM g/kg']}}</view>
+          <view class="table-cell">{{item['CL g/kg']}}</view>
+          <view class="table-cell">{{item['CEC cmol/kg']}}</view>
+          <view class="table-cell">{{item['H+ cmol/kg']}}</view>
+          <view class="table-cell">{{item['HN mg/kg']}}</view>
+          <view class="table-cell">{{item['Al3plus']}}</view>
+          <view class="table-cell">{{item['Free alumina g/kg']}}</view>
+          <view class="table-cell">{{item['Free iron oxides g/kg']}}</view>
+          <view class="table-cell">{{item['105 day pH']}}</view>
+          <view class="table-cell">{{item['Collection location']}}</view>
+          <view class="table-cell">{{item['Collection Date']}}</view>
+        </view>
+      </block>
+    </scroll-view>
+  </view>
+</scroll-view>
+
+<!-- 新增数据弹窗 -->
+<view class="modal" wx:if="{{showAddModal}}">
+  <view class="modal-content">
+    <view class="page-section-title">游离氧化铁 (free iron oxides g/kg):</view>
+    <input 
+      class="input-field" 
+      type="text" 
+      placeholder="请输入游离氧化铁含量" 
+      data-field="IronOxides" 
+      bindinput="onInputChange" 
+    />
+
+    <view class="page-section-title">有机质含量 (OM g/kg):</view>
+    <input class="input-field" placeholder="请输入OM含量" data-field="OM" bindinput="onInputChange" />
+    <view class="page-section-title">土壤粘粒重量 (CL g/kg):</view>
+    <input class="input-field" placeholder="请输入CL含量" data-field="CL" bindinput="onInputChange" />
+    <view class="page-section-title">阳离子交换量 (CEC cmol/kg):</view>
+    <input class="input-field" placeholder="请输入CEC含量" data-field="CEC" bindinput="onInputChange" />
+    <view class="page-section-title">氢离子含量 (H+ cmol/kg):</view>
+    <input class="input-field" placeholder="请输入H+含量" data-field="Hplus" bindinput="onInputChange" />
+
+    <view class="button-container">
+      <button class="submit-btn" bindtap="onSubmitAdd">提交</button>
+      <button class="cancel-btn" bindtap="onCancelAdd">取消</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>

+ 203 - 0
pages/Visualization/Visualization.wxss

@@ -0,0 +1,203 @@
+/* 确保搜索框和新增按钮固定在顶部 */
+.top {
+  position: sticky; /* 使搜索框在页面滚动时固定 */
+  top: 0; /* 固定在顶部 */
+  z-index: 100; /* 确保在表格之上 */
+  background-color: #f7f7f7; /* 背景色 */
+  padding: 2% 0; /* 上下内边距 */
+}
+
+.topsearch {
+  width: 90%;
+  margin-left: 5%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between; /* 确保搜索框和按钮分开 */
+}
+
+.frame {
+  background-color: white;
+  width: 75%;
+  border-radius: 20rpx;
+  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: 10rpx; /* 增加按钮的上内边距 */
+  display: flex;
+  justify-content: center;
+  margin-top: 10rpx; /* 新增按钮的上边距 */
+}
+
+/* 新增按钮 */
+.add-btn {
+  padding: 5px 12px;
+  background-color: #007aff;
+  color: #fff;
+  border-radius: 4px;
+  font-size: 14px;
+}
+
+/* 表格容器 */
+.table-container {
+  width: 100%;
+  overflow-x: auto; /* 启用横向滚动 */
+  background-color: #fff;
+  border: 1rpx solid #ddd;
+  margin-top: 20rpx; /* 添加上边距,确保不会和顶部元素重叠 */
+  white-space: nowrap; /* 禁止换行 */
+}
+
+/* 表头样式 */
+.table-header {
+  display: flex;
+  width: max-content;
+  background-color: #FFD700;
+  font-weight: bold;
+  border-bottom: 1rpx solid #ddd;
+  z-index: 99; /* 确保表头高于表格内容 */
+}
+
+/* 表格单元格样式 */
+.table-cell {
+  flex: none;
+  width: 250rpx;
+  padding: 10rpx;
+  text-align: center;
+  font-size: 28rpx;
+  color: #333;
+  border-right: 1rpx solid #ddd;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 表格内容样式 */
+.table-body {
+  display: flex;
+  flex-direction: column;
+  width: max-content;
+}
+
+.table-row {
+  display: flex;
+}
+
+.table-row:nth-child(odd) {
+  background-color: #f9f9f9;
+}
+
+.table-row:nth-child(even) {
+  background-color: #ffffff;
+}
+
+/* 模态框背景 */
+.modal {
+  position: fixed; /* 固定定位,始终在屏幕中央 */
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: rgba(0, 0, 0, 0.6); /* 半透明背景色,增加一点深度感 */
+  z-index: 1000; /* 提高层级,确保模态框在其他元素之上 */
+}
+
+/* 模态框内容 */
+.modal-content {
+  background-color: #fff; /* 模态框内容背景为白色 */
+  border-radius: 16px; /* 圆角 */
+  width: 80%; /* 宽度占80% */
+  max-width: 500rpx; /* 最大宽度限制 */
+  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); /* 添加阴影效果,增加浮动感 */
+  padding: 20rpx; /* 内边距 */
+  transition: transform 0.3s ease-out; /* 平滑的动画过渡 */
+}
+
+/* 每个选项项 */
+.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; /* 按钮间隔 */
+}

+ 59 - 0
pages/index/index.js

@@ -0,0 +1,59 @@
+Page({
+  data: {
+    username: '', // 用户名
+    password: '', // 密码
+    errorMessage: '' // 错误信息
+  },
+
+  // 输入用户名
+  inputUsername: function (e) {
+    this.setData({
+      username: e.detail.value
+    });
+  },
+
+  // 输入密码
+  inputPassword: function (e) {
+    this.setData({
+      password: e.detail.value
+    });
+  },
+
+  // 登录逻辑
+  login: function () {
+    const { username, password } = this.data;
+
+    // 简单验证逻辑:用户名和密码为 "123" 时验证通过
+    if (!username || !password) {
+      wx.showToast({
+        title: '用户名和密码不能为空',
+        icon: 'none',
+        duration: 2000
+      });
+      return;
+    }
+
+    wx.showLoading({
+      title: '登录中...'
+    });
+
+    setTimeout(() => {
+      if (username === '123' && password === '123') {
+        wx.hideLoading();
+        wx.switchTab({ // 跳转到 tabBar 页面
+          url: '/pages/Calculate/Calculate'
+        });
+      } else {
+        wx.hideLoading();
+        this.setData({
+          errorMessage: '用户名或密码错误'
+        });
+        wx.showToast({
+          title: '用户名或密码错误',
+          icon: 'none',
+          duration: 2000
+        });
+      }
+    }, 1500);
+  }
+});

+ 5 - 0
pages/index/index.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "navigation-bar": "/components/navigation-bar/navigation-bar"
+  }
+}

+ 6 - 0
pages/index/index.wxml

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

+ 79 - 0
pages/index/index.wxss

@@ -0,0 +1,79 @@
+.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;
+  }
+}

+ 20 - 0
pages/logs/logs.js

@@ -0,0 +1,20 @@
+// logs.js
+const util = require('../../utils/util.js')
+
+Component({
+  data: {
+    logs: []
+  },
+  lifetimes: {
+    attached() {
+      this.setData({
+        logs: (wx.getStorageSync('logs') || []).map(log => {
+          return {
+            date: util.formatTime(new Date(log)),
+            timeStamp: log
+          }
+        })
+      })
+    }
+  },
+})

+ 5 - 0
pages/logs/logs.json

@@ -0,0 +1,5 @@
+{
+  "usingComponents": {
+    "navigation-bar": "/components/navigation-bar/navigation-bar"
+  }
+}

+ 7 - 0
pages/logs/logs.wxml

@@ -0,0 +1,7 @@
+<!--logs.wxml-->
+<navigation-bar title="查看启动日志" back="{{true}}" color="black" background="#FFF"></navigation-bar>
+<scroll-view class="scrollarea" scroll-y type="list">
+  <block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
+    <view class="log-item">{{index + 1}}. {{log.date}}</view>
+  </block>
+</scroll-view>

+ 16 - 0
pages/logs/logs.wxss

@@ -0,0 +1,16 @@
+page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+.scrollarea {
+  flex: 1;
+  overflow-y: hidden;
+}
+.log-item {
+  margin-top: 20rpx;
+  text-align: center;
+}
+.log-item:last-child {
+  padding-bottom: env(safe-area-inset-bottom);
+}

+ 28 - 0
project.config.json

@@ -0,0 +1,28 @@
+{
+  "appid": "wxa4088d00b5849cba",
+  "compileType": "miniprogram",
+  "libVersion": "trial",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    }
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "auto",
+    "tabSize": 2
+  }
+}

+ 9 - 0
project.private.config.json

@@ -0,0 +1,9 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "%E5%9C%9F%E5%9C%B0%E5%8C%96%E8%AE%A1%E7%AE%97",
+  "setting": {
+    "compileHotReLoad": true,
+    "skylineRenderEnable": true,
+    "urlCheck": false
+  }
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 19 - 0
utils/util.js

@@ -0,0 +1,19 @@
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+module.exports = {
+  formatTime
+}