feat(upload): 封面上传支持横版与前端压缩

- 新增 imageCompress 与 imagePresets,上传前按规格高质量等比压缩
- 短剧封面拆分为竖版/横版两个上传位,带建议尺寸提示
- 轮播图、创作者中心封面与分集封面接入压缩与提示
This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-29 17:32:56 +08:00
1 parent dea414739e
commit 923b7dce1e
9 files changed
+486 -87

No files matched your search

+4
View File
@@ -29,6 +29,8 @@ export interface DramaItem {
genre?: string
description?: string
cover?: string
/** 横版封面,用于继续观看横幅等横向展示位 */
horizontalCover?: string
language?: string
dramaType?: 'ANIME' | 'LIVE'
dramaTypes?: Array<'ANIME' | 'LIVE'>
@@ -57,6 +59,8 @@ export interface DramaSaveData {
genre?: string
description?: string
cover?: string
/** 横版封面,用于继续观看横幅等横向展示位 */
horizontalCover?: string
language?: string
dramaType?: 'ANIME' | 'LIVE'
dramaTypes?: Array<'ANIME' | 'LIVE'>
+13
View File
@@ -188,6 +188,19 @@
gap: 10px;
}
.creator-cover-actions {
display: flex;
flex-direction: column;
gap: 6px;
}
.creator-cover-hint {
margin: 0;
color: #9a97a8;
font-size: 12px;
line-height: 1.4;
}
.creator-cover-preview {
display: grid;
place-content: center;
+23
View File
@@ -165,6 +165,19 @@
line-height: 1.4;
}
.cover-block + .cover-block {
margin-top: 18px;
padding-top: 16px;
border-top: 1px dashed var(--el-border-color-lighter);
}
.cover-label {
margin: 0 0 8px;
color: var(--el-text-color-regular);
font-size: 13px;
font-weight: 600;
}
.cover-editor {
display: flex;
gap: 18px;
@@ -183,6 +196,12 @@
background: var(--el-fill-color-lighter);
}
.cover-preview-card--landscape {
flex: 0 0 160px;
width: 160px;
height: 90px;
}
.cover-preview-card img {
position: absolute;
inset: 0;
@@ -297,6 +316,10 @@
flex-basis: 128px;
}
.cover-preview-card--landscape {
flex-basis: 160px;
}
.cover-editor-meta {
width: 100%;
}
+154
View File
@@ -0,0 +1,154 @@
// Author: fruit.com.cn
/**
* 上传前的图片压缩:只做「超出目标尺寸才等比缩小 + 同格式高质量重编码」,
* 尺寸没超、体积也不大时原样返回,避免无意义的二次编码损失。
*
* 说明:
* - 保留原格式(JPEG→JPEG / PNG→PNG / WebP→WebP),避免 PNG 透明底被抹掉;
* - GIF / SVG 直接跳过,动图与矢量图重编码会丢帧或失真;
* - 缩放用浏览器原生解码 + canvas,配合 EXIF 方向校正,避免竖拍图躺倒。
*/
export interface ImageCompressOptions {
/** 目标最大宽(像素)。 */
maxWidth: number
/** 目标最大高(像素)。 */
maxHeight: number
/** 重编码质量,默认 0.92,肉眼看不出差异。 */
quality?: number
/** 原图小于该体积且尺寸已达标时直接原样上传,默认 300KB。 */
skipBelowBytes?: number
}
const PASSTHROUGH_TYPES = ['image/gif', 'image/svg+xml']
/** 可压缩的位图格式;其余类型原样返回。 */
export function canCompressImage(file: File): boolean {
return file.type.startsWith('image/') && !PASSTHROUGH_TYPES.includes(file.type)
}
/** 按比例算出目标尺寸;不需要缩放时返回 null。 */
export function fitWithin(
width: number,
height: number,
maxWidth: number,
maxHeight: number
): { width: number; height: number } | null {
if (width <= 0 || height <= 0) return null
const scale = Math.min(maxWidth / width, maxHeight / height, 1)
if (scale >= 1) return null
return {
width: Math.max(1, Math.round(width * scale)),
height: Math.max(1, Math.round(height * scale))
}
}
export async function compressImage(file: File, options: ImageCompressOptions): Promise<File> {
if (!canCompressImage(file)) return file
const quality = options.quality ?? 0.92
const skipBelowBytes = options.skipBelowBytes ?? 300 * 1024
const bitmap = await decode(file)
if (!bitmap) return file
const { width, height } = bitmap
const target = fitWithin(width, height, options.maxWidth, options.maxHeight)
// 尺寸已经在目标范围内、体积也不大:直接用原图,一点不损失。
if (!target && file.size <= skipBelowBytes) {
close(bitmap)
return file
}
const canvas = document.createElement('canvas')
canvas.width = target?.width ?? width
canvas.height = target?.height ?? height
const context = canvas.getContext('2d')
if (!context) {
close(bitmap)
return file
}
context.imageSmoothingEnabled = true
context.imageSmoothingQuality = 'high'
context.drawImage(bitmap.source, 0, 0, canvas.width, canvas.height)
close(bitmap)
const mimeType = outputMime(file.type)
const blob = await toBlob(canvas, mimeType, quality)
if (!blob || blob.size >= file.size) return file
return new File([blob], renameWithMime(file.name, mimeType), {
type: mimeType,
lastModified: file.lastModified
})
}
function outputMime(type: string): string {
if (type === 'image/png' || type === 'image/webp') return type
return 'image/jpeg'
}
function renameWithMime(name: string, mimeType: string): string {
const ext = mimeType === 'image/png' ? 'png' : mimeType === 'image/webp' ? 'webp' : 'jpg'
const base = name.includes('.') ? name.slice(0, name.lastIndexOf('.')) : name
return `${base}.${ext}`
}
interface DecodedBitmap {
source: CanvasImageSource
width: number
height: number
release?: () => void
}
async function decode(file: File): Promise<DecodedBitmap | null> {
if (typeof createImageBitmap === 'function') {
try {
const bitmap = await createImageBitmap(file, { imageOrientation: 'from-image' })
return {
source: bitmap,
width: bitmap.width,
height: bitmap.height,
release: () => bitmap.close()
}
} catch {
// 部分浏览器不支持 imageOrientation 选项,退回 <img> 解码。
}
}
const url = URL.createObjectURL(file)
try {
const image = await loadImage(url)
return {
source: image,
width: image.naturalWidth,
height: image.naturalHeight,
release: () => URL.revokeObjectURL(url)
}
} catch {
URL.revokeObjectURL(url)
return null
}
}
function close(bitmap: DecodedBitmap): void {
bitmap.release?.()
}
function loadImage(url: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const image = new Image()
image.onload = () => resolve(image)
image.onerror = () => reject(new Error('image decode failed'))
image.src = url
})
}
function toBlob(
canvas: HTMLCanvasElement,
mimeType: string,
quality: number
): Promise<Blob | null> {
return new Promise((resolve) => {
canvas.toBlob((blob) => resolve(blob), mimeType, quality)
})
}
@@ -16,6 +16,7 @@ import {
import { pageDramasApi } from '@/api/admin/drama'
import { useRbac } from '@/stores/rbac'
import { uploadFile } from '@/utils/uploader'
import { BANNER_IMAGE, compressForPreset } from '@/views/contentManagement/upload/imagePresets'
import PageHeading from '@/views/components/PageHeading.vue'
const { can } = useRbac()
@@ -218,12 +219,13 @@ async function uploadImage(options) {
imagePercent.value = 0
imageError.value = ''
try {
const asset = await uploadFile(options.file, {
const file = await compressForPreset(options.file, BANNER_IMAGE)
const asset = await uploadFile(file, {
bizType: 'BANNER',
onProgress: (percent) => (imagePercent.value = percent)
})
form.imageUrl = asset.url
imageFileName.value = options.file.name
imageFileName.value = file.name
await resetValidation()
ElMessage.success('图片已上传')
} catch {
@@ -467,7 +469,9 @@ onMounted(async () => {
{{ imageDisplayName }}
</span>
</div>
<p class="banner-image-hint">支持 JPG、PNG、WebP,单张不超过 5MB。</p>
<p class="banner-image-hint"
>{{ BANNER_IMAGE.hint }};支持 JPG、PNG、WebP,单张不超过 5MB。</p
>
<el-alert
v-if="imageError"
:title="imageError"
@@ -26,6 +26,11 @@ import { formatCount } from '@/utils/format'
import { listContentTagsApi } from '@/api/admin/contentTag'
import { statusClass } from '@/utils/status'
import { uploadFile } from '@/utils/uploader'
import {
DRAMA_COVER_LANDSCAPE,
DRAMA_COVER_PORTRAIT,
compressForPreset
} from '@/views/contentManagement/upload/imagePresets'
const router = useRouter()
const { can, hasAny } = useRbac()
@@ -48,6 +53,10 @@ const uploadingCover = ref(false)
const coverPercent = ref(0)
const coverError = ref('')
const coverFileName = ref('')
const uploadingHorizontal = ref(false)
const horizontalPercent = ref(0)
const horizontalError = ref('')
const horizontalFileName = ref('')
const editingId = ref(null)
const editingCommentCount = ref(0)
const editingLikeCount = ref(0)
@@ -66,7 +75,8 @@ const form = reactive({
heat: 0,
isHot: false,
sort: 0,
cover: ''
cover: '',
horizontalCover: ''
})
const MAX_COVER_SIZE = 5 * 1024 * 1024
@@ -75,6 +85,11 @@ const COVER_TYPES = ['image/jpeg', 'image/png', 'image/webp']
const coverDisplayName = computed(
() => coverFileName.value || (form.cover ? coverBasename(form.cover) : '尚未选择封面')
)
const horizontalDisplayName = computed(
() =>
horizontalFileName.value ||
(form.horizontalCover ? coverBasename(form.horizontalCover) : '尚未选择横版封面')
)
const pageCoverPreviewList = computed(() => list.value.map((item) => item.cover).filter(Boolean))
@@ -192,10 +207,13 @@ function openCreate() {
heat: 0,
isHot: false,
sort: 0,
cover: ''
cover: '',
horizontalCover: ''
})
coverFileName.value = ''
coverError.value = ''
horizontalFileName.value = ''
horizontalError.value = ''
editingCommentCount.value = 0
editingLikeCount.value = 0
dialogVisible.value = true
@@ -223,10 +241,13 @@ function openEdit(row) {
heat: toCount(row.views),
isHot: !!row.isHot,
sort: toCount(row.sort),
cover: row.cover || ''
cover: row.cover || '',
horizontalCover: row.horizontalCover || ''
})
coverFileName.value = row.cover ? coverBasename(row.cover) : ''
coverError.value = ''
horizontalFileName.value = row.horizontalCover ? coverBasename(row.horizontalCover) : ''
horizontalError.value = ''
editingCommentCount.value = toCount(row.commentCount)
editingLikeCount.value = toCount(row.likeCount)
dialogVisible.value = true
@@ -245,7 +266,7 @@ function rowCategoryList(row) {
}
async function saveDrama() {
if (saving.value || uploadingCover.value) return
if (saving.value || uploadingCover.value || uploadingHorizontal.value) return
const titles = payloadLocales(form.titleLocales, textLanguages.value)
const missing = textLanguages.value.find((code) => !titles[code])
if (!titles.zh || missing) {
@@ -282,32 +303,41 @@ async function saveDrama() {
}
}
function beforeCoverUpload(file) {
coverError.value = ''
function validateCoverFile(file, errorRef, label) {
errorRef.value = ''
if (!COVER_TYPES.includes(file.type)) {
coverError.value = '封面仅支持 JPG、PNG 或 WebP 格式'
errorRef.value = `${label}仅支持 JPG、PNG 或 WebP 格式`
return false
}
if (file.size > MAX_COVER_SIZE) {
coverError.value = '封面大小不能超过 5MB'
errorRef.value = `${label}大小不能超过 5MB`
return false
}
return true
}
/** 封面走分片上传(图片只有一个分片),成功后回填地址。 */
function beforeCoverUpload(file) {
return validateCoverFile(file, coverError, '竖版封面')
}
function beforeHorizontalUpload(file) {
return validateCoverFile(file, horizontalError, '横版封面')
}
/** 封面走分片上传(图片只有一个分片),上传前按展示规格等比压缩,成功后回填地址。 */
async function uploadCover(options) {
uploadingCover.value = true
coverPercent.value = 0
coverError.value = ''
try {
const asset = await uploadFile(options.file, {
const file = await compressForPreset(options.file, DRAMA_COVER_PORTRAIT)
const asset = await uploadFile(file, {
bizType: 'DRAMA_COVER',
onProgress: (percent) => (coverPercent.value = percent)
})
form.cover = asset.url
coverFileName.value = options.file.name
ElMessage.success('封面已上传')
coverFileName.value = file.name
ElMessage.success('竖版封面已上传')
} catch {
coverError.value = '封面上传失败,请检查网络后重试'
} finally {
@@ -315,6 +345,26 @@ async function uploadCover(options) {
}
}
async function uploadHorizontalCover(options) {
uploadingHorizontal.value = true
horizontalPercent.value = 0
horizontalError.value = ''
try {
const file = await compressForPreset(options.file, DRAMA_COVER_LANDSCAPE)
const asset = await uploadFile(file, {
bizType: 'DRAMA_COVER',
onProgress: (percent) => (horizontalPercent.value = percent)
})
form.horizontalCover = asset.url
horizontalFileName.value = file.name
ElMessage.success('横版封面已上传')
} catch {
horizontalError.value = '横版封面上传失败,请检查网络后重试'
} finally {
uploadingHorizontal.value = false
}
}
function removeCover() {
if (uploadingCover.value) return
form.cover = ''
@@ -322,8 +372,15 @@ function removeCover() {
coverError.value = ''
}
function removeHorizontalCover() {
if (uploadingHorizontal.value) return
form.horizontalCover = ''
horizontalFileName.value = ''
horizontalError.value = ''
}
function beforeDialogClose(done) {
if (saving.value || uploadingCover.value) {
if (saving.value || uploadingCover.value || uploadingHorizontal.value) {
ElMessage.warning(saving.value ? '正在保存,请稍候' : '封面正在上传,请稍候')
return
}
@@ -572,65 +629,140 @@ onMounted(async () => {
<el-form ref="formRef" :model="form" label-position="top" class="drama-editor-form">
<section class="editor-section">
<h3 class="editor-section-title">封面</h3>
<div class="cover-editor">
<div class="cover-preview-card">
<img v-if="form.cover" :src="form.cover" alt="短剧封面" />
<div v-else class="cover-preview-empty">
<AppIcon :icon="AppIcons.content" :size="28" />
<span>暂无封面</span>
<div class="cover-block">
<p class="cover-label">竖版封面</p>
<div class="cover-editor">
<div class="cover-preview-card">
<img v-if="form.cover" :src="form.cover" alt="短剧竖版封面" />
<div v-else class="cover-preview-empty">
<AppIcon :icon="AppIcons.content" :size="28" />
<span>暂无封面</span>
</div>
<div v-if="uploadingCover" class="cover-upload-mask">
<span>上传中</span>
<b>{{ coverPercent }}%</b>
</div>
</div>
<div v-if="uploadingCover" class="cover-upload-mask">
<span>上传中</span>
<b>{{ coverPercent }}%</b>
<div class="cover-editor-meta">
<div class="cover-status-line">
<el-tag :type="form.cover ? 'success' : 'info'" effect="light">
{{ form.cover ? '已上传' : '未上传' }}
</el-tag>
<span class="cover-filename" :title="coverDisplayName">{{ coverDisplayName }}</span>
</div>
<p class="cover-hint"
>{{ DRAMA_COVER_PORTRAIT.hint }};支持 JPG、PNG、WebP,单张不超过 5MB。</p
>
<el-alert
v-if="coverError"
:title="coverError"
type="error"
:closable="false"
show-icon
/>
<div class="cover-actions">
<el-upload
v-if="can('cover:upload')"
:show-file-list="false"
accept="image/jpeg,image/png,image/webp"
:before-upload="beforeCoverUpload"
:http-request="uploadCover"
:disabled="uploadingCover"
>
<el-button :loading="uploadingCover" :disabled="uploadingCover">
<AppIcon :icon="AppIcons.upload" />
{{ form.cover ? '更换封面' : '选择封面' }}
</el-button>
</el-upload>
<el-button
v-if="can('cover:upload') && form.cover"
type="danger"
:disabled="uploadingCover"
@click="removeCover"
>
移除
</el-button>
<a
v-if="form.cover"
class="cover-original-link"
:href="form.cover"
target="_blank"
rel="noopener noreferrer"
>
查看原图
</a>
</div>
</div>
</div>
</div>
<div class="cover-editor-meta">
<div class="cover-status-line">
<el-tag :type="form.cover ? 'success' : 'info'" effect="light">
{{ form.cover ? '已上传' : '未上传' }}
</el-tag>
<span class="cover-filename" :title="coverDisplayName">{{ coverDisplayName }}</span>
<div class="cover-block">
<p class="cover-label">横版封面</p>
<div class="cover-editor">
<div class="cover-preview-card cover-preview-card--landscape">
<img v-if="form.horizontalCover" :src="form.horizontalCover" alt="短剧横版封面" />
<div v-else class="cover-preview-empty">
<AppIcon :icon="AppIcons.content" :size="28" />
<span>暂无横版封面</span>
</div>
<div v-if="uploadingHorizontal" class="cover-upload-mask">
<span>上传中</span>
<b>{{ horizontalPercent }}%</b>
</div>
</div>
<p class="cover-hint">支持 JPG、PNG、WebP,单张不超过 5MB。</p>
<el-alert
v-if="coverError"
:title="coverError"
type="error"
:closable="false"
show-icon
/>
<div class="cover-actions">
<el-upload
v-if="can('cover:upload')"
:show-file-list="false"
accept="image/jpeg,image/png,image/webp"
:before-upload="beforeCoverUpload"
:http-request="uploadCover"
:disabled="uploadingCover"
>
<el-button :loading="uploadingCover" :disabled="uploadingCover">
<AppIcon :icon="AppIcons.upload" />
{{ form.cover ? '更换封面' : '选择封面' }}
<div class="cover-editor-meta">
<div class="cover-status-line">
<el-tag :type="form.horizontalCover ? 'success' : 'info'" effect="light">
{{ form.horizontalCover ? '已上传' : '未上传' }}
</el-tag>
<span class="cover-filename" :title="horizontalDisplayName">{{
horizontalDisplayName
}}</span>
</div>
<p class="cover-hint">
{{ DRAMA_COVER_LANDSCAPE.hint }};支持 JPG、PNG、WebP,单张不超过 5MB。
</p>
<el-alert
v-if="horizontalError"
:title="horizontalError"
type="error"
:closable="false"
show-icon
/>
<div class="cover-actions">
<el-upload
v-if="can('cover:upload')"
:show-file-list="false"
accept="image/jpeg,image/png,image/webp"
:before-upload="beforeHorizontalUpload"
:http-request="uploadHorizontalCover"
:disabled="uploadingHorizontal"
>
<el-button :loading="uploadingHorizontal" :disabled="uploadingHorizontal">
<AppIcon :icon="AppIcons.upload" />
{{ form.horizontalCover ? '更换横版封面' : '选择横版封面' }}
</el-button>
</el-upload>
<el-button
v-if="can('cover:upload') && form.horizontalCover"
type="danger"
:disabled="uploadingHorizontal"
@click="removeHorizontalCover"
>
移除
</el-button>
</el-upload>
<el-button
v-if="can('cover:upload') && form.cover"
type="danger"
:disabled="uploadingCover"
@click="removeCover"
>
移除
</el-button>
<a
v-if="form.cover"
class="cover-original-link"
:href="form.cover"
target="_blank"
rel="noopener noreferrer"
>
查看原图
</a>
<a
v-if="form.horizontalCover"
class="cover-original-link"
:href="form.horizontalCover"
target="_blank"
rel="noopener noreferrer"
>
查看原图
</a>
</div>
</div>
</div>
</div>
@@ -0,0 +1,55 @@
// Author: fruit.com.cn
import { compressImage } from '@/utils/imageCompress'
/**
* 图片上传规格:建议尺寸来自客户端实际展示位,按 3 倍屏预留像素。
*
* - 竖版封面 3:4:首页三列 ~118dp、推荐分类两列 ~175dp、追剧收藏 ~118dp,
* 同时作为播放器起播首帧铺满竖屏,1080×1440 是兼顾清晰度与体积的平衡点。
* - 横版封面 16:9:追剧页「继续观看」横幅(约 2.06:1)与后续横向位共用。
* - 推荐轮播:客户端轮播高度固定 420dp、按屏宽铺满(约 1:1.08)。
*/
export interface ImagePreset {
label: string
/** 建议尺寸文案,展示在上传按钮旁。 */
hint: string
maxWidth: number
maxHeight: number
}
export const DRAMA_COVER_PORTRAIT: ImagePreset = {
label: '竖版封面',
hint: '建议 1080×1440(3:4),最小 720×960',
maxWidth: 1080,
maxHeight: 1440
}
export const DRAMA_COVER_LANDSCAPE: ImagePreset = {
label: '横版封面',
hint: '建议 1280×720(16:9),用于继续观看横幅等横向位',
maxWidth: 1280,
maxHeight: 720
}
export const EPISODE_COVER: ImagePreset = {
label: '分集封面',
hint: '建议 720×960(3:4)',
maxWidth: 720,
maxHeight: 960
}
export const BANNER_IMAGE: ImagePreset = {
label: '推荐轮播图',
hint: '建议 1080×1164(约 1:1.08,与客户端 420dp 轮播高度一致)',
maxWidth: 1080,
maxHeight: 1164
}
/** 按规格压缩:尺寸未超且体积不大时原样返回,不做二次编码。 */
export function compressForPreset(file: File, preset: ImagePreset): Promise<File> {
return compressImage(file, {
maxWidth: preset.maxWidth,
maxHeight: preset.maxHeight
})
}
+24 -17
View File
@@ -10,6 +10,7 @@ import { useRbac } from '@/stores/rbac'
import { useDramaEdit } from './composables/useDramaEdit'
import { useCreatorAccess } from './composables/useCreatorAccess'
import { dramaStatusMeta } from './composables/useCreatorWorkspace'
import { DRAMA_COVER_PORTRAIT, EPISODE_COVER } from '@/views/contentManagement/upload/imagePresets'
const { can } = useRbac()
const canEdit = can('creator-center:drama:edit')
@@ -148,15 +149,18 @@ function requestDramaCover(options) {
<img v-if="infoForm.cover" :src="infoForm.cover" alt="" />
<span v-else>无</span>
</div>
<el-upload
v-if="canUpload"
:show-file-list="false"
accept="image/*"
:http-request="requestDramaCover"
:disabled="uploadingId !== null"
>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
<div class="creator-cover-actions">
<el-upload
v-if="canUpload"
:show-file-list="false"
accept="image/*"
:http-request="requestDramaCover"
:disabled="uploadingId !== null"
>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
<p class="creator-cover-hint">{{ DRAMA_COVER_PORTRAIT.hint }}</p>
</div>
</div>
</el-form-item>
<el-form-item label="预告片地址">
@@ -353,14 +357,17 @@ function requestDramaCover(options) {
<img v-if="episodeForm.cover" :src="episodeForm.cover" alt="" />
<span v-else>无</span>
</div>
<el-upload
v-if="canUpload"
:show-file-list="false"
accept="image/*"
:http-request="(options) => requestEpisodeCover({ id: episodeEditingId }, options)"
>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
<div class="creator-cover-actions">
<el-upload
v-if="canUpload"
:show-file-list="false"
accept="image/*"
:http-request="(options) => requestEpisodeCover({ id: episodeEditingId }, options)"
>
<el-button size="small"> <AppIcon :icon="AppIcons.upload" />上传封面 </el-button>
</el-upload>
<p class="creator-cover-hint">{{ EPISODE_COVER.hint }}</p>
</div>
</div>
</el-form-item>
<el-form-item v-if="episodeEditingId" label="集号">
@@ -18,6 +18,11 @@ import {
} from '@/api/admin/creatorCenter'
import { dateCell } from '@/utils/format'
import { probeVideoDuration, resumeUpload, uploadFile } from '@/utils/uploader'
import {
DRAMA_COVER_PORTRAIT,
EPISODE_COVER,
compressForPreset
} from '@/views/contentManagement/upload/imagePresets'
import { submissionStatusMeta, submissionTypeText, submitOptions } from './useCreatorWorkspace'
import { useCreatorAccess } from './useCreatorAccess'
@@ -266,7 +271,9 @@ export function useDramaEdit() {
uploadingId.value = row ? row.id : -1
uploadPercent.value = 0
try {
const asset = await uploadFile(file, {
const preset = row ? EPISODE_COVER : DRAMA_COVER_PORTRAIT
const compressed = await compressForPreset(file, preset)
const asset = await uploadFile(compressed, {
bizType: 'DRAMA_COVER',
onProgress: (percent) => (uploadPercent.value = percent)
})