下载文件名称统一修改

This commit is contained in:
2026-10-13 11:45:45 +08:00
parent 7797b4479d
commit b6ee63299e
5 changed files with 616 additions and 604 deletions
+140 -137
View File
@@ -236,7 +236,7 @@ const handleSelectKeys = (keys: string[],option:any) => {
// 下载
const downloadFile = (file:any) => {
downloadFileFun(file.file_url, file.file_name);
downloadFileFun(file?.file_url, file?.file_name || file?.doc_name);
}
// 预览
@@ -354,164 +354,167 @@ onMounted(async () => {
</script>
<template>
<NSpace vertical :size="16">
<NSpin :show="loading" size="small">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
<NGi span="24 s:24 m:24">
<div class="h-100% flex items-center gap-2">
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
<p class="text-base font-semibold">变更归档(一次变更一个归档包 · 关闭即自动归档 · 只读锁定)</p>
<NSpace vertical :size="16">
<NSpin :show="loading" size="small">
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
<NGrid :x-gap="16" :y-gap="16" responsive="screen" item-responsive class="pb-[12px] border-b">
<NGi span="24 s:24 m:24">
<div class="h-100% flex items-center gap-2">
<Icon icon="mage:book" class="size-20px" :style="{ color: themeStore.themeColor }" />
<p class="text-base font-semibold">变更归档(一次变更一个归档包 · 关闭即自动归档 · 只读锁定)</p>
</div>
</NGi>
</NGrid>
<div class="flex items-center gap-2 mt-3">
<NInput v-model:value="formInfo.change_no" type="text" class="w-full text-xs" clearable placeholder="按变更单号精确查找,或按名称关键词检索(如 MOC-2026-R01-0039)" @clear="formInfo.change_no = ''; getList()">
<template #suffix>
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="getList" />
</template>
</NInput>
<NButton class="text-sm" @click="reset">
<Icon icon="system-uicons:reset" class="size-14px" />重置
</NButton>
</div>
<!-- 筛选:时间 / 组织 / 专业 -->
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 mt-3">
<NTreeSelect
class="!w-200px"
:value="formInfo.org_id"
:options="orgTree"
size="small"
default-expand-all
label-field="label"
key-field="id"
:render-switcher-icon="renderSwitcherIcon"
placeholder="请选择组织层级"
@update:value="orgChange"
/>
<div class="flex flex-wrap items-center gap-1">
<span class="text-[11px] text-slate-400">时间</span>
<NButton v-for="time in timeOptions" :key="time.value" size="tiny" round class="text-xs" :type="formInfo.time === time.value ? 'primary' : 'default'" @click="timeChange(time.value)">{{ time.label }}</NButton>
</div>
<div class="flex flex-wrap items-center gap-1">
<span class="text-[11px] text-slate-400">类型</span>
<NButton v-for="type in typeList" :key="type.value" size="tiny" round class="text-xs" :type="formInfo.type === type.value ? 'primary' : 'default'" @click="typeChange(type.value)">{{ type.label }}</NButton>
</div>
</NGi>
</NGrid>
<div class="flex items-center gap-2 mt-3">
<NInput v-model:value="formInfo.change_no" type="text" class="w-full text-xs" clearable placeholder="按变更单号精确查找,或按名称关键词检索(如 MOC-2026-R01-0039)" @clear="formInfo.change_no = ''; getList()">
<template #suffix>
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="getList" />
</template>
</NInput>
<NButton class="text-sm" @click="reset">
<Icon icon="system-uicons:reset" class="size-14px" />重置
</NButton>
</div>
<!-- 筛选:时间 / 组织 / 专业 -->
<div class="flex flex-wrap items-center gap-x-4 gap-y-1.5 mt-3">
<NTreeSelect
class="!w-200px"
:value="formInfo.org_id"
:options="orgTree"
size="small"
default-expand-all
label-field="label"
key-field="id"
:render-switcher-icon="renderSwitcherIcon"
placeholder="请选择组织层级"
@update:value="orgChange"
/>
<div class="flex flex-wrap items-center gap-1">
<span class="text-[11px] text-slate-400">时间</span>
<NButton v-for="time in timeOptions" :key="time.value" size="tiny" round class="text-xs" :type="formInfo.time === time.value ? 'primary' : 'default'" @click="timeChange(time.value)">{{ time.label }}</NButton>
</div>
<div class="flex flex-wrap items-center gap-1">
<span class="text-[11px] text-slate-400">类型</span>
<NButton v-for="type in typeList" :key="type.value" size="tiny" round class="text-xs" :type="formInfo.type === type.value ? 'primary' : 'default'" @click="typeChange(type.value)">{{ type.label }}</NButton>
</div>
</div>
<div class="flex gap-3 mt-3">
<!-- 左侧 -->
<div class="w-300px border rounded-md p-2">
<div class="scroll-left">
<NTree
class="h-100% flex flex-col"
block-line
:data="treeList"
label-field="label"
key-field="key"
:expanded-keys="expandedKeys"
:selected-keys="selectedKeys"
:render-switcher-icon="renderSwitcherIcon"
selectable
:cancelable="false"
:on-update:expanded-keys="handleExpandKeys"
:on-update:selected-keys="handleSelectKeys"
>
<template #empty>
<div class="h-100% flex items-center justify-center">
<NEmpty description="暂无数据" class="text-xs"></NEmpty>
<div class="flex gap-3 mt-3">
<!-- 左侧 -->
<div class="w-300px border rounded-md p-2">
<div class="scroll-left">
<NTree
class="h-100% flex flex-col"
block-line
:data="treeList"
label-field="label"
key-field="key"
:expanded-keys="expandedKeys"
:selected-keys="selectedKeys"
:render-switcher-icon="renderSwitcherIcon"
selectable
:cancelable="false"
:on-update:expanded-keys="handleExpandKeys"
:on-update:selected-keys="handleSelectKeys"
>
<template #empty>
<div class="h-100% flex items-center justify-center">
<NEmpty description="暂无数据" class="text-xs"></NEmpty>
</div>
</template>
</NTree>
</div>
</div>
<!-- 右:归档包详情 -->
<div class="min-w-0 flex-1">
<NSpin :show="rightLoading" size="small">
<template v-if="rightDetails">
<div class="rounded-md border col-span-2">
<div class="border-b p-3">
<div class="flex flex-wrap items-center gap-2">
<span class="text-sm font-semibold">{{ rightDetails.change_no }}</span>
</div>
<div class="mt-1 text-xs text-slate-600">{{ rightDetails.title }}</div>
<div class="mt-2 flex flex-wrap justify-between gap-x-4 gap-y-1 text-[11px] text-slate-500">
<span>归档路径:{{ currentPath.join('/') }}</span>
<span>关闭时间:{{ rightDetails?.close_time?.slice(0,10) }}</span>
<span>共 {{ extractFiles(rightDetails.folders)?.total }} 个文件 · {{ extractFiles(rightDetails.folders)?.formattedSize }}</span>
</div>
</div>
<div class="divide-y scroll-right">
<div v-for="f in rightDetails?.folders" :key="f.step_no">
<button
class="flex w-full items-center gap-2 px-3 py-2 text-left text-xs font-medium hover:bg-slate-50 bg-transparent"
@click="openFolders[f.step_no] = !openFolders[f.step_no]"
>
<Icon icon="formkit:right" class="size-14px text-slate-400 transition-transform" :class="openFolders[f.step_no] ? 'rotate-90' : ''" />
<Icon v-if="openFolders[f.step_no]" icon="quill:folder-open" class="size-16px" :style="{color:themeStore.themeColor}" />
<Icon v-else icon="material-symbols:folder-outline" class="size-16px text-slate-400" />
<span v-if="f.step_no===1">1-申请与审批</span>
<span v-else-if="f.step_no===2">2-风险分析</span>
<span v-else-if="f.step_no===3">3-培训与考核</span>
<span v-else-if="f.step_no===4">4-PSSR检查</span>
<span v-else-if="f.step_no===5">5-验收记录</span>
<span v-else>6-关闭资料</span>
<span class="ml-auto text-[11px] font-normal text-slate-400">{{ f.files.length }} 个文件</span>
</button>
<template v-if="openFolders[f.step_no]">
<div v-for="file in f.files" :key="file.name" class="flex items-center gap-2 py-1.5 pl-10 pr-3 text-xs hover:bg-slate-50">
<Icon icon="akar-icons:file" class="size-14px text-slate-400" />
<span class="min-w-0 flex-1 truncate">{{ file.file_name }}</span>
<span class="text-[11px] text-slate-400">{{ formatSize(file.file_size) }}</span>
<NButton text type="primary" title="预览" @click="preview(file)">
<Icon icon="lucide:eye" class="size-14px" />
</NButton>
<NButton text type="primary" title="下载" @click="downloadFile(file)">
<Icon icon="material-symbols:download" class="size-16px" />
</NButton>
</div>
</template>
</div>
</div>
</div>
</template>
</NTree>
<div v-else class="h-100% rounded-md border scroll-empty">
<div class="h-100% flex items-center justify-center">
<NEmpty description="暂无归档包详情数据" class="text-xs"></NEmpty>
</div>
</div>
</NSpin>
</div>
</div>
<!-- 右:归档包详情 -->
<div class="min-w-0 flex-1">
<NSpin :show="rightLoading" size="small">
<template v-if="rightDetails">
<div class="rounded-md border col-span-2">
<div class="border-b p-3">
<div class="flex flex-wrap items-center gap-2">
<span class="text-sm font-semibold">{{ rightDetails.change_no }}</span>
</div>
<div class="mt-1 text-xs text-slate-600">{{ rightDetails.title }}</div>
<div class="mt-2 flex flex-wrap justify-between gap-x-4 gap-y-1 text-[11px] text-slate-500">
<span>归档路径:{{ currentPath.join('/') }}</span>
<span>关闭时间:{{ rightDetails?.close_time?.slice(0,10) }}</span>
<span>共 {{ extractFiles(rightDetails.folders)?.total }} 个文件 · {{ extractFiles(rightDetails.folders)?.formattedSize }}</span>
</div>
</div>
<div class="divide-y scroll-right">
<div v-for="f in rightDetails?.folders" :key="f.step_no">
<button @click="openFolders[f.step_no] = !openFolders[f.step_no]"
class="flex w-full items-center gap-2 px-3 py-2 text-left text-xs font-medium hover:bg-slate-50 bg-transparent">
<Icon icon="formkit:right" class="size-14px text-slate-400 transition-transform" :class="openFolders[f.step_no] ? 'rotate-90' : ''" />
<Icon icon="quill:folder-open" v-if="openFolders[f.step_no]" class="size-16px" :style="{color:themeStore.themeColor}" />
<Icon v-else icon="material-symbols:folder-outline" class="size-16px text-slate-400" />
<span v-if="f.step_no===1">1-申请与审批</span>
<span v-else-if="f.step_no===2">2-风险分析</span>
<span v-else-if="f.step_no===3">3-培训与考核</span>
<span v-else-if="f.step_no===4">4-PSSR检查</span>
<span v-else-if="f.step_no===5">5-验收记录</span>
<span v-else>6-关闭资料</span>
<span class="ml-auto text-[11px] font-normal text-slate-400">{{ f.files.length }} 个文件</span>
</button>
<template v-if="openFolders[f.step_no]">
<div v-for="file in f.files" :key="file.name" class="flex items-center gap-2 py-1.5 pl-10 pr-3 text-xs hover:bg-slate-50">
<Icon icon="akar-icons:file" class="size-14px text-slate-400" />
<span class="min-w-0 flex-1 truncate">{{ file.file_name }}</span>
<span class="text-[11px] text-slate-400">{{ formatSize(file.file_size) }}</span>
<NButton text type="primary" title="预览" @click="preview(file)">
<Icon icon="lucide:eye" class="size-14px" />
</NButton>
<NButton text type="primary" title="下载" @click="downloadFile(file)">
<Icon icon="material-symbols:download" class="size-16px" />
</NButton>
</div>
</template>
</div>
</div>
</div>
</template>
<div v-else class="h-100% rounded-md border scroll-empty">
<div class="h-100% flex items-center justify-center">
<NEmpty description="暂无归档包详情数据" class="text-xs"></NEmpty>
</div>
</div>
</NSpin>
</div>
</div>
</NCard>
<!-- 附件在线预览弹窗 -->
<NModal
</NCard>
<!-- 附件在线预览弹窗 -->
<NModal
v-model:show="previewModal"
preset="card"
:auto-focus="false"
:style="{ width: '80%', height: 'auto' }"
:segmented="{ content: true, footer: true }"
>
>
<template #header>
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
</div>
</div>
</template>
<template #default>
<div class="space-y-3">
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
<span>上传人:{{ previewFile?.uploader_name }}</span>
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<previewComponent v-if="previewFile" :previewFile="previewFile" />
<div class="space-y-3">
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-slate-400">
<span>上传人:{{ previewFile?.uploader_name }}</span>
<span>上传时间:{{ previewFile?.created_at?.slice(0, 16) }}</span>
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
</div>
<PreviewComponent v-if="previewFile" :preview-file="previewFile" />
</div>
</template>
</NModal>
</NSpin>
</NSpace>
</NModal>
</NSpin>
</NSpace>
</template>
<style scoped>
</style>
<style scoped lang="scss">
.scroll-left {
height: calc(100vh - 303px);