瀏覽代碼

添加了模型训练页面和可视化页面

DIng 5 月之前
父節點
當前提交
433c8ced40

二進制
api/SoilAcidification.db


+ 20 - 9
api/app/routes.py

@@ -575,7 +575,7 @@ def sql_search():
 
 
 
 
 # 定义提供数据库列表,用于展示表格的 API 接口
 # 定义提供数据库列表,用于展示表格的 API 接口
-@bp.route('/tables', methods=['POST'])
+@bp.route('/table', methods=['POST'])
 def get_table():
 def get_table():
     data = request.get_json()
     data = request.get_json()
     table_name = data.get('table')
     table_name = data.get('table')
@@ -583,17 +583,28 @@ def get_table():
         return jsonify({'error': '需要表名'}), 400
         return jsonify({'error': '需要表名'}), 400
 
 
     try:
     try:
-        cur = db.cursor()
-        cur.execute(f"SELECT * FROM {table_name}")
-        rows = cur.fetchall()
+        # 创建 sessionmaker 实例
+        Session = sessionmaker(bind=db.engine)
+        session = Session()
 
 
-        if not rows:
-            return jsonify({'error': '表为空或不存在'}), 400
+        # 动态获取表的元数据
+        metadata = MetaData()
+        table = Table(table_name, metadata, autoload_with=db.engine)
+
+        # 从数据库中查询所有记录
+        query = select(table)
+        result = session.execute(query).fetchall()
 
 
-        headers = [description[0] for description in cur.description]
+        # 将结果转换为列表字典形式
+        rows = [dict(zip([column.name for column in table.columns], row)) for row in result]
+
+        # 获取列名
+        headers = [column.name for column in table.columns]
 
 
         return jsonify(rows=rows, headers=headers), 200
         return jsonify(rows=rows, headers=headers), 200
-    except sqlite3.Error as e:
+
+    except Exception as e:
         return jsonify({'error': str(e)}), 400
         return jsonify({'error': str(e)}), 400
     finally:
     finally:
-        db.close()
+        # 关闭 session
+        session.close()

二進制
api/pkl/rf_model_1225_0129.pkl


二進制
api/pkl/rf_model_1225_0141.pkl


二進制
api/pkl/rf_model_1225_0152.pkl


二進制
api/pkl/rf_model_1229_1916.pkl


二進制
api/pkl/rf_model_1229_2145.pkl


二進制
api/pkl/rf_model_1229_2155.pkl


+ 2 - 1
app.json

@@ -22,7 +22,8 @@
     "shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution",
     "shoping/Soil Acid Reduction Iterative Evolution/Soil Acid Reduction Iterative Evolution",
     "shoping/Staff/Staff",
     "shoping/Staff/Staff",
     "shoping/EditProfile/EditProfile",
     "shoping/EditProfile/EditProfile",
-    "pages/Result/Result"
+    "pages/Result/Result",
+    "pages/ModelTrain/ModelTrain"
   ],
   ],
   "window": {
   "window": {
     "backgroundTextStyle": "light",
     "backgroundTextStyle": "light",

+ 284 - 0
components/ec-canvas/ec-canvas.js

@@ -0,0 +1,284 @@
+import WxCanvas from './wx-canvas';
+import * as echarts from './echarts';
+
+let ctx;
+
+function compareVersion(v1, v2) {
+  v1 = v1.split('.')
+  v2 = v2.split('.')
+  const len = Math.max(v1.length, v2.length)
+
+  while (v1.length < len) {
+    v1.push('0')
+  }
+  while (v2.length < len) {
+    v2.push('0')
+  }
+
+  for (let i = 0; i < len; i++) {
+    const num1 = parseInt(v1[i])
+    const num2 = parseInt(v2[i])
+
+    if (num1 > num2) {
+      return 1
+    } else if (num1 < num2) {
+      return -1
+    }
+  }
+  return 0
+}
+
+Component({
+  properties: {
+    canvasId: {
+      type: String,
+      value: 'ec-canvas'
+    },
+
+    ec: {
+      type: Object
+    },
+
+    forceUseOldCanvas: {
+      type: Boolean,
+      value: false
+    }
+  },
+
+  data: {
+    isUseNewCanvas: false
+  },
+
+  ready: function () {
+    // Disable prograssive because drawImage doesn't support DOM as parameter
+    // See https://developers.weixin.qq.com/miniprogram/dev/api/canvas/CanvasContext.drawImage.html
+    echarts.registerPreprocessor(option => {
+      if (option && option.series) {
+        if (option.series.length > 0) {
+          option.series.forEach(series => {
+            series.progressive = 0;
+          });
+        }
+        else if (typeof option.series === 'object') {
+          option.series.progressive = 0;
+        }
+      }
+    });
+
+    if (!this.data.ec) {
+      console.warn('组件需绑定 ec 变量,例:<ec-canvas id="mychart-dom-bar" '
+        + 'canvas-id="mychart-bar" ec="{{ ec }}"></ec-canvas>');
+      return;
+    }
+
+    if (!this.data.ec.lazyLoad) {
+      this.init();
+    }
+  },
+
+  methods: {
+    init: function (callback) {
+      const version = wx.getSystemInfoSync().SDKVersion
+
+      const canUseNewCanvas = compareVersion(version, '2.9.0') >= 0;
+      const forceUseOldCanvas = this.data.forceUseOldCanvas;
+      const isUseNewCanvas = canUseNewCanvas && !forceUseOldCanvas;
+      this.setData({ isUseNewCanvas });
+
+      if (forceUseOldCanvas && canUseNewCanvas) {
+        console.warn('开发者强制使用旧canvas,建议关闭');
+      }
+
+      if (isUseNewCanvas) {
+        // console.log('微信基础库版本大于2.9.0,开始使用<canvas type="2d"/>');
+        // 2.9.0 可以使用 <canvas type="2d"></canvas>
+        this.initByNewWay(callback);
+      } else {
+        const isValid = compareVersion(version, '1.9.91') >= 0
+        if (!isValid) {
+          console.error('微信基础库版本过低,需大于等于 1.9.91。'
+            + '参见:https://github.com/ecomfe/echarts-for-weixin'
+            + '#%E5%BE%AE%E4%BF%A1%E7%89%88%E6%9C%AC%E8%A6%81%E6%B1%82');
+          return;
+        } else {
+          console.warn('建议将微信基础库调整大于等于2.9.0版本。升级后绘图将有更好性能');
+          this.initByOldWay(callback);
+        }
+      }
+    },
+
+    initByOldWay(callback) {
+      // 1.9.91 <= version < 2.9.0:原来的方式初始化
+      ctx = wx.createCanvasContext(this.data.canvasId, this);
+      const canvas = new WxCanvas(ctx, this.data.canvasId, false);
+
+      if (echarts.setPlatformAPI) {
+        echarts.setPlatformAPI({
+          createCanvas: () => canvas,
+        });
+      } else {
+        echarts.setCanvasCreator(() => canvas);
+      };
+      // const canvasDpr = wx.getSystemInfoSync().pixelRatio // 微信旧的canvas不能传入dpr
+      const canvasDpr = 1
+      var query = wx.createSelectorQuery().in(this);
+      query.select('.ec-canvas').boundingClientRect(res => {
+        if (typeof callback === 'function') {
+          this.chart = callback(canvas, res.width, res.height, canvasDpr);
+        }
+        else if (this.data.ec && typeof this.data.ec.onInit === 'function') {
+          this.chart = this.data.ec.onInit(canvas, res.width, res.height, canvasDpr);
+        }
+        else {
+          this.triggerEvent('init', {
+            canvas: canvas,
+            width: res.width,
+            height: res.height,
+            canvasDpr: canvasDpr // 增加了dpr,可方便外面echarts.init
+          });
+        }
+      }).exec();
+    },
+
+    initByNewWay(callback) {
+      // version >= 2.9.0:使用新的方式初始化
+      const query = wx.createSelectorQuery().in(this)
+      query
+        .select('.ec-canvas')
+        .fields({ node: true, size: true })
+        .exec(res => {
+          const canvasNode = res[0].node
+          this.canvasNode = canvasNode
+
+          const canvasDpr = wx.getSystemInfoSync().pixelRatio
+          const canvasWidth = res[0].width
+          const canvasHeight = res[0].height
+
+          const ctx = canvasNode.getContext('2d')
+
+          const canvas = new WxCanvas(ctx, this.data.canvasId, true, canvasNode)
+          if (echarts.setPlatformAPI) {
+            echarts.setPlatformAPI({
+              createCanvas: () => canvas,
+              loadImage: (src, onload, onerror) => {
+                if (canvasNode.createImage) {
+                  const image = canvasNode.createImage();
+                  image.onload = onload;
+                  image.onerror = onerror;
+                  image.src = src;
+                  return image;
+                }
+                console.error('加载图片依赖 `Canvas.createImage()` API,要求小程序基础库版本在 2.7.0 及以上。');
+                // PENDING fallback?
+              }
+            })
+          } else {
+            echarts.setCanvasCreator(() => canvas)
+          }
+
+          if (typeof callback === 'function') {
+            this.chart = callback(canvas, canvasWidth, canvasHeight, canvasDpr)
+          } else if (this.data.ec && typeof this.data.ec.onInit === 'function') {
+            this.chart = this.data.ec.onInit(canvas, canvasWidth, canvasHeight, canvasDpr)
+          } else {
+            this.triggerEvent('init', {
+              canvas: canvas,
+              width: canvasWidth,
+              height: canvasHeight,
+              dpr: canvasDpr
+            })
+          }
+        })
+    },
+    canvasToTempFilePath(opt) {
+      if (this.data.isUseNewCanvas) {
+        // 新版
+        const query = wx.createSelectorQuery().in(this)
+        query
+          .select('.ec-canvas')
+          .fields({ node: true, size: true })
+          .exec(res => {
+            const canvasNode = res[0].node
+            opt.canvas = canvasNode
+            wx.canvasToTempFilePath(opt)
+          })
+      } else {
+        // 旧的
+        if (!opt.canvasId) {
+          opt.canvasId = this.data.canvasId;
+        }
+        ctx.draw(true, () => {
+          wx.canvasToTempFilePath(opt, this);
+        });
+      }
+    },
+
+    touchStart(e) {
+      if (this.chart && e.touches.length > 0) {
+        var touch = e.touches[0];
+        var handler = this.chart.getZr().handler;
+        handler.dispatch('mousedown', {
+          zrX: touch.x,
+          zrY: touch.y,
+          preventDefault: () => {},
+          stopImmediatePropagation: () => {},
+          stopPropagation: () => {}
+        });
+        handler.dispatch('mousemove', {
+          zrX: touch.x,
+          zrY: touch.y,
+          preventDefault: () => {},
+          stopImmediatePropagation: () => {},
+          stopPropagation: () => {}
+        });
+        handler.processGesture(wrapTouch(e), 'start');
+      }
+    },
+
+    touchMove(e) {
+      if (this.chart && e.touches.length > 0) {
+        var touch = e.touches[0];
+        var handler = this.chart.getZr().handler;
+        handler.dispatch('mousemove', {
+          zrX: touch.x,
+          zrY: touch.y,
+          preventDefault: () => {},
+          stopImmediatePropagation: () => {},
+          stopPropagation: () => {}
+        });
+        handler.processGesture(wrapTouch(e), 'change');
+      }
+    },
+
+    touchEnd(e) {
+      if (this.chart) {
+        const touch = e.changedTouches ? e.changedTouches[0] : {};
+        var handler = this.chart.getZr().handler;
+        handler.dispatch('mouseup', {
+          zrX: touch.x,
+          zrY: touch.y,
+          preventDefault: () => {},
+          stopImmediatePropagation: () => {},
+          stopPropagation: () => {}
+        });
+        handler.dispatch('click', {
+          zrX: touch.x,
+          zrY: touch.y,
+          preventDefault: () => {},
+          stopImmediatePropagation: () => {},
+          stopPropagation: () => {}
+        });
+        handler.processGesture(wrapTouch(e), 'end');
+      }
+    }
+  }
+});
+
+function wrapTouch(event) {
+  for (let i = 0; i < event.touches.length; ++i) {
+    const touch = event.touches[i];
+    touch.offsetX = touch.x;
+    touch.offsetY = touch.y;
+  }
+  return event;
+}

+ 4 - 0
components/ec-canvas/ec-canvas.json

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

+ 4 - 0
components/ec-canvas/ec-canvas.wxml

@@ -0,0 +1,4 @@
+<!-- 新的:接口对其了H5 -->
+<canvas wx:if="{{isUseNewCanvas}}" type="2d" class="ec-canvas" canvas-id="{{ canvasId }}" bindinit="init" bindtouchstart="{{ ec.disableTouch ? '' : 'touchStart' }}" bindtouchmove="{{ ec.disableTouch ? '' : 'touchMove' }}" bindtouchend="{{ ec.disableTouch ? '' : 'touchEnd' }}"></canvas>
+<!-- 旧的 -->
+<canvas wx:else class="ec-canvas" canvas-id="{{ canvasId }}" bindinit="init" bindtouchstart="{{ ec.disableTouch ? '' : 'touchStart' }}" bindtouchmove="{{ ec.disableTouch ? '' : 'touchMove' }}" bindtouchend="{{ ec.disableTouch ? '' : 'touchEnd' }}"></canvas>

+ 4 - 0
components/ec-canvas/ec-canvas.wxss

@@ -0,0 +1,4 @@
+.ec-canvas {
+  width: 100%;
+  height: 100%;
+}

文件差異過大導致無法顯示
+ 34 - 0
components/ec-canvas/echarts.js


+ 111 - 0
components/ec-canvas/wx-canvas.js

@@ -0,0 +1,111 @@
+export default class WxCanvas {
+  constructor(ctx, canvasId, isNew, canvasNode) {
+    this.ctx = ctx;
+    this.canvasId = canvasId;
+    this.chart = null;
+    this.isNew = isNew
+    if (isNew) {
+      this.canvasNode = canvasNode;
+    }
+    else {
+      this._initStyle(ctx);
+    }
+
+    // this._initCanvas(zrender, ctx);
+
+    this._initEvent();
+  }
+
+  getContext(contextType) {
+    if (contextType === '2d') {
+      return this.ctx;
+    }
+  }
+
+  // canvasToTempFilePath(opt) {
+  //   if (!opt.canvasId) {
+  //     opt.canvasId = this.canvasId;
+  //   }
+  //   return wx.canvasToTempFilePath(opt, this);
+  // }
+
+  setChart(chart) {
+    this.chart = chart;
+  }
+
+  addEventListener() {
+    // noop
+  }
+
+  attachEvent() {
+    // noop
+  }
+
+  detachEvent() {
+    // noop
+  }
+
+  _initCanvas(zrender, ctx) {
+    zrender.util.getContext = function () {
+      return ctx;
+    };
+
+    zrender.util.$override('measureText', function (text, font) {
+      ctx.font = font || '12px sans-serif';
+      return ctx.measureText(text);
+    });
+  }
+
+  _initStyle(ctx) {
+    ctx.createRadialGradient = () => {
+      return ctx.createCircularGradient(arguments);
+    };
+  }
+
+  _initEvent() {
+    this.event = {};
+    const eventNames = [{
+      wxName: 'touchStart',
+      ecName: 'mousedown'
+    }, {
+      wxName: 'touchMove',
+      ecName: 'mousemove'
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'mouseup'
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'click'
+    }];
+    eventNames.forEach(name => {
+      this.event[name.wxName] = e => {
+        const touch = e.touches[0];
+        this.chart.getZr().handler.dispatch(name.ecName, {
+          zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
+          zrY: name.wxName === 'tap' ? touch.clientY : touch.y,
+          preventDefault: () => {},
+          stopImmediatePropagation: () => {},
+          stopPropagation: () => {}
+        });
+      };
+    });
+  }
+
+  set width(w) {
+    if (this.canvasNode) this.canvasNode.width = w
+  }
+  set height(h) {
+    if (this.canvasNode) this.canvasNode.height = h
+  }
+
+  get width() {
+    if (this.canvasNode)
+      return this.canvasNode.width
+    return 0
+  }
+  get height() {
+    if (this.canvasNode)
+      return this.canvasNode.height
+    return 0
+  }
+}

+ 126 - 0
pages/ModelTrain/ModelTrain.js

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

+ 6 - 0
pages/ModelTrain/ModelTrain.json

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

+ 53 - 0
pages/ModelTrain/ModelTrain.wxml

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

+ 139 - 0
pages/ModelTrain/ModelTrain.wxss

@@ -0,0 +1,139 @@
+
+/* 表格容器 */
+.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;
+}
+
+/* 单选框样式 */
+.radio {
+  display: inline-block;
+  margin-right: 10px; /* 单选框之间的间隔 */
+}
+
+/* 单选框选中时的样式 */
+.radio-checked {
+  color: #1AAD19; /* 微信小程序主题色 */
+}
+
+/* 单选框未选中时的样式 */
+.radio-unchecked {
+  color: #ccc; /* 灰色表示未选中 */
+}
+
+/* 按钮通用样式 */
+.button-container button {
+  width: 50%; /* 按钮宽度100%,占满容器宽度 */
+  height: 50px; /* 按钮高度 */
+  line-height: 50px; /* 行高与按钮高度一致,使文本垂直居中 */
+  background-color: #1AAD19; /* 微信绿 */
+  color: white; /* 文字颜色为白色 */
+  border-radius: 5px; /* 圆角边框 */
+  font-size: 16px; /* 字体大小 */
+  margin-top: 20px;
+}
+
+/* 按钮点击效果 */
+.button-container button:active {
+  background-color: #179B16; /* 点击时背景颜色变深 */
+}
+
+
+/* 下拉框容器样式 */
+.picker-container {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 16px; /* 字体大小 */
+  color: #333; /* 字体颜色 */
+  padding: 8px 12px; /* 内边距 */
+  background-color: #fff; /* 背景颜色 */
+  border: 1px solid #ddd; /* 边框 */
+  border-radius: 4px; /* 圆角边框 */
+  width: 200px; /* 固定宽度 */
+  margin-left: auto; /* 自动左边距 */
+  margin-right: auto; /* 自动右边距 */
+  box-sizing: border-box; /* 确保边框和内边距包含在宽度内 */
+}
+
+/* 下拉框样式 */
+.picker {
+  flex-grow: 1; /* 使下拉框占据剩余空间 */
+  text-align: left; /* 文字左对齐 */
+}
+
+/* 三角形样式 */
+.picker-arrow {
+  width: 0;
+  height: 0;
+  border-left: 5px solid transparent;
+  border-right: 5px solid transparent;
+  border-top: 5px solid #333; /* 三角形颜色 */
+  margin-left: 10px; /* 与下拉框文本的间隔 */
+}
+
+/* 下拉框标题样式 */
+.picker-title {
+  font-size: 16px; /* 字体大小 */
+  font-weight: bold; /* 字体加粗 */
+  color: #333; /* 字体颜色 */
+  margin-bottom: 10px; /* 与下拉框之间的间隔 */
+  text-align: center; /* 文字居中 */
+  padding: 8px 0; /* 上下内边距 */
+  background-color: #f0f0f0; /* 背景颜色 */
+  border: 1px solid #ddd; /* 边框 */
+  border-radius: 4px; /* 圆角边框 */
+  width: 200px; /* 固定宽度 */
+  margin-left: auto; /* 自动左边距 */
+  margin-right: auto; /* 自动右边距 */
+  box-sizing: border-box; /* 确保边框和内边距包含在宽度内 */
+}

+ 2 - 2
pages/Visualization/Visualization.js

@@ -38,13 +38,13 @@ Page({
 
 
   LoadData: function() {
   LoadData: function() {
     wx.request({
     wx.request({
-      url: 'http://localhost:5000/tables',
+      url: 'http://localhost:5000/table',
       method: 'POST',
       method: 'POST',
       header: {
       header: {
         'Content-Type': 'application/json'
         'Content-Type': 'application/json'
       },
       },
       data: {
       data: {
-        table: 'Soil_samples'
+        table: 'current_reduce'
     },
     },
     success: (res) => {
     success: (res) => {
       console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式
       console.log('后端返回数据:', res.data.rows); // 打印返回数据,确认格式

+ 5 - 0
pages/threshold/threshold.js

@@ -22,5 +22,10 @@ Page({
     wx.navigateTo({
     wx.navigateTo({
       url: '/pages/thres/thres',
       url: '/pages/thres/thres',
     });
     });
+  },
+  modelTrain() {
+    wx.navigateTo({
+      url: '/pages/ModelTrain/ModelTrain',
+    })
   }
   }
 });
 });

+ 2 - 0
pages/threshold/threshold.wxml

@@ -2,4 +2,6 @@
   <button class="full-width-button" bindtap="Model_Selection">模型选择</button>
   <button class="full-width-button" bindtap="Model_Selection">模型选择</button>
 
 
   <button class="full-width-button" bindtap="thres">阈值选择</button>
   <button class="full-width-button" bindtap="thres">阈值选择</button>
+
+  <button class="full-width-button" bindtap="modelTrain">模型训练</button>
 </view>
 </view>

+ 3 - 2
project.config.json

@@ -6,7 +6,7 @@
   },
   },
   "setting": {
   "setting": {
     "urlCheck": true,
     "urlCheck": true,
-    "es6": false,
+    "es6": true,
     "postcss": true,
     "postcss": true,
     "minified": true,
     "minified": true,
     "newFeature": true,
     "newFeature": true,
@@ -18,7 +18,8 @@
       "ignore": [],
       "ignore": [],
       "disablePlugins": [],
       "disablePlugins": [],
       "outputPath": ""
       "outputPath": ""
-    }
+    },
+    "enhance": true
   },
   },
   "compileType": "miniprogram",
   "compileType": "miniprogram",
   "libVersion": "3.7.1",
   "libVersion": "3.7.1",

+ 312 - 49
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js

@@ -1,66 +1,329 @@
-// pages/Regular User/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.js
-Page({
+import * as echarts from '../../components/ec-canvas/echarts';
 
 
-  /**
-   * 页面的初始数据
-   */
+Page({
   data: {
   data: {
+    ecBox: {
+      onInit: function (canvas, width, height, dpr) {
+        const boxChart = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(boxChart);
+        boxChart.setOption(getBoxOption());
 
 
-  },
+        return boxChart;
+      }
+    },
+    ecLine: {
+      onInit: function (canvas, width, height, dpr) {
+        const lineChart = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(lineChart);
+        lineChart.setOption(getLineOption());
 
 
-  /**
-   * 生命周期函数--监听页面加载
-   */
-  onLoad(options) {
+        return lineChart;
+      }
+    },
+    ecScatter: {
+      onInit: function (canvas, width, height, dpr) {
+        const scatterChart = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(scatterChart);
+        scatterChart.setOption(getScatterOption());
 
 
+        return scatterChart;
+      }
+    },
+    ecBar: {
+      onInit: function (canvas, width, height, dpr) {
+        const barChart = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr
+        });
+        canvas.setChart(barChart);
+        barChart.setOption(getBarOption());
+        return barChart;
+      }
+    },
+    ecPie: {
+      onInit: function (canvas, width, height, dpr) {
+        const pieChart = echarts.init(canvas, null, {
+          width: width,
+          height: height,
+          devicePixelRatio: dpr // new
+        });
+        canvas.setChart(pieChart);
+        pieChart.setOption(getPieOption());
+
+        return pieChart;
+      }
+    }
   },
   },
 
 
-  /**
-   * 生命周期函数--监听页面初次渲染完成
-   */
   onReady() {
   onReady() {
+    // You can add any additional logic here if needed
+  }
+});
 
 
+function getBarOption() {
+  return {
+    title: {
+      text: 'Basic Bar Chart'
+    },
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    legend: {
+      data: ['Sales']
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [
+      {
+        name: 'Sales',
+        type: 'bar',
+        data: [5, 20, 36, 10, 10, 20, 30],
+        itemStyle: {
+          color: '#c23531'
+        }
+      }
+    ]
+  }
+}
+function getBoxOption() {
+  return {
+    title: {
+      text: 'Sample ΔpH'
   },
   },
-
-  /**
-   * 生命周期函数--监听页面显示
-   */
-  onShow() {
-
+  tooltip: {
+      trigger: 'item',
+      axisPointer: {
+          type: 'shadow'
+      }
   },
   },
-
-  /**
-   * 生命周期函数--监听页面隐藏
-   */
-  onHide() {
-
+  grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
   },
   },
-
-  /**
-   * 生命周期函数--监听页面卸载
-   */
-  onUnload() {
-
+  xAxis: {
+      type: 'category',
+      data: Array.from({length: 27}, (_, i) => i + 1) // 生成 1 到 27 的样本编号
   },
   },
-
-  /**
-   * 页面相关事件处理函数--监听用户下拉动作
-   */
-  onPullDownRefresh() {
-
+  yAxis: {
+      type: 'value',
+      name: 'ΔpH',
+      min: -1.5,
+      max: 0.5
   },
   },
+  series: [
+      {
+          name: 'ΔpH',
+          type: 'boxplot',
+          data: [
+              // 每个数组代表一个样本的箱型图数据
+              // 格式为 [min, Q1, median, Q3, max],可以包含异常值作为单独的数组元素
+                [-0.8, -0.6, -0.5, -0.3, 0.0], 
+                [-0.7, -0.5, -0.4, -0.2, 0.1], 
+                [-0.9, -0.7, -0.6, -0.4, -0.1], 
+                [-1.0, -0.8, -0.7, -0.5, -0.3], 
+                [-1.1, -0.9, -0.8, -0.6, -0.4], 
+                [-1.2, -1.0, -0.9, -0.7, -0.5], 
+                [-1.3, -1.1, -1.0, -0.8, -0.6], 
+                [-1.4, -1.2, -1.1, -0.9, -0.7], 
+                [-1.5, -1.3, -1.2, -1.0, -0.8], 
+                [-1.4, -1.2, -1.1, -0.9, -0.7], 
+                [-1.3, -1.1, -1.0, -0.8, -0.6], 
+                [-1.2, -1.0, -0.9, -0.7, -0.5], 
+                [-1.1, -0.9, -0.8, -0.6, -0.4], 
+                [-1.0, -0.8, -0.7, -0.5, -0.3], 
+                [-0.9, -0.7, -0.6, -0.4, -0.1], 
+                [-0.8, -0.6, -0.5, -0.3, 0.0], 
+                [-0.7, -0.5, -0.4, -0.2, 0.1], 
+                [-0.6, -0.4, -0.3, -0.1, 0.2], 
+                [-0.5, -0.3, -0.2, 0.0, 0.3], 
+                [-0.4, -0.2, -0.1, 0.1, 0.4], 
+                [-0.3, -0.1, 0.0, 0.2, 0.5], 
+                [-0.2, 0.0, 0.1, 0.3, 0.6], 
+                [-0.1, 0.1, 0.2, 0.4, 0.7], 
+                [0.0, 0.2, -0.3, 0.5, 0.8], 
+                [-0.1, 0.3, -0.4, 0.6, 0.9], 
+                [-0.2, 0.4, -0.5, 0.7, 1.0], 
+                [-0.3, 0.5, -0.6, 0.8, 1.1]  
+          ],
+          tooltip: {
+              formatter: function (param) {
+                  return [
+                      'Experiment ' + param.name + ': ',
+                      'upper: ' + param.data[5],
+                      'Q3: ' + param.data[4],
+                      'median: ' + param.data[3],
+                      'Q1: ' + param.data[2],
+                      'lower: ' + param.data[1]
+                  ].join(' ');
+              }
+          }
+      }
+  ]
+  };
+}
 
 
-  /**
-   * 页面上拉触底事件的处理函数
-   */
-  onReachBottom() {
-
-  },
+function getScatterOption() {
+  return {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'cross'
+          }
+        },
+        legend: {
+          data: ['True vs Predicted']
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true
+        },
+        xAxis: {
+          name: 'True Values',  // 对应 Python 中的 Ytest
+          type: 'value',
+          boundaryGap: [0, 0.01]
+        },
+        yAxis: {
+          name: 'Predicted Values',  // 对应 Python 中的 y_pred
+          type: 'value',
+          boundaryGap: [0, 0.01]
+        },
+        series: [
+          {
+            name: 'True vs Predicted',
+            type: 'scatter',
+            data: [
+              [-0.003333333333333854, -0.45726666666666654], [-0.1733333333333329, -0.1726333333333331], 
+              [-0.6233333333333331, -0.5226666666666667], [-0.7088888888888892, -0.4791888888888889], 
+              [-0.3366666666666669, -0.3630666666666673], [-0.8888888888888887, -0.48272222222222183], 
+              [-0.5633333333333326, -0.7492444444444444], [-0.7333333333333325, -0.5572666666666672], 
+              [-0.3366666666666663, -0.29379999999999984], [-1.176666666666666, -0.8544111111111106], 
+              [-0.7122222222222225, -0.4959777777777775], [-0.7699999999999996, -0.6149666666666669]
+            ],
+            symbolSize: 10,
+            itemStyle: {
+              
+            }
+          },
+          {
+            name: 'Trendline',
+            type: 'line',
+            data: [
+              // 绘制对角线 y = x (这就是理想情况下 True 和 Predicted 的值相等)
+              [-1.2,-1.2],  // 最小值
+              [-0.0034, -0.0034]   // 最大值
+            ],
+            lineStyle: {
+              type: 'dashed',
+              
+            }
+          }
+        ]
+      };
+}
 
 
-  /**
-   * 用户点击右上角分享
-   */
-  onShareAppMessage() {
+function getLineOption() {
+  return {
+    tooltip: {
+      trigger: 'axis'
+    },
+    legend: {
+      data: ['Random Forest', 'XGBoost', 'Gradient Boosting']  // 模型名称
+    },
+    grid: {
+      left: '3%',
+      right: '4%',
+      bottom: '3%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: ['10%', '20%', '30%', '40%', '50%', '60%', '70%', '80%', '90%', '100%']  // train_sizes按10%递增
+    },
+    yAxis: {
+      type: 'value'
+    },
+    series: [
+      {
+        name: 'Random Forest',
+        type: 'line',
+        data: [-0.17101591951095463, -0.556719360354051, -0.04083550751401055, -0.20858221504075436, 0.07297292282221035, 0.19857845644421734, 0.28407131176770184, 0.27979356883596496, 0.36904808817286416, 0.4183018571701477]  // 使用您的实际R2分数数据
+      },
+      {
+        name: 'XGBoost',
+        type: 'line',
+        data: [-1.1811781145886937, -1.5645641005612534, -0.12619079632263497, 0.03324096120721032, 0.06969290639267578, 0.12375262461601955, 0.5331670468884062, 0.49454793164801647, 0.31904329339597803, 0.2712670704381914]
+      },
+      {
+        name: 'Gradient Boosting',
+        type: 'line',
+        data: [-0.8583039298789095, -1.073316171952042, 0.09659300885027666, 0.06097833957434784, 0.191975498544109, 0.3718334600546489, 0.3948098332187753, 0.4398778520728397, 0.452609022210963, 0.41484634172723023]
+      }
+    ]
+  };
+}
 
 
-  }
-})
+function getPieOption() {
+  return {
+    tooltip: {
+      trigger: 'item',
+      formatter: '{a} <br/>{b} : {c} ({d}%)'
+    },
+    legend: {
+      orient: 'vertical',
+      left: 'left',
+      data: ['示例1', '示例2', '示例3']
+    },
+    series: [
+      {
+        name: '访问来源',
+        type: 'pie',
+        radius: '55%',
+        center: ['50%', '60%'],
+        data: [
+          { value: 335, name: '示例1' },
+          { value: 310, name: '示例2' },
+          { value: 234, name: '示例3' },
+        ],
+        emphasis: {
+          itemStyle: {
+            shadowBlur: 10,
+            shadowOffsetX: 0,
+            shadowColor: 'rgba(0, 0, 0, 0.5)'
+          }
+        }
+      }
+    ]
+  };
+}

+ 3 - 1
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.json

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

+ 17 - 2
shoping/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml

@@ -1,2 +1,17 @@
-<!--pages/Regular User/Soil Acidification Iterative Evolution/Soil Acidification Iterative Evolution.wxml-->
-<text>这里可以放"反酸模型"计算图片</text>
+<view class="container">
+  <view class="chart-container">
+    <ec-canvas id="boxChart" canvas-id="boxChart" ec="{{ecBox}}"></ec-canvas>
+  </view>
+  <view class="chart-container">
+    <ec-canvas id="lineChart" canvas-id="lineChart" ec="{{ecLine}}"></ec-canvas>
+  </view>
+  <view class="chart-container">
+    <ec-canvas id="scatterChart" canvas-id="scatterChart" ec="{{ecScatter}}"></ec-canvas>
+  </view>
+  <view class="chart-container">
+    <ec-canvas id="pieChart" canvas-id="pieChart" ec="{{ecPie}}"></ec-canvas>
+  </view>
+  <view class="chart-container">
+    <ec-canvas id="barChart" canvas-id="barChart" ec="{{ecBar}}"></ec-canvas>
+  </view>
+</view>

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

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

部分文件因文件數量過多而無法顯示