Pārlūkot izejas kodu

应用项上传附件

zhuangyunsheng 2 mēneši atpakaļ
vecāks
revīzija
38eb389dcf

+ 2 - 2
.env.development

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

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

@@ -38,6 +38,10 @@ export default {
         parking: async function (data, config = {}) {
 			return await http.post(`${this.url}/uploadParking`, data, config);
 		},
+
+        accept: async function (data, config = {}) {
+			return await http.post(`${this.url}/uploadAccept`, data, config);
+		},
         
 		rm: async function (fileName) {
 			return await http.post(`${this.url}/remove`, { fileName });

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

@@ -98,7 +98,7 @@ export default {
         },
 
         error(message) {
-            this.$notify.error({ title: "上传文件未成功", message });
+            message && this.$notify.error({ title: "上传文件未成功", message });
         },
 
         beforeRemove({ id, name, path }) {
@@ -138,8 +138,8 @@ export default {
                 }
             }).then(res => {
                 if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type })
-                else param.onError(res.message || "未知错误");
-            }).catch(err => param.onError(err));
+                else param.onError();
+            }).catch(() => param.onError());
         }
     }
 }

+ 17 - 10
src/components/scUpload/fileViewer.vue

@@ -15,7 +15,7 @@
             </div>
             
             <div v-loading="loading" class="sc-file-viewer__content">
-                <component :is="`vue_office_${fileType.split('.')[0]}`" :src="'/api/folder/' + filePath" :options="options" @rendered="loading = false" @error="loading = false"></component>
+                <component :is="`vue_office_${fileTypes[fileType].split('.')[0]}`" :src="commonKeyDic[commonKey] + filePath" :options="options" @rendered="loading = false" @error="loading = false"></component>
             </div>
         </el-dialog>
         
@@ -25,7 +25,7 @@
 
 <script>
 import { VxeUI } from "vxe-pc-ui";
-import { fileTypes, officeOptions } from "./main";
+import { commonKeyDic, fileTypes, officeOptions } from "./main";
 
 import vue_office_docx from "@vue-office/docx";
 import vue_office_excel from "@vue-office/excel";
@@ -38,6 +38,9 @@ import "@vue-office/excel/lib/index.css";
 
 export default {
     emits: ["closed"],
+    props: {
+        commonKey: { type: String, default: "folder" }
+    },
     components: {
         vue_office_docx,
         vue_office_excel,
@@ -47,6 +50,8 @@ export default {
 
     data() {
         return {
+            commonKeyDic, fileTypes,
+
             visible: false,
             loading: false,
             fileName: null,
@@ -60,31 +65,33 @@ export default {
 
     methods: {
         init(uploadFile) {
+            if (uploadFile.status != "success") return;
+            
             this.fileName = uploadFile.name;
             this.filePath = uploadFile.path;
-            this.fileType = fileTypes[uploadFile["mineType"]] || uploadFile["mineType"];
-            if (!fileTypes[uploadFile["mineType"]]) {
+            this.fileType = uploadFile.mineType;
+            if (!fileTypes[this.fileType]) {
                 ElMessage.warning("当前只支持预览 .png/.jpg/.map4/.avi/.txt/.docx/.pdf/.xlsx/.xls 格式文件, 文件已下载");
                 this.downloadFile();
             } else {
-                if (this.fileType == "image") {
+                if (fileTypes[this.fileType] == "image") {
                     VxeUI.previewImage({
                         showDownloadButton: true,
-                        urlList: ["/api/folder/" + uploadFile.path],
+                        urlList: [commonKeyDic[this.commonKey] + uploadFile.path],
                         downloadMethod: () => this.downloadFile()
                     })
-                } else if (this.fileType == "video") this.showVideoViewer = true;
+                } else if (fileTypes[this.fileType] == "video") this.showVideoViewer = true;
                 else {
                     this.loading = true;
                     this.visible = true;
-                    this.options = officeOptions[this.fileType.split(".")[0]] || {};
-                    if (this.fileType.includes(".")) this.options.xls = this.fileType.split(".")[1] == "xls";
+                    this.options = officeOptions[fileTypes[this.fileType].split(".")[0]] || {};
+                    if (fileTypes[this.fileType].includes(".")) this.options.xls = fileTypes[this.fileType].split(".")[1] == "xls";
                 }
             }
         },
 
         downloadFile() {
-            this.$API.common.folder.download(this.filePath).then(res => {
+            this.$API.common[this.commonKey].download(this.filePath).then(res => {
                 const a = document.createElement("a");
                 const blob = new Blob([res], { type: this.fileType });
                 a.download = this.fileName;

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

@@ -6,7 +6,7 @@
 			</div>
 			<el-image class="image" :src="file.tempFile" fit="cover"></el-image>
 		</div>
-		<div v-if="file && file.status=='success'" class="sc-upload__img">
+		<div v-if="file && file.status == 'success'" class="sc-upload__img">
             <sc-image v-if="isImage(file.mineType)" ref="imageRef" class="image" :fileName="file.name" :path="file.path"></sc-image>
 			<sc-video v-if="isVideo(file.mineType)" :src="'/api/folder/' + file.path" showMask @play="videoPlay"></sc-video>
 			
@@ -47,7 +47,7 @@
 			</slot>
 		</el-upload>
 		<span style="display:none!important"><el-input v-model="value"></el-input></span>
-		<el-dialog v-model="cropperDialogVisible" title="剪裁" :width="580" draggable destroy-on-close @closed="cropperClosed">
+		<el-dialog v-model="cropperDialogVisible" title="剪裁" width="580" destroy-on-close @closed="cropperClosed">
 			<sc-cropper ref="cropper" :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio"></sc-cropper>
 			<template #footer>
 				<el-button auto-insert-space @click="cropperDialogVisible = false">取消</el-button>
@@ -77,7 +77,7 @@ export default {
         onSuccess: { type: Function, default: () => { return true } },
         cropper: { type: Boolean, default: false },
         compress: { type: Number, default: 1 },
-        aspectRatio: {type: Number, default: NaN }
+        aspectRatio: { type: Number, default: NaN }
     },
 
     data() {
@@ -146,7 +146,7 @@ export default {
         },
 
         handleRemove() {
-            const file = JSON.parse(this.value)
+            const file = JSON.parse(this.value);
             this.$confirm(`是否移除 ${file.name}? 此操作不可逆!`, "提示", {
                 type: "warning",
                 confirmButtonText: "移除"
@@ -223,7 +223,7 @@ export default {
 
         error(message) {
             this.$nextTick(() => this.clearFiles());
-            this.$notify.error({ title: "上传文件未成功", message });
+            message && this.$notify.error({ title: "上传文件未成功", message });
         },
 
         request(param) {
@@ -237,8 +237,8 @@ export default {
                 }
             }).then(res => {
                 if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type });
-                else param.onError(res.message || "未知错误");
-            }).catch(err => param.onError(err));
+                else param.onError();
+            }).catch(() => param.onError());
         },
 
         videoPlay() {

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

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

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

@@ -138,7 +138,7 @@ export default {
         },
 
         error(message) {
-            this.$notify.error({ title: "上传文件未成功", message });
+            message && this.$notify.error({ title: "上传文件未成功", message });
         },
 
         beforeRemove({ id, name }) {
@@ -178,8 +178,8 @@ export default {
                 }
             }).then(res => {
                 if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type })
-                else param.onError(res.message || "未知错误");
-            }).catch(err => param.onError(err));
+                else param.onError();
+            }).catch(() => param.onError());
         },
 
         videoPlay(file) {

+ 70 - 46
src/components/scUploadMinio/file.vue

@@ -6,6 +6,7 @@
 			action=""
 			:accept="accept"
 			:limit="limit"
+			:drag="drag"
 			:multiple="multiple"
 			:disabled="disabled"
 			show-file-list
@@ -17,37 +18,46 @@
 			:on-preview="handlePreview"
 			:on-exceed="handleExceed">
 			<slot>
-				<el-button type="primary" :disabled="disabled">Click to upload</el-button>
+                <div v-if="drag" class="file-empty">
+                    <el-icon><el-icon-upload-filled /></el-icon>
+                    <h4>将文件拖到此处,或<el-text type="primary">点击上传</el-text></h4>
+                </div>
+				<el-button v-else type="primary" :disabled="disabled">Click to upload</el-button>
 			</slot>
 			<template #tip>
-				<div v-if="tip" class="el-upload__tip">{{ tip }}</div>
+				<div v-if="tip" :class="['el-upload__tip', tipRequired && 'el-upload__tip-required']">{{ tip }}</div>
 			</template>
 		</el-upload>
 		<span style="display: none!important;"><el-input v-model="value"></el-input></span>
 	</div>
 
-    <file-viewer v-if="showViewer" ref="fileViewer" @closed="showViewer = false"></file-viewer>
+    <file-viewer v-if="showViewer" ref="fileViewer" commonKey="minio" @closed="showViewer = false"></file-viewer>
 </template>
 
 <script>
+import XEUtils from "xe-utils";
+
 export default {
     props: {
         modelValue: { type: Array, default: () => [] },
+        apiKey: { type: String, default: "accept" },
         tip: { type: String, default: "" },
+        tipRequired: { type: Boolean, default: false },
         accept: { type: String, default: "" },
         maxSize: { type: Number, default: 50 },
         limit: { type: Number, default: 0 },
+        drag: { type: Boolean, default: false },
         multiple: { type: Boolean, default: true },
         disabled: { type: Boolean, default: false },
         hideAdd: { type: Boolean, default: false },
-        onSuccess: { type: Function, default: () => { return true } }
+        onSuccess: { type: Function, default: () => { return true } },
+        params: { type: Object, default: () => {} }
     },
 
     data() {
         return {
             value: "",
             defaultFileList: [],
-            
             showViewer: false
         }
     },
@@ -98,23 +108,17 @@ export default {
         },
 
         error(message) {
-            this.$notify.error({ title: "上传文件未成功", message });
+            message && this.$notify.error({ title: "上传文件未成功", message });
         },
 
-        beforeRemove({ id, name, path }) {
-            return this.$confirm(`是否移除 ${name}? 此操作不可逆!`, "提示", {
-                type: "warning",
-                confirmButtonText: "移除"
-            }).then(() => {
-                const entityID = id || path;
-                this.$API.common.folder.rm(entityID).then(res => {
-                    if (res.code == 200) return true;
-                    else return false;
-                }).catch(() => {
-                    return false;
-                });
-            }).catch(() => {
-                return false;
+        beforeRemove(file) {
+            return new Promise((resolve, reject) => {
+                this.$confirm(`是否移除 ${file.name}? 此操作不可逆!`, "提示", {
+                    type: "warning",
+                    confirmButtonText: "移除"
+                }).then(() => {
+                    this.$API.common.minio.rm(file.path).then(res => resolve()).catch(() => reject());
+                }).catch(() => reject());
             });
         },
 
@@ -130,8 +134,9 @@ export default {
         request(param) {
             const data = new FormData();
             data.append(param.filename, param.file);
-            
-            this.$API.common.folder.up(data, {
+            XEUtils.objectEach(this.params, (value, key) => data.append(key, value));
+
+            this.$API.common.minio[this.apiKey](data, {
                 onUploadProgress: e => {
                     const percent = parseInt(((e.loaded / e.total) * 100) | 0, 10);
                     param.onProgress({ percent });
@@ -142,9 +147,9 @@ export default {
                     return data;
                 }]
             }).then(res => {
-                if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type })
-                else param.onError(res.message || "未知错误");
-            }).catch(err => param.onError(err));
+                if (res.code == 200) param.onSuccess({ path: res.expands.file, fileName: param.file.name, mineType: param.file.type });
+                else param.onError();
+            }).catch(() => param.onError());
         }
     }
 }
@@ -152,38 +157,57 @@ export default {
 
 <style lang="scss" scoped>
 .el-form-item.is-error .sc-upload-file:deep(.el-upload-dragger) {
-  border-color: var(--el-color-danger);
+    border-color: var(--el-color-danger);
 }
 
 .sc-upload-file {
-  width: 100%;
-
-  :deep(.el-upload-list__item) {
-    transition: none !important;
-  }
+    width: 100%;
 
-  .el-upload-hide-add {
-    :deep(.el-upload) {
-      display: none;
+    :deep(.el-upload-list__item) {
+        transition: none !important;
     }
 
-    :deep(.el-upload-list) {
-      margin-top: 0;
-
-      .el-upload-list__item {
-        &:hover {
-          background-color: transparent;
+    .el-upload-hide-add {
+        :deep(.el-upload) {
+            display: none;
         }
 
-        .el-upload-list__item-info {
-          width: 100%;
+        :deep(.el-upload-list) {
+            margin-top: 0;
+
+            .el-upload-list__item {
+                &:hover {
+                    background-color: transparent;
+                }
+
+                .el-upload-list__item-info {
+                    width: 100%;
+                }
+
+                .el-upload-list__item-status-label {
+                    display: none;
+                }
+            }
         }
+    }
 
-        .el-upload-list__item-status-label {
-          display: none;
+    .file-empty i {font-size: 28px;}
+    .file-empty h4 {
+        font-size: 13px;
+        font-weight: normal;
+        color: #8c939d;
+        
+        .el-text {font-size: inherit;}
+    }
+    
+    .el-upload__tip {color: #999;}
+    .el-upload__tip-required {
+        position: relative;
+        &::before {
+            content: "*";
+            margin-right: 4px;
+            color: var(--el-color-danger);
         }
-      }
     }
-  }
 }
 </style>

+ 10 - 15
src/components/scUploadMinio/index.vue

@@ -24,8 +24,8 @@
 			:disabled="disabled"
 			:show-file-list="false"
 			:accept="accept"
-			:drag="drag"
 			:limit="1"
+			:drag="drag"
 			:http-request="request"
 			:on-change="change"
 			:before-upload="before"
@@ -46,16 +46,14 @@
 			</slot>
 		</el-upload>
 		<span style="display:none!important"><el-input v-model="value"></el-input></span>
-		<el-dialog v-model="cropperDialogVisible" title="剪裁" :width="580" draggable destroy-on-close @closed="cropperClosed">
+		<el-dialog v-model="cropperDialogVisible" title="剪裁" width="580" draggable destroy-on-close @closed="cropperClosed">
 			<sc-cropper ref="cropper" :src="cropperFile.tempCropperFile" :compress="compress" :aspectRatio="aspectRatio"></sc-cropper>
 			<template #footer>
-				<el-button @click="cropperDialogVisible = false" >取 消</el-button>
-				<el-button type="primary" @click="cropperSave">确 定</el-button>
+				<el-button auto-insert-space @click="cropperDialogVisible = false">取消</el-button>
+				<el-button type="primary" auto-insert-space @click="cropperSave">确定</el-button>
 			</template>
 		</el-dialog>
 	</div>
-
-    <file-viewer v-if="showViewer" ref="fileViewer" @closed="showViewer = false"></file-viewer>
 </template>
 
 <script>
@@ -78,7 +76,7 @@ export default {
         onSuccess: { type: Function, default: () => { return true } },
         cropper: { type: Boolean, default: false },
         compress: { type: Number, default: 1 },
-        aspectRatio: {type: Number, default: NaN }
+        aspectRatio: { type: Number, default: NaN }
     },
 
     data() {
@@ -90,9 +88,7 @@ export default {
                 height: this.height + "px"
             },
             cropperDialogVisible: false,
-            cropperFile: null,
-
-            showViewer: false
+            cropperFile: null
         }
     },
 
@@ -139,8 +135,7 @@ export default {
         },
 
         handleRemove() {
-            const file = JSON.parse(this.value)
-            
+            const file = JSON.parse(this.value);
             this.$confirm(`是否移除 ${this.fileName}? 此操作不可逆!`, "提示", {
                 type: "warning",
                 confirmButtonText: "移除"
@@ -206,7 +201,7 @@ export default {
 
         error(message) {
             this.$nextTick(() => this.clearFiles());
-            this.$notify.error({ title: "上传文件未成功", message });
+            message && this.$notify.error({ title: "上传文件未成功", message });
         },
 
         request(param) {
@@ -220,8 +215,8 @@ export default {
                 }
             }).then(res => {
                 if (res.code == 200) param.onSuccess({ path: res.expands.file });
-                else param.onError(res.message || "未知错误");
-            }).catch(err => param.onError(err));
+                else param.onError();
+            }).catch(() => param.onError());
         },
 
         handleDownload() {

+ 5 - 8
src/layout/components/sideM.vue

@@ -15,22 +15,19 @@
             </el-main>
 		</el-container>
 	</el-drawer>
-
 </template>
 
 <script>
-import NavMenu from "./NavMenu.vue";
+import NavMenu from "./NavMenu";
 
 export default {
-    components: {
-        NavMenu
-    },
+    components: { NavMenu },
     data() {
         return {
             nav: false
         }
     },
-    computed:{
+    computed: {
         menu() {
             return (this.$TOOL.data.get("MENU") || []).filter(item => item.path !== this.$CONFIG.DASHBOARD_URL && !item.meta?.hidden);
         }
@@ -74,7 +71,7 @@ export default {
                 };
                 //return false不加的话可能导致黏连,就是拖到一个地方时div粘在鼠标上不下来,相当于onmouseup失效
                 return false;
-            };
+            }
         }
     }
 }
@@ -87,5 +84,5 @@ export default {
 	.mobile-nav .el-header {background: var(--el-color-primary);border: 0;}
 	.mobile-nav .el-main {padding:0;}
 	.mobile-nav .logo-bar {display: flex;align-items: center;font-weight: bold;font-size: 20px;color: #fff;}
-	.mobile-nav .logo-bar img {width: 30px;margin-right: 10px;}
+	.mobile-nav .logo-bar img {width: 35px;height: 35px;margin-right: 10px;}
 </style>

+ 5 - 3
src/layout/index.vue

@@ -46,7 +46,7 @@
             <div class="aminui-main" id="aminui-main">
                 <router-view v-slot="{ Component }">
                     <keep-alive :include="this.$store.state.keepAlive.keepLiveRoute">
-                        <component :is="Component" :key="`${$route.fullPath}.${componentKey}`" v-if="$store.state.keepAlive.routeShow"/>
+                        <component :is="Component" :key="`${$route.fullPath}.${componentKey}`" v-if="$store.state.keepAlive.routeShow" />
                     </keep-alive>
                 </router-view>
             </div>
@@ -110,8 +110,10 @@ export default {
 
         storageChange({ key, newValue }) {
             if (key == "PROJECT") {
-                this.$store.commit("SET_projects", XEUtils.toStringJSON(newValue).content);
-                this.componentKey++;
+                if (XEUtils.toJSONString(this.$store.state.project.projects) != XEUtils.toJSONString(XEUtils.toStringJSON(newValue).content)) {
+                    this.$store.commit("SET_projects", XEUtils.toStringJSON(newValue).content);
+                    this.componentKey++;
+                }
             }
         },
 

+ 1 - 1
src/router/index.js

@@ -124,7 +124,7 @@ function loadComponent(component) {
 router.getProject = async fpiId => {
     let projectRes = await api.project.info.all();
     tool.data.set("PROJECT", projectRes);
-    (!tool.data.get("PROJECT_ID") || fpiId && fpiId == tool.data.get("PROJECT_ID")) && tool.data.set("PROJECT_ID", null);
+    (!tool.data.get("PROJECT_ID") || (fpiId && fpiId == tool.data.get("PROJECT_ID"))) && tool.data.set("PROJECT_ID", null);
 }
 
 router.getGates = async (path, start = 0, gates = []) => {

+ 2 - 0
src/style/fix.scss

@@ -39,6 +39,8 @@
 .el-popconfirm__main {margin: 14px 0;}
 .el-card .el-card__header {font-size: 16px;font-weight: bold;padding: 0 24px;line-height: 55px;border-color: var(--el-border-color-lighter);}
 .el-dialog .el-dialog__title {font-size: 16px;font-weight: bold;}
+.el-drawer-splitter {width: fit-content;}
+.el-drawer-splitter .el-splitter-panel {flex-basis: auto !important;}
 .el-drawer__header>:first-child {font-size: 16px;font-weight: bold;}
 .el-tree.menu .el-tree-node__content {height:36px;}
 .el-tree.menu .el-tree-node__content .el-tree-node__label .icon {margin-right: 5px;}

+ 1 - 2
src/views/milestone/plan/components/record/index.vue

@@ -44,7 +44,6 @@ const proConfig = reactive({
 })
 
 const selectConfig = reactive({
-    span: 5,
     options: objectToArray(nodeStatusDic),
     events: {
         change: data => XEUtils.merge(formConfig.data, data)
@@ -77,6 +76,7 @@ const formConfig = reactive({
         mapFormItemSelect("projectId", "所属项目", proConfig),
         mapFormItemInput("nodeName", "节点名称"),
         mapFormItemSelect("nodeStatus", "节点状态", selectConfig),
+        {},
         mapFormItemDatePicker("planTime", "计划完成时间", datetimerangeConfig),
         mapFormItemDatePicker("actualTime", "实际完成时间", datetimerangeConfig)
     ]
@@ -98,7 +98,6 @@ const columns = reactive([
     { visible: props.hideHandler, type: "checkbox", fixed: "left", width: 40 },
     { 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") },
     { 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 },

+ 38 - 0
src/views/project/info/bindItem/file.vue

@@ -0,0 +1,38 @@
+<template>
+    <el-dialog v-model="visible" :title="`${form.acceptItem}-验收资料`" width="860" :close-on-click-modal="false" @closed="$emit('closed', form)">
+        <sc-upload-file v-model="form.fileList" tip="重复的文件名会导致覆盖,请使用新名称" tipRequired drag :params="XEUtils.omit(form, 'fileList')"></sc-upload-file>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+import API from "@/api";
+import scUploadFile from "@/components/scUploadMinio/file";
+
+const $emit = defineEmits(["closed"]);
+const visible = ref(true);
+
+const form = ref({
+    acceptProjectId: null,
+    projectId: null,
+    projectName: null,
+    acceptItem: null,
+    itemEnglishType: null,
+    fileList: []
+})
+
+const setData = data => {
+    visible.value = true;
+    XEUtils.objectEach(form.value, (_, key) => XEUtils.set(form.value, key, XEUtils.get(data, key)));
+}
+
+defineExpose({
+    setData
+})
+</script>
+
+<style scoped>
+.el-form {
+    padding-right: calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px));
+}
+</style>

+ 33 - 10
src/views/project/info/items.vue

@@ -34,12 +34,7 @@
                 </template>
 
                 <template #checked_replace="{ row }">
-                    <el-checkbox v-if="row.acceptItemType.includes('必选项')" v-model="row.replaceIds" true-value="1" :false-value="0"></el-checkbox>&nbsp;&nbsp;
-                    <!-- <div v-if="row.acceptItemType.includes('必选项')" class="replace-content">
-                        <el-select v-model="row.replaceIds" multiple collapse-tags collapse-tags-tooltip placeholder="选择替换推广项">
-                            <el-option v-for="item in XEUtils.filter(acceptItems, acc => acc.acceptItemType == '推广项')" :key="item.id" :label="item.acceptItem" :value="item.id"></el-option>
-                        </el-select>
-                    </div> -->
+                    <el-checkbox v-if="row.acceptItemType.includes('必选项')" v-model="row.replaceIds" true-value="1" false-value="0"></el-checkbox>
                 </template>
 
                 <template #date_picker_begin="{ row }">
@@ -49,6 +44,13 @@
                 <template #date_picker_end="{ row }">
                     <el-date-picker v-model="row.endTime" :disabled-date="date => disabledDate(date, row, 'endTime')" value-format="YYYY-MM-DD 23:59:59" placeholder="选择结束时间"></el-date-picker>
                 </template>
+
+                <template #folder_content="{ row }">
+                    <el-button v-if="row.fileList.length" type="primary" link @click="table_show(row)">{{ row.fileList.length }} 个资料</el-button>
+                    <el-button v-else type="primary" link @click="table_show(row)">
+                        <template #icon><sc-iconify icon="ant-design:cloud-upload-outlined"></sc-iconify></template>上传
+                    </el-button>
+                </template>
             </scTable>
         </el-form>
 
@@ -57,6 +59,8 @@
             <el-button auto-insert-space @click="visible = false">取消</el-button>
         </template>
     </el-dialog>
+
+    <accept-item-file v-if="dialog" ref="fileRef" @closed="table_closed"></accept-item-file>
 </template>
 
 <script setup>
@@ -64,6 +68,7 @@ import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
 import { categoryDic, statisticTemp } from "@/views/project/acceptItems/main";
+import acceptItemFile from "./file";
 
 const $emit = defineEmits(["success", "closed"]);
 const visible = ref(false);
@@ -101,8 +106,9 @@ const columns = reactive([
     { field: "acceptItemType", title: "应用项级别", minWidth: 160 },
     { field: "checked", title: "选择", width: 80, slots: { default: "checked_content" } },
     { visible: computed(() => itemsLevel.value == "推广项" || itemsLevel.value == "提升项"), field: "replaceIds", title: "应用项替换", minWidth: 100, slots: { default: "checked_replace" } },
-    { field: "beginTime", title: "数据开始时间", width: 160, slots: { default: "date_picker_begin" } },
-    { field: "endTime", title: "数据结束时间", width: 160, slots: { default: "date_picker_end" } },
+    { field: "beginTime", title: "数据开始时间", minWidth: 160, slots: { default: "date_picker_begin" } },
+    { field: "endTime", title: "数据结束时间", minWidth: 160, slots: { default: "date_picker_end" } },
+    { title: "验收资料", width: 120, slots: { default: "folder_content" } }
 ])
 
 const disabledDate = (date, row, field) => {
@@ -142,7 +148,8 @@ const getAcceptItems = (projectId) => {
             } else {
                 form.value.starLevel = infoRes.starLevel;
                 const selectInfo = XEUtils.find(XEUtils.get(infoRes, "data", []), info => info.itemId == item.id);
-                selectInfo && XEUtils.merge(item, { checked: true, ...XEUtils.omit(XEUtils.pick(selectInfo, "beginTime", "endTime", "replaceIds"), val => XEUtils.isEmpty(val)) })
+                selectInfo && XEUtils.merge(item, { checked: true, acceptProjectId: selectInfo.id, ...XEUtils.omit(XEUtils.pick(selectInfo, "beginTime", "endTime", "replaceIds"), val => XEUtils.isEmpty(val)) })
+                item.fileList = XEUtils.map(selectInfo.fileList, file => ({ id: file.id, mineType: file.contentType, name: XEUtils.last(file.fileUrl.split("/")), path: file.fileUrl }))
             }
         });
         
@@ -158,7 +165,23 @@ const changeLevel = e => {
         itemsLevel.value = "基础项";
         refreshTable();
     }
-};
+}
+
+const dialog = ref(false);
+const fileRef = ref();
+const table_show = row => {
+    dialog.value = true;
+    nextTick(() => fileRef.value?.setData({ ...row, ...XEUtils.pick(form.value, "projectId", "projectName") }));
+}
+
+const table_closed = row => {
+    dialog.value = false;
+    XEUtils.set(
+        XEUtils.find(xGridTable.value.getTableData().tableData, item => item.acceptProjectId == row.acceptProjectId),
+        "fileList",
+        row.fileList
+    )
+}
 
 const formRef = ref();
 const submit = () => {

+ 1 - 1
src/views/project/info/detail.vue

@@ -540,7 +540,7 @@ const submit = () => {
                 isSaving.value = false;
                 ElMessage.success("操作成功");
                 visible.value = false;
-                $emit("success", mode.value);
+                $emit("success");
             }).catch(() => isSaving.value = false);
         } else {
             return false;

+ 18 - 10
src/views/project/info/index.vue

@@ -32,7 +32,7 @@ import TOOL from "@/utils/tool";
 import { mapFormItemInput, mapFormItemSelect } from "@/components/scTable/helper";
 import { typeDic, statusDic } from "./main";
 import projectDetail from "./detail";
-import acceptItemDetail from "./items";
+import acceptItemDetail from "./bindItem/index";
 
 const router = useRouter();
 
@@ -46,6 +46,13 @@ const treeSelectProps = reactive({
     props: { label: "name", value: "name" }
 })
 
+const selectConfig = reactive({
+    options: typeDic.map(label => ({ label, value: label })),
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+})
+
 const toolbarConfig = reactive({
     enabled: true,
     print: false
@@ -57,14 +64,18 @@ const formConfig = reactive({
     },
     items: [
         mapFormItemInput("projectNameLike", "项目名称"),
-        mapFormItemInput("projectFirmName", "所属企业", { slots: { default: "tree_select" } })
+        mapFormItemInput("projectFirmName", "所属企业", { slots: { default: "tree_select" } }),
+        mapFormItemSelect("projectType", "项目类型", selectConfig),
+        mapFormItemSelect("projectStatus", "项目状态", { ...selectConfig, options: statusDic.map(label => ({ label, value: label })) })
     ]
-});
+})
 
 const paramsColums = reactive([
     { column: "projectStatusNot" },
     { column: "projectNameLike" },
-    { column: "projectFirmName" }
+    { column: "projectFirmName" },
+    { column: "projectType" },
+    { column: "projectStatus" }
 ])
 
 const columns = reactive([
@@ -84,11 +95,8 @@ const getSelectTreeData = async () => {
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add", fpiId) => {
-    xGridTable.value.reloadColumn(columns);
-    xGridTable.value.searchData(mode);
-    router.getProject(fpiId);
-}
+const refreshTable = fpiId => router.getProject(fpiId);
+
 
 const projectRef = ref();
 const acceptItemRef = ref();
@@ -120,7 +128,7 @@ const table_del = ({ fpiId }) => {
     }).then(() => {
         API.project.info.del({ fpiId }).then(() => {
             ElMessage.success("操作成功");
-            refreshTable("add", fpiId);
+            refreshTable(fpiId);
         });
     });
 }