feat: 优化原料库上传功能和标签管理
- 在 `AddRawMaterialDrawer` 组件中添加 `onUpdate` 事件以刷新数据 - 更新 `constants.ts` 中操作列的宽度 - 增加 Ant Select 和 Modal 的样式 - 新增批量添加、修改和详情的 API 函数 - 优化 `add-raw-material-drawer` 组件,增加标签输入和删除确认模态框 - 更新 `ant-select.scss` 和 `ant-modal.scss` 样式文件
This commit is contained in:
@ -191,3 +191,19 @@ export const putRawMaterialTag = (params = {}) => {
|
||||
export const deleteRawMaterialTag = (id: string) => {
|
||||
return Http.delete(`/v1/raw-material-tags/${id}`);
|
||||
};
|
||||
|
||||
// 原料库-本地批量添加
|
||||
export const postBatchRawMaterial = (params = {}) => {
|
||||
return Http.post('/v1/raw-materials/batch', params);
|
||||
};
|
||||
|
||||
// 原料库-修改
|
||||
export const putRawMaterial = (params = {}) => {
|
||||
const { id, ...rest } = params as { id: string; [key: string]: any };
|
||||
return Http.put(`/v1/raw-material/${id}`, rest);
|
||||
};
|
||||
|
||||
// 原料库-详情
|
||||
export const getRawMaterialDetail = (id: string) => {
|
||||
return Http.get(`/v1/raw-material/${id}`);
|
||||
};
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
}
|
||||
}
|
||||
.ant-modal-body {
|
||||
padding: 24px 20px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
.ant-modal-footer {
|
||||
margin-top: 0;
|
||||
@ -38,5 +38,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-confirm-body-wrapper {
|
||||
.ant-modal-confirm-title {
|
||||
.anticon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-confirm-content {
|
||||
margin-top: 8px;
|
||||
color: var(--Text-2, #55585F);
|
||||
font-family: $font-family-regular;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
|
||||
.ant-modal-confirm-btns {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,20 @@
|
||||
border-color: $color-error !important;
|
||||
}
|
||||
|
||||
&:not(.ant-select-disabled) {
|
||||
&:hover {
|
||||
.ant-select-selector {
|
||||
border-color: rgb(var(--primary-6)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-disabled {
|
||||
.ant-select-selector {
|
||||
background-color: var(--BG-200, #f2f3f5) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
@ -57,6 +71,7 @@
|
||||
|
||||
&.ant-select-multiple {
|
||||
.ant-select-selector {
|
||||
height: fit-content !important;
|
||||
padding: 0 12px 0 4px !important;
|
||||
|
||||
.ant-select-selection-overflow-item {
|
||||
|
||||
@ -3,6 +3,13 @@
|
||||
padding: 8px 12px 4px 12px;
|
||||
}
|
||||
|
||||
&:not(.ant-input-textarea-disabled) {
|
||||
&:hover {
|
||||
.ant-input {
|
||||
border-color: rgb(var(--primary-6)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.ant-input-textarea-show-count {
|
||||
&::after {
|
||||
position: absolute;
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
<script lang="tsx">
|
||||
import { Drawer, Button, Upload, Table, Input, Progress } from 'ant-design-vue';
|
||||
import { Drawer, Button, Upload, Table, Input, Progress, message, Select, Modal } from 'ant-design-vue';
|
||||
|
||||
const { Column } = Table;
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
import CommonSelect from '@/components/common-select';
|
||||
import ImgLazyLoad from '@/components/img-lazy-load';
|
||||
import TextOverTips from '@/components/text-over-tips';
|
||||
|
||||
import { formatFileSize, getVideoInfo, getFileExtension, formatUploadSpeed } from '@/utils/tools';
|
||||
import { getRawMaterialTagsList } from '@/api/all/generationWorkshop';
|
||||
import { formatFileSize, getVideoInfo, getFileExtension } from '@/utils/tools';
|
||||
import { getRawMaterialTagsList, postBatchRawMaterial, posRawMaterialTags } from '@/api/all/generationWorkshop';
|
||||
import { getFilePreSignedUrl } from '@/api/all/common';
|
||||
|
||||
import icon1 from '@/assets/img/media-account/icon-delete.png';
|
||||
@ -26,11 +28,13 @@ enum EnumUploadStatus {
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup(_, { attrs, slots, expose }) {
|
||||
emits: ['update'],
|
||||
setup(_, { emit, expose }) {
|
||||
const visible = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const uploadData = ref([]);
|
||||
const tagData = ref([]);
|
||||
const modalRef = ref(null);
|
||||
|
||||
const uploadSuccessNum = computed(() => {
|
||||
return uploadData.value.filter((item) => item.uploadStatus === EnumUploadStatus.done).length;
|
||||
@ -43,6 +47,80 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const handleTagChange = (record) => {
|
||||
console.log(record.tag_ids);
|
||||
};
|
||||
|
||||
// 添加处理标签输入的函数
|
||||
const handleTagInputPressEnter = async (e, record) => {
|
||||
const inputValue = e.target.value.trim();
|
||||
|
||||
if (!inputValue) return;
|
||||
|
||||
if (record.tag_ids.length >= 5) {
|
||||
message.warning('最多选择5个');
|
||||
return;
|
||||
}
|
||||
|
||||
const _target = tagData.value.find((item) => item.name === inputValue);
|
||||
if (_target) {
|
||||
record.tag_ids = [...record.tag_ids, _target.id];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { code, data } = await posRawMaterialTags({ name: inputValue });
|
||||
|
||||
if (code === 200 && data) {
|
||||
tagData.value.push({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
});
|
||||
e.target.value = '';
|
||||
|
||||
record.tag_ids = [...record.tag_ids, data.id];
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('添加标签失败');
|
||||
}
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
if (!uploadData.value.length) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
modalRef.value = Modal.warning({
|
||||
title: '确定要取消上传吗?',
|
||||
content: '取消后上传将中断,已传输的内容不会保存',
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
centered: true,
|
||||
footer: (
|
||||
<div class="flex items-center justify-end mt-24px">
|
||||
<Button
|
||||
onClick={() => {
|
||||
modalRef.value.destroy();
|
||||
onClose();
|
||||
}}
|
||||
class="mr-12px"
|
||||
>
|
||||
确认取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
modalRef.value.destroy();
|
||||
}}
|
||||
>
|
||||
继续上传
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const open = () => {
|
||||
getTagData();
|
||||
visible.value = true;
|
||||
@ -53,6 +131,8 @@ export default defineComponent({
|
||||
|
||||
uploadData.value = [];
|
||||
tagData.value = [];
|
||||
modalRef.value?.destroy();
|
||||
modalRef.value = null;
|
||||
submitLoading.value = false;
|
||||
};
|
||||
|
||||
@ -76,18 +156,21 @@ export default defineComponent({
|
||||
statusText = '文件大小超出限制';
|
||||
}
|
||||
|
||||
const { fileTypeLabel, cover } = await getFileInfo(file);
|
||||
const { fileTypeLabel, cover, fileType } = await getFileInfo(file);
|
||||
|
||||
const currentData = {
|
||||
uid,
|
||||
name,
|
||||
// type,
|
||||
type: fileType,
|
||||
uploadStatus: statusText ? EnumUploadStatus.error : EnumUploadStatus.uploading,
|
||||
statusText,
|
||||
percent: 0,
|
||||
size: formatFileSize(size),
|
||||
size,
|
||||
fileTypeLabel,
|
||||
tag_ids: [],
|
||||
cover,
|
||||
file: '',
|
||||
};
|
||||
uploadData.value.push(currentData);
|
||||
|
||||
@ -95,7 +178,9 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const response = await getFilePreSignedUrl({ suffix: getFileExtension(name) });
|
||||
const { upload_url } = response?.data;
|
||||
const { upload_url, file_url } = response?.data;
|
||||
const _target = uploadData.value.find((item) => item.uid === uid);
|
||||
_target.file = file_url;
|
||||
if (!upload_url) {
|
||||
throw new Error('未能获取有效的预签名上传地址');
|
||||
}
|
||||
@ -104,7 +189,6 @@ export default defineComponent({
|
||||
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;
|
||||
@ -123,21 +207,25 @@ export default defineComponent({
|
||||
if (imageExtensions.includes(ext)) {
|
||||
return {
|
||||
fileTypeLabel: '图片',
|
||||
fileType: 0,
|
||||
cover: URL.createObjectURL(file),
|
||||
};
|
||||
} else if (videoExtensions.includes(ext)) {
|
||||
const { firstFrame } = await getVideoInfo(file);
|
||||
return {
|
||||
fileTypeLabel: '视频',
|
||||
fileType: 1,
|
||||
cover: firstFrame,
|
||||
};
|
||||
} else if (documentExtensions.includes(ext)) {
|
||||
return {
|
||||
fileTypeLabel: '文本',
|
||||
fileType: 2,
|
||||
cover: icon2,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
fileType: '',
|
||||
fileTypeLabel: '其他',
|
||||
cover: '',
|
||||
};
|
||||
@ -145,10 +233,52 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
const onConfirm = async () => {
|
||||
console.log('onConfirm');
|
||||
const hasUploading = uploadData.value.some((item) => item.uploadStatus === EnumUploadStatus.uploading);
|
||||
if (hasUploading) {
|
||||
modalRef.value = Modal.warning({
|
||||
title: '上传未完成',
|
||||
content: <p class="h-22px">当前原料正在上传中,关闭弹窗将导致上传失败,请等待上传完成后再点击“确定”</p>,
|
||||
okText: '我知道了',
|
||||
centered: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { code, data } = await postBatchRawMaterial({ raw_materials: uploadData.value });
|
||||
if (code === 200) {
|
||||
message.success('上传成功');
|
||||
emit('update');
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
const handleDelete = (file) => {
|
||||
const openDeleteModal = (file) => {
|
||||
modalRef.value = Modal.warning({
|
||||
title: '确定删除该文件吗?',
|
||||
content: <p class="h-22px"></p>,
|
||||
cancelText: '取消',
|
||||
okText: '确定',
|
||||
centered: true,
|
||||
footer: (
|
||||
<div class="flex items-center justify-end mt-24px">
|
||||
<Button
|
||||
onClick={() => {
|
||||
modalRef.value.destroy();
|
||||
}}
|
||||
class="mr-12px"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
uploadData.value = uploadData.value.filter((item) => item.uid !== file.uid);
|
||||
modalRef.value.destroy();
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const renderUploadStatus = (record) => {
|
||||
@ -165,7 +295,7 @@ export default defineComponent({
|
||||
<span class="upload-text ml-26px">
|
||||
{record.uploadStatus === EnumUploadStatus.done ? '上传成功' : '上传中'}
|
||||
</span>
|
||||
<Progress percent={record.percent} class="m-0 p-0 " strokeColor="#25C883" />
|
||||
<Progress percent={record.percent} class="m-0 p-0 " />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -173,19 +303,23 @@ export default defineComponent({
|
||||
const renderUploadList = () => {
|
||||
if (!uploadData.value.length) return null;
|
||||
|
||||
console.log('renderUploadList', uploadData.value);
|
||||
|
||||
return (
|
||||
<>
|
||||
<p class="cts !color-#939499 mb-10px">
|
||||
已上传:<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">
|
||||
<Table
|
||||
ref="tableRef"
|
||||
dataSource={uploadData.value}
|
||||
pagination={false}
|
||||
class="w-100% flex-1"
|
||||
scroll={{ y: '100%' }}
|
||||
>
|
||||
<Column
|
||||
title="文件名称"
|
||||
dataIndex="name"
|
||||
key="name"
|
||||
width={347}
|
||||
width={385}
|
||||
ellipsis={true}
|
||||
customRender={({ text, record }) => {
|
||||
return (
|
||||
@ -218,39 +352,62 @@ export default defineComponent({
|
||||
width={243}
|
||||
customRender={({ text, record }) => {
|
||||
return (
|
||||
<CommonSelect
|
||||
v-model={record.tag_ids}
|
||||
class="w-full"
|
||||
multiple
|
||||
options={tagData.value}
|
||||
<Select
|
||||
disabled={record.uploadStatus === EnumUploadStatus.uploading}
|
||||
v-model:value={record.tag_ids}
|
||||
mode="tags"
|
||||
size="middle"
|
||||
placeholder="请选择标签"
|
||||
/>
|
||||
allowClear
|
||||
class="w-full"
|
||||
showSearch
|
||||
showArrow
|
||||
maxTagCount={5}
|
||||
maxTagTextLength={5}
|
||||
onChnange={() => handleTagChange(record)}
|
||||
onInputKeyDown={(e) => {
|
||||
// 检测回车键
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleTagInputPressEnter(e, record);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{tagData.value.map((item) => (
|
||||
<Option value={item.id} key={item.id}>
|
||||
{item.name}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Column title="类型" dataIndex="fileTypeLabel" key="fileTypeLabel" width={80} />
|
||||
<Column title="大小" dataIndex="size" key="size" width={100} />
|
||||
<Column
|
||||
title="大小"
|
||||
dataIndex="size"
|
||||
key="size"
|
||||
width={100}
|
||||
customRender={({ record }) => {
|
||||
return formatFileSize(record.size);
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title="操作"
|
||||
key="action"
|
||||
width={118}
|
||||
width={80}
|
||||
fixed="right"
|
||||
align="right"
|
||||
customRender={({ text, record }) => {
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
{record.uploadStatus === EnumUploadStatus.uploading && (
|
||||
<Button type="text" class="!h-22px !p-0 mr-16px">
|
||||
取消生成
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<img
|
||||
alt=""
|
||||
class="cursor-pointer"
|
||||
src={icon1}
|
||||
width="14"
|
||||
height="14"
|
||||
onClick={() => handleDelete(record)}
|
||||
onClick={() => openDeleteModal(record)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@ -272,8 +429,8 @@ export default defineComponent({
|
||||
v-model:open={visible.value}
|
||||
onClose={onClose}
|
||||
>
|
||||
<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">
|
||||
<section class="content flex-1 pt-8px px-24px pb-24px overflow-hidden flex flex-col">
|
||||
<div class="rounded-16px bg-#F7F8FA p-16px flex flex-col items-center mb-24px">
|
||||
<Upload
|
||||
file-list={uploadData.value}
|
||||
action="/"
|
||||
@ -296,16 +453,10 @@ export default defineComponent({
|
||||
</section>
|
||||
<footer class="footer h-68px px-24px flex items-center justify-end">
|
||||
<div class="flex items-center">
|
||||
<Button size="large" onClick={onClose} class="mr-12px">
|
||||
<Button size="large" onClick={onCancel} class="mr-12px">
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onConfirm}
|
||||
loading={submitLoading.value}
|
||||
disabled={!uploadSuccessNum.value}
|
||||
>
|
||||
<Button size="large" type="primary" onClick={onConfirm} loading={submitLoading.value}>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -8,6 +8,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ant-select {
|
||||
.ant-select-selection-item {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-body {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
||||
@ -81,7 +81,7 @@ export const TABLE_COLUMNS = [
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
width: 100,
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
}
|
||||
]
|
||||
|
||||
@ -184,7 +184,7 @@ export default defineComponent({
|
||||
</div>
|
||||
|
||||
<TagsManageModal ref={tagsManageModalRef} />
|
||||
<AddRawMaterialDrawer ref={addRawMaterialDrawerRef} />
|
||||
<AddRawMaterialDrawer ref={addRawMaterialDrawerRef} onUpdate={getData} />
|
||||
<DeleteRawMaterialModal ref={deleteRawMaterialModalRef} onBatchUpdate={onBatchSuccess} onUpdate={getData} />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user