zhuangyunsheng 3 hónapja
szülő
commit
009835bb13

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

@@ -0,0 +1,90 @@
+import config from "@/config"
+import http from "@/utils/request"
+
+export default {
+    mounted: {
+        url: `${config.API_URL}/ops/elevator/getMountedList`,
+        name: "安装点查询",
+        get: async function (data = {}) {
+            return await http.post(this.url, data);
+        }
+    },
+
+    record: {
+        name: "监测记录",
+        url: `${config.API_URL}/ops/elevator`,
+        get: async function (data = {}) {
+            return await http.post(`${this.url}/getPage`, data);
+        },
+
+        add: async function (data = {}) {
+            return await http.post(`${this.url}/save`, data);
+        },
+
+        edit: async function (data = {}) {
+            return await http.post(`${this.url}/update`, data);
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/remove`, data);
+        },
+
+        batchDel: async function (data = {}) {
+            return await http.post(`${this.url}/batchRemove`, data);
+        }
+    },
+
+    warning: {
+        name: "告警记录",
+        url: `${config.API_URL}/ops/elevator`,
+        get: async function (data = {}) {
+            return await http.post(`${this.url}/getWarningPage`, data);
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/removeWarning`, data);
+        },
+
+        batchDel: async function (data = {}) {
+            return await http.post(`${this.url}/batchRemoveWarning`, data);
+        }
+    },
+
+    attendance: {
+        name: "打卡记录",
+        url: `${config.API_URL}/ops/elevator`,
+        get: async function (data = {}) {
+            return await http.post(`${this.url}/getPersonPage`, data);
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/removePersonRecord`, data);
+        },
+
+        batchDel: async function (data = {}) {
+            return await http.post(`${this.url}/batchRemovePerson`, data);
+        }
+    },
+
+    dataMock: {
+        copyData: {
+            url: `${config.API_URL}/ops/elevator`,
+            name: "数据模拟-复制",
+            normal: async function (data = {}) {
+                return await http.post(`${this.url}/copyData`, data);
+            }
+        },
+
+        makeData: {
+            url: `${config.API_URL}/ops/elevator`,
+            name: "数据模拟-参数",
+            attendance: async function (data = {}) {
+                return await http.post(`${this.url}/makePersonData`, data);
+            },
+
+            alarm: async function (data = {}) {
+                return await http.post(`${this.url}/makeWarningData`, data);
+            }
+        }
+    }
+}

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

@@ -0,0 +1,128 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.elevator.warning" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
+        <template #action="{ row }">
+            <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>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource, objectToArray } from "@/utils/basicDic";
+import { warningTypeDic } from "@/views/dataMock/tower/main";
+
+const proConfig = reactive({
+    span: 5,
+    storageKey: "PROJECT",
+    resetValue: TOOL.data.get("PROJECT_ID"),
+    optionProps: { label: "projectName", value: "fpiId" },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, { ...data, mountedId: null })
+    }
+})
+
+const mountedConfig = reactive({
+    api: {
+        key: "elevator.mounted",
+        query: {
+            projectId: computed(() => formConfig.data.projectId),
+            projectIdNot: 1
+        }
+    },
+    slot: {
+        style: { float: "right", paddingLeft: "6px", color: "#8492a6" }
+    },
+    optionProps: { label: "mountedName", value: "id", slot: ({ data }) => XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId === data.projectId), "projectName") },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, data)
+    }
+})
+
+const selectConfig = reactive({
+    options: objectToArray(XEUtils.omit(warningTypeDic, "WARNING_MANY")),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
+const datetimerangeConfig = reactive({
+    span: 7,
+    resetValue: () => [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().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: {
+        orderBy: "ew.createTime_desc",
+        projectId: TOOL.data.get("PROJECT_ID"),
+        projectIdNot: 1,
+        createTime: [moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemSelect("mountedId", "设备安装点", mountedConfig),
+        mapFormItemDatePicker("createTime", "监测时间", datetimerangeConfig),
+        mapFormItemSelect("warningType", "告警类型", selectConfig)
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "orderBy" },
+    { column: "projectId" },
+    { column: "projectIdNot" },
+    { column: "mountedId" },
+    { column: "warningType" },
+    { column: "er.createTimeBegin", field: "createTime[0]" },
+    { column: "er.createTimeEnd", field: "createTime[1]" }
+])
+
+const columns = reactive([
+    { type: "checkbox", fixed: "left", width: 40 },
+    { type: "seq", fixed: "left", width: 60 },
+    { 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: "groundName", title: "工地场区", minWidth: 160, sortable: true },
+    { type: "html", field: "mountedName", title: "设备安装点", minWidth: 160, sortable: true },
+    { type: "html", field: "createTime", title: "告警时间", minWidth: 160, sortable: true },
+    { type: "html", field: "warningType", title: "告警类型", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(warningTypeDic, cellValue, cellValue) },
+    { type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { title: "操作", fixed: "right", width: 120, align: "center", slots: { default: "action" } }
+])
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+
+const refreshTable = () => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData();
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该告警记录?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.elevator.warning.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+</script>

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

@@ -0,0 +1,139 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.elevator.attendance" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
+        <template #default_imgText="{ row, column }">
+            <template v-if="formatCertificate(row, column)">
+                <el-button type="primary" link @click="handlePreview(row, column)">{{ XEUtils.get(formatCertificate(row, column), "certificateNo") }}</el-button>
+            </template>
+        </template>
+        
+        <template #action="{ row }">
+            <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>
+
+    <file-viewer v-if="showViewer" ref="viewerRef" @closed="showViewer = false"></file-viewer>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource, objectToArray } from "@/utils/basicDic";
+import { folderKeyDic } from "@/views/dataMock/tower/main";
+
+const formatCertificate = (row, { field }) => XEUtils.find(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "person.features")), "certificate", []), item => item.type == XEUtils.get(folderKeyDic, field));
+
+const proConfig = reactive({
+    span: 5,
+    storageKey: "PROJECT",
+    resetValue: TOOL.data.get("PROJECT_ID"),
+    optionProps: { label: "projectName", value: "fpiId" },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, { ...data, mountedId: null })
+    }
+})
+
+const mountedConfig = reactive({
+    api: {
+        key: "elevator.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().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: "ELEVATOR_RECORD_ENTER",
+        createTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemSelect("mountedId", "设备安装点", mountedConfig),
+        mapFormItemDatePicker("createTime", "监测时间", datetimerangeConfig)
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "projectId" },
+    { column: "projectIdNot" },
+    { column: "mountedId" },
+    { column: "recordType" },
+    { column: "createTimeBegin", field: "createTime[0]" },
+    { column: "createTimeEnd", field: "createTime[1]" }
+])
+
+const columns = reactive([
+    { type: "checkbox", fixed: "left", width: 40 },
+    { type: "seq", fixed: "left", width: 60 },
+    { type: "html", field: "person.name", title: "人员姓名", minWidth: 100, sortable: true },
+    { type: "html", field: "person.idCard", title: "身份证号", minWidth: 160, sortable: true },
+    { type: "html", field: "person.phoneNumber", title: "手机号", minWidth: 160, sortable: true },
+    { type: "html", field: "createTime", title: "打卡时间", minWidth: 160, sortable: true },
+    { 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: "groundName", title: "工地场区", minWidth: 160, sortable: true },
+    { type: "html", field: "mountedName", title: "设备安装点", minWidth: 160, sortable: true },
+    { field: "facerec/elevator_zs", title: "特种作业证", minWidth: 140, align: "center", slots: { default: "default_imgText" } },
+    { type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { title: "操作", fixed: "right", width: 120, align: "center", slots: { default: "action" } }
+])
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+
+const refreshTable = () => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData();
+}
+
+const showViewer = ref(false);
+const viewerRef = ref();
+const handlePreview = (row, { field }) => {
+    showViewer.value = true;
+    nextTick(() => viewerRef.value.init(XEUtils.get(row, `person.folders.${field}.entities[0]`)));
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该考勤记录?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.elevator.attendance.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+</script>

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

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

@@ -0,0 +1,320 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="880" :close-on-click-modal="false" @closed="$emit('closed', isDel)">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="140">
+            <el-row v-if="props.projectId != 1">
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="所属项目" prop="projectId">
+                        <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="设备安装点" prop="mountedId">
+                        <el-select v-model="form.mountedId" filterable placeholder="请选择设备安装点">
+                            <el-option v-for="item in filterMounteds" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <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="前门锁闭状态" prop="features.frontDoorStatus">
+                        <el-select v-model="form.features.frontDoorStatus" placeholder="请选择前门锁闭状态">
+                            <el-option v-for="(label, index) in elevatorDic.isOpen" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="后门锁闭状态" prop="features.backDoorStatus">
+                        <el-select v-model="form.features.backDoorStatus" placeholder="请选择后门锁闭状态">
+                            <el-option v-for="(label, index) in elevatorDic.isOpen" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="驾驶员认证状态" prop="features.driverStatus">
+                        <el-select v-model="form.features.driverStatus" placeholder="请选择驾驶员认证状态">
+                            <el-option v-for="(label, index) in elevatorDic.isCert" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="前门状态" prop="features.frontStatus">
+                        <el-select v-model="form.features.frontStatus" placeholder="请选择前门状态">
+                            <el-option v-for="(label, index) in elevatorDic.isNormal" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="后门状态" prop="features.backStatus">
+                        <el-select v-model="form.features.backStatus" placeholder="请选择后门状态">
+                            <el-option v-for="(label, index) in elevatorDic.isNormal" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="高度" prop="features.height">
+                        <el-input-number v-model="form.features.height" :precision="1" :controls="false" placeholder="请输入高度">
+                            <template #suffix>m</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="限位系统状态" prop="features.heightLimit">
+                        <el-select v-model="form.features.heightLimit" placeholder="请选择限位系统状态">
+                            <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="门锁异常指示" prop="features.lockStatus">
+                        <el-select v-model="form.features.lockStatus" placeholder="请选择门锁异常指示">
+                            <el-option v-for="(label, index) in elevatorDic.isNormal" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="超速状态" prop="features.overspeed">
+                        <el-select v-model="form.features.overspeed" placeholder="请选择超速状态">
+                            <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="人数" prop="features.personCount">
+                        <el-input-number v-model="form.features.personCount" :min="0" :controls="false" placeholder="请输入人数"></el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="人数状态" prop="features.personStatus">
+                        <el-select v-model="form.features.personStatus" placeholder="请选择人数状态">
+                            <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                 <el-col :md="12" :xs="24">
+                    <el-form-item label="速度" prop="features.speed">
+                        <el-input-number v-model="form.features.speed" :precision="1" :controls="false" placeholder="请输入速度">
+                            <template #suffix>m/s</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="移动状态" prop="features.status">
+                        <el-select v-model="form.features.status" placeholder="请选择移动状态">
+                            <el-option v-for="(label, index) in elevatorDic.move" :key="index" :label="label" :value="index + ''"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="倾角x" prop="features.tilt1">
+                        <el-input-number v-model="form.features.tilt1" :precision="2" :controls="false" placeholder="请输入倾角x">
+                            <template #suffix>°</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="倾角x百分比" prop="features.tilt1Rate">
+                        <el-input-number v-model="form.features.tilt1Rate" :precision="2" :controls="false" placeholder="请输入倾角x百分比">
+                            <template #suffix>%</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="倾角y" prop="features.tilt2">
+                        <el-input-number v-model="form.features.tilt2" :precision="2" :controls="false" placeholder="请输入倾角y">
+                            <template #suffix>°</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="倾角y百分比" prop="features.tilt2Rate">
+                        <el-input-number v-model="form.features.tilt2Rate" :precision="2" :controls="false" placeholder="请输入倾角y百分比">
+                            <template #suffix>%</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="倾角状态" prop="features.tiltStatus">
+                        <el-select v-model="form.features.tiltStatus" placeholder="请选择倾角状态">
+                            <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="载重" prop="features.weight">
+                        <el-input-number v-model="form.features.weight" :controls="false" placeholder="请输入载重">
+                            <template #suffix>kg</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="载重百分比" prop="features.weightRate">
+                        <el-input-number v-model="form.features.weightRate" :precision="2" :controls="false" placeholder="请输入载重百分比">
+                            <template #suffix>%</template>
+                        </el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="重量系统状态" prop="features.weightStatus">
+                        <el-select v-model="form.features.weightStatus" placeholder="请选择重量系统状态">
+                            <el-option v-for="(label, key) in elevatorDic.binary" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { valueFormatDic, elevatorDic } from "@/views/dataMock/elevator/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,
+    features: {
+        frontDoorStatus: null,
+        backDoorStatus: null,
+        driverStatus: null,
+        frontStatus: null,
+        backStatus: null,
+        height: null,
+        heightLimit: null,
+        lockStatus: null,
+        overspeed: null,
+        personCount: null,
+        personStatus: null,
+        speed: null,
+        status: null,
+        tilt1: null,
+        tilt1Rate: null,
+        tilt2: null,
+        tilt2Rate: null,
+        tiltStatus: null,
+        weight: null,
+        weightRate: null,
+        weightStatus: null
+    }
+});
+const rules = reactive({
+    projectId: [{ required: true, message: "请选择所属项目" }],
+    mountedId: [{ required: true, message: "请选择设备安装点" }],
+    createTime: [{ required: true, message: "请选择监测时间" }],
+    "features.frontDoorStatus": [{ required: true, message: "请选择前门锁闭状态" }],
+    "features.backDoorStatus": [{ required: true, message: "请选择后门锁闭状态" }],
+    "features.driverStatus": [{ required: true, message: "请选择驾驶员认证状态" }],
+    "features.frontStatus": [{ required: true, message: "请选择前门状态" }],
+    "features.backStatus": [{ required: true, message: "请选择后门状态" }],
+    "features.height": [{ required: true, message: "请输入高度" }],
+    "features.heightLimit": [{ required: true, message: "请选择限位系统状态" }],
+    "features.lockStatus": [{ required: true, message: "请选择门锁异常指示" }],
+    "features.overspeed": [{ required: true, message: "请选择超速状态" }],
+    "features.personCount": [{ required: true, message: "请输入人数" }],
+    "features.personStatus": [{ required: true, message: "请选择人数状态" }],
+    "features.speed": [{ required: true, message: "请输入速度" }],
+    "features.status": [{ required: true, message: "请选择移动状态" }],
+    "features.tilt1": [{ required: true, message: "请输入倾角x" }],
+    "features.tilt1Rate": [{ required: true, message: "请输入倾角x百分比" }],
+    "features.tilt2": [{ required: true, message: "请输入倾角y" }],
+    "features.tilt2Rate": [{ required: true, message: "请输入倾角y百分比" }],
+    "features.tiltStatus": [{ required: true, message: "请选择倾角状态" }],
+    "features.weight": [{ required: true, message: "请输入载重" }],
+    "features.weightRate": [{ required: true, message: "请输入载重百分比" }],
+    "features.weightStatus": [{ 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.elevator.mounted.get();
+    mounteds.value = res || [];
+    if (props.projectId == 1) form.value.mountedId = XEUtils.get(XEUtils.find(res, item => item.projectId == 1), "id");
+}
+
+const open = () => {
+    visible.value = true;
+    fetchMounted();
+}
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(form.value, (_, key) => {
+        if (key == "features") {
+            const features = XEUtils.toStringJSON(XEUtils.get(data, key));
+            XEUtils.objectEach(valueFormatDic, (item, digits) => XEUtils.arrayEach(item, feaKey => XEUtils.set(features, feaKey, XEUtils.divide(XEUtils.get(features, feaKey), digits))));
+            XEUtils.merge(form.value.features, features);
+        } else XEUtils.set(form.value, key, XEUtils.get(data, key));
+    });
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            const data = XEUtils.omit(form.value, "features");
+
+            const features = XEUtils.clone(form.value.features, true);
+            XEUtils.objectEach(valueFormatDic, (item, digits) => XEUtils.arrayEach(item, feaKey => XEUtils.set(features, feaKey, XEUtils.multiply(XEUtils.get(features, feaKey), digits))));
+            XEUtils.set(data, "features", XEUtils.toJSONString(features));
+            
+            isSaving.value = true;
+            API.elevator.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>

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

@@ -0,0 +1,179 @@
+<template>
+    <scTable ref="xGridTable" batchDel :apiObj="$API.elevator.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" v-bind="props.options">
+        <template #action="{ row }">
+            <el-button type="primary" link @click="table_edit(row)">
+                <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
+            </el-button>
+            <el-button type="primary" link @click="table_del(row)">
+                <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
+            </el-button>
+        </template>
+    </scTable>
+
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog = false"></record-detail>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { dataSource, objectToArray } from "@/utils/basicDic";
+import { elevatorDic } from "@/views/dataMock/elevator/main";
+import recordDetail from "./detail";
+
+const formatFea = row => XEUtils.toStringJSON(XEUtils.get(row, "features", "{}"));
+
+const props = defineProps({
+    options: { type: Object, default: () => {} },
+    isTemp: { type: Boolean, default: false },
+    hideHandler: { type: Boolean, default: false }
+})
+const visible = computed(() => !props.isTemp);
+
+const proConfig = reactive({
+    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: "elevator.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().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().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: "frontDoorStatus", title: "前门锁闭状态", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.isOpen, XEUtils.get(formatFea(row), "frontDoorStatus")) },
+    { type: "html", field: "backDoorStatus", title: "后门锁闭状态", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.isOpen, XEUtils.get(formatFea(row), "backDoorStatus")) },
+    { type: "html", field: "driverStatus", title: "驾驶员认证状态", minWidth: 130, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.isCert, XEUtils.get(formatFea(row), "driverStatus")) },
+    { type: "html", field: "frontStatus", title: "前门状态", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.isNormal, XEUtils.get(formatFea(row), "frontStatus")) },
+    { type: "html", field: "backStatus", title: "后门状态", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.isNormal, XEUtils.get(formatFea(row), "backStatus")) },
+    { type: "html", field: "height", title: "高度(m)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "height"), 10) },
+    { type: "html", field: "heightLimit", title: "限位系统状态", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.binary, XEUtils.get(formatFea(row), "heightLimit")) },
+    { type: "html", field: "lockStatus", title: "门锁异常指示", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.isNormal, XEUtils.get(formatFea(row), "lockStatus")) },
+    { type: "html", field: "overspeed", title: "超速状态", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.binary, XEUtils.get(formatFea(row), "overspeed")) },
+    { type: "html", field: "personCount", title: "人数", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "personCount") },
+    { type: "html", field: "personStatus", title: "人数状态", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.binary, XEUtils.get(formatFea(row), "personStatus")) },
+    { type: "html", field: "speed", title: "速度(m/s)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "speed"), 10) },
+    { type: "html", field: "status", title: "移动状态", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.move, XEUtils.get(formatFea(row), "status")) },
+    { type: "html", field: "tilt1", title: "倾角x(°)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "tilt1"), 100) },
+    { type: "html", field: "tilt1Rate", title: "倾角x百分比", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "tilt1Rate") },
+    { type: "html", field: "tilt2", title: "倾角y(°)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "tilt2"), 100) },
+    { type: "html", field: "tilt2Rate", title: "倾角y百分比", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "tilt2Rate") },
+    { type: "html", field: "tiltStatus", title: "倾角状态", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.binary, XEUtils.get(formatFea(row), "tiltStatus")) },
+    { type: "html", field: "weight", title: "载重(kg)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "weight") },
+    { type: "html", field: "weightRate", title: "载重百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "weightRate") },
+    { type: "html", field: "weightStatus", title: "重量系统状态", minWidth: 120, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(elevatorDic.binary, XEUtils.get(formatFea(row), "weightStatus")) },
+    { visible, type: "html", field: "dataSource", title: "数据来源", fixed: "right", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(dataSource, cellValue, cellValue) },
+    { visible: !props.hideHandler, title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+])
+
+const imageToolbar = reactive({
+    print: false,
+    download: true
+})
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const toggleFormEnabled = () => xGridTable.value.toggleFormEnabled();
+const getTableTotal = () => xGridTable.value.getTableData().tableData.length;
+
+const refreshTable = () => {
+    xGridTable.value.reloadColumn(columns);
+    xGridTable.value.searchData();
+}
+
+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.elevator.record.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    });
+}
+
+defineExpose({
+    table_add,
+    refreshTable,
+    getTableTotal
+})
+</script>

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

+ 294 - 0
src/views/dataMock/elevator/detail.vue

@@ -0,0 +1,294 @@
+<template>
+    <el-dialog v-model="visible" title="数据模拟" fullscreen :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-tabs v-model="dataSouce" @tab-change="apiKey = dataSouce == 'normal' ? 'copyData' : 'makeData'">
+            <el-tab-pane label="标准数据" name="normal"></el-tab-pane>
+            <el-tab-pane label="报警数据" name="alarm"></el-tab-pane>
+            <el-tab-pane label="考勤数据" name="attendance"></el-tab-pane>
+        </el-tabs>
+
+        <el-tabs v-model="apiKey">
+            <el-tab-pane label="参数配置" name="makeData" :disabled="dataSouce == 'normal'"></el-tab-pane>
+            <el-tab-pane label="数据复制" name="copyData" :disabled="dataSouce == 'alarm' || dataSouce == 'attendance'"></el-tab-pane>
+        </el-tabs>
+
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="126">
+            <el-row>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目" prop="targetProjectId">
+                        <el-select v-model="form.targetProjectId" filterable placeholder="请选择模拟项目" @change="form.targetMountedId = null, dataTimeRange()">
+                            <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col v-if="form.targetProjectId" :md="12" :xs="24">
+                    <el-form-item label="数据时间范围">
+                        <template v-if="XEUtils.isEmpty(acceptItem)">该项目未配置验收清单,<el-button type="primary" link @click="$router.push('/basic/acceptItems')">去配置</el-button></template>
+                        <template v-else-if="acceptItem.beginTime">{{ $TOOL.dateFormat(acceptItem.beginTime, "YY.M.D") }}<span>-{{ acceptItem.endTime && $TOOL.dateFormat(acceptItem.endTime, "YY.M.D") || "至今" }}</span></template>
+                        <template v-else>该项目未配置数据时间范围,<el-button type="primary" link @click="$router.push('/basic/project')">去配置</el-button></template>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="模拟项目安装点" prop="targetMountedId">
+                        <el-select v-model="form.targetMountedId" filterable placeholder="请选择模拟项目安装点">
+                            <el-option v-for="item in filterTargetM" :key="item.id" :label="item.mountedName" :value="item.id"></el-option>
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+
+                <template v-if="dataSouce == 'normal'">
+                    <el-col :md="12" :xs="24">
+                        <el-form-item class="range-item" label="模拟时间范围" prop="targetBeginTime">
+                            <el-date-picker v-model="form.targetBeginTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="new Date(2000, 1, 1, 23, 59, 59)" placeholder="请选择模拟数据开始时间" @change="dateChange"></el-date-picker>
+                            <span>至</span>
+                            <el-input v-model="form.targetEndTime" readonly placeholder="模拟数据结束时间">
+                                <template #prefix><sc-iconify icon="ep:clock"></sc-iconify></template>
+                            </el-input>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="12" :xs="24">
+                        <el-form-item label="数据处理" prop="isCover">
+                            <el-radio-group v-model="form.isCover">
+                                <el-radio :value="false">重复新增</el-radio>
+                                <el-radio :value="true">数据覆盖</el-radio>
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="12" :xs="24">
+                        <el-form-item 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-radio-group>
+                        </el-form-item>
+                    </el-col>
+                </template>
+
+                <template v-if="dataSouce == 'alarm'">
+                    <el-col :md="12" :xs="24">
+                        <el-form-item label="模拟时间范围" prop="targetTime">
+                            <el-date-picker v-model="form.targetTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"></el-date-picker>
+                        </el-form-item>
+                    </el-col>
+                    <el-col :md="12" :xs="24">
+                        <el-form-item label="报警条数" prop="warnNum">
+                            <el-input-number v-model="form.warnNum" :min="0" :controls="false" placeholder="请输入报警条数"></el-input-number>
+                        </el-form-item>
+                    </el-col>
+                </template>
+
+                <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-form-item>
+                    </el-col>
+                    <el-col :md="12" :xs="24">
+                        <el-form-item label="数据处理" prop="isCover">
+                            <el-radio-group v-model="form.isCover">
+                                <el-radio :value="false">重复新增</el-radio>
+                                <el-radio :value="true">数据覆盖</el-radio>
+                            </el-radio-group>
+                        </el-form-item>
+                    </el-col>
+                </template>
+            </el-row>
+
+            <template v-if="dataSouce == 'normal'">
+                <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="dateChange(), refreshTable()"></el-date-picker>
+                        </el-form-item>
+                    </el-col>
+                </el-row>
+
+                <data-table ref="tableRef" :isTemp="form.source == 'template'" hideHandler :options="tableOptions"></data-table>
+            </template>
+        </el-form>
+
+        <template #footer>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit()">提交</el-button>
+            <el-button v-if="dataSouce == 'normal' && form.source == 'other'" :loading="isSaving" type="primary" auto-insert-space @click="submit('template')">保存为模版</el-button>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import moment from "moment";
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { rangeShortcuts } from "@/utils/shortcuts";
+import dataTable from "./components/record";
+
+const route = useRoute();
+const $emit = defineEmits(["success", "closed"]);
+const dataSouce = ref("normal");
+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,
+    targetBeginTime: null,
+    targetEndTime: null,
+    targetTime: [],
+    isCover: false,
+    source: "other",
+    warnNum: null,
+    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({
+    targetProjectId: [{ required: true, message: "请选择模拟项目" }],
+    targetMountedId: [{ required: true, message: "请选择模拟项目安装点" }],
+    targetBeginTime: [{ required: true, message: "请选择模拟数据开始时间" }],
+    targetTime: [
+        { required: true, message: "请选择模拟时间范围" },
+        { validator: (rule, value, callback) => {
+            if (dataSouce.value == "alarm" && moment(XEUtils.last(value)).diff(XEUtils.first(value), "minute") < 5) {
+                callback(new Error("模拟时间范围至少为5分钟"));
+            } else callback();
+        }}
+    ],
+    isCover: [{ required: true }],
+    source: [{ required: true }],
+    warnNum: [{ 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: "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.elevator.mounted.get();
+    mounteds.value = res || [];
+}
+
+const open = () => {
+    visible.value = true;
+    TOOL.data.get("PROJECT_ID") && dataTimeRange();
+    fetchMounted();
+}
+
+const dateChange = () => {
+    if (form.value.sourceTime && form.value.sourceTime.length && form.value.targetBeginTime) form.value.targetEndTime = moment(form.value.targetBeginTime).add(moment(XEUtils.last(form.value.sourceTime)).diff(XEUtils.first(form.value.sourceTime))).format("YYYY-MM-DD HH:mm:ss")
+    else form.value.targetEndTime = null;
+}
+
+const formRef = ref();
+const submit = key => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            if (tableRef.value?.getTableTotal() == 0) return ElMessage.warning("暂无相关数据,请调整条件后重试。");
+            
+            let data = XEUtils.omit(form.value, "sourceProjectId", "sourceProjectIdNot", "targetTime", "source", "warnNum", "sourceTime");
+            if (dataSouce.value == "normal") {
+                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"));
+                }
+            }
+            if (dataSouce.value == "alarm") {
+                data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "warnNum");
+                XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+                XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            }
+
+            if (dataSouce.value == "attendance") {
+                data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "isCover");
+                XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
+                XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
+            }
+            
+            isSaving.value = true;
+            API.elevator.dataMock[apiKey.value][dataSouce.value](data).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", dataSouce.value);
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open
+})
+</script>
+
+<style lang="scss" scoped>
+.el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
+.el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form .range-item :deep(.el-form-item__content) {
+    .el-date-editor, .el-input {flex: 1;}
+    .el-date-editor + span {padding: 0 10px;}
+}
+
+.el-form-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>

+ 58 - 0
src/views/dataMock/elevator/index.vue

@@ -0,0 +1,58 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header addText="数据模拟" @add="mock_add">
+            <template #extra-right>
+                <el-button v-if="activeName == 'record' || activeName == 'template'" 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>
+
+        <el-tabs v-if="activeName == 'monos'" v-model="taskType" @tab-change="refreshMono">
+            <el-tab-pane label="标准数据" name="elevator"></el-tab-pane>
+            <el-tab-pane label="报警数据" name="elevator_warning"></el-tab-pane>
+            <el-tab-pane label="考勤数据" name="elevator_person"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" :taskType="taskType" />
+	</el-container>
+
+    <mock-detail v-if="dialog" ref="mockRef" @success="refreshState" @closed="dialog = false"></mock-detail>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import { workerStates, monoType } from "./main";
+import comp from "./components";
+import monos from "@/views/dataMock/tasks/monos";
+import mockDetail from "./detail";
+
+const allcomp = { ...comp, monos };
+const activeName = ref("record");
+const taskType = ref("elevator");
+
+const componentRef = ref();
+const mockRef = ref();
+const dialog = ref(false);
+
+const table_add = () => componentRef.value.table_add();
+
+const mock_add = () => {
+    dialog.value = true;
+    nextTick(() => mockRef.value?.open());
+}
+
+const refreshMono = () => componentRef.value.refreshTable();
+const refreshState = mode => {
+    if (activeName.value == "monos") {
+        taskType.value = XEUtils.get(monoType, mode, "elevator");
+        setTimeout(() => refreshMono(), 2000);
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>

+ 30 - 0
src/views/dataMock/elevator/main.js

@@ -0,0 +1,30 @@
+export const workerStates = {
+    record: "设备监控",
+    alarm: "监控告警",
+    attendance: "考勤记录",
+    monos: "任务中心",
+    template: "模版项目"
+}
+
+export const valueFormatDic = {
+    10: ["height", "speed"],
+    100: ["tilt1", "tilt2"]
+}
+
+export const monoType = {
+    normal: "elevator",
+    alarm: "elevator_warning",
+    attendance: "elevator_person"
+}
+
+export const elevatorDic = {
+    isOpen: ["关闭", "开启"],
+    isNormal: ["正常", "异常"],
+    isCert: ["未认证", "已认证"],
+    move: ["停止", "上行", "下行"],
+    binary: {
+        "00": "正常",
+        "01": "预警",
+        "02": "报警"
+    }
+}

+ 1 - 1
src/views/dataMock/tasks/monos.vue

@@ -26,7 +26,7 @@ const props = defineProps({
 });
 
 const radioConfig = reactive({
-    visibleMethod: ({ data }) => ["car_rinse", "aihazard", "tcm"].includes(data.taskType),
+    visibleMethod: ({ data }) => ["car_rinse", "aihazard", "tcm", "elevator"].includes(data.taskType),
     span: 5,
     resetValue: false,
     options: [{ label: "是", value: true }, { label: "否", value: false }],

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

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

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

@@ -22,8 +22,9 @@ import { mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/h
 import { dataSource, objectToArray } from "@/utils/basicDic";
 import recordDetail from "./detail";
 
+const formatFea = row => XEUtils.toStringJSON(XEUtils.get(row, "features", "{}"));
 const formatTilt = row => {
-    const feaTilt = XEUtils.divide(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "tiltAngle"), 100);
+    const feaTilt = XEUtils.divide(XEUtils.get(formatFea(row), "tiltAngle"), 100);
     const tilt = feaTilt > 0 ? feaTilt : XEUtils.add(360, feaTilt);
     return tilt >= 180 ? XEUtils.subtract(360, tilt) : tilt;
 };
@@ -108,16 +109,16 @@ const columns = reactive([
     { 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: "weight", title: "起重量(kg)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "weight") },
-    { type: "html", field: "weightRate", title: "载重百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "weightRate") },
-    { type: "html", field: "height", title: "高度(m)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "height"), 10) },
-    { type: "html", field: "powerRate", title: "力矩百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "powerRate") },
-    { type: "html", field: "rotationAngle", title: "回转角度(°)", minWidth: 110, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "rotationAngle"), 10) },
+    { type: "html", field: "weight", title: "起重量(kg)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "weight") },
+    { type: "html", field: "weightRate", title: "载重百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "weightRate") },
+    { type: "html", field: "height", title: "高度(m)", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.divide(XEUtils.get(formatFea(row), "height"), 10) },
+    { type: "html", field: "powerRate", title: "力矩百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(formatFea(row), "powerRate") },
+    { 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(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "windSpeed"), 10) },
-    { type: "html", field: "windSpeedRate", title: "风力百分比", minWidth: 100, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.toInteger(XEUtils.multiply(XEUtils.divide(XEUtils.get(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "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(XEUtils.toStringJSON(XEUtils.get(row, "features", "{}")), "workAngle"), 10) },
+    { type: "html", field: "windSpeed", title: "风力(级)", 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) },
     { visible: !props.hideHandler, title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
 ])

+ 3 - 13
src/views/dataMock/tower/detail.vue

@@ -82,11 +82,6 @@
                             <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-form-item>
                     </el-col>
-                    <el-col :md="12" :xs="24">
-                        <el-form-item label="人员身份证号" prop="personIdCard">
-                            <el-input v-model="form.personIdCard" placeholder="请输入人员身份证号"></el-input>
-                        </el-form-item>
-                    </el-col>
                     <el-col :md="12" :xs="24">
                         <el-form-item label="数据处理" prop="isCover">
                             <el-radio-group v-model="form.isCover">
@@ -144,7 +139,6 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { rangeShortcuts } from "@/utils/shortcuts";
-import { verifyIdCard } from "@/utils/verificate";
 import dataTable from "./components/record";
 
 const route = useRoute();
@@ -165,7 +159,6 @@ const form = ref({
     isCover: false,
     source: "other",
     warnNum: null,
-    personIdCard: null,
     sourceProjectId: null,
     sourceProjectIdNot: 1,
     sourceMountedId: null,
@@ -187,10 +180,6 @@ const rules = reactive({
     isCover: [{ required: true }],
     source: [{ required: true }],
     warnNum: [{ required: true, message: "请输入报警条数" }],
-    personIdCard: [
-        { required: true, message: "请输入人员身份证号" },
-        { validator: (rule, value, callback) => verifyIdCard(rule, value, callback) }
-    ],
     sourceProjectId: [{ required: true, message: "请选择数据源项目" }],
     sourceMountedId: [{ required: true, message: "请选择数据源安装点" }],
     sourceTime: [{ required: true, message: "请选择数据源抓拍时间" }]
@@ -205,6 +194,7 @@ const tableOptions = reactive({
     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]" }
     ])
@@ -237,7 +227,7 @@ const open = () => {
 }
 
 const dateChange = () => {
-    if (form.value.sourceTime && form.value.sourceTime.length && form.value.targetBeginTime) form.value.targetEndTime = moment(form.value.targetBeginTime).add(moment(XEUtils.last(form.value.sourceTime)).diff(XEUtils.first(form.value.sourceTime), "millisecond"), "millisecond").format("YYYY-MM-DD HH:mm:ss")
+    if (form.value.sourceTime && form.value.sourceTime.length && form.value.targetBeginTime) form.value.targetEndTime = moment(form.value.targetBeginTime).add(moment(XEUtils.last(form.value.sourceTime)).diff(XEUtils.first(form.value.sourceTime))).format("YYYY-MM-DD HH:mm:ss")
     else form.value.targetEndTime = null;
 }
 
@@ -265,7 +255,7 @@ const submit = key => {
             }
 
             if (dataSouce.value == "attendance") {
-                data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "personIdCard", "isCover");
+                data = XEUtils.pick(form.value, "targetProjectId", "targetMountedId", "isCover");
                 XEUtils.set(data, "targetBeginTime", XEUtils.first(form.value.targetTime));
                 XEUtils.set(data, "targetEndTime", XEUtils.last(form.value.targetTime));
             }

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

@@ -141,6 +141,7 @@ const tableOptions = reactive({
     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]" }
     ])