zhuangyunsheng 3 hónapja
szülő
commit
19445d6a1f
34 módosított fájl, 771 hozzáadás és 659 törlés
  1. 64 56
      src/App.vue
  2. 34 6
      src/api/model/env.js
  3. 1 0
      src/components/scTable/index.vue
  4. 2 2
      src/router/index.js
  5. 2 1
      src/utils/basicDic.js
  6. 10 10
      src/views/dataMock/carwash/detail.vue
  7. 1 1
      src/views/dataMock/carwash/index.vue
  8. 1 1
      src/views/dataMock/elevator/components/attendance.vue
  9. 1 1
      src/views/dataMock/elevator/components/record/index.vue
  10. 11 10
      src/views/dataMock/elevator/detail.vue
  11. 1 1
      src/views/dataMock/elevator/index.vue
  12. 8 3
      src/views/dataMock/env/components/index.js
  13. 0 116
      src/views/dataMock/env/components/monos.vue
  14. 0 78
      src/views/dataMock/env/components/record.vue
  15. 204 0
      src/views/dataMock/env/components/record/detail.vue
  16. 158 0
      src/views/dataMock/env/components/record/index.vue
  17. 12 0
      src/views/dataMock/env/components/template.vue
  18. 185 112
      src/views/dataMock/env/detail.vue
  19. 19 7
      src/views/dataMock/env/index.vue
  20. 19 0
      src/views/dataMock/env/main.js
  21. 0 47
      src/views/dataMock/env/mono/index.vue
  22. 0 33
      src/views/dataMock/env/mono/tableExpand.vue
  23. 0 134
      src/views/dataMock/main.js
  24. 1 1
      src/views/dataMock/spray/index.vue
  25. 4 3
      src/views/dataMock/tasks/monos.vue
  26. 1 5
      src/views/dataMock/tasks/tableExpand.vue
  27. 3 3
      src/views/dataMock/tower/components/alarm.vue
  28. 1 1
      src/views/dataMock/tower/components/attendance.vue
  29. 4 4
      src/views/dataMock/tower/components/record/detail.vue
  30. 2 2
      src/views/dataMock/tower/components/record/index.vue
  31. 11 10
      src/views/dataMock/tower/detail.vue
  32. 1 1
      src/views/dataMock/tower/index.vue
  33. 9 9
      src/views/dataMock/ugliAi/detail.vue
  34. 1 1
      src/views/dataMock/ugliAi/index.vue

+ 64 - 56
src/App.vue

@@ -5,69 +5,77 @@
 </template>
 
 <script>
-    const debounce = (fn, delay) => {
-		let timer = null;
-		return function () {
-			let context = this;
-			let args = arguments;
-			clearTimeout(timer);
-			timer = setTimeout(function () {
-				fn.apply(context, args);
-			}, delay);
-		}
-	}
+const debounce = (fn, delay) => {
+    let timer = null;
+    return function () {
+        let context = this;
+        let args = arguments;
+        clearTimeout(timer);
+        timer = setTimeout(function () {
+            fn.apply(context, args);
+        }, delay);
+    }
+}
 
-	const _ResizeObserver = window.ResizeObserver;
-	window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
-		constructor(callback) {
-			callback = debounce(callback, 16);
-			super(callback);
-		}
-	}
+const _ResizeObserver = window.ResizeObserver;
+window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
+    constructor(callback) {
+        const wrappedCallback = (entries, observer) => {
+            // 过滤掉已经不在文档中的元素对应的entry
+            const validEntries = entries.filter(entry => document.contains(entry.target));
+            if (validEntries.length > 0) {
+                callback(validEntries, observer);
+            }
+        };
 
-	import colorTool from "@/utils/color";
+        callback = debounce(wrappedCallback, 16);
+        super(callback);
+    }
+}
 
-	export default {
-		name: "App",
-		data() {
-			return {
-				config: {
-					size: "default",
-					zIndex: 2000,
-					button: {
-						autoInsertSpace: false
-					}
-				}
-			}
-		},
+import colorTool from "@/utils/color";
 
-		computed: {
-			locale() {
-				return this.$i18n.messages[this.$i18n.locale].el
-			}
-		},
+export default {
+    name: "App",
+    data() {
+        return {
+            config: {
+                size: "default",
+                zIndex: 2000,
+                button: {
+                    autoInsertSpace: false
+                }
+            }
+        }
+    },
 
-		created() {
-			// 设置主题颜色
-			const app_color = this.$TOOL.data.get("APP_COLOR") || this.$CONFIG.COLOR;
-			if (app_color) {
-				document.documentElement.style.setProperty("--el-color-primary", app_color);
-				for (let i = 1; i <= 9; i++) {
-					document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color, i / 10));
-				}
-				for (let i = 1; i <= 9; i++) {
-					document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(app_color, i / 10));
-				}
+    computed: {
+        locale() {
+            return this.$i18n.messages[this.$i18n.locale].el
+        }
+    },
 
-                // vxe-Table
-				document.documentElement.style.setProperty("--vxe-ui-font-primary-color", app_color);
-				document.documentElement.style.setProperty("--vxe-ui-font-primary-darken-color", app_color);
-				document.documentElement.style.setProperty("--vxe-ui-font-primary-lighten-color", app_color);
-			}
-		}
-	}
+    created() {
+        // 设置主题颜色
+        const app_color = this.$TOOL.data.get("APP_COLOR") || this.$CONFIG.COLOR;
+        if (app_color) {
+            document.documentElement.style.setProperty("--el-color-primary", app_color);
+            for (let i = 1; i <= 9; i++) {
+                document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color, i / 10));
+            }
+            for (let i = 1; i <= 9; i++) {
+                document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(app_color, i / 10));
+            }
+
+            // vxe-Table
+            document.documentElement.style.setProperty("--vxe-ui-font-primary-color", app_color);
+            document.documentElement.style.setProperty("--vxe-ui-font-primary-darken-color", app_color);
+            document.documentElement.style.setProperty("--vxe-ui-font-primary-lighten-color", app_color);
+        }
+    }
+}
 </script>
 
 <style lang="scss">
-	@use "@/style/style.scss";
+@use "@/style/style.scss";
 </style>

+ 34 - 6
src/api/model/env.js

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

+ 1 - 0
src/components/scTable/index.vue

@@ -186,6 +186,7 @@ const gridOptions = ref({
 })
 
 watch(() => xGrid.value?.getCheckboxRecords(), val => selectedRows.value = val);
+watch(() => props.options, val => XEUtils.merge(gridOptions.value, val), { deep: true });
 
 addEventListener("resize", () => resizeTable());
 onMounted(() => {

+ 2 - 2
src/router/index.js

@@ -67,8 +67,8 @@ router.beforeEach(async (to, from, next) => {
 	if (to.meta.fullpage) to.matched = [to.matched[to.matched.length - 1]];
     
     // 所有闸口/安装点
-    const index = ["facerec", "passqrcode", "env"].findIndex(key => to.fullPath.includes(key));
-    if (to.name && index !== -1) router.getGates(["facerec", "passqrcode", "env"][index])
+    const index = ["facerec", "passqrcode"].findIndex(key => to.fullPath.includes(key));
+    if (to.name && index !== -1) router.getGates(["facerec", "passqrcode"][index])
     
     // 加载动态/静态路由
 	if (!isGetRouter) {

+ 2 - 1
src/utils/basicDic.js

@@ -27,7 +27,8 @@ export const taskDic = {
     }
 }
 
-export const dataSource = ["现存数据", "iot数据", "第三方数据"];
+export const tempProModel = ["car_rinse", "aihazard", "envdev", "tcm", "elevator"]
+export const dataSource = ["现存数据", "iot数据", "第三方数据"]
 
 export const aiTypeDic = {
     AIHAZARD_REC_NO_HELMET: "未带安全帽",

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

@@ -34,24 +34,24 @@
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="数据处理" prop="isCover">
-                        <el-radio-group v-model="form.isCover">
-                            <el-radio :value="false">重复新增</el-radio>
-                            <el-radio :value="true">数据覆盖</el-radio>
+                    <el-form-item label="数据来源" prop="source">
+                        <el-radio-group v-model="form.source" @change="refreshTable">
+                            <el-radio value="template">模版项目</el-radio>
+                            <el-radio value="other">其他项目</el-radio>
                         </el-radio-group>
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item style="margin-bottom: 0;" label="数据来源" prop="source">
-                        <el-radio-group v-model="form.source" @change="refreshTable">
-                            <el-radio value="template">模版项目</el-radio>
-                            <el-radio value="other">其他项目</el-radio>
+                    <el-form-item label="数据处理" prop="isCover">
+                        <el-radio-group v-model="form.isCover">
+                            <el-radio :value="false">重复新增</el-radio>
+                            <el-radio :value="true">数据覆盖</el-radio>
                         </el-radio-group>
                     </el-form-item>
                 </el-col>
             </el-row>
 
-            <el-divider />
+            <el-divider style="margin-top: 6px;" />
 
             <el-row>
                 <el-col v-if="form.source == 'other'" :md="12" :xs="24">
@@ -146,7 +146,7 @@ const formRef = ref();
 const submit = key => {
     formRef.value.validate(valid => {
         if (valid) {
-            if (tableRef.value.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
+            if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             
             const data = XEUtils.omit(form.value, "source", "sourceProjectIdNot", "sourceTime");
             XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));

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

@@ -11,7 +11,7 @@
             <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="car_rinse" />
+        <component ref="componentRef" :is="allcomp[activeName]" m_apiKey="carwash" taskType="car_rinse" />
 	</el-container>
 
     <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>

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

@@ -22,7 +22,7 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
-import { dataSource, objectToArray } from "@/utils/basicDic";
+import { dataSource } from "@/utils/basicDic";
 import { folderKeyDic } from "@/views/dataMock/tower/main";
 
 const formatCertificate = (row, { field }) => XEUtils.find(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "person.features")), "certificate", []), item => item.type == XEUtils.get(folderKeyDic, field));

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

@@ -19,7 +19,7 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
-import { dataSource, objectToArray } from "@/utils/basicDic";
+import { dataSource } from "@/utils/basicDic";
 import { elevatorDic } from "@/views/dataMock/elevator/main";
 import recordDetail from "./detail";
 

+ 11 - 10
src/views/dataMock/elevator/detail.vue

@@ -46,18 +46,18 @@
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
-                        <el-form-item label="数据处理" prop="isCover">
-                            <el-radio-group v-model="form.isCover">
-                                <el-radio :value="false">重复新增</el-radio>
-                                <el-radio :value="true">数据覆盖</el-radio>
+                        <el-form-item label="数据来源" prop="source">
+                            <el-radio-group v-model="form.source" @change="refreshTable">
+                                <el-radio value="template">模版项目</el-radio>
+                                <el-radio value="other">其他项目</el-radio>
                             </el-radio-group>
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
-                        <el-form-item style="margin-bottom: 0;" label="数据来源" prop="source">
-                            <el-radio-group v-model="form.source" @change="refreshTable">
-                                <el-radio value="template">模版项目</el-radio>
-                                <el-radio value="other">其他项目</el-radio>
+                        <el-form-item label="数据处理" prop="isCover">
+                            <el-radio-group v-model="form.isCover">
+                                <el-radio :value="false">重复新增</el-radio>
+                                <el-radio :value="true">数据覆盖</el-radio>
                             </el-radio-group>
                         </el-form-item>
                     </el-col>
@@ -79,7 +79,7 @@
                 <template v-if="dataSouce == 'attendance'">
                     <el-col :md="12" :xs="24">
                         <el-form-item label="模拟时间范围" prop="targetTime">
-                            <el-date-picker v-model="form.targetTime" type="daterange" :clearable="false" value-format="YYYY-MM-DD 00:00:00" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+                            <el-date-picker v-model="form.targetTime" type="daterange" :clearable="false" value-format="YYYY-MM-DD 00:00:00" :shortcuts="daterangeShortcuts" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
@@ -94,7 +94,7 @@
             </el-row>
 
             <template v-if="dataSouce == 'normal'">
-                <el-divider />
+                <el-divider style="margin-top: 6px;" />
 
                 <el-row>
                     <template v-if="form.source == 'other'">
@@ -150,6 +150,7 @@ const visible = ref(false);
 const isSaving = ref(false);
 
 const shortcuts = rangeShortcuts();
+const daterangeShortcuts = rangeShortcuts("YYYY-MM-DD 00:00:00");
 const form = ref({
     targetProjectId: TOOL.data.get("PROJECT_ID"),
     targetMountedId: null,

+ 1 - 1
src/views/dataMock/elevator/index.vue

@@ -16,7 +16,7 @@
             <el-tab-pane label="考勤数据" name="elevator_person"></el-tab-pane>
         </el-tabs>
 
-        <component ref="componentRef" :is="allcomp[activeName]" :taskType="taskType" />
+        <component ref="componentRef" :is="allcomp[activeName]" m_apiKey="elevator" :taskType="taskType" />
 	</el-container>
 
     <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>

+ 8 - 3
src/views/dataMock/env/components/index.js

@@ -1,12 +1,17 @@
+import XEUtils from "xe-utils"
+
 const resultComps = {}
 let requireComponent = require.context(
     "./", // 在当前目录下查找
-    false, // 不遍历子文件夹
+    true, // 遍历子文件夹
     /\.vue$/ // 正则匹配 以 .vue结尾的文件
 )
 requireComponent.keys().forEach(fileName => {
-    let comp = requireComponent(fileName)
-    resultComps[fileName.replace(/^\.\/(.*)\.\w+$/, "$1")] = comp.default
+    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

+ 0 - 116
src/views/dataMock/env/components/monos.vue

@@ -1,116 +0,0 @@
-<template>
-    <el-main>
-        <el-card>
-            <template #header>Group任务池配置<el-button type="primary" @click="mono_detail">任务列表</el-button></template>
-
-            <el-form>
-                <el-row :gutter="15">
-                    <el-col :span="8">
-                        <el-form-item label="最大组任务数:">{{ XEUtils.get(defaultGroups, "configs.maxGroupQueues") }}</el-form-item>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-form-item label="队列最大任务数:">{{ XEUtils.get(defaultGroups, "configs.maxQueueLimit") }}</el-form-item>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-form-item label="最大并发队列数:">{{ XEUtils.get(defaultGroups, "configs.maxQueues") }}</el-form-item>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-form-item label="队列任务间隔(秒):">{{ XEUtils.get(defaultGroups, "configs.taskIntervalSeconds") }}</el-form-item>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-form-item label="重试任务间隔(秒):">{{ XEUtils.get(defaultGroups, "configs.taskRetryInterval") }}</el-form-item>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-form-item label="任务最大重试次数(秒):">{{ XEUtils.get(defaultGroups, "configs.taskRetryTimes") }}</el-form-item>
-                    </el-col>
-                    <el-col :span="8">
-                        <el-form-item label="任务最大等待时间(秒):">{{ XEUtils.get(defaultGroups, "configs.taskWaitSeconds") }}</el-form-item>
-                    </el-col>
-                </el-row>
-            </el-form>
-        </el-card>
-
-        <el-card>
-            <el-form>
-                <el-form-item label="workers忙碌数量:">0</el-form-item>
-                <el-form-item label="workers空闲数量:">50</el-form-item>
-                <el-form-item label="Groups忙碌数量:">0</el-form-item>
-                <el-form-item label="Groups队列数量:">0</el-form-item>
-                <el-form-item label="mono总数:">0</el-form-item>
-            </el-form>
-        </el-card>
-        <div class="echart-bar">
-            <sc-echarts :option="option" @chartClick="chart_click"></sc-echarts>
-        </div>
-    </el-main>
-
-    <mono-detail v-if="dialog" ref="monoRef" @closed="dialog = false"></mono-detail>
-</template>
-
-<script setup>
-import XEUtils from "xe-utils"
-import { defaultGroups } from "../../main";
-import monoDetail from "../mono/index"
-
-const $emit = defineEmits(["closed"]);
-
-const option = ref({
-    grid: { left: "5%", right: "10%", bottom: 60 },
-    dataZoom: [
-        { type: "inside", endValue: 15, zoomLock: true }, { 
-        type: "slider",
-        showDetail: false,
-        moveHandleIcon: "none", // 移动手柄
-        moveHandleStyle: { opacity: 0 }
-    }],
-    xAxis: {
-        name: "workers",
-        nameTextStyle: { fontSize: 14, color: "#606266" },
-        axisLabel: { color: '#5d5d5d', rotate: -30 },
-        data: XEUtils.keys(XEUtils.get(defaultGroups, "workers"))
-    },
-    yAxis: {
-        name: "mono",
-        type: "value",
-        min: 0,
-        max: XEUtils.max([8, 5]),
-        nameTextStyle: { fontSize: 14, color: "#606266" },
-        minInterval: 1,
-        axisLabel: { color: '#5d5d5d' },
-        axisLine: { show: true, lineStyle: { color: "#d3d2d3" } },
-        splitLine: { show: false }
-    },
-    series: [{
-        type: "bar",
-        data: [8, 5],
-        animationDuration: 2500,
-        animationDurationUpdate: 2500
-    }]
-})
-
-const dialog = ref(false);
-const monoRef = ref();
-const chart_click = ({ name }) => {
-    dialog.value = true;
-    nextTick(() => monoRef.value.open(XEUtils.get(defaultGroups, `groups.${name}.monos`)));
-}
-
-const mono_detail = () => {
-    dialog.value = true;
-    nextTick(() => monoRef.value.open());
-}
-</script>
-
-<style lang="scss" scoped>
-.el-main {padding: 0 12px 12px;background: #fff;}
-.el-card :deep(.el-card__header) {display: flex;justify-content: space-between;align-items: center;padding-left: 20px;background: #f9fdff;font-weight: 700;}
-.el-card :deep(.el-card__body) {padding: 10px var(--el-card-padding);}
-.el-card + .el-card {margin-top: 10px;}
-
-.el-form .el-form-item {margin-bottom: 0;}
-.el-form :deep(.el-form-item__content) {font: 22px calculator-all;color: #1890ff;letter-spacing: 2px;}
-
-.el-card + .el-card .el-form {display: flex;justify-content: space-evenly;}
-
-.echart-bar {width: 100%;height: 400px;}
-</style>

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

@@ -1,78 +0,0 @@
-<template>
-    <scTable ref="xGridTable" :apiObj="$API.env.records" framework="zeroLiteOld" :toolbarConfig="toolbarConfig" :formConfig="formConfig" :paramsColums="paramsColums" :columns="columns"></scTable>
-</template>
-
-<script setup>
-import moment from "moment";
-import XEUtils from "xe-utils";
-import TOOL from "@/utils/tool";
-import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
-
-const route = useRoute()
-
-const proConfig = reactive({
-    storageKey: "PROJECT",
-    resetValue: XEUtils.toNumber(XEUtils.get(route, "query.projectId", 11)),
-    optionProps: { label: "projectName", value: "fpiId" },
-    events: {
-        change: data => XEUtils.assign(formConfig.data, data)
-    }
-})
-
-const daterangeConfig = reactive({
-    span: 9,
-    resetValue: () => [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")],
-    props: {
-        type: "datetimerange",
-        clearable: false,
-        startPlaceholder: "开始时间",
-        endPlaceholder: "结束时间",
-        format: "YYYY-MM-DD HH:mm"
-    }
-})
-
-const toolbarConfig = reactive({
-    enabled: true,
-    print: false
-})
-
-const formConfig = reactive({
-    data: {
-        projectID: XEUtils.toNumber(XEUtils.get(route, "query.projectId", 11)),
-        createTime: [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
-    },
-    items: [
-        mapFormItemSelect("projectID", "所属项目", proConfig),
-        mapFormItemDatePicker("createTime", "监测时间", daterangeConfig)
-    ]
-})
-
-const paramsColums = reactive([
-    { type: "relation", column: "createTime", symbol: "eqgt", field: "createTime[0]", formatValue: value => moment(value).format("YYYY-MM-DDTHH:mm:ss[Z]") },
-    { type: "relation", column: "createTime", symbol: "eqlt", field: "createTime[1]", formatValue: value => moment(value).format("YYYY-MM-DDTHH:mm:ss[Z]") },
-    { type: "orderby", column: "createTime", symbol: "desc" },
-    { type: "expands", column: "projectID", symbol: "eq" }
-]);
-
-const columns = reactive([
-    { type: "seq", fixed: "left", width: 60 },
-    { type: "html", field: "device.device_num", title: "设备唯一标识", fixed: "left", minWidth: 150, sortable: true },
-    { type: "html", field: "projectName", title: "所属项目", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === XEUtils.get(row, "device.project_id")), "projectName") },
-    { type: "html", field: "mounted.ground.groundName", title: "工地场区", minWidth: 150, sortable: true },
-    { type: "html", field: "mounted.mountedName", title: "安装点名称", minWidth: 150, sortable: true },
-    { type: "html", field: "taskTime", title: "监测时间", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || TOOL.dateFormat(row.createTime) },
-    { type: "html", field: "PM2_5", title: "pm2.5", minWidth: 110, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(row, "pm2_5"), 10000) + "μg/m³" },
-    { type: "html", field: "PM10", title: "pm10", minWidth: 110, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(row, "pm10"), 10000) + "μg/m³" },
-    { type: "html", field: "PM100", title: "pm100", minWidth: 110, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(row, "pm100"), 10000) + "μg/m³" },
-    { type: "html", field: "windPower", title: "风力", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "级") || cellValue },
-    { type: "html", field: "windSpeed", title: "风速", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "m/s") || cellValue },
-    { type: "html", field: "noise", title: "噪声", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "dB") || cellValue },
-    { type: "html", field: "Temperature", title: "温度", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(row, "temperature"), 10000) + "℃" },
-    { type: "html", field: "Humidity", title: "湿度", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(row, "humidity"), 10000) + "%" }
-])
-
-// 显示隐藏 筛选表单
-const xGridTable = ref();
-onMounted(() => window.addEventListener("setItemEvent", ({ key, newValue }) => key === "ENV_GATE" && newValue && xGridTable.value?.reloadColumn(columns)));
-onUnmounted(() => window.removeEventListener("setItemEvent", () => {}));
-</script>

+ 204 - 0
src/views/dataMock/env/components/record/detail.vue

@@ -0,0 +1,204 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="860" :close-on-click-modal="false" @closed="$emit('closed', isDel)">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-row v-if="props.projectId != 1">
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="所属项目" prop="projectId">
+                        <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="mountedId">
+                        <el-select v-model="form.mountedId" filterable placeholder="请选择设备安装点">
+                            <el-option v-for="item in filterMounteds" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="监测时间" prop="createTime">
+                        <el-date-picker v-model="form.createTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择监测时间"></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="pm2.5" prop="pm25">
+                        <el-input-number v-model="form.pm25" :precision="2" :controls="false" placeholder="请输入pm2.5">
+                            <template #suffix>μg/m³</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="pm10" prop="pm10">
+                        <el-input-number v-model="form.pm10" :precision="2" :controls="false" placeholder="请输入pm10">
+                            <template #suffix>μg/m³</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="pm100" prop="pm100">
+                        <el-input-number v-model="form.pm100" :precision="2" :controls="false" placeholder="请输入pm100">
+                            <template #suffix>μg/m³</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="风力" prop="windPower">
+                        <el-input-number v-model="form.windPower" readonly :controls="false" placeholder="请输入风力">
+                            <template #suffix>级</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="风速" prop="windSpeed">
+                        <el-input-number v-model="form.windSpeed" :precision="2" :controls="false" placeholder="请输入风速" @change="windSpeedChange">
+                            <template #suffix>m/s</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="噪声" prop="noise">
+                        <el-input-number v-model="form.noise" :precision="2" :controls="false" placeholder="请输入噪声">
+                            <template #suffix>dB</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="温度" prop="temperature">
+                        <el-input-number v-model="form.temperature" :precision="2" :controls="false" placeholder="请输入温度">
+                            <template #suffix>℃</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="湿度" prop="humidity">
+                        <el-input-number v-model="form.humidity" :precision="2" :controls="false" placeholder="请输入湿度">
+                            <template #suffix>%</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+            <el-button 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 { valueFormatDic, transferPower } from "@/views/dataMock/env/main";
+
+const $emit = defineEmits(["success", "closed"]);
+const props = defineProps({
+    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") }
+});
+
+const visible = ref(false);
+const isSaving = ref(false);
+const isDel = ref(false);
+
+const mode = ref("add");
+const titleMap = reactive({
+    add: "数据录入",
+    edit: "修改"
+});
+
+const form = ref({
+    id: null,
+    projectId: props.projectId,
+    mountedId: null,
+    createTime: null,
+    pm25: null,
+    pm10: null,
+    pm100: null,
+    windPower: null,
+    windSpeed: null,
+    noise: null,
+    temperature: null,
+    humidity: null,
+    features: "{}"
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    mountedId: [{ required: true, message: "请选择设备安装点" }],
+    createTime: [{ required: true, message: "请选择监测时间" }],
+    pm25: [{ required: true, message: "请输入pm10" }],
+    pm10: [{ required: true, message: "请输入pm10" }],
+    pm100: [{ required: true, message: "请输入pm100" }],
+    windPower: [{ required: true, message: "请输入风力" }],
+    windSpeed: [{ required: true, message: "请输入风速" }],
+    noise: [{ required: true, message: "请输入噪声" }],
+    temperature: [{ required: true, message: "请输入温度" }],
+    humidity: [{ required: true, message: "请输入湿度" }]
+})
+
+const mounteds = ref([]);
+const filterMounteds = computed(() => form.value.projectId ? XEUtils.filter(mounteds.value, item => item.projectId == form.value.projectId) : []);
+const fetchMounted = async () => {
+    const res = await API.env.mounted.get();
+    mounteds.value = res || [];
+    if (props.projectId == 1) form.value.mountedId = XEUtils.get(XEUtils.find(res, item => item.projectId == 1), "id");
+}
+
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(form.value, (_, key) => {
+        if (valueFormatDic.includes(key)) XEUtils.set(form.value, key, XEUtils.divide(XEUtils.get(data, key), 10000));
+        else XEUtils.set(form.value, key, XEUtils.get(data, key));
+    });
+}
+
+const formRef = ref();
+const windSpeedChange = e => {
+    form.value.windPower = e ? transferPower(e) : null;
+    formRef.value.validateField("windPower");
+}
+
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.omit(form.value, valueFormatDic);
+            XEUtils.arrayEach(valueFormatDic, key => XEUtils.set(data, key, XEUtils.multiply(XEUtils.get(form.value, key), 10000)));
+
+            isSaving.value = true;
+            API.env.record[mode.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", mode.value);
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open,
+    setData
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+
+    .el-input-number {width: 100%;}
+    .el-input-number :deep(.el-input__inner) {text-align: unset;}
+    .el-input-number :deep(.el-input__suffix) {font-size: 12px;}
+}
+</style>

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

@@ -0,0 +1,158 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.env.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
+        <template #action="{ row }">
+            <el-button type="primary" link @click="table_edit(row)">
+                <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
+            </el-button>
+            <el-button type="primary" link @click="table_del(row)">
+                <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
+            </el-button>
+        </template>
+    </scTable>
+
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog = false"></record-detail>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource } 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: "env.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("day").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")],
+    props: {
+        type: "datetimerange",
+        startPlaceholder: "开始时间",
+        endPlaceholder: "结束时间",
+        format: "YYYY-MM-DD HH:mm"
+    }
+})
+
+const toolbarConfig = reactive({
+    enabled: true,
+    print: false
+})
+
+const formConfig = reactive({
+    data: {
+        projectId: TOOL.data.get("PROJECT_ID"),
+        projectIdNot: 1,
+        createTime: [moment().startOf("day").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: "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 },
+    { type: "html", field: "pm25", title: "pm2.5", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "μg/m³") || cellValue },
+    { type: "html", field: "pm10", title: "pm10", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "μg/m³") || cellValue },
+    { type: "html", field: "pm100", title: "pm100", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "μg/m³") || cellValue },
+    { type: "html", field: "windPower", title: "风力", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "级") || cellValue },
+    { type: "html", field: "windSpeed", title: "风速", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "m/s") || cellValue },
+    { type: "html", field: "noise", title: "噪声", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "dB") || cellValue },
+    { type: "html", field: "temperature", title: "温度", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "℃") || cellValue },
+    { type: "html", field: "humidity", title: "湿度", minWidth: 100, sortable: true, formatter: ({ cellValue }) => (XEUtils.isNumber(cellValue) && XEUtils.divide(cellValue, 10000) + "%") || cellValue },
+    { visible, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { visible: !props.hideHandler, title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+])
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
+
+const refreshTable = (mode = "add") => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData(mode);
+}
+
+const recordRef = ref();
+const dialog = ref(false);
+
+const table_add = () => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.open());
+}
+
+const table_edit = row => {
+    dialog.value = true;
+    nextTick(() => recordRef.value?.setData(row));
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该监测记录?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.env.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable,
+    getTableTotal
+})
+</script>

+ 12 - 0
src/views/dataMock/env/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>

+ 185 - 112
src/views/dataMock/env/detail.vue

@@ -1,68 +1,107 @@
 <template>
-    <el-dialog v-model="visible" title="数据模拟" width="860" :close-on-click-modal="false" @closed="$emit('closed')">
-        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+    <el-dialog v-model="visible" title="数据模拟" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-tabs v-model="apiKey">
+            <el-tab-pane label="参数配置" name="makeData"></el-tab-pane>
+            <el-tab-pane label="数据复制" name="copyData"></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="dateRange">
-                        <el-date-picker v-model="form.dateRange" type="daterange" :clearable="false" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+                    <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 :md="12" :xs="24">
-                    <el-form-item class="step-item" label="时间步长" prop="timeStep">
-                        <el-input-number v-model="form.timeStep" :min="0"
-                            :max="['second', 'minute'].includes(form.timeStepType) ? 60 : form.timeStepType == 'hour' ? 12 : Infinity" 
-                            :controls="false" placeholder="时间步长">
-                        </el-input-number>
-                        <el-form-item>
-                            <el-select v-model="form.timeStepType">
-                                <el-option label="秒" value="second"></el-option>
-                                <el-option label="分钟" value="minute"></el-option>
-                                <el-option label="小时" value="hour"></el-option>
-                                <el-option label="天" value="day"></el-option>
-                            </el-select>
-                        </el-form-item>
+                <el-col v-if="form.targetProjectId" :md="12" :xs="24">
+                    <el-form-item label="数据时间范围">
+                        <template v-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/basic/acceptItems')">去配置</el-button></template>
+                        <template v-else-if="acceptItem.beginTime">{{ $TOOL.dateFormat(acceptItem.beginTime, "YY.M.D") }}<span>-{{ acceptItem.endTime && $TOOL.dateFormat(acceptItem.endTime, "YY.M.D") || "至今" }}</span></template>
+                        <template v-else>该项目未配置数据时间范围,<el-button type="primary" link @click="$router.push('/basic/project')">去配置</el-button></template>
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="精度偏差" prop="precision">
-                        <el-input-number v-model="form.precision" :min="0" :max="2" :precision="2" :controls="false" placeholder="请输入精度偏差">
-                            <template #prefix>±</template>
-                        </el-input-number>
+                    <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 v-if="apiKey == 'makeData'" :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="结束时间"></el-date-picker>
                     </el-form-item>
                 </el-col>
+                <template v-if="apiKey == 'copyData'">
+                    <el-col :md="12" :xs="24">
+                        <el-form-item label="模拟年份" prop="targetYear">
+                            <el-date-picker v-model="form.targetYear" type="year" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
+                        </el-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>
+                </template>
+
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="数据处理" prop="handler">
-                        <el-radio-group v-model="form.handler">
-                            <el-radio value="copy">重复新增</el-radio>
-                            <el-radio value="cover">数据覆盖</el-radio>
-                            <el-radio value="partly">部分覆盖</el-radio>
+                    <el-form-item style="margin-bottom: 0;" label="数据处理" prop="isCover">
+                        <el-radio-group v-model="form.isCover">
+                            <el-radio :value="false">重复新增</el-radio>
+                            <el-radio :value="true">数据覆盖</el-radio>
                         </el-radio-group>
                     </el-form-item>
                 </el-col>
             </el-row>
 
-            <el-form-item label="数据设置">
-                <sc-form-table :header-cell-style="headerCellStyle" v-model="form.warnList" hideSeq :span-method="spanMethod">
-                    <el-table-column prop="field" width="100" align="center"></el-table-column>
-                    <el-table-column label="报警类型" prop="fieldDesc" width="160" align="center"></el-table-column>
-                    <el-table-column class-name="threshold-cell" label="阈值" prop="threshold">
-                        <template #default="scope">
-                            <el-input-number v-model="scope.row.min" :min="0" :max="scope.row.max" :precision="2" :controls="false" placeholder="最小值"></el-input-number>
-                            <div class="symbol">至</div>
-                            <el-input-number v-model="scope.row.max" :min="scope.row.min" :precision="2" :controls="false" placeholder="最大值"></el-input-number>
-                        </template>
-                    </el-table-column>
-                    <el-table-column label="报警条数" prop="count" width="120" align="center">
-                        <template #default="scope">
-                            <el-input-number v-model="scope.row.count" :min="0" :controls="false" placeholder="请输入报警条数"></el-input-number>
-                        </template>
-                    </el-table-column>
-                </sc-form-table>
-            </el-form-item>
+            <template v-if="apiKey == 'copyData'">
+                <el-divider />
+
+                <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)]" 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>
+            </template>
         </el-form>
 
         <template #footer>
-            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit()">提交</el-button>
+            <el-button v-if="form.source == 'other'" :loading="isSaving" type="primary" auto-insert-space @click="submit('template')">保存为模版</el-button>
             <el-button auto-insert-space @click="visible = false">取消</el-button>
         </template>
     </el-dialog>
@@ -71,76 +110,123 @@
 <script setup>
 import moment from "moment";
 import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
 
+const route = useRoute();
 const $emit = defineEmits(["success", "closed"]);
+const apiKey = ref("makeData");
 const visible = ref(false);
 const isSaving = ref(false);
 
-const shortcuts = [
-    { text: "上周", value: () => [moment().subtract(1, "week"), moment()] },
-    { text: "上月", value: () => [moment().subtract(1, "month"), moment()] },
-    { text: "去年", value: () => [moment().subtract(1, "year"), moment()] }
-]
-
+const shortcuts = rangeShortcuts();
 const form = ref({
-    id: null,
-    dateRange: null,
-    timeStepType: "minute",
-    timeStep: 3,
-    precision: null,
-    handler: "copy",
-    warnList: [
-        { field: "PM2.5 (μg/m³)", min: 0, max: 75, count: 0 },
-        { field: "PM10 (μg/m³)", min: 0, max: 150, count: 0 },
-        { field: "PM100 (μg/m³)", min: 0, max: 300, count: 0 },
-        { field: "噪音 (dB)", fieldDesc: "白天", min: 0, max: 70, count: 0 },
-        { field: "噪音 (dB)", fieldDesc: "夜间", min: 0, max: 55, count: 0 },
-        
-        { field: "温度 (℃)", fieldDesc: "春秋(3-6月、9-11月)", min: 5, max: 27, count: 0 },
-        { field: "温度 (℃)", fieldDesc: "冬季(12-2月)", min: -5, max: 5, count: 0 },
-        { field: "温度 (℃)", fieldDesc: "夏季(7-8月)", min: 28, max: 35, count: 0 },
-        
-        { field: "湿度 (%)", fieldDesc: "春秋(3-6月、9-11月)", min: 40, max: 70, count: 0 },
-        { field: "湿度 (%)", fieldDesc: "冬季(12-2月)", min: 40, max: 70, count: 0 },
-        { field: "湿度 (%)", fieldDesc: "夏季(7-8月)", min: 75, max: 95, count: 0 }
-    ]
+    targetProjectId: TOOL.data.get("PROJECT_ID"),
+    targetMountedId: null,
+    targetTime: [],
+    targetYear: null,
+    targetMonth: null,
+    isCover: false,
+    source: "other",
+    sourceProjectId: null,
+    sourceProjectIdNot: 1,
+    sourceMountedId: null,
+    sourceTime: [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
 });
 
 const rules = reactive({
-    dateRange: [{ required: true, message: "请选择时间范围" }],
-    timeStep: [{ required: true, message: "请输入时间步长" }],
-    precision: [{ required: true, message: "请输入精度偏差" }],
-    warnList: [{ required: true, validator: (rule, value, callback) => {
-        callback();
-    }}]
+    targetProjectId: [{ required: true, message: "请选择模拟项目" }],
+    targetMountedId: [{ required: true, message: "请选择模拟项目安装点" }],
+    targetYear: [{ required: true, message: "请选择模拟年份" }],
+    targetTime: [
+        { required: true, message: "请选择模拟时间范围" },
+        { validator: (rule, value, callback) => {
+            if (apiKey.value == "makeData" && moment(XEUtils.last(value)).diff(XEUtils.first(value), "minute") < 3) {
+                callback(new Error("模拟时间范围至少为3分钟"));
+            } else callback();
+        }}
+    ],
+    isCover: [{ required: true }],
+    source: [{ required: true }],
+    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: "createTimeBegin", field: "sourceTime[0]" },
+        { column: "createTimeEnd", field: "sourceTime[1]" }
+    ])
 })
+const refreshTable = () => tableRef.value.refreshTable();
 
-// 合并表头
-const headerCellStyle = ({ row, columnIndex }) => {
-    if (row[0].level === 1) {
-        row[1].colSpan = 2;
-        if (columnIndex === 0) return { display: "none" };
+
+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 spanMethod = ({ row, rowIndex, columnIndex }) => {
-    if ([0, 1, 2].includes(rowIndex)) {
-        if (columnIndex === 0) return [1, 2];
-        if (columnIndex === 1) return [0, 0];
-    }
 
-    if (columnIndex === 0) {
-        if ([5, 8].includes(rowIndex)) return [3, 1];
-        if ([6, 7, 9, 10].includes(rowIndex)) return [0, 0];
-    }
+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.env.mounted.get();
+    mounteds.value = res || [];
 }
 
-const open = () => visible.value = true;
-const formRef = ref();
+const open = () => {
+    visible.value = true;
+    TOOL.data.get("PROJECT_ID") && dataTimeRange();
+    fetchMounted();
+}
 
-const submit = () => {
+const formRef = ref();
+const submit = key => {
     formRef.value.validate(valid => {
         if (valid) {
+            if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
+            
+            let data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "isCover");
+            if (apiKey.value == "makeData") {
+                XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+                XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            }
+            if (apiKey.value == "copyData") {
+                data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "source", "sourceTime");
+                XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));
+                XEUtils.set(data, "sourceEndTime", XEUtils.last(form.value.sourceTime));
+                
+                form.value.source == "template" && XEUtils.set(data, "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.env.dataMock[apiKey.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success");
+            }).catch(() => isSaving.value = false);
         } else {
             return false;
         }
@@ -153,20 +239,7 @@ defineExpose({
 </script>
 
 <style lang="scss" scoped>
-.el-form {padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));}
-
-.el-form-item .el-input-number {width: 100%;}
-.el-form-item .el-input-number :deep(.el-input__prefix) {margin-right: 8px;}
-.el-form-item .el-input-number :deep(.el-input__inner) {text-align: unset;}
-.el-form-item .el-input-number + .el-form-item {margin-left: 20px;}
-    
-.el-form-item.step-item {
-    .el-input-number {flex: 1;}
-    .el-form-item {width: 100px;}
-}
-
+.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 :deep(.el-table) .threshold-cell .cell {display: flex;justify-content: center;align-items: center;}
-.el-form-item :deep(.el-table) .threshold-cell .cell .symbol {margin: 0 10px;}
+.el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
 </style>

+ 19 - 7
src/views/dataMock/env/index.vue

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

+ 19 - 0
src/views/dataMock/env/main.js

@@ -0,0 +1,19 @@
+export const workerStates = {
+    record: "设备监控",
+    monos: "任务中心",
+    template: "模版项目",
+    threshold: "阈值设置",
+    calendar: "温/湿度日历表"
+}
+
+export const valueFormatDic = ["pm25", "pm10", "pm100", "windPower", "windSpeed", "noise", "temperature", "humidity"]
+
+export function transferPower(value) {
+    if (value < 0.3) return 0;
+    else if (value >= 0.3 && value <= 1.5) return 1;
+    else if (value >= 1.6 && value <= 3.3) return 2;
+    else if (value >= 3.4 && value <= 5.4) return 3;
+    else if (value >= 5.5 && value <= 7.9) return 4;
+    else if (value >= 8.0 && value <= 10.7) return 5;
+    else return 0;
+}

+ 0 - 47
src/views/dataMock/env/mono/index.vue

@@ -1,47 +0,0 @@
-<template>
-    <el-dialog v-model="visible" title="任务详情" width="80%" @closed="$emit('closed')">
-        <!-- <scTable :apiObj="formConfig.data.deviceCode && $API.passqrcode.online" min-height="108" max-height="600" framework="zeroLite" :formConfig="formConfig" :paramsColums="paramsColums" :columns="columns"></scTable> -->
-        <scTable :maxHeight="600" :options="options">
-            <template #expand_content="{ row }">
-                <table-expand :rowData="row.monos"></table-expand>
-            </template>
-            <template #progress_content="{ row }">
-                <el-progress text-inside :stroke-width="16" :status="row.status == '已完成' ? 'success' : row.status == '执行失败' ? 'exception' : ''" :percentage="row.progress" />
-            </template>
-        </scTable>
-    </el-dialog>
-</template>
-
-<script setup>
-import XEUtils from "xe-utils";
-import TOOL from "@/utils/tool";
-import { defaultGroups } from "../../main";
-
-import tableExpand from "./tableExpand";
-
-const visible = ref(false);
-const open = data => {
-    visible.value = true;
-    options.data = data ? [XEUtils.first(XEUtils.get(defaultGroups, "groups.000.monos"))] : XEUtils.get(defaultGroups, "groups.000.monos")
-}
-
-const options = reactive({
-    minHeight: 108,
-    data: [],
-    formConfig: { enabled: false },
-    pagerConfig: { enabled: false },
-    columns: [
-        { type: "expand", fixed: "left", width: 50, align: "center", slots: { content: "expand_content" } },
-        { field: "option", title: "任务类型", minWidth: 100 },
-        { field: "createTime", title: "触发时间", minWidth: 160, formatter: ({ cellValue }) => TOOL.dateFormat(cellValue) },
-        { field: "status", title: "任务状态", minWidth: 100 },
-        { field: "executeTimes", title: "执行次数", minWidth: 80 },
-        { field: "progress", title: "执行进度", minWidth: 250, slots: { default: 'progress_content' } },
-        { field: "reason", title: "失败原因", minWidth: 160 }
-    ]
-})
-
-defineExpose({
-    open
-})
-</script>

+ 0 - 33
src/views/dataMock/env/mono/tableExpand.vue

@@ -1,33 +0,0 @@
-<template>
-    <scTable :maxHeight="192" :options="options">
-        <template #progress_content="{ row }">
-            <el-progress text-inside :stroke-width="16" :status="row.status == '已完成' ? 'success' : row.status == '执行失败' ? 'exception' : ''" :percentage="row.progress" />
-        </template>
-    </scTable>
-</template>
-
-<script setup>
-import TOOL from "@/utils/tool";
-
-const options = reactive({
-    minHeight: 108,
-    data: useAttrs().rowData,
-    formConfig: { enabled: false },
-    pagerConfig: { enabled: false },
-    columns: [
-        { field: "dateRange", title: "时间范围", minWidth: 160 },
-        { field: "timeStep", title: "时间步长", minWidth: 100 },
-        { field: "precision", title: "精度偏差", minWidth: 100 },
-        { field: "handler", title: "数据处理", minWidth: 80 },
-        { field: "createTime", title: "触发时间", minWidth: 160, formatter: ({ cellValue }) => TOOL.dateFormat(cellValue) },
-        { field: "status", title: "任务状态", minWidth: 100 },
-        { field: "executeTimes", title: "执行次数", minWidth: 80 },
-        { field: "progress", title: "执行进度", minWidth: 250, slots: { default: 'progress_content' } },
-        { field: "reason", title: "失败原因", minWidth: 160 }
-    ]
-})
-</script>
-
-<style scoped>
-.el-main {padding: 0;}
-</style>

+ 0 - 134
src/views/dataMock/main.js

@@ -1,134 +0,0 @@
-export const workerStates = {
-    monos: "任务中心",
-    record: "模拟记录",
-    threshold: "阈值设置",
-    calendar: "温/湿度日历表"
-}
-
-export const defaultGroups = {
-    configs: {
-        // keeperName: "",
-        maxGroupQueues: 10,
-        maxQueueLimit: 2000,
-        maxQueues: 50,
-        taskIntervalSeconds: 1,
-        taskRetryInterval: 3,
-        taskRetryTimes: 5,
-        taskWaitSeconds: 5
-    },
-    groups: {
-        "group-000::": {
-            monos: []
-        },
-        "000": {
-            monos: [
-                {
-                    executeTimes: 1,
-                    option: "新增",
-                    status: "执行中",
-                    createTime: "2025-07-21 16:26:33",
-                    progress: 50,
-                    monos: [{
-                        dateRange: "2025-07-17至2025-07-24",
-                        timeStep: "3分钟",
-                        precision: "±0.5",
-                        handler: "重复新增",
-                        status: "执行中",
-                        executeTimes: 1,
-                        progress: 11,
-                        createTime: "2025-07-21 16:26:33"
-                    },{
-                        dateRange: "2025-07-17至2025-07-24",
-                        timeStep: "3分钟",
-                        precision: "±0.5",
-                        handler: "重复新增",
-                        status: "执行中",
-                        progress: 9,
-                        executeTimes: 1,
-                        createTime: "2025-07-21 16:26:33"
-                    }]
-                },
-                { executeTimes: 1, option: "覆盖", status: "已完成", progress: 100, createTime: "2025-07-22 10:26:33" },
-                { executeTimes: 6, option: "新增", status: "执行失败", progress: 47, createTime: "2025-07-22 01:26:33",
-                    reason: "mono `` exec failed, reason: exceeded maximum attempts, maxExecuteTimes:6 executeTimes:6"
-                }
-            ]
-        }
-    },
-    workers: {
-        "group-000::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-000::"
-        },
-        "group-001::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-001::"
-        },
-        "group-002::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-002::"
-        },
-        "group-003::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-003::"
-        },
-        "group-004::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-004::"
-        },
-        "group-005::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-005::"
-        },
-        "group-006::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-006::"
-        },
-        "group-007::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-007::"
-        },
-        "group-008::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-008::"
-        },
-        "group-009::": {
-            "executing": "",
-            "status": "running",
-            "workName": "group-009::"
-        }
-    }
-}
-
-// pm2.5
-// 0-35 优
-// 36-75 良
-// 76-115 轻度污染
-// 116-150 中度污染
-// 151-250 重度污染
-// 251- 严重污染
-
-// pm10
-// 0-50 优
-// 51-150 良
-// 151-250 轻度污染
-// 251-350 中度污染
-// 351-420 重度污染
-// 421- 严重污染
-
-// pm100
-// 0-120 优 #00e400
-// 121-300 良 #ff0
-// 301-500 轻度污染 #ff7e00
-// 501-625 中度污染 #f00
-// 626-800 重度污染 #99004c
-// 801- 严重污染 #7e0023

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

@@ -10,7 +10,7 @@
             <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
         </el-tabs>
 
-        <component ref="componentRef" :is="allcomp[activeName]" taskType="autospray" />
+        <component ref="componentRef" :is="allcomp[activeName]" m_apiKey="spray" taskType="autospray" />
 	</el-container>
 
     <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>

+ 4 - 3
src/views/dataMock/tasks/monos.vue

@@ -18,15 +18,16 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemRadio, mapFormItemDatePicker } from "@/components/scTable/helper";
-import { taskDic, objectToArray } from "@/utils/basicDic";
+import { taskDic, tempProModel, objectToArray } from "@/utils/basicDic";
 import tableExpand from "./tableExpand";
 
 const props = defineProps({
+    m_apiKey: { type: String, default: "" },
     taskType: { type: String, default: "" }
 });
 
 const radioConfig = reactive({
-    visibleMethod: ({ data }) => ["car_rinse", "aihazard", "tcm", "elevator"].includes(data.taskType),
+    visibleMethod: ({ data }) => tempProModel.includes(data.taskType),
     span: 5,
     resetValue: false,
     options: [{ label: "是", value: true }, { label: "否", value: false }],
@@ -51,7 +52,7 @@ const proConfig = reactive({
 const mountedConfig = reactive({
     visibleMethod: ({ data }) => !(data.isTemp == 1 || data.taskType == "car_rinse"),
     api: {
-        key: "ugliAi.mounted",
+        key: computed(() => `${props.m_apiKey || props.taskType}.mounted`),
         query: {
             projectId: computed(() => formConfig.data.projectId),
             projectIdNot: 1

+ 1 - 5
src/views/dataMock/tasks/tableExpand.vue

@@ -1,5 +1,5 @@
 <template>
-    <scTable v-bind="tableOptions" :options="options"></scTable>
+    <scTable v-bind="tableOptions" :options="{ data: $attrs.rowData }"></scTable>
 </template>
 
 <script setup>
@@ -23,10 +23,6 @@ const tableOptions = reactive({
         { field: "message", title: "失败原因", minWidth: 250, editRender: { name: "$cell-tag", props: { effect: "dark", type: "custom", color: "#f50" } } },
     ]
 });
-
-const options = reactive({
-    data: useAttrs().rowData
-})
 </script>
 
 <style scoped>

+ 3 - 3
src/views/dataMock/tower/components/alarm.vue

@@ -69,7 +69,7 @@ const toolbarConfig = reactive({
 
 const formConfig = reactive({
     data: {
-        orderBy: "ew.createTime_desc",
+        orderBy: "tw.createTime_desc",
         projectId: TOOL.data.get("PROJECT_ID"),
         projectIdNot: 1,
         createTime: [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().add(1, "hour").format("YYYY-MM-DD HH:mm:ss")]
@@ -88,8 +88,8 @@ const paramsColums = reactive([
     { column: "projectIdNot" },
     { column: "mountedId" },
     { column: "warningType" },
-    { column: "er.createTimeBegin", field: "createTime[0]" },
-    { column: "er.createTimeEnd", field: "createTime[1]" }
+    { column: "tr.createTimeBegin", field: "createTime[0]" },
+    { column: "tr.createTimeEnd", field: "createTime[1]" }
 ])
 
 const columns = reactive([

+ 1 - 1
src/views/dataMock/tower/components/attendance.vue

@@ -22,7 +22,7 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
-import { dataSource, objectToArray } from "@/utils/basicDic";
+import { dataSource } from "@/utils/basicDic";
 import { folderKeyDic } from "@/views/dataMock/tower/main";
 
 const formatCertificate = (row, { field }) => XEUtils.find(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "person.features")), "certificate", []), item => item.type == XEUtils.get(folderKeyDic, field));

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

@@ -67,9 +67,9 @@
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="风" prop="features.windSpeed">
-                        <el-input-number v-model="form.features.windSpeed" :precision="1" :controls="false" placeholder="请输入风">
-                            <template #suffix></template>
+                    <el-form-item label="风" prop="features.windSpeed">
+                        <el-input-number v-model="form.features.windSpeed" :precision="1" :controls="false" placeholder="请输入风">
+                            <template #suffix>m/s</template>
                         </el-input-number>
                     </el-form-item>
                 </el-col>
@@ -137,7 +137,7 @@ const rules = reactive({
     "features.powerRate": [{ required: true, message: "请输入力矩百分比" }],
     "features.rotationAngle": [{ required: true, message: "请输入回转角度" }],
     "features.tiltAngle": [{ required: true, message: "请输入倾角" }],
-    "features.windSpeed": [{ required: true, message: "请输入风" }],
+    "features.windSpeed": [{ required: true, message: "请输入风" }],
     "features.workAngle": [{ required: true, message: "请输入幅度" }]
 })
 

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

@@ -19,7 +19,7 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
-import { dataSource, objectToArray } from "@/utils/basicDic";
+import { dataSource } from "@/utils/basicDic";
 import recordDetail from "./detail";
 
 const formatFea = row => XEUtils.toStringJSON(XEUtils.get(row, "features", "{}"));
@@ -116,7 +116,7 @@ const columns = reactive([
     { type: "html", field: "rotationAngle", title: "回转角度(°)", minWidth: 110, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "rotationAngle"), 10) },
     { type: "html", field: "tiltAngle", title: "倾角(°)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || formatTilt(row) },
     { type: "html", field: "tiltAngleRate", title: "倾角百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.toInteger(XEUtils.multiply(XEUtils.divide(formatTilt(row), XEUtils.divide(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "mountedFeatures", "{}")), "maxTilt"), 10)), 100)) },
-    { type: "html", field: "windSpeed", title: "风力(级)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "windSpeed"), 10) },
+    { type: "html", field: "windSpeed", title: "风速(m/s)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "windSpeed"), 10) },
     { type: "html", field: "windSpeedRate", title: "风力百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.toInteger(XEUtils.multiply(XEUtils.divide(XEUtils.get(formatFea(row), "windSpeed"), XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "mountedFeatures", "{}")), "maxWindSpeed")), 100)) },
     { type: "html", field: "workAngle", title: "幅度(m)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "workAngle"), 10) },
     { visible, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },

+ 11 - 10
src/views/dataMock/tower/detail.vue

@@ -46,18 +46,18 @@
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
-                        <el-form-item label="数据处理" prop="isCover">
-                            <el-radio-group v-model="form.isCover">
-                                <el-radio :value="false">重复新增</el-radio>
-                                <el-radio :value="true">数据覆盖</el-radio>
+                        <el-form-item label="数据来源" prop="source">
+                            <el-radio-group v-model="form.source" @change="refreshTable">
+                                <el-radio value="template">模版项目</el-radio>
+                                <el-radio value="other">其他项目</el-radio>
                             </el-radio-group>
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
-                        <el-form-item style="margin-bottom: 0;" label="数据来源" prop="source">
-                            <el-radio-group v-model="form.source" @change="refreshTable">
-                                <el-radio value="template">模版项目</el-radio>
-                                <el-radio value="other">其他项目</el-radio>
+                        <el-form-item label="数据处理" prop="isCover">
+                            <el-radio-group v-model="form.isCover">
+                                <el-radio :value="false">重复新增</el-radio>
+                                <el-radio :value="true">数据覆盖</el-radio>
                             </el-radio-group>
                         </el-form-item>
                     </el-col>
@@ -79,7 +79,7 @@
                 <template v-if="dataSouce == 'attendance'">
                     <el-col :md="12" :xs="24">
                         <el-form-item label="模拟时间范围" prop="targetTime">
-                            <el-date-picker v-model="form.targetTime" type="daterange" :clearable="false" value-format="YYYY-MM-DD 00:00:00" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
+                            <el-date-picker v-model="form.targetTime" type="daterange" :clearable="false" value-format="YYYY-MM-DD 00:00:00" :shortcuts="daterangeShortcuts" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
                         </el-form-item>
                     </el-col>
                     <el-col :md="12" :xs="24">
@@ -94,7 +94,7 @@
             </el-row>
 
             <template v-if="dataSouce == 'normal'">
-                <el-divider />
+                <el-divider style="margin-top: 6px;" />
 
                 <el-row>
                     <template v-if="form.source == 'other'">
@@ -150,6 +150,7 @@ const visible = ref(false);
 const isSaving = ref(false);
 
 const shortcuts = rangeShortcuts();
+const daterangeShortcuts = rangeShortcuts("YYYY-MM-DD 00:00:00");
 const form = ref({
     targetProjectId: TOOL.data.get("PROJECT_ID"),
     targetMountedId: null,

+ 1 - 1
src/views/dataMock/tower/index.vue

@@ -16,7 +16,7 @@
             <el-tab-pane label="考勤数据" name="tcm_person"></el-tab-pane>
         </el-tabs>
 
-        <component ref="componentRef" :is="allcomp[activeName]" :taskType="taskType" />
+        <component ref="componentRef" :is="allcomp[activeName]" m_apiKey="tower" :taskType="taskType" />
 	</el-container>
 
     <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>

+ 9 - 9
src/views/dataMock/ugliAi/detail.vue

@@ -41,18 +41,18 @@
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="数据处理" prop="isCover">
-                        <el-radio-group v-model="form.isCover">
-                            <el-radio :value="false">重复新增</el-radio>
-                            <el-radio :value="true">数据覆盖</el-radio>
+                    <el-form-item label="数据来源" prop="source">
+                        <el-radio-group v-model="form.source" @change="refreshTable">
+                            <el-radio value="template">模版项目</el-radio>
+                            <el-radio value="other">其他项目</el-radio>
                         </el-radio-group>
                     </el-form-item>
                 </el-col>
                 <el-col :md="12" :xs="24">
-                    <el-form-item style="margin-bottom: 0;" label="数据来源" prop="source">
-                        <el-radio-group v-model="form.source" @change="refreshTable">
-                            <el-radio value="template">模版项目</el-radio>
-                            <el-radio value="other">其他项目</el-radio>
+                    <el-form-item style="margin-bottom: 0;" label="数据处理" prop="isCover">
+                        <el-radio-group v-model="form.isCover">
+                            <el-radio :value="false">重复新增</el-radio>
+                            <el-radio :value="true">数据覆盖</el-radio>
                         </el-radio-group>
                     </el-form-item>
                 </el-col>
@@ -177,7 +177,7 @@ const formRef = ref();
 const submit = key => {
     formRef.value.validate(valid => {
         if (valid) {
-            if (tableRef.value.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
+            if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
             
             const data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "source", "sourceTime");
             XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));

+ 1 - 1
src/views/dataMock/ugliAi/index.vue

@@ -10,7 +10,7 @@
             <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" />
+        <component ref="componentRef" :is="allcomp[activeName]" m_apiKey="ugliAi" taskType="aihazard" />
 	</el-container>
 
     <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>