Kaynağa Gözat

批量删除

zhuangyunsheng 2 ay önce
ebeveyn
işleme
57800d75af
100 değiştirilmiş dosya ile 4297 ekleme ve 409 silme
  1. 2 2
      .env.development
  2. 4 0
      src/api/model/aihazard.js
  3. 4 0
      src/api/model/autospray.js
  4. 4 0
      src/api/model/broadcast.js
  5. 4 0
      src/api/model/car_rinse.js
  6. 5 1
      src/api/model/common.js
  7. 54 0
      src/api/model/edgeprot.js
  8. 12 0
      src/api/model/elevator.js
  9. 4 0
      src/api/model/envdev.js
  10. 48 0
      src/api/model/parking.js
  11. 4 0
      src/api/model/scc.js
  12. 48 0
      src/api/model/smokedet.js
  13. 12 0
      src/api/model/tcm.js
  14. 1 1
      src/components/scPageHeader/index.vue
  15. 1 1
      src/components/scTable/helper.js
  16. 3 1
      src/components/scTable/renderer/pager-batch-del.vue
  17. 1 1
      src/components/scUpload/fileViewer.vue
  18. 23 17
      src/components/scUpload/index.vue
  19. 0 5
      src/components/scUpload/main.js
  20. 373 0
      src/components/scUpload/minio.vue
  21. 1 1
      src/components/scUpload/multiple.vue
  22. 34 10
      src/config/route.js
  23. 24 1
      src/utils/basicDic.js
  24. 38 38
      src/views/basic/project/detail.vue
  25. 1 1
      src/views/basic/project/items.vue
  26. 137 0
      src/views/dataMock/aihazard/batchDel.vue
  27. 27 45
      src/views/dataMock/aihazard/components/record/detail.vue
  28. 1 1
      src/views/dataMock/aihazard/components/record/index.vue
  29. 16 7
      src/views/dataMock/aihazard/detail.vue
  30. 22 8
      src/views/dataMock/aihazard/index.vue
  31. 0 5
      src/views/dataMock/aihazard/main.js
  32. 0 46
      src/views/dataMock/autospray/index.vue
  33. 122 0
      src/views/dataMock/broadcast/batchDel.vue
  34. 1 1
      src/views/dataMock/broadcast/components/record/detail.vue
  35. 0 1
      src/views/dataMock/broadcast/detail.vue
  36. 23 8
      src/views/dataMock/broadcast/index.vue
  37. 0 4
      src/views/dataMock/broadcast/main.js
  38. 118 0
      src/views/dataMock/carwash/batchDel.vue
  39. 1 1
      src/views/dataMock/carwash/components/info/detail.vue
  40. 3 3
      src/views/dataMock/carwash/components/record/detail.vue
  41. 1 1
      src/views/dataMock/carwash/components/record/index.vue
  42. 14 5
      src/views/dataMock/carwash/detail.vue
  43. 24 8
      src/views/dataMock/carwash/index.vue
  44. 122 0
      src/views/dataMock/edgeprot/batchDel.vue
  45. 0 0
      src/views/dataMock/edgeprot/components/index.js
  46. 187 0
      src/views/dataMock/edgeprot/components/record/detail.vue
  47. 169 0
      src/views/dataMock/edgeprot/components/record/index.vue
  48. 187 0
      src/views/dataMock/edgeprot/detail.vue
  49. 59 0
      src/views/dataMock/edgeprot/index.vue
  50. 9 0
      src/views/dataMock/edgeprot/main.js
  51. 146 0
      src/views/dataMock/elevator/batchDel.vue
  52. 1 1
      src/views/dataMock/elevator/components/alarm.vue
  53. 17 5
      src/views/dataMock/elevator/components/attendance.vue
  54. 40 40
      src/views/dataMock/elevator/components/record/detail.vue
  55. 13 7
      src/views/dataMock/elevator/detail.vue
  56. 22 20
      src/views/dataMock/elevator/index.vue
  57. 134 0
      src/views/dataMock/env/batchDel.vue
  58. 0 0
      src/views/dataMock/env/components/calendar.vue
  59. 0 0
      src/views/dataMock/env/components/index.js
  60. 1 1
      src/views/dataMock/envdev/components/record/detail.vue
  61. 0 0
      src/views/dataMock/env/components/record/index.vue
  62. 0 0
      src/views/dataMock/env/components/template.vue
  63. 0 0
      src/views/dataMock/env/components/threshold.vue
  64. 9 3
      src/views/dataMock/envdev/detail.vue
  65. 60 0
      src/views/dataMock/env/index.vue
  66. 0 0
      src/views/dataMock/env/main.js
  67. 0 46
      src/views/dataMock/envdev/index.vue
  68. 138 0
      src/views/dataMock/parking/batchDel.vue
  69. 0 0
      src/views/dataMock/parking/components/index.js
  70. 189 0
      src/views/dataMock/parking/components/record/detail.vue
  71. 186 0
      src/views/dataMock/parking/components/record/index.vue
  72. 0 0
      src/views/dataMock/parking/components/template.vue
  73. 229 0
      src/views/dataMock/parking/detail.vue
  74. 60 0
      src/views/dataMock/parking/index.vue
  75. 39 0
      src/views/dataMock/parking/main.js
  76. 0 46
      src/views/dataMock/scc/index.vue
  77. 124 0
      src/views/dataMock/smoke/batchDel.vue
  78. 0 0
      src/views/dataMock/smoke/components/index.js
  79. 113 0
      src/views/dataMock/smoke/components/record/detail.vue
  80. 166 0
      src/views/dataMock/smoke/components/record/index.vue
  81. 163 0
      src/views/dataMock/smoke/detail.vue
  82. 60 0
      src/views/dataMock/smoke/index.vue
  83. 9 0
      src/views/dataMock/smoke/main.js
  84. 122 0
      src/views/dataMock/spray/batchDel.vue
  85. 17 0
      src/views/dataMock/spray/components/index.js
  86. 1 1
      src/views/dataMock/autospray/components/record/detail.vue
  87. 1 1
      src/views/dataMock/autospray/components/record/index.vue
  88. 0 0
      src/views/dataMock/spray/detail.vue
  89. 60 0
      src/views/dataMock/spray/index.vue
  90. 0 0
      src/views/dataMock/spray/main.js
  91. 122 0
      src/views/dataMock/standard/batchDel.vue
  92. 17 0
      src/views/dataMock/standard/components/index.js
  93. 1 1
      src/views/dataMock/scc/components/record/detail.vue
  94. 1 1
      src/views/dataMock/scc/components/record/index.vue
  95. 0 0
      src/views/dataMock/standard/components/threshold.vue
  96. 2 1
      src/views/dataMock/scc/detail.vue
  97. 60 0
      src/views/dataMock/standard/index.vue
  98. 0 0
      src/views/dataMock/standard/main.js
  99. 26 11
      src/views/dataMock/tasks/monos.vue
  100. 0 0
      src/views/dataMock/tasks/tableExpand.vue

+ 2 - 2
.env.development

@@ -8,8 +8,8 @@ VUE_APP_TITLE = EasyDo运营中心
 # VUE_APP_API_BASEURL = http://www.qdeasydo.com/api
 # VUE_APP_OPS_BASEURL = http://www.qdeasydo.com/ops
 VUE_APP_ZEROAPI_BASEURL = http://www.qdeasydo.com
-VUE_APP_API_BASEURL  = http://192.168.101.93:8802
-VUE_APP_OPS_BASEURL = http://192.168.101.93:8804
+VUE_APP_API_BASEURL  = http://192.168.101.93:8805
+VUE_APP_OPS_BASEURL = http://192.168.101.93:8806
 
 # 本地端口
 VUE_APP_PORT = 3200

+ 4 - 0
src/api/model/aihazard.js

@@ -39,6 +39,10 @@ export default {
         name: "数据模拟-参数/复制",
         copyData: async function (data = {}) {
             return await http.post(`${this.url}/copyData`, data);
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
         }
     }
 }

+ 4 - 0
src/api/model/autospray.js

@@ -39,6 +39,10 @@ export default {
         name: "数据模拟-参数/复制",
         makeData: async function (data = {}) {
             return await http.post(`${this.url}/makeData`, data);
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
         }
     }
 }

+ 4 - 0
src/api/model/broadcast.js

@@ -39,6 +39,10 @@ export default {
         name: "数据模拟-参数/复制",
         makeData: async function (data = {}) {
             return await http.post(`${this.url}/makeData`, data);
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
         }
     }
 }

+ 4 - 0
src/api/model/car_rinse.js

@@ -59,6 +59,10 @@ export default {
         name: "数据模拟-参数/复制",
         copyData: async function (data = {}) {
             return await http.post(`${this.url}/copyData`, data);
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
         }
     }
 }

+ 5 - 1
src/api/model/common.js

@@ -31,9 +31,13 @@ export default {
     minio: {
 		url: `${config.API_URL}/ops/minio`,
 		name: "文件上传",
-		up: async function (data, config = {}) {
+		airecord: async function (data, config = {}) {
 			return await http.post(`${this.url}/uploadAihazard`, data, config);
 		},
+
+        parking: async function (data, config = {}) {
+			return await http.post(`${this.url}/uploadParking`, data, config);
+		},
         
 		rm: async function (fileName) {
 			return await http.post(`${this.url}/remove`, { fileName });

+ 54 - 0
src/api/model/edgeprot.js

@@ -0,0 +1,54 @@
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+    mounted: {
+        url: `${config.API_URL}/ops/edgeprot/getMountedList`,
+        name: "安装点查询",
+        get: async function (data = {}) {
+            return await http.post(this.url, data);
+        }
+    },
+
+    record: {
+        name: "监测记录",
+        url: `${config.API_URL}/ops/edgeprot`,
+        get: async function (data = {}) {
+            return await http.post(`${this.url}/getPage`, data);
+        },
+
+        add: async function (data = {}) {
+            return await http.post(`${this.url}/save`, data);
+        },
+
+        edit: async function (data = {}) {
+            return await http.post(`${this.url}/update`, data);
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/remove`, data);
+        },
+
+        batchDel: async function (data = {}) {
+            return await http.post(`${this.url}/batchRemove`, data);
+        }
+    },
+
+    dataMock: {
+        makeData: {
+            url: `${config.API_URL}/ops/edgeprot`,
+            name: "数据模拟-参数",
+            normal: async function (data = {}) {
+                return await http.post(`${this.url}/makeData`, data);
+            },
+
+            alarm: async function (data = {}) {
+                return await http.post(`${this.url}/makeWarningData`, data);
+            }
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${config.API_URL}/ops/edgeprot/removeData`, data);
+        }
+    }
+}

+ 12 - 0
src/api/model/elevator.js

@@ -85,6 +85,18 @@ export default {
             alarm: async function (data = {}) {
                 return await http.post(`${this.url}/makeWarningData`, data);
             }
+        },
+
+        removeData: {
+            url: `${config.API_URL}/ops/elevator`,
+            name: "数据模拟-批量删除",
+            normal: async function (data = {}) {
+                return await http.post(`${this.url}/removeData`, data);
+            },
+
+            attendance: async function (data = {}) {
+                return await http.post(`${this.url}/removePersonData`, data);
+            }
         }
     }
 }

+ 4 - 0
src/api/model/envdev.js

@@ -43,6 +43,10 @@ export default {
 
         copyData: async function (data = {}) {
             return await http.post(`${this.url}/copyData`, data);
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
         }
     }
 }

+ 48 - 0
src/api/model/parking.js

@@ -0,0 +1,48 @@
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+    gate: {
+        url: `${config.API_URL}/ops/parking/getGateList`,
+        name: "闸口查询",
+        get: async function (data = {}) {
+            return await http.post(this.url, data);
+        }
+    },
+
+    record: {
+        name: "预警记录",
+        url: `${config.API_URL}/ops/parking`,
+        get: async function (data = {}) {
+            return await http.post(`${this.url}/getPage`, data);
+        },
+
+        add: async function (data = {}) {
+            return await http.post(`${this.url}/save`, data);
+        },
+
+        edit: async function (data = {}) {
+            return await http.post(`${this.url}/update`, data);
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/remove`, data);
+        },
+
+        batchDel: async function (data = {}) {
+            return await http.post(`${this.url}/batchRemove`, data);
+        }
+    },
+
+    dataMock: {
+        url: `${config.API_URL}/ops/parking`,
+        name: "数据模拟-参数/复制",
+        copyData: async function (data = {}) {
+            return await http.post(`${this.url}/copyData`, data);
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
+        }
+    }
+}

+ 4 - 0
src/api/model/scc.js

@@ -45,6 +45,10 @@ export default {
             alarm: async function (data = {}) {
                 return await http.post(`${this.url}/makeWarningData`, data);
             }
+        },
+
+        removeData: async function (data = {}) {
+            return await http.post(`${config.API_URL}/ops/scc/removeData`, data);
         }
     }
 }

+ 48 - 0
src/api/model/smokedet.js

@@ -0,0 +1,48 @@
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+    mounted: {
+        url: `${config.API_URL}/ops/smokedet/getMountedList`,
+        name: "安装点查询",
+        get: async function (data = {}) {
+            return await http.post(this.url, data);
+        }
+    },
+
+    record: {
+        name: "监测记录",
+        url: `${config.API_URL}/ops/smokedet`,
+        get: async function (data = {}) {
+            return await http.post(`${this.url}/getPage`, data);
+        },
+
+        add: async function (data = {}) {
+            return await http.post(`${this.url}/save`, data);
+        },
+
+        edit: async function (data = {}) {
+            return await http.post(`${this.url}/update`, data);
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/remove`, data);
+        },
+
+        batchDel: async function (data = {}) {
+            return await http.post(`${this.url}/batchRemove`, data);
+        }
+    },
+
+    dataMock: {
+        url: `${config.API_URL}/ops/smokedet`,
+        name: "数据模拟-参数/复制",
+        makeData: async function (data = {}) {
+            return await http.post(`${this.url}/makeData`, data);
+        },
+        
+        removeData: async function (data = {}) {
+            return await http.post(`${this.url}/removeData`, data);
+        }
+    }
+}

+ 12 - 0
src/api/model/tcm.js

@@ -85,6 +85,18 @@ export default {
             alarm: async function (data = {}) {
                 return await http.post(`${this.url}/makeWarningData`, data);
             }
+        },
+
+        removeData: {
+            url: `${config.API_URL}/ops/tcm`,
+            name: "数据模拟-批量删除",
+            normal: async function (data = {}) {
+                return await http.post(`${this.url}/removeData`, data);
+            },
+
+            attendance: async function (data = {}) {
+                return await http.post(`${this.url}/removePersonData`, data);
+            }
         }
     }
 }

+ 1 - 1
src/components/scPageHeader/index.vue

@@ -20,7 +20,7 @@
                         <template #icon><sc-iconify icon="ant-design:filter-outlined"></sc-iconify></template>筛选
                     </el-button>
                     <el-button v-if="$attrs.onAdd" type="primary" @click="$emit('add')">
-                        <template #icon><sc-iconify icon="ant-design:plus-outlined"></sc-iconify></template>{{ $attrs.addText || "新增" }}
+                        <template #icon><sc-iconify :icon="$attrs.addIcon || 'ant-design:plus-outlined'"></sc-iconify></template>{{ $attrs.addText || "新增" }}
                     </el-button>
                     <slot name="extra-right"></slot>
                 </div>

+ 1 - 1
src/components/scTable/helper.js

@@ -75,7 +75,7 @@ export const mapFormItemDatePicker = (field, title, config = {}) => ({
             endPlaceholder: "结束日期",
             valueFormat: "YYYY-MM-DD HH:mm:ss",
             placeholder: `请选择${title}`,
-            [XEUtils.get(config, "props.type")?.includes("range") ? "defaultTime" : ""]: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)],
+            defaultTime: XEUtils.get(config, "props.type")?.includes("range") ? [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)] : new Date(2000, 1, 1, 23, 59, 59),
             ...XEUtils.get(config, "props")
         },
         ...XEUtils.omit(config, "props")

+ 3 - 1
src/components/scTable/renderer/pager-batch-del.vue

@@ -1,5 +1,7 @@
 <template>
-    <el-button type="danger" plain :disabled="!ids || !ids.length" @click="$emit('success', ids)">批量删除</el-button>
+    <el-button type="danger" plain :disabled="!ids || !ids.length" @click="$emit('success', ids)">
+        <sc-iconify icon="ant-design:delete-outlined"></sc-iconify>
+    </el-button>
 </template>
 
 <script setup>

+ 1 - 1
src/components/scUpload/fileViewer.vue

@@ -85,7 +85,7 @@ export default {
         downloadFile() {
             this.$API.common.folder.download(this.filePath).then(res => {
                 const a = document.createElement("a");
-                const blob = new Blob([res.data], { type: this.fileType });
+                const blob = new Blob([res], { type: this.fileType });
                 a.download = this.fileName;
                 a.href = URL.createObjectURL(blob);
                 a.click();

+ 23 - 17
src/components/scUpload/index.vue

@@ -7,10 +7,13 @@
 			<el-image class="image" :src="file.tempFile" fit="cover"></el-image>
 		</div>
 		<div v-if="file && file.status=='success'" class="sc-upload__img">
-            <vxe-image v-if="isImage(file.mineType)" class="image" :src="urlPrefix[apiKey] + file.path" :toolbar-config="imageToolbar"></vxe-image>
-			<sc-video v-if="isVideo(file.mineType)" :src="urlPrefix[apiKey] + file.path" showMask @play="videoPlay"></sc-video>
+            <vxe-image v-if="isImage(file.mineType)" class="image" :src="'/api/folder/' + file.path" :toolbar-config="imageToolbar"></vxe-image>
+			<sc-video v-if="isVideo(file.mineType)" :src="'/api/folder/' + file.path" showMask @play="videoPlay"></sc-video>
 			
 			<div class="sc-upload__img-actions" v-if="!disabled">
+                <el-button type="primary" @click="handleDownload">
+                    <sc-iconify icon="ant-design:download-outlined"></sc-iconify>
+                </el-button>
                 <el-button type="danger" @click="handleRemove">
                     <sc-iconify icon="ant-design:delete-outlined"></sc-iconify>
                 </el-button>
@@ -52,12 +55,11 @@
 </template>
 
 <script>
-import { urlPrefix, fileTypes } from "./main";
+import { fileTypes } from "./main";
 
 export default {
     props: {
         modelValue: { type: Object, default: () => {} },
-        apiKey: { type: String, default: () => "folder" },
         width: { type: Number, default: 148 },
         height: { type: Number, default: 148 },
         title: { type: String, default: "" },
@@ -74,7 +76,6 @@ export default {
 
     data() {
         return {
-            urlPrefix,
             imageToolbar: {
                 print: false,
                 download: true
@@ -149,18 +150,11 @@ export default {
                 type: "warning",
                 confirmButtonText: "移除"
             }).then(() => {
-                if (this.apiKey == "folder") {
-                    if (file.id) {
-                        this.$API.common[this.apiKey].rm(file.id).then(res => {
-                            if (res.code == 200) this.clearFiles();
-                        }).catch(() => {});
-                    } else this.clearFiles();
-                } else {
-                    this.$API.common[this.apiKey].rm(this.file.id || this.file.path).then(res => {
-                        this.clearFiles();
-                        this.$emit("removeSuccess");
+                if (file.id) {
+                    this.$API.common.folder.rm(file.id).then(res => {
+                        if (res.code == 200) this.clearFiles();
                     }).catch(() => {});
-                }
+                } else this.clearFiles();
             }).catch(() => {});
         },
 
@@ -233,7 +227,7 @@ export default {
             const data = new FormData();
             data.append(param.filename, param.file);
 
-            this.$API.common[this.apiKey].up(data, {
+            this.$API.common.folder.up(data, {
                 onUploadProgress: e => {
                     const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);
                     param.onProgress({ percent });
@@ -247,6 +241,18 @@ export default {
         videoPlay() {
             this.showViewer = true;
             nextTick(() => this.$refs.fileViewer.init(this.file));
+        },
+
+        handleDownload() {
+            const file = JSON.parse(this.value);
+            
+            this.$API.common.folder.download(file.path).then(res => {
+                const a = document.createElement("a");
+                const blob = new Blob([res], { type: "image/jpeg" });
+                a.download = `/api/folder/${file.path}`.replaceAll("/", "_");
+                a.href = URL.createObjectURL(blob);
+                a.click();
+            });
         }
     }
 }

+ 0 - 5
src/components/scUpload/main.js

@@ -1,8 +1,3 @@
-export const urlPrefix = {
-    folder: "/api/folder/",
-    minio: "/minio"
-}
-
 export const fileTypes = {
     "image/gif": "image",
     "image/jpeg": "image",

+ 373 - 0
src/components/scUpload/minio.vue

@@ -0,0 +1,373 @@
+<template>
+	<div class="sc-upload" :class="{ 'sc-upload-round': round }" :style="style">
+		<div v-if="file && file.status != 'success'" class="sc-upload__uploading">
+			<div class="sc-upload__progress">
+				<el-progress :percentage="file.percentage" text-inside :stroke-width="16" />
+			</div>
+			<el-image class="image" :src="file.tempFile" fit="cover"></el-image>
+		</div>
+		<div v-if="file && file.status=='success'" class="sc-upload__img">
+            <vxe-image class="image" :src="formatUrl(file.path)" :toolbar-config="imageToolbar"></vxe-image>
+			
+			<div class="sc-upload__img-actions" v-if="!disabled">
+                <el-button type="primary" @click="handleDownload">
+                    <sc-iconify icon="ant-design:download-outlined"></sc-iconify>
+                </el-button>
+                <el-button type="danger" @click="handleRemove">
+                    <sc-iconify icon="ant-design:delete-outlined"></sc-iconify>
+                </el-button>
+			</div>
+		</div>
+		<el-upload v-if="!file" class="uploader" ref="uploader"
+			action=""
+			:auto-upload="cropper ? false : true"
+			:disabled="disabled"
+			:show-file-list="false"
+			:accept="accept"
+			:limit="1"
+			:http-request="request"
+			:on-change="change"
+			:before-upload="before"
+			:on-success="success"
+			:on-error="error"
+			:on-exceed="handleExceed">
+			<slot>
+				<div class="el-upload--picture-card" :class="disabled && 'is-disabled'">
+					<div class="file-empty">
+						<el-icon><component :is="icon" /></el-icon>
+						<h4 v-if="title">{{ title }}</h4>
+					</div>
+				</div>
+			</slot>
+		</el-upload>
+		<span style="display:none!important"><el-input v-model="value"></el-input></span>
+		<el-dialog title="剪裁" draggable v-model="cropperDialogVisible" :width="580" @closed="cropperClosed" destroy-on-close>
+			<sc-cropper :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio" ref="cropper"></sc-cropper>
+			<template #footer>
+				<el-button @click="cropperDialogVisible = false" >取 消</el-button>
+				<el-button type="primary" @click="cropperSave">确 定</el-button>
+			</template>
+		</el-dialog>
+	</div>
+
+    <file-viewer v-if="showViewer" ref="fileViewer" @closed="showViewer = false"></file-viewer>
+</template>
+
+<script>
+export default {
+    props: {
+        modelValue: { type: Object, default: () => {} },
+        apiKey: { type: String, default: "airecord" },
+        width: { type: Number, default: 148 },
+        height: { type: Number, default: 148 },
+        title: { type: String, default: "" },
+        accept: { type: String, default: "image/jpeg, image/png" },
+        icon: { type: String, default: "el-icon-plus" },
+        maxSize: { type: Number, default: 50 },
+        disabled: { type: Boolean, default: false },
+        round: { type: Boolean, default: false },
+        onSuccess: { type: Function, default: () => { return true } },
+        cropper: { type: Boolean, default: false },
+        compress: { type: Number, default: 1 },
+        aspectRatio: {type: Number, default: NaN }
+    },
+
+    data() {
+        return {
+            imageToolbar: {
+                print: false,
+                download: true
+            },
+
+            value: "{}",
+            file: null,
+            style: {
+                width: this.width + "px",
+                height: this.height + "px"
+            },
+            cropperDialogVisible: false,
+            cropperFile: null,
+
+            showViewer: false
+        }
+    },
+
+    watch: {
+        modelValue(val) {
+            this.value = JSON.stringify(val);
+            this.newFile(val);
+        },
+
+        value(val) {
+            this.$emit("update:modelValue", JSON.parse(val));
+        }
+    },
+
+    mounted() {
+        if (this.modelValue) {
+            this.value = JSON.stringify(this.modelValue);
+            this.newFile(this.modelValue);
+        }
+    },
+    
+    methods: {
+        formatUrl(path) {
+            return path.startsWith(`/${this.apiKey}`) ? "/minio" + path : "data:image/jpeg;base64," + path;
+        },
+
+        newFile(data) {
+            this.file = Object.keys(data).length ? { status: "success", ...data } : null;
+        },
+
+        cropperSave() {
+            this.$refs.cropper.getCropFile(file => {
+
+                file.uid = this.cropperFile.uid;
+                this.cropperFile.raw = file;
+
+                this.file = this.cropperFile;
+                this.file.tempFile = URL.createObjectURL(this.file.raw);
+                this.$refs.uploader.submit();
+
+            }, this.cropperFile.name, this.cropperFile.type);
+            this.cropperDialogVisible = false;
+        },
+
+        cropperClosed() {
+            URL.revokeObjectURL(this.cropperFile.tempCropperFile);
+            delete this.cropperFile.tempCropperFile;
+        },
+
+        handleRemove() {
+            const file = JSON.parse(this.value)
+            const fileName = this.formatUrl(file.path).replaceAll("/", "_");
+            
+            this.$confirm(`是否移除 ${fileName}? 此操作不可逆!`, "提示", {
+                type: "warning",
+                confirmButtonText: "移除"
+            }).then(() => {
+                this.$API.common.minio.rm(file.path).then(res => {
+                    this.clearFiles();
+                    this.$emit("removeSuccess");
+                }).catch(() => {});
+            }).catch(() => {});
+        },
+
+        clearFiles() {
+            URL.revokeObjectURL(this.file.tempFile);
+            this.value = "{}";
+            this.file = null;
+            this.$nextTick(() => this.$refs.uploader.clearFiles());
+        },
+
+        change(file, files) {
+            if (files.length > 1) files.splice(0, 1);
+
+            if (this.cropper && file.status == "ready") {
+                this.cropperFile = file;
+                this.cropperFile.tempCropperFile = URL.createObjectURL(file.raw);
+                this.cropperDialogVisible = true;
+                return false;
+            }
+
+            this.file = file;
+            if (file.status == "ready") file.tempFile = URL.createObjectURL(file.raw);
+        },
+
+        before(file) {
+            const maxSize = file.size / 1024 / 1024 < this.maxSize;
+            if (!maxSize) {
+                this.$message.warning(`上传文件大小不能超过 ${this.maxSize}MB!`);
+                this.clearFiles();
+                return false;
+            }
+        },
+
+        handleExceed(files) {
+            const file = files[0];
+            file.uid = genFileId();
+            this.$refs.uploader.handleStart(file);
+        },
+
+        success(res, file) {
+            // 释放内存删除blob
+            URL.revokeObjectURL(file.tempFile);
+            delete file.tempFile;
+            let os = this.onSuccess(res, file);
+            if (os != undefined && os == false) {
+                this.$nextTick(() => {
+                    this.file = null;
+                    this.value = "{}";
+                });
+                return false;
+            }
+            file.path = res.path;
+            this.value = JSON.stringify({ path: res.path });
+        },
+
+        error(message) {
+            this.$nextTick(() => this.clearFiles());
+            this.$notify.error({ title: "上传文件未成功", message });
+        },
+
+        request(param) {
+            const data = new FormData();
+            data.append(param.filename, param.file);
+
+            this.$API.common.minio[this.apiKey](data, {
+                onUploadProgress: e => {
+                    const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);
+                    param.onProgress({ percent });
+                }
+            }).then(res => {
+                if (res.code == 200) param.onSuccess({ path: res.expands.file });
+                else param.onError(res.message || "未知错误");
+            }).catch(err => param.onError(err));
+        },
+
+        videoPlay() {
+            this.showViewer = true;
+            nextTick(() => this.$refs.fileViewer.init(this.file));
+        },
+
+        handleDownload() {
+            const file = JSON.parse(this.value);
+            const fileName = this.formatUrl(file.path).replaceAll("/", "_");
+            
+            if (file.path.startsWith(`/${this.apiKey}`)) {
+                this.$API.common.minio.download(file.path).then(res => {
+                    const a = document.createElement("a");
+                    const blob = new Blob([res], { type: "image/jpeg" });
+                    a.download = fileName;
+                    a.href = URL.createObjectURL(blob);
+                    a.click();
+                });
+            } else  {
+                const binaryData = atob(file.path);
+                const length = binaryData.length;
+                const uint8Array = new Uint8Array(length);
+                for (let i = 0; i < length; i++) {
+                    uint8Array[i] = binaryData.charCodeAt(i);
+                }
+                const a = document.createElement("a");
+                const blob = new Blob([uint8Array], { type: "image/jpeg" });
+                a.download = fileName;
+                a.href = URL.createObjectURL(blob);
+                a.click();
+            }
+        }
+    }
+}
+</script>
+
+<style scoped>
+.el-form-item.is-error .sc-upload .el-upload--picture-card {
+  border-color: var(--el-color-danger);
+}
+.sc-upload .el-upload--picture-card {
+  width: 100%;
+  height: 100%;
+  border-radius: 0;
+}
+
+.sc-upload .el-upload--picture-card.is-disabled {
+  border-color: var(--el-border-color-darker);
+  cursor: not-allowed;
+}
+
+.sc-upload .uploader,
+.sc-upload:deep(.el-upload) {
+  width: 100%;
+  height: 100%;
+  justify-content: unset;
+}
+
+.sc-upload__img {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.sc-upload__img .image {
+  width: 100%;
+  height: 100%;
+  border: 1px solid var(--el-border-color);
+  cursor: pointer;
+}
+.sc-upload__img-actions {
+  z-index: 120;
+  position: absolute;
+  top: 0;
+  right: 0;
+  display: none;
+}
+.sc-upload__img-actions .el-button {
+  width: 25px;
+  height: 25px;
+  padding: 0;
+  border-radius: 0;
+}
+.sc-upload__img-actions .el-button + .el-button {
+ margin-left: 0;
+}
+.sc-upload__img:hover .sc-upload__img-actions {
+  display: flex;
+}
+.sc-upload__uploading {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.sc-upload__progress {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: var(--el-overlay-color-lighter);
+  z-index: 1;
+  padding: 10px;
+}
+.sc-upload__progress .el-progress {
+  width: 100%;
+}
+.sc-upload__uploading .image {
+  width: 100%;
+  height: 100%;
+}
+
+.sc-upload .file-empty {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+}
+.sc-upload .file-empty i {
+  font-size: 28px;
+}
+.sc-upload .file-empty h4 {
+  font-size: 12px;
+  font-weight: normal;
+  color: #8c939d;
+  margin-top: 8px;
+}
+
+.sc-upload.sc-upload-round {
+  border-radius: 50%;
+  overflow: hidden;
+}
+.sc-upload.sc-upload-round .el-upload--picture-card {
+  border-radius: 50%;
+}
+.sc-upload.sc-upload-round .sc-upload__img-actions {
+  top: auto;
+  left: 0;
+  right: 0;
+  bottom: 0;
+}
+.sc-upload.sc-upload-round .sc-upload__img-actions span {
+  width: 100%;
+}
+</style>

+ 1 - 1
src/components/scUpload/multiple.vue

@@ -190,7 +190,7 @@ export default {
             this.$API.common.folder.download(file.path).then(res => {
                 this.loading = false;
                 const a = document.createElement("a");
-                const blob = new Blob([res.data], { type: file.mineType });
+                const blob = new Blob([res], { type: file.mineType });
                 a.download = this.file.name;
                 a.href = URL.createObjectURL(blob);
                 a.click();

+ 34 - 10
src/config/route.js

@@ -91,7 +91,7 @@ const routes = [
     {
         name: "dataMock",
         path: "/dataMock",
-        meta: { title: "数据管理与模拟", icon: "majesticons:data-plus-line" },
+        meta: { title: "数据管理与模拟", icon: "ix:data-management" },
         redirect: "/dataMock/aihazard",
         children: [
             {
@@ -102,9 +102,9 @@ const routes = [
             },
             {
                 name: "towerMock",
-                path: "/dataMock/tcm",
+                path: "/dataMock/tower",
                 meta: { title: "数据管理与模拟-塔机监测", icon: "mingcute:tower-crane-line" },
-                component: "dataMock/tcm"
+                component: "dataMock/tower"
             },
             {
                 name: "elevatorMock",
@@ -112,23 +112,41 @@ const routes = [
                 meta: { title: "数据管理与模拟-施工升降电梯监测", icon: "icon-park-outline:elevator" },
                 component: "dataMock/elevator"
             },
+            {
+                name: "smokeMock",
+                path: "/dataMock/smoke",
+                meta: { title: "数据管理与模拟-智能烟感", icon: "cbi:smoke-detector" },
+                component: "dataMock/smoke"
+            },
+            {
+                name: "edgeprotMock",
+                path: "/dataMock/edgeprot",
+                meta: { title: "数据管理与模拟-智能临边防护网监测", icon: "lineicons:netlify" },
+                component: "dataMock/edgeprot"
+            },
+            {
+                name: "warehouseMock",
+                path: "/dataMock/warehouse",
+                meta: { title: "数据管理与模拟-库房监测", icon: "lucide:house-wifi" },
+                component: "dataMock/warehouse"
+            },
             {
                 name: "standardMock",
-                path: "/dataMock/scc",
+                path: "/dataMock/standard",
                 meta: { title: "数据管理与模拟-标养室监测", icon: "dashicons:dashboard" },
-                component: "dataMock/scc"
+                component: "dataMock/standard"
             },
             {
                 name: "envMock",
-                path: "/dataMock/envdev",
+                path: "/dataMock/env",
                 meta: { title: "数据管理与模拟-环境监测", icon: "fluent:earth-leaf-16-regular" },
-                component: "dataMock/envdev"
+                component: "dataMock/env"
             },
             {
                 name: "sprayMock",
-                path: "/dataMock/autospray",
+                path: "/dataMock/spray",
                 meta: { title: "数据管理与模拟-自动喷淋系统", icon: "covid:vaccine-protection-sanitizer-spray" },
-                component: "dataMock/autospray"
+                component: "dataMock/spray"
             },
             {
                 name: "carwashMock",
@@ -136,10 +154,16 @@ const routes = [
                 meta: { title: "数据管理与模拟-渣土运输管理", icon: "map:car-wash" },
                 component: "dataMock/carwash"
             },
+            {
+                name: "parkingMock",
+                path: "/dataMock/parking",
+                meta: { title: "数据管理与模拟-夜间施工监测", icon: "material-symbols:partly-cloudy-night-outline" },
+                component: "dataMock/parking"
+            },
             {
                 name: "broadcastMock",
                 path: "/dataMock/broadcast",
-                meta: { title: "数据管理与模拟-智能广播", icon: "bi:broadcast-pin" },
+                meta: { title: "数据管理与模拟-智能广播", icon: "ri:broadcast-fill" },
                 component: "dataMock/broadcast"
             }
         ]

+ 24 - 1
src/utils/basicDic.js

@@ -14,7 +14,30 @@ export const deviceStateDic = {
 
 /* ************************************************************************* */ 
 export const taskDic = {
+    mountedSelect: {
+        broadcast: { prefix: "task", label: "广播任务" },
+        parking: { prefix: "gate", label: "场区闸口" }
+    },
+
     option: {
+        tcm: {
+            tcm_warning: "报警数据",
+            tcm_person: "考勤数据",
+            tcm_person_remove: "考勤数据删除"
+        },
+        elevator: {
+            elevator_warning: "报警数据",
+            elevator_person: "考勤数据",
+            elevator_person_remove: "考勤数据删除"
+        },
+        scc: {
+            scc_warning: "报警数据",
+        },
+        edgeprot: {
+            edgeprot_warning: "报警数据"
+        }
+    },
+    childrenOption: {
         d: "删除数据",
         i: "新增数据"
     },
@@ -27,7 +50,7 @@ export const taskDic = {
     }
 }
 
-export const tempProModel = ["car_rinse", "aihazard", "envdev", "tcm", "elevator"]
+export const tempProModel = ["car_rinse", "aihazard", "envdev", "tcm", "elevator", "parking"]
 export const dataSource = ["现存数据", "iot数据", "第三方数据"]
 
 export const aiTypeDic = {

+ 38 - 38
src/views/basic/project/detail.vue

@@ -1,38 +1,38 @@
 <template>
-    <el-dialog v-model="visible" :title="titleMap[mode]" width="860" :close-on-click-modal="false" @closed="$emit('closed')">
+    <el-dialog v-model="visible" :title="titleMap[mode]" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
             <el-card header="基础信息" shadow="never">
                 <el-row>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="项目名称" prop="projectName">
                             <el-input v-model="form.projectName" placeholder="请输入项目名称"></el-input>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="项目别名">
                             <el-input v-model="form.projectAliasName" placeholder="请输入项目别名"></el-input>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="所属企业" prop="deptId">
                             <el-tree-select v-model="form.deptId" v-bind="props.treeSelectProps" :props="{ label: 'name', value: 'deptId' }" ></el-tree-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="项目类型" prop="projectType">
                             <el-select v-model="form.projectType" placeholder="请选择项目类型">
                                 <el-option v-for="item in typeDic" :key="item" :value="item"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="项目状态" prop="projectStatus">
                             <el-select v-model="form.projectStatus" placeholder="请选择项目状态">
                                 <el-option v-for="item in statusDic" :key="item" :value="item"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="建设规模" prop="projectScale">
                             <el-radio-group v-model="form.projectScale">
                                 <el-radio value="3">小型</el-radio>
@@ -41,56 +41,56 @@
                             </el-radio-group>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="投资总额">
                             <el-input-number v-model="form.projectMoney" :min="0" :precision="2" :controls="false" placeholder="请输入投资总额">
                                 <template #suffix>万元</template>
                             </el-input-number>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="安监备案号">
                             <el-input v-model="form.projectBackupsNumber" clearable placeholder="请输入安监备案号"></el-input>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="建筑面积">
                             <el-input-number v-model="form.projectBuildingArea" :min="0" :precision="2" :controls="false" placeholder="请输入建筑面积">
                                 <template #suffix>㎡</template>
                             </el-input-number>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="结构形式">
                             <el-input v-model="form.projectStructure" clearable placeholder="请输入结构形式"></el-input>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="合同工期">
                             <el-date-picker v-model="form.contractDate" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="设计使用年限">
                             <el-input-number v-model="form.designLife" :min="0" :controls="false" placeholder="请输入设计使用年限"></el-input-number>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="发证机关">
                             <el-input v-model="form.licenceAuthority" clearable placeholder="请输入发证机关"></el-input>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="发证日期">
                             <el-date-picker v-model="form.licenceDate" value-format="YYYY-MM-DD" placeholder="请选择发证日期"></el-date-picker>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="监督机构">
                             <el-input v-model="form.superviseAuthority" clearable placeholder="请输入工程质量监督机构"></el-input>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="施工许可证编号">
                             <el-input v-model="form.licenceSgNo" clearable placeholder="请输入施工许可证编号"></el-input>
                         </el-form-item>
@@ -110,27 +110,27 @@
             <el-card header="接口信息" shadow="never">
                 <el-card header="青岛市市平台接口信息" shadow="never">
                     <el-row>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目名称">
                                 <el-input v-model="form.qdProjectName" clearable placeholder="请输入项目名称"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目ID" prop="qdProjectId">
                                 <el-input v-model="form.qdProjectId" clearable placeholder="请输入项目ID"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目TOKEN">
                                 <el-input v-model="form.qdProjectToken" clearable placeholder="请输入项目TOKEN"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目PROVIDER">
                                 <el-input v-model="form.qdProjectProvider" clearable placeholder="请输入项目PROVIDER"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="劳务ID">
                                 <el-input v-model="form.qdProjectNo" clearable placeholder="请输入劳务ID"></el-input>
                             </el-form-item>
@@ -140,27 +140,27 @@
 
                 <el-card header="崂山区平台接口信息" shadow="never">
                     <el-row>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目名称">
                                 <el-input v-model="form.lsProjectName" clearable placeholder="请输入项目名称"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目ID" prop="qdProjectId">
                                 <el-input v-model="form.lsProjectId" clearable placeholder="请输入项目ID"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目TOKEN">
                                 <el-input v-model="form.lsProjectToken" clearable placeholder="请输入项目TOKEN"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目PROVIDER">
                                 <el-input v-model="form.lsProjectProvider" clearable placeholder="请输入项目PROVIDER"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="劳务ID">
                                 <el-input v-model="form.lsProjectNo" clearable placeholder="请输入劳务ID"></el-input>
                             </el-form-item>
@@ -170,27 +170,27 @@
 
                 <el-card header="青岛市市政平台接口信息" shadow="never">
                     <el-row>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目名称">
                                 <el-input v-model="form.szProjectName" clearable placeholder="请输入项目名称"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目ID" prop="qdProjectId">
                                 <el-input v-model="form.szProjectId" clearable placeholder="请输入项目ID"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目TOKEN">
                                 <el-input v-model="form.szProjectToken" clearable placeholder="请输入项目TOKEN"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="项目PROVIDER">
                                 <el-input v-model="form.szProjectProvider" clearable placeholder="请输入项目PROVIDER"></el-input>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="劳务ID">
                                 <el-input v-model="form.szProjectNo" clearable placeholder="请输入劳务ID"></el-input>
                             </el-form-item>
@@ -201,41 +201,41 @@
 
             <el-card header="位置信息" shadow="never">
                 <el-row>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="省/直辖市">
                             <el-select v-model="form.province" clearable :disabled="!districtComplete" placeholder="选择省/直辖市" @change="changeDistrict($event, 'province')">
                                 <el-option v-for="(item, index) in districtList.province" :key="index" :label="item.name" :value="item.name"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="地级市">
                             <el-select v-model="form.city" clearable :disabled="!districtComplete" placeholder="选择地级市" @change="changeDistrict($event, 'city')">
                                 <el-option v-for="(item, index) in districtList.city" :key="index" :label="item.name" :value="item.name"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="区县">
                             <el-select v-model="form.region" clearable :disabled="!districtComplete" placeholder="选择区县" @change="changeDistrict($event, 'region')">
                                 <el-option v-for="(item, index) in districtList.region" :key="index" :label="item.name" :value="item.name"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="街道">
                             <el-select v-model="form.street" clearable :disabled="!districtComplete" placeholder="选择街道" @change="changeDistrict($event, 'street')">
                                 <el-option v-for="(item, index) in districtList.street" :key="index" :label="item.name" :value="item.name"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="项目地址">
                             <el-input v-model="form.address" clearable :disabled="!districtComplete" placeholder="请输入项目地址" @change="addressChange"></el-input>
 				            <div class="el-form-item-msg">输入地址进行查询,并点击坐标点以确认坐标。若无坐标点请输入更详情地址</div>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="项目坐标">
                             <el-input v-model="form.projectCoordinates" readonly placeholder="选择行政区级别或输入地址查询"></el-input>
                         </el-form-item>

+ 1 - 1
src/views/basic/project/items.vue

@@ -1,5 +1,5 @@
 <template>
-    <el-dialog v-model="visible" :title="`${form.projectName}-验收清单`" width="860" :close-on-click-modal="false" @closed="$emit('closed')">
+    <el-dialog v-model="visible" :title="`${form.projectName}-验收清单`" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" label-width="120">
             <el-form-item required>
                 <template #label>

+ 137 - 0
src/views/dataMock/aihazard/batchDel.vue

@@ -0,0 +1,137 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-form-item label="数据来源" prop="isTemp">
+                <el-radio-group v-model="form.isTemp" @change="refreshTable">
+                    <el-radio :value="true">模版项目</el-radio>
+                    <el-radio :value="false">其他项目</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            
+            <el-row>
+                <template v-if="!form.isTemp">
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="项目名称" prop="targetProjectId">
+                            <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                                <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="设备安装点" prop="targetMountedId">
+                            <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                                <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" :isTemp="form.isTemp" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    isTemp: false,
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetMountedId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    isTemp: [{ required: true }],
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: form.value.isTemp ? "targetProjectIdNot" : "targetProjectId"  },
+        form.value.isTemp ? {} : { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.aihazard.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            if (form.value.isTemp) {
+                XEUtils.set(data, "targetProjectId", 1);
+                XEUtils.set(data, "targetMountedId", XEUtils.get(XEUtils.find(mounteds.value, item => item.projectId == 1), "id"));
+            }
+
+            isSaving.value = true;
+            API.aihazard.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 27 - 45
src/views/dataMock/aihazard/components/record/detail.vue

@@ -21,8 +21,8 @@
                     <el-option v-for="(label, key) in aiTypeDic" :key="key" :label="label" :value="key"></el-option>
                 </el-select>
             </el-form-item>
-            <el-form-item label="抓拍图片" prop="features.bigImage.file">
-                <sc-upload v-model="form.features.bigImage.file" apiKey="minio" :width="140" :height="180" accept="image/jpeg" @removeSuccess="removeSuccess"></sc-upload>
+            <el-form-item label="抓拍图片" prop="fileData">
+                <sc-upload v-model="form.fileData" :width="240" :height="143" @removeSuccess="removeSuccess"></sc-upload>
             </el-form-item>
         </el-form>
 
@@ -38,7 +38,7 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { aiTypeDic } from "@/utils/basicDic";
-import scUpload from "@/components/scUpload/index";
+import scUpload from "@/components/scUpload/minio";
 
 const $emit = defineEmits(["success", "closed"]);
 const props = defineProps({
@@ -61,18 +61,15 @@ const form = ref({
     mountedId: null,
     recordType: null,
     createTime: null,
-    features: {
-        bigImage: {
-            file: {}
-        }
-    }
+    features: {},
+    fileData: {}
 });
 const rules = reactive({
     projectId: [{ required: true, message: "请选择所属项目" }],
     mountedId: [{ required: true, message: "请选择设备安装点" }],
     createTime: [{ required: true, message: "请选择抓拍时间" }],
     recordType: [{ required: true, message: "请选择识别结果" }],
-    "features.bigImage.file": [{ required: true, validator: (rule, value, callback) => {
+    fileData: [{ required: true, validator: (rule, value, callback) => {
         if (XEUtils.isEmpty(value)) return callback(new Error("请上传抓拍图片"));
         callback();
     }}]
@@ -93,22 +90,13 @@ const open = () => {
 const setData = data => {
     open();
     mode.value = "edit";
-    XEUtils.objectEach(form.value, (_, key) => {
+    XEUtils.objectEach(XEUtils.omit(form.value, "fileData"), (_, key) => {
         if (key == "features") {
-            const features = XEUtils.toStringJSON(XEUtils.get(data, key));
+            const features = XEUtils.toStringJSON(XEUtils.get(data, key, "{}"));
             const path = XEUtils.get(features, "bigImage.image", "");
-
-            XEUtils.set(form.value, key, {
-                ...XEUtils.omit(features, "bigImage"),
-                bigImage: {
-                    ...XEUtils.get(features, "bigImage"),
-                    file: path ? {
-                        name: `/minio${path}`.replaceAll("/", "_"),
-                        mineType: "image/jpeg",
-                        path
-                    } : {}
-                }
-            })
+            
+            path && XEUtils.set(form.value, "fileData", { path });
+            XEUtils.set(form.value, key, features);
         } else XEUtils.set(form.value, key, XEUtils.get(data, key));
     });
 }
@@ -117,16 +105,10 @@ const formRef = ref();
 const submit = () => {
     formRef.value.validate(valid => {
         if (valid) {
-            const data = {
-                ...XEUtils.omit(form.value, "features"),
-                features: XEUtils.toJSONString({
-                    ...form.value.features,
-                    bigImage: {
-                        ...XEUtils.omit(form.value.features.bigImage, "file"),
-                        image: form.value.features.bigImage.file.path
-                    }
-                })
-            }
+            const data = XEUtils.omit(form.value, "features", "fileData");
+            const features = XEUtils.clone(form.value.features);
+            XEUtils.set(features, "bigImage.image", form.value.fileData.path);
+            XEUtils.set(data, "features", XEUtils.toJSONString(features));
 
             isSaving.value = true;
             API.aihazard.record[mode.value](data).then(() => {
@@ -142,19 +124,19 @@ const submit = () => {
 }
 
 const removeSuccess = () => {
-    const data = {
-        id: form.value.id,
-        features: JSON.stringify({
-            ...form.value.features,
-            bigImage: {
-                ...XEUtils.omit(form.value.features.bigImage, "file"),
-                image: ""
-            }
-        })
+    if (form.value.id) {
+        const features = XEUtils.clone(form.value.features);
+        XEUtils.set(features, "bigImage.image", "");
+        
+        const data = {
+            id: form.value.id,
+            mountedId: form.value.mountedId,
+            features: XEUtils.toJSONString(features)
+        }
+        
+        isDel.value = true;
+        API.aihazard.record.edit(data);
     }
-    
-    isDel.value = true;
-    API.aihazard.record.edit(data);
 }
 
 defineExpose({

+ 1 - 1
src/views/dataMock/aihazard/components/record/index.vue

@@ -162,7 +162,7 @@ const table_edit = row => {
 }
 
 const table_del = ({ id }) => {
-    ElMessageBox.confirm("是否确认删除该监记录?", "删除警告", {
+    ElMessageBox.confirm("是否确认删除该监记录?", "删除警告", {
         type: "warning",
         confirmButtonText: "确定",
         cancelButtonText: "取消"

+ 16 - 7
src/views/dataMock/aihazard/detail.vue

@@ -30,7 +30,7 @@
                 </el-col>
                 <el-col :md="12" :xs="24">
                     <el-form-item label="模拟年份" prop="targetYear">
-                        <el-date-picker v-model="form.targetYear" type="year" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
+                        <el-date-picker v-model="form.targetYear" type="year" :clearable="false" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
@@ -62,14 +62,14 @@
 
             <el-row>
                 <template v-if="form.source == 'other'">
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="数据源项目" prop="sourceProjectId">
                             <el-select v-model="form.sourceProjectId" filterable placeholder="请选择数据源项目" @change="form.sourceMountedId = null, refreshTable()">
                                 <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
                             </el-select>
                         </el-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="数据源安装点" prop="sourceMountedId">
                             <el-select v-model="form.sourceMountedId" filterable placeholder="请选择数据源安装点" @change="refreshTable">
                                 <el-option v-for="item in filterSourceM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
@@ -78,9 +78,9 @@
                     </el-col>
                 </template>
 
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="抓拍时间" prop="sourceTime">
-                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
                     </el-form-item>
                 </el-col>
             </el-row>
@@ -97,9 +97,11 @@
 </template>
 
 <script setup>
+import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
 import dataTable from "./components/record";
 
 const route = useRoute();
@@ -108,6 +110,7 @@ const apiKey = ref("copyData");
 const visible = ref(false);
 const isSaving = ref(false);
 
+const shortcuts = rangeShortcuts();
 const form = ref({
     targetProjectId: TOOL.data.get("PROJECT_ID"),
     targetMountedId: null,
@@ -118,7 +121,7 @@ const form = ref({
     sourceProjectId: null,
     sourceProjectIdNot: 1,
     sourceMountedId: null,
-    sourceTime: []
+    sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
 });
 
 const rules = reactive({
@@ -175,7 +178,13 @@ const open = () => {
 
 const formRef = ref();
 const submit = key => {
-    formRef.value.validate(valid => {
+    let validateField = XEUtils.keys(rules);
+    if (key == "template") {
+        validateField = XEUtils.keys(XEUtils.omit(rules, "targetProjectId", "targetMountedId"));
+        formRef.value.clearValidate(["targetProjectId", "targetMountedId"]);
+    }
+
+    formRef.value.validateField(validateField, valid => {
         if (valid) {
             if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             

+ 22 - 8
src/views/dataMock/aihazard/index.vue

@@ -1,8 +1,13 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
             <template #extra-right>
-                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">数据录入</el-button>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
             </template>
         </sc-page-header>
 
@@ -13,32 +18,41 @@
         <component ref="componentRef" :is="allcomp[activeName]" taskType="aihazard" />
 	</el-container>
 
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
 </template>
 
 <script setup>
-import { workerStates } from "./main";
+import { workerStates } from "@/views/dataMock/parking/main";
 import comp from "./components";
 import monos from "@/views/dataMock/tasks/monos";
 import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
 
 const allcomp = { ...comp, monos };
 const activeName = ref("record");
 
 const componentRef = ref();
 const mockRef = ref();
-const dialog = ref(false);
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
 
 const table_add = () => componentRef.value.table_add();
 
 const mock_add = () => {
-    dialog.value = true;
+    dialog.value.mock = true;
     nextTick(() => mockRef.value?.open());
 }
 
-const refreshState = () => {
-    if (activeName.value == "monos") setTimeout(() => componentRef.value.refreshTable(), 2000);
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
 }
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
 </script>
 
 <style lang="scss" scoped>

+ 0 - 5
src/views/dataMock/aihazard/main.js

@@ -1,5 +0,0 @@
-export const workerStates = {
-    record: "设备监控",
-    monos: "任务中心",
-    template: "模版项目"
-}

+ 0 - 46
src/views/dataMock/autospray/index.vue

@@ -1,46 +0,0 @@
-<template>
-	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
-            <template #extra-right>
-                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">数据录入</el-button>
-            </template>
-        </sc-page-header>
-
-        <el-tabs v-model="activeName">
-            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
-        </el-tabs>
-
-        <component ref="componentRef" :is="allcomp[activeName]" taskType="autospray" />
-	</el-container>
-
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
-</template>
-
-<script setup>
-import { workerStates } from "./main";
-import comp from "./components";
-import monos from "@/views/dataMock/tasks/monos";
-import mockDetail from "./detail";
-
-const allcomp = { ...comp, monos };
-const activeName = ref("record");
-
-const componentRef = ref();
-const mockRef = ref();
-const dialog = ref(false);
-
-const table_add = () => componentRef.value.table_add();
-
-const mock_add = () => {
-    dialog.value = true;
-    nextTick(() => mockRef.value?.open());
-}
-
-const refreshState = () => {
-    if (activeName.value == "monos") setTimeout(() => componentRef.value.refreshTable(), 2000);
-}
-</script>
-
-<style lang="scss" scoped>
-.el-tabs {padding: 0 12px;background: #fff;}
-</style>

+ 122 - 0
src/views/dataMock/broadcast/batchDel.vue

@@ -0,0 +1,122 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="项目名称" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="广播任务" prop="targetTaskId">
+                        <el-select v-model="form.targetTaskId" filterable placeholder="请选择删除数据的广播任务" @change="refreshTable">
+                            <el-option v-for="item in filterTasks" :key="item.id" :label="item.taskName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetTaskId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetTaskId: [{ required: true, message: "请选择删除数据的广播任务" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: "targetProjectId"  },
+        { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "taskId", field: "targetTaskId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const tasks = ref([]);
+const filterTasks = computed(() => form.value.targetProjectId ? XEUtils.filter(tasks.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchTask = async () => {
+    const res = await API.broadcast.task.get();
+    tasks.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchTask();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetTaskId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+
+            isSaving.value = true;
+            API.broadcast.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 1 - 1
src/views/dataMock/broadcast/components/record/detail.vue

@@ -3,7 +3,7 @@
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
             <template v-if="props.projectId != 1">
                 <el-form-item label="所属项目" prop="projectId">
-                    <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                    <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.taskId = null">
                         <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
                     </el-select>
                 </el-form-item>

+ 0 - 1
src/views/dataMock/broadcast/detail.vue

@@ -45,7 +45,6 @@
                     </el-form-item>
                 </el-col>
 
-                <!--  s -->
                 <el-col :md="12" :xs="24">
                     <el-form-item label="数据处理" prop="isCover">
                         <el-radio-group v-model="form.isCover">

+ 23 - 8
src/views/dataMock/broadcast/index.vue

@@ -1,8 +1,13 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
             <template #extra-right>
-                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">数据录入</el-button>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
             </template>
         </sc-page-header>
 
@@ -13,32 +18,42 @@
         <component ref="componentRef" :is="allcomp[activeName]" taskType="broadcast" />
 	</el-container>
 
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
 </template>
 
+
 <script setup>
-import { workerStates } from "./main";
+import { workerStates } from "@/views/dataMock/smoke/main";
 import comp from "./components";
 import monos from "@/views/dataMock/tasks/monos";
 import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
 
 const allcomp = { ...comp, monos };
 const activeName = ref("record");
 
 const componentRef = ref();
 const mockRef = ref();
-const dialog = ref(false);
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
 
 const table_add = () => componentRef.value.table_add();
 
 const mock_add = () => {
-    dialog.value = true;
+    dialog.value.mock = true;
     nextTick(() => mockRef.value?.open());
 }
 
-const refreshState = () => {
-    if (activeName.value == "monos") setTimeout(() => componentRef.value.refreshTable(), 2000);
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
 }
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
 </script>
 
 <style lang="scss" scoped>

+ 0 - 4
src/views/dataMock/broadcast/main.js

@@ -1,4 +0,0 @@
-export const workerStates = {
-    record: "设备监控",
-    monos: "任务中心"
-}

+ 118 - 0
src/views/dataMock/carwash/batchDel.vue

@@ -0,0 +1,118 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-row>
+                <el-col :md="6" :xs="24">
+                    <el-form-item label="数据来源" prop="isTemp">
+                        <el-radio-group v-model="form.isTemp" @change="refreshTable">
+                            <el-radio :value="true">模版项目</el-radio>
+                            <el-radio :value="false">其他项目</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="18" :xs="24">
+                    <el-row>
+                        <el-col v-if="!form.isTemp" :md="12" :xs="24">
+                            <el-form-item label="项目名称" prop="targetProjectId">
+                                <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目">
+                                    <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                                </el-select>
+                            </el-form-item>
+                        </el-col>
+                        <el-col :md="12" :xs="24">
+                            <el-form-item label="数据时间" prop="targetTime">
+                                <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" :isTemp="form.isTemp" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    isTemp: false,
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    isTemp: [{ required: true }],
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "fpiId", field: form.value.isTemp ? "targetProjectIdNot" : "targetProjectId"  },
+        form.value.isTemp ? {} : { column: "fpiIdNot", field: "targetProjectIdNot" },
+        { column: "captureTimeBegin", field: "targetTime[0]" },
+        { column: "captureTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+
+const open = () => visible.value = true;
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            form.value.isTemp && XEUtils.set(data, "targetProjectId", 1);
+
+            isSaving.value = true;
+            API.car_rinse.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 1 - 1
src/views/dataMock/carwash/components/info/detail.vue

@@ -2,7 +2,7 @@
     <el-dialog v-model="visible" :title="titleMap[mode]" width="480" :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
             <el-form-item label="车牌号" prop="plateNumber">
-                <el-input v-model="form.plateNumber" placeholder="请输入车牌号"></el-input>
+                <el-input v-model="form.plateNumber" :formatter="value => value.toUpperCase()" placeholder="请输入车牌号"></el-input>
             </el-form-item>
             <el-form-item label="车辆类型" prop="vehicleType">
                 <el-select v-model="form.vehicleType" placeholder="请选择车辆类型">

+ 3 - 3
src/views/dataMock/carwash/components/record/detail.vue

@@ -11,7 +11,7 @@
                 </el-col>
                 <el-col :md="12" :xs="24">
                     <el-form-item label="车牌号" prop="licensePlate">
-                        <el-input v-model="form.licensePlate" placeholder="请输入车牌号"></el-input>
+                        <el-input v-model="form.licensePlate" :formatter="value => value.toUpperCase()" placeholder="请输入车牌号"></el-input>
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
@@ -54,12 +54,12 @@
             <el-row>
                 <el-col :md="12" :xs="24">
                     <el-form-item label="车身清洗图片" prop="folders[carrinse/attach].entities[0]">
-                        <sc-upload v-model="form.folders['carrinse/attach'].entities[0]" :width="140" :height="180" accept="image/jpeg, image/png"></sc-upload>
+                        <sc-upload v-model="form.folders['carrinse/attach'].entities[0]" :width="240" :height="143" accept="image/jpeg, image/png"></sc-upload>
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
                     <el-form-item label="后盖密闭图片" prop="folders[carrinse/side].entities[0]">
-                        <sc-upload v-model="form.folders['carrinse/side'].entities[0]" :width="140" :height="180" accept="image/jpeg, image/png"></sc-upload>
+                        <sc-upload v-model="form.folders['carrinse/side'].entities[0]" :width="240" :height="143" accept="image/jpeg, image/png"></sc-upload>
                     </el-form-item>
                 </el-col>
             </el-row>

+ 1 - 1
src/views/dataMock/carwash/components/record/index.vue

@@ -16,7 +16,7 @@
         </template>
     </scTable>
 
-    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog.value = false"></record-detail>
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog = false"></record-detail>
 </template>
 
 <script setup>

+ 14 - 5
src/views/dataMock/carwash/detail.vue

@@ -23,7 +23,7 @@
                 </el-col>
                 <el-col :md="12" :xs="24">
                     <el-form-item label="模拟年份" prop="targetYear">
-                        <el-date-picker v-model="form.targetYear" type="year" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
+                        <el-date-picker v-model="form.targetYear" type="year" :clearable="false" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
@@ -63,7 +63,7 @@
                 </el-col>
                 <el-col :md="12" :xs="24">
                     <el-form-item label="抓拍时间" prop="sourceTime">
-                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
                     </el-form-item>
                 </el-col>
             </el-row>
@@ -80,9 +80,11 @@
 </template>
 
 <script setup>
+import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
 import dataTable from "./components/record";
 
 const route = useRoute();
@@ -91,6 +93,7 @@ const apiKey = ref("copyData");
 const visible = ref(false);
 const isSaving = ref(false);
 
+const shortcuts = rangeShortcuts();
 const form = ref({
     targetProjectId: TOOL.data.get("PROJECT_ID"),
     targetYear: null,
@@ -99,7 +102,7 @@ const form = ref({
     source: "other",
     sourceProjectId: null,
     sourceProjectIdNot: 1,
-    sourceTime: []
+    sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
 });
 
 const rules = reactive({
@@ -144,7 +147,13 @@ const open = () => {
 
 const formRef = ref();
 const submit = key => {
-    formRef.value.validate(valid => {
+    let validateField = XEUtils.keys(rules);
+    if (key == "template") {
+        validateField = XEUtils.keys(XEUtils.omit(rules, "targetProjectId"));
+        formRef.value.clearValidate("targetProjectId");
+    }
+
+    formRef.value.validateField(validateField, valid => {
         if (valid) {
             if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             
@@ -164,7 +173,7 @@ const submit = key => {
         } else {
             return false;
         }
-    });
+    })
 }
 
 defineExpose({

+ 24 - 8
src/views/dataMock/carwash/index.vue

@@ -1,9 +1,16 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
             <template #extra-right>
-                <el-button v-if="activeName == 'info'" type="primary" @click="table_add">车辆录入</el-button>
-                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">数据录入</el-button>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'info'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>车辆录入
+                </el-button>
+                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
             </template>
         </sc-page-header>
 
@@ -14,7 +21,8 @@
         <component ref="componentRef" :is="allcomp[activeName]" taskType="car_rinse" />
 	</el-container>
 
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
 </template>
 
 <script setup>
@@ -22,24 +30,32 @@ import { workerStates } from "./main";
 import comp from "./components";
 import monos from "@/views/dataMock/tasks/monos";
 import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
 
 const allcomp = { ...comp, monos };
 const activeName = ref("info");
 
 const componentRef = ref();
 const mockRef = ref();
-const dialog = ref(false);
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
 
 const table_add = () => componentRef.value.table_add();
 
 const mock_add = () => {
-    dialog.value = true;
+    dialog.value.mock = true;
     nextTick(() => mockRef.value?.open());
 }
 
-const refreshState = () => {
-    if (activeName.value == "monos") setTimeout(() => componentRef.value.refreshTable(), 2000);
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
 }
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
 </script>
 
 <style lang="scss" scoped>

+ 122 - 0
src/views/dataMock/edgeprot/batchDel.vue

@@ -0,0 +1,122 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="项目名称" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetMountedId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: "targetProjectId"  },
+        { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.edgeprot.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+
+            isSaving.value = true;
+            API.edgeprot.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

src/views/dataMock/autospray/components/index.js → src/views/dataMock/edgeprot/components/index.js


+ 187 - 0
src/views/dataMock/edgeprot/components/record/detail.vue

@@ -0,0 +1,187 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="860" :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-row v-if="props.projectId != 1">
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="所属项目" prop="projectId">
+                        <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="mountedId">
+                        <el-select v-model="form.mountedId" filterable placeholder="请选择设备安装点">
+                            <el-option v-for="item in filterMounteds" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="监测类型" prop="dataType">
+                        <el-select v-model="form.dataType" placeholder="请选择监测类型">
+                            <el-option v-for="(label, key) in typeDic" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="监测时间" prop="createTime">
+                        <el-date-picker v-model="form.createTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择监测时间"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="磁锁1" prop="features.magneticLock1">
+                        <el-select v-model="form.features.magneticLock1" placeholder="请选择磁锁1状态">
+                            <el-option v-for="(label, key) in statusDic.slice(1)" :key="key" :label="label" :value="key + 1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="磁锁2" prop="features.magneticLock2">
+                        <el-select v-model="form.features.magneticLock2" placeholder="请选择磁锁2状态">
+                            <el-option v-for="(label, key) in statusDic.slice(1)" :key="key" :label="label" :value="key + 1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="接近报警" prop="features.proximity">
+                        <el-select v-model="form.features.proximity" placeholder="请选择接近报警状态">
+                            <el-option v-for="(label, key) in statusDic.slice(1)" :key="key" :label="label" :value="key + 1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="磁锁传感器1" prop="features.magneticSensor1">
+                        <el-select v-model="form.features.magneticSensor1" placeholder="请选择磁锁传感器1状态">
+                            <el-option v-for="(label, key) in statusDic.slice(1)" :key="key" :label="label" :value="key + 1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="磁锁传感器2" prop="features.magneticSensor2">
+                        <el-select v-model="form.features.magneticSensor2" placeholder="请选择磁锁传感器2状态">
+                            <el-option v-for="(label, key) in statusDic.slice(1)" :key="key" :label="label" :value="key + 1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="人体感应器" prop="features.proximitySensor">
+                        <el-select v-model="form.features.proximitySensor" placeholder="请选择人体感应器状态">
+                            <el-option v-for="(label, key) in statusDic.slice(1)" :key="key" :label="label" :value="key + 1"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { typeDic, statusDic } from "@/views/dataMock/edgeprot/main";
+
+const $emit = defineEmits(["success", "closed"]);
+const props = defineProps({
+    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") }
+});
+
+const visible = ref(false);
+const isSaving = ref(false);
+
+const mode = ref("add");
+const titleMap = reactive({
+    add: "数据录入",
+    edit: "修改"
+});
+
+const form = ref({
+    id: null,
+    projectId: props.projectId,
+    mountedId: null,
+    dataType: null,
+    createTime: null,
+    features: {
+        magneticLock1: null,
+        magneticLock2: null,
+        proximity: null,
+        magneticSensor1: null,
+        magneticSensor2: null,
+        proximitySensor: null
+    }
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    mountedId: [{ required: true, message: "请选择设备安装点" }],
+    dataType: [{ required: true, message: "请选择监测类型" }],
+    createTime: [{ required: true, message: "请选择监测时间" }],
+    "features.magneticLock1": [{ required: true, message: "请选择磁锁1状态" }],
+    "features.magneticLock2": [{ required: true, message: "请选择磁锁2状态" }],
+    "features.proximity": [{ required: true, message: "请选择接近报警状态" }],
+    "features.magneticSensor1": [{ required: true, message: "请选择磁锁传感器1状态" }],
+    "features.magneticSensor2": [{ required: true, message: "请选择磁锁传感器2状态" }],
+    "features.proximitySensor": [{ required: true, message: "请选择人体感应器状态" }]
+})
+
+const mounteds = ref([]);
+const filterMounteds = computed(() => form.value.projectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.projectId) : []);
+const fetchMounted = async () => {
+    const res = await API.edgeprot.mounted.get();
+    mounteds.value = res || [];
+    if (props.projectId == 1) form.value.mountedId = XEUtils.get(XEUtils.find(res, item => item.projectId == 1), "id");
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(form.value, (_, key) => {
+        if (key == "features") {
+            const features = XEUtils.toStringJSON(XEUtils.get(data, key));
+            XEUtils.merge(form.value.features, features);
+        } else XEUtils.set(form.value, key, XEUtils.get(data, key));
+    });
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.omit(form.value, "features");
+            XEUtils.set(data, "features", XEUtils.toJSONString(form.value.features));
+
+            isSaving.value = true;
+            API.edgeprot.record[mode.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", mode.value);
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open,
+    setData
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+}
+</style>

+ 169 - 0
src/views/dataMock/edgeprot/components/record/index.vue

@@ -0,0 +1,169 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.edgeprot.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
+        <template #action="{ row }">
+            <el-button type="primary" link @click="table_edit(row)">
+                <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
+            </el-button>
+            <el-button type="primary" link @click="table_del(row)">
+                <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
+            </el-button>
+        </template>
+    </scTable>
+
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog = false"></record-detail>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource, objectToArray } from "@/utils/basicDic";
+import { typeDic, statusDic } from "@/views/dataMock/edgeprot/main";
+import recordDetail from "./detail";
+
+const formatFea = row => XEUtils.toStringJSON(XEUtils.get(row, "features", "{}"));
+
+const props = defineProps({
+    options: { type: Object, default: () => {} },
+    isTemp: { type: Boolean, default: false },
+    hideHandler: { type: Boolean, default: false }
+})
+const visible = computed(() => !props.isTemp);
+
+const proConfig = reactive({
+    visible,
+    span: 5,
+    storageKey: "PROJECT",
+    resetValue: TOOL.data.get("PROJECT_ID"),
+    optionProps: { label: "projectName", value: "fpiId" },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, { ...data, mountedId: null })
+    }
+})
+
+const mountedConfig = reactive({
+    visible,
+    api: {
+        key: "edgeprot.mounted",
+        query: {
+            projectId: computed(() => formConfig.data.projectId),
+            projectIdNot: 1
+        }
+    },
+    slot: {
+        style: { float: "right", paddingLeft: "6px", color: "#8492a6" }
+    },
+    optionProps: { label: "mountedName", value: "id", slot: ({ data }) => XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === data.projectId), "projectName") },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, data)
+    }
+})
+
+const selectConfig = reactive({
+    options: objectToArray(typeDic),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
+const datetimerangeConfig = reactive({
+    span: 7,
+    resetValue: () => [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")],
+    props: {
+        type: "datetimerange",
+        startPlaceholder: "开始时间",
+        endPlaceholder: "结束时间",
+        format: "YYYY-MM-DD HH:mm"
+    }
+})
+
+const toolbarConfig = reactive({
+    enabled: true,
+    print: false
+})
+
+const formConfig = reactive({
+    data: {
+        projectId: TOOL.data.get("PROJECT_ID"),
+        projectIdNot: 1,
+        createTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")]
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemSelect("mountedId", "设备安装点", mountedConfig),
+        mapFormItemDatePicker("createTime", "监测时间", datetimerangeConfig),
+        mapFormItemSelect("dataType", "监测类型", selectConfig)
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "projectId", field: visible.value ? "" : "projectIdNot" },
+    visible.value ? { column: "projectIdNot" } : {},
+    { column: "mountedId" },
+    { column: "dataType" },
+    { column: "createTimeBegin", field: "createTime[0]" },
+    { column: "createTimeEnd", field: "createTime[1]" }
+])
+
+const columns = reactive([
+    { visible: !props.hideHandler, type: "checkbox", fixed: "left", width: 40 },
+    { type: "seq", fixed: "left", width: 60 },
+    { visible, type: "html", field: "projectName", title: "项目名称", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId == row.projectId), "projectName") },
+    { visible, type: "html", field: "groundName", title: "工地场区", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "mountedName", title: "设备安装点", minWidth: 160, sortable: true },
+    { type: "html", field: "dataType", title: "监测类型", minWidth: 130, sortable: true, formatter: ({ cellValue }) => XEUtils.get(typeDic, cellValue, cellValue) },
+    { type: "html", field: "createTime", title: "监测时间", minWidth: 160, sortable: true },
+    { type: "html", field: "magneticLock1", title: "磁锁1", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(statusDic, XEUtils.get(formatFea(row), "magneticLock1")) },
+    { type: "html", field: "magneticLock2", title: "磁锁2", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(statusDic, XEUtils.get(formatFea(row), "magneticLock2")) },
+    { type: "html", field: "proximity", title: "接近报警", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(statusDic, XEUtils.get(formatFea(row), "proximity")) },
+    { type: "html", field: "magneticSensor1", title: "磁锁传感器1", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(statusDic, XEUtils.get(formatFea(row), "magneticSensor1")) },
+    { type: "html", field: "magneticSensor2", title: "磁锁传感器2", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(statusDic, XEUtils.get(formatFea(row), "magneticSensor2")) },
+    { type: "html", field: "proximitySensor", title: "人体感应器", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(statusDic, XEUtils.get(formatFea(row), "proximitySensor")) },
+    { visible, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { visible: !props.hideHandler, title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+])
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
+
+const refreshTable = (mode = "add") => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData(mode);
+}
+
+const recordRef = ref();
+const dialog = ref(false);
+
+const table_add = () => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.open());
+}
+
+const table_edit = row => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.setData(row));
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该监测记录?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.edgeprot.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable,
+    getTableTotal
+})
+</script>

+ 187 - 0
src/views/dataMock/edgeprot/detail.vue

@@ -0,0 +1,187 @@
+<template>
+    <el-dialog v-model="visible" title="数据模拟" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-tabs v-model="apiKey">
+            <el-tab-pane label="参数配置" name="makeData"></el-tab-pane>
+            <el-tab-pane label="数据复制" name="copyData" disabled></el-tab-pane>
+        </el-tabs>
+
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="126">
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择模拟项目" @change="form.targetMountedId = null, dataTimeRange()">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col v-if="form.targetProjectId" :md="12" :xs="24">
+                    <el-form-item label="数据时间范围">
+                        <template v-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/basic/acceptItems')">去配置</el-button></template>
+                        <template v-else-if="acceptItem.beginTime">{{ $TOOL.dateFormat(acceptItem.beginTime, "YY.M.D") }}<span>-{{ acceptItem.endTime && $TOOL.dateFormat(acceptItem.endTime, "YY.M.D") || "至今" }}</span></template>
+                        <template v-else>该项目未配置数据时间范围,<el-button type="primary" link @click="$router.push('/basic/project')">去配置</el-button></template>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择模拟项目安装点">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟时间范围" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="数据类型" prop="dataSouce">
+                        <el-radio-group v-model="form.dataSouce">
+                            <el-radio value="normal">标准数据</el-radio>
+                            <el-radio value="alarm">报警数据</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+                
+                <template v-if="form.dataSouce == 'normal'">
+                    <el-col :md="12" :xs="24">
+                        <el-form-item label="数据处理" prop="isCover">
+                            <el-radio-group v-model="form.isCover">
+                                <el-radio :value="false">重复新增</el-radio>
+                                <el-radio :value="true">数据覆盖</el-radio>
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="12" :xs="24">
+                        <el-form-item class="step-item" label="时间步长" prop="timeStepSec">
+                            <el-input-number v-model="form.timeStepSec" :min="0" :controls="false" placeholder="时间步长"></el-input-number>
+
+                            <el-radio-group v-model="form.timeStepType" @change="stepTypeChange">
+                                <el-radio value="second">秒</el-radio>
+                                <el-radio value="minute">分钟</el-radio>
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                </template>
+                <el-col v-if="form.dataSouce == 'alarm'" :md="12" :xs="24">
+                    <el-form-item label="报警数量" prop="warnNum">
+                        <el-input-number v-model="form.warnNum" :min="0" :controls="false" placeholder="请输入报警数量"></el-input-number>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit()">提交</el-button>
+            <el-button v-if="form.source == 'other'" :loading="isSaving" type="primary" auto-insert-space @click="submit('template')">保存为模版</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+
+const route = useRoute();
+const $emit = defineEmits(["success", "closed"]);
+const apiKey = ref("makeData");
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetMountedId: null,
+    targetTime: [],
+    dataSouce: "normal",
+    isCover: false,
+    timeStepSec: 3,
+    timeStepType: "minute",
+    warnNum: null
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择模拟项目" }],
+    targetMountedId: [{ required: true, message: "请选择模拟项目安装点" }],
+    targetTime: [{ required: true, message: "请选择模拟时间范围" }],
+    dataSouce: [{ required: true }],
+    isCover: [{ required: true }],
+    timeStepSec: [{ required: true, message: "请输入时间步长" }],
+    warnNum: [{ required: true, message: "请输入报警数量" }]
+});
+
+const acceptItem = ref({});
+const dataTimeRange = async () => {
+    const query = {
+        projectId: form.value.targetProjectId,
+        itemName: XEUtils.last(route.meta.title.split("-"))
+    }
+    const res = await API.system.project.bindItem.judgment(query);
+    acceptItem.value = res || {};
+}
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.edgeprot.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    TOOL.data.get("PROJECT_ID") && dataTimeRange();
+    fetchMounted();
+}
+
+const stepTypeChange = e => {
+    if (e == "minute") XEUtils.set(form.value, "timeStepSec", XEUtils.floor(XEUtils.divide(form.value.timeStepSec, 60)));
+    if (e == "second") XEUtils.set(form.value, "timeStepSec", XEUtils.multiply(form.value.timeStepSec, 60));
+}
+
+const formRef = ref();
+const submit = key => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            
+            if (form.value.dataSouce == "normal") {
+                XEUtils.set(data, "timeStepSec", form.value.timeStepType == "minute" ? XEUtils.multiply(form.value.timeStepSec, 60) : form.value.timeStepSec);
+                XEUtils.set(data, "isCover", form.value.isCover);
+            }
+            if (form.value.dataSouce == "alarm") XEUtils.set(data, "warnNum", form.value.warnNum);
+            
+            isSaving.value = true;
+            API.edgeprot.dataMock[apiKey.value][form.value.dataSouce](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+
+.el-form-item .el-input-number {width: 100%;}
+.el-form-item .el-input-number :deep(.el-input__prefix) {margin-right: 8px;}
+.el-form-item .el-input-number :deep(.el-input__inner) {text-align: unset;}
+
+.el-form-item.step-item {
+    .el-input-number {flex: 1;}
+    .el-radio-group {margin-left: 20px;}
+}
+</style>

+ 59 - 0
src/views/dataMock/edgeprot/index.vue

@@ -0,0 +1,59 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
+            <template #extra-right>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="edgeprot" />
+	</el-container>
+
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
+</template>
+
+
+<script setup>
+import { workerStates } from "@/views/dataMock/smoke/main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+
+const componentRef = ref();
+const mockRef = ref();
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value.mock = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
+}
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

+ 9 - 0
src/views/dataMock/edgeprot/main.js

@@ -0,0 +1,9 @@
+export const typeDic = {
+    EDGEPROT_RECORD_TYPE_NORMAL: "正常",
+    EDGEPROT_RECORD_TYPE_PROXIMITY: "有人靠近",
+    EDGEPROT_RECORD_TYPE_FAILURE: "围栏故障",
+    EDGEPROT_RECORD_TYPE_ALL: "有人靠近,围栏故障",
+    EDGEPROT_RECORD_TYPE_DEVICE_ERR: "设备故障"
+}
+
+export const statusDic = [null, "正常", "报警"]

+ 146 - 0
src/views/dataMock/elevator/batchDel.vue

@@ -0,0 +1,146 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-tabs v-model="dataSouce">
+            <el-tab-pane label="标准数据" name="normal"></el-tab-pane>
+            <el-tab-pane label="考勤数据" name="attendance"></el-tab-pane>
+        </el-tabs>
+
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="126">
+            <el-form-item v-if="dataSouce == 'normal'" label="数据来源" prop="isTemp">
+                <el-radio-group v-model="form.isTemp" @change="refreshTable">
+                    <el-radio :value="true">模版项目</el-radio>
+                    <el-radio :value="false">其他项目</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <template v-if="dataSouce == 'attendance' || !form.isTemp">
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="项目名称" prop="targetProjectId">
+                            <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                                <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="设备安装点" prop="targetMountedId">
+                            <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                                <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <component ref="tableRef" :is="dataSouce == 'normal' ? comp['record'] : comp[dataSouce]" :isTemp="form.isTemp" hideHandler :options="tableOptions"></component>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import comp from "./components";
+
+const route = useRoute();
+const $emit = defineEmits(["success", "closed"]);
+const dataSouce = ref("normal");
+
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    isTemp: false,
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetMountedId: null,
+    recordType: "ELEVATOR_RECORD_ENTER",
+    targetTime: [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    isTemp: [{ required: true }],
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: form.value.isTemp ? "targetProjectIdNot" : "targetProjectId"  },
+        form.value.isTemp ? {} : { column: "projectIdNot", field: "targetProjectIdNot" },
+        dataSouce.value == 'normal' ? {} : { column: "recordType" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.elevator.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            if (form.value.isTemp) {
+                XEUtils.set(data, "targetProjectId", 1);
+                XEUtils.set(data, "targetMountedId", XEUtils.get(XEUtils.find(mounteds.value, item => item.projectId == 1), "id"));
+            }
+
+            isSaving.value = true;
+            API.elevator.dataMock.removeData[dataSouce.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 1 - 1
src/views/dataMock/elevator/components/alarm.vue

@@ -15,7 +15,7 @@ import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
 import { dataSource, objectToArray } from "@/utils/basicDic";
-import { warningTypeDic } from "@/views/dataMock/tcm/main";
+import { warningTypeDic } from "@/views/dataMock/tower/main";
 
 const proConfig = reactive({
     span: 5,

+ 17 - 5
src/views/dataMock/elevator/components/attendance.vue

@@ -1,5 +1,5 @@
 <template>
-    <scTable ref="xGridTable" batchDel :apiObj="$API.elevator.attendance" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
+    <scTable ref="xGridTable" batchDel :apiObj="$API.elevator.attendance" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
         <template #default_imgText="{ row, column }">
             <template v-if="formatCertificate(row, column)">
                 <el-button type="primary" link @click="handlePreview(row, column)">{{ XEUtils.get(formatCertificate(row, column), "certificateNo") }}</el-button>
@@ -23,12 +23,19 @@ import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
 import { dataSource } from "@/utils/basicDic";
-import { folderKeyDic } from "@/views/dataMock/tcm/main";
+import { folderKeyDic } from "@/views/dataMock/tower/main";
 
 const formatCertificate = (row, { field }) => XEUtils.find(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "person.features")), "certificate", []), item => item.type == XEUtils.get(folderKeyDic, field));
 
+const props = defineProps({
+    options: { type: Object, default: () => {} },
+    hideHandler: { type: Boolean, default: false }
+})
+const visible = computed(() => !props.hideHandler);
+
 const proConfig = reactive({
     span: 5,
+    visible,
     storageKey: "PROJECT",
     resetValue: TOOL.data.get("PROJECT_ID"),
     optionProps: { label: "projectName", value: "fpiId" },
@@ -38,6 +45,7 @@ const proConfig = reactive({
 })
 
 const mountedConfig = reactive({
+    visible,
     api: {
         key: "elevator.mounted",
         query: {
@@ -94,7 +102,7 @@ const paramsColums = reactive([
 ])
 
 const columns = reactive([
-    { type: "checkbox", fixed: "left", width: 40 },
+    { visible: !props.hideHandler, type: "checkbox", fixed: "left", width: 40 },
     { type: "seq", fixed: "left", width: 60 },
     { type: "html", field: "person.name", title: "人员姓名", minWidth: 100, sortable: true },
     { type: "html", field: "person.idCard", title: "身份证号", minWidth: 160, sortable: true },
@@ -104,8 +112,8 @@ const columns = reactive([
     { type: "html", field: "groundName", title: "工地场区", minWidth: 160, sortable: true },
     { type: "html", field: "mountedName", title: "设备安装点", minWidth: 160, sortable: true },
     { field: "facerec/elevator_zs", title: "特种作业证", minWidth: 140, align: "center", slots: { default: "default_imgText" } },
-    { type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
-    { title: "操作", fixed: "right", width: 120, align: "center", slots: { default: "action" } }
+    { visible: !props.hideHandler, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { visible: !props.hideHandler, title: "操作", fixed: "right", width: 120, align: "center", slots: { default: "action" } }
 ])
 
 // 显示隐藏 筛选表单
@@ -136,4 +144,8 @@ const table_del = ({ id }) => {
         });
     });
 }
+
+defineExpose({
+    refreshTable
+})
 </script>

+ 40 - 40
src/views/dataMock/elevator/components/record/detail.vue

@@ -1,168 +1,168 @@
 <template>
-    <el-dialog v-model="visible" :title="titleMap[mode]" :width="880" :close-on-click-modal="false" @closed="$emit('closed')">
+    <el-dialog v-model="visible" :title="titleMap[mode]" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="140">
-            <el-row v-if="props.projectId != 1">
-                <el-col :md="12" :xs="24">
-                    <el-form-item label="所属项目" prop="projectId">
-                        <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
-                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
-                        </el-select>
-                    </el-form-item>
-                </el-col>
-                <el-col :md="12" :xs="24">
-                    <el-form-item label="设备安装点" prop="mountedId">
-                        <el-select v-model="form.mountedId" filterable placeholder="请选择设备安装点">
-                            <el-option v-for="item in filterMounteds" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
-                        </el-select>
-                    </el-form-item>
-                </el-col>
-            </el-row>
-
             <el-row>
-                <el-col :md="12" :xs="24">
+                <template v-if="props.projectId != 1">
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="所属项目" prop="projectId">
+                            <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                                <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="设备安装点" prop="mountedId">
+                            <el-select v-model="form.mountedId" filterable placeholder="请选择设备安装点">
+                                <el-option v-for="item in filterMounteds" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="监测时间" prop="createTime">
                         <el-date-picker v-model="form.createTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择监测时间"></el-date-picker>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="前门锁闭状态" prop="features.frontDoorStatus">
                         <el-select v-model="form.features.frontDoorStatus" placeholder="请选择前门锁闭状态">
                             <el-option v-for="(label, index) in elevatorDic.isOpen" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="后门锁闭状态" prop="features.backDoorStatus">
                         <el-select v-model="form.features.backDoorStatus" placeholder="请选择后门锁闭状态">
                             <el-option v-for="(label, index) in elevatorDic.isOpen" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="驾驶员认证状态" prop="features.driverStatus">
                         <el-select v-model="form.features.driverStatus" placeholder="请选择驾驶员认证状态">
                             <el-option v-for="(label, index) in elevatorDic.isCert" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="前门状态" prop="features.frontStatus">
                         <el-select v-model="form.features.frontStatus" placeholder="请选择前门状态">
                             <el-option v-for="(label, index) in elevatorDic.isNormal" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="后门状态" prop="features.backStatus">
                         <el-select v-model="form.features.backStatus" placeholder="请选择后门状态">
                             <el-option v-for="(label, index) in elevatorDic.isNormal" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="高度" prop="features.height">
                         <el-input-number v-model="form.features.height" :precision="1" :controls="false" placeholder="请输入高度">
                             <template #suffix>m</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="限位系统状态" prop="features.heightLimit">
                         <el-select v-model="form.features.heightLimit" placeholder="请选择限位系统状态">
                             <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="门锁异常指示" prop="features.lockStatus">
                         <el-select v-model="form.features.lockStatus" placeholder="请选择门锁异常指示">
                             <el-option v-for="(label, index) in elevatorDic.isNormal" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="超速状态" prop="features.overspeed">
                         <el-select v-model="form.features.overspeed" placeholder="请选择超速状态">
                             <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="人数" prop="features.personCount">
                         <el-input-number v-model="form.features.personCount" :min="0" :controls="false" placeholder="请输入人数"></el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="人数状态" prop="features.personStatus">
                         <el-select v-model="form.features.personStatus" placeholder="请选择人数状态">
                             <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                 <el-col :md="12" :xs="24">
+                 <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="速度" prop="features.speed">
                         <el-input-number v-model="form.features.speed" :precision="1" :controls="false" placeholder="请输入速度">
                             <template #suffix>m/s</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="移动状态" prop="features.status">
                         <el-select v-model="form.features.status" placeholder="请选择移动状态">
                             <el-option v-for="(label, index) in elevatorDic.move" :key="index" :label="label" :value="index + ''"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="倾角x" prop="features.tilt1">
                         <el-input-number v-model="form.features.tilt1" :precision="2" :controls="false" placeholder="请输入倾角x">
                             <template #suffix>°</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="倾角x百分比" prop="features.tilt1Rate">
                         <el-input-number v-model="form.features.tilt1Rate" :precision="2" :controls="false" placeholder="请输入倾角x百分比">
                             <template #suffix>%</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="倾角y" prop="features.tilt2">
                         <el-input-number v-model="form.features.tilt2" :precision="2" :controls="false" placeholder="请输入倾角y">
                             <template #suffix>°</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="倾角y百分比" prop="features.tilt2Rate">
                         <el-input-number v-model="form.features.tilt2Rate" :precision="2" :controls="false" placeholder="请输入倾角y百分比">
                             <template #suffix>%</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="倾角状态" prop="features.tiltStatus">
                         <el-select v-model="form.features.tiltStatus" placeholder="请选择倾角状态">
                             <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
                         </el-select>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="载重" prop="features.weight">
                         <el-input-number v-model="form.features.weight" :controls="false" placeholder="请输入载重">
                             <template #suffix>kg</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="载重百分比" prop="features.weightRate">
                         <el-input-number v-model="form.features.weightRate" :precision="2" :controls="false" placeholder="请输入载重百分比">
                             <template #suffix>%</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :lg="8" :md="12" :xs="24">
                     <el-form-item label="重量系统状态" prop="features.weightStatus">
                         <el-select v-model="form.features.weightStatus" placeholder="请选择重量系统状态">
                             <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>

+ 13 - 7
src/views/dataMock/elevator/detail.vue

@@ -38,7 +38,7 @@
                 <template v-if="dataSouce == 'normal'">
                     <el-col :md="12" :xs="24">
                         <el-form-item class="range-item" label="模拟时间范围" prop="targetBeginTime">
-                            <el-date-picker v-model="form.targetBeginTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="new Date(2000, 1, 1, 23, 59, 59)" placeholder="请选择模拟数据开始时间" @change="dateChange"></el-date-picker>
+                            <el-date-picker v-model="form.targetBeginTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="new Date()" placeholder="请选择模拟数据开始时间" @change="dateChange"></el-date-picker>
                             <span>至</span>
                             <el-input v-model="form.targetEndTime" readonly placeholder="模拟数据结束时间">
                                 <template #prefix><sc-iconify icon="ep:clock"></sc-iconify></template>
@@ -98,14 +98,14 @@
 
                 <el-row>
                     <template v-if="form.source == 'other'">
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="数据源项目" prop="sourceProjectId">
                                 <el-select v-model="form.sourceProjectId" filterable placeholder="请选择数据源项目" @change="form.sourceMountedId = null, refreshTable()">
                                     <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
                                 </el-select>
                             </el-form-item>
                         </el-col>
-                        <el-col :md="12" :xs="24">
+                        <el-col :lg="8" :md="12" :xs="24">
                             <el-form-item label="数据源安装点" prop="sourceMountedId">
                                 <el-select v-model="form.sourceMountedId" filterable placeholder="请选择数据源安装点" @change="refreshTable">
                                     <el-option v-for="item in filterSourceM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
@@ -114,9 +114,9 @@
                         </el-col>
                     </template>
 
-                    <el-col :md="12" :xs="24">
+                    <el-col :lg="8" :md="12" :xs="24">
                         <el-form-item label="监测时间" prop="sourceTime">
-                            <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
+                            <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
                         </el-form-item>
                     </el-col>
                 </el-row>
@@ -234,7 +234,13 @@ const dateChange = () => {
 
 const formRef = ref();
 const submit = key => {
-    formRef.value.validate(valid => {
+    let validateField = XEUtils.keys(rules);
+    if (key == "template") {
+        validateField = XEUtils.keys(XEUtils.omit(rules, "targetProjectId", "targetMountedId"));
+        formRef.value.clearValidate(["targetProjectId", "targetMountedId"]);
+    }
+
+    formRef.value.validateField(validateField, valid => {
         if (valid) {
             if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             
@@ -266,7 +272,7 @@ const submit = key => {
                 isSaving.value = false;
                 ElMessage.success("操作成功");
                 visible.value = false;
-                $emit("success", dataSouce.value);
+                $emit("success");
             }).catch(() => isSaving.value = false);
         } else {
             return false;

+ 22 - 20
src/views/dataMock/elevator/index.vue

@@ -1,8 +1,13 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
             <template #extra-right>
-                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">数据录入</el-button>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
             </template>
         </sc-page-header>
 
@@ -10,47 +15,44 @@
             <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
         </el-tabs>
 
-        <el-tabs v-if="activeName == 'monos'" v-model="taskType" @tab-change="refreshMono">
-            <el-tab-pane label="标准数据" name="elevator"></el-tab-pane>
-            <el-tab-pane label="报警数据" name="elevator_warning"></el-tab-pane>
-            <el-tab-pane label="考勤数据" name="elevator_person"></el-tab-pane>
-        </el-tabs>
-
-        <component ref="componentRef" :is="allcomp[activeName]" :taskType="taskType" />
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="elevator" />
 	</el-container>
 
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
 </template>
 
 <script setup>
-import XEUtils from "xe-utils";
 import { workerStates, monoType } from "./main";
 import comp from "./components";
 import monos from "@/views/dataMock/tasks/monos";
 import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
 
 const allcomp = { ...comp, monos };
 const activeName = ref("record");
-const taskType = ref("elevator");
 
 const componentRef = ref();
 const mockRef = ref();
-const dialog = ref(false);
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
 
 const table_add = () => componentRef.value.table_add();
 
 const mock_add = () => {
-    dialog.value = true;
+    dialog.value.mock = true;
     nextTick(() => mockRef.value?.open());
 }
 
-const refreshMono = () => componentRef.value.refreshTable();
-const refreshState = mode => {
-    if (activeName.value == "monos") {
-        taskType.value = XEUtils.get(monoType, mode, "elevator");
-        setTimeout(() => refreshMono(), 2000);
-    }
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
 }
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
 </script>
 
 <style lang="scss" scoped>

+ 134 - 0
src/views/dataMock/env/batchDel.vue

@@ -0,0 +1,134 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-form-item label="数据来源" prop="isTemp">
+                <el-radio-group v-model="form.isTemp" @change="refreshTable">
+                    <el-radio :value="true">模版项目</el-radio>
+                    <el-radio :value="false">其他项目</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <template v-if="!form.isTemp">
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="项目名称" prop="targetProjectId">
+                            <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                                <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="设备安装点" prop="targetMountedId">
+                            <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                                <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" :isTemp="form.isTemp" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const form = ref({
+    isTemp: false,
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetMountedId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    isTemp: [{ required: true }],
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: form.value.isTemp ? "targetProjectIdNot" : "targetProjectId"  },
+        form.value.isTemp ? {} : { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.envdev.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            if (form.value.isTemp) {
+                XEUtils.set(data, "targetProjectId", 1);
+                XEUtils.set(data, "targetMountedId", XEUtils.get(XEUtils.find(mounteds.value, item => item.projectId == 1), "id"));
+            }
+
+            isSaving.value = true;
+            API.envdev.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

src/views/dataMock/envdev/components/calendar.vue → src/views/dataMock/env/components/calendar.vue


src/views/dataMock/envdev/components/index.js → src/views/dataMock/env/components/index.js


+ 1 - 1
src/views/dataMock/envdev/components/record/detail.vue

@@ -94,7 +94,7 @@
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
-import { valueFormatDic, transferPower } from "@/views/dataMock/envdev/main";
+import { valueFormatDic, transferPower } from "@/views/dataMock/env/main";
 
 const $emit = defineEmits(["success", "closed"]);
 const props = defineProps({

src/views/dataMock/envdev/components/record/index.vue → src/views/dataMock/env/components/record/index.vue


src/views/dataMock/envdev/components/template.vue → src/views/dataMock/env/components/template.vue


src/views/dataMock/envdev/components/threshold.vue → src/views/dataMock/env/components/threshold.vue


+ 9 - 3
src/views/dataMock/envdev/detail.vue

@@ -37,7 +37,7 @@
                 <template v-if="apiKey == 'copyData'">
                     <el-col :md="12" :xs="24">
                         <el-form-item label="模拟年份" prop="targetYear">
-                            <el-date-picker v-model="form.targetYear" type="year" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
+                            <el-date-picker v-model="form.targetYear" type="year" :clearable="false" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
@@ -133,7 +133,7 @@ const form = ref({
     sourceProjectId: null,
     sourceProjectIdNot: 1,
     sourceMountedId: null,
-    sourceTime: [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+    sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
 });
 
 const rules = reactive({
@@ -198,7 +198,13 @@ const open = () => {
 
 const formRef = ref();
 const submit = key => {
-    formRef.value.validate(valid => {
+    let validateField = XEUtils.keys(rules);
+    if (key == "template") {
+        validateField = XEUtils.keys(XEUtils.omit(rules, "targetProjectId", "targetMountedId"));
+        formRef.value.clearValidate(["targetProjectId", "targetMountedId"]);
+    }
+
+    formRef.value.validateField(validateField, valid => {
         if (valid) {
             if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             

+ 60 - 0
src/views/dataMock/env/index.vue

@@ -0,0 +1,60 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
+            <template #extra-right>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="envdev" />
+	</el-container>
+
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
+</template>
+
+<script setup>
+import { workerStates } from "./main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+
+const componentRef = ref();
+const mockRef = ref();
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value.mock = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
+}
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

src/views/dataMock/envdev/main.js → src/views/dataMock/env/main.js


+ 0 - 46
src/views/dataMock/envdev/index.vue

@@ -1,46 +0,0 @@
-<template>
-	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
-            <template #extra-right>
-                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">数据录入</el-button>
-            </template>
-        </sc-page-header>
-
-        <el-tabs v-model="activeName">
-            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
-        </el-tabs>
-
-        <component ref="componentRef" :is="allcomp[activeName]" taskType="envdev" />
-	</el-container>
-
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
-</template>
-
-<script setup>
-import { workerStates } from "./main";
-import comp from "./components";
-import monos from "@/views/dataMock/tasks/monos";
-import mockDetail from "./detail";
-
-const allcomp = { ...comp, monos };
-const activeName = ref("record");
-
-const componentRef = ref();
-const mockRef = ref();
-const dialog = ref(false);
-
-const table_add = () => componentRef.value.table_add();
-
-const mock_add = () => {
-    dialog.value = true;
-    nextTick(() => mockRef.value?.open());
-}
-
-const refreshState = () => {
-    if (activeName.value == "monos") setTimeout(() => componentRef.value.refreshTable(), 2000);
-}
-</script>
-
-<style lang="scss" scoped>
-.el-tabs {padding: 0 12px;background: #fff;}
-</style>

+ 138 - 0
src/views/dataMock/parking/batchDel.vue

@@ -0,0 +1,138 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-form-item label="数据来源" prop="isTemp">
+                <el-radio-group v-model="form.isTemp" @change="refreshTable">
+                    <el-radio :value="true">模版项目</el-radio>
+                    <el-radio :value="false">其他项目</el-radio>
+                </el-radio-group>
+            </el-form-item>
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <template v-if="!form.isTemp">
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="项目名称" prop="targetProjectId">
+                            <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                                <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="场区闸口" prop="targetGateId">
+                            <el-select v-model="form.targetGateId" filterable placeholder="请选择删除数据的场区闸口" @change="refreshTable">
+                                <el-option v-for="item in filterTargetG" :key="item.id" :label="item.gateName" :value="item.id"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" :isTemp="form.isTemp" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    isTemp: false,
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    applySql: "(day_seconds >= 64800 and day_seconds <= 86399) or (day_seconds >=0 and day_seconds <= 21599)",
+    targetGateId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    isTemp: [{ required: true }],
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetGateId: [{ required: true, message: "请选择删除数据的场区闸口" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: form.value.isTemp ? "targetProjectIdNot" : "targetProjectId"  },
+        form.value.isTemp ? {} : { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "gateId", field: "targetGateId" },
+        { column: "applySql" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const gates = ref([]);
+const filterTargetG = computed(() => form.value.targetProjectId ? XEUtils.filter(gates.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchGate = async () => {
+    const res = await API.parking.gate.get();
+    gates.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchGate();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetGateId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            if (form.value.isTemp) {
+                XEUtils.set(data, "targetProjectId", 1);
+                XEUtils.set(data, "targetGateId", XEUtils.get(XEUtils.find(mounteds.value, item => item.projectId == 1), "id"));
+            }
+
+            isSaving.value = true;
+            API.parking.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

src/views/dataMock/scc/components/index.js → src/views/dataMock/parking/components/index.js


+ 189 - 0
src/views/dataMock/parking/components/record/detail.vue

@@ -0,0 +1,189 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="860" :close-on-click-modal="false" @closed="$emit('closed', isDel)">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-row v-if="props.projectId != 1">
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="所属项目" prop="projectId">
+                        <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.gateId = null">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="场区闸口" prop="gateId">
+                        <el-select v-model="form.gateId" filterable placeholder="请选择场区闸口">
+                            <el-option v-for="item in filterGates" :key="item.id" :label="item.gateName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="车牌号" prop="plate">
+                        <el-input v-model="form.plate" :formatter="value => value.toUpperCase()" placeholder="请输入车牌号"></el-input>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="车牌颜色" prop="color">
+                        <el-select v-model="form.color" filterable placeholder="请选择车牌颜色">
+                            <el-option v-for="(label, key) in plateColorDic" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="事件类型" prop="event">
+                        <el-select v-model="form.event" filterable placeholder="请选择事件类型">
+                            <el-option v-for="(label, key) in eventDic" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="抓拍时间" prop="createTime">
+                        <el-date-picker v-model="form.createTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :disabled-date="date => moment(date).hour() > 5 && moment(date).hour() < 18" :disabled-hours="() => Array.from({ length: 12 }, (_, i) => i + 6)" placeholder="请选择抓拍时间"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <el-col :xs="24">
+                    <el-form-item label="抓拍图片" prop="fileData">
+                        <sc-upload v-model="form.fileData" apiKey="parking" :width="240" :height="143" @removeSuccess="removeSuccess"></sc-upload>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { plateColorDic, eventDic, formatMeta } from "@/views/dataMock/parking/main";
+import scUpload from "@/components/scUpload/minio";
+
+const $emit = defineEmits(["success", "closed"]);
+const props = defineProps({
+    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") }
+});
+
+const visible = ref(false);
+const isSaving = ref(false);
+const isDel = ref(false);
+
+const mode = ref("add");
+const titleMap = reactive({
+    add: "数据录入",
+    edit: "修改"
+});
+
+const form = ref({
+    id: null,
+    projectId: props.projectId,
+    gateId: null,
+    plate: null,
+    color: null,
+    event: null,
+    createTime: null,
+    features: "{}",
+    metaFeatures: {},
+    fileData: {}
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    gateId: [{ required: true, message: "请选择场区闸口" }],
+    plate: [{ required: true, message: "请输入车牌号" }],
+    color: [{ required: true, message: "请选择车牌颜色" }],
+    event: [{ required: true, message: "请选择事件类型" }],
+    createTime: [{ required: true, message: "请选择抓拍时间" }],
+    fileData: [{ required: true, validator: (rule, value, callback) => {
+        if (XEUtils.isEmpty(value)) return callback(new Error("请上传抓拍图片"));
+        callback();
+    }}]
+})
+
+const gates = ref([]);
+const filterGates = computed(() => form.value.projectId ? XEUtils.filter(gates.value, item => item.projectId == form.value.projectId) : []);
+const fetchGate = async () => {
+    const res = await API.parking.gate.get();
+    gates.value = res || [];
+    if (props.projectId == 1) form.value.gateId = XEUtils.get(XEUtils.find(res, item => item.projectId == 1), "id");
+}
+
+const open = () => {
+    visible.value = true;
+    fetchGate();
+}
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(XEUtils.omit(form.value, "fileData"), (_, key) => {
+        if (key == "metaFeatures") {
+            if (formatMeta(data).url) {
+                XEUtils.set(form.value, "fileData", {
+                    metaKey: formatMeta(data).metaKey,
+                    path: formatMeta(data).url
+                })
+            }
+
+            XEUtils.set(form.value, key, XEUtils.toStringJSON(XEUtils.get(data, key, "{}")));
+        } else XEUtils.set(form.value, key, XEUtils.get(data, key));
+    });
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const metaKey = XEUtils.get(form.value, "fileData.metaKey", "imageFile");
+            const data = XEUtils.omit(form.value, "metaFeatures", "fileData");
+            const metaFeatures = XEUtils.clone(form.value.metaFeatures);
+
+            XEUtils.set(metaFeatures, `request.AlarmInfoPlate.result.PlateResult.${metaKey}`, form.value.fileData.path);
+            XEUtils.set(data, "metaFeatures", XEUtils.toJSONString(metaFeatures));
+
+            isSaving.value = true;
+            API.parking.record[mode.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", mode.value);
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+const removeSuccess = () => {
+    if (form.value.id) {
+        const metaKey = XEUtils.get(form.value, "fileData.metaKey", "imageFile");
+        const metaFeatures = XEUtils.clone(form.value.metaFeatures);
+        XEUtils.set(metaFeatures, `request.AlarmInfoPlate.result.PlateResult.${metaKey}`, "");
+        
+        const data = {
+            id: form.value.id,
+            gateId: form.value.gateId,
+            metaFeatures: XEUtils.toJSONString(metaFeatures)
+        }
+        
+        isDel.value = true;
+        API.parking.record.edit(data);
+    }
+}
+
+defineExpose({
+    open,
+    setData
+})
+</script>
+
+<style scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+}
+</style>

+ 186 - 0
src/views/dataMock/parking/components/record/index.vue

@@ -0,0 +1,186 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.parking.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
+        <template #default_imgUrl="{ row }">
+            <template v-if="formatMeta(row).url">
+                <vxe-image v-if="XEUtils.startsWith(formatMeta(row).url, '/parking')" style="cursor: pointer;" :src="'/minio' + formatMeta(row).url" width="40" height="40" :toolbar-config="imageToolbar"></vxe-image>
+                <vxe-image v-else style="cursor: pointer;" :src="'data:image/jpeg;base64,' + formatMeta(row).url" width="40" height="40" :toolbar-config="imageToolbar"></vxe-image>
+            </template>
+        </template>
+        
+        <template #action="{ row }">
+            <el-button type="primary" link @click="table_edit(row)">
+                <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
+            </el-button>
+            <el-button type="primary" link @click="table_del(row)">
+                <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
+            </el-button>
+        </template>
+    </scTable>
+
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialogClose"></record-detail>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource, objectToArray } from "@/utils/basicDic";
+import { plateColorDic, eventDic, formatMeta } from "@/views/dataMock/parking/main";
+import recordDetail from "./detail";
+
+const props = defineProps({
+    options: { type: Object, default: () => {} },
+    isTemp: { type: Boolean, default: false },
+    hideHandler: { type: Boolean, default: false }
+})
+const visible = computed(() => !props.isTemp);
+
+const proConfig = reactive({
+    visible,
+    storageKey: "PROJECT",
+    resetValue: TOOL.data.get("PROJECT_ID"),
+    optionProps: { label: "projectName", value: "fpiId" },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, { ...data, gateId: null })
+    }
+})
+
+const gateConfig = reactive({
+    visible,
+    api: {
+        key: "parking.gate",
+        query: {
+            projectId: computed(() => formConfig.data.projectId),
+            projectIdNot: 1
+        }
+    },
+    slot: {
+        style: { float: "right", paddingLeft: "6px", color: "#8492a6" }
+    },
+    optionProps: { label: "gateName", value: "id", slot: ({ data }) => XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === data.projectId), "projectName") },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, data)
+    }
+})
+
+const selectConfig = reactive({
+    options: objectToArray(plateColorDic),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
+const datetimerangeConfig = reactive({
+    span: 7,
+    resetValue: () => [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().endOf("day").format("YYYY-MM-DD HH:mm:ss")],
+    props: {
+        type: "datetimerange",
+        startPlaceholder: "开始时间",
+        endPlaceholder: "结束时间",
+        format: "YYYY-MM-DD HH:mm"
+    }
+})
+
+const toolbarConfig = reactive({
+    enabled: true,
+    print: false
+})
+
+const formConfig = reactive({
+    data: {
+        projectId: TOOL.data.get("PROJECT_ID"),
+        projectIdNot: 1,
+        applySql: "(day_seconds >= 64800 and day_seconds <= 86399) or (day_seconds >=0 and day_seconds <= 21599)",
+        createTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().endOf("day").format("YYYY-MM-DD HH:mm:ss")]
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemSelect("gateId", "场区闸口", gateConfig),
+        mapFormItemInput("plate", "车牌号"),
+        mapFormItemSelect("color", "车牌颜色", selectConfig),
+        mapFormItemSelect("event", "事件类型", { ...selectConfig, options: objectToArray(eventDic) }),
+        mapFormItemDatePicker("createTime", "抓拍时间", datetimerangeConfig)
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "projectId", field: visible.value ? "" : "projectIdNot" },
+    visible.value ? { column: "projectIdNot" } : {},
+    { column: "gateId" },
+    { column: "plateLike", field: "plate" },
+    { column: "color" },
+    { column: "event" },
+    { column: "applySql" },
+    { column: "createTimeBegin", field: "createTime[0]" },
+    { column: "createTimeEnd", field: "createTime[1]" }
+])
+
+const columns = reactive([
+    { visible: !props.hideHandler, type: "checkbox", fixed: "left", width: 40 },
+    { type: "seq", fixed: "left", width: 60 },
+    { visible, type: "html", field: "projectName", title: "项目名称", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId == row.projectId), "projectName") },
+    { visible, type: "html", field: "groundName", title: "工地场区", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "gateName", title: "场区闸口", minWidth: 160, sortable: true },
+    { type: "html", field: "plate", title: "车牌号", minWidth: 120, sortable: true },
+    { type: "html", field: "color", title: "车牌颜色", minWidth: 120, sortable: true, formatter: ({ cellValue }) => XEUtils.get(plateColorDic, cellValue, cellValue) },
+    { type: "html", field: "event", title: "事件类型", minWidth: 120, sortable: true, formatter: ({ cellValue }) => XEUtils.get(eventDic, cellValue, cellValue) },
+    { type: "html", field: "createTime", title: "抓拍时间", minWidth: 160, sortable: true },
+    { field: "imageFile", title: "抓拍图片", minWidth: 110, align: "center", slots: { default: "default_imgUrl" } },
+    { visible, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { visible: !props.hideHandler, title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+])
+
+const imageToolbar = reactive({
+    print: false,
+    download: true
+})
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
+
+const refreshTable = (mode = "add") => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData(mode);
+}
+
+const recordRef = ref();
+const dialog = ref(false);
+
+const dialogClose = isDel => {
+    dialog.value = false;
+    isDel && refreshTable();
+}
+
+const table_add = () => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.open());
+}
+
+const table_edit = row => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.setData(row));
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该监测记录?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.parking.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable,
+    getTableTotal
+})
+</script>

src/views/dataMock/tcm/components/template.vue → src/views/dataMock/parking/components/template.vue


+ 229 - 0
src/views/dataMock/parking/detail.vue

@@ -0,0 +1,229 @@
+<template>
+    <el-dialog v-model="visible" title="数据模拟" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-tabs v-model="apiKey">
+            <el-tab-pane label="参数配置" name="makeData" disabled></el-tab-pane>
+            <el-tab-pane label="数据复制" name="copyData"></el-tab-pane>
+        </el-tabs>
+
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="126">
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择模拟项目" @change="form.targetGateId = null, dataTimeRange()">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col v-if="form.targetProjectId" :md="12" :xs="24">
+                    <el-form-item label="数据时间范围">
+                        <template v-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/basic/acceptItems')">去配置</el-button></template>
+                        <template v-else-if="acceptItem.beginTime">{{ $TOOL.dateFormat(acceptItem.beginTime, "YY.M.D") }}<span>-{{ acceptItem.endTime && $TOOL.dateFormat(acceptItem.endTime, "YY.M.D") || "至今" }}</span></template>
+                        <template v-else>该项目未配置数据时间范围,<el-button type="primary" link @click="$router.push('/basic/project')">去配置</el-button></template>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟场区闸口" prop="targetGateId">
+                        <el-select v-model="form.targetGateId" filterable placeholder="请选择模拟场区闸口" @change="targetGateChange">
+                            <el-option v-for="item in filterTargetG" :key="item.id" :label="item.gateName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item class="range-item" label="模拟时间范围" prop="targetBeginTime">
+                        <el-date-picker v-model="form.targetBeginTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="new Date()" placeholder="请选择模拟数据开始时间" @change="dateChange"></el-date-picker>
+                        <span>至</span>
+                        <el-input v-model="form.targetEndTime" readonly placeholder="模拟数据结束时间">
+                            <template #prefix><sc-iconify icon="ep:clock"></sc-iconify></template>
+                        </el-input>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="数据来源" prop="source">
+                        <el-radio-group v-model="form.source" @change="refreshTable">
+                            <el-radio value="template">模版项目</el-radio>
+                            <el-radio value="other">其他项目</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item style="margin-bottom: 0;" label="数据处理" prop="isCover">
+                        <el-radio-group v-model="form.isCover">
+                            <el-radio :value="false">重复新增</el-radio>
+                            <el-radio :value="true">数据覆盖</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider />
+
+            <el-row>
+                <template v-if="form.source == 'other'">
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="数据源项目" prop="sourceProjectId">
+                            <el-select v-model="form.sourceProjectId" filterable placeholder="请选择数据源项目" @change="form.sourceGateId = null, refreshTable()">
+                                <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :lg="8" :md="12" :xs="24">
+                        <el-form-item label="数据源闸口" prop="sourceGateId">
+                            <el-select v-model="form.sourceGateId" filterable placeholder="请选择数据源闸口" @change="refreshTable">
+                                <el-option v-for="item in filterSourceG" :key="item.id" :label="item.gateName" :value="item.id"></el-option>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="抓拍时间" prop="sourceTime">
+                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <data-table ref="tableRef" :isTemp="form.source == 'template'" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit()">提交</el-button>
+            <el-button v-if="form.source == 'other'" :loading="isSaving" type="primary" auto-insert-space @click="submit('template')">保存为模版</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import { transferType } from "./main";
+import dataTable from "./components/record";
+
+const route = useRoute();
+const $emit = defineEmits(["success", "closed"]);
+const apiKey = ref("copyData");
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetGateId: null,
+    targetEventType: null,
+    targetBeginTime: null,
+    targetEndTime: null,
+    isCover: false,
+    source: "other",
+    sourceProjectId: null,
+    sourceProjectIdNot: 1,
+    applySql: "(day_seconds >= 64800 and day_seconds <= 86399) or (day_seconds >=0 and day_seconds <= 21599)",
+    sourceGateId: null,
+    sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择模拟项目" }],
+    targetGateId: [{ required: true, message: "请选择模拟场区闸口" }],
+    targetBeginTime: [{ required: true, message: "请选择模拟数据开始时间" }],
+    isCover: [{ required: true }],
+    source: [{ required: true }],
+    sourceProjectId: [{ required: true, message: "请选择数据源项目" }],
+    sourceGateId: [{ required: true, message: "请选择数据源闸口" }],
+    sourceTime: [{ required: true, message: "请选择数据源抓拍时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: form.value.source == "template" ? "sourceProjectIdNot" : "sourceProjectId"  },
+        form.value.source == "template" ? {} : { column: "projectIdNot", field: "sourceProjectIdNot" },
+        { column: "gateId", field: "sourceGateId" },
+        { column: "applySql" },
+        { column: "createTimeBegin", field: "sourceTime[0]" },
+        { column: "createTimeEnd", field: "sourceTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const acceptItem = ref({});
+const dataTimeRange = async () => {
+    const query = {
+        projectId: form.value.targetProjectId,
+        itemName: XEUtils.last(route.meta.title.split("-"))
+    }
+    const res = await API.system.project.bindItem.judgment(query);
+    acceptItem.value = res || {};
+}
+
+const gates = ref([]);
+const filterTargetG = computed(() => form.value.targetProjectId ? XEUtils.filter(gates.value, item => item.projectId == form.value.targetProjectId) : []);
+const filterSourceG = computed(() => form.value.sourceProjectId ? XEUtils.filter(gates.value, item => item.projectId == form.value.sourceProjectId) : []);
+const fetchGate = async () => {
+    const res = await API.parking.gate.get();
+    gates.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    TOOL.data.get("PROJECT_ID") && dataTimeRange();
+    fetchGate();
+}
+
+const targetGateChange = id => form.value.targetEventType = XEUtils.get(transferType, XEUtils.get(XEUtils.find(gates.value, item => item.id == id), "gateType"));
+
+const dateChange = () => {
+    if (form.value.sourceTime && form.value.sourceTime.length && form.value.targetBeginTime) form.value.targetEndTime = moment(form.value.targetBeginTime).add(moment(XEUtils.last(form.value.sourceTime)).diff(XEUtils.first(form.value.sourceTime))).format("YYYY-MM-DD HH:mm:ss")
+    else form.value.targetEndTime = null;
+}
+
+const formRef = ref();
+const submit = key => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
+            
+            const data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "applySql", "source", "sourceTime");
+            XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));
+            XEUtils.set(data, "sourceEndTime", XEUtils.last(form.value.sourceTime));
+            
+            form.value.source == "template" && XEUtils.set(data, "sourceGateId", XEUtils.get(XEUtils.find(gates.value, item => item.projectId == 1), "id"));
+            if (key == "template") {
+                XEUtils.set(data, "targetProjectId", 1);
+                XEUtils.set(data, "targetGateId", XEUtils.get(XEUtils.find(gates.value, item => item.projectId == 1), "id"));
+            }
+
+            isSaving.value = true;
+            API.parking.dataMock[apiKey.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form .range-item :deep(.el-form-item__content) {
+    .el-date-editor, .el-input {flex: 1;}
+    .el-date-editor + span {padding: 0 10px;}
+}
+
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 60 - 0
src/views/dataMock/parking/index.vue

@@ -0,0 +1,60 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
+            <template #extra-right>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record' || activeName == 'template'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="parking" />
+	</el-container>
+
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
+</template>
+
+<script setup>
+import { workerStates } from "./main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+
+const componentRef = ref();
+const mockRef = ref();
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value.mock = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
+}
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

+ 39 - 0
src/views/dataMock/parking/main.js

@@ -0,0 +1,39 @@
+import XEUtils from "xe-utils"
+
+export const workerStates = {
+    record: "设备监控",
+    monos: "任务中心",
+    template: "模版项目"
+}
+
+export const transferType = {
+    PARKING_GATETYPE_ENTER: "PARKING_EVENT_ENTER",
+    PARKING_GATETYPE_EXIT: "PARKING_EVENT_EXIT"
+}
+
+export const eventDic = {
+    PARKING_EVENT_ENTER: "车辆进场",
+    PARKING_EVENT_EXIT: "车辆出场",
+    PARKING_EVENT_SCAN: "陌生车辆"
+}
+
+export const plateColorDic = {
+    blue: "蓝色",
+    yellow: "黄色",
+    black: "黑色",
+    white: "白色",
+    green: "绿色"
+}
+
+export function formatMeta(row) {
+    let metaKey = ""
+    const meta = XEUtils.toStringJSON(row.metaFeatures, "{}")
+
+    if (!XEUtils.isUndefined(XEUtils.get(meta, "request.AlarmInfoPlate.result.PlateResult.imageFragmentFile"))) metaKey = "imageFragmentFile"
+    if (!XEUtils.isUndefined(XEUtils.get(meta, "request.AlarmInfoPlate.result.PlateResult.imageFile"))) metaKey = "imageFile"
+
+    return {
+        metaKey,
+        url: XEUtils.get(meta, "request.AlarmInfoPlate.result.PlateResult.imageFile", XEUtils.get(meta, "request.AlarmInfoPlate.result.PlateResult.imageFragmentFile"))
+    }
+}

+ 0 - 46
src/views/dataMock/scc/index.vue

@@ -1,46 +0,0 @@
-<template>
-	<el-container class="is-vertical">
-        <sc-page-header addText="数据模拟" @add="mock_add">
-            <template #extra-right>
-                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">数据录入</el-button>
-            </template>
-        </sc-page-header>
-
-        <el-tabs v-model="activeName">
-            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
-        </el-tabs>
-
-        <component ref="componentRef" :is="allcomp[activeName]" taskType="scc" />
-	</el-container>
-
-    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
-</template>
-
-<script setup>
-import { workerStates } from "./main";
-import comp from "./components";
-import monos from "@/views/dataMock/tasks/monos";
-import mockDetail from "./detail";
-
-const allcomp = { ...comp, monos };
-const activeName = ref("record");
-
-const componentRef = ref();
-const mockRef = ref();
-const dialog = ref(false);
-
-const table_add = () => componentRef.value.table_add();
-
-const mock_add = () => {
-    dialog.value = true;
-    nextTick(() => mockRef.value?.open());
-}
-
-const refreshState = () => {
-    if (activeName.value == "monos") setTimeout(() => componentRef.value.refreshTable(), 2000);
-}
-</script>
-
-<style lang="scss" scoped>
-.el-tabs {padding: 0 12px;background: #fff;}
-</style>

+ 124 - 0
src/views/dataMock/smoke/batchDel.vue

@@ -0,0 +1,124 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="项目名称" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    smokedetType: "smokedet_standard",
+    targetMountedId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: "targetProjectId"  },
+        { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "smokedetType" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.smokedet.mounted.get({ groundType: "SMOKEDET_GROUND_STANDARD" });
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "smokedetType");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+
+            isSaving.value = true;
+            API.smokedet.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

src/views/dataMock/tcm/components/index.js → src/views/dataMock/smoke/components/index.js


+ 113 - 0
src/views/dataMock/smoke/components/record/detail.vue

@@ -0,0 +1,113 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="480" :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <template v-if="props.projectId != 1">
+                <el-form-item label="所属项目" prop="projectId">
+                    <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                        <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="设备安装点" prop="mountedId">
+                    <el-select v-model="form.mountedId" filterable placeholder="请选择设备安装点">
+                        <el-option v-for="item in filterMounteds" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                    </el-select>
+                </el-form-item>
+            </template>
+            <el-form-item label="数据类型" prop="dataType">
+                <el-select v-model="form.dataType" placeholder="请选择数据类型">
+                    <el-option v-for="(label, key) in typeDic" :key="key" :label="label" :value="key"></el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="监测时间" prop="createTime">
+                <el-date-picker v-model="form.createTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择监测时间"></el-date-picker>
+            </el-form-item>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { typeDic } from "@/views/dataMock/smoke/main";
+
+const $emit = defineEmits(["success", "closed"]);
+const props = defineProps({
+    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") }
+});
+
+const visible = ref(false);
+const isSaving = ref(false);
+
+const mode = ref("add");
+const titleMap = reactive({
+    add: "数据录入",
+    edit: "修改"
+});
+
+const form = ref({
+    id: null,
+    projectId: props.projectId,
+    mountedId: null,
+    dataType: null,
+    createTime: null,
+    features: "{}"
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    mountedId: [{ required: true, message: "请选择设备安装点" }],
+    dataType: [{ required: true, message: "请选择数据类型" }],
+    createTime: [{ required: true, message: "请选择监测时间" }]
+})
+
+const mounteds = ref([]);
+const filterMounteds = computed(() => form.value.projectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.projectId) : []);
+const fetchMounted = async () => {
+    const res = await API.smokedet.mounted.get({ groundType: "SMOKEDET_GROUND_STANDARD" });
+    mounteds.value = res || [];
+    if (props.projectId == 1) form.value.mountedId = XEUtils.get(XEUtils.find(res, item => item.projectId == 1), "id");
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(form.value, (_, key) => XEUtils.set(form.value, key, XEUtils.get(data, key)));
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            isSaving.value = true;
+            API.smokedet.record[mode.value](form.value).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", mode.value);
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open,
+    setData
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+}
+</style>

+ 166 - 0
src/views/dataMock/smoke/components/record/index.vue

@@ -0,0 +1,166 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.smokedet.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
+        <template #action="{ row }">
+            <el-button type="primary" link @click="table_edit(row)">
+                <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
+            </el-button>
+            <el-button type="primary" link @click="table_del(row)">
+                <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
+            </el-button>
+        </template>
+    </scTable>
+
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog = false"></record-detail>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource, objectToArray } from "@/utils/basicDic";
+import { typeDic } from "@/views/dataMock/smoke/main";
+import recordDetail from "./detail";
+
+const props = defineProps({
+    options: { type: Object, default: () => {} },
+    isTemp: { type: Boolean, default: false },
+    hideHandler: { type: Boolean, default: false }
+})
+const visible = computed(() => !props.isTemp);
+
+const proConfig = reactive({
+    visible,
+    span: 5,
+    storageKey: "PROJECT",
+    resetValue: TOOL.data.get("PROJECT_ID"),
+    optionProps: { label: "projectName", value: "fpiId" },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, { ...data, mountedId: null })
+    }
+})
+
+const mountedConfig = reactive({
+    visible,
+    api: {
+        key: "smokedet.mounted",
+        query: {
+            projectId: computed(() => formConfig.data.projectId),
+            groundType: "SMOKEDET_GROUND_STANDARD",
+            projectIdNot: 1
+        }
+    },
+    slot: {
+        style: { float: "right", paddingLeft: "6px", color: "#8492a6" }
+    },
+    optionProps: { label: "mountedName", value: "id", slot: ({ data }) => XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === data.projectId), "projectName") },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, data)
+    }
+})
+
+const selectConfig = reactive({
+    options: objectToArray(typeDic),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
+const datetimerangeConfig = reactive({
+    span: 7,
+    resetValue: () => [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")],
+    props: {
+        type: "datetimerange",
+        startPlaceholder: "开始时间",
+        endPlaceholder: "结束时间",
+        format: "YYYY-MM-DD HH:mm"
+    }
+})
+
+const toolbarConfig = reactive({
+    enabled: true,
+    print: false
+})
+
+const formConfig = reactive({
+    data: {
+        projectId: TOOL.data.get("PROJECT_ID"),
+        projectIdNot: 1,
+        smokedetType: "smokedet_standard",
+        createTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")]
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemSelect("mountedId", "设备安装点", mountedConfig),
+        mapFormItemDatePicker("createTime", "监测时间", datetimerangeConfig),
+        mapFormItemSelect("dataType", "数据类型", selectConfig)
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "projectId", field: visible.value ? "" : "projectIdNot" },
+    visible.value ? { column: "projectIdNot" } : {},
+    { column: "smokedetType" },
+    { column: "mountedId" },
+    { column: "dataType" },
+    { column: "createTimeBegin", field: "createTime[0]" },
+    { column: "createTimeEnd", field: "createTime[1]" }
+])
+
+const columns = reactive([
+    { visible: !props.hideHandler, type: "checkbox", fixed: "left", width: 40 },
+    { type: "seq", fixed: "left", width: 60 },
+    { visible, type: "html", field: "projectName", title: "项目名称", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId == row.projectId), "projectName") },
+    { visible, type: "html", field: "groundName", title: "工地场区", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "mountedName", title: "设备安装点", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "sensorName", title: "传感器名称", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "sensorNo", title: "传感器编号", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.toStringJSON(row.features), "nodeId") },
+    { type: "html", field: "dataType", title: "数据类型", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(typeDic, cellValue, cellValue) },
+    { type: "html", field: "createTime", title: "监测时间", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { visible: !props.hideHandler, title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+])
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
+
+const refreshTable = (mode = "add") => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData(mode);
+}
+
+const recordRef = ref();
+const dialog = ref(false);
+
+const table_add = () => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.open());
+}
+
+const table_edit = row => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.setData(row));
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该监测记录?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.smokedet.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable,
+    getTableTotal
+})
+</script>

+ 163 - 0
src/views/dataMock/smoke/detail.vue

@@ -0,0 +1,163 @@
+<template>
+    <el-dialog v-model="visible" title="数据模拟" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-tabs v-model="apiKey">
+            <el-tab-pane label="参数配置" name="makeData"></el-tab-pane>
+            <el-tab-pane label="数据复制" name="copyData" disabled></el-tab-pane>
+        </el-tabs>
+
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="126">
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择模拟项目" @change="form.targetMountedId = null, dataTimeRange()">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col v-if="form.targetProjectId" :md="12" :xs="24">
+                    <el-form-item label="数据时间范围">
+                        <template v-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/basic/acceptItems')">去配置</el-button></template>
+                        <template v-else-if="acceptItem.beginTime">{{ $TOOL.dateFormat(acceptItem.beginTime, "YY.M.D") }}<span>-{{ acceptItem.endTime && $TOOL.dateFormat(acceptItem.endTime, "YY.M.D") || "至今" }}</span></template>
+                        <template v-else>该项目未配置数据时间范围,<el-button type="primary" link @click="$router.push('/basic/project')">去配置</el-button></template>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择模拟项目安装点">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟时间范围" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :shortcuts="shortcuts" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item class="step-item" label="时间步长" prop="timeStepSec">
+                        <el-input-number v-model="form.timeStepSec" :min="0" :controls="false" placeholder="时间步长"></el-input-number>
+
+                        <el-radio-group v-model="form.timeStepType" @change="stepTypeChange">
+                            <el-radio value="second">秒</el-radio>
+                            <el-radio value="minute">分钟</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="数据处理" prop="isCover">
+                        <el-radio-group v-model="form.isCover">
+                            <el-radio :value="false">重复新增</el-radio>
+                            <el-radio :value="true">数据覆盖</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit()">提交</el-button>
+            <el-button v-if="form.source == 'other'" :loading="isSaving" type="primary" auto-insert-space @click="submit('template')">保存为模版</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+
+const route = useRoute();
+const $emit = defineEmits(["success", "closed"]);
+const apiKey = ref("makeData");
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    smokedetType: "smokedet_standard",
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetMountedId: null,
+    targetTime: [],
+    timeStepSec: 3,
+    timeStepType: "minute",
+    isCover: false
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择模拟项目" }],
+    targetMountedId: [{ required: true, message: "请选择模拟项目安装点" }],
+    targetTime: [{ required: true, message: "请选择模拟时间范围" }],
+    timeStepSec: [{ required: true, message: "请输入时间步长" }],
+    isCover: [{ required: true }]
+});
+
+const acceptItem = ref({});
+const dataTimeRange = async () => {
+    const query = {
+        projectId: form.value.targetProjectId,
+        itemName: XEUtils.last(route.meta.title.split("-"))
+    }
+    const res = await API.system.project.bindItem.judgment(query);
+    acceptItem.value = res || {};
+}
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.smokedet.mounted.get({ groundType: "SMOKEDET_GROUND_STANDARD" });
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    TOOL.data.get("PROJECT_ID") && dataTimeRange();
+    fetchMounted();
+}
+
+const stepTypeChange = e => {
+    if (e == "minute") XEUtils.set(form.value, "timeStepSec", XEUtils.floor(XEUtils.divide(form.value.timeStepSec, 60)));
+    if (e == "second") XEUtils.set(form.value, "timeStepSec", XEUtils.multiply(form.value.timeStepSec, 60));
+}
+
+const formRef = ref();
+const submit = key => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.omit(form.value, "targetTime", "timeStepType");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            XEUtils.set(data, "timeStepSec", form.value.timeStepType == "minute" ? XEUtils.multiply(form.value.timeStepSec, 60) : form.value.timeStepSec);
+            
+            isSaving.value = true;
+            API.smokedet.dataMock[apiKey.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+
+.el-form-item .el-input-number {width: 100%;}
+.el-form-item .el-input-number :deep(.el-input__prefix) {margin-right: 8px;}
+.el-form-item .el-input-number :deep(.el-input__inner) {text-align: unset;}
+
+.el-form-item.step-item {
+    .el-input-number {flex: 1;}
+    .el-radio-group {margin-left: 20px;}
+}
+</style>

+ 60 - 0
src/views/dataMock/smoke/index.vue

@@ -0,0 +1,60 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
+            <template #extra-right>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="smokedet_standard" />
+	</el-container>
+
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
+</template>
+
+<script setup>
+import { workerStates } from "./main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+
+const componentRef = ref();
+const mockRef = ref();
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value.mock = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
+}
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

+ 9 - 0
src/views/dataMock/smoke/main.js

@@ -0,0 +1,9 @@
+export const workerStates = {
+    record: "设备监控",
+    monos: "任务中心"
+}
+
+export const typeDic = {
+    SMOKEDET_NORMAL: "正常数据",
+    SMOKEDET_WARN: "报警数据"
+}

+ 122 - 0
src/views/dataMock/spray/batchDel.vue

@@ -0,0 +1,122 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="项目名称" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetMountedId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: "targetProjectId"  },
+        { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.autospray.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+
+            isSaving.value = true;
+            API.autospray.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 17 - 0
src/views/dataMock/spray/components/index.js

@@ -0,0 +1,17 @@
+import XEUtils from "xe-utils"
+
+const resultComps = {}
+let requireComponent = require.context(
+    "./", // 在当前目录下查找
+    true, // 遍历子文件夹
+    /\.vue$/ // 正则匹配 以 .vue结尾的文件
+)
+requireComponent.keys().forEach(fileName => {
+    const compName = fileName.replace(/^\.\/(.*)\.\w+$/, "$1")
+    const comp = requireComponent(fileName)
+    if (compName.includes("/")) {
+        if (XEUtils.last(compName.split("/")) == "index") resultComps[XEUtils.first(compName.split("/"))] = comp.default
+    } else resultComps[compName] = comp.default
+})
+
+export default resultComps

+ 1 - 1
src/views/dataMock/autospray/components/record/detail.vue

@@ -34,7 +34,7 @@
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
-import { typeDic } from "@/views/dataMock/autospray/main";
+import { typeDic } from "@/views/dataMock/spray/main";
 
 const $emit = defineEmits(["success", "closed"]);
 const props = defineProps({

+ 1 - 1
src/views/dataMock/autospray/components/record/index.vue

@@ -20,7 +20,7 @@ import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
 import { dataSource, objectToArray } from "@/utils/basicDic";
-import { typeDic } from "@/views/dataMock/autospray/main";
+import { typeDic } from "@/views/dataMock/spray/main";
 import recordDetail from "./detail";
 
 const props = defineProps({

src/views/dataMock/autospray/detail.vue → src/views/dataMock/spray/detail.vue


+ 60 - 0
src/views/dataMock/spray/index.vue

@@ -0,0 +1,60 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
+            <template #extra-right>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="autospray" />
+	</el-container>
+
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
+</template>
+
+<script setup>
+import { workerStates } from "./main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+
+const componentRef = ref();
+const mockRef = ref();
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value.mock = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
+}
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

src/views/dataMock/autospray/main.js → src/views/dataMock/spray/main.js


+ 122 - 0
src/views/dataMock/standard/batchDel.vue

@@ -0,0 +1,122 @@
+<template>
+    <el-dialog v-model="visible" title="批量删除" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="110">
+            <el-row>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="项目名称" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择删除数据的项目" @change="refreshTable">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择删除数据的安装点" @change="refreshTable">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="数据时间" prop="targetTime">
+                        <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <data-table ref="tableRef" hideHandler :options="tableOptions"></data-table>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">提交</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const shortcuts = rangeShortcuts();
+const form = ref({
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetProjectIdNot: 1,
+    targetMountedId: null,
+    targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+});
+
+const rules = reactive({
+    targetProjectId: [{ required: true, message: "请选择删除数据的项目" }],
+    targetMountedId: [{ required: true, message: "请选择删除数据的安装点" }],
+    targetTime: [{ required: true, message: "请选择删除数据的时间" }]
+})
+
+const tableRef = ref();
+const tableOptions = reactive({
+    batchDel: false,
+    maxHeight: 1048,
+    toolbarConfig: { enabled: true, print: false, zoom: false },
+    formConfig: { enabled: false, data: form },
+    paramsColums: computed(() => [
+        { column: "projectId", field: "targetProjectId"  },
+        { column: "projectIdNot", field: "targetProjectIdNot" },
+        { column: "mountedId", field: "targetMountedId" },
+        { column: "createTimeBegin", field: "targetTime[0]" },
+        { column: "createTimeEnd", field: "targetTime[1]" }
+    ])
+})
+const refreshTable = () => tableRef.value.refreshTable();
+
+const mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.scc.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+            XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+
+            isSaving.value = true;
+            API.scc.dataMock.removeData(data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 17 - 0
src/views/dataMock/standard/components/index.js

@@ -0,0 +1,17 @@
+import XEUtils from "xe-utils"
+
+const resultComps = {}
+let requireComponent = require.context(
+    "./", // 在当前目录下查找
+    true, // 遍历子文件夹
+    /\.vue$/ // 正则匹配 以 .vue结尾的文件
+)
+requireComponent.keys().forEach(fileName => {
+    const compName = fileName.replace(/^\.\/(.*)\.\w+$/, "$1")
+    const comp = requireComponent(fileName)
+    if (compName.includes("/")) {
+        if (XEUtils.last(compName.split("/")) == "index") resultComps[XEUtils.first(compName.split("/"))] = comp.default
+    } else resultComps[compName] = comp.default
+})
+
+export default resultComps

+ 1 - 1
src/views/dataMock/scc/components/record/detail.vue

@@ -39,7 +39,7 @@
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
-import { typeDic, unitDic } from "@/views/dataMock/scc/main";
+import { typeDic, unitDic } from "@/views/dataMock/standard/main";
 
 const $emit = defineEmits(["success", "closed"]);
 const props = defineProps({

+ 1 - 1
src/views/dataMock/scc/components/record/index.vue

@@ -20,7 +20,7 @@ import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
 import { dataSource, objectToArray } from "@/utils/basicDic";
-import { typeDic, unitDic } from "@/views/dataMock/scc/main";
+import { typeDic, unitDic } from "@/views/dataMock/standard/main";
 import recordDetail from "./detail";
 
 const props = defineProps({

src/views/dataMock/scc/components/threshold.vue → src/views/dataMock/standard/components/threshold.vue


+ 2 - 1
src/views/dataMock/scc/detail.vue

@@ -170,7 +170,8 @@ const submit = key => {
             if (form.value.dataSouce == "normal") {
                 XEUtils.set(data, "floatNumber", XEUtils.multiply(form.value.floatNumber, 100));
                 XEUtils.set(data, "timeStepSec", form.value.timeStepType == "minute" ? XEUtils.multiply(form.value.timeStepSec, 60) : form.value.timeStepSec);
-            } else {
+            } 
+            if (form.value.dataSouce == "alarm") {
                 XEUtils.set(data, "temWarnNum", form.value.temWarnNum);
                 XEUtils.set(data, "humWarnNum", form.value.humWarnNum);
             }

+ 60 - 0
src/views/dataMock/standard/index.vue

@@ -0,0 +1,60 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" addIcon="majesticons:data-plus-line" @add="mock_add">
+            <template #extra-right>
+                <el-button type="danger" @click="data_batch_del">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>数据删除
+                </el-button>
+                <el-button v-if="activeName == 'record'" type="primary" @click="table_add">
+                    <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>数据录入
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" taskType="scc" />
+	</el-container>
+
+    <mock-detail v-if="dialog.mock" ref="mockRef" @success="refreshState" @closed="dialog.mock = false"></mock-detail>
+    <batch-del-detail v-if="dialog.batchDel" ref="batchDelRef" @success="refreshState" @closed="dialog.batchDel = false"></batch-del-detail>
+</template>
+
+<script setup>
+import { workerStates } from "./main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+import batchDelDetail from "./batchDel";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+
+const componentRef = ref();
+const mockRef = ref();
+const batchDelRef = ref();
+const dialog = ref({
+    mock: false,
+    batchDel: false
+});
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value.mock = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const data_batch_del = () => {
+    dialog.value.batchDel = true;
+    nextTick(() => batchDelRef.value?.open());
+}
+
+const refreshState = () => activeName.value == "monos" && componentRef.value.refreshTable();
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

src/views/dataMock/scc/main.js → src/views/dataMock/standard/main.js


+ 26 - 11
src/views/dataMock/tasks/monos.vue

@@ -22,13 +22,18 @@ import { taskDic, tempProModel, objectToArray } from "@/utils/basicDic";
 import tableExpand from "./tableExpand";
 
 const props = defineProps({
-    m_apiKey: { type: String, default: "" },
     taskType: { type: String, default: "" }
 });
 
+const optionsDic = {
+    [props.taskType]: "标准数据",
+    [`${props.taskType}_remove`]: "数据删除",
+    ...XEUtils.get(taskDic.option, props.taskType, {})
+}
+
 const radioConfig = reactive({
-    visibleMethod: ({ data }) => tempProModel.includes(data.taskType),
-    span: 5,
+    visibleMethod: () => tempProModel.includes(props.taskType),
+    span: computed(() => props.taskType == "car_rinse" ? 6 : 5),
     resetValue: false,
     options: [{ label: "是", value: true }, { label: "否", value: false }],
     events: {
@@ -50,9 +55,9 @@ const proConfig = reactive({
 })
 
 const mountedConfig = reactive({
-    visibleMethod: ({ data }) => !(data.isTemp == 1 || data.taskType == "car_rinse"),
+    visibleMethod: ({ data }) => !(data.isTemp == 1 || props.taskType == "car_rinse"),
     api: {
-        key: computed(() => `${XEUtils.first(props.taskType.split("_"))}.mounted`),
+        key: computed(() => `${props.taskType}.${XEUtils.get(taskDic.mountedSelect, `${props.taskType}.prefix`, "mounted")}`),
         query: {
             projectId: computed(() => formConfig.data.projectId),
             projectIdNot: 1
@@ -61,12 +66,20 @@ const mountedConfig = reactive({
     slot: {
         style: { float: "right", paddingLeft: "6px", color: "#8492a6" }
     },
-    optionProps: { label: "mountedName", value: "id", slot: ({ data }) => XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === data.projectId), "projectName") },
+    optionProps: { label: computed(() => `${XEUtils.get(taskDic.mountedSelect, `${props.taskType}.prefix`, "mounted")}Name`), value: "id", slot: ({ data }) => XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === data.projectId), "projectName") },
     events: {
         change: data => XEUtils.assign(formConfig.data, data)
     }
 })
 
+const optionConfig = reactive({
+    span: 5,
+    options: objectToArray(optionsDic),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
 const selectConfig = reactive({
     options: objectToArray(taskDic.state),
     events: {
@@ -106,15 +119,16 @@ const formConfig = reactive({
         parentId: "0",
         projectId: TOOL.data.get("PROJECT_ID"),
         projectIdNot: 1,
-        taskType: computed(() => props.taskType),
+        taskTypeLike: props.taskType,
         dateRange: [],
         createTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")]
     },
     items: [
         mapFormItemRadio("isTemp", "是否模版项目", radioConfig),
         mapFormItemSelect("projectId", "所属项目", proConfig),
-        mapFormItemSelect("mountedId", "所属安装点", mountedConfig),
+        mapFormItemSelect("mountedId", computed(() => XEUtils.get(taskDic.mountedSelect, `${props.taskType}.label`, "所属安装点")).value, mountedConfig),
         mapFormItemSelect("taskStatus", "任务状态", selectConfig),
+        mapFormItemSelect("taskType", "任务类型", optionConfig),
         mapFormItemDatePicker("dateRange", "时间范围", daterangeConfig),
         mapFormItemDatePicker("createTime", "创建时间", datetimerangeConfig)
     ]
@@ -122,10 +136,10 @@ const formConfig = reactive({
 
 const paramsColums = computed(() => [
     { column: "parentId" },
-    { column: "taskType" },
+    formConfig.data.taskType ? { column: "taskType" } : { column: "taskTypeLike" },
     { column: "projectId", field: formConfig.data.isTemp ? "projectIdNot" : "projectId" },
     formConfig.data.isTemp ? {} : { column: "projectIdNot" },
-    { column: "mountedId" },
+    { column: `${XEUtils.get(taskDic.mountedSelect, `${props.taskType}.prefix`, "mounted")}Id`, field: "mountedId" },
     { column: "taskStatus" },
     { column: "planBeginTimeBegin", field: "dateRange[0]" },
     { column: "planEndTimeEnd", field: "dateRange[1]" },
@@ -137,7 +151,8 @@ const columns = reactive([
     { type: "expand", fixed: "left", width: 40, align: "center", slots: { content: "expand_content" } },
     { type: "seq", fixed: "left", width: 60 },
     { visible: computed(() => !formConfig.data.isTemp), type: "html", field: "projectName", title: "项目名称", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId == row.projectId), "projectName") },
-    { visible: computed(() => !(formConfig.data.isTemp || formConfig.data.taskType == "car_rinse")), type: "html", field: "mountedName", title: "设备安装点", minWidth: 160, sortable: true },
+    { visible: computed(() => !(formConfig.data.isTemp || props.taskType == "car_rinse")), type: "html", field: computed(() => `${XEUtils.get(taskDic.mountedSelect, `${props.taskType}.prefix`, "mounted")}Name`).value, title: computed(() => `${XEUtils.get(taskDic.mountedSelect, `${props.taskType}.label`, "设备安装点")}`).value, minWidth: 160, sortable: true },
+    { type: "html", field: "taskType", title: "任务类型", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(optionsDic, cellValue, cellValue) },
     { type: "html", field: "planNumber", title: "任务总数", minWidth: 100, sortable: true },
     { type: "html", field: "finishNumber", title: "已完成数量", minWidth: 120, sortable: true },
     { field: "taskStatus", title: "任务状态", minWidth: 100, align: "center", editRender: { name: "$cell-tag" }, formatter: ({ cellValue }) => XEUtils.get(taskDic.state, cellValue, cellValue) },

+ 0 - 0
src/views/dataMock/tasks/tableExpand.vue


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor