Selaa lähdekoodia

重构 + 拖拽上传 + 里程碑

zhuangyunsheng 2 kuukautta sitten
vanhempi
commit
adb3c5336b
65 muutettua tiedostoa jossa 1352 lisäystä ja 116 poistoa
  1. 3 0
      src/assets/icons/PerimeterProtection.vue
  2. 1 1
      src/components/scCropper/index.vue
  3. 1 1
      src/components/scPageHeader/index.vue
  4. 1 1
      src/components/scUpload/file.vue
  5. 22 7
      src/components/scUpload/index.vue
  6. 15 2
      src/components/scUpload/minio.vue
  7. 40 13
      src/config/route.js
  8. 3 4
      src/views/basic/project/detail.vue
  9. 1 3
      src/views/basic/project/index.vue
  10. 1 1
      src/views/basic/project/items.vue
  11. 1 3
      src/views/basic/supplier/index.vue
  12. 11 1
      src/views/dataMock/aihazard/batchDel.vue
  13. 0 1
      src/views/dataMock/aihazard/components/record/index.vue
  14. 38 13
      src/views/dataMock/aihazard/detail.vue
  15. 0 1
      src/views/dataMock/broadcast/components/record/index.vue
  16. 1 1
      src/views/dataMock/broadcast/detail.vue
  17. 0 2
      src/views/dataMock/carwash/components/info/index.vue
  18. 8 3
      src/views/dataMock/carwash/components/record/detail.vue
  19. 6 2
      src/views/dataMock/carwash/components/record/index.vue
  20. 1 1
      src/views/dataMock/carwash/detail.vue
  21. 0 1
      src/views/dataMock/edgeprot/components/record/index.vue
  22. 1 1
      src/views/dataMock/edgeprot/detail.vue
  23. 0 2
      src/views/dataMock/elevator/components/alarm.vue
  24. 0 2
      src/views/dataMock/elevator/components/attendance.vue
  25. 0 1
      src/views/dataMock/elevator/components/record/index.vue
  26. 4 4
      src/views/dataMock/elevator/detail.vue
  27. 0 1
      src/views/dataMock/env/components/record/index.vue
  28. 3 3
      src/views/dataMock/env/detail.vue
  29. 0 1
      src/views/dataMock/parking/components/record/index.vue
  30. 6 6
      src/views/dataMock/parking/detail.vue
  31. 140 0
      src/views/dataMock/perimeter/batchDel.vue
  32. 17 0
      src/views/dataMock/perimeter/components/index.js
  33. 145 0
      src/views/dataMock/perimeter/components/record/detail.vue
  34. 168 0
      src/views/dataMock/perimeter/components/record/index.vue
  35. 12 0
      src/views/dataMock/perimeter/components/template.vue
  36. 240 0
      src/views/dataMock/perimeter/detail.vue
  37. 60 0
      src/views/dataMock/perimeter/index.vue
  38. 0 1
      src/views/dataMock/smoke/components/record/index.vue
  39. 1 1
      src/views/dataMock/smoke/detail.vue
  40. 0 1
      src/views/dataMock/spray/components/record/index.vue
  41. 1 1
      src/views/dataMock/spray/detail.vue
  42. 0 1
      src/views/dataMock/standard/components/record/index.vue
  43. 1 1
      src/views/dataMock/standard/detail.vue
  44. 0 2
      src/views/dataMock/tasks/monos.vue
  45. 0 2
      src/views/dataMock/tower/components/alarm.vue
  46. 0 2
      src/views/dataMock/tower/components/attendance.vue
  47. 0 1
      src/views/dataMock/tower/components/record/index.vue
  48. 4 4
      src/views/dataMock/tower/detail.vue
  49. 0 1
      src/views/dataMock/warehouse/components/record/index.vue
  50. 1 1
      src/views/dataMock/warehouse/detail.vue
  51. 1 2
      src/views/equipment/env.vue
  52. 1 3
      src/views/equipment/facerec.vue
  53. 1 3
      src/views/equipment/passqrcode.vue
  54. 1 2
      src/views/equipment/tower.vue
  55. 0 0
      src/views/system/acceptItems/detail.vue
  56. 1 3
      src/views/basic/acceptItems/index.vue
  57. 0 0
      src/views/system/acceptItems/main.js
  58. 17 0
      src/views/system/milestone/components/index.js
  59. 160 0
      src/views/system/milestone/components/record/detail.vue
  60. 155 0
      src/views/system/milestone/components/record/index.vue
  61. 12 0
      src/views/system/milestone/components/template.vue
  62. 34 0
      src/views/system/milestone/index.vue
  63. 10 0
      src/views/system/milestone/main.js
  64. 0 0
      src/views/system/tag/detail.vue
  65. 1 1
      src/views/basic/tag/index.vue

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 3 - 0
src/assets/icons/PerimeterProtection.vue


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

@@ -46,7 +46,7 @@
 		methods: {
 			init(){
 				this.crop = new Cropper(this.$refs.img, {
-					viewMode: 2,
+					viewMode: 0,
 					dragMode: 'move',
 					responsive: false,
 					aspectRatio: this.aspectRatio,

+ 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="$attrs.addIcon || 'ant-design:plus-outlined'"></sc-iconify></template>{{ $attrs.addText || "新增" }}
+                        <template #icon><sc-iconify :icon="$attrs.addIcon || 'ant-design:cloud-upload-outlined'"></sc-iconify></template>{{ $attrs.addText || "新增" }}
                     </el-button>
                     <slot name="extra-right"></slot>
                 </div>

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

@@ -101,7 +101,7 @@ export default {
             this.$notify.error({ title: "上传文件未成功", message });
         },
 
-        beforeRemove({ id, name, path }) { // id, name, path, size
+        beforeRemove({ id, name, path }) {
             return this.$confirm(`是否移除 ${name}? 此操作不可逆!`, "提示", {
                 type: "warning",
                 confirmButtonText: "移除"

+ 22 - 7
src/components/scUpload/index.vue

@@ -25,6 +25,7 @@
 			:disabled="disabled"
 			:show-file-list="false"
 			:accept="accept"
+			:drag="drag"
 			:limit="1"
 			:http-request="request"
 			:on-change="change"
@@ -33,8 +34,12 @@
 			:on-error="error"
 			:on-exceed="handleExceed">
 			<slot>
-				<div class="el-upload--picture-card" :class="disabled && 'is-disabled'">
-					<div class="file-empty">
+				<div :class="['el-upload--picture-card', disabled && 'is-disabled', drag && 'is-drag']">
+					<div v-if="drag" class="file-empty">
+						<el-icon><el-icon-upload-filled /></el-icon>
+						<h4>将文件拖到此处,或<el-text type="primary">点击上传</el-text></h4>
+					</div>
+                    <div v-else class="file-empty">
 						<el-icon><component :is="icon" /></el-icon>
 						<h4 v-if="title">{{ title }}</h4>
 					</div>
@@ -66,6 +71,7 @@ export default {
         accept: { type: String, default: "image/gif, image/jpeg, image/png, video/mp4 , video/avi" },
         icon: { type: String, default: "el-icon-plus" },
         maxSize: { type: Number, default: 50 },
+        drag: { type: Boolean, default: false },
         disabled: { type: Boolean, default: false },
         round: { type: Boolean, default: false },
         onSuccess: { type: Function, default: () => { return true } },
@@ -150,11 +156,13 @@ export default {
                 type: "warning",
                 confirmButtonText: "移除"
             }).then(() => {
-                if (file.id) {
-                    this.$API.common.folder.rm(file.id).then(res => {
-                        if (res.code == 200) this.clearFiles();
-                    }).catch(() => {});
-                } else this.clearFiles();
+                const entityID = file.id || file.path;
+                this.$API.common.folder.rm(entityID).then(res => {
+                    if (res.code == 200) {
+                        this.clearFiles();
+                        this.$emit("removeSuccess");
+                    }
+                }).catch(() => {});
             }).catch(() => {});
         },
 
@@ -351,6 +359,9 @@ export default {
   color: #8c939d;
   margin-top: 8px;
 }
+.sc-upload .file-empty h4 .el-text {
+  font-size: inherit;
+}
 
 .sc-upload.sc-upload-round {
   border-radius: 50%;
@@ -368,4 +379,8 @@ export default {
 .sc-upload.sc-upload-round .sc-upload__img-actions span {
   width: 100%;
 }
+
+.sc-upload :deep(.el-upload-dragger) {width: 100%;height: 100%;padding: 0;}
+.sc-upload :deep(.el-upload-dragger.is-dragover) {border-width: 1px;}
+.sc-upload .el-upload--picture-card.is-drag {border: none;}
 </style>

+ 15 - 2
src/components/scUpload/minio.vue

@@ -24,6 +24,7 @@
 			:disabled="disabled"
 			:show-file-list="false"
 			:accept="accept"
+			:drag="drag"
 			:limit="1"
 			:http-request="request"
 			:on-change="change"
@@ -32,8 +33,12 @@
 			:on-error="error"
 			:on-exceed="handleExceed">
 			<slot>
-				<div class="el-upload--picture-card" :class="disabled && 'is-disabled'">
-					<div class="file-empty">
+				<div :class="['el-upload--picture-card', disabled && 'is-disabled', drag && 'is-drag']">
+					<div v-if="drag" class="file-empty">
+						<el-icon><el-icon-upload-filled /></el-icon>
+						<h4>将文件拖到此处,或<el-text type="primary">点击上传</el-text></h4>
+					</div>
+                    <div v-else class="file-empty">
 						<el-icon><component :is="icon" /></el-icon>
 						<h4 v-if="title">{{ title }}</h4>
 					</div>
@@ -64,6 +69,7 @@ export default {
         accept: { type: String, default: "image/jpeg, image/png" },
         icon: { type: String, default: "el-icon-plus" },
         maxSize: { type: Number, default: 50 },
+        drag: { type: Boolean, default: false },
         disabled: { type: Boolean, default: false },
         round: { type: Boolean, default: false },
         onSuccess: { type: Function, default: () => { return true } },
@@ -353,6 +359,9 @@ export default {
   color: #8c939d;
   margin-top: 8px;
 }
+.sc-upload .file-empty h4 .el-text {
+  font-size: inherit;
+}
 
 .sc-upload.sc-upload-round {
   border-radius: 50%;
@@ -370,4 +379,8 @@ export default {
 .sc-upload.sc-upload-round .sc-upload__img-actions span {
   width: 100%;
 }
+
+.sc-upload :deep(.el-upload-dragger) {width: 100%;height: 100%;padding: 0;}
+.sc-upload :deep(.el-upload-dragger.is-dragover) {border-width: 1px;}
+.sc-upload .el-upload--picture-card.is-drag {border: none;}
 </style>

+ 40 - 13
src/config/route.js

@@ -29,12 +29,6 @@ const routes = [
                 meta: { title: "项目管理", icon: "ix:projects" },
                 component: "basic/project"
             },
-            {
-                name: "acceptItems",
-                path: "/basic/acceptItems",
-                meta: { title: "验收清单项", icon: "pajamas:list-bulleted" },
-                component: "basic/acceptItems"
-            },
             {
                 name: "supplier",
                 path: "/basic/supplier",
@@ -46,12 +40,6 @@ const routes = [
                 path: "/basic/customer",
                 meta: { title: "客户管理", icon: "garden:customer-lists-fill-26" },
                 component: "basic/customer"
-            },
-            {
-                name: "tag",
-                path: "/basic/tag",
-                meta: { title: "标签管理", icon: "mingcute:tag-line" },
-                component: "basic/tag"
             }
         ]
     },
@@ -124,6 +112,12 @@ const routes = [
                 meta: { title: "数据管理与模拟-智能临边防护网监测", icon: "lineicons:netlify" },
                 component: "dataMock/edgeprot"
             },
+            {
+                name: "perimeterMock",
+                path: "/dataMock/perimeter",
+                meta: { title: "数据管理与模拟-周界防护", icon: "PerimeterProtection" },
+                component: "dataMock/perimeter"
+            },
             {
                 name: "warehouseMock",
                 path: "/dataMock/warehouse",
@@ -201,7 +195,40 @@ const routes = [
         meta: { title: "售后管理", icon: "icon-park-outline:market" },
         redirect: "/afterSales/afterSales",
         children: []
-    }
+    },
+
+    {
+        name: "system",
+        path: "/system",
+        meta: { title: "系统管理", icon: "fluent:apps-settings-16-filled" },
+        redirect: "/system/milestone",
+        children: [
+            {
+                name: "milestone",
+                path: "/system/milestone",
+                meta: { title: "里程碑管理", icon: "streamline:business-progress-bar-2-remix" },
+                component: "system/milestone"
+            },
+            {
+                name: "task",
+                path: "/system/task",
+                meta: { title: "任务管理", icon: "streamline-ultimate:task-list-pin" },
+                component: "system/task"
+            },
+            {
+                name: "acceptItems",
+                path: "/system/acceptItems",
+                meta: { title: "验收清单项", icon: "pajamas:list-bulleted" },
+                component: "system/acceptItems"
+            },
+            {
+                name: "tag",
+                path: "/system/tag",
+                meta: { title: "标签管理", icon: "mingcute:tag-line" },
+                component: "system/tag"
+            }
+        ]
+    },
 ]
 
 export default routes;

+ 3 - 4
src/views/basic/project/detail.vue

@@ -15,7 +15,7 @@
                     </el-col>
                     <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-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 :lg="8" :md="12" :xs="24">
@@ -255,13 +255,12 @@
 </template>
 
 <script setup>
+import XEUtils from "xe-utils";
+import AMapLoader from "@amap/amap-jsapi-loader";
 import API from "@/api";
 import TOOL from "@/utils/tool";
-import XEUtils from "xe-utils";
-import AMapLoader from '@amap/amap-jsapi-loader';
 import { typeDic, statusDic, interfaceDic, polygonOptions } from "./main";
 import scUploadFile from "@/components/scUpload/file";
-import { ElMessage } from "element-plus";
 
 const $emit = defineEmits(["success", "closed"]);
 const props = defineProps({

+ 1 - 3
src/views/basic/project/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled" @add="table_add"></sc-page-header>
+        <sc-page-header @add="table_add"></sc-page-header>
 
         <scTable ref="xGridTable" :apiObj="$API.system.project" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
             <template #tree_select>
@@ -85,8 +85,6 @@ const getRemoteData = async () => {
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = (mode = "add", fpiId) => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData(mode);

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

@@ -63,7 +63,7 @@
 import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
-import { categoryDic, statisticTemp } from "@/views/basic/acceptItems/main";
+import { categoryDic, statisticTemp } from "@/views/system/acceptItems/main";
 
 const $emit = defineEmits(["success", "closed"]);
 const visible = ref(false);

+ 1 - 3
src/views/basic/supplier/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled" @add="table_add"></sc-page-header>
+        <sc-page-header @add="table_add"></sc-page-header>
 
         <scTable ref="xGridTable" :apiObj="$API.easyRun.supplier" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
             <template #action="{ row }">
@@ -57,8 +57,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = (mode = "add") => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData(mode);

+ 11 - 1
src/views/dataMock/aihazard/batchDel.vue

@@ -25,6 +25,13 @@
                         </el-form-item>
                     </el-col>
                 </template>
+                <el-col :lg="8" :md="12" :xs="24">
+                    <el-form-item label="识别结果">
+                        <el-select v-model="form.recordType" filterable clearable placeholder="请选择识别结果" @change="refreshTable">
+                            <el-option v-for="(label, key) in aiTypeDic" :key="key" :label="label" :value="key"></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>
@@ -49,6 +56,7 @@ import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
+import { aiTypeDic } from "@/utils/basicDic";
 import { rangeShortcuts } from "@/utils/shortcuts";
 import dataTable from "./components/record";
 
@@ -62,6 +70,7 @@ const form = ref({
     targetProjectId: TOOL.data.get("PROJECT_ID"),
     targetProjectIdNot: 1,
     targetMountedId: null,
+    recordType: null,
     targetTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
 });
 
@@ -82,6 +91,7 @@ const tableOptions = reactive({
         { column: "projectId", field: form.value.isTemp ? "targetProjectIdNot" : "targetProjectId"  },
         form.value.isTemp ? {} : { column: "projectIdNot", field: "targetProjectIdNot" },
         { column: "mountedId", field: "targetMountedId" },
+        { column: "recordType" },
         { column: "createTimeBegin", field: "targetTime[0]" },
         { column: "createTimeEnd", field: "targetTime[1]" }
     ])
@@ -104,7 +114,7 @@ const formRef = ref();
 const submit = () => {
     formRef.value.validate(valid => {
         if (valid) {
-            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId");
+            const data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "recordType");
             XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
             XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
             if (form.value.isTemp) {

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

@@ -135,7 +135,6 @@ const imageToolbar = reactive({
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 38 - 13
src/views/dataMock/aihazard/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>
@@ -49,27 +49,33 @@
                     </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-form-item label="数据处理" prop="opsDataType">
+                        <el-radio-group v-model="form.opsDataType">
+                            <el-radio :value="0">重复新增</el-radio>
+                            <el-radio :value="1">数据覆盖</el-radio>
+                            <el-radio :value="2">数据迁移</el-radio>
                         </el-radio-group>
                     </el-form-item>
                 </el-col>
+                <el-col v-if="form.opsDataType == 2" :md="12" :xs="24">
+                    <el-form-item label="迁移数据量" prop="recordNum">
+                        <el-input-number v-model="form.recordNum" :min="0" :controls="false" placeholder="请输入迁移数据量"></el-input-number>
+                    </el-form-item>
+                </el-col>
             </el-row>
 
-            <el-divider />
+            <el-divider style="margin-top: 6px;" />
 
             <el-row>
                 <template v-if="form.source == 'other'">
-                    <el-col :lg="8" :md="12" :xs="24">
+                    <el-col :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 :lg="8" :md="12" :xs="24">
+                    <el-col :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,7 +84,15 @@
                     </el-col>
                 </template>
 
-                <el-col :lg="8" :md="12" :xs="24">
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="识别结果">
+                        <el-select v-model="form.recordType" filterable clearable placeholder="请选择识别结果" @change="refreshTable">
+                            <el-option v-for="(label, key) in aiTypeDic" :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="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="refreshTable"></el-date-picker>
                     </el-form-item>
@@ -90,7 +104,7 @@
 
         <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 v-if="form.opsDataType != 2 && 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>
@@ -101,6 +115,7 @@ import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
+import { aiTypeDic } from "@/utils/basicDic";
 import { rangeShortcuts } from "@/utils/shortcuts";
 import dataTable from "./components/record";
 
@@ -116,11 +131,13 @@ const form = ref({
     targetMountedId: null,
     targetYear: null,
     targetMonth: null,
-    isCover: false,
     source: "other",
+    opsDataType: 0,
+    recordNum: null,
     sourceProjectId: null,
     sourceProjectIdNot: 1,
     sourceMountedId: null,
+    recordType: null,
     sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
 });
 
@@ -128,8 +145,9 @@ const rules = reactive({
     targetProjectId: [{ required: true, message: "请选择模拟项目" }],
     targetMountedId: [{ required: true, message: "请选择模拟项目安装点" }],
     targetYear: [{ required: true, message: "请选择模拟年份" }],
-    isCover: [{ required: true }],
     source: [{ required: true }],
+    opsDataType: [{ required: true }],
+    recordNum: [{ required: true, message: "请输入迁移数据量" }],
     sourceProjectId: [{ required: true, message: "请选择数据源项目" }],
     sourceMountedId: [{ required: true, message: "请选择数据源安装点" }],
     sourceTime: [{ required: true, message: "请选择数据源抓拍时间" }]
@@ -145,6 +163,7 @@ const tableOptions = reactive({
         { column: "projectId", field: form.value.source == "template" ? "sourceProjectIdNot" : "sourceProjectId"  },
         form.value.source == "template" ? {} : { column: "projectIdNot", field: "sourceProjectIdNot" },
         { column: "mountedId", field: "sourceMountedId" },
+        { column: "recordType" },
         { column: "createTimeBegin", field: "sourceTime[0]" },
         { column: "createTimeEnd", field: "sourceTime[1]" }
     ])
@@ -188,10 +207,11 @@ const submit = key => {
         if (valid) {
             if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             
-            const data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "source", "sourceTime");
+            const data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "source", "sourceTime", "recordNum");
             XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));
             XEUtils.set(data, "sourceEndTime", XEUtils.last(form.value.sourceTime));
             
+            form.value.opsDataType == 2 && XEUtils.set(data, "recordNum", form.value.recordNum);
             form.value.source == "template" && XEUtils.set(data, "sourceMountedId", XEUtils.get(XEUtils.find(mounteds.value, item => item.projectId == 1), "id"));
             if (key == "template") {
                 XEUtils.set(data, "targetProjectId", 1);
@@ -219,5 +239,10 @@ defineExpose({
 <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 :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
 </style>

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

@@ -110,7 +110,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

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

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

+ 0 - 2
src/views/dataMock/carwash/components/info/index.vue

@@ -78,8 +78,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = (mode = "add") => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData(mode);

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

@@ -1,5 +1,5 @@
 <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]" width="860" :close-on-click-modal="false" @closed="$emit('closed', isDel)">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
             <el-row>
                 <el-col v-if="props.projectId != 1" :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="240" :height="143" 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" drag cropper @removeSuccess="removeSuccess"></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="240" :height="143" 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" drag cropper @removeSuccess="removeSuccess"></sc-upload>
                     </el-form-item>
                 </el-col>
             </el-row>
@@ -86,6 +86,7 @@ const props = defineProps({
 
 const visible = ref(false);
 const isSaving = ref(false);
+const isDel = ref(false);
 
 const mode = ref("add");
 const titleMap = reactive({
@@ -171,6 +172,10 @@ const submit = () => {
     });
 }
 
+const removeSuccess = () => {
+    if (form.value.id) isDel.value = true;
+}
+
 defineExpose({
     open,
     setData

+ 6 - 2
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 = false"></record-detail>
+    <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>
@@ -118,7 +118,6 @@ const imageToolbar = reactive({
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {
@@ -129,6 +128,11 @@ const refreshTable = (mode = "add") => {
 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());

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

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

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

@@ -127,7 +127,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 1 - 1
src/views/dataMock/edgeprot/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

+ 0 - 2
src/views/dataMock/elevator/components/alarm.vue

@@ -106,8 +106,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = () => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData();

+ 0 - 2
src/views/dataMock/elevator/components/attendance.vue

@@ -118,8 +118,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = () => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData();

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

@@ -132,7 +132,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 4 - 4
src/views/dataMock/elevator/detail.vue

@@ -22,7 +22,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>
@@ -98,14 +98,14 @@
 
                 <el-row>
                     <template v-if="form.source == 'other'">
-                        <el-col :lg="8" :md="12" :xs="24">
+                        <el-col :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 :lg="8" :md="12" :xs="24">
+                        <el-col :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,7 +114,7 @@
                         </el-col>
                     </template>
 
-                    <el-col :lg="8" :md="12" :xs="24">
+                    <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)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
                         </el-form-item>

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

@@ -116,7 +116,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 3 - 3
src/views/dataMock/env/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>
@@ -58,7 +58,7 @@
                 </template>
 
                 <el-col :md="12" :xs="24">
-                    <el-form-item style="margin-bottom: 0;" label="数据处理" prop="isCover">
+                    <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>
@@ -68,7 +68,7 @@
             </el-row>
 
             <template v-if="apiKey == 'copyData'">
-                <el-divider />
+                <el-divider style="margin-top: 6px;" />
 
                 <el-row>
                     <template v-if="form.source == 'other'">

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

@@ -139,7 +139,6 @@ const imageToolbar = reactive({
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 6 - 6
src/views/dataMock/parking/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>
@@ -46,7 +46,7 @@
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item style="margin-bottom: 0;" label="数据处理" prop="isCover">
+                    <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>
@@ -55,18 +55,18 @@
                 </el-col>
             </el-row>
 
-            <el-divider />
+            <el-divider style="margin-top: 6px;" />
 
             <el-row>
                 <template v-if="form.source == 'other'">
-                    <el-col :lg="8" :md="12" :xs="24">
+                    <el-col :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-col :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>
@@ -75,7 +75,7 @@
                     </el-col>
                 </template>
 
-                <el-col :lg="8" :md="12" :xs="24">
+                <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)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
                     </el-form-item>

+ 140 - 0
src/views/dataMock/perimeter/batchDel.vue

@@ -0,0 +1,140 @@
+<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 { aiTypeDic } from "@/utils/basicDic";
+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,
+    recordType: "AIHAZARD_REC_ILLEGAL_ENTRY",
+    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: "recordType" },
+        { 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", "recordType");
+            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>

+ 17 - 0
src/views/dataMock/perimeter/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

+ 145 - 0
src/views/dataMock/perimeter/components/record/detail.vue

@@ -0,0 +1,145 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="480" :close-on-click-modal="false" @closed="$emit('closed', isDel)">
+        <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="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-item label="抓拍图片" prop="fileData">
+                <sc-upload v-model="form.fileData" :width="240" :height="143" @removeSuccess="removeSuccess"></sc-upload>
+            </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 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,
+    mountedId: null,
+    recordType: "AIHAZARD_REC_ILLEGAL_ENTRY",
+    createTime: null,
+    features: {},
+    fileData: {}
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    mountedId: [{ required: true, message: "请选择设备安装点" }],
+    createTime: [{ required: true, message: "请选择抓拍时间" }],
+    fileData: [{ required: true, validator: (rule, value, callback) => {
+        if (XEUtils.isEmpty(value)) return callback(new Error("请上传抓拍图片"));
+        callback();
+    }}]
+})
+
+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.aihazard.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(XEUtils.omit(form.value, "fileData"), (_, key) => {
+        if (key == "features") {
+            const features = XEUtils.toStringJSON(XEUtils.get(data, key, "{}"));
+            const path = XEUtils.get(features, "bigImage.image", "");
+            
+            path && XEUtils.set(form.value, "fileData", { path });
+            XEUtils.set(form.value, key, 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", "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(() => {
+                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 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);
+    }
+}
+
+defineExpose({
+    open,
+    setData
+})
+</script>
+
+<style scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+}
+</style>

+ 168 - 0
src/views/dataMock/perimeter/components/record/index.vue

@@ -0,0 +1,168 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.aihazard.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
+        <template #default_imgUrl="{ row }">
+            <template v-if="XEUtils.get(XEUtils.toStringJSON(row.features), 'bigImage.image')">
+                <vxe-image style="cursor: pointer;" :src="'/minio' + XEUtils.get(XEUtils.toStringJSON(row.features), 'bigImage.image')" 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 { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource } from "@/utils/basicDic";
+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({
+    span: 5,
+    visible,
+    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: "aihazard.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 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,
+        recordType: "AIHAZARD_REC_ILLEGAL_ENTRY",
+        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)
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "projectId", field: visible.value ? "" : "projectIdNot" },
+    visible.value ? { column: "projectIdNot" } : {},
+    { column: "mountedId" },
+    { column: "recordType" },
+    { 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: "createTime", title: "抓拍时间", minWidth: 160, sortable: true },
+    { field: "bigImage", 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 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.aihazard.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable,
+    getTableTotal
+})
+</script>

+ 12 - 0
src/views/dataMock/perimeter/components/template.vue

@@ -0,0 +1,12 @@
+<template>
+    <data-table ref="tableRef" isTemp></data-table>
+</template>
+
+<script setup>
+import dataTable from "./record";
+
+const tableRef = ref();
+defineExpose({
+    table_add: () => tableRef.value.table_add()
+})
+</script>

+ 240 - 0
src/views/dataMock/perimeter/detail.vue

@@ -0,0 +1,240 @@
+<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.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('/system/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="targetYear">
+                        <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">
+                    <el-form-item label="模拟月份">
+                        <el-select v-model="form.targetMonth" filterable clearable placeholder="请选择模拟月份">
+                            <el-option v-for="item in 12" :key="item" :label="item + '月'" :value="XEUtils.padStart(item, 2, '0')"></el-option>
+                        </el-select>
+                    </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 label="数据处理" prop="opsDataType">
+                        <el-radio-group v-model="form.opsDataType">
+                            <el-radio :value="0">重复新增</el-radio>
+                            <el-radio :value="1">数据覆盖</el-radio>
+                            <el-radio :value="2">数据迁移</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+                <el-col v-if="form.opsDataType == 2" :md="12" :xs="24">
+                    <el-form-item label="迁移数据量" prop="recordNum">
+                        <el-input-number v-model="form.recordNum" :min="0" :controls="false" placeholder="请输入迁移数据量"></el-input-number>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-divider style="margin-top: 6px;" />
+
+            <el-row>
+                <template v-if="form.source == 'other'">
+                    <el-col :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-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>
+                            </el-select>
+                        </el-form-item>
+                    </el-col>
+                </template>
+
+                <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)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="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.opsDataType != 2 && 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 { aiTypeDic } from "@/utils/basicDic";
+import { rangeShortcuts } from "@/utils/shortcuts";
+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"),
+    targetMountedId: null,
+    targetYear: null,
+    targetMonth: null,
+    source: "other",
+    opsDataType: 0,
+    recordNum: null,
+    sourceProjectId: null,
+    sourceProjectIdNot: 1,
+    sourceMountedId: null,
+    recordType: "AIHAZARD_REC_ILLEGAL_ENTRY",
+    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: "请选择模拟项目" }],
+    targetMountedId: [{ required: true, message: "请选择模拟项目安装点" }],
+    targetYear: [{ required: true, message: "请选择模拟年份" }],
+    source: [{ required: true }],
+    opsDataType: [{ required: true }],
+    recordNum: [{ required: true, message: "请输入迁移数据量" }],
+    sourceProjectId: [{ required: true, message: "请选择数据源项目" }],
+    sourceMountedId: [{ 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: "mountedId", field: "sourceMountedId" },
+        { column: "recordType" },
+        { 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 mounteds = ref([]);
+const filterTargetM = computed(() => form.value.targetProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.targetProjectId) : []);
+const filterSourceM = computed(() => form.value.sourceProjectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.sourceProjectId) : []);
+const fetchMounted = async () => {
+    const res = await API.aihazard.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    TOOL.data.get("PROJECT_ID") && dataTimeRange();
+    fetchMounted();
+}
+
+const formRef = ref();
+const submit = key => {
+    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("暂无相关数据,请调整条件后重试。");
+            
+            const data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "source", "sourceTime", "recordNum");
+            XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));
+            XEUtils.set(data, "sourceEndTime", XEUtils.last(form.value.sourceTime));
+            
+            form.value.opsDataType == 2 && XEUtils.set(data, "recordNum", form.value.recordNum);
+            form.value.source == "template" && XEUtils.set(data, "sourceMountedId", XEUtils.get(XEUtils.find(mounteds.value, item => item.projectId == 1), "id"));
+            if (key == "template") {
+                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[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 :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
+</style>

+ 60 - 0
src/views/dataMock/perimeter/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="aihazard" />
+	</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/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 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>

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

@@ -124,7 +124,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 1 - 1
src/views/dataMock/smoke/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

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

@@ -119,7 +119,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 1 - 1
src/views/dataMock/spray/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

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

@@ -132,7 +132,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 1 - 1
src/views/dataMock/standard/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

+ 0 - 2
src/views/dataMock/tasks/monos.vue

@@ -165,8 +165,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = () => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData();

+ 0 - 2
src/views/dataMock/tower/components/alarm.vue

@@ -106,8 +106,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = () => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData();

+ 0 - 2
src/views/dataMock/tower/components/attendance.vue

@@ -118,8 +118,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = () => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData();

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

@@ -125,7 +125,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 4 - 4
src/views/dataMock/tower/detail.vue

@@ -22,7 +22,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>
@@ -98,14 +98,14 @@
 
                 <el-row>
                     <template v-if="form.source == 'other'">
-                        <el-col :lg="8" :md="12" :xs="24">
+                        <el-col :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 :lg="8" :md="12" :xs="24">
+                        <el-col :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,7 +114,7 @@
                         </el-col>
                     </template>
 
-                    <el-col :lg="8" :md="12" :xs="24">
+                    <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)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
                         </el-form-item>

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

@@ -124,7 +124,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
 
 const refreshTable = (mode = "add") => {

+ 1 - 1
src/views/dataMock/warehouse/detail.vue

@@ -16,7 +16,7 @@
                 </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-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/system/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>

+ 1 - 2
src/views/equipment/env.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled">
+        <sc-page-header>
             <template #extra-right>
                 <el-button type="warning" @click="refreshState">刷新状态</el-button>
             </template>
@@ -62,7 +62,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 
 onMounted(() => window.addEventListener("setItemEvent", ({ key, newValue }) => key === "ENV_GATE" && newValue && xGridTable.value?.reloadColumn(columns)))
 onUnmounted(() => window.removeEventListener("setItemEvent", () => {}));

+ 1 - 3
src/views/equipment/facerec.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled">
+        <sc-page-header>
             <template #extra-right>
                 <el-button type="warning" @click="refreshState">刷新状态</el-button>
             </template>
@@ -68,8 +68,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 
 // const tableLength = computed(() => xGridTable.value?.getTableData().fullData.length);
 // watch(tableLength, value => value > 0 && getDeviceState());

+ 1 - 3
src/views/equipment/passqrcode.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled">
+        <sc-page-header>
             <template #extra-right>
                 <el-button type="warning" @click="refreshState">刷新状态</el-button>
             </template>
@@ -68,8 +68,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 
 // const tableLength = computed(() => xGridTable.value?.getTableData().fullData.length);
 // watch(tableLength, value => value > 0 && getDeviceState());

+ 1 - 2
src/views/equipment/tower.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled">
+        <sc-page-header>
             <template #extra-right>
                 <el-button type="warning" @click="refreshState">刷新状态</el-button>
             </template>
@@ -62,7 +62,6 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
 
 onMounted(() => window.addEventListener("setItemEvent", ({ key, newValue }) => key === "TOWER_GATE" && newValue && xGridTable.value?.reloadColumn(columns)))
 onUnmounted(() => window.removeEventListener("setItemEvent", () => {}));

src/views/basic/acceptItems/detail.vue → src/views/system/acceptItems/detail.vue


+ 1 - 3
src/views/basic/acceptItems/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled" @add="table_add"></sc-page-header>
+        <sc-page-header @add="table_add"></sc-page-header>
 
         <scTable ref="xGridTable" :apiObj="$API.system.acceptItems" apiKey="all" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" :options="options">
             <template #action="{ row }">
@@ -84,8 +84,6 @@ const options = reactive({
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
-
 const refreshTable = (mode = "add") => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData(mode);

src/views/basic/acceptItems/main.js → src/views/system/acceptItems/main.js


+ 17 - 0
src/views/system/milestone/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

+ 160 - 0
src/views/system/milestone/components/record/detail.vue

@@ -0,0 +1,160 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="480" :close-on-click-modal="false" @closed="$emit('closed', isDel)">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item v-if="props.projectId != 1" 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="parentId">
+                        <el-tree-select v-model="form.parentId" v-bind="props.treeSelectProps"></el-tree-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="节点序号" prop="nodeSeq">
+                        <el-input-number v-model="form.nodeSeq" :min="0" :controls="false" placeholder="请输入节点序号"></el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="节点名称" prop="nodeName">
+                        <el-input v-model="form.nodeName" placeholder="请输入节点名称"></el-input>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="计划开始时间">
+                        <el-date-picker v-model="form.planStartTime" :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="计划完成时间">
+                        <el-date-picker v-model="form.planFinishTime" :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="实际开始时间">
+                        <el-date-picker v-model="form.createTime" :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="实际完成时间">
+                        <el-date-picker v-model="form.createTime" :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="节点状态" prop="nodeStatus">
+                        <el-select v-model="form.nodeStatus" filterable placeholder="请选择节点状态">
+                            <el-option v-for="(label, key) in nodeStatusDic" :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="描述内容">
+                        <el-input v-model="form.remark" type="textarea" :rows="4" placeholder="请输入描述内容"></el-input>
+                    </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 { nodeStatusDic } from "@/views/system/milestone/main";
+
+const $emit = defineEmits(["success", "closed"]);
+const props = defineProps({
+    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") },
+    treeSelectProps: { type: Object, default: () => {} }
+});
+
+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,
+    parentId: 0,
+    nodeName: null,
+    nodeType: null,
+    nodeSeq: null,
+    planStartTime: null,
+    planFinishTime: null,
+    actualStartTime: null,
+    actualFinishTime: null,
+    nodeStatus: null,
+    remark: null
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    mountedId: [{ required: true, message: "请选择设备安装点" }],
+    createTime: [{ required: true, message: "请选择抓拍时间" }],
+    recordType: [{ required: true, message: "请选择识别结果" }],
+})
+
+const open = () => visible.value = true;
+
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(XEUtils.omit(form.value, "fileData"), (_, key) => {
+        // if (key == "features") {
+        //     const features = XEUtils.toStringJSON(XEUtils.get(data, key, "{}"));
+        //     const path = XEUtils.get(features, "bigImage.image", "");
+            
+        //     path && XEUtils.set(form.value, "fileData", { path });
+        //     XEUtils.set(form.value, key, 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", "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(() => {
+            //     isSaving.value = false;
+            //     ElMessage.success("操作成功");
+            //     visible.value = false;
+            //     $emit("success", mode.value);
+            // }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open,
+    setData
+})
+</script>
+
+<style scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+}
+</style>

+ 155 - 0
src/views/system/milestone/components/record/index.vue

@@ -0,0 +1,155 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.aihazard.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
+        <template #tree_select>
+            <el-tree-select v-model="formConfig.data.projectFirmName" v-bind="treeSelectProps"></el-tree-select>
+        </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')" :treeSelectProps="treeSelectProps" @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 { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { objectToArray } from "@/utils/basicDic";
+import { nodeStatusDic } from "@/views/system/milestone/main";
+import recordDetail from "./detail";
+
+const props = defineProps({
+    isTemp: { 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, mountedId: null })
+    }
+})
+
+const treeSelectProps = reactive({
+    popperClass: "vxe-table-slot--popper",
+    data: [{ id: 0, nodeName: "根目录" }],
+    filterable: true,
+    clearable: true,
+    checkStrictly: true,
+    clearable: true,
+    placeholder: "请选择所属节点",
+    props: { label: "nodeName", value: "id" }
+})
+
+const selectConfig = reactive({
+    options: objectToArray(nodeStatusDic),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
+const datetimerangeConfig = reactive({
+    span: 7,
+    resetValue: () => [],
+    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
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemInput("nodeName", "节点名称"),
+        mapFormItemSelect("nodeType", "节点类型", selectConfig),
+        mapFormItemSelect("nodeStatus", "节点状态", selectConfig),
+        mapFormItemDatePicker("planTime", "计划完成时间", datetimerangeConfig),
+        mapFormItemDatePicker("actualTime", "实际完成时间", datetimerangeConfig),
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "projectId", field: visible.value ? "" : "projectIdNot" },
+    visible.value ? { column: "projectIdNot" } : {},
+
+    { column: "planFinishTimeBegin", field: "planTime[0]" },
+    { column: "planFinishTimeEnd", field: "planTime[1]" },
+    { column: "actualFinishTimeBegin", field: "actualTime[0]" },
+    { column: "actualFinishTimeEnd", field: "actualTime[1]" }
+])
+
+const columns = reactive([
+    { 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") },
+    
+    { type: "html", field: "createTime", title: "抓拍时间", minWidth: 160, sortable: true },
+    // { type: "html", field: "recordType", title: "识别结果", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(aiTypeDic, cellValue, cellValue) },
+    { title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+])
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const refreshTable = (mode = "add") => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData(mode);
+}
+
+watch(() => xGridTable.value?.getTableData(), val => {
+    console.log(val)
+    // treeSelectProps.data = XEUtils.toArrayTree(res, { parentKey: "parentId", key: "id" });
+})
+
+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.aihazard.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable
+})
+</script>

+ 12 - 0
src/views/system/milestone/components/template.vue

@@ -0,0 +1,12 @@
+<template>
+    <data-table ref="tableRef" isTemp></data-table>
+</template>
+
+<script setup>
+import dataTable from "./record";
+
+const tableRef = ref();
+defineExpose({
+    table_add: () => tableRef.value.table_add()
+})
+</script>

+ 34 - 0
src/views/system/milestone/index.vue

@@ -0,0 +1,34 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="新增节点" @add="table_add">
+            <template #extra-right>
+                <el-button v-if="activeName == 'record'" type="primary" @click="table_copy">
+                    <template #icon><sc-iconify icon="ant-design:copy-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]" />
+	</el-container>
+</template>
+
+<script setup>
+import { workerStates } from "./main";
+import allcomp from "./components";
+
+const activeName = ref("record");
+
+const componentRef = ref();
+
+const table_add = () => componentRef.value.table_add();
+
+const table_copy = () => {};
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

+ 10 - 0
src/views/system/milestone/main.js

@@ -0,0 +1,10 @@
+export const workerStates = {
+    record: "其他项目",
+    template: "模版项目"
+}
+
+export const nodeStatusDic = {
+    inactive: "未开始",
+    active: "进行中",
+    done: "已完成"
+}

src/views/basic/tag/detail.vue → src/views/system/tag/detail.vue


+ 1 - 1
src/views/basic/tag/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<el-container class="is-vertical">
-        <sc-page-header @filter="toggleFormEnabled" @add="table_add"></sc-page-header>
+        <sc-page-header @add="table_add"></sc-page-header>
 
         <!-- <scTable ref="xGridTable" :apiObj="$API.easyRun.supplier" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns"> -->
         <scTable :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :options="options">