From e756e3cc5ddd0ddb42c5f00d6bb3eee76ba73e6f Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期一, 09 三月 2026 14:14:24 +0800
Subject: [PATCH] feat(project-type): 优化附件管理和界面交互

---
 src/views/projectManagement/projectType/index.vue                        |  160 ++++++++++++++++++++++++++-------------
 src/views/projectManagement/projectType/components/ProjectTypeDialog.vue |   69 +++++++++--------
 2 files changed, 143 insertions(+), 86 deletions(-)

diff --git a/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue b/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
index d73f464..2888516 100644
--- a/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
+++ b/src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
@@ -32,16 +32,19 @@
         <div class="info-item">
           <span class="item-label">闄勪欢</span>
           <el-upload
+            v-if="isEdit"
             :action="uploadUrl"
             :headers="uploadHeaders"
             :on-success="handleUploadSuccess"
             :on-remove="handleRemove"
-            :file-list="form.attachmentList"
+            v-model:file-list="uploadFileList"
+            :limit="3"
             name="files"
             multiple
           >
             <el-button type="primary">涓婁紶闄勪欢</el-button>
           </el-upload>
+          <span v-else class="text-gray-400 text-sm">璇峰厛淇濆瓨鍚庡啀涓婁紶闄勪欢</span>
         </div>
       </div>
 
@@ -130,6 +133,11 @@
               <el-input v-model="scope.row.workContent" placeholder="璇疯緭鍏�" />
             </template>
           </el-table-column>
+          <el-table-column label="鎿嶄綔" min-width="150">
+            <template #default="scope">
+              <el-button type="danger" size="mini" @click="removeStep(scope.$index)">鍒犻櫎</el-button>
+            </template>
+          </el-table-column>
         </el-table>
 
         <div class="add-row-btn" @click="addStep">
@@ -151,7 +159,7 @@
 import { ref, watch, onMounted, nextTick } from 'vue';
 import { Plus, QuestionFilled } from '@element-plus/icons-vue';
 import { userListNoPageByTenantId } from '@/api/system/user';
-import { ElMessage } from 'element-plus';
+import { ElMessage, ElMessageBox } from 'element-plus';
 import { getToken } from '@/utils/auth';
 import Sortable from 'sortablejs';
 
@@ -166,6 +174,7 @@
 const visible = ref(false);
 const formRef = ref(null);
 const userOptions = ref([]);
+const isEdit = ref(false);
 const uploadHeaders = { Authorization: "Bearer " + getToken() };
 // 涓婁紶鍦板潃
 const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload";
@@ -176,9 +185,9 @@
   name: '',
   description: '',
   attachmentIds: [],
-  attachmentList: [],
   savePlanNodeList: []
 });
+const uploadFileList = ref([]);
 
 const rules = {
   name: [{ required: true, message: '璇疯緭鍏ュ悕绉�', trigger: 'blur' }]
@@ -190,19 +199,16 @@
   if (val) {
     if (props.data) {
       // 缂栬緫妯″紡 - 鍥炴樉鏁版嵁
+      isEdit.value = true;
       form.value = {
         id: props.data.id,
         name: props.data.name,
         description: props.data.description,
-        attachmentIds: [],
-        attachmentList: props.data.attachmentList || [],
+        attachmentIds: Array.isArray(props.data.attachmentIds)
+          ? props.data.attachmentIds
+          : (props.data.attachmentList || []).map(f => f.id).filter(Boolean),
         savePlanNodeList: []
       };
-      
-      // 鍥炴樉闄勪欢ID
-      if (form.value.attachmentList && form.value.attachmentList.length > 0) {
-        form.value.attachmentIds = form.value.attachmentList.map(item => item.id);
-      }
       
       // 鍥炴樉姝ラ鑺傜偣
       if (props.data.planNodeList && props.data.planNodeList.length > 0) {
@@ -222,6 +228,7 @@
       }
     } else {
       // 鏂板妯″紡
+      isEdit.value = false;
       resetForm();
     }
     // 鍒濆鍖栨嫋鎷�
@@ -273,9 +280,9 @@
     name: '',
     description: '',
     attachmentIds: [],
-    attachmentList: [],
     savePlanNodeList: [createDefaultNode()]
   };
+  uploadFileList.value = [];
   if (formRef.value) {
     formRef.value.resetFields();
   }
@@ -304,19 +311,14 @@
 /** 澶勭悊鏂囦欢涓婁紶鎴愬姛 */
 function handleUploadSuccess(response, file, fileList) {
   if (response.code === 200) {
-    ElMessage.success('涓婁紶鎴愬姛');
-    // 鍋囪鍚庣杩斿洖鐨勬暟鎹粨鏋勪腑鍖呭惈鏂囦欢ID鍜孶RL绛変俊鎭�
-    // 杩欓噷闇�瑕佹牴鎹疄闄呮帴鍙h繑鍥炵粨鏋勮繘琛岃皟鏁�
-    // 閫氬父 response.data 鍖呭惈鏂囦欢淇℃伅
     const newFile = response.data;
-    if (newFile && newFile.id) {
-       form.value.attachmentIds.push(newFile.id);
-       form.value.attachmentList.push({
-         name: file.name,
-         url: newFile.url,
-         id: newFile.id
-       });
-    }
+    const list = Array.isArray(newFile) ? newFile : [newFile];
+    list.forEach(element => {
+      const id = element?.id;
+      if (id && !form.value.attachmentIds.includes(id)) {
+        form.value.attachmentIds.push(id);
+      }
+    });
   } else {
     ElMessage.error(response.msg || '涓婁紶澶辫触');
   }
@@ -324,15 +326,9 @@
 
 /** 澶勭悊鏂囦欢绉婚櫎 */
 function handleRemove(file) {
-  const index = form.value.attachmentList.findIndex(item => item.name === file.name);
-  if (index !== -1) {
-    const fileId = form.value.attachmentList[index].id;
-    form.value.attachmentList.splice(index, 1);
-    const idIndex = form.value.attachmentIds.indexOf(fileId);
-    if (idIndex !== -1) {
-      form.value.attachmentIds.splice(idIndex, 1);
-    }
-  }
+  const removedId = file?.id || file?.response?.data?.id;
+  if (!removedId) return;
+  form.value.attachmentIds = form.value.attachmentIds.filter(id => id !== removedId);
 }
 
 /** 娣诲姞姝ラ */
@@ -346,7 +342,14 @@
     ElMessage.warning('鑷冲皯淇濈暀涓�涓楠�');
     return;
   }
-  form.value.savePlanNodeList.splice(index, 1);
+  
+  ElMessageBox.confirm('鏄惁纭鍒犻櫎璇ユ楠わ紵', '绯荤粺鎻愮ず', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning'
+  }).then(() => {
+    form.value.savePlanNodeList.splice(index, 1);
+  }).catch(() => {});
 }
 
 /** 绉诲姩姝ラ */
diff --git a/src/views/projectManagement/projectType/index.vue b/src/views/projectManagement/projectType/index.vue
index 34344c9..c2ea441 100644
--- a/src/views/projectManagement/projectType/index.vue
+++ b/src/views/projectManagement/projectType/index.vue
@@ -9,54 +9,64 @@
     </div>
 
     <div class="content-section" v-loading="loading">
-      <div v-for="item in projectTypeList" :key="item.id" class="project-type-card">
-        <div class="card-header">
-          <div class="info-group">
-            <span class="label">绫诲瀷鍚嶇О:</span>
-            <span class="value">{{ item.name }}</span>
-          </div>
-          <div class="info-group">
-            <span class="label">澶囨敞:</span>
-            <span class="value">{{ item.description || '--' }}</span>
-          </div>
-          <div class="info-group">
-            <span class="label">闄勪欢:</span>
-            <div class="attachment-info" v-if="item.attachment" @click="handleExpand(item)">
-              <el-icon class="file-icon"><Document /></el-icon>
-              <span class="file-name">{{ item.attachment.name }}</span>
-              <el-icon class="download-icon" @click.stop="handleDownload(item.attachment)"><Download /></el-icon>
-              <span class="expand-link">{{ item.expanded ? '鏀惰捣' : '灞曞紑' }}</span>
-              <el-icon class="arrow-icon" :class="{ 'is-reverse': item.expanded }"><ArrowDown /></el-icon>
+      <div class="card-list-scroll">
+        <div v-for="item in projectTypeList" :key="item.id" class="project-type-card">
+          <div class="card-header">
+            <div class="info-group">
+              <span class="label">绫诲瀷鍚嶇О:</span>
+              <span class="value">{{ item.name }}</span>
             </div>
-            <span class="value" v-else>--</span>
-          </div>
-          <div class="actions">
-            <el-button link type="primary" @click="handleUpdate(item)">缂栬緫</el-button>
-            <el-button link type="primary" @click="handleCopy(item)">澶嶅埗</el-button>
-            <el-button link type="danger" @click="handleDelete(item)">鍒犻櫎</el-button>
-          </div>
-        </div>
-
-        <el-collapse-transition>
-          <div v-show="item.expanded" class="expanded-content">
-            <div class="attachment-list">
-              <div class="attachment-item">
-                <el-icon><Document /></el-icon>
-                <span>{{ item.attachment?.name }}</span>
-                <el-button link type="primary" size="small" @click="handleDownload(item.attachment)">涓嬭浇</el-button>
+            <div class="info-group">
+              <span class="label">澶囨敞:</span>
+              <span class="value">{{ item.description || '--' }}</span>
+            </div>
+            <div class="info-group">
+              <span class="label">闄勪欢:</span>
+              <div
+                class="attachment-info"
+                v-if="(item.attachmentList?.length || 0) > 0"
+                @click="handleExpand(item)"
+              >
+                {{ item.attachmentList[0]?.fileName || item.attachmentList[0]?.name }}
+                <span v-if="item.attachmentList.length > 1" class="file-count">
+                  +{{ item.attachmentList.length - 1 }}
+                </span>
+                <span class="expand-link">{{ item.expanded ? '鏀惰捣' : '灞曞紑' }}</span>
               </div>
+              <span class="value" v-else>--</span>
+            </div>
+            <div class="actions">
+              <el-button link type="primary" @click="handleUpdate(item)">缂栬緫</el-button>
+              <el-button link type="primary" @click="handleCopy(item)">澶嶅埗</el-button>
+              <el-button link type="danger" @click="handleDelete(item)">鍒犻櫎</el-button>
             </div>
           </div>
-        </el-collapse-transition>
 
-        <div class="card-body">
-          <div class="workflow-container">
-            <div v-for="(step, index) in item.steps" :key="index" class="workflow-step">
-              <div class="step-main">
-                <div class="step-circle">{{ index + 1 }}</div>
-                <div v-if="index < item.steps.length - 1" class="step-line"></div>
+          <el-collapse-transition>
+            <div v-show="item.expanded" class="expanded-content">
+              <div class="attachment-list">
+                <div
+                  v-for="att in (item.attachmentList || [])"
+                  :key="att.id || att.url || att.fileUrl || att.fileName || att.name"
+                  class="attachment-item"
+                >
+                  <el-icon><Document /></el-icon>
+                  <span class="attachment-name">{{ att.fileName || att.name || '--' }}</span>
+                  <el-button link type="primary" size="small" @click="handleDownload(att)">涓嬭浇</el-button>
+                </div>
               </div>
-              <div class="step-label">{{ step.label }}</div>
+            </div>
+          </el-collapse-transition>
+
+          <div class="card-body">
+            <div class="workflow-container">
+              <div v-for="(step, index) in item.steps" :key="index" class="workflow-step">
+                <div class="step-main">
+                  <div class="step-circle">{{ index + 1 }}</div>
+                  <div v-if="index < item.steps.length - 1" class="step-line"></div>
+                </div>
+                <div class="step-label">{{ step.label }}</div>
+              </div>
             </div>
           </div>
         </div>
@@ -122,6 +132,7 @@
       projectTypeList.value = res.data.records.map(item => ({
         ...item,
         expanded: false,
+        attachmentList: Array.isArray(item.attachmentList) ? item.attachmentList : [],
         // 鍚庣杩斿洖鐨勮妭鐐瑰垪琛ㄥ彲鑳芥槸 planNodeList 鎴� savePlanNodeList
         steps: (item.planNodeList || item.savePlanNodeList || []).map(node => ({
           label: node.name
@@ -140,7 +151,7 @@
           id: 1,
           name: 'A椤圭洰',
           description: '',
-          attachment: { name: 'precaution...' },
+          attachmentList: [{ id: 1, fileName: 'precaution...' }],
           steps: [{ label: '绔嬮」' }, { label: '璁捐' }, { label: '閲囪喘' }, { label: '鐢熶骇' }, { label: '鍑鸿揣' }],
           expanded: false
         }
@@ -208,7 +219,9 @@
   const copyData = {
     name: row.name + " - 鍓湰",
     description: row.description,
-    attachmentIds: row.attachmentIds || [],
+    attachmentIds: Array.isArray(row.attachmentIds)
+      ? row.attachmentIds
+      : (row.attachmentList || []).map(x => x.id).filter(Boolean),
     savePlanNodeList: (row.planNodeList || row.savePlanNodeList || []).map(node => ({
       name: node.name,
       leaderId: node.leaderId,
@@ -238,8 +251,12 @@
 
 /** 涓嬭浇闄勪欢 */
 function handleDownload(attachment) {
-  // 瀹炵幇涓嬭浇閫昏緫
-  ElMessage.info("寮�濮嬩笅杞�: " + (attachment.name || "鏂囦欢"));
+  const url = attachment?.url || attachment?.fileUrl || attachment?.tempPath || attachment?.fileName;
+  if (!url) {
+    ElMessage.warning("鏈壘鍒板彲涓嬭浇鐨勬枃浠跺湴鍧�");
+    return;
+  }
+  proxy.$download.name(url);
 }
 
 onMounted(() => {
@@ -250,11 +267,15 @@
 <style scoped lang="scss">
 .app-container {
   background-color: #f5f7fa;
-  min-height: calc(100vh - 84px);
+  height: calc(100vh - 84px);
   padding: 20px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
 }
 
 .header-section {
+  flex-shrink: 0;
   display: flex;
   justify-content: space-between;
   align-items: center;
@@ -288,12 +309,33 @@
   }
 }
 
+.content-section{
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+}
+
+.card-list-scroll {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+}
+
 .project-type-card {
   background-color: #fff;
   border-radius: 8px;
   padding: 20px;
   margin-bottom: 20px;
   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+  border: 1px solid #ebeef5;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
 
   .card-header {
     display: flex;
@@ -335,6 +377,12 @@
           overflow: hidden;
           text-overflow: ellipsis;
           white-space: nowrap;
+        }
+
+        .file-count {
+          margin-right: 8px;
+          font-size: 12px;
+          color: #909399;
         }
 
         .download-icon {
@@ -379,6 +427,13 @@
       .el-icon {
         font-size: 16px;
         color: #409eff;
+      }
+
+      .attachment-name {
+        flex: 1;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
       }
     }
   }
@@ -443,9 +498,13 @@
 }
 
 .pagination-container {
+  flex-shrink: 0;
   display: flex;
   justify-content: flex-end;
-  margin-top: 20px;
+  padding: 10px 20px;
+  background-color: #fff;
+  border-top: 1px solid #ebeef5;
+  margin-top: 0;
 }
 
 .step-config-item {
@@ -453,10 +512,5 @@
   align-items: center;
   gap: 10px;
   margin-bottom: 10px;
-}
-.content-section{
-    background: #fff;
-    border-radius: 8px;
-    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
 }
 </style>

--
Gitblit v1.9.3