riku
2024-10-30 d4e7c11e06b643c9353444c839cec40c25945219
1. 场景报告模块编写(暂存)
已修改7个文件
已添加9个文件
1216 ■■■■■ 文件已修改
src/api/fysp/deviceApi.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components.d.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FYImageSelectDialog.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SideList.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/menu.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enum/device/device.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enum/device/monitorDevice.js 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enum/device/productionDevice.js 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/enum/device/treatmentDevice.js 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/fysp/device.js 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/model/info 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/check/components/ArbitraryPhoto.vue 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/ProdSceneReport.vue 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompImgInfo.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/data-product/components/CompProblemTable.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/fysp/scene/CompSceneConstructionInfo.vue 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/fysp/deviceApi.js
@@ -13,11 +13,17 @@
  // èŽ·å–è®¾å¤‡
  async fetchDevices(sceneId, deviceTypeId) {
    const params = `?sceneId=${sceneId}&deviceTypeId=${deviceTypeId}`;
    return await $fysp.get(`device${params}`).then((res) => res).then((res) => res.data);
    return await $fysp
      .get(`device${params}`)
      .then((res) => res)
      .then((res) => res.data);
  },
  // èŽ·å–è®¾å¤‡çŠ¶æ€ä»¥åŠè®¾å¤‡è¯¦æƒ…
  async fetchDeviceStatus({deviceId, sceneId, deviceTypeId}) {
    const params = `?deviceId=${deviceId}&sceneId=${sceneId}&deviceTypeId=${deviceTypeId}`;
    return await $fysp.get(`device/status${params}`).then((res) => res).then((res) => res.data);
    return await $fysp
      .get(`device/status${params}`)
      .then((res) => res)
      .then((res) => res.data);
  }
};
src/components.d.ts
@@ -12,17 +12,13 @@
    BasePanelLayout: typeof import('./components/core/BasePanelLayout.vue')['default']
    CompQuickSet: typeof import('./components/search-option/CompQuickSet.vue')['default']
    Content: typeof import('./components/core/Content.vue')['default']
    ElAffix: typeof import('element-plus/es')['ElAffix']
    ElAside: typeof import('element-plus/es')['ElAside']
    ElAvatar: typeof import('element-plus/es')['ElAvatar']
    ElBadge: typeof import('element-plus/es')['ElBadge']
    ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
    ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
    ElButton: typeof import('element-plus/es')['ElButton']
    ElCalendar: typeof import('element-plus/es')['ElCalendar']
    ElCard: typeof import('element-plus/es')['ElCard']
    ElCascader: typeof import('element-plus/es')['ElCascader']
    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
    ElCol: typeof import('element-plus/es')['ElCol']
    ElCollapse: typeof import('element-plus/es')['ElCollapse']
    ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@@ -41,19 +37,14 @@
    ElIcon: typeof import('element-plus/es')['ElIcon']
    ElImage: typeof import('element-plus/es')['ElImage']
    ElInput: typeof import('element-plus/es')['ElInput']
    ElLink: typeof import('element-plus/es')['ElLink']
    ElMain: typeof import('element-plus/es')['ElMain']
    ElMenu: typeof import('element-plus/es')['ElMenu']
    ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
    ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
    ElOption: typeof import('element-plus/es')['ElOption']
    ElPagination: typeof import('element-plus/es')['ElPagination']
    ElPopover: typeof import('element-plus/es')['ElPopover']
    ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
    ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
    ElRow: typeof import('element-plus/es')['ElRow']
    ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
    ElSegmented: typeof import('element-plus/es')['ElSegmented']
    ElSelect: typeof import('element-plus/es')['ElSelect']
    ElSpace: typeof import('element-plus/es')['ElSpace']
    ElStep: typeof import('element-plus/es')['ElStep']
@@ -65,7 +56,6 @@
    ElTabs: typeof import('element-plus/es')['ElTabs']
    ElTag: typeof import('element-plus/es')['ElTag']
    ElText: typeof import('element-plus/es')['ElText']
    ElTooltip: typeof import('element-plus/es')['ElTooltip']
    ElTree: typeof import('element-plus/es')['ElTree']
    ElUpload: typeof import('element-plus/es')['ElUpload']
    Footer: typeof import('./components/core/Footer.vue')['default']
@@ -74,6 +64,7 @@
    FYBgTaskDialog: typeof import('./components/bg-task/FYBgTaskDialog.vue')['default']
    FYBgTaskItem: typeof import('./components/bg-task/FYBgTaskItem.vue')['default']
    FYForm: typeof import('./components/form/FYForm.vue')['default']
    FYImageSelectDialog: typeof import('./components/FYImageSelectDialog.vue')['default']
    FYInfoSearch: typeof import('./components/search-option/FYInfoSearch.vue')['default']
    FYList: typeof import('./components/table/FYList.vue')['default']
    FYOptionLocation: typeof import('./components/search-option/FYOptionLocation.vue')['default']
src/components/FYImageSelectDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,99 @@
<template>
  <el-dialog
    v-model="anyPhotoDialog"
    width="66%"
    title="任意图片"
    destroy-on-close
  >
    <div class="main">
      <el-row justify="end" class="btns" v-if="!readonly">
        <el-text size="small" type="info" class="m-r-8"
          >最多选择{{ maxSelect }}张图片</el-text
        >
        <el-button
          size="small"
          type="primary"
          @click="sendSelectedImg(true)"
          :disabled="selectedImgUrlList.length == 0"
          >确定</el-button
        >
        <el-button size="small" type="primary" @click="sendSelectedImg(false)"
          >取消</el-button
        >
      </el-row>
      <div class="center">
        <el-tabs v-if="typeList.length > 0" v-model="activeId" type="card">
          <el-tab-pane
            v-for="item in typeList"
            :key="item.typeId"
            :label="
              item.typeName + ' (' + typeImgMap.get(activeId).length + ')'
            "
            :name="item.typeId"
          >
          </el-tab-pane>
        </el-tabs>
        <el-empty v-if="isEmpty" description="暂无记录" />
        <el-scrollbar class="imgs">
          <el-image
            v-for="(img, i) in typeImgMap.get(activeId)"
            :key="i"
            :class="[img.isSelect ? 'selected' : 'noActive', 'image']"
            fit="cover"
            :src="img.url"
            lazy
            @click="onSelect(img, i)"
          />
        </el-scrollbar>
      </div>
    </div>
  </el-dialog>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
  typeList: {
    type: Array,
    default: () => []
  },
  // æ˜¯å¦ä»¥åªè¯»çš„形式查看当前页面
  readonly: {
    type: Boolean,
    default: false
  },
  defaultFile: {
    type: Array,
    default: () => []
  },
  // å›¾ç‰‡å¯é€‰æ•°é‡ï¼Œå½“传入数字时,代表图片数量
  maxSelect: {
    type: Number,
    default: 3
  }
});
const activeId = ref('');
const typeImgMap = ref(new Map());
const selectedImgList = ref([]);
watch(typeImgMap, (newMap, oldMap) => {
  if (newMap.get(activeId.value) == undefined) {
    return;
  }
  newMap.get(activeId.value).forEach(
    (i) => {
      if (i.isSelect == true) {
        return;
      }
      props.defaultFile.forEach((imgItem) => {
        if (imgItem.url == i.url) {
          i.isSelect = true;
        }
      });
    },
    { deep: true }
  );
});
</script>
src/components/SideList.vue
@@ -11,6 +11,7 @@
    empty-text="暂无记录"
  >
    <template #default="{ node, data }">
      <slot :node="node" :data="data">
      <div
        :class="
          data.selected
@@ -29,6 +30,7 @@
        /></el-icon>
        <div>{{ node.label }}</div>
      </div>
      </slot>
    </template>
  </el-tree>
</template>
@@ -43,20 +45,20 @@
          type: 0,
          title: 'title',
          categoly: '2022-10-10',
          data: {},
          data: {}
        }
      ]
        },
      ],
    },
    loading: Boolean,
    loading: Boolean
  },
  emits: ['itemClick'],
  data() {
    return {
      defaultProps: {
        children: 'children',
        label: 'title',
        label: 'title'
      },
      isLoading: this.loading,
      isLoading: this.loading
    };
  },
  computed: {
@@ -75,7 +77,7 @@
      for (const [key, value] of itemMap) {
        const i = {
          title: key,
          children: [],
          children: []
        };
        value.forEach((v) => {
          i.children.push(v);
@@ -92,11 +94,11 @@
        }
      });
      return list;
    },
    }
  },
  watch: {
    loading(nValue) {
      this.isLoading = nValue
      this.isLoading = nValue;
    },
    //当数据第一次更新时
    dataList(nValue) {
@@ -117,7 +119,7 @@
        }
        this.isLoading = false;
      }
    },
    }
  },
  methods: {
    handleNodeClick(data) {
@@ -131,8 +133,8 @@
      if (data.children == undefined) {
        this.$emit('itemClick', data);
      }
    },
  },
    }
  }
};
</script>
src/constants/menu.js
@@ -37,7 +37,7 @@
  },
  {
    icon: 'DataAnalysis',
    name: '数据产品',
    name: '中间数据产品',
    children: [
      {
        path: '/fysp/data-product/profollow',
@@ -59,6 +59,12 @@
        icon: 'Document',
        name: '日报管理',
      },
    ],
  },
  {
    icon: 'DataAnalysis',
    name: '最终数据产品',
    children: [
      {
        path: '/fysp/data-product/scenereport',
        icon: 'Document',
src/enum/device/device.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
import monitor from './monitorDevice';
import treatment from './treatmentDevice';
import production from './productionDevice';
function enumDevice() {
  return [
    { value: 0, label: '监控设备' },
    { value: 1, label: '治理设备' },
    { value: 2, label: '生产设备' }
  ];
}
function toLabel(sceneType, deviceType, valueArr) {
  switch (deviceType + '') {
    case '0':
      return monitor.toLabel(sceneType, valueArr);
    case '1':
      return treatment.toLabel(sceneType, valueArr);
    case '2':
      return production.toLabel(sceneType, valueArr);
  }
}
export { enumDevice, toLabel };
src/enum/device/monitorDevice.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,77 @@
const dustDeviceType = [
  {
    label: '扬尘监测',
    value: '1',
    children: [
      {
        label: '扬尘监测',
        value: '1',
      },
    ],
  },
];
const fumeDeviceType = [
  {
    label: '油烟监测',
    value: '1',
    children: [
      {
        label: '油烟监测',
        value: '1',
      },
    ],
  },
];
const vocDeviceType = [
  {
    label: 'VOC监测',
    value: '1',
    children: [
      {
        label: 'VOC监测',
        value: '1',
      },
    ],
  },
];
// ç›‘测设备类型
function monitorDevices(sceneType) {
  switch (parseInt(sceneType)) {
    // å·¥åœ°,码头,搅拌站,堆场
    case 1:
    case 2:
    case 3:
    case 14:
      return dustDeviceType;
    // é¤é¥®
    case 5:
      return fumeDeviceType;
    // å·¥ä¸šä¼ä¸š,汽修
    case 4:
    case 6:
      return vocDeviceType;
    default:
      return dustDeviceType;
  }
}
function toLabel(sceneType, valueArr) {
  const labelArr = [];
  let options = monitorDevices(sceneType);
  valueArr.forEach(v => {
    if (options) {
      const op = options.find(o => {
        return (o.value + '') == (v + '');
      });
      labelArr.push(op.label);
      options = op.children;
    }
  });
  return labelArr;
}
export default { monitorDevices, toLabel };
src/enum/device/productionDevice.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
const dustDeviceType = [
  {
    label: '非道',
    value: '1',
    children: [
      { label: '挖掘机', value: '1' },
      { label: '叉车', value: '2' },
      { label: '履带吊', value: '3' },
      { label: '铲车', value: '4' },
      { label: '发动机', value: '5' },
    ],
  },
];
const fumeDeviceType = [
  {
    label: '厨具',
    value: '1',
    children: [{ label: '厨具', value: '1' }],
  },
];
const vocDeviceType = [
  {
    label: 'VOC',
    value: '1',
    children: [{ label: 'VOC', value: '1' }],
  },
];
// ç”Ÿäº§è®¾å¤‡ç±»åž‹
function productionDevices(sceneType) {
  switch (parseInt(sceneType)) {
    // å·¥åœ°,码头,搅拌站,堆场
    case 1:
    case 2:
    case 3:
    case 14:
      return dustDeviceType;
    // é¤é¥®
    case 5:
      return fumeDeviceType;
    // å·¥ä¸šä¼ä¸š,汽修
    case 4:
    case 6:
      return vocDeviceType;
    default:
      return dustDeviceType;
  }
}
function toLabel(sceneType, valueArr) {
  const labelArr = [];
  let options = productionDevices(sceneType);
  valueArr.forEach(v => {
    if (options) {
      const op = options.find(o => {
        return (o.value + '') == (v + '');
      });
      labelArr.push(op.label);
      options = op.children;
    }
  });
  return labelArr;
}
export default { productionDevices, toLabel };
src/enum/device/treatmentDevice.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
const dustDeviceType = [
  {
    label: '技防',
    value: '1',
    children: [
      { label: '环保洒水车(大型非电动)', value: '1' },
      { label: '电动雾炮车', value: '2' },
      { label: '电动洒水车(小型)', value: '3' },
      { label: '雾炮车(固定或轮式)', value: '4' },
      { label: '自动冲洗装置(封闭式)', value: '5' },
      { label: '高效洗轮机', value: '6' },
      { label: '高压水枪', value: '7' },
      { label: '普通水管或消防栓', value: '8' },
      { label: '塔吊喷淋', value: '9' },
      { label: '围墙喷淋', value: '10' },
      { label: '扬尘监测与喷淋联动', value: '11' },
      { label: '堆场喷淋', value: '12' },
      { label: '生产区喷淋', value: '13' },
    ],
  },
];
const fumeDeviceType = [
  {
    label: '净化',
    value: '1',
    children: [{ label: '油烟净化', value: '1' }],
  },
];
const vocDeviceType = [
  {
    label: '净化',
    value: '1',
    children: [{ label: '固废净化', value: '1' }],
  },
];
// æ²»ç†è®¾å¤‡ç±»åž‹
function treatmentDevices(sceneType) {
  switch (parseInt(sceneType)) {
    // å·¥åœ°,码头,搅拌站,堆场
    case 1:
    case 2:
    case 3:
    case 14:
      return dustDeviceType;
    // é¤é¥®
    case 5:
      return fumeDeviceType;
    // å·¥ä¸šä¼ä¸š,汽修
    case 4:
    case 6:
      return vocDeviceType;
    default:
      return dustDeviceType;
  }
}
function toLabel(sceneType, valueArr) {
  const labelArr = [];
  let options = treatmentDevices(sceneType);
  valueArr.forEach(v => {
    if (options) {
      const op = options.find(o => {
        return (o.value + '') == (v + '');
      });
      labelArr.push(op.label);
      options = op.children;
    }
  });
  return labelArr;
}
export default { treatmentDevices, toLabel };
src/model/fysp/device.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
/**
 * æ ‡å‡†åŒ–属性名
 * å°†ä¸‰ç§è®¾å¤‡[监控设备],[治理设备],[生产设备]对象的属性名统一格式化
 * @param {*} data
 */
function formatDevice(data) {
  // å°†ä¸€ä¸ªjs对象中所有di,wi,pi开头的属性全部改成去掉这些前缀并且重新变为驼峰式命名
  const newObj = {};
  for (const key in data) {
    let newKey = key;
    if (key.startsWith('di')) {
      newKey = key.substring(2);
    } else if (key.startsWith('wi')) {
      newKey = key.substring(2);
    } else if (key.startsWith('pi')) {
      newKey = key.substring(2);
    }
    newKey = newKey.charAt(0).toLowerCase() + newKey.slice(1);
    newObj[newKey] = data[key];
  }
  return newObj;
}
function formatDeviceList(dataList) {
  return dataList.map((v) => formatDevice(v));
}
export { formatDevice, formatDeviceList };
src/model/info
src/views/fysp/check/components/ArbitraryPhoto.vue
@@ -1,49 +1,50 @@
<template>
  <div class="main">
    <div class="filters" v-if="false">
      <el-select
        v-for="(key_select, index_select) of filters.keys()"
        :placeholder="key_select.text"
    <el-row justify="end" class="btns" v-if="!readonly">
      <el-text size="small" type="info" class="m-r-8"
        >最多选择{{ maxSelect }}张图片</el-text
      >
        <el-option
          v-for="(key_option) in filters.get(key_select.key)"
          :key="key_option.key"
          :value="key_option.value"
          :label="key_option.label"
      <el-button
        size="small"
        type="primary"
        @click="sendSelectedImg(true)"
        :disabled="selectedImgUrlList.length == 0"
        >确定</el-button
        >
        </el-option>
      </el-select>
    </div>
    <div class="btns" v-if="!readonly">
      <el-button size="small" type="primary" @click="sendSelectedImg(true)">确定</el-button>
      <el-button size="small" type="primary" @click="sendSelectedImg(false)">取消</el-button>
    </div>
      <el-button size="small" type="primary" @click="sendSelectedImg(false)"
        >取消</el-button
      >
    </el-row>
    <div class="center">
      <el-descriptions>
      <!-- <el-descriptions>
        <el-descriptions-item label="总数">
          <span>{{ this.imgUrlList.length }}</span>
        </el-descriptions-item>
      </el-descriptions>
      </el-descriptions> -->
      <el-tabs v-model="activeId" type="card">
        <el-tab-pane
          v-for="item in typeList"
          :label="item.businesstype"
          :key="item.businesstypeid"
          :label="
            item.businesstype + ' (' + typeImgMap.get(activeId).length + ')'
          "
          :name="item.businesstypeid"
        >
        </el-tab-pane>
      </el-tabs>
      <el-empty v-if="isEmpty" description="暂无记录" />
      <div class="imgs">
      <el-scrollbar class="imgs">
        <el-image
          v-for="(img, i) in typeImgMap.get(activeId)"
          :key="i"
          :class="[Boolean(img.isSelect) ? 'selected' : 'noActive', 'image']"
          fit="cover"
          :src="img.url"
          lazy
          @click="onSelect(img, i)"
        />
      </div>
      </el-scrollbar>
    </div>
  </div>
</template>
@@ -70,6 +71,11 @@
    defaultFile: {
      type: Array,
      default: () => []
    },
    // å›¾ç‰‡å¯é€‰æ•°é‡ï¼Œå½“传入数字时,代表图片数量
    maxSelect: {
      type: Number,
      default: 3
    }
  },
  data() {
@@ -95,10 +101,19 @@
        // { businesstypeid: 60, businesstype: '扩展类十' }
      ],
      typeImgMap: new Map(),
      imgUrlList: []
      imgUrlList: [],
      selectedImgUrlList: []
    };
  },
  watch: {
    subtask: {
      handler(nV, oV) {
        if (nV != oV && nV) {
          this.getAllImgList();
        }
      },
      immediate: true
    },
    defaultFile: {
      handler(newFileList, oldFileList) {
        if (this.isClose) {
@@ -128,9 +143,9 @@
    }
  },
  mounted() {
    if (this.subtask) {
      this.getAllImgList();
    }
    // if (this.subtask) {
    //   this.getAllImgList();
    // }
  },
  methods: {
    // åˆå§‹åŒ–刚开始选中的标签
@@ -139,11 +154,13 @@
        this.activeId = this.typeList[0].businesstypeid;
      }
    },
    async getAllImgList() {
    getAllImgList() {
      // for(var k of this.typeImgMap.keys()) {
      //     this.typeImgMap.set(k, [])
      // }
      await mediafileApi.getRoutineByStGuid(this.subtask.stGuid).then((res) => {
      this.typeImgMap.clear();
      this.typeList = [];
      mediafileApi.getRoutineByStGuid(this.subtask.stGuid).then((res) => {
        this.isEmpty = false;
        let data = res.data;
        if (data.length == 0) {
@@ -179,7 +196,6 @@
          this.imgUrlList.push(e);
          // TODO imgUrl全局配置
          e.url = $fysp.imgUrl + e.extension1 + e.guid + '.jpg';
          // e.url = "http://47.100.191.150:9005/images/" + e.extension1 + e.guid + '.jpg'
          e.isSelect = false;
        }
        this.initSelectedTab();
@@ -217,20 +233,31 @@
      });
    },
    onSelect(img, i) {
      // if (i == 2 && !this.isAll) {
      //   this.getAllImgList();
      //   this.isAll = true;
      // } else {
      //   if (this.readonly) {
      //     return;
      //   }
      //   img.isSelect = !Boolean(img.isSelect);
      // }
      if (this.readonly) {
        return;
      }
      img.isSelect = !img.isSelect;
      const index = this.selectedImgUrlList.indexOf(img);
      if (index == -1) {
        if (this.maxSelect == 1) {
          img.isSelect = true;
          this.selectedImgUrlList.push(img);
          if (this.selectedImgUrlList.length > 1) {
            this.selectedImgUrlList.splice(0, 1).forEach((e) => {
              e.isSelect = false;
            });
          }
        } else if (this.maxSelect > 1) {
          if (this.selectedImgUrlList.length < this.maxSelect) {
            img.isSelect = true;
            this.selectedImgUrlList.push(img);
          }
        }
      } else {
        this.selectedImgUrlList.splice(index, 1);
        img.isSelect = false;
      }
      // img.isSelect = !img.isSelect;
    },
    sendSelectedImg(isOk) {
      let result = [];
@@ -280,7 +307,7 @@
} */
.imgs {
  height: 650px;
  height: 50vh;
  width: 90%;
  min-height: 100px !important;
  /* border-style:solid;
@@ -295,6 +322,7 @@
}
.image {
  margin: 5px;
  height: 210px;
  width: 200px;
  border-radius: 4px;
@@ -308,10 +336,10 @@
}
.selected {
  padding: 5px;
  margin: 3px;
  color: #4abe84;
  box-shadow: 0 2px 7px 0 rgba(85, 110, 97, 0.35);
  border: 1px solid rgba(74, 190, 132, 1);
  border: 2px solid rgba(74, 190, 132, 1);
}
.selected:before {
@@ -338,7 +366,7 @@
}
.noActive {
  padding: 5px;
  /* padding: 5px; */
}
.blurry {
src/views/fysp/data-product/ProdSceneReport.vue
@@ -1,3 +1,271 @@
<template>
  åœºæ™¯æŠ¥å‘Šç®¡ç†
  <BaseContentLayout>
    <template #header>
      <SearchBar @on-submit="search"> </SearchBar>
</template>
    <template #aside>
      <SideList
        :items="subtasks"
        :loading="sideLoading"
        @item-click="chooseSubtask"
      ></SideList>
    </template>
    <template #main>
      <el-scrollbar class="el-scrollbar" v-loading="mainLoading">
        <CompSceneConstructionInfo
          title="A、工地基本信息"
          :form-info="formSubScene"
        />
        <div><el-text type="">附图片:</el-text></div>
        <CompImgInfo
          title="施工铭牌"
          :img-src="sceneImg.url"
          @change="anyPhotoDialog = true"
        ></CompImgInfo>
        <el-divider />
        <el-text tag="h1">B、主要污染防治设施</el-text>
        <el-space wrap>
          <CompImgInfo
            v-for="(item, i) in deviceList"
            :key="i"
            down-title
            :title="item._deviceTypeName"
            :img-src="item._showStatusPic"
            @change="showDevicePhotoDialog(item)"
          ></CompImgInfo>
        </el-space>
        <el-divider />
        <el-text tag="h1">C、现场污染问题与整改情况</el-text>
      </el-scrollbar>
    </template>
  </BaseContentLayout>
  <el-dialog
    v-model="anyPhotoDialog"
    width="66%"
    title="任意图片"
    destroy-on-close
  >
    <ArbitraryPhoto
      :max-select="1"
      :readonly="false"
      :subtask="curSubtask.data"
      @selectByAnyPhonoEvent="handleSelectAnyPhoto"
      :defaultFile="[sceneImg]"
    >
    </ArbitraryPhoto>
  </el-dialog>
  <el-dialog
    title="设备图片"
    width="66%"
    v-model="deiveceImgDialog"
    destroy-on-close
  >
    <CompDevicePhono
      @selectPhonoEvent="handleSelectDevicePhoto"
      :imgPathsDataSource="showDeviceImgList"
    >
    </CompDevicePhono>
  </el-dialog>
</template>
<script setup>
import { ref, computed } from 'vue';
import { $fysp } from '@/api/index';
import taskApi from '@/api/fysp/taskApi';
import sceneApi from '@/api/fysp/sceneApi';
import deviceApi from '@/api/fysp/deviceApi';
import { formatDeviceList } from '@/model/fysp/device';
import { enumDevice, toLabel } from '@/enum/device/device';
import CompSceneConstructionInfo from '@/views/fysp/scene/CompSceneConstructionInfo.vue';
import ArbitraryPhoto from '@/views/fysp/check/components/ArbitraryPhoto.vue';
import CompDevicePhono from '@/views/fysp/check/components/CompDevicePhono.vue';
import CompImgInfo from '@/views/fysp/data-product/components/CompImgInfo.vue';
/************************* å·¦ä¾§å·¡æŸ¥ä»»åŠ¡é€‰å• **********************************/
const curSubtask = ref({});
const subtasks = ref([]);
const sideLoading = ref(false);
const mainLoading = ref(false);
//获取任务问题的审核情况
function getSubtaskType(s) {
  let type = 0;
  if (s.proNum == 0) {
    type = 2;
  } else if (s.proCheckedNum == 0) {
    type = 0;
  } else if (s.proCheckedNum < s.proNum) {
    type = 1;
  } else {
    type = 2;
  }
  return type;
}
//查询子任务统计信息
function search(formSearch) {
  // this.topTask = formSearch.topTask;
  sideLoading.value = true;
  mainLoading.value = true;
  // this.curProList = [];
  curSubtask.value = {};
  const param = {
    topTaskId: formSearch.topTask.tguid,
    sceneTypeId: formSearch.sceneTypeId
  };
  taskApi.getSubtaskSummary(param).then((res) => {
    const list = [];
    res.forEach((s) => {
      const t = getSubtaskType(s);
      list.push({
        type: t,
        title: s.stName,
        categoly: s.stPlanTime.split('T')[0],
        data: s
      });
    });
    subtasks.value = list;
    if (list.length == 0) {
      sideLoading.value = false;
      mainLoading.value = false;
    }
  });
}
//点击左侧菜单任务事件
function chooseSubtask(s) {
  sideLoading.value = false;
  mainLoading.value = true;
  curSubtask.value = s;
  fetchSceneInfo(s.data.sceneId).finally(() => {
    mainLoading.value = false;
  });
  fetchDeviceList(s);
  fetchProblems(s)
}
/************************* åœºæ™¯åŸºæœ¬ä¿¡æ¯ **********************************/
const formSubScene = ref({});
function fetchSceneInfo(sceneId) {
  return sceneApi.getSceneDetail(sceneId).then((res) => {
    //场景
    // if (res.data.scense) formScene = res.data.scense;
    formSubScene.value = res.data.subScene ? res.data.subScene : {};
    // if (res.data.sceneDevice) {
    //   formSceneDevice = res.data.sceneDevice;
    // } else {
    //   formSceneDevice = {
    //     sGuid: formScene.guid,
    //   };
    // }
  });
}
// ä»»æ„æ‹å›¾ç‰‡é€‰æ‹©å¯¹è¯æ¡†
const anyPhotoDialog = ref(false);
const sceneImg = ref({});
function handleSelectAnyPhoto(data) {
  anyPhotoDialog.value = false;
  if (data.length > 0) {
    sceneImg.value = { url: data[0].url };
  }
}
/************************* åœºæ™¯è®¾å¤‡ä¿¡æ¯ **********************************/
// è®¾å¤‡å›¾ç‰‡é€‰æ‹©å¯¹è¯æ¡†
const deiveceImgDialog = ref(false);
const showDeviceImg = ref({});
const showDeviceImgList = ref([]);
// è®¾å¤‡å›¾ç‰‡åˆ—表
const deviceList = ref([]);
function showDevicePhotoDialog(device) {
  deiveceImgDialog.value = true;
  showDeviceImgList.value = [];
  let imgList = [];
  device._status
    .map((s) => s._picUrls)
    .forEach((pics) => {
      imgList = imgList.concat(
        pics.map((p) => {
          return {
            topTypeId: device.topTypeId,
            _picUrl: p
          };
        })
      );
    });
  console.log(imgList);
  showDeviceImgList.value = imgList;
}
function handleSelectDevicePhoto(data) {
  deiveceImgDialog.value = false;
  if (data.length > 0) {
    showDeviceImg.value = { url: data[0].url };
  }
}
const deviceTopTypes = [
  { id: 0, label: '监控设备' },
  { id: 1, label: '治理设备' }
  // { id: 2, label: '生产设备' }
];
function fetchDeviceList(s) {
  deviceList.value = [];
  for (const deviceTopTypeElement of deviceTopTypes) {
    const topTypeId = deviceTopTypeElement.id;
    deviceApi
      .fetchDevices(s.data.sceneId, topTypeId)
      .then((res) => {
        return formatDeviceList(res.data);
      })
      .then((result) => {
        result.forEach((r) => {
          const param = {
            deviceId: r.id,
            sceneId: r.sceneGuid,
            deviceTypeId: topTypeId
          };
          deviceApi
            .fetchDeviceStatus(param)
            .then((res) => {
              res.data.forEach((e) => {
                if (e.dlPicUrl && e.dlPicUrl.trim() != '') {
                  e._picUrls = e.dlPicUrl
                    .split(';')
                    .map((v) => $fysp.imgUrl + v);
                } else {
                  e._picUrls = [];
                }
              });
              return res;
            })
            .then((res) => {
              if (res.data.length > 0 && res.data[0]._picUrls.length > 0) {
                r._showStatusPic = res.data[0]._picUrls[0];
              }
              r._status = res.data;
              r.topTypeId = topTypeId;
              r._deviceTypeName = toLabel(r.sceneTypeId, topTypeId, [
                r.typeId,
                r.subtypeId
              ]).join('-');
              deviceList.value.push(r);
            });
        });
      });
  }
}
/************************* çŽ°åœºå·¡æŸ¥æƒ…å†µï¼ˆé—®é¢˜ä¸Žæ•´æ”¹ï¼‰ **********************************/
//当前任务的问题列表
const curProList = ref([]);
function fetchProblems(s) {
  taskApi.getProBySubtask(s.data.stGuid).then((res) => {
    curProList.value = res;
  });
}
/************************* æ‰¬å°˜é˜²æ²»å»ºè®® **********************************/
</script>
src/views/fysp/data-product/components/CompImgInfo.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
<template>
  <table>
    <tbody>
      <tr v-if="!downTitle">
        <td>
          <el-row justify="space-between" align="middle">
            {{ title }}
            <el-button size="small" @click="$emit('change')">{{
              btnName
            }}</el-button>
          </el-row>
        </td>
      </tr>
      <tr>
        <td>
          <el-image
            class="image"
            :src="imgSrc"
            :preview-src-list="[imgSrc]"
            :initial-index="0"
            fit="cover"
            lazy
          />
        </td>
      </tr>
      <tr v-if="downTitle">
        <td>
          <el-row justify="space-between" align="middle">
            {{ title }}
            <el-button size="small" @click="$emit('change')">{{
              btnName
            }}</el-button>
          </el-row>
        </td>
      </tr>
    </tbody>
  </table>
</template>
<script setup>
import { ref } from 'vue';
const props = defineProps({
  // æ ‡é¢˜æ˜¯å¦åœ¨å›¾ç‰‡ä¸‹æ–¹
  downTitle: Boolean,
  title: String,
  imgSrc: String,
  btnName: {
    type: String,
    default: '修改'
  }
});
const emit = defineEmits(['change']);
</script>
<style scoped>
.image {
  width: 200px;
  height: 210px;
  border-radius: 4px;
}
table {
  color: #333333;
  border-color: #666666;
  border-collapse: collapse;
}
tr {
  font-size: var(--el-font-size-small);
}
td {
  border: 1px solid black;
  padding: 2px 6px;
  /* border-width: 1px;
  padding: 8px;
  border-style: solid;
  border-color: #666666; */
}
</style>
src/views/fysp/data-product/components/CompProblemTable.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,116 @@
<template>
  <table>
    <tbody>
      <tr>
        <td>{{ title }}</td>
      </tr>
      <tr>
        <td>
          <el-image
            class="image"
            :src="seletcedProblemPic"
            :preview-src-list="[seletcedProblemPic]"
            :initial-index="0"
            fit="cover"
            lazy
          />
        </td>
        <td>
          <el-image
            class="image"
            :src="seletcedChangePic"
            :preview-src-list="[seletcedChangePic]"
            :initial-index="0"
            fit="cover"
            lazy
          />
        </td>
      </tr>
      <tr>
        <td>
          <el-row justify="space-between" align="middle">
            <div>位置:{{ problem.location }}</div>
            <div>描述:{{ problem.problemname }}</div>
            <el-button size="small" @click="$emit('change')">{{
              btnName
            }}</el-button>
          </el-row>
        </td>
        <td>
          <el-row justify="space-between" align="middle">
            <div>位置:{{ problem.location }}</div>
            <div>描述:{{ problem.problemname }}</div>
            <el-button size="small" @click="$emit('change')">{{
              btnName
            }}</el-button>
          </el-row>
        </td>
      </tr>
    </tbody>
  </table>
</template>
<script setup>
import { ref, watch } from 'vue';
import ProCheckProxy from '@/views/check/ProCheckProxy';
const props = defineProps({
  problem: {
    type: Object,
    default: () => {
      return {};
    }
  }
});
const emit = defineEmits(['change']);
const pics = ref([]);
const seletcedProblemPic = ref();
const seletcedChangePic = ref();
function getPics() {
  pics.value = ProCheckProxy.proPics(props.problem);
  if (pics.value[0].path.length > 0) {
    seletcedProblemPic.value = pics.value[0].path[0];
  }
  if (pics.value[1].path.length > 0) {
    seletcedChangePic.value = pics.value[1].path[0];
  }
}
watch(
  () => props.problem,
  (nV, oV) => {
    if (nV != oV) {
      getPics();
    }
  },
  { immediate: true }
);
</script>
<style scoped>
.image {
  width: 200px;
  height: 210px;
  border-radius: 4px;
}
table {
  color: #333333;
  border-color: #666666;
  border-collapse: collapse;
}
tr {
  font-size: var(--el-font-size-small);
}
td {
  border: 1px solid black;
  padding: 2px 6px;
  /* border-width: 1px;
  padding: 8px;
  border-style: solid;
  border-color: #666666; */
}
</style>
src/views/fysp/scene/CompSceneConstructionInfo.vue
@@ -1,6 +1,7 @@
<!-- å·¥åœ°ä¸“属信息编辑 -->
<template>
  <el-form
    v-if="showStyle == 'form'"
    :inline="false"
    :model="formObj"
    ref="formRef"
@@ -161,6 +162,183 @@
      <el-button :disabled="!edit" @click="onReset">重置</el-button>
    </el-form-item>
  </el-form>
  <el-descriptions
    v-else-if="showStyle == 'descriptions'"
    :column="2"
    :size="fontSize"
    direction="horizontal"
    border
  >
    <template #title>
      <el-text tag="h1">{{ title }}</el-text>
    </template>
    <template #extra>
      <el-button
        :size="fontSize"
        :disabled="!edit"
        type="primary"
        @click="onSubmit"
        :loading="loading"
        >提交</el-button
      >
      <el-button :size="fontSize" :disabled="!edit" @click="onReset"
        >重置</el-button
      >
    </template>
    <el-descriptions-item label="工程类型"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csProjectType"
        placeholder="工程类型"
    /></el-descriptions-item>
    <!-- <el-descriptions-item label="建设状态"
      ><el-select
        v-model="formObj.csStatus"
        placeholder="建设状态"
      >
        <el-option
          v-for="s in status"
          :key="s.value"
          :label="s.label"
          :value="s.value"
        /> </el-select
    ></el-descriptions-item> -->
    <el-descriptions-item label="施工阶段"
      ><el-select
        v-model="formObj.siExtension1"
        placeholder="施工阶段"
        :size="fontSize"
      >
        <el-option
          v-for="s in stage"
          :key="s.value"
          :label="s.label"
          :value="s.value"
        /> </el-select
    ></el-descriptions-item>
    <el-descriptions-item label="施工时间"
      ><el-date-picker
        :size="fontSize"
        v-model="formObj._timeRange"
        type="daterange"
        range-separator="至"
        start-placeholder="开工时间"
        end-placeholder="完工时间"
    /></el-descriptions-item>
    <el-descriptions-item label="剩余工期"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csLeftTime"
        placeholder="剩余工期,例如几年、几月、几周、几天等"
    /></el-descriptions-item>
    <el-descriptions-item label="施工地址" span="2"
      ><el-input
        clearable
        v-model="formObj.location"
        placeholder="施工地址"
        :size="fontSize"
    /></el-descriptions-item>
    <el-descriptions-item label="占地面积" rowspan="2"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csFloorSpace"
        placeholder="占地面积"
      >
        <template #append>㎡</template>
      </el-input></el-descriptions-item
    >
    <el-descriptions-item label="施工面积"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csConstructionArea"
        placeholder="施工面积"
      >
        <template #append>㎡</template>
      </el-input></el-descriptions-item
    >
    <el-descriptions-item label="每月施工面积"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csConstructionAreaPerMonth"
        placeholder="每月施工面积"
      >
        <template #append>㎡</template>
      </el-input></el-descriptions-item
    >
    <el-descriptions-item label="安全员"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csSecurityOfficer"
        placeholder="安全员"
    /></el-descriptions-item>
    <el-descriptions-item label="安全员电话"
      ><el-input
        :size="fontSize"
        clearable
        type="tel"
        v-model="formObj.csSecurityOfficerTel"
        placeholder="安全员电话"
      >
        <template #prepend>
          <el-icon><Iphone /></el-icon>
        </template> </el-input
    ></el-descriptions-item>
    <el-descriptions-item label="总包单位"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csConstructionUnit"
        placeholder="总包单位"
    /></el-descriptions-item>
    <!-- <el-descriptions-item label="施工单位联系人"
      ><el-input
        clearable
        v-model="formObj.csConstructionContacts"
        placeholder="施工单位联系人"
    /></el-descriptions-item>
    <el-descriptions-item label="施工单位联系电话"
      ><el-input
        clearable
        type="tel"
        v-model="formObj.csConstructionContactsTel"
        placeholder="施工单位联系电话"
      >
        <template #prepend>
          <el-icon><Iphone /></el-icon>
        </template> </el-input
    ></el-descriptions-item> -->
    <el-descriptions-item label="建设单位"
      ><el-input
        :size="fontSize"
        clearable
        v-model="formObj.csEmployerUnit"
        placeholder="建设单位"
    /></el-descriptions-item>
    <!-- <el-descriptions-item label="业主单位联系人"
      ><el-input
        clearable
        v-model="formObj.csEmployerContacts"
        placeholder="业主单位联系人"
    /></el-descriptions-item>
    <el-descriptions-item label="业主单位联系电话"
      ><el-input
        clearable
        type="tel"
        v-model="formObj.csEmployerContactsTel"
        placeholder="业主单位联系电话"
      >
        <template #prepend>
          <el-icon><Iphone /></el-icon>
        </template> </el-input
    ></el-descriptions-item> -->
  </el-descriptions>
</template>
<script setup>
@@ -171,23 +349,32 @@
import { useFormConfirm } from '@/composables/formConfirm';
const props = defineProps({
  scene: Object,
  //工地额外信息
  formInfo: Object,
  //场景类型:工地
  sceneType: {
    type: Number,
    default: 1,
    default: 1
  },
  // å±•示样式 form:表单;descriptions:描述列表
  showStyle: {
    type: String,
    default: 'descriptions'
    // default:'form'
  },
  title: String
});
const fontSize = ref('small');
const emit = defineEmits(['onSubmit', 'onCancel']);
const { formObj, formRef, edit, onSubmit, onReset } = useFormConfirm({
  submit: {
    do: submit,
    do: submit
  },
  cancel: {
    do: cancel,
  },
    do: cancel
  }
});
const loading = ref(false);
const status = reactive(enumStatusNA());
@@ -197,23 +384,23 @@
    {
      required: true,
      message: '工程类型不能为空',
      trigger: 'blur',
    },
      trigger: 'blur'
    }
  ],
  csStatus: [
    {
      required: true,
      message: '建设状态不能为空',
      trigger: 'change',
    },
      trigger: 'change'
    }
  ],
  siExtension1: [
    {
      required: true,
      message: '施工阶段不能为空',
      trigger: 'change',
    },
  ],
      trigger: 'change'
    }
  ]
});
// åˆ›å»ºæˆ–更新场景详情
@@ -252,7 +439,7 @@
      formObj.value = nValue;
      formObj.value._timeRange = [
        new Date(formObj.value.csStartTime),
        new Date(formObj.value.csEndTime),
        new Date(formObj.value.csEndTime)
      ];
    }
  },