feat: 上传视频/图片处理
This commit is contained in:
@ -16,7 +16,20 @@ export function configAutoImport() {
|
|||||||
'@vueuse/core',
|
'@vueuse/core',
|
||||||
{
|
{
|
||||||
dayjs: [['default', 'dayjs']],
|
dayjs: [['default', 'dayjs']],
|
||||||
'lodash-es': ['cloneDeep', 'omit', 'pick', 'union', 'uniq', 'isNumber', 'uniqBy', 'isEmpty', 'merge', 'debounce', 'isEqual'],
|
'lodash-es': [
|
||||||
|
'cloneDeep',
|
||||||
|
'omit',
|
||||||
|
'pick',
|
||||||
|
'union',
|
||||||
|
'map',
|
||||||
|
'uniq',
|
||||||
|
'isNumber',
|
||||||
|
'uniqBy',
|
||||||
|
'isEmpty',
|
||||||
|
'merge',
|
||||||
|
'debounce',
|
||||||
|
'isEqual',
|
||||||
|
],
|
||||||
'@/hooks': ['useModal'],
|
'@/hooks': ['useModal'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -121,14 +121,147 @@ export function formatFileSize(bytes: number): string {
|
|||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVideoDuration(file: any) {
|
// 修改函数以同时获取视频时长和首帧
|
||||||
|
export function getVideoInfo(file: File): Promise<{ duration: number; firstFrame: string }> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const video = document.createElement('video');
|
const video = document.createElement('video');
|
||||||
video.preload = 'metadata';
|
video.preload = 'metadata';
|
||||||
|
video.crossOrigin = 'anonymous'; // 避免跨域问题
|
||||||
|
video.muted = true; // 静音,避免意外播放声音
|
||||||
|
video.style.position = 'fixed'; // 确保视频元素在DOM中
|
||||||
|
video.style.top = '-1000px'; // 但不可见
|
||||||
|
document.body.appendChild(video); // 添加到DOM
|
||||||
|
|
||||||
|
let hasResolved = false;
|
||||||
|
|
||||||
|
// 先获取元数据(时长)
|
||||||
video.onloadedmetadata = function () {
|
video.onloadedmetadata = function () {
|
||||||
window.URL.revokeObjectURL(video.src);
|
// 视频时长
|
||||||
resolve(video.duration);
|
const duration = video.duration;
|
||||||
|
|
||||||
|
// 尝试将视频定位到非常小的时间点,确保有帧可捕获
|
||||||
|
if (duration > 0) {
|
||||||
|
video.currentTime = Math.min(0.1, duration / 2);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 当视频定位完成后尝试捕获首帧
|
||||||
|
video.onseeked = function () {
|
||||||
|
if (hasResolved) return;
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
window.URL.revokeObjectURL(video.src);
|
||||||
|
document.body.removeChild(video);
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
hasResolved = true;
|
||||||
|
resolve({
|
||||||
|
duration: video.duration,
|
||||||
|
firstFrame: canvas.toDataURL('image/jpeg', 0.9) // 提高质量
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 作为备选方案,监听loadeddata事件
|
||||||
|
video.onloadeddata = function () {
|
||||||
|
if (hasResolved) return;
|
||||||
|
|
||||||
|
// 尝试捕获帧
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = video.videoWidth;
|
||||||
|
canvas.height = video.videoHeight;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否捕获到有效帧(非全黑)
|
||||||
|
const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
if (imageData) {
|
||||||
|
let isAllBlack = true;
|
||||||
|
for (let i = 0; i < imageData.data.length; i += 4) {
|
||||||
|
if (imageData.data[i] > 10 || imageData.data[i+1] > 10 || imageData.data[i+2] > 10) {
|
||||||
|
isAllBlack = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAllBlack) {
|
||||||
|
// 清理
|
||||||
|
window.URL.revokeObjectURL(video.src);
|
||||||
|
document.body.removeChild(video);
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
hasResolved = true;
|
||||||
|
resolve({
|
||||||
|
duration: video.duration,
|
||||||
|
firstFrame: canvas.toDataURL('image/jpeg', 0.9)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是全黑帧,尝试定位到0.1秒
|
||||||
|
if (video.duration > 0) {
|
||||||
|
video.currentTime = 0.1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 设置视频源以触发加载
|
||||||
video.src = URL.createObjectURL(file);
|
video.src = URL.createObjectURL(file);
|
||||||
|
|
||||||
|
// 设置超时,防止长时间无响应
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!hasResolved) {
|
||||||
|
document.body.removeChild(video);
|
||||||
|
resolve({
|
||||||
|
duration: 0,
|
||||||
|
firstFrame: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, 5000); // 5秒超时
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatDuration = (seconds: number) => {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const remainingSecondsAfterHours = seconds % 3600;
|
||||||
|
const minutes = Math.floor(remainingSecondsAfterHours / 60);
|
||||||
|
const remainingSeconds = Math.floor(remainingSecondsAfterHours % 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
if (remainingSecondsAfterHours === 0) {
|
||||||
|
return `${hours}小时`;
|
||||||
|
}
|
||||||
|
if (remainingSeconds === 0) {
|
||||||
|
return `${hours}小时${minutes}分`;
|
||||||
|
}
|
||||||
|
return `${hours}小时${minutes}分${remainingSeconds}秒`;
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
if (remainingSeconds === 0) {
|
||||||
|
return `${minutes}分`;
|
||||||
|
}
|
||||||
|
return `${minutes}分${remainingSeconds}秒`;
|
||||||
|
} else {
|
||||||
|
return `${remainingSeconds}秒`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatUploadSpeed = (bytesPerSecond: number): string => {
|
||||||
|
if (bytesPerSecond < 1024) {
|
||||||
|
return `${bytesPerSecond.toFixed(2)} B/s`;
|
||||||
|
} else if (bytesPerSecond < 1024 * 1024) {
|
||||||
|
return `${(bytesPerSecond / 1024).toFixed(2)} KB/s`;
|
||||||
|
} else {
|
||||||
|
return `${(bytesPerSecond / (1024 * 1024)).toFixed(2)} MB/s`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import CommonSelect from '@/components/common-select';
|
|||||||
import { VueDraggable } from 'vue-draggable-plus';
|
import { VueDraggable } from 'vue-draggable-plus';
|
||||||
import TextOverTips from '@/components/text-over-tips';
|
import TextOverTips from '@/components/text-over-tips';
|
||||||
|
|
||||||
import { formatFileSize, getVideoDuration } from '@/utils/tools';
|
import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from '@/utils/tools';
|
||||||
import { getProjectList } from '@/api/all/propertyMarketing';
|
import { getProjectList } from '@/api/all/propertyMarketing';
|
||||||
|
import { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants.ts';
|
||||||
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
|
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
|
||||||
|
|
||||||
import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
|
import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
|
||||||
@ -15,13 +16,27 @@ import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
|
|||||||
const FORM_RULES = {
|
const FORM_RULES = {
|
||||||
title: [{ required: true, message: '请输入标题' }],
|
title: [{ required: true, message: '请输入标题' }],
|
||||||
};
|
};
|
||||||
|
export const ENUM_UPLOAD_STATUS = {
|
||||||
const ENUM_UPLOAD_STATUS = {
|
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
UPLOADING: 'uploading',
|
UPLOADING: 'uploading',
|
||||||
END: 'end',
|
END: 'end',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const INITIAL_VIDEO_INFO = {
|
||||||
|
name: '',
|
||||||
|
size: '',
|
||||||
|
percent: 0,
|
||||||
|
poster: '',
|
||||||
|
time: '',
|
||||||
|
uploadSpeed: '0 KB/s',
|
||||||
|
startTime: 0,
|
||||||
|
lastTime: 0,
|
||||||
|
lastLoaded: 0,
|
||||||
|
estimatedTime: 0,
|
||||||
|
poster: '',
|
||||||
|
uploadStatus: ENUM_UPLOAD_STATUS.DEFAULT,
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ManuscriptForm',
|
name: 'ManuscriptForm',
|
||||||
props: {
|
props: {
|
||||||
@ -33,124 +48,167 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => FORM_RULES,
|
default: () => FORM_RULES,
|
||||||
},
|
},
|
||||||
|
formData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
emits: ['reValidate', 'change'],
|
},
|
||||||
|
emits: ['reValidate', 'change', 'update:modelValue', 'updateVideoInfo'],
|
||||||
setup(props, { emit, expose }) {
|
setup(props, { emit, expose }) {
|
||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
|
const formData = ref({});
|
||||||
const uploadRef = ref(null);
|
const uploadRef = ref(null);
|
||||||
const projects = ref([]);
|
|
||||||
const uploadStatus = ref(ENUM_UPLOAD_STATUS.DEFAULT);
|
|
||||||
|
|
||||||
const videoInfo = ref({
|
|
||||||
name: '',
|
|
||||||
size: '',
|
|
||||||
percent: 0,
|
|
||||||
poster: '',
|
|
||||||
time: '',
|
|
||||||
});
|
|
||||||
function getFileExtension(filename) {
|
function getFileExtension(filename) {
|
||||||
const match = filename.match(/\.([^.]+)$/);
|
const match = filename.match(/\.([^.]+)$/);
|
||||||
return match ? match[1].toLowerCase() : '';
|
return match ? match[1].toLowerCase() : '';
|
||||||
}
|
}
|
||||||
const isVideo = computed(() => true);
|
const isVideo = computed(() => formData.value.type === EnumManuscriptType.Video);
|
||||||
|
const setVideoInfo = (file) => {
|
||||||
|
formData.value.videoInfo.percent = 0;
|
||||||
|
formData.value.videoInfo.name = file.name;
|
||||||
|
formData.value.videoInfo.size = formatFileSize(file.size);
|
||||||
|
formData.value.videoInfo.startTime = Date.now();
|
||||||
|
formData.value.videoInfo.lastTime = Date.now();
|
||||||
|
formData.value.videoInfo.lastLoaded = 0;
|
||||||
|
formData.value.videoInfo.uploadSpeed = '0 KB/s';
|
||||||
|
emit('updateVideoInfo', formData.value.videoInfo);
|
||||||
|
|
||||||
function formatDuration(seconds) {
|
getVideoInfo(file)
|
||||||
const minutes = Math.floor(seconds / 60);
|
.then(({ duration, firstFrame }) => {
|
||||||
const remainingSeconds = Math.floor(seconds % 60);
|
console.log({ duration, firstFrame });
|
||||||
const milliseconds = Math.floor((seconds % 1) * 100);
|
formData.value.videoInfo.poster = firstFrame;
|
||||||
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}.${milliseconds
|
formData.value.videoInfo.time = formatDuration(duration);
|
||||||
.toString()
|
emit('updateVideoInfo', formData.value.videoInfo);
|
||||||
.padStart(2, '0')}`;
|
})
|
||||||
}
|
.catch((error) => {
|
||||||
|
console.error('获取视频时长失败:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const replaceVideo = async (option) => {
|
const uploadVideo = async (option) => {
|
||||||
try {
|
try {
|
||||||
|
formData.value.videoInfo.uploadStatus = ENUM_UPLOAD_STATUS.UPLOADING;
|
||||||
|
emit('updateVideoInfo', formData.value.videoInfo);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fileItem: { file },
|
fileItem: { file },
|
||||||
} = option;
|
} = option;
|
||||||
|
setVideoInfo(file);
|
||||||
uploadStatus.value = ENUM_UPLOAD_STATUS.UPLOADING;
|
|
||||||
// 重置进度为0
|
|
||||||
videoInfo.value.percent = 0;
|
|
||||||
videoInfo.value.name = file.name;
|
|
||||||
videoInfo.value.size = formatFileSize(file.size);
|
|
||||||
|
|
||||||
// const duration = getVideoDuration(file);
|
|
||||||
// videoInfo.value.time = formatDuration(duration);
|
|
||||||
|
|
||||||
const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) });
|
const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) });
|
||||||
const { file_name, upload_url } = response?.data;
|
const { file_name, upload_url, file_url } = response?.data;
|
||||||
if (!upload_url) {
|
if (!upload_url) {
|
||||||
throw new Error('未能获取有效的预签名上传地址');
|
throw new Error('未能获取有效的预签名上传地址');
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = new Blob([file], { type: file.type });
|
const blob = new Blob([file], { type: file.type });
|
||||||
const res = await axios.put(upload_url, blob, {
|
await axios.put(upload_url, blob, {
|
||||||
headers: { 'Content-Type': file.type },
|
headers: { 'Content-Type': file.type },
|
||||||
onUploadProgress: (progressEvent) => {
|
onUploadProgress: (progressEvent) => {
|
||||||
const percentCompleted = Math.round(progressEvent.progress * 100);
|
const percentCompleted = Math.round(progressEvent.progress * 100);
|
||||||
videoInfo.value.percent = percentCompleted;
|
formData.value.videoInfo.percent = percentCompleted;
|
||||||
|
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const currentLoaded = progressEvent.loaded;
|
||||||
|
const totalSize = progressEvent.total;
|
||||||
|
|
||||||
|
if (formData.value.videoInfo.lastLoaded === 0) {
|
||||||
|
formData.value.videoInfo.lastLoaded = currentLoaded;
|
||||||
|
formData.value.videoInfo.lastTime = currentTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeDiff = (currentTime - formData.value.videoInfo.lastTime) / 1000;
|
||||||
|
const bytesDiff = currentLoaded - formData.value.videoInfo.lastLoaded;
|
||||||
|
|
||||||
|
// 避免频繁更新,至少间隔200ms计算一次速率
|
||||||
|
if (timeDiff >= 0.2) {
|
||||||
|
const bytesPerSecond = bytesDiff / timeDiff;
|
||||||
|
formData.value.videoInfo.uploadSpeed = formatUploadSpeed(bytesPerSecond);
|
||||||
|
formData.value.videoInfo.lastLoaded = currentLoaded;
|
||||||
|
formData.value.videoInfo.lastTime = currentTime;
|
||||||
|
|
||||||
|
// 计算预估剩余时间
|
||||||
|
if (totalSize && bytesPerSecond > 0) {
|
||||||
|
const remainingBytes = totalSize - currentLoaded;
|
||||||
|
const remainingSeconds = remainingBytes / bytesPerSecond;
|
||||||
|
formData.value.videoInfo.estimatedTime = formatDuration(remainingSeconds);
|
||||||
|
} else {
|
||||||
|
formData.value.videoInfo.estimatedTime = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('updateVideoInfo', formData.value.videoInfo);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
props.modelValue.files.push(file_name);
|
|
||||||
|
formData.value.files.push(file_url);
|
||||||
|
onChange();
|
||||||
} finally {
|
} finally {
|
||||||
uploadStatus.value = ENUM_UPLOAD_STATUS.END;
|
formData.value.videoInfo.uploadStatus = ENUM_UPLOAD_STATUS.END;
|
||||||
|
emit('updateVideoInfo', formData.value.videoInfo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChange = () => {
|
||||||
|
emit('change', formData.value);
|
||||||
|
};
|
||||||
// 文件上传处理
|
// 文件上传处理
|
||||||
const handleUpload = (option) => {
|
const uploadImage = async (option) => {
|
||||||
const {
|
const {
|
||||||
fileItem: { file },
|
fileItem: { file },
|
||||||
} = option;
|
} = option;
|
||||||
|
|
||||||
// 验证文件类型
|
|
||||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
|
||||||
if (!allowedTypes.includes(file.type)) {
|
|
||||||
AMessage.error('只能上传图片文件!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证文件大小 (5MB)
|
// 验证文件大小 (5MB)
|
||||||
const maxSize = 5 * 1024 * 1024;
|
// const maxSize = 5 * 1024 * 1024;
|
||||||
if (file.size > maxSize) {
|
// if (file.size > maxSize) {
|
||||||
AMessage.error('图片大小不能超过5MB!');
|
// AMessage.error('图片大小不能超过5MB!');
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 验证文件数量
|
// 验证文件数量
|
||||||
if (props.modelValue.files?.length >= 18) {
|
if (formData.value.files?.length >= 18) {
|
||||||
AMessage.error('最多只能上传18张图片!');
|
AMessage.error('最多只能上传18张图片!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
props.modelValue.files.push(URL.createObjectURL(file));
|
const response = await getImagePreSignedUrl({ suffix: getFileExtension(file.name) });
|
||||||
emit('change');
|
const { file_name, upload_url, file_url } = response?.data;
|
||||||
|
|
||||||
|
const blob = new Blob([file], { type: file.type });
|
||||||
|
await axios.put(upload_url, blob, {
|
||||||
|
headers: { 'Content-Type': file.type },
|
||||||
|
});
|
||||||
|
|
||||||
|
formData.value.files.push(file_url);
|
||||||
|
onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 删除文件
|
// 删除文件
|
||||||
const handleDeleteFile = (index) => {
|
const handleDeleteFile = (index) => {
|
||||||
props.modelValue.files.splice(index, 1);
|
formData.value.files.splice(index, 1);
|
||||||
emit('change');
|
onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取项目列表
|
// // 获取项目列表
|
||||||
const getProjects = async () => {
|
// const getProjects = async () => {
|
||||||
try {
|
// try {
|
||||||
const { code, data } = await getProjectList();
|
// const { code, data } = await getProjectList();
|
||||||
if (code === 200) {
|
// if (code === 200) {
|
||||||
projects.value = data;
|
// projects.value = data;
|
||||||
}
|
// }
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error('获取项目列表失败:', error);
|
// console.error('获取项目列表失败:', error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
// 表单验证
|
// 表单验证
|
||||||
const validate = () => {
|
const validate = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
formRef.value?.validate((errors) => {
|
formRef.value?.validate((errors) => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
reject(props.modelValue);
|
reject(formData.value);
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@ -160,6 +218,7 @@ export default {
|
|||||||
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
|
formData.value = {};
|
||||||
formRef.value?.resetFields?.();
|
formRef.value?.resetFields?.();
|
||||||
formRef.value?.clearValidate?.();
|
formRef.value?.clearValidate?.();
|
||||||
};
|
};
|
||||||
@ -170,13 +229,13 @@ export default {
|
|||||||
ref={uploadRef}
|
ref={uploadRef}
|
||||||
action="/"
|
action="/"
|
||||||
draggable
|
draggable
|
||||||
custom-request={replaceVideo}
|
custom-request={uploadVideo}
|
||||||
accept=".mp4,.webm,.ogg,.mov,.avi,.flv,.wmv,.mkv"
|
accept=".mp4,.webm,.ogg,.mov,.avi,.flv,.wmv,.mkv"
|
||||||
show-file-list={false}
|
show-file-list={false}
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
'upload-button': () => {
|
'upload-button': () => {
|
||||||
if (uploadStatus.value === ENUM_UPLOAD_STATUS.DEFAULT) {
|
if (formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT) {
|
||||||
return (
|
return (
|
||||||
<div class="upload-box">
|
<div class="upload-box">
|
||||||
<icon-plus size="14" class="mb-16px color-#3C4043" />
|
<icon-plus size="14" class="mb-16px color-#3C4043" />
|
||||||
@ -192,8 +251,8 @@ export default {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
const renderVideo = () => {
|
const renderVideo = () => {
|
||||||
const isUploading = uploadStatus.value === ENUM_UPLOAD_STATUS.UPLOADING;
|
const isUploading = formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.UPLOADING;
|
||||||
const isEnd = uploadStatus.value === ENUM_UPLOAD_STATUS.END;
|
const isEnd = formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.END;
|
||||||
return (
|
return (
|
||||||
<FormItem
|
<FormItem
|
||||||
field="files"
|
field="files"
|
||||||
@ -206,7 +265,7 @@ export default {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{uploadStatus.value === ENUM_UPLOAD_STATUS.DEFAULT ? (
|
{formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT ? (
|
||||||
renderVideoUpload()
|
renderVideoUpload()
|
||||||
) : (
|
) : (
|
||||||
<div class="flex items-center justify-between p-12px rounded-8px bg-#F7F8FA w-784px">
|
<div class="flex items-center justify-between p-12px rounded-8px bg-#F7F8FA w-784px">
|
||||||
@ -216,20 +275,31 @@ export default {
|
|||||||
<icon-loading size="24" class="color-#B1B2B5" />
|
<icon-loading size="24" class="color-#B1B2B5" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<img src={''} class="w-80 h-80 object-cover mr-16px rounded-8px" />
|
<img src={formData.value.videoInfo.poster} class="w-80 h-80 object-cover mr-16px rounded-8px" />
|
||||||
)}
|
)}
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<TextOverTips context={videoInfo.value.name} class="mb-4px cts !text-14px !lh-22px color-#211F24" />
|
<TextOverTips
|
||||||
|
context={formData.value.videoInfo.name}
|
||||||
|
class="mb-4px cts !text-14px !lh-22px color-#211F24"
|
||||||
|
/>
|
||||||
{isEnd ? (
|
{isEnd ? (
|
||||||
<p>
|
<p>
|
||||||
<span class="cts color-#939499 mr-24px">视频大小:{videoInfo.value.size}</span>
|
<span class="cts color-#939499 mr-24px">视频大小:{formData.value.videoInfo.size}</span>
|
||||||
<span class="cts color-#939499">视频时长:2.73s</span>
|
<span class="cts color-#939499">视频时长:{formData.value.videoInfo.time}</span>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
<div class="flex items-center mr-24px w-100px">
|
||||||
<icon-loading size="16" class="color-#6D4CFE mr-8px" />
|
<icon-loading size="16" class="color-#6D4CFE mr-8px" />
|
||||||
<span class="cts !color-#6D4CFE mr-4px">上传中</span>
|
<span class="cts !color-#6D4CFE mr-4px">上传中</span>
|
||||||
<span class="cts !color-#6D4CFE mr-4px">{videoInfo.value.percent}%</span>
|
<span class="cts !color-#6D4CFE ">{formData.value.videoInfo.percent}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center w-160px mr-24px">
|
||||||
|
<span class="cts color-#939499">上传速度:{formData.value.videoInfo.uploadSpeed}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="cts color-#939499">预估剩余时间:{formData.value.videoInfo.estimatedTime}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -249,7 +319,7 @@ export default {
|
|||||||
label: () => (
|
label: () => (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="cts !color-#211F24 mr-4px">图片</span>
|
<span class="cts !color-#211F24 mr-4px">图片</span>
|
||||||
<span class="cts mr-8px !color-#939499">{`(${props.modelValue.files?.length ?? 0}/18)`}</span>
|
<span class="cts mr-8px !color-#939499">{`(${formData.value.files?.length ?? 0}/18)`}</span>
|
||||||
<span class="cts !color-#939499">第一张为首图,支持拖拽排序</span>
|
<span class="cts !color-#939499">第一张为首图,支持拖拽排序</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@ -258,8 +328,8 @@ export default {
|
|||||||
<div>
|
<div>
|
||||||
<div class="">
|
<div class="">
|
||||||
{/* 已上传的图片列表 */}
|
{/* 已上传的图片列表 */}
|
||||||
<VueDraggable v-model={props.modelValue.files} class="grid grid-cols-7 gap-8px">
|
<VueDraggable v-model={formData.value.files} class="grid grid-cols-7 gap-8px">
|
||||||
{props.modelValue.files?.map((file, index) => (
|
{formData.value.files?.map((file, index) => (
|
||||||
<div key={index} class="group relative cursor-move">
|
<div key={index} class="group relative cursor-move">
|
||||||
<img src={file} class="w-100px h-100px object-cover rounded-8px border-1px border-#E6E6E8" />
|
<img src={file} class="w-100px h-100px object-cover rounded-8px border-1px border-#E6E6E8" />
|
||||||
<img
|
<img
|
||||||
@ -273,16 +343,16 @@ export default {
|
|||||||
))}
|
))}
|
||||||
</VueDraggable>
|
</VueDraggable>
|
||||||
</div>
|
</div>
|
||||||
{props.modelValue.files?.length < 18 && (
|
{formData.value.files?.length < 18 && (
|
||||||
<Upload
|
<Upload
|
||||||
ref={uploadRef}
|
ref={uploadRef}
|
||||||
action="/"
|
action="/"
|
||||||
draggable
|
draggable
|
||||||
custom-request={handleUpload}
|
custom-request={uploadImage}
|
||||||
accept=".jpg,.jpeg,.png,.gif,.webp"
|
accept=".jpg,.jpeg,.png,.gif,.webp"
|
||||||
show-file-list={false}
|
show-file-list={false}
|
||||||
multiple
|
multiple
|
||||||
limit={18 - props.modelValue.files?.length}
|
limit={18 - formData.value.files?.length}
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
'upload-button': () => (
|
'upload-button': () => (
|
||||||
@ -305,18 +375,21 @@ export default {
|
|||||||
resetForm,
|
resetForm,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件挂载时获取项目列表
|
watch(
|
||||||
onMounted(() => {
|
() => props.formData,
|
||||||
getProjects();
|
(val) => {
|
||||||
});
|
formData.value = val;
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<Form ref={formRef} model={props.modelValue} rules={props.rules} layout="vertical" auto-label-width>
|
<Form ref={formRef} model={formData.value} rules={props.rules} layout="vertical" auto-label-width>
|
||||||
<FormItem label="标题" field="title" required>
|
<FormItem label="标题" field="title" required>
|
||||||
<Input
|
<Input
|
||||||
v-model={props.modelValue.title}
|
v-model={formData.value.title}
|
||||||
onChange={() => {
|
onInput={() => {
|
||||||
emit('change');
|
onChange();
|
||||||
emit('reValidate');
|
emit('reValidate');
|
||||||
}}
|
}}
|
||||||
placeholder="请输入标题"
|
placeholder="请输入标题"
|
||||||
@ -327,10 +400,10 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem label="作品描述" field="desc">
|
<FormItem label="作品描述" field="content">
|
||||||
<Textarea
|
<Textarea
|
||||||
v-model={props.modelValue.content}
|
v-model={formData.value.content}
|
||||||
onChange={() => emit('change')}
|
onInput={onChange}
|
||||||
placeholder="请输入作品描述"
|
placeholder="请输入作品描述"
|
||||||
size="large"
|
size="large"
|
||||||
class="h-200px !w-784px"
|
class="h-200px !w-784px"
|
||||||
@ -341,16 +414,16 @@ export default {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
{isVideo.value ? renderVideo() : renderImage()}
|
{isVideo.value ? renderVideo() : renderImage()}
|
||||||
|
|
||||||
<FormItem label="所属项目" field="project_ids">
|
{/* <FormItem label="所属项目" field="project_ids">
|
||||||
<CommonSelect
|
<CommonSelect
|
||||||
v-model={props.modelValue.project_ids}
|
v-model={formData.value.project_ids}
|
||||||
onChange={() => emit('change')}
|
onChange={() => emit('change')}
|
||||||
options={projects.value}
|
options={projects.value}
|
||||||
placeholder="请选择所属项目"
|
placeholder="请选择所属项目"
|
||||||
size="large"
|
size="large"
|
||||||
class="!w-280px"
|
class="!w-280px"
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>*/}
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -358,35 +431,5 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.cts {
|
@import './style.scss';
|
||||||
font-family: $font-family-regular;
|
|
||||||
font-size: 12px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
.upload-box {
|
|
||||||
display: flex;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px dashed var(--Border-1, #d7d7d9);
|
|
||||||
background: var(--BG-200, #f2f3f5);
|
|
||||||
&:hover {
|
|
||||||
background: var(--Primary-1, #e6e6e8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.group {
|
|
||||||
cursor: move;
|
|
||||||
border-radius: 8px;
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%),
|
|
||||||
url(<path-to-image>) lightgray 0px -40.771px / 100% 149.766% no-repeat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
.cts {
|
||||||
|
font-family: $font-family-regular;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
.upload-box {
|
||||||
|
display: flex;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px dashed var(--Border-1, #d7d7d9);
|
||||||
|
background: var(--BG-200, #f2f3f5);
|
||||||
|
&:hover {
|
||||||
|
background: var(--Primary-1, #e6e6e8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.group {
|
||||||
|
cursor: move;
|
||||||
|
border-radius: 8px;
|
||||||
|
&:hover {
|
||||||
|
background: linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%),
|
||||||
|
url(<path-to-image>) lightgray 0px -40.771px / 100% 149.766% no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,17 @@
|
|||||||
<script lang="jsx">
|
<script lang="jsx">
|
||||||
import { Button, Message as AMessage } from '@arco-design/web-vue';
|
import { Button, Message as AMessage } from '@arco-design/web-vue';
|
||||||
import EditForm from '../components/edit-form';
|
import EditForm, { ENUM_UPLOAD_STATUS, INITIAL_VIDEO_INFO } from '../components/edit-form';
|
||||||
import CancelEditModal from './cancel-edit-modal.vue';
|
import CancelEditModal from './cancel-edit-modal.vue';
|
||||||
import { getWorksDetail, putWorksUpdate } from '@/api/all/generationWorkshop';
|
import { getWorksDetail, putWorksUpdate } from '@/api/all/generationWorkshop';
|
||||||
|
import { EnumManuscriptType } from '@/views/creative-generation-workshop/manuscript/list/constants.ts';
|
||||||
|
|
||||||
const INITIAL_DATA = {
|
const INITIAL_DATA = {
|
||||||
title: '',
|
title: '',
|
||||||
desc: '',
|
|
||||||
content: '',
|
content: '',
|
||||||
project_ids: [],
|
type: EnumManuscriptType.Video,
|
||||||
|
// project_ids: [],
|
||||||
files: [],
|
files: [],
|
||||||
|
videoInfo: cloneDeep(INITIAL_VIDEO_INFO),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -39,7 +41,8 @@ export default {
|
|||||||
|
|
||||||
const onSave = async (check = false) => {
|
const onSave = async (check = false) => {
|
||||||
formRef.value?.validate().then(async () => {
|
formRef.value?.validate().then(async () => {
|
||||||
const { code, data } = await putWorksUpdate({ id: workId.value, ...dataSource.value });
|
const filteredWorks = omit(dataSource.value, 'videoInfo');
|
||||||
|
const { code, data } = await putWorksUpdate({ id: workId.value, ...filteredWorks });
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
AMessage.success('保存成功');
|
AMessage.success('保存成功');
|
||||||
isSaved.value = true;
|
isSaved.value = true;
|
||||||
@ -47,7 +50,7 @@ export default {
|
|||||||
if (check) {
|
if (check) {
|
||||||
router.push({ name: 'ManuscriptCheck', params: { id: workId.value } });
|
router.push({ name: 'ManuscriptCheck', params: { id: workId.value } });
|
||||||
} else {
|
} else {
|
||||||
onBack()
|
onBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -63,8 +66,11 @@ export default {
|
|||||||
remoteDataSource.value = cloneDeep(data);
|
remoteDataSource.value = cloneDeep(data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const handleFormChange = () => {
|
const onChange = (val) => {
|
||||||
console.log('handleFormChange');
|
dataSource.value = val;
|
||||||
|
};
|
||||||
|
const onUpdateVideoInfo = (newVideoInfo) => {
|
||||||
|
dataSource.value.videoInfo = { ...dataSource.value.videoInfo, ...newVideoInfo };
|
||||||
};
|
};
|
||||||
const onBack = () => {
|
const onBack = () => {
|
||||||
router.push({ name: 'ManuscriptList' });
|
router.push({ name: 'ManuscriptList' });
|
||||||
@ -85,7 +91,12 @@ export default {
|
|||||||
<span class="cts bold !color-#1D2129">编辑内容稿件</span>
|
<span class="cts bold !color-#1D2129">编辑内容稿件</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 overflow-y-auto p-24px bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid">
|
<div class="flex-1 overflow-y-auto p-24px bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid">
|
||||||
<EditForm ref={formRef} v-model={dataSource.value} onChange={handleFormChange} />
|
<EditForm
|
||||||
|
ref={formRef}
|
||||||
|
formData={dataSource.value}
|
||||||
|
onChange={onChange}
|
||||||
|
onUpdateVideoInfo={onUpdateVideoInfo}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end items-center px-16px py-20px w-full bg-#fff footer-row">
|
<div class="flex justify-end items-center px-16px py-20px w-full bg-#fff footer-row">
|
||||||
|
|||||||
@ -120,6 +120,11 @@ export default {
|
|||||||
|
|
||||||
// 手写提交处理
|
// 手写提交处理
|
||||||
const handleHandwriteSubmit = () => {
|
const handleHandwriteSubmit = () => {
|
||||||
|
if(!form.value.link) {
|
||||||
|
AMessage.warning('请输入上传链接!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
copy(form.value.link);
|
copy(form.value.link);
|
||||||
AMessage.success('复制成功!');
|
AMessage.success('复制成功!');
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script lang="jsx">
|
<script lang="jsx">
|
||||||
import { Button, Message as AMessage } from '@arco-design/web-vue';
|
import { Button, Message as AMessage } from '@arco-design/web-vue';
|
||||||
import TextOverTips from '@/components/text-over-tips';
|
import TextOverTips from '@/components/text-over-tips';
|
||||||
import EditForm from '../components/edit-form';
|
import EditForm, { ENUM_UPLOAD_STATUS, INITIAL_VIDEO_INFO } from '../components/edit-form';
|
||||||
import CancelUploadModal from './cancel-upload-modal.vue';
|
import CancelUploadModal from './cancel-upload-modal.vue';
|
||||||
import UploadSuccessModal from './upload-success-modal.vue';
|
import UploadSuccessModal from './upload-success-modal.vue';
|
||||||
|
|
||||||
@ -11,15 +11,14 @@ import { postWorksBatch } from '@/api/all/generationWorkshop.ts';
|
|||||||
import icon1 from '@/assets/img/creative-generation-workshop/icon-photo.png';
|
import icon1 from '@/assets/img/creative-generation-workshop/icon-photo.png';
|
||||||
import icon2 from '@/assets/img/creative-generation-workshop/icon-video.png';
|
import icon2 from '@/assets/img/creative-generation-workshop/icon-video.png';
|
||||||
|
|
||||||
const generateMockData = (count = 20) =>
|
const generateMockData = (count = 4) =>
|
||||||
Array.from({ length: count }, (_, i) => ({
|
Array.from({ length: count }, (_, i) => ({
|
||||||
id: `${i + 1}`,
|
id: `${i + 1}`,
|
||||||
title: '',
|
title: `标题${i + 1}`,
|
||||||
desc: '',
|
|
||||||
content: '挖到宝了!这个平价好物让我素颜出门都自信✨挖到宝了!这个平价好物让我素颜出门都自信✨',
|
content: '挖到宝了!这个平价好物让我素颜出门都自信✨挖到宝了!这个平价好物让我素颜出门都自信✨',
|
||||||
type: 1,
|
type: i % 2 === 0 ? EnumManuscriptType.Image : EnumManuscriptType.Video,
|
||||||
project_ids: [],
|
|
||||||
files: [],
|
files: [],
|
||||||
|
videoInfo: cloneDeep(INITIAL_VIDEO_INFO),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -29,6 +28,7 @@ export default {
|
|||||||
setup(props, { emit, expose }) {
|
setup(props, { emit, expose }) {
|
||||||
const formRef = ref(null);
|
const formRef = ref(null);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
const cancelUploadModal = ref(null);
|
const cancelUploadModal = ref(null);
|
||||||
const uploadSuccessModal = ref(null);
|
const uploadSuccessModal = ref(null);
|
||||||
const works = ref([]);
|
const works = ref([]);
|
||||||
@ -43,6 +43,14 @@ export default {
|
|||||||
|
|
||||||
const validateDataSource = () => {
|
const validateDataSource = () => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
const uploadingVideos = works.value.filter(
|
||||||
|
(item) => item.videoInfo?.uploadStatus === ENUM_UPLOAD_STATUS.UPLOADING,
|
||||||
|
);
|
||||||
|
if (uploadingVideos.length > 0) {
|
||||||
|
AMessage.warning(`有 ${uploadingVideos.length} 个视频正在上传中,请等待上传完成后再提交`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
works.value.forEach((item) => {
|
works.value.forEach((item) => {
|
||||||
if (!item.title?.trim()) {
|
if (!item.title?.trim()) {
|
||||||
errorDataCards.value.push(item);
|
errorDataCards.value.push(item);
|
||||||
@ -64,13 +72,10 @@ export default {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpload = async (e, check = false) => {
|
const onSubmit = async (check) => {
|
||||||
console.log('onUpload', works.value);
|
uploadLoading.value = true;
|
||||||
formRef.value
|
const filteredWorks = map(works.value, (work) => omit(work, 'videoInfo'));
|
||||||
?.validate()
|
const { code, data } = await postWorksBatch({ works: filteredWorks });
|
||||||
.then(() => {
|
|
||||||
validateDataSource().then(async (res) => {
|
|
||||||
const { code, data } = await postWorksBatch({ works: works.value });
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
uploadLoading.value = false;
|
uploadLoading.value = false;
|
||||||
if (check) {
|
if (check) {
|
||||||
@ -79,9 +84,20 @@ export default {
|
|||||||
router.push({ name: 'ManuscriptList' });
|
router.push({ name: 'ManuscriptList' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const onUpload = async (e, check = false) => {
|
||||||
|
console.log('onUpload', works.value);
|
||||||
|
formRef.value
|
||||||
|
?.validate()
|
||||||
|
.then(() => {
|
||||||
|
return validateDataSource();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
onSubmit(check);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
console.log('catch');
|
||||||
errorDataCards.value.push(err);
|
errorDataCards.value.push(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -95,8 +111,9 @@ export default {
|
|||||||
deleteErrorCard(selectCardInfo.value);
|
deleteErrorCard(selectCardInfo.value);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const onselect = (item) => {
|
const onSelect = (item) => {
|
||||||
selectCardInfo.value = item;
|
formRef.value.resetForm();
|
||||||
|
selectCardInfo.value = cloneDeep(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteErrorCard = (item) => {
|
const deleteErrorCard = (item) => {
|
||||||
@ -112,7 +129,7 @@ export default {
|
|||||||
if (item.id === selectCardInfo.value.id) {
|
if (item.id === selectCardInfo.value.id) {
|
||||||
if (works.value.length) {
|
if (works.value.length) {
|
||||||
const _target = works.value[0] ?? {};
|
const _target = works.value[0] ?? {};
|
||||||
selectCardInfo.value = _target;
|
selectCardInfo.value = cloneDeep(_target);
|
||||||
} else {
|
} else {
|
||||||
selectCardInfo.value = {};
|
selectCardInfo.value = {};
|
||||||
}
|
}
|
||||||
@ -149,7 +166,7 @@ export default {
|
|||||||
};
|
};
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
works.value = generateMockData();
|
works.value = generateMockData();
|
||||||
selectCardInfo.value = works.value[0] ?? {};
|
selectCardInfo.value = cloneDeep(works.value[0] ?? {});
|
||||||
};
|
};
|
||||||
const getCardClass = (item) => {
|
const getCardClass = (item) => {
|
||||||
if (selectCardInfo.value.id === item.id) {
|
if (selectCardInfo.value.id === item.id) {
|
||||||
@ -160,6 +177,22 @@ export default {
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChange = (val) => {
|
||||||
|
const index = works.value.findIndex((v) => v.id === selectCardInfo.value.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
works.value[index] = cloneDeep(val);
|
||||||
|
}
|
||||||
|
selectCardInfo.value = val;
|
||||||
|
};
|
||||||
|
const onUpdateVideoInfo = (newVideoInfo) => {
|
||||||
|
const index = works.value.findIndex((v) => v.id === selectCardInfo.value.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
works.value[index].videoInfo = cloneDeep(newVideoInfo);
|
||||||
|
}
|
||||||
|
selectCardInfo.value.videoInfo = { ...selectCardInfo.value.videoInfo, ...newVideoInfo };
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getData();
|
getData();
|
||||||
});
|
});
|
||||||
@ -168,7 +201,13 @@ export default {
|
|||||||
<>
|
<>
|
||||||
<div class="manuscript-upload-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid flex">
|
<div class="manuscript-upload-wrap bg-#fff rounded-8px border-1px border-#D7D7D9 border-solid flex">
|
||||||
<div class="left flex-1 overflow-y-auto p-24px">
|
<div class="left flex-1 overflow-y-auto p-24px">
|
||||||
<EditForm ref={formRef} v-model={selectCardInfo.value} onReValidate={handleReValidate} />
|
<EditForm
|
||||||
|
ref={formRef}
|
||||||
|
formData={selectCardInfo.value}
|
||||||
|
onReValidate={handleReValidate}
|
||||||
|
onChange={onChange}
|
||||||
|
onUpdateVideoInfo={onUpdateVideoInfo}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{works.value.length > 1 && (
|
{works.value.length > 1 && (
|
||||||
<div class="right pt-24px pb-12px flex flex-col">
|
<div class="right pt-24px pb-12px flex flex-col">
|
||||||
@ -185,7 +224,7 @@ export default {
|
|||||||
class={`group relative mb-12px px-8px py-12px flex flex-col rounded-8px bg-#F7F8FA border-1px border-solid border-transparent transition-all duration-300 cursor-pointer hover:bg-#E6E6E8 ${getCardClass(
|
class={`group relative mb-12px px-8px py-12px flex flex-col rounded-8px bg-#F7F8FA border-1px border-solid border-transparent transition-all duration-300 cursor-pointer hover:bg-#E6E6E8 ${getCardClass(
|
||||||
item,
|
item,
|
||||||
)}`}
|
)}`}
|
||||||
onClick={() => onselect(item)}
|
onClick={() => onSelect(item)}
|
||||||
>
|
>
|
||||||
<icon-close-circle-fill
|
<icon-close-circle-fill
|
||||||
size={16}
|
size={16}
|
||||||
|
|||||||
Reference in New Issue
Block a user