feat: 优化删除标签模态框和上传组件,新增文件预签名URL获取功能

- 简化 `delete-tag.vue` 模态框模板结构
- 更新 `add-raw-material-drawer/index.vue` 组件,增加文件预签名URL获取和上传状态显示
- 新增 `getFilePreSignedUrl` API 函数
- 更新 `tools.ts` 中的文件扩展名提取函数
- 优化按钮激活状态样式
- 添加 `icon-no-text.png` 图标文件
This commit is contained in:
rd
2025-09-17 11:59:31 +08:00
parent 7c85582564
commit c08b13673f
8 changed files with 214 additions and 80 deletions

View File

@ -1,5 +1,5 @@
<script lang="tsx">
import { Drawer, Button, Upload, Table, Input } from 'ant-design-vue';
import { Drawer, Button, Upload, Table, Input, Progress } from 'ant-design-vue';
const { Column } = Table;
const { TextArea } = Input;
@ -7,15 +7,24 @@ const { TextArea } = Input;
import CommonSelect from '@/components/common-select';
import ImgLazyLoad from '@/components/img-lazy-load';
import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from '@/utils/tools';
import { formatFileSize, getVideoInfo, getFileExtension, formatUploadSpeed } from '@/utils/tools';
import { getRawMaterialTagsList } from '@/api/all/generationWorkshop';
import { getFilePreSignedUrl } from '@/api/all/common';
import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '../../img/icon-no-text.png';
import axios from 'axios';
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'];
const videoExtensions = ['.mp4', '.mov', '.avi', '.flv', '.wmv', '.m4v'];
const documentExtensions = ['.txt', '.doc', '.docx', '.pdf', '.xls', '.xlsx'];
enum EnumUploadStatus {
done = 'done',
error = 'error',
uploading = 'uploading',
}
export default defineComponent({
setup(_, { attrs, slots, expose }) {
const visible = ref(false);
@ -23,8 +32,8 @@ export default defineComponent({
const uploadData = ref([]);
const tagData = ref([]);
const checkSuccessNum = computed(() => {
return uploadData.value.filter((item) => item.status === 'success').length;
const uploadSuccessNum = computed(() => {
return uploadData.value.filter((item) => item.uploadStatus === EnumUploadStatus.done).length;
});
const getTagData = async () => {
@ -48,31 +57,120 @@ export default defineComponent({
};
const handleUpload = async (option) => {
// console.log('handleUpload', option);
};
const { file } = option;
const { name, size, type, uid } = file;
const getFileType = (fileName) => {
const ext = fileName.slice(fileName.lastIndexOf('.')).toLowerCase();
let statusText = '';
const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
const isImage = imageExtensions.includes(ext);
const isVideo = videoExtensions.includes(ext);
const isDocument = documentExtensions.includes(ext);
if (imageExtensions.includes(ext)) {
return '图片';
} else if (videoExtensions.includes(ext)) {
return '视频';
} else if (documentExtensions.includes(ext)) {
return '文档';
} else {
return '其他';
if (!isImage && !isVideo && !isDocument) {
statusText = '当前格式不支持';
} else if (
(isImage && size > 20 * 1024 * 1024) || // 图片大于20MB
(isVideo && size > 1000 * 1024 * 1024) || // 视频大于1000MB
(isDocument && size > 20 * 1024 * 1024) // 文档大于20MB
) {
statusText = '文件大小超出限制';
}
const { fileTypeLabel, cover } = await getFileInfo(file);
const currentData = {
uid,
name,
uploadStatus: statusText ? EnumUploadStatus.error : EnumUploadStatus.uploading,
statusText,
percent: 0,
size: formatFileSize(size),
fileTypeLabel,
tag_ids: [],
cover,
};
uploadData.value.push(currentData);
if (statusText) return;
try {
const response = await getFilePreSignedUrl({ suffix: getFileExtension(name) });
const { upload_url } = response?.data;
if (!upload_url) {
throw new Error('未能获取有效的预签名上传地址');
}
const blob = new Blob([file], { type });
await axios.put(upload_url, blob, {
headers: { 'Content-Type': type },
onUploadProgress: (progressEvent) => {
const _target = uploadData.value.find((item) => item.uid === uid);
const percentCompleted = Math.round(progressEvent.progress * 100);
_target.percent = percentCompleted;
percentCompleted === 100 && (_target.uploadStatus = EnumUploadStatus.done);
},
});
} catch {
currentData.uploadStatus = EnumUploadStatus.error;
}
};
const onConfirm = () => {
const getFileInfo = async (file: any) => {
const { name } = file;
const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
if (imageExtensions.includes(ext)) {
return {
fileTypeLabel: '图片',
cover: URL.createObjectURL(file),
};
} else if (videoExtensions.includes(ext)) {
const { firstFrame } = await getVideoInfo(file);
return {
fileTypeLabel: '视频',
cover: firstFrame,
};
} else if (documentExtensions.includes(ext)) {
return {
fileTypeLabel: '文本',
cover: icon2,
};
} else {
return {
fileTypeLabel: '其他',
cover: '',
};
}
};
const onConfirm = async () => {
console.log('onConfirm');
};
const handleDelete = (file) => {
uploadData.value = uploadData.value.filter((item) => item.uid !== file.uid);
};
const renderUploadList = (file, actions) => {
const renderUploadStatus = (record) => {
if (record.uploadStatus === EnumUploadStatus.error) {
return (
<div>
<p class="upload-text">上传失败</p>
<span class="upload-text color-#F64B31">{record.statusText}</span>
</div>
);
}
return (
<div>
<span class="upload-text ml-26px">
{record.uploadStatus === EnumUploadStatus.done ? '上传成功' : '上传中'}
</span>
<Progress percent={record.percent} class="m-0 p-0 " strokeColor="#25C883" />
</div>
);
};
const renderUploadList = () => {
if (!uploadData.value.length) return null;
console.log('renderUploadList', uploadData.value);
@ -80,7 +178,7 @@ export default defineComponent({
return (
<>
<p class="cts !color-#939499 mb-10px">
已上传<span class="!color-#211F24">{`${checkSuccessNum.value}/${uploadData.value.length}`}</span>
已上传<span class="!color-#211F24">{`${uploadSuccessNum.value}/${uploadData.value.length}`}</span>
</p>
<Table ref="tableRef" dataSource={uploadData.value} pagination={false} class="manuscript-table w-100% flex-1">
<Column
@ -91,7 +189,7 @@ export default defineComponent({
ellipsis={true}
customRender={({ text, record }) => {
return (
<div class="flex items-center">
<div class="flex items-center justify-end">
<ImgLazyLoad width={64} height={64} src={record.cover} class="!rounded-6px mr-16px flex-shrink-0" />
<TextArea
@ -108,53 +206,30 @@ export default defineComponent({
/>
<Column
title="上传状态"
dataIndex="status"
key="status"
dataIndex="uploadStatus"
key="uploadStatus"
width={164}
customRender={({ text, record }) => {
if (record.status === 'done') {
return <span class="upload-text">上传成功</span>;
} else if (record.status === 'error') {
return <span class="upload-text">上传失败</span>;
} else {
return <span class="upload-text">上传中</span>;
}
}}
customRender={({ text, record }) => renderUploadStatus(record)}
/>
<Column
title="标签"
dataIndex="tags"
key="tags"
dataIndex="tag_ids"
key="tag_ids"
width={243}
customRender={({ text, record }) => {
return (
<CommonSelect
v-model={record.tags}
v-model={record.tag_ids}
class="w-full"
multiple
options={tagData}
options={tagData.value}
placeholder="请选择标签"
/>
);
}}
/>
<Column
title="类型"
dataIndex="type"
key="type"
width={80}
customRender={({ text, record }) => {
const fileName = record.name || '';
return <span>{getFileType(fileName)}</span>;
}}
/>
<Column
title="大小"
dataIndex="size"
key="size"
width={100}
customRender={({ text, record }) => formatFileSize(record.size)}
/>
<Column title="类型" dataIndex="fileTypeLabel" key="fileTypeLabel" width={80} />
<Column title="大小" dataIndex="size" key="size" width={100} />
<Column
title="操作"
key="action"
@ -163,10 +238,14 @@ export default defineComponent({
customRender={({ text, record }) => {
return (
<div class="flex items-center">
<Button type="text" class="!h-22px !p-0 mr-16px">
取消生成
</Button>
{record.uploadStatus === EnumUploadStatus.uploading && (
<Button type="text" class="!h-22px !p-0 mr-16px">
取消生成
</Button>
)}
<img
alt=""
class="cursor-pointer"
src={icon1}
width="14"
@ -196,7 +275,7 @@ export default defineComponent({
<section class="content flex-1 pt-8px px-24px pb-24px">
<div class=" rounded-16px bg-#F7F8FA p-16px flex flex-col items-center mb-24px">
<Upload
v-model:file-list={uploadData.value}
file-list={uploadData.value}
action="/"
multiple
customRequest={handleUpload}
@ -204,15 +283,13 @@ export default defineComponent({
accept={[...imageExtensions, ...videoExtensions, ...documentExtensions].join(',')}
showUploadList={false}
>
<div
class="upload-box rounded-8px cursor-pointer h-100px w-full border border-dashed border-#D7D7D9 flex flex-col items-center justify-center w-full">
<div class="upload-box rounded-8px cursor-pointer h-100px w-full border border-dashed border-#D7D7D9 flex flex-col items-center justify-center w-full">
<icon-plus size="14" class="mb-10px color-#55585F" />
<span class="cts">点击或拖拽文件到此处上传</span>
</div>
</Upload>
<p class="mb-4px cts !color-#939499 !text-12px !lh-20px">{`视频格式视频格式MP4、AVI、MOV大小<1000MB`}</p>
<p
class="mb-4px cts !color-#939499 !text-12px !lh-20px">{`图片格式PNG、JPG、JPEG、GIF、WEBP、BMP大小<20MB`}</p>
<p class="mb-4px cts !color-#939499 !text-12px !lh-20px">{`图片格式PNG、JPG、JPEG、GIF、WEBP、BMP大小<20MB`}</p>
<p class="cts !color-#939499 !text-12px !lh-20px">{`文本格式TXT、DOC、DOCX、PDF大小<20MB`}</p>
</div>
{renderUploadList()}
@ -227,7 +304,7 @@ export default defineComponent({
type="primary"
onClick={onConfirm}
loading={submitLoading.value}
disabled={!checkSuccessNum.value}
disabled={!uploadSuccessNum.value}
>
确定
</Button>

View File

@ -40,6 +40,62 @@
font-weight: 400;
line-height: 20px;
}
.ant-progress {
display: flex;
align-items: center;
justify-content: center;
.ant-progress-outer {
position: relative;
width: 100%;
height: 6px !important;
margin: 0;
padding: 0;
.ant-progress-inner {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 100px;
height: 6px !important;
background: var(--BG-200, #F2F3F5);
.ant-progress-bg {
height: 6px !important;
background-color: #6D4CFE;
}
}
}
.ant-progress-text {
color: var(--Text-1, #211F24);
font-family: $font-family-regular;
font-size: 12px;
font-style: normal;
font-weight: 400;
.anticon {
font-size: 16px;
}
}
&.ant-progress-status-success {
.ant-progress-outer {
.ant-progress-bg {
background-color: #25C883;
}
}
.ant-progress-text {
color: #25C883;
}
}
}
}
.footer {

View File

@ -4,7 +4,6 @@
:title="isEdit ? '编辑标签' : '添加新标签'"
centered
width="400px"
wrapClassName="tags-manage-modal"
@cancel="onClose"
>
<Form ref="formRef" :model="form" :rules="rules" auto-label-width layout="horizontal">

View File

@ -3,28 +3,21 @@
* @Date: 2025-06-26 17:23:52
-->
<template>
<Modal
v-model:open="visible"
centered
title="删除标签"
width="400px"
wrapClassName="account-manage-modal"
@cancel="onClose"
>
<Modal v-model:open="visible" centered title="删除标签" width="400px" @cancel="onClose">
<div class="flex items-center">
<img :src="icon1" class="mr-12px" height="20" width="20" />
<span>确认删除 "{{ tagName }}" 这个标签吗</span>
</div>
<template #footer>
<Button size="large" @click="onClose">取消</Button>
<Button class="ml-16px" danger size="large" type="primary" @click="onDelete">确认删除</Button>
<Button class="ml-16px" danger size="large" type="primary" @click="onSubmit">确认删除</Button>
</template>
</Modal>
</template>
<script setup>
import { ref } from 'vue';
import { Button, message } from 'ant-design-vue';
import { Button, message, Modal } from 'ant-design-vue';
import { deleteRawMaterialTag } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -35,12 +28,12 @@ const visible = ref(false);
const tagId = ref('');
const tagName = ref('');
function onClose() {
const onClose = () => {
visible.value = false;
tagId.value = '';
tagName.value = '';
emits('close');
}
};
const open = (record) => {
const { id = '', name = '' } = record;
@ -50,14 +43,14 @@ const open = (record) => {
visible.value = true;
};
async function onDelete() {
const onSubmit = async () => {
const { code } = await deleteRawMaterialTag(tagId.value);
if (code === 200) {
message.success('删除成功');
emits('success');
onClose();
}
}
};
defineExpose({ open });
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB