Browse Source

里程碑

zhuangyunsheng 2 months ago
parent
commit
499ef57983

+ 53 - 4
src/api/model/system.js

@@ -3,8 +3,8 @@ import http from "@/utils/request"
 
 
 export default {
 export default {
 	project: {
 	project: {
-		url: `${config.API_URL}/ops/projectInfo`,
 		name: "获取项目",
 		name: "获取项目",
+		url: `${config.API_URL}/ops/projectInfo`,
         dept: async function (data = {}) {
         dept: async function (data = {}) {
 			return await http.post(`${this.url}/getName`, data);
 			return await http.post(`${this.url}/getName`, data);
 		},
 		},
@@ -51,8 +51,8 @@ export default {
 	},
 	},
 
 
     acceptItems: {
     acceptItems: {
-		url: `${config.API_URL}/ops/acceptanceItems`,
 		name: "获取验收清单项",
 		name: "获取验收清单项",
+		url: `${config.API_URL}/ops/acceptanceItems`,
         get: async function (data = {}) {
         get: async function (data = {}) {
 			return await http.post(`${this.url}/getPage`, data);
 			return await http.post(`${this.url}/getPage`, data);
 		},
 		},
@@ -74,17 +74,66 @@ export default {
 		}
 		}
 	},
 	},
 
 
+    milestone: {
+		name: "获取里程碑",
+        url: `${config.API_URL}/ops/opsPlan`,
+        all: async function (data = {}) {
+			return await http.post(`${this.url}/getList`, 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);
+		},
+        
+        // 一键复制
+        copyData: async function (data = {}) {
+			return await http.post(`${this.url}/remove`, data);
+		}
+    },
+
+    todoTask: {
+        name: "待办任务",
+        url: `${config.API_URL}/ops/opsTodo`,
+        get: async function (data = {}) {
+			return await http.post(`${this.url}/getPage`, data);
+		},
+
+        all: async function (data = {}) {
+			return await http.post(`${this.url}/getList`, 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);
+		}
+    },
+
     device: {
     device: {
-		url: `${config.API_URL}/api/deviceStock/getDevicePage`,
 		name: "设备查询",
 		name: "设备查询",
+		url: `${config.API_URL}/api/deviceStock/getDevicePage`,
         get: async function (data = {}) {
         get: async function (data = {}) {
 			return await http.post(this.url, data);
 			return await http.post(this.url, data);
 		}
 		}
 	},
 	},
 
 
     user: {
     user: {
-        url: `${config.API_URL}/api/users`,
 		name: "用户信息",
 		name: "用户信息",
+        url: `${config.API_URL}/api/users`,
         get: async function (params = {}) {
         get: async function (params = {}) {
 			return await http.get(this.url, params);
 			return await http.get(this.url, params);
 		},
 		},

+ 3 - 0
src/components/scPageHeader/index.vue

@@ -19,6 +19,9 @@
                     <el-button v-if="$attrs.onFilter" text @click="$emit('filter')">
                     <el-button v-if="$attrs.onFilter" text @click="$emit('filter')">
                         <template #icon><sc-iconify icon="ant-design:filter-outlined"></sc-iconify></template>筛选
                         <template #icon><sc-iconify icon="ant-design:filter-outlined"></sc-iconify></template>筛选
                     </el-button>
                     </el-button>
+                    <el-button v-if="$attrs.onExpand" @click="$emit('expand')">
+                        <template #icon><sc-iconify icon="ant-design:swap-outlined"></sc-iconify></template>展开/折叠
+                    </el-button>
                     <el-button v-if="$attrs.onAdd" type="primary" @click="$emit('add')">
                     <el-button v-if="$attrs.onAdd" type="primary" @click="$emit('add')">
                         <template #icon><sc-iconify :icon="$attrs.addIcon || 'ant-design:cloud-upload-outlined'"></sc-iconify></template>{{ $attrs.addText || "新增" }}
                         <template #icon><sc-iconify :icon="$attrs.addIcon || 'ant-design:cloud-upload-outlined'"></sc-iconify></template>{{ $attrs.addText || "新增" }}
                     </el-button>
                     </el-button>

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

@@ -248,6 +248,8 @@ const toggleTableLoading = value => gridOptions.value.loading = value;
 
 
 const toggleFormEnabled = () => gridOptions.value.formConfig.enabled = !gridOptions.value.formConfig.enabled;
 const toggleFormEnabled = () => gridOptions.value.formConfig.enabled = !gridOptions.value.formConfig.enabled;
 
 
+const toggleTableExpand = () => xGrid.value.getTreeExpandRecords().length && xGrid.value.clearTreeExpand() || xGrid.value.setAllTreeExpand(true);
+
 const toggleToolbarProps = obj => XEUtils.objectEach(obj, (value, key) => XEUtils.set(gridOptions.value.toolbarConfig, key, value));
 const toggleToolbarProps = obj => XEUtils.objectEach(obj, (value, key) => XEUtils.set(gridOptions.value.toolbarConfig, key, value));
 
 
 const reloadColumn = columns => xGrid.value.reloadColumn(columns);
 const reloadColumn = columns => xGrid.value.reloadColumn(columns);
@@ -290,6 +292,7 @@ defineExpose({
     selectedRows,
     selectedRows,
     toggleTableLoading,
     toggleTableLoading,
     toggleFormEnabled,
     toggleFormEnabled,
+    toggleTableExpand,
     toggleToolbarProps,
     toggleToolbarProps,
     reloadColumn,
     reloadColumn,
     getTableData,
     getTableData,

+ 4 - 1
src/components/scTable/renderer/cell-tag.vue

@@ -19,7 +19,10 @@ const colorDic = {
     任务超时: "orange",
     任务超时: "orange",
 
 
     任务成功: "success",
     任务成功: "success",
-    任务失败: "danger"
+    任务失败: "danger",
+
+    未开始: "default",
+    进行中: "warning"
 }
 }
 
 
 const props = defineProps({
 const props = defineProps({

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

@@ -130,6 +130,7 @@ export default {
         request(param) {
         request(param) {
             const data = new FormData();
             const data = new FormData();
             data.append(param.filename, param.file);
             data.append(param.filename, param.file);
+            
             this.$API.common.folder.up(data, {
             this.$API.common.folder.up(data, {
                 onUploadProgress: e => {
                 onUploadProgress: e => {
                     const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);
                     const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);

+ 4 - 5
src/config/route.js

@@ -43,7 +43,6 @@ const routes = [
             }
             }
         ]
         ]
     },
     },
-
     {
     {
         name: "equipment",
         name: "equipment",
         path: "/equipment",
         path: "/equipment",
@@ -210,10 +209,10 @@ const routes = [
                 component: "system/milestone"
                 component: "system/milestone"
             },
             },
             {
             {
-                name: "task",
-                path: "/system/task",
-                meta: { title: "任务管理", icon: "streamline-ultimate:task-list-pin" },
-                component: "system/task"
+                name: "todoTask",
+                path: "/system/todoTask",
+                meta: { title: "待办任务", icon: "streamline-ultimate:task-list-pin" },
+                component: "system/todoTask"
             },
             },
             {
             {
                 name: "acceptItems",
                 name: "acceptItems",

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

@@ -42,7 +42,6 @@ const treeSelectProps = reactive({
     filterable: true,
     filterable: true,
     clearable: true,
     clearable: true,
     checkStrictly: true,
     checkStrictly: true,
-    clearable: true,
     placeholder: "请选择所属企业",
     placeholder: "请选择所属企业",
     props: { label: "name", value: "name" }
     props: { label: "name", value: "name" }
 })
 })
@@ -78,10 +77,10 @@ const columns = reactive([
 ])
 ])
 
 
 // 获取组织树
 // 获取组织树
-const getRemoteData = async () => {
+const getSelectTreeData = async () => {
     const res = await API.system.project.dept();
     const res = await API.system.project.dept();
     treeSelectProps.data = XEUtils.toArrayTree(XEUtils.filter(res, item => item.firmNature !== "学校"), { parentKey: "pid", key: "deptId" });
     treeSelectProps.data = XEUtils.toArrayTree(XEUtils.filter(res, item => item.firmNature !== "学校"), { parentKey: "pid", key: "deptId" });
-};
+}
 
 
 // 显示隐藏 筛选表单
 // 显示隐藏 筛选表单
 const xGridTable = ref();
 const xGridTable = ref();
@@ -126,5 +125,5 @@ const table_del = ({ fpiId }) => {
     });
     });
 }
 }
 
 
-getRemoteData();
+getSelectTreeData();
 </script>
 </script>

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

@@ -22,15 +22,12 @@
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="模拟年份" prop="targetYear">
-                        <el-date-picker v-model="form.targetYear" type="year" :clearable="false" value-format="YYYY" format="YYYY" placeholder="请选择模拟年份" />
-                    </el-form-item>
-                </el-col>
-                <el-col :md="12" :xs="24">
-                    <el-form-item label="模拟月份">
-                        <el-select v-model="form.targetMonth" filterable clearable placeholder="请选择模拟月份">
-                            <el-option v-for="item in 12" :key="item" :label="item + '月'" :value="XEUtils.padStart(item, 2, '0')"></el-option>
-                        </el-select>
+                    <el-form-item class="range-item" label="模拟时间范围" prop="targetBeginTime">
+                        <el-date-picker v-model="form.targetBeginTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="new Date()" placeholder="请选择模拟数据开始时间" @change="dateChange"></el-date-picker>
+                        <span>至</span>
+                        <el-input v-model="form.targetEndTime" readonly placeholder="模拟数据结束时间">
+                            <template #prefix><sc-iconify icon="ep:clock"></sc-iconify></template>
+                        </el-input>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
@@ -63,7 +60,7 @@
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
                     <el-form-item label="抓拍时间" prop="sourceTime">
                     <el-form-item label="抓拍时间" prop="sourceTime">
-                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="refreshTable"></el-date-picker>
+                        <el-date-picker v-model="form.sourceTime" type="datetimerange" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" :shortcuts="shortcuts" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" @change="dateChange(), refreshTable()"></el-date-picker>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
             </el-row>
             </el-row>
@@ -96,10 +93,11 @@ const isSaving = ref(false);
 const shortcuts = rangeShortcuts();
 const shortcuts = rangeShortcuts();
 const form = ref({
 const form = ref({
     targetProjectId: TOOL.data.get("PROJECT_ID"),
     targetProjectId: TOOL.data.get("PROJECT_ID"),
-    targetYear: null,
-    targetMonth: null,
+    targetBeginTime: null,
+    targetEndTime: null,
     isCover: false,
     isCover: false,
     source: "other",
     source: "other",
+    orderBy: "captureTime_desc",
     sourceProjectId: null,
     sourceProjectId: null,
     sourceProjectIdNot: 1,
     sourceProjectIdNot: 1,
     sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
     sourceTime: [moment().startOf("month").format("YYYY-MM-DD HH:mm:ss"), moment().format("YYYY-MM-DD HH:mm:ss")]
@@ -107,7 +105,7 @@ const form = ref({
 
 
 const rules = reactive({
 const rules = reactive({
     targetProjectId: [{ required: true, message: "请选择模拟项目" }],
     targetProjectId: [{ required: true, message: "请选择模拟项目" }],
-    targetYear: [{ required: true, message: "请选择模拟年份" }],
+    targetBeginTime: [{ required: true, message: "请选择模拟数据开始时间" }],
     isCover: [{ required: true }],
     isCover: [{ required: true }],
     source: [{ required: true }],
     source: [{ required: true }],
     sourceProjectId: [{ required: true, message: "请选择数据源项目" }],
     sourceProjectId: [{ required: true, message: "请选择数据源项目" }],
@@ -121,6 +119,7 @@ const tableOptions = reactive({
     toolbarConfig: { enabled: true, print: false, zoom: false },
     toolbarConfig: { enabled: true, print: false, zoom: false },
     formConfig: { enabled: false, data: form },
     formConfig: { enabled: false, data: form },
     paramsColums: computed(() => [
     paramsColums: computed(() => [
+        { column: "orderBy" },
         { column: "fpiId", field: form.value.source == "template" ? "sourceProjectIdNot" : "sourceProjectId"  },
         { column: "fpiId", field: form.value.source == "template" ? "sourceProjectIdNot" : "sourceProjectId"  },
         form.value.source == "template" ? {} : { column: "fpiIdNot", field: "sourceProjectIdNot" },
         form.value.source == "template" ? {} : { column: "fpiIdNot", field: "sourceProjectIdNot" },
         { column: "captureTimeBegin", field: "sourceTime[0]" },
         { column: "captureTimeBegin", field: "sourceTime[0]" },
@@ -145,6 +144,11 @@ const open = () => {
     TOOL.data.get("PROJECT_ID") && dataTimeRange();
     TOOL.data.get("PROJECT_ID") && dataTimeRange();
 }
 }
 
 
+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 formRef = ref();
 const submit = key => {
 const submit = key => {
     let validateField = XEUtils.keys(rules);
     let validateField = XEUtils.keys(rules);
@@ -157,7 +161,7 @@ const submit = key => {
         if (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");
+            const data = XEUtils.omit(form.value, "source", "orderBy", "sourceProjectIdNot", "sourceTime");
             XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));
             XEUtils.set(data, "sourceBeginTime", XEUtils.first(form.value.sourceTime));
             XEUtils.set(data, "sourceEndTime", XEUtils.last(form.value.sourceTime));
             XEUtils.set(data, "sourceEndTime", XEUtils.last(form.value.sourceTime));
             form.value.source == "template" && XEUtils.set(data, "sourceProjectId", 1);
             form.value.source == "template" && XEUtils.set(data, "sourceProjectId", 1);
@@ -184,5 +188,10 @@ defineExpose({
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
 .el-form {margin-top: 5px;padding-right: var(--el-message-close-size, 16px);}
 .el-form-item .el-radio-group {flex-wrap: nowrap;}
 .el-form-item .el-radio-group {flex-wrap: nowrap;}
+.el-form .range-item :deep(.el-form-item__content) {
+    .el-date-editor, .el-input {flex: 1;}
+    .el-date-editor + span {padding: 0 10px;}
+}
+
 .el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
 .el-form :deep(.el-main) {padding-right: 0;padding-bottom: 0;}
 </style>
 </style>

+ 80 - 53
src/views/system/milestone/components/record/detail.vue

@@ -1,57 +1,64 @@
 <template>
 <template>
-    <el-dialog v-model="visible" :title="titleMap[mode]" :width="480" :close-on-click-modal="false" @closed="$emit('closed')">
+    <el-dialog v-model="visible" :title="titleMap[mode]" :width="860" :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
             <el-row>
             <el-row>
-                <el-col :md="12" :xs="24">
-                    <el-form-item v-if="props.projectId != 1" label="所属项目" prop="projectId">
-                        <el-select v-model="form.projectId" filterable placeholder="请选择所属项目" @change="form.mountedId = null">
+                <el-col v-if="props.projectId != 1" :md="12" :xs="24">
+                    <el-form-item label="所属项目" prop="projectId">
+                        <el-select v-model="form.projectId" filterable :disabled="mode != 'add'" placeholder="请选择所属项目">
                             <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
                             <el-option v-for="item in $TOOL.data.get('PROJECT')" :key="item.fpiId" :label="item.projectName" :value="item.fpiId"></el-option>
                         </el-select>
                         </el-select>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="所属节点" prop="parentId">
-                        <el-tree-select v-model="form.parentId" v-bind="props.treeSelectProps"></el-tree-select>
+                    <el-form-item label="上级节点" prop="parentId">
+                        <el-tree-select v-model="form.parentId" v-bind="treeSelectProps" :disabled="mode != 'add'"></el-tree-select>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="节点序号" prop="nodeSeq">
-                        <el-input-number v-model="form.nodeSeq" :min="0" :controls="false" placeholder="请输入节点序号"></el-input-number>
+                    <el-form-item label="节点名称" prop="nodeName">
+                        <el-input v-model="form.nodeName" placeholder="请输入节点名称"></el-input>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
-                    <el-form-item label="节点名称" prop="nodeName">
-                        <el-input v-model="form.nodeName" placeholder="请输入节点名称"></el-input>
+                    <el-form-item label="节点排序" prop="nodeSeq">
+                        <el-input-number v-model="form.nodeSeq" :min="0" step-strictly :controls="false" placeholder="请输入节点排序"></el-input-number>
+                    </el-form-item>
+                </el-col>
+                <el-col :md="12" :xs="24">
+                    <el-form-item label="节点状态" prop="nodeStatus">
+                        <el-select v-model="form.nodeStatus" filterable placeholder="请选择节点状态">
+                            <el-option v-for="(label, key) in nodeStatusDic" :key="key" :label="label" :value="key"></el-option>
+                        </el-select>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
                     <el-form-item label="计划开始时间">
                     <el-form-item label="计划开始时间">
-                        <el-date-picker v-model="form.planStartTime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择计划开始时间"></el-date-picker>
+                        <el-date-picker v-model="form.planStartTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择计划开始时间"></el-date-picker>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
                     <el-form-item label="计划完成时间">
                     <el-form-item label="计划完成时间">
-                        <el-date-picker v-model="form.planFinishTime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择计划完成时间"></el-date-picker>
+                        <el-date-picker v-model="form.planFinishTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择计划完成时间"></el-date-picker>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
                     <el-form-item label="实际开始时间">
                     <el-form-item label="实际开始时间">
-                        <el-date-picker v-model="form.createTime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择实际开始时间"></el-date-picker>
+                        <el-date-picker v-model="form.actualStartTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择实际开始时间"></el-date-picker>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
                 <el-col :md="12" :xs="24">
                 <el-col :md="12" :xs="24">
                     <el-form-item label="实际完成时间">
                     <el-form-item label="实际完成时间">
-                        <el-date-picker v-model="form.createTime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择实际完成时间"></el-date-picker>
+                        <el-date-picker v-model="form.actualFinishTime" type="datetime" :clearable="false" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择实际完成时间"></el-date-picker>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
-                <el-col :md="12" :xs="24">
-                    <el-form-item label="节点状态" prop="nodeStatus">
-                        <el-select v-model="form.nodeStatus" filterable placeholder="请选择节点状态">
-                            <el-option v-for="(label, key) in nodeStatusDic" :key="key" :label="label" :value="key"></el-option>
+                <el-col :xs="24">
+                    <el-form-item label="可选任务">
+                        <el-select v-model="form.todoList" filterable clearable multiple collapse-tags collapse-tags-tooltip :max-collapse-tags="2" placeholder="请选择待办任务">
+                            <el-option v-for="item in filterTodoL" :key="item.id" :label="item.content" :value="item.id"></el-option>
                         </el-select>
                         </el-select>
                     </el-form-item>
                     </el-form-item>
                 </el-col>
                 </el-col>
-                <el-col :md="12" :xs="24">
+                <el-col :xs="24">
                     <el-form-item label="描述内容">
                     <el-form-item label="描述内容">
                         <el-input v-model="form.remark" type="textarea" :rows="4" placeholder="请输入描述内容"></el-input>
                         <el-input v-model="form.remark" type="textarea" :rows="4" placeholder="请输入描述内容"></el-input>
                     </el-form-item>
                     </el-form-item>
@@ -74,8 +81,7 @@ import { nodeStatusDic } from "@/views/system/milestone/main";
 
 
 const $emit = defineEmits(["success", "closed"]);
 const $emit = defineEmits(["success", "closed"]);
 const props = defineProps({
 const props = defineProps({
-    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") },
-    treeSelectProps: { type: Object, default: () => {} }
+    projectId: { type: Number, default: TOOL.data.get("PROJECT_ID") }
 });
 });
 
 
 const visible = ref(false);
 const visible = ref(false);
@@ -83,64 +89,80 @@ const isSaving = ref(false);
 
 
 const mode = ref("add");
 const mode = ref("add");
 const titleMap = reactive({
 const titleMap = reactive({
-    add: "数据录入",
+    add: "新增",
+    add_child: "新增子节点",
     edit: "修改"
     edit: "修改"
-});
+})
 
 
 const form = ref({
 const form = ref({
     id: null,
     id: null,
-    projectId: props.projectId,
-    parentId: 0,
+    projectId: null,
+    parentId: "0",
     nodeName: null,
     nodeName: null,
-    nodeType: null,
     nodeSeq: null,
     nodeSeq: null,
+    nodeStatus: null,
     planStartTime: null,
     planStartTime: null,
     planFinishTime: null,
     planFinishTime: null,
     actualStartTime: null,
     actualStartTime: null,
     actualFinishTime: null,
     actualFinishTime: null,
-    nodeStatus: null,
+    todoList: [],
     remark: null
     remark: null
-});
+})
 const rules = reactive({
 const rules = reactive({
     projectId: [{ required: true, message: "请选择所属项目" }],
     projectId: [{ required: true, message: "请选择所属项目" }],
-    parentId: [{ required: true, message: "请选择所属节点" }],
-    nodeSeq: [{ required: true, message: "请输入节点序号" }],
+    parentId: [{ required: true, message: "请选择上级节点" }],
     nodeName: [{ required: true, message: "请输入节点名称" }],
     nodeName: [{ required: true, message: "请输入节点名称" }],
-    nodeStatus: [{ required: true, message: "请选择节点状态" }],
+    nodeSeq: [{ required: true, message: "请输入节点排序" }],
+    nodeStatus: [{ required: true, message: "请选择节点状态" }]
+})
+
+const treeSelectProps = reactive({
+    popperClass: "vxe-table-slot--popper",
+    data: [{ id: "0", nodeName: "根目录" }],
+    filterable: true,
+    checkStrictly: true,
+    placeholder: "请选择上级节点",
+    props: { label: "nodeName", value: "id" }
 })
 })
 
 
-const open = () => visible.value = true;
+const todoTasks = ref([]);
+const filterTodoL = computed(() => mode.value == "edit" ? todoTasks.value : XEUtils.filter(todoTasks.value, item => item.planId == "-"));
+const getTreeData = async projectId => {
+    const planRes = await API.system.milestone.all({ orderBy: "nodeSeq_asc", projectId });
+    treeSelectProps.data = XEUtils.toArrayTree([{ id: "0", nodeName: "根目录" }, ...planRes], { parentKey: "parentId", key: "id" });
+    
+    const taskRes = await API.system.todoTask.all({ orderBy: "createTime_asc", projectId });
+    todoTasks.value = taskRes;
+}
+
+watch(() => form.value.projectId, val => val && getTreeData(val));
+const open = data => {
+    visible.value = true;
+    form.value.projectId = !XEUtils.isEmpty(data) ? data.projectId : props.projectId;
+
+    if (!XEUtils.isEmpty(data)) {
+        mode.value = "add_child";
+        form.value.parentId = data.id;
+    }
+}
 
 
 const setData = data => {
 const setData = data => {
     open();
     open();
     mode.value = "edit";
     mode.value = "edit";
-    XEUtils.objectEach(XEUtils.omit(form.value, "fileData"), (_, key) => {
-        // if (key == "features") {
-        //     const features = XEUtils.toStringJSON(XEUtils.get(data, key, "{}"));
-        //     const path = XEUtils.get(features, "bigImage.image", "");
-            
-        //     path && XEUtils.set(form.value, "fileData", { path });
-        //     XEUtils.set(form.value, key, features);
-        // } else XEUtils.set(form.value, key, XEUtils.get(data, key));
-    });
+    XEUtils.objectEach(form.value, (_, key) => XEUtils.set(form.value, key, XEUtils.get(data, key)));
 }
 }
 
 
 const formRef = ref();
 const formRef = ref();
 const submit = () => {
 const submit = () => {
     formRef.value.validate(valid => {
     formRef.value.validate(valid => {
         if (valid) {
         if (valid) {
-            // const data = XEUtils.omit(form.value, "features", "fileData");
-            // const features = XEUtils.clone(form.value.features);
-            // XEUtils.set(features, "bigImage.image", form.value.fileData.path);
-            // XEUtils.set(data, "features", XEUtils.toJSONString(features));
-
-            // isSaving.value = true;
-            // API.aihazard.record[mode.value](data).then(() => {
-            //     isSaving.value = false;
-            //     ElMessage.success("操作成功");
-            //     visible.value = false;
-            //     $emit("success", mode.value);
-            // }).catch(() => isSaving.value = false);
+            isSaving.value = true;
+            API.system.milestone[XEUtils.first(mode.value.split("_"))](form.value).then(() => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", mode.value);
+            }).catch(() => isSaving.value = false);
         } else {
         } else {
             return false;
             return false;
         }
         }
@@ -157,4 +179,9 @@ defineExpose({
 .el-form {
 .el-form {
     padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
     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-select :deep(.el-select__selection) .el-select__selected-item .el-tag{max-width: 204px !important;}
 </style>
 </style>

+ 33 - 37
src/views/system/milestone/components/record/index.vue

@@ -1,24 +1,22 @@
 <template>
 <template>
-    <scTable ref="xGridTable" batchDel :apiObj="$API.aihazard.record" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns">
-        <template #tree_select>
-            <el-tree-select v-model="formConfig.data.projectFirmName" v-bind="treeSelectProps"></el-tree-select>
-        </template>
-        
-        <template #action="{ row }">
-            <el-button type="primary" link @click="table_edit(row)">
+    <scTable ref="xGridTable" :apiObj="hasAPI && $API.system.milestone" apiKey="all" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" :pagerConfig="pagerConfig" :options="options">
+        <template #action="{ $grid, rowid }">
+            <el-button type="primary" link @click="table_add($grid.getData(XEUtils.findIndexOf($grid.getData(), item => item.id == rowid)))">
+                <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>新增子节点
+            </el-button>
+            <el-button type="primary" link @click="table_edit($grid.getData(XEUtils.findIndexOf($grid.getData(), item => item.id == rowid)))">
                 <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
                 <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
             </el-button>
             </el-button>
-            <el-button type="primary" link @click="table_del(row)">
+            <el-button type="primary" link @click="table_del($grid.getData(XEUtils.findIndexOf($grid.getData(), item => item.id == rowid)))">
                 <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
                 <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
             </el-button>
             </el-button>
         </template>
         </template>
     </scTable>
     </scTable>
 
 
-    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" :treeSelectProps="treeSelectProps" @success="refreshTable" @closed="dialog = false"></record-detail>
+    <record-detail v-if="dialog" ref="recordRef" :projectId="props.isTemp ? 1 : TOOL.data.get('PROJECT_ID')" @success="refreshTable" @closed="dialog = false"></record-detail>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import moment from "moment";
 import XEUtils from "xe-utils";
 import XEUtils from "xe-utils";
 import API from "@/api";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import TOOL from "@/utils/tool";
@@ -31,6 +29,7 @@ const props = defineProps({
     isTemp: { type: Boolean, default: false }
     isTemp: { type: Boolean, default: false }
 })
 })
 const visible = computed(() => !props.isTemp);
 const visible = computed(() => !props.isTemp);
+const hasAPI = computed(() => props.isTemp || formConfig.data.projectId);
 
 
 const proConfig = reactive({
 const proConfig = reactive({
     visible,
     visible,
@@ -42,18 +41,8 @@ const proConfig = reactive({
     }
     }
 })
 })
 
 
-const treeSelectProps = reactive({
-    popperClass: "vxe-table-slot--popper",
-    data: [{ id: 0, nodeName: "根目录" }],
-    filterable: true,
-    clearable: true,
-    checkStrictly: true,
-    clearable: true,
-    placeholder: "请选择所属节点",
-    props: { label: "nodeName", value: "id" }
-})
-
 const selectConfig = reactive({
 const selectConfig = reactive({
+    span: 5,
     options: objectToArray(nodeStatusDic),
     options: objectToArray(nodeStatusDic),
     events: {
     events: {
         change: data => XEUtils.merge(formConfig.data, data)
         change: data => XEUtils.merge(formConfig.data, data)
@@ -78,6 +67,7 @@ const toolbarConfig = reactive({
 
 
 const formConfig = reactive({
 const formConfig = reactive({
     data: {
     data: {
+        orderBy: "nodeSeq_asc",
         projectId: TOOL.data.get("PROJECT_ID"),
         projectId: TOOL.data.get("PROJECT_ID"),
         projectIdNot: 1
         projectIdNot: 1
     },
     },
@@ -86,11 +76,12 @@ const formConfig = reactive({
         mapFormItemInput("nodeName", "节点名称"),
         mapFormItemInput("nodeName", "节点名称"),
         mapFormItemSelect("nodeStatus", "节点状态", selectConfig),
         mapFormItemSelect("nodeStatus", "节点状态", selectConfig),
         mapFormItemDatePicker("planTime", "计划完成时间", datetimerangeConfig),
         mapFormItemDatePicker("planTime", "计划完成时间", datetimerangeConfig),
-        mapFormItemDatePicker("actualTime", "实际完成时间", datetimerangeConfig),
+        mapFormItemDatePicker("actualTime", "实际完成时间", datetimerangeConfig)
     ]
     ]
 })
 })
 
 
 const paramsColums = reactive([
 const paramsColums = reactive([
+    { column: "orderBy" },
     { column: "projectId", field: visible.value ? "" : "projectIdNot" },
     { column: "projectId", field: visible.value ? "" : "projectIdNot" },
     visible.value ? { column: "projectIdNot" } : {},
     visible.value ? { column: "projectIdNot" } : {},
     { column: "nodeName" },
     { column: "nodeName" },
@@ -102,32 +93,37 @@ const paramsColums = reactive([
 ])
 ])
 
 
 const columns = reactive([
 const columns = reactive([
-    { type: "seq", fixed: "left", width: 60 },
+    { type: "seq", title: "序号", fixed: "left", width: 70 },
+    { type: "html", field: "nodeName", title: "节点名称", minWidth: 200, sortable: true, treeNode: true },
     { 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: "projectName", title: "项目名称", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId == row.projectId), "projectName") },
-    
-    { type: "html", field: "createTime", title: "抓拍时间", minWidth: 160, sortable: true },
-    // { type: "html", field: "recordType", title: "识别结果", minWidth: 100, sortable: true, formatter: ({ cellValue }) => XEUtils.get(aiTypeDic, cellValue, cellValue) },
-    { title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+    { type: "html", field: "planStartTime", title: "计划开始时间", minWidth: 160, sortable: true },
+    { type: "html", field: "planFinishTime", title: "计划完成时间", minWidth: 160, sortable: true },
+    { type: "html", field: "actualStartTime", title: "实际开始时间", minWidth: 160, sortable: true },
+    { type: "html", field: "actualFinishTime", title: "实际完成时间", minWidth: 160, sortable: true },
+    { field: "nodeStatus", title: "节点状态", fixed: "right", minWidth: 100, align: "center", editRender: { name: "$cell-tag" }, formatter: ({ cellValue }) => XEUtils.get(nodeStatusDic, cellValue, cellValue) },
+    { title: "操作", fixed: "right", width: 230, align: "center", slots: { default: "action" } }
 ])
 ])
 
 
+const options = reactive({
+    treeConfig: { transform: true, expandAll: true },
+    checkboxConfig: {},
+    pagerConfig: { enabled: false }
+})
+
 // 显示隐藏 筛选表单
 // 显示隐藏 筛选表单
 const xGridTable = ref();
 const xGridTable = ref();
+const table_expand = () => xGridTable.value.toggleTableExpand();
 const refreshTable = (mode = "add") => {
 const refreshTable = (mode = "add") => {
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.reloadColumn(columns);
     xGridTable.value.searchData(mode);
     xGridTable.value.searchData(mode);
 }
 }
 
 
-watch(() => xGridTable.value?.getTableData(), val => {
-    console.log(val)
-    // treeSelectProps.data = XEUtils.toArrayTree(res, { parentKey: "parentId", key: "id" });
-})
-
 const recordRef = ref();
 const recordRef = ref();
 const dialog = ref(false);
 const dialog = ref(false);
 
 
-const table_add = () => {
+const table_add = (row = {}) => {
     dialog.value = true;
     dialog.value = true;
-    nextTick(() => recordRef.value?.open());
+    nextTick(() => recordRef.value?.open(XEUtils.pick(row, "id", "projectId")));
 }
 }
 
 
 const table_edit = row => {
 const table_edit = row => {
@@ -136,12 +132,12 @@ const table_edit = row => {
 }
 }
 
 
 const table_del = ({ id }) => {
 const table_del = ({ id }) => {
-    ElMessageBox.confirm("是否确认删除该监测记录?", "删除警告", {
+    ElMessageBox.confirm("是否确认删除该里程碑节点?", "删除警告", {
         type: "warning",
         type: "warning",
         confirmButtonText: "确定",
         confirmButtonText: "确定",
         cancelButtonText: "取消"
         cancelButtonText: "取消"
     }).then(() => {
     }).then(() => {
-        API.aihazard.record.del({ id }).then(() => {
+        API.system.milestone.del({ id }).then(() => {
             ElMessage.success("操作成功");
             ElMessage.success("操作成功");
             refreshTable();
             refreshTable();
         });
         });
@@ -150,6 +146,6 @@ const table_del = ({ id }) => {
 
 
 defineExpose({
 defineExpose({
     table_add,
     table_add,
-    refreshTable
+    table_expand
 })
 })
 </script>
 </script>

+ 2 - 1
src/views/system/milestone/components/template.vue

@@ -7,6 +7,7 @@ import dataTable from "./record";
 
 
 const tableRef = ref();
 const tableRef = ref();
 defineExpose({
 defineExpose({
-    table_add: () => tableRef.value.table_add()
+    table_add: () => tableRef.value.table_add(),
+    table_expand: () => tableRef.value.table_expand()
 })
 })
 </script>
 </script>

+ 2 - 1
src/views/system/milestone/index.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
 	<el-container class="is-vertical">
 	<el-container class="is-vertical">
-        <sc-page-header addText="新增节点" @add="table_add">
+        <sc-page-header addText="新增节点" @add="table_add" @expand="table_expand">
             <template #extra-right>
             <template #extra-right>
                 <el-button v-if="activeName == 'record'" type="primary" @click="table_copy">
                 <el-button v-if="activeName == 'record'" type="primary" @click="table_copy">
                     <template #icon><sc-iconify icon="ant-design:copy-outlined"></sc-iconify></template>一键复制
                     <template #icon><sc-iconify icon="ant-design:copy-outlined"></sc-iconify></template>一键复制
@@ -25,6 +25,7 @@ const activeName = ref("record");
 const componentRef = ref();
 const componentRef = ref();
 
 
 const table_add = () => componentRef.value.table_add();
 const table_add = () => componentRef.value.table_add();
+const table_expand = () => componentRef.value.table_expand();
 
 
 const table_copy = () => {};
 const table_copy = () => {};
 </script>
 </script>

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

+ 93 - 0
src/views/system/todoTask/components/record/index.vue

@@ -0,0 +1,93 @@
+<template>
+    <scTable ref="xGridTable" :apiObj="$API.system.todoTask" :formConfig="formConfig" :paramsColums="paramsColums" :toolbarConfig="toolbarConfig" :columns="columns" :pagerConfig="pagerConfig">
+        <template #action="{ row }">
+            <el-button type="primary" link @click="table_bind(row)">
+                <template #icon><sc-iconify icon="mdi:relation-only-one-to-one"></sc-iconify></template>关联里程碑
+            </el-button>
+        </template>
+    </scTable>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { objectToArray } from "@/utils/basicDic";
+import { nodeStatusDic } from "@/views/system/milestone/main";
+
+const props = defineProps({
+    isTemp: { type: Boolean, default: false }
+})
+const visible = computed(() => !props.isTemp);
+
+const proConfig = reactive({
+    visible,
+    storageKey: "PROJECT",
+    props: { clearable: false },
+    resetValue: TOOL.data.get("PROJECT_ID"),
+    optionProps: { label: "projectName", value: "fpiId" },
+    events: {
+        change: data => XEUtils.assign(formConfig.data, { ...data, mountedId: null })
+    }
+})
+
+const selectConfig = reactive({
+    visible,
+    options: objectToArray(nodeStatusDic),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
+const datetimerangeConfig = reactive({
+    visible,
+    span: 7,
+    resetValue: () => [],
+    props: {
+        type: "datetimerange",
+        startPlaceholder: "开始时间",
+        endPlaceholder: "结束时间",
+        format: "YYYY-MM-DD HH:mm"
+    }
+})
+
+const toolbarConfig = reactive({
+    enabled: true,
+    print: false
+})
+
+const formConfig = reactive({
+    data: {
+        orderBy: "createTime_asc",
+        projectId: TOOL.data.get("PROJECT_ID"),
+        projectIdNot: 1
+    },
+    items: [
+        mapFormItemSelect("projectId", "所属项目", proConfig),
+        mapFormItemInput("content", "任务内容"),
+        mapFormItemSelect("status", "任务状态", selectConfig),
+        mapFormItemDatePicker("finishTime", "完成时间", datetimerangeConfig),
+    ]
+})
+
+const paramsColums = reactive([
+    { column: "orderBy" },
+    { column: "projectId", field: visible.value ? "" : "projectIdNot" },
+    visible.value ? { column: "projectIdNot" } : {},
+    { column: "content" },
+    { column: "status" },
+    { column: "finishTimeBegin", field: "finishTime[0]" },
+    { column: "finishTimeEnd", field: "finishTime[1]" }
+])
+
+const columns = reactive([
+    { type: "seq", width: 60 },
+    { visible, type: "html", field: "projectName", title: "项目名称", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(TOOL.data.get("PROJECT"), item => item.fpiId == row.projectId), "projectName") },
+    { type: "html", field: "content", title: "任务内容", minWidth: 200, sortable: true },
+    { visible, field: "status", title: "任务状态", minWidth: 100, align: "center", editRender: { name: "$cell-tag" }, formatter: ({ cellValue }) => XEUtils.get(nodeStatusDic, cellValue, cellValue) },
+    { visible, type: "html", field: "finishTime", title: "完成时间", minWidth: 160, sortable: true },
+    { visible, type: "html", field: "deadlineTime", title: "截止时间", minWidth: 160, sortable: true },
+    { title: "操作", fixed: "right", width: 230, align: "center", slots: { default: "action" } }
+])
+</script>

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

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

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

@@ -0,0 +1,34 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header>
+            <template #extra-right>
+                <el-button type="primary" @click="table_copy">
+                    <template #icon><sc-iconify icon="ant-design:copy-outlined"></sc-iconify></template>一键复制
+                </el-button>
+            </template>
+        </sc-page-header>
+
+        <el-tabs v-model="activeName">
+            <el-tab-pane v-for="(label, key) in workerStates" :key="key" :label="label" :name="key"></el-tab-pane>
+        </el-tabs>
+
+        <component ref="componentRef" :is="allcomp[activeName]" />
+	</el-container>
+</template>
+
+<script setup>
+import { workerStates } from "@/views/system/milestone/main";
+import allcomp from "./components";
+
+const activeName = ref("record");
+
+const componentRef = ref();
+
+const table_add = () => componentRef.value.table_add();
+
+const table_copy = () => {};
+</script>
+
+<style lang="scss" scoped>
+.el-tabs {padding: 0 12px;background: #fff;}
+</style>