ZN
5 天以前 e3fc89bb84fc32e0048cbd53ee96e26f01a8881f
feat(项目类型): 优化项目类型对话框的附件上传和步骤排序功能

- 将附件上传改为支持多文件上传并显示文件列表,支持文件删除功能
- 使用拖拽排序替代原有的上移/下移按钮,提升用户体验
- 在编辑模式下正确回显附件列表和步骤数据
- 提交前自动按顺序更新步骤的排序字段
已修改1个文件
161 ■■■■ 文件已修改
src/views/projectManagement/projectType/components/ProjectTypeDialog.vue 161 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/projectManagement/projectType/components/ProjectTypeDialog.vue
@@ -32,10 +32,13 @@
        <div class="info-item">
          <span class="item-label">附件</span>
          <el-upload
            action="#"
            :auto-upload="false"
            :show-file-list="false"
            @change="handleFileChange"
            :action="uploadUrl"
            :headers="uploadHeaders"
            :on-success="handleUploadSuccess"
            :on-remove="handleRemove"
            :file-list="form.attachmentList"
            name="files"
            multiple
          >
            <el-button type="primary">上传附件</el-button>
          </el-upload>
@@ -43,11 +46,20 @@
      </div>
      <!-- 步骤配置表格 -->
       <p class="top-tip">请按照顺序配置项目阶段,拖拽<b>步骤</b>排序即可</p>
      <div class="step-table-container">
        <el-table :data="form.savePlanNodeList" border style="width: 100%">
          <el-table-column label="步骤" width="80" align="center">
        <el-table
          :data="form.savePlanNodeList"
          border
          style="width: 100%"
          row-key="id"
          class="drag-table"
        >
          <el-table-column label="步骤" width="80" align="center" class-name="drag-handle">
            <template #default="scope">
              {{ scope.$index + 1 }}
              <div class="step-index" style="cursor: move;">
                {{ scope.$index + 1 }}
              </div>
            </template>
          </el-table-column>
          
@@ -118,28 +130,6 @@
              <el-input v-model="scope.row.workContent" placeholder="请输入" />
            </template>
          </el-table-column>
          <el-table-column label="操作" width="180" align="center">
            <template #default="scope">
              <el-button
                link
                type="primary"
                :disabled="scope.$index === 0"
                @click="moveStep(scope.$index, -1)"
              >上移</el-button>
              <el-button
                link
                type="primary"
                :disabled="scope.$index === form.savePlanNodeList.length - 1"
                @click="moveStep(scope.$index, 1)"
              >下移</el-button>
              <el-button
                link
                type="danger"
                @click="removeStep(scope.$index)"
              >删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <div class="add-row-btn" @click="addStep">
@@ -158,10 +148,12 @@
</template>
<script setup>
import { ref, watch, onMounted } from 'vue';
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 { getToken } from '@/utils/auth';
import Sortable from 'sortablejs';
const props = defineProps({
  modelValue: Boolean,
@@ -174,12 +166,17 @@
const visible = ref(false);
const formRef = ref(null);
const userOptions = ref([]);
const uploadHeaders = { Authorization: "Bearer " + getToken() };
// 上传地址
const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/basic/customer-follow/upload";
let sortable = null;
const form = ref({
  id: undefined,
  name: '',
  description: '',
  attachmentIds: [],
  attachmentList: [],
  savePlanNodeList: []
});
@@ -192,21 +189,70 @@
  visible.value = val;
  if (val) {
    if (props.data) {
      // 编辑模式
      form.value = JSON.parse(JSON.stringify(props.data));
      if (!form.value.savePlanNodeList || form.value.savePlanNodeList.length === 0) {
      // 编辑模式 - 回显数据
      form.value = {
        id: props.data.id,
        name: props.data.name,
        description: props.data.description,
        attachmentIds: [],
        attachmentList: props.data.attachmentList || [],
        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) {
        form.value.savePlanNodeList = props.data.planNodeList.map(node => ({
          id: node.id,
          projectManagementPlanId: node.projectManagementPlanId,
          sort: node.sort,
          name: node.name,
          leaderId: node.leaderId,
          leaderName: node.leaderName,
          estimatedDuration: node.estimatedDuration,
          hourlyRate: node.hourlyRate,
          workContent: node.workContent
        }));
      } else {
        form.value.savePlanNodeList = [createDefaultNode()];
      }
    } else {
      // 新增模式
      resetForm();
    }
    // 初始化拖拽
    nextTick(() => {
      initSortable();
    });
  }
});
watch(visible, (val) => {
  emit('update:modelValue', val);
});
/** 初始化拖拽 */
function initSortable() {
  const el = document.querySelector('.drag-table .el-table__body-wrapper tbody');
  if (!el) return;
  if (sortable) {
    sortable.destroy();
  }
  sortable = Sortable.create(el, {
    handle: '.drag-handle',
    animation: 150,
    onEnd: ({ newIndex, oldIndex }) => {
      const targetRow = form.value.savePlanNodeList.splice(oldIndex, 1)[0];
      form.value.savePlanNodeList.splice(newIndex, 0, targetRow);
    }
  });
}
/** 创建默认节点对象 */
function createDefaultNode() {
@@ -227,6 +273,7 @@
    name: '',
    description: '',
    attachmentIds: [],
    attachmentList: [],
    savePlanNodeList: [createDefaultNode()]
  };
  if (formRef.value) {
@@ -254,12 +301,38 @@
  }
}
/** 处理文件变化 */
function handleFileChange(file) {
  // 这里实现文件上传逻辑,获取 attachmentId
  ElMessage.info('正在上传: ' + file.name);
  // 模拟上传成功
  // form.value.attachmentIds.push(newId);
/** 处理文件上传成功 */
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
       });
    }
  } else {
    ElMessage.error(response.msg || '上传失败');
  }
}
/** 处理文件移除 */
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);
    }
  }
}
/** 添加步骤 */
@@ -294,6 +367,10 @@
  try {
    const valid = await formRef.value.validate();
    if (valid) {
      // 提交前自动填充 sort 字段,按当前数组顺序排序
      form.value.savePlanNodeList.forEach((node, index) => {
        node.sort = index;
      });
      emit('submit', form.value);
    }
  } catch (error) {
@@ -382,4 +459,10 @@
  gap: 15px;
  padding-top: 10px;
}
.top-tip {
  font-size: 14px;
  color: #606266;
  margin:0 0 10px 10px;
}
</style>