From 05b977cc33aa823981f580800c884779ec33bd75 Mon Sep 17 00:00:00 2001
From: Zhou Weidong <502333560@qq.com>
Date: Wed, 21 Oct 2026 22:25:50 +0800
Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E6=89=B9=E9=87=8F=E5=BB=BA?=
=?UTF-8?q?=E5=88=86=E9=9B=86=E6=98=AF=E5=90=A6=E5=85=8D=E8=B4=B9=E6=94=B9?=
=?UTF-8?q?=E7=94=B1=E7=9F=AD=E5=89=A7=E5=85=8D=E8=B4=B9=E9=9B=86=E6=95=B0?=
=?UTF-8?q?=E5=86=B3=E5=AE=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/locales/en.ts | 6 ++-
src/locales/zh-CN.ts | 7 ++-
.../EpisodeManagementView.vue | 1 +
.../upload/BatchUploadDialog.vue | 22 ++++++---
.../upload/CreateEpisodesDialog.vue | 45 ++++++++++++++-----
5 files changed, 59 insertions(+), 22 deletions(-)
diff --git a/src/locales/en.ts b/src/locales/en.ts
index c15626d..59f86de 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -555,11 +555,13 @@ export default {
resumeUnmatched: '{count} session(s) could not match local files; pick again manually',
createTitle: 'Create episodes in batch',
createHint:
- 'Title uses the video file name; episode numbers are assigned automatically. You can edit them later in episode settings.',
+ 'Title uses the video file name; episode numbers are assigned automatically. Free vs paid follows the drama free-episode count; episodes beyond that are paid. You can edit them later in episode settings.',
colSource: 'Source file',
colNumber: 'Episode No.',
colTitle: 'Title',
- colFree: 'Free',
+ colFree: 'Pricing',
+ labelFree: 'Free',
+ labelPaid: 'Paid',
colResult: 'Result',
missingVideo: 'Missing video URL',
createOk: 'Created',
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index 023a84f..29a0cf8 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -546,11 +546,14 @@ export default {
resumeMatched: '已匹配 {count} 个文件并开始续传',
resumeUnmatched: '有 {count} 个会话无法匹配本地文件,请手动重选',
createTitle: '批量创建分集',
- createHint: '标题取视频文件名,集号自动顺延分配,创建后可在分集编辑中修改',
+ createHint:
+ '标题取视频文件名,集号自动顺延分配;免费集数由短剧管理中的「免费集数」配置决定,超出部分为收费集;创建后可在分集编辑中修改',
colSource: '源文件',
colNumber: '集号',
colTitle: '标题',
- colFree: '免费',
+ colFree: '收费类型',
+ labelFree: '免费',
+ labelPaid: '收费',
colResult: '结果',
missingVideo: '缺少视频地址',
createOk: '已创建',
diff --git a/src/views/contentManagement/EpisodeManagementView.vue b/src/views/contentManagement/EpisodeManagementView.vue
index bfb449d..3686df7 100644
--- a/src/views/contentManagement/EpisodeManagementView.vue
+++ b/src/views/contentManagement/EpisodeManagementView.vue
@@ -337,6 +337,7 @@ onMounted(async () => {
:drama-id="selectedDramaId"
:next-episode-number="batchNextNumber"
:default-unlock-coin="selectedDrama?.unlockCoin ?? 0"
+ :free-episodes="selectedDrama?.freeEpisodes ?? 0"
:existing-numbers="existingEpisodeNumbers"
@refreshed="loadEpisodes"
@created="onBatchEpisodesCreated"
diff --git a/src/views/contentManagement/upload/BatchUploadDialog.vue b/src/views/contentManagement/upload/BatchUploadDialog.vue
index 52e01f5..df5ac7b 100644
--- a/src/views/contentManagement/upload/BatchUploadDialog.vue
+++ b/src/views/contentManagement/upload/BatchUploadDialog.vue
@@ -12,13 +12,20 @@ import { useUploadQueue } from './useUploadQueue'
import { MAX_VIDEO_SIZE, VIDEO_ACCEPT_ATTR, VIDEO_EXTENSIONS } from './videoLimit'
import type { UploadSessionRecord } from './uploadSessions'
-const props = defineProps<{
- modelValue: boolean
- dramaId: string | number
- nextEpisodeNumber: number
- defaultUnlockCoin: number
- existingNumbers: number[]
-}>()
+const props = withDefaults(
+ defineProps<{
+ modelValue: boolean
+ dramaId: string | number
+ nextEpisodeNumber: number
+ defaultUnlockCoin: number
+ existingNumbers: number[]
+ /** 所属短剧免费集数;不传默认 0 */
+ freeEpisodes?: number
+ }>(),
+ {
+ freeEpisodes: 0
+ }
+)
const emit = defineEmits<{
'update:modelValue': [value: boolean]
@@ -272,6 +279,7 @@ function onEpisodesCreated(): void {
:done-items="queue.doneItems.value"
:next-episode-number="nextEpisodeNumber"
:default-unlock-coin="defaultUnlockCoin"
+ :free-episodes="freeEpisodes"
:existing-numbers="existingNumbers"
@settled="emit('refreshed')"
@created="onEpisodesCreated"
diff --git a/src/views/contentManagement/upload/CreateEpisodesDialog.vue b/src/views/contentManagement/upload/CreateEpisodesDialog.vue
index 31683bf..e6d7aeb 100644
--- a/src/views/contentManagement/upload/CreateEpisodesDialog.vue
+++ b/src/views/contentManagement/upload/CreateEpisodesDialog.vue
@@ -8,14 +8,21 @@ import { createEpisodeApi } from '@/api/admin/episode'
import { useI18n } from '@/hooks/web/useI18n'
import type { UploadQueueItem } from './useUploadQueue'
-const props = defineProps<{
- modelValue: boolean
- dramaId: string | number
- doneItems: UploadQueueItem[]
- nextEpisodeNumber: number
- defaultUnlockCoin: number
- existingNumbers: number[]
-}>()
+const props = withDefaults(
+ defineProps<{
+ modelValue: boolean
+ dramaId: string | number
+ doneItems: UploadQueueItem[]
+ nextEpisodeNumber: number
+ defaultUnlockCoin: number
+ existingNumbers: number[]
+ /** 所属短剧免费集数;不传默认 0 */
+ freeEpisodes?: number
+ }>(),
+ {
+ freeEpisodes: 0
+ }
+)
const emit = defineEmits<{
'update:modelValue': [value: boolean]
@@ -66,16 +73,18 @@ function buildRows(): void {
)
const used = new Set(props.existingNumbers)
let cursor = props.nextEpisodeNumber
+ const freeLimit = props.freeEpisodes
rows.value = sorted.map((item) => {
const number = nextFreeNumber(cursor, used)
used.add(number)
cursor = number + 1
+ const isFree = number <= freeLimit
return {
key: item.id,
sourceName: item.fileName,
number,
title: baseName(item.fileName),
- isFree: true,
+ isFree,
videoUrl: item.url || '',
durationSec: item.durationSec || 0,
saved: false,
@@ -173,7 +182,9 @@ function hasFailed(): boolean {
{{ row.sourceName }}
{{ row.number }}
{{ row.title }}
-
+
+ {{ row.isFree ? t('episodeBatch.labelFree') : t('episodeBatch.labelPaid') }}
+
{{ row.saved ? t('episodeBatch.createOk') : row.error }}
@@ -199,11 +210,23 @@ function hasFailed(): boolean {
.create-episode-head,
.create-episode-row {
display: grid;
- grid-template-columns: minmax(120px, 1.2fr) 72px minmax(140px, 1.4fr) 48px minmax(80px, 0.8fr);
+ grid-template-columns: minmax(120px, 1.2fr) 72px minmax(140px, 1.4fr) 56px minmax(80px, 0.8fr);
gap: 8px;
align-items: center;
}
+.create-episode-free {
+ font-size: 12px;
+}
+
+.create-episode-free.is-free {
+ color: var(--el-color-success);
+}
+
+.create-episode-free.is-paid {
+ color: var(--el-color-warning);
+}
+
.create-episode-head {
margin-bottom: 8px;
color: var(--el-text-color-secondary);