| src/views/projectManagement/projectType/components/ProjectTypeDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/projectManagement/projectType/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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和URL等信息 // 这里需要根据实际接口返回结构进行调整 // 通常 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; } ElMessageBox.confirm('是否确认删除该步骤?', '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { form.value.savePlanNodeList.splice(index, 1); }).catch(() => {}); } /** 移动步骤 */ src/views/projectManagement/projectType/index.vue
@@ -9,6 +9,7 @@ </div> <div class="content-section" v-loading="loading"> <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"> @@ -21,12 +22,16 @@ </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> <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> <el-icon class="arrow-icon" :class="{ 'is-reverse': item.expanded }"><ArrowDown /></el-icon> </div> <span class="value" v-else>--</span> </div> @@ -40,10 +45,14 @@ <el-collapse-transition> <div v-show="item.expanded" class="expanded-content"> <div class="attachment-list"> <div class="attachment-item"> <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>{{ item.attachment?.name }}</span> <el-button link type="primary" size="small" @click="handleDownload(item.attachment)">下载</el-button> <span class="attachment-name">{{ att.fileName || att.name || '--' }}</span> <el-button link type="primary" size="small" @click="handleDownload(att)">下载</el-button> </div> </div> </div> @@ -57,6 +66,7 @@ <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>