docx、xlsx、pdf、图片等文件预览功能添加
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { request } from '../request';
|
||||
|
||||
// 上传文件
|
||||
export function uploadFileApi(id: number, params: any) {
|
||||
return request({
|
||||
url: `/api/changes/${id}/attachments`,
|
||||
method: 'post',
|
||||
data: params
|
||||
});
|
||||
}
|
||||
|
||||
// 删除文件
|
||||
export function deleteFileApi(id: number, fileId: number) {
|
||||
return request({
|
||||
url: `/api/changes/${id}/attachments/${fileId}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
Vendored
+2
@@ -56,6 +56,7 @@ declare module 'vue' {
|
||||
NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||
NGi: typeof import('naive-ui')['NGi']
|
||||
NGrid: typeof import('naive-ui')['NGrid']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
@@ -145,6 +146,7 @@ declare global {
|
||||
const NFormItemGi: typeof import('naive-ui')['NFormItemGi']
|
||||
const NGi: typeof import('naive-ui')['NGi']
|
||||
const NGrid: typeof import('naive-ui')['NGrid']
|
||||
const NImage: typeof import('naive-ui')['NImage']
|
||||
const NInput: typeof import('naive-ui')['NInput']
|
||||
const NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||
const NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
|
||||
@@ -6,8 +6,13 @@ import { useThemeStore } from '@/store/modules/theme';
|
||||
import { tagTextColor, tagBgColor, tagBorderColor } from '@/utils/common';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { useChangeStore } from '@/store/modules/change';
|
||||
import Footer from './footer.vue'
|
||||
import { AiError, aiTypeJudge, aiLevelScore } from "@/service/api/ai";
|
||||
import { uploadFileApi, deleteFileApi } from "@/service/api/file";
|
||||
import Footer from './footer.vue'
|
||||
|
||||
import VuePdfEmbed from 'vue-pdf-embed';
|
||||
import { VueOfficeDocx, VueOfficeExcel } from 'vue3-office-preview'
|
||||
import 'vue3-office-preview/lib/style.css'
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const changeStore = useChangeStore();
|
||||
@@ -19,9 +24,10 @@ const aiFail = (e: any) => {
|
||||
if (e instanceof AiError) window.$message?.warning(`AI 分析失败:${e.message},可手动填写`);
|
||||
else window.$message?.warning("AI 分析失败,可手动填写");
|
||||
};
|
||||
const today = () => new Date().toISOString().slice(0, 10);
|
||||
|
||||
const uploadFileRef = ref<any>(null);
|
||||
const footerRef = ref<any>(null);
|
||||
const uploadLoading = ref<boolean>(false);
|
||||
const specialties = ['生产','技术', '仪表', '电气', '设备', '安全', '质量'];
|
||||
// 总分数
|
||||
const totalScore = ref<number>(0);
|
||||
@@ -30,8 +36,11 @@ const currentLevel = computed(() => props.levelList.find((x:{value:number | null
|
||||
// 当前用户
|
||||
const userInfo = ref<any>(localStg.get('userInfo'));
|
||||
// 预览文件
|
||||
const previewFile = ref<string | null>(null);
|
||||
const previewFile = ref<any>(null);
|
||||
const sheet = ref<string>('');
|
||||
// 上传文件
|
||||
const uploadFile = ref<{id:number, file_name:string, file_url:string, uploader_name:string, file_type:string, created_at:string, version:string | null}[]>([]);
|
||||
|
||||
// 表单
|
||||
const form = ref<{
|
||||
change_type: number | null,
|
||||
@@ -279,6 +288,52 @@ const renderSwitcherIcon = (node:any) => {
|
||||
});
|
||||
}
|
||||
|
||||
// 判断文件类型
|
||||
const handleFileType = (type:string) => {
|
||||
if(type){
|
||||
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
|
||||
const wordExtensions = ['docx']
|
||||
const excelExtensions = ['xlsx']
|
||||
if(wordExtensions.includes(type)){
|
||||
return 'word'
|
||||
}
|
||||
if(excelExtensions.includes(type)){
|
||||
return 'excel'
|
||||
}
|
||||
if(imageExtensions.includes(type)){
|
||||
return 'image'
|
||||
}
|
||||
if(type==='pdf'){
|
||||
return 'pdf'
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 上传资料
|
||||
const onUploadFile = async (e:any) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('step', '2');
|
||||
uploadLoading.value = true;
|
||||
const {data,error} = await uploadFileApi(props.currentId, formData);
|
||||
if(!error){
|
||||
window.$message?.success('上传成功');
|
||||
uploadFile.value = [...uploadFile.value, data];
|
||||
}
|
||||
uploadLoading.value = false;
|
||||
}
|
||||
// 删除资料
|
||||
const removeFile = async (index:number) => {
|
||||
const {error} = await deleteFileApi(props.currentId, uploadFile.value[index].id);
|
||||
if(!error){
|
||||
window.$message?.success('删除成功');
|
||||
uploadFile.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存草稿
|
||||
const saveDraft = () => {
|
||||
emit('save');
|
||||
@@ -483,22 +538,35 @@ defineExpose({form})
|
||||
<div
|
||||
class="relative"
|
||||
:style="{'--theme-color':themeStore.themeColor}"
|
||||
v-for="(f,index) in form.update_docs" :key="f"
|
||||
v-for="(f,index) in uploadFile" :key="f.id"
|
||||
>
|
||||
<div
|
||||
class="text-xs cursor-pointer flex items-center border py-1.5 px-2 rounded-md hover:border-[var(--theme-color)] hover:text-[var(--theme-color)]"
|
||||
title="点击在线预览"
|
||||
@click="previewFile = f"
|
||||
>
|
||||
<Icon icon="akar-icons:file" class="size-14px mr-1" />{{ f }}
|
||||
<Icon icon="akar-icons:file" class="size-14px mr-1" />{{ f.file_name }}
|
||||
</div>
|
||||
<NButton text type="error" class="absolute top-[-8px] right-[-8px] bg-[#fff]" @click="form.update_docs.splice(index, 1)">
|
||||
<Icon icon="ci:close-circle" class="size-16px" />
|
||||
</NButton>
|
||||
<NPopconfirm
|
||||
positive-text="确定"
|
||||
:positiveButtonProps="{ size: 'tiny' }"
|
||||
:negativeButtonProps="{ size: 'tiny' }"
|
||||
@positive-click="removeFile(index)"
|
||||
>
|
||||
<template #trigger>
|
||||
<NButton text type="error" class="absolute top-[-8px] right-[-8px] bg-[#fff]">
|
||||
<Icon icon="ci:close-circle" class="size-16px" />
|
||||
</NButton>
|
||||
</template>
|
||||
确定删除吗?
|
||||
</NPopconfirm>
|
||||
</div>
|
||||
<NButton @click="form.update_docs = [...form.update_docs, `资料_${form.update_docs.length + 1}.pdf`]">
|
||||
<Icon icon="material-symbols:upload" class="size-16px" /> 资料上传
|
||||
<NButton :disabled="uploadLoading" @click="uploadFileRef?.click()">
|
||||
<Icon v-if="uploadLoading" icon="ri:loader-4-fill" class="size-16px animate-spin" />
|
||||
<Icon v-else icon="material-symbols:upload" class="size-16px" />
|
||||
资料上传
|
||||
</NButton>
|
||||
<input ref="uploadFileRef" type="file" accept="image/*,.pdf,.docx,.xlsx" class="hidden" @change="onUploadFile" />
|
||||
<span class="text-xs text-slate-400">(点击附件名弹窗预览,支持 PDF / Word / 图片在线查看)</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -652,24 +720,39 @@ defineExpose({form})
|
||||
:show="previewFile !== null"
|
||||
preset="card"
|
||||
:auto-focus="false"
|
||||
:style="{ width: '700px', height: 'auto' }"
|
||||
:style="{ width: '1000px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
@update:show="(v) => !v && (previewFile = null)"
|
||||
>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2 text-base font-semibold text-slate-800">
|
||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile }}
|
||||
<Icon icon="akar-icons:file" class="size-18px" /> {{ previewFile?.file_name }}
|
||||
</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>上传人:{{ userInfo?.real_name }}</span><span>上传时间:{{ today() }}</span><span>版本:V1(受控)</span>
|
||||
<span>上传人:{{ previewFile?.uploader_name }}</span>
|
||||
<span>上传时间:{{ previewFile?.created_at }}</span>
|
||||
<span v-if="previewFile?.version">{{ `版本:${previewFile?.version}(受控)` }}</span>
|
||||
</div>
|
||||
<div class="flex h-72 flex-col items-center justify-center gap-2 rounded-lg border bg-slate-50 text-slate-400">
|
||||
<Icon icon="akar-icons:file" class="size-42px" />
|
||||
<span class="text-sm">文件在线预览区(演示)</span>
|
||||
<span class="text-xs">正式环境由文档预览服务渲染 PDF / Word / 图片内容,支持缩放、翻页与移动端查看</span>
|
||||
<div v-if="previewFile" class="flex max-h-[70vh] overflow-x-auto items-center justify-center rounded-lg border py-2">
|
||||
<!-- 图片预览 -->
|
||||
<template v-if="handleFileType(previewFile?.file_type) === 'image'">
|
||||
<NImage :src="previewFile?.file_url" />
|
||||
</template>
|
||||
<!-- Word 预览 -->
|
||||
<template v-if="handleFileType(previewFile?.file_type) === 'word'">
|
||||
<VueOfficeDocx :src="previewFile?.file_url" class="w-full" />
|
||||
</template>
|
||||
<!-- Excel 预览 -->
|
||||
<template v-if="handleFileType(previewFile?.file_type) === 'excel'">
|
||||
<VueOfficeExcel :src="previewFile?.file_url" class="w-full" />
|
||||
</template>
|
||||
<!-- PDF 预览 -->
|
||||
<template v-if="handleFileType(previewFile?.file_type) === 'pdf'">
|
||||
<VuePdfEmbed :source="previewFile?.file_url" annotation-layer text-layer class="w-full" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -98,12 +98,12 @@ defineExpose({form})
|
||||
✨ AI 生成 · 需人工确认
|
||||
</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<NButton ghost size="small" class="text-xs" title="上传 PSSR 纸质确认表照片 / 扫描件(JPG / PNG / PDF),系统不做内容识别,上传即视为全部检查项完成确认" @click="pssrFileRef?.click()">
|
||||
<Icon icon="material-symbols:upload" class="size-14px" />
|
||||
上传纸质确认表
|
||||
</NButton>
|
||||
<input ref="pssrFileRef" type="file" accept="image/*,.pdf" class="hidden" @change="onPssrFile" />
|
||||
<span class="hidden text-xs text-slate-400 xl:inline">申请阶段:点击条目内容直接编辑,可增删项,完成时点互斥选择;审批通过后逐项点击确认,全部完成自动推送属地负责人</span>
|
||||
<NButton ghost size="small" class="text-xs" title="上传 PSSR 纸质确认表照片 / 扫描件(JPG / PNG / PDF),系统不做内容识别,上传即视为全部检查项完成确认" @click="pssrFileRef?.click()">
|
||||
<Icon icon="material-symbols:upload" class="size-14px" />
|
||||
上传纸质确认表
|
||||
</NButton>
|
||||
<input ref="pssrFileRef" type="file" accept="image/*,.pdf" class="hidden" @change="onPssrFile" />
|
||||
<span class="hidden text-xs text-slate-400 xl:inline">申请阶段:点击条目内容直接编辑,可增删项,完成时点互斥选择;审批通过后逐项点击确认,全部完成自动推送属地负责人</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="pssrSheet" class="flex items-center gap-2 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-2 text-xs text-emerald-700">
|
||||
|
||||
Reference in New Issue
Block a user