opt: 附件上传/删除
This commit is contained in:
@@ -28,5 +28,9 @@ export default {
|
||||
// 获取项目附件表详情
|
||||
bindProjectFile(data) {
|
||||
return request('bindProjectFile', data, 'post')
|
||||
},
|
||||
// 获取项目附件表详情
|
||||
deleteFile(data) {
|
||||
return request('delete-file', data, 'post')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,9 @@ export default {
|
||||
// 获取项目阶段详情
|
||||
projectStageDetail(data) {
|
||||
return request('detail', data, 'get')
|
||||
},
|
||||
// 获取项目阶段详情
|
||||
batchEdit(data) {
|
||||
return request('batch-edit', data, 'post')
|
||||
}
|
||||
}
|
||||
|
||||
153
nl-vue/src/views/pmm/project/request/batchStageAddDialog.vue
Normal file
153
nl-vue/src/views/pmm/project/request/batchStageAddDialog.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<xn-form-container title="编辑项目阶段" :width="700" v-model:open="open" :destroy-on-close="true" @close="onClose">
|
||||
<div class="stage-form">
|
||||
<div class="stage-form__header">
|
||||
<div>阶段名称</div>
|
||||
<div>阶段序号</div>
|
||||
<div>操作</div>
|
||||
</div>
|
||||
<div v-if="!visibleStages.length" class="stage-form__empty">
|
||||
<a-empty description="暂无阶段,请新增一行" />
|
||||
</div>
|
||||
<div v-for="(stage, index) in visibleStages" :key="stage._key ?? index" class="stage-form__row">
|
||||
<a-input v-model:value="stage.stageName" placeholder="请输入阶段名称" allow-clear :maxlength="50" />
|
||||
<a-input-number v-model:value="stage.stageSeq" placeholder="请输入阶段序号" :min="1" style="width: 100%" />
|
||||
<a-space>
|
||||
<a-tooltip title="删除当前行以及需求">
|
||||
<a-button type="link" danger @click="removeRow(stage)">删除</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="addRow"> 新增一行 </a-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
|
||||
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
|
||||
</template>
|
||||
</xn-form-container>
|
||||
</template>
|
||||
|
||||
<script setup name="projectStageForm">
|
||||
import projectStageApi from '@/api/pmm/projectStageApi'
|
||||
import { createVNode } from 'vue'
|
||||
import { Modal } from 'ant-design-vue'
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
// 抽屉状态
|
||||
const open = ref(false)
|
||||
const emit = defineEmits({ successful: null })
|
||||
// 表单数据
|
||||
const formData = ref([])
|
||||
const projectId = ref('')
|
||||
const visibleStages = computed(() => formData.value.filter((item) => !item.isDeleted))
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const createEmptyStage = () => ({
|
||||
stageId: '',
|
||||
stageName: '',
|
||||
projectId: projectId,
|
||||
stageSeq: visibleStages.value.length + 1,
|
||||
isDeleted: false,
|
||||
_key: `${Date.now()}-${Math.random()}`
|
||||
})
|
||||
|
||||
// 打开抽屉
|
||||
const onOpen = (record) => {
|
||||
if (record.length > 0) {
|
||||
projectId.value = record[0].projectId
|
||||
}
|
||||
open.value = true
|
||||
const list = Array.isArray(record) ? record : []
|
||||
formData.value = cloneDeep(list).map((item, index) => ({
|
||||
stageId: item.stageId ?? '',
|
||||
stageName: item.stageName ?? '',
|
||||
projectId: item.projectId ?? '',
|
||||
stageSeq: item.stageSeq ?? index + 1,
|
||||
isDeleted: item.isDeleted ?? false,
|
||||
_key: `${Date.now()}-${index}`
|
||||
}))
|
||||
if (!formData.value.length) {
|
||||
formData.value.push(createEmptyStage())
|
||||
}
|
||||
}
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
formData.value = []
|
||||
open.value = false
|
||||
}
|
||||
|
||||
const addRow = () => {
|
||||
formData.value.push(createEmptyStage())
|
||||
}
|
||||
|
||||
const removeRow = (stage) => {
|
||||
stage.isDeleted = true
|
||||
if (!visibleStages.value.length) {
|
||||
formData.value.push(createEmptyStage())
|
||||
}
|
||||
}
|
||||
|
||||
const submitStages = () => {
|
||||
submitLoading.value = true
|
||||
const submitList = formData.value.map(({ _key, ...rest }) => rest)
|
||||
projectStageApi
|
||||
.batchEdit(submitList)
|
||||
.then(() => {
|
||||
onClose()
|
||||
emit('successful')
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
}
|
||||
// 验证并提交数据
|
||||
const onSubmit = () => {
|
||||
const deletedStageCount = formData.value.filter((item) => item.stageId && item.isDeleted).length
|
||||
const addedStageCount = formData.value.filter((item) => !item.stageId && !item.isDeleted).length
|
||||
const content = `将删除${deletedStageCount}个阶段以及该阶段的需求,新增${addedStageCount}个阶段,是否继续?`
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
submitStages()
|
||||
}
|
||||
})
|
||||
}
|
||||
// 抛出函数
|
||||
defineExpose({
|
||||
onOpen
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.stage-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.stage-form__header,
|
||||
.stage-form__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 120px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
.stage-form__header {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.stage-form__empty {
|
||||
padding: 24px 0;
|
||||
border: 1px dashed var(--border-color-split);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
.stage-form__row {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color-split);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -60,6 +60,7 @@
|
||||
<span class="hours-label">计划工时:</span>
|
||||
<span class="hours-value">{{ plannedHours }}</span>
|
||||
<a-button type="primary" class="create-stage-btn" @click="formRef.onOpen(projectInfo.projectId)">创建项目阶段</a-button>
|
||||
<a-button type="primary" class="create-stage-btn" @click="form2Ref.onOpen(stageList)">编辑项目阶段</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
@@ -156,6 +157,14 @@
|
||||
class="attachment-item"
|
||||
@click="handlePreview(file)"
|
||||
>
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
class="attachment-delete-btn"
|
||||
@click.stop="handleAttachmentDelete(file)"
|
||||
>
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
<div class="attachment-thumb">
|
||||
<img :src="getAttachmentThumbnail(file)" :alt="file.name" />
|
||||
</div>
|
||||
@@ -173,6 +182,7 @@
|
||||
<preview v-else ref="previewRef" @goBack="handlePreviewClose" />
|
||||
<stage-add-dialog ref="formRef" @successful="fetchDetail"/>
|
||||
<fj-upload-form ref="uploadFormRef" @successful="fetchAttachments"/>
|
||||
<batch-stage-add-dialog ref="form2Ref" @successful="fetchDetail"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -180,10 +190,13 @@
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import projectApi from '@/api/pmm/projectApi'
|
||||
import { message } from 'ant-design-vue'
|
||||
import projectFileApi from '@/api/pmm/projectFileApi'
|
||||
import { Modal, message } from 'ant-design-vue'
|
||||
import StageAddDialog from "@/views/pmm/project/request/stageAddDialog.vue";
|
||||
import Preview from '@/views/dev/file/preview.vue'
|
||||
import FjUploadForm from "@/views/pmm/project/request/fjUploadForm.vue";
|
||||
import BatchStageAddDialog from "@/views/pmm/project/request/batchStageAddDialog.vue";
|
||||
import { CloseOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
interface AttachmentItem {
|
||||
id: number | string
|
||||
@@ -199,6 +212,7 @@
|
||||
const stageList = ref<any[]>([])
|
||||
const plannedHours = ref(0)
|
||||
const formRef = ref()
|
||||
const form2Ref = ref()
|
||||
const previewRef = ref()
|
||||
const uploadFormRef = ref()
|
||||
const previewVisible = ref(false)
|
||||
@@ -324,7 +338,7 @@ const attachmentList = ref<AttachmentItem[]>([])
|
||||
return total
|
||||
}
|
||||
|
||||
// 计算计划工时(所有阶段的总天数 * 24)
|
||||
// 计算计划工时(所有阶段的总天数 * 8)
|
||||
const calculatePlannedHours = (stages: any[]) => {
|
||||
let total = 0
|
||||
stages.forEach((stage) => {
|
||||
@@ -332,7 +346,7 @@ const attachmentList = ref<AttachmentItem[]>([])
|
||||
total += getStageTotalDays(stage.descriptions)
|
||||
}
|
||||
})
|
||||
return total * 24
|
||||
return total * 8
|
||||
}
|
||||
|
||||
// 获取项目需求明细
|
||||
@@ -512,6 +526,29 @@ watch(projectId, () => {
|
||||
const handlePreviewClose = () => {
|
||||
previewVisible.value = false
|
||||
}
|
||||
|
||||
const handleAttachmentDelete = (file: AttachmentItem) => {
|
||||
const param = {
|
||||
id: file.id
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确认删除该附件?',
|
||||
content: `附件:${file.name}`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
projectFileApi
|
||||
.deleteFile(param)
|
||||
.then(() => {
|
||||
message.success(`已确认删除:${file.name}`)
|
||||
fetchAttachments()
|
||||
})
|
||||
},
|
||||
onCancel: () => {
|
||||
console.log('取消删除附件:', file)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -669,6 +706,23 @@ watch(projectId, () => {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.attachment-delete-btn {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.attachment-delete-btn:hover {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.attachment-item:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
const onOpen = (record) => {
|
||||
open.value = true
|
||||
if (record) {
|
||||
console.log(record)
|
||||
formData.value.projectId = record
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user