Parcourir la source

Merge branch 'master' of http://code.nyzhwlw.com:10202/yf_lj/bigDataApp

leo il y a 4 jours
Parent
commit
a4d67fe2b5
41 fichiers modifiés avec 2689 ajouts et 378 suppressions
  1. 57 1
      pages.json
  2. BIN
      pages/cb/shuifeizsFirst/assets/bottomLine.png
  3. BIN
      pages/cb/shuifeizsFirst/assets/bucketStop1.png
  4. BIN
      pages/cb/shuifeizsFirst/assets/formulaSetting.png
  5. BIN
      pages/cb/shuifeizsFirst/assets/lineBottomBorder.png
  6. BIN
      pages/cb/shuifeizsFirst/assets/lineBottomBorderLittle.png
  7. BIN
      pages/cb/shuifeizsFirst/assets/lineHeight.png
  8. BIN
      pages/cb/shuifeizsFirst/assets/lineOne.png
  9. BIN
      pages/cb/shuifeizsFirst/assets/lineTopBorder.png
  10. BIN
      pages/cb/shuifeizsFirst/assets/lineTopBorderLittle.png
  11. BIN
      pages/cb/shuifeizsFirst/assets/manualControl.png
  12. BIN
      pages/cb/shuifeizsFirst/assets/media-pause-fill.png
  13. BIN
      pages/cb/shuifeizsFirst/assets/media-pause.png
  14. BIN
      pages/cb/shuifeizsFirst/assets/media-play-fill.png
  15. BIN
      pages/cb/shuifeizsFirst/assets/media-play.png
  16. BIN
      pages/cb/shuifeizsFirst/assets/operatingRecord.png
  17. BIN
      pages/cb/shuifeizsFirst/assets/rain.png
  18. BIN
      pages/cb/shuifeizsFirst/assets/solenoidCloseValve.png
  19. BIN
      pages/cb/shuifeizsFirst/assets/solenoidStopValve.png
  20. BIN
      pages/cb/shuifeizsFirst/assets/solenoidValveGray.png
  21. BIN
      pages/cb/shuifeizsFirst/assets/solenoidValveStop.png
  22. BIN
      pages/cb/shuifeizsFirst/assets/stop-fill.png
  23. BIN
      pages/cb/shuifeizsFirst/assets/stop.png
  24. BIN
      pages/cb/shuifeizsFirst/assets/timing.png
  25. BIN
      pages/cb/shuifeizsFirst/assets/wheelIrrigation.png
  26. 700 0
      pages/cb/shuifeizsFirst/autoSetting.vue
  27. 23 7
      pages/cb/shuifeizsFirst/automation.vue
  28. 1 3
      pages/cb/shuifeizsFirst/components/base.vue
  29. 128 22
      pages/cb/shuifeizsFirst/components/facilitystate.vue
  30. 15 4
      pages/cb/shuifeizsFirst/components/irrigatedArea.vue
  31. 29 2
      pages/cb/shuifeizsFirst/components/rotationBottom.vue
  32. 7 2
      pages/cb/shuifeizsFirst/components/rotationCard.vue
  33. 2 1
      pages/cb/shuifeizsFirst/components/rotationItems.vue
  34. 280 60
      pages/cb/shuifeizsFirst/control.vue
  35. 0 257
      pages/cb/shuifeizsFirst/detail.vue
  36. 381 0
      pages/cb/shuifeizsFirst/formulaSetting.vue
  37. 17 17
      pages/cb/shuifeizsFirst/history.vue
  38. 2 2
      pages/cb/shuifeizsFirst/rotationflow.vue
  39. 738 0
      pages/cb/shuifeizsFirst/shuifeizs.vue
  40. 302 0
      pages/cb/shuifeizsFirst/timingSetting.vue
  41. 7 0
      pages/equipList/index.vue

+ 57 - 1
pages.json

@@ -855,7 +855,7 @@
       }
     },
     {
-      "path": "pages/cb/shuifeizsFirst/detail",
+      "path": "pages/cb/shuifeizsFirst/shuifeizs",
       "style": {
         "navigationBarTitleText": "水肥一体机",
         "enablePullDownRefresh": false,
@@ -863,6 +863,62 @@
       }
     },
     {
+      "path": "pages/cb/shuifeizsFirst/control",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cb/shuifeizsFirst/automation",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cb/shuifeizsFirst/rotationflow",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cb/shuifeizsFirst/autoSetting",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cb/shuifeizsFirst/formulaSetting",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cb/shuifeizsFirst/timingSetting",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/cb/shuifeizsFirst/history",
+      "style": {
+        "navigationBarTitleText": "",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/cb/shuifeizs/control",
       "style": {
         "navigationBarTitleText": "",

BIN
pages/cb/shuifeizsFirst/assets/bottomLine.png


BIN
pages/cb/shuifeizsFirst/assets/bucketStop1.png


BIN
pages/cb/shuifeizsFirst/assets/formulaSetting.png


BIN
pages/cb/shuifeizsFirst/assets/lineBottomBorder.png


BIN
pages/cb/shuifeizsFirst/assets/lineBottomBorderLittle.png


BIN
pages/cb/shuifeizsFirst/assets/lineHeight.png


BIN
pages/cb/shuifeizsFirst/assets/lineOne.png


BIN
pages/cb/shuifeizsFirst/assets/lineTopBorder.png


BIN
pages/cb/shuifeizsFirst/assets/lineTopBorderLittle.png


BIN
pages/cb/shuifeizsFirst/assets/manualControl.png


BIN
pages/cb/shuifeizsFirst/assets/media-pause-fill.png


BIN
pages/cb/shuifeizsFirst/assets/media-pause.png


BIN
pages/cb/shuifeizsFirst/assets/media-play-fill.png


BIN
pages/cb/shuifeizsFirst/assets/media-play.png


BIN
pages/cb/shuifeizsFirst/assets/operatingRecord.png


BIN
pages/cb/shuifeizsFirst/assets/rain.png


BIN
pages/cb/shuifeizsFirst/assets/solenoidCloseValve.png


BIN
pages/cb/shuifeizsFirst/assets/solenoidStopValve.png


BIN
pages/cb/shuifeizsFirst/assets/solenoidValveGray.png


BIN
pages/cb/shuifeizsFirst/assets/solenoidValveStop.png


BIN
pages/cb/shuifeizsFirst/assets/stop-fill.png


BIN
pages/cb/shuifeizsFirst/assets/stop.png


BIN
pages/cb/shuifeizsFirst/assets/timing.png


BIN
pages/cb/shuifeizsFirst/assets/wheelIrrigation.png


+ 700 - 0
pages/cb/shuifeizsFirst/autoSetting.vue

@@ -0,0 +1,700 @@
+<template>
+  <view class="auto-fertilization-container">
+    <custom-card>
+      <block slot="backText">自动施肥</block>
+    </custom-card>
+    <!-- 内容区域 -->
+    <view class="content">
+      <!-- 灌溉模式 -->
+      <view class="setting-top-container">
+        <view class="setting-item">
+          <text class="setting-label">灌溉模式</text>
+          <view class="radio-group">
+            <view class="group-check-container" @click="selectFertType('1')">
+              <view class="group-check-radius" :class="{'active': FertType == '1'}">
+                <u-icon class="check-icon" name="checkmark" size="24rpx" v-if="FertType == '1'"/>
+              </view>
+              <text class="text" :class="FertType == '1'?'text-active':''">定时</text>
+            </view>
+            <view class="group-check-container" @click="selectFertType('2')" :class="{'active': FertType == '2'}" style="width: 160rpx;">
+              <view class="group-check-radius" :class="{'active': FertType == '2'}">
+                <u-icon class="check-icon" name="checkmark" size="24rpx" v-if="FertType == '2'"/>
+              </view>
+              <text class="text" :class="FertType == '2'?'text-active':''">定量</text>  
+            </view>
+            <view class="group-check-container" @click="selectFertType('0')" :class="{'active': FertType == '0'}">
+              <view class="group-check-radius" :class="{'active': FertType == '0'}">
+                <u-icon class="check-icon" name="checkmark" size="24rpx" v-if="FertType == '0'"/>
+              </view>
+              <text class="text" :class="FertType == '0'?'text-active':''">禁用</text>  
+            </view>
+          </view>
+        </view>
+
+        <!-- 配方方式 -->
+        <view class="setting-item">
+          <text class="setting-label">配方方式</text>
+          <view class="radio-group">
+            <view class="group-check-container" @click="selectFertPidType('1')" >
+              <view class="group-check-radius" :class="{'active': FertPidType == '1'}">
+                <u-icon class="check-icon" name="checkmark" size="24rpx" v-if="FertPidType == '1'"/>
+              </view>
+              <text class="text" :class="FertPidType == '1'?'text-active':''">PID</text>
+            </view>
+            <view class="group-check-container"  @click="selectFertPidType('2')"  :class="{'active': FertPidType == '2'}" style="width: 160rpx;">
+              <view class="group-check-radius" :class="{'active': FertPidType == '2'}">
+                <u-icon class="check-icon" name="checkmark" size="24rpx" v-if="FertPidType == '2'"/>
+              </view>
+              <text class="text" :class="FertPidType == '2'?'text-active':''">水肥比例</text>  
+            </view>
+            <view class="group-check-container" @click="selectFertPidType('0')"  :class="{'active': FertPidType == '0'}">
+              <view class="group-check-radius" :class="{'active': FertPidType == '0'}">
+                <u-icon class="check-icon" name="checkmark" size="24rpx" v-if="FertPidType == '0'"/>
+              </view>
+              <text class="text" :class="FertPidType == '0'?'text-active':''">禁用</text>  
+            </view>
+          </view>
+        </view>
+
+        <!-- 轮灌次数 -->
+        <view class="setting-item">
+          <text class="setting-label">轮灌次数</text>
+          <view class="number-input">
+            <u-number-box v-model="IrrCnt" placeholder="请输入轮灌次数" min="1" max="9999999" @minus="irrChange(IrrCnt)" @plus="irrChange(IrrCnt)"></u-number-box>
+          </view>
+        </view>
+
+        <!-- 轮灌间隔 -->
+        <view class="setting-item">
+          <text class="setting-label">轮灌间隔</text>
+          <view class="time-input">
+            <input type="number" v-model="IdleTim" class="time-input-field" min="0" max="1440" placeholder="请输入轮灌间隔(分钟)" @change="idleChange(IdleTim)" />
+            <text class="time-unit">分钟</text>
+          </view>
+        </view>
+
+        <!-- 肥前水 -->
+        <view class="setting-item">
+          <text class="setting-label">肥前水</text>
+          <view class="time-input">
+            <input type="number" v-model="FrontClearWater" class="time-input-field"  min="0" max="1440" placeholder="请输入肥前水(秒)" @change="frontChange(FrontClearWater)" />
+            <text class="time-unit">秒</text>
+          </view>
+        </view>
+
+        <!-- 肥后水 -->
+        <view class="setting-item">
+          <text class="setting-label">肥后水</text>
+          <view class="time-input">
+            <input type="number" v-model="UnderClearWater" class="time-input-field"  min="0" max="1440" placeholder="请输入肥后水(秒)" @change="underChange(UnderClearWater)" />
+            <text class="time-unit">秒</text>
+          </view>
+        </view>
+      </view>
+      <u-picker v-model="show" mode="selector" :range="selector" range-key="label" @confirm="confirmHandler"></u-picker>
+      <!-- 定时轮灌组 -->
+      <view class="round-groups-section">
+        <text class="section-title">定时轮灌组</text>
+        <view class="round-groups-container">
+          <!-- 左侧轮灌组列表 -->
+          <view class="round-groups-list">
+            <view 
+              v-for="(item,index) in group_list" :key="item.group"
+              class="round-group-item"
+              @click="selectGroup(index)"
+              :class="{ active: selectedGroup === index }"
+            >
+              <text :class="{ 'green-text': selectedGroup === index }">{{ index + 1 }}组</text>
+            </view>
+          </view>
+
+          <!-- 右侧轮灌组详情 -->
+          <view class="round-group-detail">
+            <view class="group-detail-item" v-for="(item,index) in group_list" :key="item.group" :id="'group-'+index">
+              <view class="group-header">
+                <text class="group-title">{{index + 1}}组</text>
+                <view class="group-check" :class="{ active: item.selected }" @click="changeGroupStatus(item)">
+                  <u-icon class="check-icon" name="checkmark" size="24rpx" />
+                </view>
+              </view>
+              <view class="group-settings">
+                <view class="setting-row">
+                  <text class="setting-row-label">施肥配方</text>
+                  <view @click="selectFormula(index)" class="select-group">{{ getTitle(item) }}</view>
+                </view>
+                <view class="setting-row">
+                  <text class="setting-row-label">灌溉时长</text>
+                  <input
+                    type="number"
+                    v-model="item.PartTim"
+                    class="setting-row-input"
+                    min="0"
+                    max="1440"
+                    placeholder="请输入灌溉时长(分钟)"
+                    @change="(item) => PartTimChange(item)"
+                  />
+                  <text class="setting-row-unit">分钟</text>
+                </view>
+                <view class="setting-row">
+                  <text class="setting-row-label">施肥时长</text>
+                  <input
+                    type="number"
+                    v-model="item.FertTim"
+                    class="setting-row-input"
+                    min="0"
+                    max="1440"
+                    placeholder="请输入施肥时长(分钟)"
+                    @change="(item) => FertTimChange(item)"
+                  />
+                  <text class="setting-row-unit">分钟</text>
+                </view>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      show: false,
+      devBid:'',
+      title: '选择配方',
+      selector: [],
+      selectedGroup: 0,
+      FertType: -1,
+      FertPidType: -1,
+      IrrCnt: 0,
+      IdleTim: 0,
+      FrontClearWater: 0,
+      UnderClearWater: 0,
+      group_list:[],
+      currentIndex: -1,
+    };
+  },
+  
+  async onLoad(options) {
+    const { devBid } = options;
+    this.devBid = devBid;
+    await this.getInfoList();
+    await this.getConfigInfo();
+  },
+  methods: {
+    async refresh(){
+      const params = {
+        devBid: this.devBid,
+      };
+      await this.$myRequest({
+        url:'/api/v2/iot/device/sf/refresh/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+    },
+    getTitle(item){
+      let value = '';
+      if(item.labelText){
+        return item.labelText;
+      }
+      for(let key in item){
+        if(key.includes('Formula')){
+          value = item[key];
+          break;
+        }
+      }
+      for(let i = 0;i< this.selector.length;i++){
+        const item = this.selector[i];
+        if(item.Formula == value){
+          return item.label;
+        }
+      }
+      return '选择配方';
+    },
+   confirmHandler(e){
+    const currentItem = this.selector[e];
+    const Formula = currentItem.Formula;
+    const item = this.group_list[this.currentIndex];
+    item.labelText = currentItem.label;
+    if(item.selected){
+      let keyLabel = '';
+      for(let key in item){
+        if(key.includes('Formula')){
+          keyLabel = key
+          break;
+        }
+      }
+      const params = {
+        [keyLabel]: Formula,
+        group_value: 1,
+      }
+      this.editGroupList(params);
+    }
+   },
+   async getInfoList(){
+      this.recipeList = [];
+      const params = {
+        devBid: this.devBid
+      }
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/peifang/list/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+      const list = res || [];
+      console.log(list,'listliustlist')
+      list.forEach((item,index)=>{
+        item.label = '配方' + (index + 1);
+      })
+      this.selector = list;
+    },
+    selectFertType(type){
+      this.FertType = type;
+      this.editGroup({
+        FertType: type,
+      });
+    },
+    selectFertPidType(type){
+      this.FertPidType = type;
+      this.editGroup({
+        FertPidType: type,
+      });
+    },
+    underChange(val){
+      this.UnderClearWater = val;
+      this.editGroup({UnderClearWater: val});
+    },
+    PartTimChange(item){
+      if(item.selected){
+        this.editGroupList({group_value: item.group_value, PartTim: item.PartTim});
+      }
+    },
+    FertTimChange(item){
+      if(item.selected){
+        this.editGroupList({group_value: item.group_value, FertTim: item.FertTim});
+      }
+    },
+    frontChange(val){
+      this.FrontClearWater = val;
+      this.editGroup({FrontClearWater: val});
+    },
+    idleChange(val){
+      this.IdleTim = val;
+      this.editGroup({IdleTim: val});
+    },
+    irrChange(val){
+      this.IrrCnt = val;
+      this.editGroup({IrrCnt: val});
+    },
+    selectFormula(index){
+      this.show = true;
+      this.currentIndex = index;
+    },
+    async editGroupList(data){
+      const params = {
+        devBid: parseInt(this.devBid),
+        data,
+      }
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/auto/group/edit/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+      console.log(res,'resres')
+      if(res?.code === '000000'){
+        uni.showToast({
+          title: '保存成功',
+          icon: 'success',
+        })
+        this.refresh();
+      }
+    },
+    async editGroup(data){
+      const params = {
+        devBid: parseInt(this.devBid),
+        data,
+      }
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/auto/config/edit/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+      if(res?.code === '000000'){
+        uni.showToast({
+          title: '保存成功',
+          icon: 'success',
+        })
+        this.refresh();
+      }
+    },
+    async getConfigInfo(){
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/auto/config/info/',
+        method:'post',
+        data: {
+          devBid: String(this.devBid),
+        },
+      })
+      const resData = res || {};
+      this.FertPidType = resData?.FertPidType;
+      this.FertType = resData?.FertType;
+      this.IrrCnt = resData?.IrrCnt;
+      this.IdleTim = resData?.IdleTim;
+      this.FrontClearWater = resData?.FrontClearWater;
+      this.UnderClearWater = resData?.UnderClearWater;
+      const group_list = resData.group_list || [];
+      this.group_list = group_list.map((item, index) => {
+        const selected = item.group_value == 1 ? true : false;
+        for (let key in item) {
+          if (key.includes('PartTim')) {
+            item.PartTim = item[key];
+          } else if (key.includes('FertTim')) {
+            item.FertTim = item[key];
+          } else if (key.includes('Formula')) {
+            item.Formula = item[key];
+          }
+        }
+        return {
+          ...item,
+          selected
+        };
+      });
+    },
+    changeGroupStatus(item) {
+      item.selected = !item.selected;
+      this.editGroupList({group_value: item.group_value});
+    },
+    selectGroup(index) {
+      this.selectedGroup = index;
+      uni.createSelectorQuery().select('#group-' + index).boundingClientRect((data) => {
+        if (data) {
+          uni.pageScrollTo({
+            scrollTop: data.top,
+            duration: 300
+          });
+        }
+      }).exec();
+    },
+    // 减少轮灌次数
+    decreaseRoundCount() {
+      if (this.roundCount > 1) {
+        this.roundCount--;
+      }
+    },
+    // 增加轮灌次数
+    increaseRoundCount() {
+      this.roundCount++;
+    },
+    // 确认按钮点击事件
+    confirm() {
+      // 这里可以添加确认逻辑
+      console.log('确认设置');
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.auto-fertilization-container {
+  width: 100%;
+  min-height: 100vh;
+  background: linear-gradient(180deg, #f5f6fa00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  font-family: 'Source Han Sans CN';
+}
+.group-check-container{
+  display: flex;
+  width: 120rpx;
+}
+.select-group{
+  text-align:right;
+}
+.group-check-radius{
+  width: 30rpx;
+  height: 30rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 50%;
+  background: #ffffff;
+  border: 2rpx solid #E4E7ED;
+  font-size: 24rpx;
+  color: #14a478;
+  margin-right: 10rpx;
+}
+.active{
+  border-color: #0BBC58;
+}
+.text{
+  color: #666666;
+  font-family: "Source Han Sans CN VF";
+  font-size: 28rpx;
+}
+.text-active{
+  color: #0BBC58;
+}
+/* 内容区域 */
+.content {
+  overflow-y: auto;
+  width: calc(100% - 64rpx);
+  margin-left: 32rpx;
+  display:flex;
+  flex-direction: column;
+}
+.setting-top-container{
+  border: 2px solid #FFF;
+  border-radius: 8px;
+  padding: 32rpx;
+  background: #FFF;
+}
+/* 设置项 */
+.setting-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 24rpx 0;
+  border-bottom: 1rpx solid #e8e8e8;
+
+  .setting-label {
+    font-size: 28rpx;
+    color: #042118;
+  }
+
+  /* 单选组 */
+  .radio-group {
+    display: flex;
+    gap: 32rpx;
+
+    .radio-item {
+      display: flex;
+      align-items: center;
+      gap: 8rpx;
+      font-size: 26rpx;
+      color: #666;
+      width: 120rpx;
+      &.active {
+        color: #14a478;
+      }
+
+      radio {
+        transform: scale(0.8);
+      }
+    }
+  }
+
+  /* 数字输入 */
+  .number-input {
+    display: flex;
+    align-items: center;
+    gap: 16rpx;
+
+    .number-btn {
+      width: 40rpx;
+      height: 40rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: #f5f5f5;
+      border-radius: 4rpx;
+      font-size: 28rpx;
+      color: #666;
+    }
+
+    .number-value {
+      min-width: 60rpx;
+      text-align: center;
+      font-size: 26rpx;
+      color: #042118;
+    }
+  }
+
+  /* 时间输入 */
+  .time-input {
+    display: flex;
+    align-items: center;
+    gap: 8rpx;
+
+    .time-input-field {
+      flex:1;
+      height: 52rpx;
+      padding: 0 16rpx;
+      font-size: 26rpx;
+      text-align: right;
+      color: #042118;
+    }
+
+    .time-unit {
+      font-size: 24rpx;
+      color: #666;
+    }
+  }
+}
+
+/* 轮灌组区域 */
+.round-groups-section {
+  margin-top: 40rpx;
+  flex:1;
+  .section-title {
+    font-size: 28rpx;
+    font-weight: 500;
+    color: #042118;
+  }
+
+  .round-groups-container {
+    margin-top: 24rpx;
+    display: flex;
+    border-radius: 12rpx;
+    flex:1;
+    /* 左侧轮灌组列表 */
+    .round-groups-list {
+      width: 120rpx;
+      background: #ffffff;
+      border-radius: 8rpx;
+
+      .round-group-item {
+        height: 80rpx;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: 26rpx;
+        color: #666;
+        border-left-top-radius: 16rpx;
+        border-left-bottom-radius: 16rpx;
+        &.active {
+          background: #0bbc581a;
+          color:#0BBC58;
+          font-weight: 700;
+          color: #0bbc58;
+          font-family: "Source Han Sans CN VF";
+        }
+
+        &:last-child {
+          border-bottom: none;
+        }
+
+        .green-text {
+          color: #14a478;
+          font-weight: 500;
+        }
+      }
+    }
+
+    /* 右侧轮灌组详情 */
+    .round-group-detail {
+      margin-left: 24rpx;
+      border-radius: 16rpx;
+      height: 600rpx;
+      overflow-y: auto;
+      .group-detail-item {
+        margin-bottom: 32rpx;
+        padding: 24rpx;
+        background: #ffffff;
+        border-radius: 8rpx;
+        &:last-child {
+          margin-bottom: 0;
+        }
+
+        .group-header {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          margin-bottom: 24rpx;
+
+          .group-title {
+            font-size: 28rpx;
+            font-weight: 500;
+            color: #042118;
+          }
+
+          .group-check {
+            width: 38rpx;
+            height: 38rpx;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background: #C9CDD4;
+            color: #ffffff;
+            border-radius: 50%;
+            font-size: 24rpx;
+            .check-icon{
+              font-size: 24rpx;
+            }
+            &.active {
+              background: #0BBC58;
+              color: #fff;
+            }
+          }
+        }
+
+        .group-settings {
+          .setting-row {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            margin-bottom: 24rpx;
+
+            .setting-row-label {
+              width: 120rpx;
+              font-size: 26rpx;
+              color: #666;
+            }
+
+            .setting-row-input {
+              flex: 1;
+              height: 56rpx;
+              padding: 0 16rpx;
+              font-size: 26rpx;
+              color: #042118;
+              margin-right: 12rpx;
+              text-align: right;
+            }
+
+            .setting-row-unit {
+              font-size: 24rpx;
+              color: #666;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/* 底部确定按钮 */
+.confirm-btn-container {
+  padding: 32rpx;
+
+  .confirm-btn {
+    height: 88rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #14a478;
+    border-radius: 12rpx;
+    box-shadow: 0 4rpx 12rpx rgba(20, 164, 120, 0.3);
+
+    .confirm-btn-text {
+      font-size: 32rpx;
+      font-weight: 500;
+      color: #fff;
+    }
+  }
+}
+</style>

+ 23 - 7
pages/cb/shuifeizsFirst/automation.vue

@@ -36,12 +36,17 @@
         </view>
       </view>
       <view class="automation-body">
-        <rotation-items :formData="formData" :tktype="tktype" />
+        <rotation-items
+          :formData="formData"
+          :tktype="tktype"
+          @formDataHandler="updateFormData"
+        />
         <view class="automation-body-title">灌区选择</view>
         <rotation-bottom
           :activeIndex="activeIndex"
           :irrigatedAreaList="irrigatedAreaList"
           :alreadyList="alreadyList"
+          @updateList="updateListHandler"
           :tktype="tktype"
         />
       </view>
@@ -108,7 +113,7 @@ export default {
       if (hasRunningTask) {
         setTimeout(() => {
           uni.redirectTo({
-            url: `/pages/cb/shuifeizs/rotationflow?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
+            url: `/pages/cb/shuifeizsFirst/rotationflow?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
           });
         }, 1000);
       } else {
@@ -118,6 +123,12 @@ export default {
     });
   },
   methods: {
+    updateListHandler(list) {
+      this.alreadyList = list;
+    },
+    updateFormData(options) {
+      this.formData[options.key] = options.value;
+    },
     async getAlreadyList() {
       const res = await this.$myRequest({
         url: '/api/v2/iot/mobile/device/sf/group/already/list/',
@@ -128,7 +139,7 @@ export default {
       });
 
       console.log(res, '-------------------- get already list');
-      res.forEach((item) => {
+      res?.forEach((item) => {
         item.isChecked = false;
       });
       this.alreadyList = res;
@@ -179,6 +190,7 @@ export default {
       uni.showLoading({
         title: '正在执行',
       });
+      console.log(payload, 'payloadpayload');
       const res = await this.$myRequest({
         url: '/api/v2/iot/mobile/device/sf/zsrf/task/ctl/',
         method: 'post',
@@ -188,13 +200,14 @@ export default {
         },
       });
       uni.hideLoading();
-      if (res.code === '000000') {
+      console.log(res, 'resresres');
+      if (res?.code === '000000') {
         uni.showToast({
           title: '操作成功',
         });
         setTimeout(() => {
           uni.redirectTo({
-            url: `/pages/cb/shuifeizs/rotationflow?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
+            url: `/pages/cb/shuifeizsFirst/rotationflow?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
           });
         }, 1000);
       } else {
@@ -205,6 +218,7 @@ export default {
       }
     },
     immediateExecution() {
+      console.log(this.formData, 'formDataformDataformData');
       if (!this.formData['lgjg']) {
         uni.showToast({
           title: '请输入轮灌间隔时间',
@@ -302,7 +316,8 @@ uni-page-wrapper {
       left: 0;
       right: 0;
       bottom: 0;
-      background: url('./assets/controlbg.png') no-repeat;
+      background: url('https://webstaticimg.oss-cn-hangzhou.aliyuncs.com/bigdata_app/newindex/controlbg.png')
+        no-repeat;
       background-size: cover;
     }
   }
@@ -317,7 +332,8 @@ uni-page-wrapper {
       left: 0;
       right: 0;
       bottom: 0;
-      background: url('./assets/controlbg.png') no-repeat;
+      background: url('https://webstaticimg.oss-cn-hangzhou.aliyuncs.com/bigdata_app/newindex/controlbg.png')
+        no-repeat;
       background-size: cover;
       transform: rotateY(180deg);
       transform-origin: center center;

+ 1 - 3
pages/cb/shuifeizsFirst/components/base.vue

@@ -28,13 +28,11 @@ export default {
   display: flex;
   flex-direction: column;
   align-items: center;
-  width: calc(100% - 64rpx);
   padding: 16rpx 0;
-  margin: 0 32rpx;
   justify-content: space-between;
   border-radius: 16rpx;
   background: linear-gradient(180deg, #edfbfb 0%, #d2f2ed 100%);
-  margin-bottom: 30rpx;
+  margin: 30rpx 0;
   .base-list {
     width: calc(100% - 32rpx);
     padding: 0 16px;

+ 128 - 22
pages/cb/shuifeizsFirst/components/facilitystate.vue

@@ -9,8 +9,8 @@
       已关闭</view>
     </view>
     <view class="facilitystate-list">
-      <image :src="ggbCurrent.value == 1?bucketOpenTop:bucketCloseTop" class="bucketOpenTop" />
-      <image :src="sfbCurrent.value == 1?bucketOpenTop:bucketCloseTop" class="bucketOpenTop2"/>
+      <image :src="ggbCurrent.value == 1?bucketOpenTop:bucketCloseTop" class="bucketOpenTop2" />
+      <image :src="sfbCurrent.value == 1?bucketOpenTop:bucketCloseTop" class="bucketOpenTop"/>
       <view class="facilitystate-list__left-label">
         <view class="facilitystate-item__left-label-text" style="margin-bottom: 2rpx;">瞬时 L/min :</view>
         <view class="facilitystate-item__left-label-text">累计L:</view>
@@ -28,32 +28,48 @@
             <view class="facilitystate-item__label-value">{{getBottomDeviceName(item.childrenList)}}</view>
           </view>
           <view class="facilitystate-item__image">
-            <image :src="getJSCurrentValue(item.childrenList)?bucketOpen:bucketClose" class="bucketClose"/>
+            <image :src="getJSCurrentValue(item.childrenList)?bucketOpen:bucketClose" class="bucketClose" v-if="isHave"/>
             <view  v-if="index == alreadyfertilizerBucketList.length - 1? false:true">
-              <image :src="lineTopBottom" class="facilitystate-item__bg"/>
-              <image :src="lineTopAndBottom" class="facilitystate-item__line"/>
+              <image :src="bottomLine" class="facilitystate-item__bg3"/>
+            <!--一条直线有下一个有waterChannel的时候应该展示-->
+              <image :src="line" class="facilitystate-item-line__bg" v-if="isHaveNextWaterChannel(index)"/>
+              <!--一条直线有下一个没有waterChannel的时候应该展示并且isHavewaterChannel=true-->
+              <image :src="lineTopBorder" class="facilitystate-item-line__bg3" v-if="!isHaveNextWaterChannel(index) && isHavewaterChannel(item.childrenList)"/>
+              <!--一条直线有下一个有waterChannel的时候应该展示并且isHavewaterChannel=true的时候展示-->
+              <image :src="lineHeight" class="facilitystate-item-lineheight__bg" v-if="isHaveNextWaterChannel(index) && isHavewaterChannel(item.childrenList)"/>
+              <image :src="bottomLine" class="facilitystate-item__bg"/>
+              <image :src="lineHeight" class="facilitystate-item__line"/>
             </view>
             <view  v-else>
-              <image :src="lineTopBottom" class="facilitystate-item__bg2"/>
-              <image :src="lastBorder" class="facilitystate-item__line2"/>
+              <image :src="lineTopBorder" class="facilitystate-item-line__bg2" v-if="isHavewaterChannel(item.childrenList)"/>
+              <image :src="lineBottomBorder" class="facilitystate-item__bg2"/>
+              <image :src="lineBottomBorder" class="facilitystate-item__bg4"/>
+              <!-- <image :src="lastBorder" class="facilitystate-item__line2"/> -->
             </view>
             <view class="facilitystate-item-fan">
               <view class="fertTopBottom">
-              <text class="fertTopBottom-text">{{item.sfDisplayname || item.sfName}}</text>
-              <image :src="getJBCurrentValue(item.childrenList)? fanRun : fan" class="fan" :class="{'fan-run': getJBCurrentValue(item.childrenList)}" />
+                <text class="fertTopBottom-text">{{item.sfDisplayname || item.sfName}}</text>
+                <image :src="getJBCurrentValue(item.childrenList)? fanRun : fan" class="fan" :class="{'fan-run': getJBCurrentValue(item.childrenList)}" />
               </view>
             </view>
             <image :src="getXSCurrentValue(item.childrenList)?bucketOpen:bucketClose" class="bucketClose1" />
           </view>
         </view>
       </view>
-      </view>
-      <irrigatedArea :irrigatedAreaList="irrigatedAreaList"/>
     </view>
+    <irrigatedArea :irrigatedAreaList="irrigatedAreaList"/>
   </view>
 </template>
 <script>
 import fertTopBottom from '../assets/fertTopBottom.png';
+import line from '../assets/line.png';
+import lineBottomBorder from '../assets/lineBottomBorder.png';
+import lineBottomBorderLittle from '../assets/lineBottomBorderLittle.png';
+import lineTopBorderLittle from '../assets/lineTopBorderLittle.png';
+import lineHeight from '../assets/lineHeight.png';
+import lineOne from '../assets/lineOne.png';
+import bottomLine from '../assets/bottomLine.png';
+import lineTopBorder from '../assets/lineTopBorder.png';
 import lineTopAndBottom from '../assets/lineTopAndBottom.png';
 import lineTopBottom from '../assets/lineTopBottom.png';
 import fertTopBottomRadius from '../assets/fertTopBottomRadius.png';
@@ -91,10 +107,18 @@ export default {
   data() {
     return {
       fan,
+      line,
       fanRun,
       lastBorder,
       lineTopBottom,
       fertTopBottom,
+      lineTopBorderLittle,
+      lineBottomBorder,
+      lineHeight,
+      lineBottomBorderLittle,
+      lineOne,
+      bottomLine,
+      lineTopBorder,
       lineTopAndBottom,
       bucketOpenTop,
       bucketOpenTopStatus:false,
@@ -103,6 +127,7 @@ export default {
       bucketClose,
       bucketOpen,
       fertTopBottomRadius,
+      isHave:false,
     };
   },
   methods:{
@@ -118,6 +143,31 @@ export default {
       })
       return current?.value || '0'
     },
+    isHavewaterChannel(childList = []){
+      let isHaveWater = false;
+      for(let i = 0;i< childList.length;i++){
+        const item = childList[i];
+        if(item.sfType === '8'){
+          isHaveWater = true
+        }
+      }
+      return isHaveWater;
+    },
+    isHaveNextWaterChannel(currentIndex){
+      let isNextHaveWater;
+      for(let i = currentIndex+1;i< this.alreadyfertilizerBucketList.length;i++){
+        // const item = this.alreadyfertilizerBucketList[i];
+        isNextHaveWater = false;
+        for(let j = i;j< this.alreadyfertilizerBucketList.length;j++){
+          const currentItem = this.alreadyfertilizerBucketList[j];
+          isNextHaveWater = this.isHavewaterChannel(currentItem.childrenList || []);
+          if(isNextHaveWater){
+            return true
+          }
+        }
+      }
+      return isNextHaveWater
+    },
     getBottomDeviceName(list){
       let current = {}
       list?.forEach(item =>{
@@ -137,6 +187,7 @@ export default {
         if(item.sfType === '8'){
           const name = item.sfCode;
           current = item;
+          this.isHave = true;
         }
       })
       return current?.value == 1
@@ -183,7 +234,7 @@ export default {
 </script>
 <style scoped lang="scss">
 .facilitystate-container {
-  padding: 16rpx 32rpx;
+  padding: 16rpx 0rpx;
   justify-content: space-between;
   border-radius: 16rpx;
   position: relative;
@@ -222,7 +273,7 @@ export default {
     }
   }
   .facilitystate-list {
-    background: url('../assets/beijing.png') no-repeat;
+    background: url('https://webstaticimg.oss-cn-hangzhou.aliyuncs.com/bigdata_app/newindex/beijing.png') no-repeat;
     background-size: 100%;
     height: 376rpx;
     width: 690rpx;
@@ -266,8 +317,9 @@ export default {
       flex-direction: column;
       justify-content: center;
       align-items: center;
-      height: 230rpx;
+      height: 228rpx;
       margin-top: 112rpx;
+      position:relative;
       &__label{
         display: flex;
         flex-direction: column;
@@ -292,7 +344,8 @@ export default {
       }
       &__image {
         width: 100%;
-        height: 138rpx;
+        height: 140rpx;
+
         position: relative;
         .facilitystate-item-fan{
           position: absolute;
@@ -322,23 +375,76 @@ export default {
           z-index:100;
         }
       }
+      .facilitystate-item-line__bg{
+        position:absolute;
+        width: 102%;
+        height: 4rpx;
+        top: 0rpx;
+      }
       &__bg{
+        position:absolute;
         width: 100%;
-        height: 153rpx;
+        left:0;
+        height: 36rpx;
+        bottom: -16rpx;
+      }
+      &__bg3{
+        position:absolute;
+        width: 100%;
+        left:0;
+        height: 36rpx;
         top: 0rpx;
+        // 旋转180度
+        transform: rotate(180deg);
+      }
+      .facilitystate-item-lineheight__bg{
+        height: 32rpx;
+        width: 4rpx;
+        position:absolute;
+        left:0;
+        right: 0;
+        margin: auto;
       }
       &__line{
         position: absolute;
         width: 8rpx;
-        height: 152rpx;
+        height: 20rpx;
         left: 50%;
-        top: 0;
+        bottom: 0;
         transform: translateX(-50%);
       }
+      .facilitystate-item-line__bg2{
+        position:absolute;
+        top: 0;
+        width:50%;
+        height: 32rpx;
+      }
+      .facilitystate-item-line__bg4{
+        position:absolute;
+        top: 0;
+        width:50%;
+        height: 32rpx;
+      }
+      .facilitystate-item-line__bg3{
+        position:absolute;
+        top: 0;
+        width:50%;
+        height: 32rpx;
+      }
       &__bg2{
-        width: 50%;
-        height: 153rpx;
-        left: -4rpx;
+        width: 55%;
+        height: 32rpx;
+        left: -6rpx;
+        position:absolute;
+        bottom: -16rpx;
+      }
+      &__bg4{
+        width: 55%;
+        height: 32rpx;
+        left: -6rpx;
+        position:absolute;
+        top: 0rpx;
+        transform: rotateX(180deg);
       }
       &__line2{
         position: absolute;
@@ -353,7 +459,7 @@ export default {
         height: 94rpx;
         margin: 0 auto;
         margin-top: 12rpx;
-        background: url('../assets/bucket.png') no-repeat center center;
+        background: url('https://webstaticimg.oss-cn-hangzhou.aliyuncs.com/bigdata_app/newindex/bucket.png')  no-repeat center center;
         background-size: 100% 100%;
         display: flex;
         flex-direction: column;

+ 15 - 4
pages/cb/shuifeizsFirst/components/irrigatedArea.vue

@@ -21,9 +21,15 @@
             <view class="irrigated-area-item-content-item-title">{{
               i.sfDisplayname || i.sfName || '暂无数据'
             }}</view>
-            <view class="irrigated-area-item-content-item-icon">
+            <view class="irrigated-area-item-content-item-icon" v-if="i.sfType != '10'">
               <image
-                :src="i.value !== '0' ? bucketOpen1 : bucketClose1"
+                :src="i.value != '0' ? bucketOpen1 : bucketClose1"
+                class="bucket-icon"
+              />
+            </view>
+            <view class="irrigated-area-item-content-item-icon" v-else>
+              <image
+                :src="i.value == '0' ? bucketStop1 : i.value == '1'? bucketOpen1 : bucketClose1"
                 class="bucket-icon"
               />
             </view>
@@ -36,6 +42,8 @@
 <script>
 import bucketOpen1 from '../assets/bucketOpen1.png';
 import bucketClose1 from '../assets/bucketClose1.png';
+import bucketStop1 from '../assets/bucketStop1.png';
+
 export default {
   props: {
     irrigatedAreaList: {
@@ -47,6 +55,7 @@ export default {
     return {
       bucketOpen1,
       bucketClose1,
+      bucketStop1
     };
   },
   methods: {
@@ -66,7 +75,7 @@ export default {
       justify-content: space-between;
       align-items: center;
       padding: 10px;
-      background: #f5f7fa;
+      background: #F5F6FA;
       margin-bottom: 8rpx;
       .irrigated-area-item-title {
         width: 120rpx;
@@ -94,7 +103,9 @@ export default {
       .irrigated-area-item-content {
         display: flex;
         width: calc(100% - 120rpx);
-        justify-content: start;
+        justify-content: flex-start;
+        flex-wrap:wrap;
+        gap:10rpx;
         .irrigated-area-item-content-item {
           width: 20%;
           .irrigated-area-item-content-item-title {

+ 29 - 2
pages/cb/shuifeizsFirst/components/rotationBottom.vue

@@ -11,7 +11,7 @@
     <view class="rotation-wraper-left">
       <view
         class="rotation-wraper-left__item"
-        v-for="(item, index) in alreadyList"
+        v-for="(item, index) in listData"
         :key="item.sfBid"
         @click="clickHandler(item, index)"
         :class="{ active: index === currentIndex }"
@@ -32,10 +32,12 @@
         :scroll-into-view="activeMenuId"
       >
         <rotationCard
-          v-for="item in alreadyList"
+          v-for="item in listData"
           :item="item"
           :tktype="tktype"
           :key="item.sfBid"
+          @actionChecked="actionCheckedHandler"
+          @changeTi="changeTiHandler"
           :id="'tab' + item.sfBid"
         />
       </scroll-view>
@@ -63,6 +65,7 @@ export default {
   },
   data() {
     return {
+      listData: [],
       borderBottomGray,
       currentIndex: 0,
       currentItem: {},
@@ -75,11 +78,35 @@ export default {
       const firstItem = this.alreadyList[0];
       this.activeMenuId = `tab${firstItem?.sfBid}`;
     },
+    alreadyList(value) {
+      this.listData = value;
+    },
   },
   components: {
     rotationCard,
   },
   methods: {
+    changeTiHandler(item) {
+      const index = this.getItemBySfBid(item.sfBid);
+      this.$set(this.listData, index, {
+        ...item,
+        ti: item.ti,
+      });
+      this.$emit('updateList', this.listData);
+    },
+    // 通过sfBid查到某个item的index
+    getItemBySfBid(sfBid) {
+      return this.listData.findIndex((item) => item.sfBid === sfBid);
+    },
+    actionCheckedHandler(item) {
+      const index = this.getItemBySfBid(item.sfBid);
+      this.$set(this.listData, index, {
+        ...item,
+        isChecked: !item.isChecked,
+      });
+      this.$emit('updateList', this.listData);
+    },
+
     clickHandler(item, index) {
       this.currentItem = item;
       this.currentIndex = index;

+ 7 - 2
pages/cb/shuifeizsFirst/components/rotationCard.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="rotation-card" @tap="actionChecked">
+  <view class="rotation-card" @click="actionChecked">
     <view class="rotation-card__header">
       {{ item.sfDisplayname || item.sfName }}
       <u-icon
@@ -27,6 +27,8 @@
           type="number"
           maxlength="6"
           :max="999999"
+          :value="item.ti"
+          @input="changeTi"
           v-model="item.ti"
           placeholder="请输入时长"
           :border="false"
@@ -55,7 +57,10 @@ export default {
   },
   methods: {
     actionChecked() {
-      this.item.isChecked = !this.item.isChecked;
+      this.$emit('actionChecked', this.item);
+    },
+    changeTi(value) {
+      this.$emit('changeTi', this.item);
     },
   },
 };

+ 2 - 1
pages/cb/shuifeizsFirst/components/rotationItems.vue

@@ -25,7 +25,7 @@
             v-if="!item.isNumber && !item.isSelect"
           />
           <text
-            style="margin-left: 16rpx"
+            style="margin-left: 16rpx; width: 80rpx"
             v-if="!item.isNumber && !item.isSelect"
             >分钟</text
           >
@@ -155,6 +155,7 @@ export default {
   methods: {
     valChange(item) {
       this.formData[item.key] = item.value;
+      this.$emit('formDataHandler', { key: [item.key], value: item.value });
     },
     actionSheetCallback(index) {
       this.formData['sfmode'] = this.actionSheetList[index].id;

+ 280 - 60
pages/cb/shuifeizsFirst/control.vue

@@ -12,42 +12,68 @@
           @click="controlClick(index)"
           :class="{ 'control-list-left-item-active': index === activeIndex }"
         >
-          <image
-            :src="borderRadius"
-            class="borderTopRadius"
-            v-if="index === activeIndex"
-          />
           {{ item.sfDisplayname }}
-          <image
-            :src="borderRadius"
-            class="borderBottomRadius"
-            v-if="index === activeIndex"
-          />
         </view>
       </view>
       <view class="control-list-right">
         <view
-          class="control-list-right-item"
           v-for="(item, index) in deviceList"
+          class="control-list-right-item"
+          :class="item.sfType == '10' ? 'control-list-right-items' : ''"
           :key="index"
         >
-          <view class="control-list-right-item-icon">
-            <image
-              :src="item.sfType == '3' ? stir : solenoidValve"
-              class="solenoid-valve"
-            />
+          <view style="display: flex; align-items: center;" v-if="item.sfType != '10'">
+            <view class="control-list-right-item-icon">
+              <image
+                :src="item.sfType == '3' ? stir : solenoidValve"
+                class="solenoid-valve"
+              />
+            </view>
+            <view class="control-list-right-item-title">{{
+              item.sfDisplayname || item.sfName
+            }}</view>
+            <view class="control-list-right-item-action">
+              <u-switch
+                :value="item.value == 1 ? true : false"
+                @change="changeSwitch(item)"
+                activeColor="#14A478"
+                size="40"
+                inactiveColor="rgb(230, 230, 230)"
+              ></u-switch>
+            </view>
           </view>
-          <view class="control-list-right-item-title">{{
-            item.sfDisplayname || item.sfName
-          }}</view>
-          <view class="control-list-right-item-action">
-            <u-switch
-              :value="item.value == 1 ? true : false"
-              @change="changeSwitch(item)"
-              activeColor="#14A478"
-              size="40"
-              inactiveColor="rgb(230, 230, 230)"
-            ></u-switch>
+          <view v-else style="width:100%">
+            <view class="control-list-right-item-icon">
+              <image
+                :src="getSrc(item)"
+                class="solenoid-valve"
+              />
+              <text style="margin-left:6rpx">
+                {{ 
+                  item.sfDisplayname || item.sfName 
+                }}
+              </text>
+            </view>
+            <view class="control-list-right-item-actions">
+             <text style="color:#999999">状态 </text>
+              <view class="control-list-right-item-actions-status">
+                <view
+                  class="status-item"
+                  :class="item.value == '1'?'status-item-open':''"
+                  @click="changeStatus(item, '1')"
+                >开</view>
+                <view
+                  class="status-item" 
+                  :class="item.value == '0'?'status-item-pause':''" 
+                  @click="changeStatus(item, '0')"
+                >停</view>
+                <view 
+                  class="status-item status-item-close-label"
+                  :class="item.value == '2'?'status-item-close':''"
+                  @click="changeStatus(item, '2')"
+                >关</view>
+              </view>
+            </view>
           </view>
         </view>
         <u-empty v-if="deviceList.length === 0" text="暂无数据"></u-empty>
@@ -59,6 +85,8 @@
 <script>
 import solenoidValve from './assets/solenoidValve.png';
 import stir from './assets/stir.png';
+import solenoidValveGray from './assets/solenoidValveGray.png';
+import solenoidValveStop from './assets/solenoidValveStop.png';
 import borderRadius from './assets/borderRadius.png';
 
 export default {
@@ -68,30 +96,144 @@ export default {
       activeIndex: 0,
       value: false,
       solenoidValve,
+      solenoidValveGray,
+      solenoidValveStop,
       stir,
       borderRadius,
       title: '控制面板',
       leftList: [],
       deviceList: [],
       devBid: '',
+      ws: null,
+      heartbeatTimer: null,
+      webSockedData: null,
+      dataArray: [],
+      info: null,
+      sfToken: '',
+      entityId: '',
+      reconnectCount: 0,
     };
   },
   methods: {
+    getSrc(item){
+      if(item.value == '1'){
+        return this.solenoidValve;
+      }else if(item.value == '0'){
+        return this.solenoidValveStop;
+      }else if(item.value == '2'){
+        return this.solenoidValveGray;
+      }
+    },
+    initWebSocket() {
+      const url = 'wss://things.ysiot.net:18080/api/ws';
+      this.ws = uni.connectSocket({
+        url: url,
+        success: () => {
+          console.log('WebSocket 连接初始化成功');
+        }
+      });
+
+      uni.onSocketOpen(() => {
+        console.log('WebSocket 已连接');
+        // 发送测试参数
+        const testParams = {
+          cmds: [
+            {
+              type: 'TIMESERIES',
+              entityType: 'DEVICE',
+              entityId: this.entityId,
+              scope: 'LATEST_TELEMETRY',
+              cmdId: 1
+            }
+          ],
+          authCmd: {
+            cmdId: 0,
+            token: this.sfToken
+          }
+        };
+        console.log('发送测试参数:', testParams);
+        uni.sendSocketMessage({
+          data: JSON.stringify(testParams)
+        });
+
+        // 心跳:每 30 秒 ping 一次
+        this.heartbeatTimer = setInterval(() => {
+          uni.sendSocketMessage({
+            data: JSON.stringify({ type: 'ping' })
+          });
+        }, 30 * 1000);
+      });
+
+      uni.onSocketMessage((evt) => {
+        try {
+          const data = JSON.parse(evt.data);
+          this.webSockedData = data;
+          // 收到实时数据后刷新右侧组件
+            if(this.dataArray.length){
+              this.mergeTwoObject(this.dataArray,this.webSockedData?.data);
+              this.initData(this.dataArray);
+            }
+        } catch (e) {
+          console.error('WebSocket 消息解析失败', e);
+        }
+      });
+
+      uni.onSocketError((e) => {
+        console.error('WebSocket 错误', e);
+      });
+
+      uni.onSocketClose(() => {
+        console.warn('WebSocket 已断开,3 秒后尝试重连');
+        clearInterval(this.heartbeatTimer);
+        this.reconnectCount = (this.reconnectCount || 0) + 1;
+        if (this.reconnectCount <= 10) {
+          setTimeout(() => this.initWebSocket(), 3000);
+        } else {
+          console.warn('WebSocket 重连次数已达上限,停止重连');
+        }
+      });
+    },
     getChecked(item) {
       return item.value == 1 ? true : false;
     },
-    async changeSwitch(item) {
-      item.value = item.value == 1 ? 0 : 1;
-      const sfCode = item.sfCode;
-      const params = {};
-      params[sfCode] = item.value;
-      const payload = {
-        data: params,
-      };
-      const data = {
-        devBid: this.devBid,
-        data: params,
+    
+    mergeTwoObject(firstObject, secondObject) {
+      // 构建 sfCode 到对象的映射,实现 O(1) 查找
+      const sfCodeMap = new Map();
+      
+      // 递归构建映射
+      const buildMap = (items) => {
+        for (const item of items) {
+          if (item.sfCode) {
+            sfCodeMap.set(item.sfCode, item);
+          }
+          if (item.childrenList && item.childrenList.length) {
+            buildMap(item.childrenList);
+          }
+        }
       };
+      
+      buildMap(firstObject);
+      
+      // 遍历 secondObject 并更新值
+      for (const key in secondObject) {
+        let newKey = '';
+        if(key.includes('GHChannelDev:ChannelParamSet')){
+          newKey = key + ':Status';
+          const item = sfCodeMap.get(newKey);
+          if (item) {
+            const params = JSON.parse(secondObject[key][0][1]);
+            this.$set(item,'value',params.Status)
+          }
+        }else{
+          const item = sfCodeMap.get(key);
+          if (item) {
+            this.$set(item,'value',secondObject[key][0][1])
+          }
+        }
+      }
+    },
+    async sfDecvtl(data){
       const res = await this.$myRequest({
         url: '/api/v2/iot/mobile/device/sf/devctl/',
         method: 'post',
@@ -108,6 +250,27 @@ export default {
         icon: 'none',
       });
     },
+    changeStatus(item,index){
+      const sfCode = item.sfCode;
+      const params = {};
+      params[sfCode] = index;
+      const data = {
+        devBid: this.devBid,
+        data: params,
+      };
+      this.sfDecvtl(data)
+    },
+    changeSwitch(item) {
+      item.value = item.value == 1 ? 0 : 1;
+      const sfCode = item.sfCode;
+      const params = {};
+      params[sfCode] = item.value;
+      const data = {
+        devBid: this.devBid,
+        data: params,
+      };
+      this.sfDecvtl(data)
+    },
     controlClick(index) {
       this.activeIndex = index;
       this.deviceList = this.leftList[this.activeIndex]?.childrenList || [];
@@ -122,14 +285,29 @@ export default {
       6	传感器	无	水肥机上的 温度,压力,流速,PH EC等监测类要素
       7	灌区    无	逻辑区域,电磁阀的分组
       */
-    async getdeviceSfStatus() {
+    async getsfrhinfo() {
       const res = await this.$myRequest({
-        url: '/api/v2/iot/mobile/device/sf/status/',
+        url: '/api/v2/iot/device/sf/info/',
         method: 'post',
         data: {
-          devBid: this.devBid,
+          devBid: String(this.devBid),
         },
       });
+      this.info = res;
+      this.sfToken = this.info?.sfToken;
+      this.entityId = this.info?.sfUuid;
+      this.closeWebSocket();
+      this.initWebSocket();
+    },
+    // 关闭 WebSocket
+    closeWebSocket() {
+      clearInterval(this.heartbeatTimer);
+      if (this.ws) {
+        uni.closeSocket();
+        this.ws = null;
+      }
+    },
+    initData(res){
       this.deviceList = [];
       const sfCurrent = [
         {
@@ -159,11 +337,23 @@ export default {
       this.leftList = [...sfCurrent, ...irrigatedAreaList];
       this.deviceList = this.leftList[this.activeIndex]?.childrenList || [];
     },
+    async getdeviceSfStatus() {
+      const res = await this.$myRequest({
+        url: '/api/v2/iot/mobile/device/sf/status/',
+        method: 'post',
+        data: {
+          devBid: this.devBid,
+        },
+      });
+      this.dataArray = res;
+      this.initData(res);
+    },
   },
   onLoad(options) {
     const { devBid } = options;
     this.devBid = devBid;
     this.getdeviceSfStatus();
+    this.getsfrhinfo();
   },
 };
 </script>
@@ -207,25 +397,11 @@ uni-page-body {
         position: relative;
       }
       .control-list-left-item-active {
-        background: #ffffff;
-        border-radius: 0 -16rpx -16rpx 0;
-      }
-      .borderTopRadius {
-        position: absolute;
-        width: 40rpx;
-        height: 40rpx;
-        right: 10rpx;
-        top: -32rpx;
-        transform: rotate(90deg);
-        z-index: 100;
-      }
-      .borderBottomRadius {
-        position: absolute;
-        z-index: 100;
-        width: 40rpx;
-        height: 40rpx;
-        bottom: -32rpx;
-        right: 10rpx;
+        background: #0bbc581a;
+        color:#0BBC58;
+        font-family: "Source Han Sans CN VF";
+        font-size: 32rpx;
+        font-weight: 700;
       }
     }
     .control-list-right {
@@ -242,6 +418,8 @@ uni-page-body {
         margin-bottom: 22rpx;
         position: relative;
         .control-list-right-item-icon {
+          display: flex;
+          align-items: center;
           .solenoid-valve {
             width: 40rpx;
             height: 40rpx;
@@ -254,6 +432,48 @@ uni-page-body {
           position: absolute;
           right: 16rpx;
         }
+        .control-list-right-item-actions {
+          display: flex;
+          align-items: center;
+          margin-top: 22rpx;
+          justify-content: space-between;
+          .control-list-right-item-actions-status {
+            display: flex;
+            align-items: center;
+            border-radius: 12rpx;
+            background: #FFF;
+            padding: 6rpx;
+            margin-left: 12rpx;
+            height: 50rpx;
+            .status-item{
+              width: 92rpx;
+              height: 50rpx;
+              line-height: 50rpx;
+              text-align: center;
+              border-radius: 8rpx;
+              background: #FFF;
+              color:#656565;
+            }
+            .status-item-close-label{
+              color:#FF5951;
+            }
+            .status-item-open{
+              background: #0BBC58;
+              color: #fff;
+            }
+            .status-item-pause{
+              background: #F8B610;
+              color: #fff;
+            }
+            .status-item-close{
+              background: #DDDFE6;
+              color: #303133;
+            }
+          }
+        }
+      }
+      .control-list-right-items{
+        height: 172rpx;
       }
     }
   }

+ 0 - 257
pages/cb/shuifeizsFirst/detail.vue

@@ -1,257 +0,0 @@
-<template>
-  <view class="facilitystate-page">
-    <custom-card>
-      <block slot="backText">
-        <view class="facilitystate-top__title">
-          <text :class="devStatus == '1' ? 'online' : 'offline'">{{
-            devStatus == '1' ? '在线' : '离线'
-          }}</text>
-          {{ devName }}
-        </view>
-      </block>
-    </custom-card>
-    <view class="facilitystate-top">
-      <view
-        class="facilitystate-top__item"
-        v-for="(item, index) in deviceList"
-        :key="index"
-        @click="nativeTo(item)"
-      >
-        <image class="item-icon" :src="item.icon" />
-        <view class="facilitystate-top__item-text">{{ item.title }}</view>
-      </view>
-    </view>
-    <Base :dataList="dataList" />
-    <facilitystate
-      :ggbCurrent="ggbCurrent"
-      :sfbCurrent="sfbCurrent"
-      :irrigatedAreaList="irrigatedAreaList"
-      :alreadyfertilizerBucketList="alreadyfertilizerBucketList"
-    />
-  </view>
-</template>
-
-<script>
-import manualControl from './assets/manualControl.png';
-import wheelIrrigation from './assets/wheelIrrigation.png';
-import timing from './assets/timing.png';
-import masterStop from './assets/masterStop.png';
-import fertilizationFormula from './assets/fertilizationFormula.png';
-import backwashControl from './assets/backwashControl.png';
-import basicSetting from './assets/basicSetting.png';
-import operatingRecord from './assets/operatingRecord.png';
-import Base from './components/base.vue';
-import facilitystate from './components/facilitystate.vue';
-export default {
-  data() {
-    return {
-      title: '水肥一体机',
-      manualControl,
-      devBid: '',
-      devName: '',
-      devStatus: '1',
-      dataList: [],
-      irrigatedAreaList: [],
-      alreadyfertilizerBucketList: [],
-      deviceList: [
-        {
-          icon: manualControl,
-          title: '手动控制',
-          url: '/pages/cb/shuifeizs/control',
-        },
-        // {
-        //   icon: wheelIrrigation,
-        //   title: '自动控制',
-        //   url: '',
-        // },
-        {
-          icon: operatingRecord,
-          title: '操作记录',
-          url: '/pages/cb/shuifeizs/history',
-        },
-        // {
-        //   icon: timing,
-        //   title: '定时计划',
-        //   url: '',
-        // },
-        // {
-        //   icon: masterStop,
-        //   title: '总停',
-        //   url: '',
-        // },
-        // {
-        //   icon: fertilizationFormula,
-        //   title: '施肥配方',
-        //   url: '',
-        // },
-        // {
-        //   icon: backwashControl,
-        //   title: '反冲洗控制',
-        //   url: '',
-        // },
-        // {
-        //   icon: basicSetting,
-        //   title: '基础设置',
-        //   url: '',
-        // },
-      ],
-      ggbCurrent: {},
-      sfbCurrent: {},
-    };
-  },
-  components: {
-    Base,
-    facilitystate,
-  },
-  onLoad(options) {
-    /*
-      0	水源	泵类	负责水源进入管道的总泵或者阀
-      1	肥料	泵类	负责肥料进入管道的总阀或者泵
-      2	吸肥	泵类	控制肥料桶出肥的阀或者泵,每个肥料桶一个
-      3	搅拌	泵类	肥料桶的搅拌电机或者泵 每个肥料桶一个
-      4	肥料桶	泵类	肥料桶,每个施肥机有多个或者没有,不一定真实存在,只是逻辑上的概念
-      5	电磁阀	无	管道最末端每个田地里控制出水的阀门
-      6	传感器	无	水肥机上的 温度,压力,流速,PH EC等监测类要素
-      7	灌区    无	逻辑区域,电磁阀的分组
-      */
-    const { devBid, devName, devStatus } = options;
-    this.devBid = devBid;
-    this.devName = devName;
-    this.devStatus = devStatus;
-    this.deviceList[0].url = `/pages/cb/shuifeizs/control?devBid=${options.devBid}`;
-    this.deviceList[
-      this.deviceList.length - 1
-    ].url = `/pages/cb/shuifeizs/history?devBid=${options.devBid}`;
-    if (devBid) {
-      this.init();
-    }
-  },
-  methods: {
-    async init() {
-      this.getdeviceSfStatus();
-      await this.getpeifangRefresh();
-      await this.getRunStatus();
-    },
-    // iot/mobile/device/sf/peifang/refresh/
-    async getpeifangRefresh() {
-      const res = await this.$myRequest({
-        url: '/api/v2/iot/mobile/device/sf/peifang/refresh/',
-        method: 'post',
-        data: {
-          devBid: this.devBid,
-        },
-      });
-    },
-    async getRunStatus() {
-      const res = await this.$myRequest({
-        url: '/api/v2/iot/mobile/device/sf/zsrf/task/run/status/',
-        method: 'post',
-        data: {
-          devBid: this.devBid,
-        },
-      });
-      const data = JSON.stringify(res || {});
-
-      if (data == '{}') {
-        this.isRun = true;
-        this.deviceList[1].url = `/pages/cb/shuifeizs/automation?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`;
-      } else {
-        this.isRun = false;
-        this.deviceList[1].url = `/pages/cb/shuifeizs/rotationflow?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`;
-      }
-    },
-    async getdeviceSfStatus() {
-      const res = await this.$myRequest({
-        url: '/api/v2/iot/mobile/device/sf/status/',
-        method: 'post',
-        data: {
-          devBid: this.devBid,
-        },
-      });
-      this.dataList = [];
-      this.irrigatedAreaList = [];
-      this.alreadyfertilizerBucketList = [];
-      res.forEach((item) => {
-        if (item.sfType === '0') {
-          this.ggbCurrent = item;
-        } else if (item.sfType === '1') {
-          this.sfbCurrent = item;
-        } else if (item.sfType === '4') {
-          this.alreadyfertilizerBucketList.push(item);
-        } else if (item.sfType === '6') {
-          this.dataList.push(item);
-        } else if (item.sfType === '7') {
-          this.irrigatedAreaList.push(item);
-        }
-      });
-    },
-    nativeTo(item) {
-      uni.navigateTo({
-        url: item.url,
-      });
-    },
-  },
-};
-</script>
-<style scoped lang="scss">
-uni-page-body {
-  position: relative;
-  height: 100%;
-}
-.online {
-  height: 32rpx;
-  line-height: 32rpx;
-  padding: 0 8rpx;
-  border-radius: 4rpx;
-  background: #14a478;
-  color: #ffffff;
-  font-family: 'Source Han Sans CN';
-  font-size: 20rpx;
-  font-weight: 500;
-  margin-right: 8rpx;
-  top: -4rpx;
-  position: relative;
-}
-.offline {
-  height: 32rpx;
-  line-height: 32rpx;
-  padding: 0 8rpx;
-  border-radius: 4rpx;
-  background: #ff4d4f;
-  color: #ffffff;
-  font-family: 'Source Han Sans CN';
-  font-size: 20rpx;
-  font-weight: 500;
-  margin-right: 8rpx;
-  top: -4rpx;
-  position: relative;
-}
-.facilitystate-page {
-  background: linear-gradient(180deg, #ffffff00 0%, #fff 23.64%, #fff 100%),
-    linear-gradient(102deg, #bfeadd 6.77%, #b8f1e7 40.15%, #b9eef5 84.02%);
-  height: 100%;
-  width: 100%;
-  overflow-x: hidden;
-  overflow-y: scroll;
-  .facilitystate-top {
-    display: grid;
-    grid-template-columns: repeat(4, 1fr);
-    text-align: center;
-    margin-top: 18rpx;
-    &__item {
-      margin-bottom: 32rpx;
-      .item-icon {
-        width: 96rpx;
-        height: 96rpx;
-      }
-    }
-    &__item-text {
-      color: #042118;
-      font-family: 'Source Han Sans CN VF';
-      font-size: 28rpx;
-      font-weight: 400;
-      margin-top: 16rpx;
-    }
-  }
-}
-</style>

+ 381 - 0
pages/cb/shuifeizsFirst/formulaSetting.vue

@@ -0,0 +1,381 @@
+<template>
+  <view class="formula-setting-container">
+    <custom-card>
+      <block slot="backText">{{ title }}</block>
+    </custom-card>
+
+    <!-- 内容区域 -->
+    <view class="content">
+      <!-- 配方列表 -->
+      <view class="formula-list">
+        <!-- 配方1 -->
+        <view class="formula-item" v-for="(recipe,index) in recipeList" :key="recipe.id">
+          <view class="formula-header">
+            <text class="formula-name">配方{{index + 1}}</text>
+          </view>
+          <view class="formula-details">
+            <view class="detail-row">
+              <text class="detail-label">目标EC</text>
+              <view class="detail-value">
+                <input
+                  v-model="recipe.ec"
+                  type="number"
+                  placeholder="请输入EC值"
+                  class="input"
+                  @blur="(e)=>peifangEditHandle(index, -1, 'ec', e.detail.value)"
+                />
+                <text class="detail-unit">us/cm</text>
+              </view>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">目标PH</text>
+              <view class="detail-value">
+                <input
+                  v-model="recipe.ph"
+                  type="number"
+                  placeholder="请输入PH值"
+                  class="input"
+                  @blur="(e)=>peifangEditHandle(index, -1, 'ph', e.detail.value)"
+                />
+              </view>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">目标水肥比</text>
+              <view class="detail-value">
+                <input
+                  v-model="recipe.wfratio"
+                  type="number"
+                  placeholder="请输入水肥比值"
+                  class="input" 
+                  @blur="(e)=>peifangEditHandle(index, -1, 'wfratio', e.detail.value)"
+                />
+              </view>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">吸肥通道比例</text>
+              <view class="detail-value">
+                <input
+                  v-model="channel.percentage"
+                  type="number"
+                  placeholder="请输入吸肥通道比例值"
+                  class="input1"
+                  v-for="(channel,channelIndex) in recipe.channels"
+                  :key="channelIndex"
+                  @blur="(e)=>peifangEditHandle(index, channelIndex, 'fertratio', e.detail.value)"
+                />
+                <text class="detail-unit">%</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      title: '配方设置',
+      devBid: '',
+      recipeList: [],
+    };
+  },
+  onLoad(options) {
+    const { devBid } = options;
+    this.devBid = devBid;
+    this.getInfoList();
+  },
+  methods: {
+    async peifangEditHandle(index, channelIndex, type, value) {
+      // 判断value 是否是数字
+      if (isNaN(value)) {
+        uni.showToast({
+          title: '请输入数字',
+          icon: 'none',
+        })
+        return;
+      }
+      let key = '';
+      if (type == 'fertratio') {
+        // 4个加一起不能超过100
+        const sum = this.recipeList[index].channels.reduce(
+          (acc, cur) => Number(acc) + Number(cur.percentage),
+          0
+        );
+        if (sum > 100) {
+        uni.showToast({
+          title: '吸肥通道比例加起来不能超过100',
+          icon: 'none',
+        })
+          // 如果超过了100,恢复原来的值
+          this.recipeList[index].channels[channelIndex].percentage = +value;
+          return;
+        }
+      }
+      if (channelIndex == -1) {
+        key = `ValveFormula:VF0${index}:${type}`;
+      } else {
+        key = `ValveFormula:VF0${index}:${type}${channelIndex}`;
+      }
+      const peifang = {
+        [key]: +value
+      };
+      const params = {
+        devBid: Number(this.devBid),
+        peifang: peifang
+      }
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/peifang/edit/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+      const resData = res
+      if (resData.code === '000000') {
+        uni.showToast({
+          title: '配方保存成功',
+          icon: 'success',
+        })
+      } else {
+        uni.showToast({
+          title: resData.msg || '配方保存失败',
+          icon: 'none',
+        })
+      }
+    },
+    async getInfoList(){
+      this.recipeList = [];
+      const params = {
+        devBid: this.devBid
+      }
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/peifang/list/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+      const resData = res || [];
+      for (let i = 0; i < resData.length; i++) {
+        const item = resData[i];
+        const recipe = {
+          ec: item[`ValveFormula:VF0${i}:ec`],
+          ph: item[`ValveFormula:VF0${i}:ph`],
+          wfratio: item[`ValveFormula:VF0${i}:wfratio`],
+          channels: []
+        };
+        if (
+          item[`ValveFormula:VF0${i}:fertratio0`] !== null &&
+          item[`ValveFormula:VF0${i}:fertratio0`] !== undefined
+        ) {
+          recipe.channels.push({
+            percentage: item[`ValveFormula:VF0${i}:fertratio0`]
+          });
+        }
+        if (
+          item[`ValveFormula:VF0${i}:fertratio1`] !== null &&
+          item[`ValveFormula:VF0${i}:fertratio1`] !== undefined
+        ) {
+          recipe.channels.push({
+            percentage: item[`ValveFormula:VF0${i}:fertratio1`]
+          });
+        }
+        if (
+          item[`ValveFormula:VF0${i}:fertratio2`] !== null &&
+          item[`ValveFormula:VF0${i}:fertratio2`] !== undefined
+        ) {
+          recipe.channels.push({
+            percentage: item[`ValveFormula:VF0${i}:fertratio2`]
+          });
+        }
+        if (
+          item[`ValveFormula:VF0${i}:fertratio3`] !== null &&
+          item[`ValveFormula:VF0${i}:fertratio3`] !== undefined
+        ) {
+          recipe.channels.push({
+            percentage: item[`ValveFormula:VF0${i}:fertratio3`]
+          });
+        }
+        this.recipeList.push(recipe);
+      }
+    },
+    // 确认按钮点击事件
+    confirm() {
+      // 这里可以添加确认逻辑
+      console.log('确认设置');
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.formula-setting-container {
+  width: 100%;
+  min-height: 100vh;
+  background: linear-gradient(180deg, #f5f6fa00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  font-family: 'Source Han Sans CN';
+  ::v-deep .u-input{
+    text-align:right !important;
+  }
+}
+.input{
+  width: 100%;
+  height: 80rpx;
+  text-align:right;
+  color:#020305;
+}
+.input1{
+  width: 25%;
+  color:#020305;
+}
+/* 顶部导航栏 */
+.nav-bar {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx 32rpx;
+  background: linear-gradient(180deg, #14a478 0%, #0f8a64 100%);
+  color: #fff;
+
+  .nav-left {
+    .back-icon {
+      font-size: 32rpx;
+    }
+  }
+
+  .nav-center {
+    .nav-title {
+      font-size: 32rpx;
+      font-weight: 500;
+    }
+  }
+
+  .nav-right {
+    display: flex;
+    gap: 24rpx;
+
+    .nav-icon {
+      font-size: 28rpx;
+    }
+  }
+}
+
+/* 内容区域 */
+.content {
+  padding: 32rpx;
+}
+
+/* 配方管理 */
+.formula-management {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: #fff;
+  padding: 24rpx;
+  border-radius: 12rpx;
+  margin-bottom: 24rpx;
+
+  .management-title {
+    font-size: 28rpx;
+    font-weight: 500;
+    color: #042118;
+  }
+
+  .add-formula-btn {
+    width: 40rpx;
+    height: 40rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: #14a478;
+    color: #fff;
+    border-radius: 50%;
+    font-size: 28rpx;
+    font-weight: bold;
+  }
+}
+
+/* 配方列表 */
+.formula-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+/* 配方项 */
+.formula-item {
+  background: #fff;
+  border-radius: 12rpx;
+  padding: 24rpx;
+
+  .formula-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 24rpx;
+
+    .formula-name {
+      font-size: 28rpx;
+      font-weight: 500;
+      color: #020305;
+    }
+
+    .delete-formula-btn {
+      width: 32rpx;
+      height: 32rpx;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      background: #f5f5f5;
+      color: #666;
+      border-radius: 50%;
+      font-size: 24rpx;
+      font-weight: bold;
+    }
+  }
+
+  .formula-details {
+    .detail-row {
+      display: flex;
+      align-items: center;
+      padding: 16rpx 0;
+      border-bottom: 1rpx solid #f0f0f0;
+
+      &:last-child {
+        border-bottom: none;
+      }
+
+      .detail-label {
+        width: 150rpx;
+        font-size: 26rpx;
+        color: #333333;
+      }
+
+      .detail-value {
+        display: flex;
+        flex:1;
+        align-items: center;
+        justify-content: flex-end;
+        height: 60rpx;
+        font-size: 26rpx;
+        color: #042118;
+        text-align: right;
+      }
+
+      .detail-unit {
+        margin-left: 8rpx;
+        font-size: 24rpx;
+        color: #333333;
+      }
+    }
+  }
+}
+
+</style>

+ 17 - 17
pages/cb/shuifeizsFirst/history.vue

@@ -40,7 +40,7 @@
             }}</view>
             <view class="form-content__item">
               <view class="item-open" v-if="item.oprecdContent == '打开'"
-                >开</view
+                >开</view
               >
               <view class="item-close" v-else>{{ item.oprecdContent }}</view>
             </view>
@@ -58,12 +58,12 @@
         <u-empty v-else style="height: 80%"
       /></view>
     </view>
-    <!-- <u-calendar
+    <u-calendar
       v-model="show"
       mode="range"
       @change="handleConfirm"
-    ></u-calendar> -->
-    <wu-calendar
+    ></u-calendar>
+    <!-- <wu-calendar
       ref="calendar"
       mode="range"
       :insert="false"
@@ -71,7 +71,7 @@
       :rangeSameDay="true"
       :itemHeight="55"
       @confirm="handleConfirm"
-    ></wu-calendar>
+    ></wu-calendar> -->
   </view>
 </template>
 <script>
@@ -108,6 +108,9 @@ export default {
       const res = await this.$myRequest({
         url: '/api/v2/iot/mobile/device/sf/op/record/list/',
         method: 'post',
+        header: {
+          'Content-Type': 'application/x-www-form-urlencoded',
+        },
         data: {
           pageSize: this.pageSize,
           pageNum: this.pageNum,
@@ -117,7 +120,7 @@ export default {
         },
         sfType: true,
       });
-      this.historyList = this.historyList.concat(res?.data);
+      this.historyList = this.historyList.concat(res.data);
       this.total = res.total;
     },
     formartDate(month = 0) {
@@ -131,8 +134,8 @@ export default {
       return `${year}-${monthStr}-${dayStr}`;
     },
     handleCalendarClick() {
-      // this.show = true;
-      this.$refs.calendar.open();
+      this.show = true;
+      // this.$refs.calendar.open();
     },
     handleClose() {
       this.show = false;
@@ -144,15 +147,12 @@ export default {
       }
     },
     handleConfirm(e) {
-      const range = e.range;
-      const { before, after } = range;
-      if (!before || !after) {
-        return;
-      }
+      const startDate = e.startDate;
+      const endDate = e.endDate;
       this.pageNum = 1;
       this.historyList = [];
-      this.startTime = before;
-      this.endTime = after;
+      this.startTime = startDate;
+      this.endTime = endDate;
       this.getHistory();
     },
   },
@@ -177,7 +177,7 @@ uni-page-body {
   opacity: 0.6;
 }
 .scroll-container {
-  height: calc(100vh - 320rpx);
+  height: calc(100vh - 420rpx);
 }
 .history-container {
   background: linear-gradient(180deg, #ffffff00 0%, #fff 23.64%, #fff 100%),
@@ -223,7 +223,7 @@ uni-page-body {
   }
   .history-form-container {
     width: calc(100% - 64rpx);
-    height: calc(100vh - 260rpx);
+    height: calc(100vh - 300rpx);
     margin: 32rpx;
     border-radius: 16rpx;
     background: #fff;

+ 2 - 2
pages/cb/shuifeizsFirst/rotationflow.vue

@@ -201,7 +201,7 @@ export default {
         setTimeout(() => {
           uni.hideLoading();
           uni.redirectTo({
-            url: `/pages/cb/shuifeizs/detail?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
+            url: `/pages/cb/shuifeizsFirst/shuifeizs?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
           });
         }, 1000);
         return;
@@ -250,7 +250,7 @@ export default {
         });
         setTimeout(() => {
           uni.navigateTo({
-            url: `/pages/cb/shuifeizs/detail?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
+            url: `/pages/cb/shuifeizsFirst/shuifeizs?devBid=${this.devBid}&devName=${this.devName}&devStatus=${this.devStatus}`,
           });
         }, 1000);
       } else {

+ 738 - 0
pages/cb/shuifeizsFirst/shuifeizs.vue

@@ -0,0 +1,738 @@
+<template>
+  <view class="facilitystate-page">
+    <custom-card>
+      <block slot="backText">
+        <view class="facilitystate-top__title">
+          <text :class="devStatus == '1' ? 'online' : 'offline'">{{
+            devStatus == '1' ? '在线' : '离线'
+          }}</text>
+          {{ devName }}
+        </view>
+      </block>
+    </custom-card>
+    <view class="facilitystate-top">
+      <view
+        class="facilitystate-top__item"
+        v-for="(item, index) in deviceList"
+        :key="index"
+        @click="nativeTo(item)"
+      >
+        <view class="item-icon-container" :class="item.className">
+          <image class="item-icon" :src="item.icon" />
+        </view>
+        <view class="facilitystate-top__item-text">{{ item.title }}</view>
+      </view>
+    </view>
+    <view class="irrMode">
+      <view class="irrMode__header">
+        <view @click="changeMode('1')" :class="workStatus == '1' ? 'active' : ''">施肥模式</view>
+        <view @click="changeMode('0')" :class="workStatus == '0' ? 'active' : ''">灌溉模式</view>
+      </view>
+      <view class="irrMode__content">
+        <view class="irrMode__content-header">
+          <view class="irrMode__content-header-list">
+            <view class="irrMode__content-header-title">
+              <view class="irrMode__content-header-container">
+                <image class="header-item-icon" :src="rain" />
+                <text style="color:#333;font-size: 24rpx;margin-left: 10rpx">当前灌溉区域:</text>{{ runTitle }}
+                </view>
+              <view class="timer-text">{{ timer }}分</view>
+            </view>
+          </view>
+          <view class="irrMode__content-item">
+            <view class="irrMode__content-item-raduis" :class="runStatus == '1' ? 'active-radius' : ''" @click="changeActive('1')">
+              <image class="item-icon" :src="runStatus=='1'?mediaPlayFill:mediaPlay" />
+              <view class="item-text">启动</view>
+            </view>
+            <view class="irrMode__content-item-raduis" :class="runStatus == '2' ? 'active-radius' : ''" @click="changeActive('2')">
+              <image class="item-icon" :src="runStatus=='2'?mediaPauseFill:mediaPause" />
+              <view class="item-text">暂停</view>
+            </view>
+            <view class="irrMode__content-item-raduis" :class="runStatus == '0' ? 'active-radius' : ''" @click="changeActive('0')" style="margin-right:0">
+              <image class="item-icon" :src="runStatus=='0'?stopFill:stop" />
+              <view class="item-text">停止</view>
+            </view>
+          </view>
+        <view class="irr-run-list">
+          <text v-for="(item, index) in selectAreaList" :key="index">
+            {{ item.group_name }}
+            <text v-if="index != selectAreaList.length - 1">、</text>
+          </text>
+        </view>
+        </view>
+      </view>
+      <Base :dataList="dataList" />
+      <facilitystate
+        :devBid="devBid"
+        :ggbCurrent="ggbCurrent"
+        :sfbCurrent="sfbCurrent"
+        :irrigatedAreaList="irrigatedAreaList"
+        :webSockedData="webSockedData"
+        :alreadyfertilizerBucketList="alreadyfertilizerBucketList"
+      />
+    </view>
+  </view>
+</template>
+
+<script>
+import manualControl from './assets/manualControl.png';
+import mediaPause from './assets/media-pause.png';
+import mediaPauseFill from './assets/media-pause-fill.png';
+import mediaPlay from './assets/media-play.png';
+import mediaPlayFill from './assets/media-play-fill.png';
+import rain from './assets/rain.png';
+import stop from './assets/stop.png';
+import stopFill from './assets/stop-fill.png';
+import wheelIrrigation from './assets/wheelIrrigation.png';
+import time from './assets/timing.png';
+import formulaSetting from './assets/formulaSetting.png';
+import operatingRecord from './assets/operatingRecord.png';
+import Base from './components/base.vue';
+import facilitystate from './components/facilitystate.vue';
+export default {
+  data() {
+    return {
+      ws: null,
+      currentMode: '1',
+      title: '水肥一体机',
+      manualControl,
+      devBid: '',
+      devName: '',
+      devStatus: '1',
+      dataList: [],
+      irrigatedAreaList: [],
+      alreadyfertilizerBucketList: [],
+      mediaPause,
+      mediaPauseFill,
+      mediaPlay,
+      mediaPlayFill,
+      rain,
+      stop,
+      stopFill,
+      deviceList: [
+        {
+          icon: manualControl,
+          title: '手动控制',
+          className: 'manualControl',
+          url: '/pages/cb/shuifeizsFirst/control',
+        },
+        {
+          icon: wheelIrrigation,
+          title: '自动控制',
+          className: 'wheelIrrigation',
+          url: '/pages/cb/shuifeizsFirst/autoSetting?devBid=' + this.devBid,
+        }, {
+          icon: time,
+          title: '定时设置',  
+          className: 'timedSetting',
+          url: '/pages/cb/shuifeizsFirst/timingSetting?devBid=' + this.devBid,
+        },{
+          icon: formulaSetting,
+          title: '配方设置',  
+          className: 'formulaSetting',
+          url: '/pages/cb/shuifeizsFirst/formulaSetting?devBid=' + this.devBid,
+        }, {
+          icon: operatingRecord,
+          title: '操作记录',  
+          className: 'operatingRecord',
+          url: '/pages/cb/shuifeizsFirst/history?devBid=' + this.devBid,
+        },
+      ],
+      ggbCurrent: {},
+      sfbCurrent: {},
+      sfToken: '',
+      entityId: '',
+      heartbeatTimer: null,
+      info: {},
+      dataArray:[],
+      webSockedData: {},
+      reconnectCount: 0,
+      isActive: '0',
+      runStatus: -1,
+      workStatus: -1,
+      clearIrr: {},
+      selectAreaList: [],
+      runTitle: '--',
+      timer:'--'
+    };
+  },
+  components: {
+    Base,
+    facilitystate,
+  },
+  onLoad(options) {
+    /*
+      0	水源	泵类	负责水源进入管道的总泵或者阀
+      1	肥料	泵类	负责肥料进入管道的总阀或者泵
+      2	吸肥	泵类	控制肥料桶出肥的阀或者泵,每个肥料桶一个
+      3	搅拌	泵类	肥料桶的搅拌电机或者泵 每个肥料桶一个
+      4	肥料桶	泵类	肥料桶,每个施肥机有多个或者没有,不一定真实存在,只是逻辑上的概念
+      5	电磁阀	无	管道最末端每个田地里控制出水的阀门
+      6	传感器	无	水肥机上的 温度,压力,流速,PH EC等监测类要素
+      7	灌区    无	逻辑区域,电磁阀的分组
+      */
+    const { devBid, devName, devStatus } = options;
+    this.devBid = devBid;
+    this.devName = devName;
+    this.devStatus = devStatus;
+    this.deviceList[0].url = `/pages/cb/shuifeizsFirst/control?devBid=${options.devBid}`;
+    this.deviceList[1].url = `/pages/cb/shuifeizsFirst/autoSetting?devBid=${options.devBid}`;
+    this.deviceList[2].url = `/pages/cb/shuifeizsFirst/timingSetting?devBid=${options.devBid}`;
+    this.deviceList[3].url = `/pages/cb/shuifeizsFirst/formulaSetting?devBid=${options.devBid}`;
+    this.deviceList[
+      this.deviceList.length - 1
+    ].url = `/pages/cb/shuifeizsFirst/history?devBid=${options.devBid}`;
+    if (devBid) {
+      this.init();
+    }
+  },
+  onUnload() {
+    this.ws.close();
+  },
+  onShow(){
+    if (this.devBid) {
+      this.init();
+    }
+  },
+  methods: {
+    isHaveTime(childrenList) {
+      let time = 0;
+      let currentKey = '';
+      let currentItem = {};
+      for (let i = 0; i < this.selectAreaList.length; i++) {
+        for (let key in this.selectAreaList[i]) {
+          if (key.includes(':Status')) {
+            currentKey = key;
+            currentItem = this.selectAreaList[i];
+            break;
+          }
+        }
+        for (let i = 0; i < childrenList.length; i++) {
+          const item = childrenList[i];
+          if (item.sfCode === currentKey) {
+            for (let key in currentItem) {
+              if (key.includes(':PartTim')) {
+                time = currentItem[key];
+              }
+            }
+            break;
+          }
+        }
+      }
+      return time;
+    },
+    isAutoRun(childrenList) {
+      for (let i = 0; i < childrenList.length; i++) {
+        const item = childrenList[i];
+        if (item.value == 1) {
+          return true
+        }
+      }
+      return false
+    },
+    async initsfyunshangAutoConfigInfo() {
+      const selectAreaList = [];
+      const res = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/auto/config/info/',
+        method:'post',
+        data: {
+          devBid: String(this.devBid),
+        },
+      })
+      const list = res;
+      const group_list = list.group_list || [];
+      group_list.forEach((item) => {
+        if (item.group_value == 1) {
+          selectAreaList.push(item);
+        }
+      });
+      this.selectAreaList = selectAreaList;
+    },
+    async devctlContorl(data){
+      const params = {
+        devBid: Number(this.devBid),
+        data,
+      };
+      const res = await this.$myRequest({
+        url: '/api/v2/iot/mobile/device/sf/devctl/',
+        method: 'post',
+        data:params,
+        header: {
+          'Content-Type': 'application/json',
+        },
+      });
+      uni.showToast({
+        title: res.msg,
+        icon: 'none',
+      });
+    },
+    changeActive(active) {
+      this.runStatus = active;
+      const params = {
+        IrrStatus:active
+      }
+      this.devctlContorl(params);
+    },
+    changeMode(mode) {
+      if(this.runStatus == '1' || this.runStatus == '2'){
+        uni.showToast({
+          title: '请先停止运行',
+          icon: 'none',
+        });
+        return
+      }
+      this.currentMode = mode;
+      const params = {
+        IrrMode:mode
+      }
+      this.devctlContorl(params);
+    },
+    initWebSocket() {
+      const url = 'wss://things.ysiot.net:18080/api/ws';
+      this.ws = uni.connectSocket({
+        url: url,
+        success: () => {
+          console.log('WebSocket 连接初始化成功');
+        }
+      });
+
+      uni.onSocketOpen(() => {
+        console.log('WebSocket 已连接');
+        // 发送测试参数
+        const testParams = {
+          cmds: [
+            {
+              type: 'TIMESERIES',
+              entityType: 'DEVICE',
+              entityId: this.entityId,
+              scope: 'LATEST_TELEMETRY',
+              cmdId: 1
+            }
+          ],
+          authCmd: {
+            cmdId: 0,
+            token: this.sfToken,
+          }
+        };
+        console.log('发送测试参数:', testParams);
+        uni.sendSocketMessage({
+          data: JSON.stringify(testParams)
+        });
+
+        // 心跳:每 30 秒 ping 一次
+        this.heartbeatTimer = setInterval(() => {
+          uni.sendSocketMessage({
+            data: JSON.stringify({ type: 'ping' })
+          });
+        }, 30 * 1000);
+      });
+
+      uni.onSocketMessage((evt) => {
+        try {
+          const data = JSON.parse(evt.data);
+          this.webSockedData = data;
+          // 收到实时数据后刷新右侧组件
+            if(this.dataArray.length){
+              this.mergeTwoObject(this.dataArray,this.webSockedData?.data);
+              this.initData(this.dataArray);
+            }
+        } catch (e) {
+          console.error('WebSocket 消息解析失败', e);
+        }
+      });
+
+      uni.onSocketError((e) => {
+        console.error('WebSocket 错误', e);
+      });
+
+      uni.onSocketClose(() => {
+        console.warn('WebSocket 已断开,3 秒后尝试重连');
+        clearInterval(this.heartbeatTimer);
+        this.reconnectCount = (this.reconnectCount || 0) + 1;
+        if (this.reconnectCount <= 10) {
+          setTimeout(() => this.initWebSocket(), 3000);
+        } else {
+          console.warn('WebSocket 重连次数已达上限,停止重连');
+        }
+      });
+    },
+    
+    
+    mergeTwoObject(firstObject, secondObject) {
+      // 构建 sfCode 到对象的映射,实现 O(1) 查找
+      const sfCodeMap = new Map();
+      
+      // 递归构建映射
+      const buildMap = (items) => {
+        for (const item of items) {
+          if (item.sfCode) {
+            sfCodeMap.set(item.sfCode, item);
+          }
+          if (item.childrenList && item.childrenList.length) {
+            buildMap(item.childrenList);
+          }
+        }
+      };
+      
+      buildMap(firstObject);
+      
+      // 遍历 secondObject 并更新值
+      for (const key in secondObject) {
+        let newKey = '';
+        if(key.includes('GHChannelDev:ChannelParamSet')){
+          newKey = key + ':Status';
+          const item = sfCodeMap.get(newKey);
+          if (item) {
+            const params = JSON.parse(secondObject[key][0][1]);
+            this.$set(item,'value',params.Status)
+          }
+        }else{
+          const item = sfCodeMap.get(key);
+          if (item) {
+            this.$set(item,'value',secondObject[key][0][1])
+          }
+        }
+      }
+    },
+    // 关闭 WebSocket
+    closeWebSocket() {
+      clearInterval(this.heartbeatTimer);
+      if (this.ws) {
+        uni.closeSocket();
+        this.ws = null;
+      }
+    },
+    async getsfrhinfo() {
+      const res = await this.$myRequest({
+        url: '/api/v2/iot/device/sf/info/',
+        method: 'post',
+        data: {
+          devBid: String(this.devBid),
+        },
+      });
+      this.info = res;
+      this.sfToken = this.info?.sfToken;
+      this.entityId = this.info?.sfUuid;
+      this.closeWebSocket();
+      this.initWebSocket();
+    },
+    async init() {
+      this.getdeviceSfStatus();
+      await this.getpeifangRefresh();
+      this.getsfrhinfo();
+      this.initsfyunshangAutoConfigInfo();
+      await this.getRunStatus();
+    },
+    async getpeifangRefresh() {
+      await this.$myRequest({
+        url: '/api/v2/iot/mobile/device/sf/peifang/refresh/',
+        method: 'post',
+        data: {
+          devBid: this.devBid,
+        },
+      });
+    },
+    async getRunStatus() {
+      const res = await this.$myRequest({
+        url: '/api/v2/iot/mobile/device/sf/zsrf/task/run/status/',
+        method: 'post',
+        data: {
+          devBid: this.devBid,
+        },
+      });
+      const data = JSON.stringify(res || {});
+
+      if (data == '{}') {
+        this.isRun = true;
+      } else {
+        this.isRun = false;
+      }
+    },
+    initData(res){
+      this.dataList = [];
+      this.irrigatedAreaList = [];
+      this.alreadyfertilizerBucketList = [];
+      res?.forEach((item) => {
+        if (item.sfType === '0') {
+          this.ggbCurrent = item;
+        } else if (item.sfType === '1') {
+          this.sfbCurrent = item;
+        } else if (item.sfType === '4') {
+          this.alreadyfertilizerBucketList.push(item);
+        } else if (item.sfType === '6') {
+          this.dataList.push(item);
+        } else if (item.sfType === '7') {
+          this.irrigatedAreaList.push(item);
+        } else if (item.sfType === '9') {
+          if (item.sfCode === 'IrrStatus') {
+            this.runStatus = item.value;
+          } else if (item.sfCode === 'IrrMode') {
+            this.workStatus = item.value;
+          } else if (item.sfCode === 'WaterFlowSum:Value') {
+            this.clearIrr = item;
+          }
+        }
+      });
+      this.runTitle = '--';
+      this.timer = '--';
+      if(this.runStatus == '1'){
+        this.irrigatedAreaList.forEach((item) => {
+          if (this.isAutoRun(item.childrenList)) {
+            this.runTitle = item.sfDisplayname  || item.sfName;
+            this.timer = this.isHaveTime(item.childrenList);
+          }
+        });
+      }else{
+        this.runTitle = '--';
+        this.timer = '--';
+      }
+    },
+    async getdeviceSfStatus() {
+      const res = await this.$myRequest({
+        url: '/api/v2/iot/mobile/device/sf/status/',
+        method: 'post',
+        data: {
+          devBid: this.devBid,
+        },
+      });
+      this.dataArray = res || [];
+      this.initData(this.dataArray);
+    },
+    nativeTo(item) {
+      if(item.className == 'manualControl'){
+        if(this.runStatus == '1' || this.runStatus == '2'){
+          uni.showToast({
+            title: '请先停止运行',
+            icon: 'none',
+          });
+          return
+        }
+      }
+      uni.navigateTo({
+        url: item.url,
+      });
+    },
+  },
+};
+</script>
+<style scoped lang="scss">
+uni-page-body {
+  position: relative;
+  height: 100%;
+}
+.irrMode{
+  margin-top: 30rpx;
+  border-radius: 32rpx 32rpx 0 0;
+  margin-bottom: 30rpx;
+  padding: 32rpx;
+  border: 3rpx solid #FFF;
+  background: linear-gradient(180deg, #D3F3F2 0%, #FFF 41.06%);
+  .irrMode__header{
+    display: flex;
+    width:320rpx;
+    padding:0 10rpx;
+    align-items: center;
+    background: #ffffff;
+    border-radius: 12rpx;
+    margin-bottom: 24rpx;
+    font-family: "Source Han Sans CN VF";
+    font-size: 28rpx;
+    font-style: normal;
+    font-weight: 400;
+    line-height: normal;
+    color:#999999;
+    height: 68rpx;
+    line-height: 68rpx;
+    view{
+      width: 160rpx;
+      text-align: center;
+    }
+  }
+  .active{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8rpx;
+    color: #ffffff;
+    background: #0BBC58; 
+    height: 56rpx;
+  }
+  .irrMode__content{
+    margin-top: 24rpx;
+    border-radius: 16rpx;
+    padding: 2rpx;
+    background: linear-gradient(180deg, #FFF 0%, #F5F6FA 100%);
+    .irrMode__content-header{
+      width: calc(100% - 32rpx);
+      padding: 16rpx 16rpx 16rpx 16rpx;
+      border-radius: 12rpx;
+      background: linear-gradient(180deg, #BBF1EA 0%, #E6F4F9 100%);
+      .irrMode__content-header-list{
+        display:flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 12rpx;
+      }
+      .irrMode__content-header-title{
+        color: #020305;
+        text-align: right;
+        font-family: "Source Han Sans CN VF";
+        font-size: 30rpx;
+        font-weight: 500;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        width: 100%;
+        .irrMode__content-header-container{
+          display: flex;
+          align-items: center;
+        }
+        .header-item-icon{
+          width: 48rpx;
+          height: 48rpx;
+        }
+        .timer-text{
+          color: #0bbc58;
+          text-align: right;
+          font-family: "Source Han Sans CN VF";
+          font-size: 28rpx;
+          font-weight: 400;
+        }
+      }
+      .irrMode__content-header-desc{
+        color: #999999;
+        text-align: right;
+        font-family: "Source Han Sans CN VF";
+        font-size: 28rpx;
+        font-weight: 400;
+      }
+    }
+    .irrMode__content-item{
+      display:flex;
+      align-items: center;
+      justify-content: center;
+      padding: 16rpx 0;
+      border-radius: 12rpx;
+      .irrMode__content-item-raduis{
+        display: flex;
+        height: 88rpx;
+        padding: 0rpx 32rpx;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+        flex: 1 0 0;
+        border-radius: 16rpx;
+        margin-right: 16rpx;
+        border: 2rpx solid #0BBC58;
+        color: #0bbc58;
+        text-align: right;
+        font-family: "Source Han Sans CN VF";
+        font-size: 20rpx;
+        image{
+          width: 48rpx;
+          height: 48rpx;
+        }
+      }
+      .active-radius{
+        background: #0BBC58;
+        color:#ffffff;
+      }
+    }
+    .irr-run-list{
+      padding: 16rpx;
+      font-family: "Source Han Sans CN VF";
+      color: #999999;
+      font-family: "Source Han Sans CN VF";
+      font-size: 24rpx;
+      font-weight: 400;
+    }
+  }
+}
+.online {
+  height: 32rpx;
+  line-height: 32rpx;
+  padding: 0 8rpx;
+  border-radius: 4rpx;
+  background: #14a478;
+  color: #ffffff;
+  font-family: 'Source Han Sans CN';
+  font-size: 20rpx;
+  font-weight: 500;
+  margin-right: 8rpx;
+  top: -4rpx;
+  display:inline-block;
+  position: relative;
+}
+.manualControl{
+  background: linear-gradient(326deg, #FFAB3D 6.86%, #FED55A 93.52%);
+}
+.wheelIrrigation{
+  background: linear-gradient(152deg, #83D2FF 14.82%, #02A2FC 92.92%);
+}
+.timedSetting{
+  background: linear-gradient(335deg, #0BBC58 6.91%, #60D799 89.95%);
+}
+.formulaSetting{
+  background: linear-gradient(147deg, #FE9797 12.77%, #FF6060 92.63%);
+}
+.operatingRecord{
+  background: linear-gradient(152deg, #B9B9F6 11.22%, #8080F8 94.92%);
+}
+.facilitystate-top__title{
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+}
+.offline {
+  height: 32rpx;
+  line-height: 32rpx;
+  padding: 0 8rpx;
+  border-radius: 4rpx;
+  background: #ff4d4f;
+  color: #ffffff;
+  font-family: 'Source Han Sans CN';
+  font-size: 20rpx;
+  font-weight: 500;
+  margin-right: 8rpx;
+  top: -4rpx;
+  display:inline-block;
+  position: relative;
+}
+.facilitystate-page {
+  background: linear-gradient(180deg, #f5f6fa00 0%, #F5F6FA 23.46%, #FFF 25.24%, #FFF 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  height: 100%;
+  width: 100%;
+  overflow-x: hidden;
+  overflow-y: scroll;
+  .facilitystate-top {
+    display: grid;
+    grid-template-columns: repeat(5, 1fr);
+    text-align: center;
+    margin-top: 18rpx;
+    &__item {
+      margin-bottom: 32rpx;
+      .item-icon-container{
+        width: 96rpx;
+        height: 96rpx;
+        border-radius: 26rpx;
+        overflow: hidden;
+        margin: 0 auto;
+        display:flex;
+        align-items: center;
+        justify-content: center;
+      }
+      .item-icon {
+        width: 64rpx;
+        height: 64rpx;
+      }
+    }
+    &__item-text {
+      color: #042118;
+      font-family: 'Source Han Sans CN VF';
+      font-size: 28rpx;
+      font-weight: 400;
+      margin-top: 16rpx;
+    }
+  }
+}
+</style>

+ 302 - 0
pages/cb/shuifeizsFirst/timingSetting.vue

@@ -0,0 +1,302 @@
+<template>
+  <view class="timing-setting-page">
+    <custom-card>
+      <block slot="backText">定时设置</block>
+    </custom-card>
+
+    <view class="timing-content">
+      <!-- 定时卡片列表 -->
+      <view
+        class="timer-card"
+        v-for="(timer, index) in timerList"
+        :key="index"
+      >
+        <!-- 定时标题和开关 -->
+        <view class="timer-header">
+          <text class="timer-title">定时{{ index + 1 }}</text>
+          <u-switch
+            size="40"
+            space="4"
+            activeColor="#0BBC58"
+            inactiveColor="#C3CAD8"
+            v-model="timer.enabled"
+            @change="onSwitchChange($event, index, timer)"
+          />
+        </view>
+
+        <!-- 单选按钮组 -->
+        <view class="radio-group">
+          <view
+            class="radio-item"
+            :class="{ active: timer.frequency === 'once' }"
+            @click="selectFrequency(index, 'once', timer)"
+          >
+            <view class="radio-icon">
+              <view v-if="timer.frequency === 'once'" class="radio-dot"></view>
+            </view>
+            <text class="radio-text">仅一次</text>
+          </view>
+          <view
+            class="radio-item"
+            style="margin-left: 48rpx;"
+            :class="{ active: timer.frequency === 'daily' }"
+            @click="selectFrequency(index, 'daily', timer)"
+          >
+            <view class="radio-icon">
+              <view v-if="timer.frequency === 'daily'" class="radio-dot"></view>
+            </view>
+            <text class="radio-text">每天一次</text>
+          </view>
+        </view>
+        <!-- 时间选择器 -->
+        <picker
+          mode="time"
+          :value="timer.time"
+          @change="onTimeChange($event, index)"
+        >
+          <view class="time-picker">
+            <text class="time-text" :class="{ placeholder: !timer.time }">
+              {{ timer.timeLabel }}
+            </text>
+            <u-icon name="clock" color="#999999" size="32rpx" />
+          </view>
+        </picker>
+      </view>
+
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      devBid:'',
+      timerList: [],
+    };
+  },
+  onLoad(options) {
+    const { devBid } = options;
+    this.devBid = devBid;
+    this.getTimerList();
+  },
+  methods: {
+    // 编辑
+    async editInfo(data){
+      const params = {
+        devBid: parseInt(this.devBid),
+        data: data,
+      };
+      const resData = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/timer/edit/',
+        method:'POST',
+        data: params,
+        header: {
+          'Content-Type': 'application/json',
+          'accept': 'application/json, text/plain, */*'
+        }
+      })
+      console.log(resData,'resDataresData')
+      if(resData.code === '000000'){
+        uni.showToast({
+          title: '保存成功',
+          icon: 'success',
+        })
+      }
+    },
+    async getTimerList(){
+      const resData = await this.$myRequest({
+        url:'/api/v2/iot/device/sf/yunshang/timer/list/',
+        method:'POST',
+        data: {
+          devBid: this.devBid,
+        },
+      })
+      for (let i = 0; i < resData.length; i++) {
+        const item = resData[i];
+        const time = item[`Timer0${i}:TimerHM`];
+        const hour = time.slice(0, 2);
+        const minute = time.slice(2, 4);
+        item.enabled = item[`Timer0${i}:TimerEn`] == 1;
+        item.frequency = item[`Timer0${i}:Mode`] == 1 ? 'daily' : 'once';
+        item.time = hour + ':' + minute;
+        item.timeLabel = hour + ':' + minute;
+      }
+      this.timerList = resData;
+    },
+    // 开关状态改变
+    onSwitchChange(e, index,timer) {
+      console.log(timer,e,'开关状态改变')
+      this.timerList[index].enabled = e;
+      this.editInfo({
+        [`Timer0${index}:TimerEn`]: e ? 1 : 0,
+      });
+    },
+
+    // 选择频率
+    selectFrequency(index, frequency) {
+      this.timerList[index].frequency = frequency;
+      this.editInfo({
+        [`Timer0${index}:Mode`]: frequency === 'daily' ? 1 : 0,
+      });
+    },
+
+    // 时间改变
+    onTimeChange(e, index) {
+      console.log(e.detail.value,'时间改变')
+      this.timerList[index].time = e.detail.value;
+      this.timerList[index].timeLabel = e.detail.value;
+      this.editInfo({
+        [`Timer0${index}:TimerHM`]: e.detail.value.slice(0, 2) + e.detail.value.slice(3, 5),
+      });
+    },
+
+    // 确认按钮
+    handleConfirm() {
+      const enabledTimers = this.timerList.filter((timer, index) => {
+        if (timer.enabled) {
+          if (!timer.time) {
+            uni.showToast({
+              title: `定时${index + 1}请选择时间`,
+              icon: 'none'
+            });
+            return false;
+          }
+          return true;
+        }
+        return false;
+      });
+
+      console.log('保存的定时设置:', enabledTimers);
+
+      uni.showToast({
+        title: '保存成功',
+        icon: 'success'
+      });
+
+      // TODO: 调用接口保存定时设置
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+uni-page-body {
+  position: relative;
+  height: 100%;
+  overflow-y: auto;
+}
+
+.timing-setting-page {
+  min-height: 100%;
+  background: linear-gradient(180deg, #f5f6fa00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  overflow-x: hidden;
+  overflow-y: auto;
+
+  .right-icons {
+    display: flex;
+    align-items: center;
+  }
+
+  .timing-content {
+    height: 100%;
+    padding: 24rpx 32rpx;
+    overflow-y: auto;
+  }
+
+  // 定时卡片
+  .timer-card {
+    background: #ffffff;
+    border-radius: 12rpx;
+    padding: 24rpx;
+    margin-bottom: 20rpx;
+
+    // 卡片头部
+    .timer-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 24rpx;
+
+      .timer-title {
+        font-size: 32rpx;
+        font-weight: 600;
+        color: #333333;
+        font-family: 'Source Han Sans CN';
+      }
+
+      .timer-switch {
+        transform: scale(0.9);
+      }
+    }
+
+    // 单选按钮组
+    .radio-group {
+      display: flex;
+      margin-bottom: 24rpx;
+
+      .radio-item {
+        display: flex;
+        align-items: center;
+        margin-right: 48rpx;
+        cursor: pointer;
+
+        .radio-icon {
+          width: 36rpx;
+          height: 36rpx;
+          border: 4rpx solid #cccccc;
+          border-radius: 50%;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-right: 12rpx;
+          box-sizing: border-box;
+
+          .radio-dot {
+            width: 18rpx;
+            height: 18rpx;
+            background: #00c853;
+            border-radius: 50%;
+          }
+        }
+
+        &.active {
+          .radio-icon {
+            border-color: #00c853;
+          }
+
+          .radio-text {
+            color: #333333;
+          }
+        }
+
+        .radio-text {
+          font-size: 28rpx;
+          color: #999999;
+          font-family: 'Source Han Sans CN';
+        }
+      }
+    }
+
+    // 时间选择器
+    .time-picker {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 10rpx 24rpx;
+      background: #F5F6FA;
+      border-radius: 8rpx;
+
+      .time-text {
+        font-size: 28rpx;
+        color: #333333;
+        font-family: 'Source Han Sans CN';
+
+        &.placeholder {
+          color: #999999;
+        }
+      }
+    }
+  }
+}
+</style>

+ 7 - 0
pages/equipList/index.vue

@@ -629,6 +629,13 @@ export default {
             url: `../cb/wenshizs/detail?devBid=${item.devBid}&devName=${item.device_name}&devStatus=${item.devStatus}`,
           });
           break;
+        case 45:
+          item.addtime = item.uptime;
+          console.log(item, 'itemitmeimtie');
+          uni.navigateTo({
+            url: `../cb/shuifeizsFirst/shuifeizs?devBid=${item.d_id}&devName=${item.device_name}&devStatus=${item.is_online}`,
+          });
+          break;
         case 46:
           uni.navigateTo({
             url: `../deviceDetails/weatherStation/index?devBid=${item.imei}&devName=${item.device_name}&devStatus=${item.is_online}&address=${item.address}&uptime=${item.uptime}&d_id=${item.d_id}&deviceType=46`,