From c8fe571be19d5a6a10040107c6a8fc79dae6fe77 Mon Sep 17 00:00:00 2001
From: Zhou Weidong <502333560@qq.com>
Date: Wed, 21 Oct 2026 15:43:51 +0800
Subject: [PATCH 1/6] =?UTF-8?q?feat(admin):=20=E7=9F=AD=E5=89=A7=E5=88=97?=
=?UTF-8?q?=E8=A1=A8=E5=B1=95=E7=A4=BA=E5=B9=B6=E7=BC=96=E8=BE=91=E4=BA=92?=
=?UTF-8?q?=E5=8A=A8=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/admin/drama.ts | 1 +
src/api/admin/types.ts | 8 ++
src/styles/business/drama-management.css | 18 +++
.../contentManagement/DramaManagementView.vue | 107 +++++++++++++++++-
4 files changed, 132 insertions(+), 2 deletions(-)
diff --git a/src/api/admin/drama.ts b/src/api/admin/drama.ts
index 753bff4..885227d 100644
--- a/src/api/admin/drama.ts
+++ b/src/api/admin/drama.ts
@@ -16,6 +16,7 @@ export interface DramaPageParams {
status?: string
categoryId?: number
isHot?: boolean
+ dramaTypes?: Array<'ANIME' | 'LIVE'>
}
export const pageDramasApi = (params: DramaPageParams) => {
diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts
index 15780cb..632951a 100644
--- a/src/api/admin/types.ts
+++ b/src/api/admin/types.ts
@@ -25,6 +25,7 @@ export interface DramaItem {
description?: string
cover?: string
language?: string
+ dramaType?: 'ANIME' | 'LIVE'
episodes?: number
freeEpisodes?: number
unlockCoin?: number
@@ -33,6 +34,9 @@ export interface DramaItem {
status: string
statusText: string
views?: number
+ favoriteCount?: number
+ likeCount?: number
+ commentCount?: number
sort?: number
publishAt?: string
createdAt?: string
@@ -47,8 +51,12 @@ export interface DramaSaveData {
description?: string
cover?: string
language?: string
+ dramaType?: 'ANIME' | 'LIVE'
freeEpisodes?: number
unlockCoin?: number
+ favoriteCount?: number
+ likeCount?: number
+ heat?: number
isHot?: boolean
sort?: number
}
diff --git a/src/styles/business/drama-management.css b/src/styles/business/drama-management.css
index da70d90..4cb4cf9 100644
--- a/src/styles/business/drama-management.css
+++ b/src/styles/business/drama-management.css
@@ -147,6 +147,24 @@
width: 180px !important;
}
+.readonly-stat {
+ min-width: 180px;
+ height: 32px;
+ padding: 0 12px;
+ color: var(--el-text-color-secondary);
+ line-height: 32px;
+ background: var(--el-fill-color-lighter);
+ border: 1px solid var(--el-border-color-lighter);
+ border-radius: var(--el-border-radius-base);
+}
+
+.readonly-stat-hint {
+ margin: 4px 0 0;
+ color: var(--el-text-color-placeholder);
+ font-size: 12px;
+ line-height: 1.4;
+}
+
.cover-editor {
display: flex;
gap: 18px;
diff --git a/src/views/contentManagement/DramaManagementView.vue b/src/views/contentManagement/DramaManagementView.vue
index 9941efe..d914ec6 100644
--- a/src/views/contentManagement/DramaManagementView.vue
+++ b/src/views/contentManagement/DramaManagementView.vue
@@ -30,6 +30,7 @@ const categories = ref([])
const keyword = ref('')
const statusFilter = ref(undefined)
const categoryId = ref(undefined)
+const dramaTypeFilter = ref(undefined)
const hotFilter = ref(undefined)
const pageIndex = ref(1)
const pageSize = ref(10)
@@ -41,13 +42,18 @@ const coverPercent = ref(0)
const coverError = ref('')
const coverFileName = ref('')
const editingId = ref(null)
+const editingCommentCount = ref(0)
const formRef = ref()
const form = reactive({
title: '',
categoryId: undefined,
+ dramaType: 'LIVE',
description: '',
freeEpisodes: 3,
unlockCoin: 0,
+ favoriteCount: 0,
+ likeCount: 0,
+ heat: 0,
isHot: false,
cover: ''
})
@@ -57,7 +63,8 @@ const COVER_TYPES = ['image/jpeg', 'image/png', 'image/webp']
const formRules = {
title: [{ required: true, message: '请输入短剧名称', trigger: 'blur' }],
- categoryId: [{ required: true, message: '请选择短剧类型', trigger: 'change' }]
+ categoryId: [{ required: true, message: '请选择短剧类型', trigger: 'change' }],
+ dramaType: [{ required: true, message: '请选择内容类型', trigger: 'change' }]
}
const coverDisplayName = computed(
@@ -78,6 +85,11 @@ const statusOptions = [
{ code: 'offline', text: '已下线' }
]
+const dramaTypeOptions = [
+ { value: 'ANIME', label: '漫剧' },
+ { value: 'LIVE', label: '真人剧' }
+]
+
const actionMeta = {
submit: { label: '提交审核', done: '已提交审核' },
approve: { label: '审核通过', done: '已通过审核' },
@@ -95,6 +107,7 @@ async function load() {
keyword: keyword.value.trim() || undefined,
status: statusFilter.value || undefined,
categoryId: categoryId.value,
+ dramaTypes: dramaTypeFilter.value ? [dramaTypeFilter.value] : undefined,
isHot: hotFilter.value
})
list.value = res.data?.list || []
@@ -118,6 +131,7 @@ function resetFilters() {
keyword.value = ''
statusFilter.value = undefined
categoryId.value = undefined
+ dramaTypeFilter.value = undefined
hotFilter.value = undefined
search()
}
@@ -143,14 +157,19 @@ function openCreate() {
Object.assign(form, {
title: '',
categoryId: undefined,
+ dramaType: 'LIVE',
description: '',
freeEpisodes: 3,
unlockCoin: 0,
+ favoriteCount: 0,
+ likeCount: 0,
+ heat: 0,
isHot: false,
cover: ''
})
coverFileName.value = ''
coverError.value = ''
+ editingCommentCount.value = 0
dialogVisible.value = true
resetValidation()
}
@@ -160,18 +179,27 @@ function openEdit(row) {
Object.assign(form, {
title: row.title,
categoryId: row.categoryId || undefined,
+ dramaType: row.dramaType || 'LIVE',
description: row.description || '',
freeEpisodes: row.freeEpisodes ?? 3,
unlockCoin: row.unlockCoin ?? 0,
+ favoriteCount: row.favoriteCount ?? 0,
+ likeCount: row.likeCount ?? 0,
+ heat: row.views ?? 0,
isHot: !!row.isHot,
cover: row.cover || ''
})
coverFileName.value = row.cover ? coverBasename(row.cover) : ''
coverError.value = ''
+ editingCommentCount.value = row.commentCount ?? 0
dialogVisible.value = true
resetValidation()
}
+function dramaTypeLabel(value) {
+ return dramaTypeOptions.find((item) => item.value === value)?.label || '真人剧'
+}
+
async function saveDrama() {
if (saving.value || uploadingCover.value) return
try {
@@ -344,6 +372,12 @@ onMounted(async () => {
:key="item.id"
:label="item.name"
:value="item.id" /> {
>
+ {{
+ dramaTypeLabel(row.dramaType)
+ }}
{{ row.episodes }} 集
- {{ formatCount(row.views) }}
+ {{
+ formatCount(row.favoriteCount ?? 0)
+ }}
+ {{
+ formatCount(row.likeCount ?? 0)
+ }}
+ {{
+ formatCount(row.commentCount ?? 0)
+ }}
{{
@@ -487,6 +543,16 @@ onMounted(async () => {
/>
+
+
+
+
+
{
+
+ 互动数据
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatCount(editingCommentCount) }}
+ 实时统计,不可编辑
+
+
+
+
封面
From 0e814840b18f025a59b92bd9953d201a1ace1ba0 Mon Sep 17 00:00:00 2001
From: Zhou Weidong <502333560@qq.com>
Date: Wed, 21 Oct 2026 15:59:08 +0800
Subject: [PATCH 2/6] =?UTF-8?q?feat(admin):=20=E5=89=A7=E9=9B=86=E5=88=97?=
=?UTF-8?q?=E8=A1=A8=E5=B1=95=E7=A4=BA=E5=88=86=E9=9B=86=E4=BA=92=E5=8A=A8?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/admin/types.ts | 5 ++
.../contentManagement/DramaManagementView.vue | 17 +++----
.../EpisodeManagementView.vue | 48 ++++++++++++++++++-
3 files changed, 58 insertions(+), 12 deletions(-)
diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts
index 632951a..8056e89 100644
--- a/src/api/admin/types.ts
+++ b/src/api/admin/types.ts
@@ -77,6 +77,10 @@ export interface EpisodeItem {
status: string
statusText: string
views?: number
+ likeCount?: number
+ commentCount?: number
+ favoriteCount?: number
+ heat?: number
sort?: number
}
@@ -89,6 +93,7 @@ export interface EpisodeSaveData {
videoUrl?: string
isFree?: boolean
unlockCoin?: number
+ likeCount?: number
status?: string
sort?: number
}
diff --git a/src/views/contentManagement/DramaManagementView.vue b/src/views/contentManagement/DramaManagementView.vue
index d914ec6..fd63ee5 100644
--- a/src/views/contentManagement/DramaManagementView.vue
+++ b/src/views/contentManagement/DramaManagementView.vue
@@ -43,6 +43,7 @@ const coverError = ref('')
const coverFileName = ref('')
const editingId = ref(null)
const editingCommentCount = ref(0)
+const editingLikeCount = ref(0)
const formRef = ref()
const form = reactive({
title: '',
@@ -52,7 +53,6 @@ const form = reactive({
freeEpisodes: 3,
unlockCoin: 0,
favoriteCount: 0,
- likeCount: 0,
heat: 0,
isHot: false,
cover: ''
@@ -162,7 +162,6 @@ function openCreate() {
freeEpisodes: 3,
unlockCoin: 0,
favoriteCount: 0,
- likeCount: 0,
heat: 0,
isHot: false,
cover: ''
@@ -170,6 +169,7 @@ function openCreate() {
coverFileName.value = ''
coverError.value = ''
editingCommentCount.value = 0
+ editingLikeCount.value = 0
dialogVisible.value = true
resetValidation()
}
@@ -184,7 +184,6 @@ function openEdit(row) {
freeEpisodes: row.freeEpisodes ?? 3,
unlockCoin: row.unlockCoin ?? 0,
favoriteCount: row.favoriteCount ?? 0,
- likeCount: row.likeCount ?? 0,
heat: row.views ?? 0,
isHot: !!row.isHot,
cover: row.cover || ''
@@ -192,6 +191,7 @@ function openEdit(row) {
coverFileName.value = row.cover ? coverBasename(row.cover) : ''
coverError.value = ''
editingCommentCount.value = row.commentCount ?? 0
+ editingLikeCount.value = row.likeCount ?? 0
dialogVisible.value = true
resetValidation()
}
@@ -606,13 +606,8 @@ onMounted(async () => {
/>
-
+ {{ formatCount(editingLikeCount) }}
+ 全剧分集汇总,只读
{
{{ formatCount(editingCommentCount) }}
- 实时统计,不可编辑
+ 全剧汇总,实时统计,只读
diff --git a/src/views/contentManagement/EpisodeManagementView.vue b/src/views/contentManagement/EpisodeManagementView.vue
index 6e1b4a9..0a5e5fd 100644
--- a/src/views/contentManagement/EpisodeManagementView.vue
+++ b/src/views/contentManagement/EpisodeManagementView.vue
@@ -35,6 +35,7 @@ const videoFileName = ref('')
const previewVisible = ref(false)
const previewEpisode = ref(null)
const editingId = ref(null)
+const editingCommentCount = ref(0)
const formRef = ref()
const form = reactive({
number: 1,
@@ -43,6 +44,7 @@ const form = reactive({
videoUrl: '',
isFree: true,
unlockCoin: 0,
+ likeCount: 0,
status: 'draft'
})
@@ -127,10 +129,12 @@ function openCreate() {
videoUrl: '',
isFree: true,
unlockCoin: selectedDrama.value?.unlockCoin ?? 0,
+ likeCount: 0,
status: 'draft'
})
videoFileName.value = ''
videoError.value = ''
+ editingCommentCount.value = 0
dialogVisible.value = true
resetValidation()
}
@@ -144,10 +148,12 @@ function openEdit(row) {
videoUrl: row.videoUrl || '',
isFree: !!row.isFree,
unlockCoin: row.unlockCoin || 0,
+ likeCount: row.likeCount ?? 0,
status: row.status
})
videoFileName.value = row.videoUrl ? episodeBasename(row.videoUrl) : ''
videoError.value = ''
+ editingCommentCount.value = row.commentCount ?? 0
dialogVisible.value = true
resetValidation()
}
@@ -167,6 +173,7 @@ async function saveEpisode() {
videoUrl: form.videoUrl.trim(),
isFree: form.isFree,
unlockCoin: form.isFree ? 0 : form.unlockCoin || 0,
+ likeCount: form.likeCount,
status: form.status
}
saving.value = true
@@ -325,7 +332,9 @@ onMounted(async () => {
当前短剧
{{ selectedDrama?.title || '暂无短剧' }}
共 {{ selectedDrama?.episodes ?? episodes.length }} 集共 {{ selectedDrama?.episodes ?? episodes.length }} 集 · 收藏
+ {{ formatCount(selectedDrama?.favoriteCount ?? 0) }} · 热度
+ {{ formatCount(selectedDrama?.views ?? 0) }}
@@ -398,6 +407,24 @@ onMounted(async () => {
{{ formatCount(row.views) }}
+
{{
+ formatCount(row.likeCount ?? 0)
+ }}
+
{{
+ formatCount(row.commentCount ?? 0)
+ }}
+
{{
+ formatCount(row.favoriteCount ?? 0)
+ }}
+
{{ formatCount(row.heat ?? 0) }}
{{
@@ -568,6 +595,25 @@ onMounted(async () => {
+
+ 互动数据
+
+
+
+
+
+ {{ formatCount(editingCommentCount) }}
+ 按集实时统计,不可编辑
+
+
+
+
付费与状态
From 0710ea55f3188059e63e678a6d69a698f52cc10d Mon Sep 17 00:00:00 2001
From: Zhou Weidong <502333560@qq.com>
Date: Wed, 21 Oct 2026 16:35:41 +0800
Subject: [PATCH 3/6] =?UTF-8?q?feat(admin):=20=E8=A7=82=E7=9C=8B=E8=AE=B0?=
=?UTF-8?q?=E5=BD=95=E7=8B=AC=E7=AB=8B=E8=8F=9C=E5=8D=95=E4=B8=8E=E6=B2=BB?=
=?UTF-8?q?=E7=90=86=E7=8A=B6=E6=80=81=E5=85=A8=E9=83=A8=E7=AD=9B=E9=80=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/admin/appUser.ts | 21 ++
src/api/admin/types.ts | 7 +
.../governance/ContentGovernanceView.vue | 25 +-
src/views/userManagement/WatchHistoryView.vue | 239 ++++++++++++++++++
4 files changed, 289 insertions(+), 3 deletions(-)
create mode 100644 src/views/userManagement/WatchHistoryView.vue
diff --git a/src/api/admin/appUser.ts b/src/api/admin/appUser.ts
index ae6bfa7..884938d 100644
--- a/src/api/admin/appUser.ts
+++ b/src/api/admin/appUser.ts
@@ -13,6 +13,20 @@ export interface AppUserPageParams {
registerSource?: string
}
+export interface WatchHistoryPageParams {
+ pageIndex?: number
+ pageSize?: number
+ keyword?: string
+ userId?: number
+ dramaTitle?: string
+ dramaId?: number
+ episodeId?: number
+ source?: string
+ finished?: boolean
+ startTime?: string
+ endTime?: string
+}
+
export const pageAppUsersApi = (params: AppUserPageParams) => {
return request.get
>({ url: '/admin/app-users/page', params })
}
@@ -32,6 +46,13 @@ export const listAppUserWatchHistoryApi = (id: number, limit = 50) => {
})
}
+export const pageWatchHistoryApi = (params: WatchHistoryPageParams) => {
+ return request.get>({
+ url: '/admin/watch-history/page',
+ params
+ })
+}
+
/** normal / limited / banned,封禁与限制会立即吊销该用户全部会话 */
export const updateAppUserStatusApi = (id: number, status: string, reason?: string) => {
return request.put({
diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts
index 8056e89..e8562a8 100644
--- a/src/api/admin/types.ts
+++ b/src/api/admin/types.ts
@@ -307,11 +307,15 @@ export interface AppUserBanRecord {
export interface WatchHistoryItem {
id: number
+ userId?: number
+ uid?: string
+ nickname?: string
dramaId: number
dramaTitle?: string
dramaPublished?: boolean
episodeId: number
episodeNo?: number
+ episodeTitle?: string
positionMs: number
durationMs: number
finished?: boolean
@@ -324,7 +328,10 @@ export interface WatchHistoryItem {
export interface CommentItem {
id: number
dramaId: number
+ dramaTitle?: string
episodeId: number
+ episodeNo?: number
+ episodeTitle?: string
parentId: number
rootId: number
userId: number
diff --git a/src/views/governance/ContentGovernanceView.vue b/src/views/governance/ContentGovernanceView.vue
index d49d567..90a9b8f 100644
--- a/src/views/governance/ContentGovernanceView.vue
+++ b/src/views/governance/ContentGovernanceView.vue
@@ -26,12 +26,14 @@ const canWordUpdate = can('sensitive-word:update')
const activeTab = ref('comments')
const COMMENT_STATUS = [
+ { value: '', label: '全部状态', tag: 'info' },
{ value: 'pending', label: '待审核', tag: 'warning' },
{ value: 'published', label: '已发布', tag: 'success' },
{ value: 'rejected', label: '已驳回', tag: 'danger' },
{ value: 'deleted', label: '已删除', tag: 'info' }
]
const REPORT_STATUS = [
+ { value: '', label: '全部状态', tag: 'info' },
{ value: 'pending', label: '待处理', tag: 'warning' },
{ value: 'handled', label: '已处理', tag: 'success' },
{ value: 'rejected', label: '已驳回', tag: 'info' }
@@ -48,6 +50,13 @@ function meta(list, value) {
function levelText(level) {
return WORD_LEVELS.find((item) => item.value === level)?.label || level
}
+function commentEpisodeText(row) {
+ if (row.episodeNo) return `第 ${row.episodeNo} 集`
+ return row.episodeId ? `分集 #${row.episodeId}` : '未关联分集'
+}
+function commentContentTooltip(row) {
+ return `短剧 ID:${row.dramaId} / 分集 ID:${row.episodeId || '未关联'}`
+}
const comments = reactive({
rows: [],
@@ -55,7 +64,7 @@ const comments = reactive({
loading: false,
page: 1,
size: 10,
- status: 'pending',
+ status: '',
keyword: ''
})
async function loadComments() {
@@ -123,7 +132,7 @@ const reports = reactive({
loading: false,
page: 1,
size: 10,
- status: 'pending'
+ status: ''
})
async function loadReports() {
reports.loading = true
@@ -325,7 +334,17 @@ const handleTargetText = computed(() => {
-
+
+
+
+
+ {{ row.dramaTitle || `短剧 #${row.dramaId}` }}
+ {{ commentEpisodeText(row) }}
+ {{ row.episodeTitle }}
+
+
+
+
{{
diff --git a/src/views/userManagement/WatchHistoryView.vue b/src/views/userManagement/WatchHistoryView.vue
new file mode 100644
index 0000000..67b1233
--- /dev/null
+++ b/src/views/userManagement/WatchHistoryView.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 重置
+ 共 {{ total }} 条
+
+
+
+
+
+
+
+ {{ row.nickname || '未设置昵称' }}
+ {{ row.uid || `用户 #${row.userId || '—'}` }}
+
+
+
+
+
+
+
+ {{ row.dramaTitle || `短剧 #${row.dramaId}` }}
+ 已下线
+
+
+
+
+
+
+
+ {{ episodeText(row) }}
+ {{ row.episodeTitle }}
+
+
+
+
+
+ {{ progressText(row) }}
+
+
+
+
+
+ {{ row.finished ? '已看完' : '未看完' }}
+
+
+
+
+ {{ sourceText(row.source) }}
+
+
+ {{ row.deviceId || '—' }}
+
+
+
+
+
+
+
+
From e583bfed5f04d96fec6f00313e1ac178a064abdc Mon Sep 17 00:00:00 2001
From: Zhou Weidong <502333560@qq.com>
Date: Wed, 21 Oct 2026 16:44:55 +0800
Subject: [PATCH 4/6] =?UTF-8?q?feat(admin):=20=E4=B8=BE=E6=8A=A5=E5=88=97?=
=?UTF-8?q?=E8=A1=A8=E5=B1=95=E7=A4=BA=E7=9F=AD=E5=89=A7=E4=B8=8E=E5=88=86?=
=?UTF-8?q?=E9=9B=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/admin/types.ts | 11 ++++
.../governance/ContentGovernanceView.vue | 50 +++++++++++++++----
2 files changed, 51 insertions(+), 10 deletions(-)
diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts
index e8562a8..9e499a5 100644
--- a/src/api/admin/types.ts
+++ b/src/api/admin/types.ts
@@ -350,9 +350,20 @@ export interface CommentItem {
export interface ReportItem {
id: number
reporterId: number
+ reporterNickname?: string
+ reporterUid?: string
/** COMMENT / USER / DRAMA */
targetType: string
targetId: number
+ targetSummary?: string
+ dramaId?: number
+ dramaTitle?: string
+ episodeId?: number
+ episodeNo?: number
+ episodeTitle?: string
+ commentContent?: string
+ targetNickname?: string
+ targetUid?: string
reasonCode: string
description: string
/** pending / handled / rejected */
diff --git a/src/views/governance/ContentGovernanceView.vue b/src/views/governance/ContentGovernanceView.vue
index 90a9b8f..ab83d40 100644
--- a/src/views/governance/ContentGovernanceView.vue
+++ b/src/views/governance/ContentGovernanceView.vue
@@ -1,7 +1,7 @@
@@ -429,8 +438,22 @@ const handleTargetText = computed(() => {
empty-text="没有符合条件的工单"
>
-
-
+
+
+
+
+ 原始对象:#{{ row.targetId }}
+ 评论内容:{{ row.commentContent }}
+
+
+
+ {{ reportTargetMeta(row.targetType).label }}
+
+ {{ reportTargetSummary(row) }}
+
+
+
+
{
min-width="180"
show-overflow-tooltip
/>
-
+
+ {{ reporterText(row) }}
+
{{
@@ -569,7 +594,12 @@ const handleTargetText = computed(() => {
-
+
+
+ {{ reportTargetMeta(handleTarget.targetType).label }}
+
+ {{ reportTargetSummary(handleTarget) }}
+
From 9e427d485f5c7d738687f3c9ab1268fc227bb020 Mon Sep 17 00:00:00 2001
From: Zhou Weidong <502333560@qq.com>
Date: Wed, 21 Oct 2026 17:01:22 +0800
Subject: [PATCH 5/6] =?UTF-8?q?feat(admin):=20=E7=9F=AD=E5=89=A7=E7=B1=BB?=
=?UTF-8?q?=E5=9E=8B=E4=B8=8E=E5=86=85=E5=AE=B9=E7=B1=BB=E5=9E=8B=E5=A4=9A?=
=?UTF-8?q?=E9=80=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/api/admin/drama.ts | 1 +
src/api/admin/types.ts | 5 +
.../contentManagement/DramaManagementView.vue | 116 ++++++++++++++----
3 files changed, 95 insertions(+), 27 deletions(-)
diff --git a/src/api/admin/drama.ts b/src/api/admin/drama.ts
index 885227d..abe00bf 100644
--- a/src/api/admin/drama.ts
+++ b/src/api/admin/drama.ts
@@ -15,6 +15,7 @@ export interface DramaPageParams {
keyword?: string
status?: string
categoryId?: number
+ categoryIds?: number[]
isHot?: boolean
dramaTypes?: Array<'ANIME' | 'LIVE'>
}
diff --git a/src/api/admin/types.ts b/src/api/admin/types.ts
index 9e499a5..4040874 100644
--- a/src/api/admin/types.ts
+++ b/src/api/admin/types.ts
@@ -20,12 +20,15 @@ export interface DramaItem {
title: string
subtitle?: string
categoryId?: number
+ categoryIds?: number[]
+ categoryNames?: string[]
category?: string
genre?: string
description?: string
cover?: string
language?: string
dramaType?: 'ANIME' | 'LIVE'
+ dramaTypes?: Array<'ANIME' | 'LIVE'>
episodes?: number
freeEpisodes?: number
unlockCoin?: number
@@ -47,11 +50,13 @@ export interface DramaSaveData {
title: string
subtitle?: string
categoryId?: number
+ categoryIds?: number[]
genre?: string
description?: string
cover?: string
language?: string
dramaType?: 'ANIME' | 'LIVE'
+ dramaTypes?: Array<'ANIME' | 'LIVE'>
freeEpisodes?: number
unlockCoin?: number
favoriteCount?: number
diff --git a/src/views/contentManagement/DramaManagementView.vue b/src/views/contentManagement/DramaManagementView.vue
index fd63ee5..439ebad 100644
--- a/src/views/contentManagement/DramaManagementView.vue
+++ b/src/views/contentManagement/DramaManagementView.vue
@@ -29,8 +29,8 @@ const categories = ref([])
const keyword = ref('')
const statusFilter = ref(undefined)
-const categoryId = ref(undefined)
-const dramaTypeFilter = ref(undefined)
+const categoryIds = ref([])
+const dramaTypes = ref([])
const hotFilter = ref(undefined)
const pageIndex = ref(1)
const pageSize = ref(10)
@@ -47,8 +47,8 @@ const editingLikeCount = ref(0)
const formRef = ref()
const form = reactive({
title: '',
- categoryId: undefined,
- dramaType: 'LIVE',
+ categoryIds: [],
+ dramaTypes: ['LIVE'],
description: '',
freeEpisodes: 3,
unlockCoin: 0,
@@ -63,8 +63,12 @@ const COVER_TYPES = ['image/jpeg', 'image/png', 'image/webp']
const formRules = {
title: [{ required: true, message: '请输入短剧名称', trigger: 'blur' }],
- categoryId: [{ required: true, message: '请选择短剧类型', trigger: 'change' }],
- dramaType: [{ required: true, message: '请选择内容类型', trigger: 'change' }]
+ categoryIds: [
+ { type: 'array', required: true, min: 1, message: '请至少选择一个类型', trigger: 'change' }
+ ],
+ dramaTypes: [
+ { type: 'array', required: true, min: 1, message: '请至少选择一个内容类型', trigger: 'change' }
+ ]
}
const coverDisplayName = computed(
@@ -106,8 +110,8 @@ async function load() {
pageSize: pageSize.value,
keyword: keyword.value.trim() || undefined,
status: statusFilter.value || undefined,
- categoryId: categoryId.value,
- dramaTypes: dramaTypeFilter.value ? [dramaTypeFilter.value] : undefined,
+ categoryIds: categoryIds.value.length ? categoryIds.value : undefined,
+ dramaTypes: dramaTypes.value.length ? dramaTypes.value : undefined,
isHot: hotFilter.value
})
list.value = res.data?.list || []
@@ -130,8 +134,8 @@ function search() {
function resetFilters() {
keyword.value = ''
statusFilter.value = undefined
- categoryId.value = undefined
- dramaTypeFilter.value = undefined
+ categoryIds.value = []
+ dramaTypes.value = []
hotFilter.value = undefined
search()
}
@@ -156,8 +160,8 @@ function openCreate() {
editingId.value = null
Object.assign(form, {
title: '',
- categoryId: undefined,
- dramaType: 'LIVE',
+ categoryIds: [],
+ dramaTypes: ['LIVE'],
description: '',
freeEpisodes: 3,
unlockCoin: 0,
@@ -178,8 +182,18 @@ function openEdit(row) {
editingId.value = row.id
Object.assign(form, {
title: row.title,
- categoryId: row.categoryId || undefined,
- dramaType: row.dramaType || 'LIVE',
+ categoryIds:
+ Array.isArray(row.categoryIds) && row.categoryIds.length
+ ? row.categoryIds
+ : row.categoryId
+ ? [row.categoryId]
+ : [],
+ dramaTypes:
+ Array.isArray(row.dramaTypes) && row.dramaTypes.length
+ ? row.dramaTypes
+ : row.dramaType
+ ? [row.dramaType]
+ : ['LIVE'],
description: row.description || '',
freeEpisodes: row.freeEpisodes ?? 3,
unlockCoin: row.unlockCoin ?? 0,
@@ -200,6 +214,16 @@ function dramaTypeLabel(value) {
return dramaTypeOptions.find((item) => item.value === value)?.label || '真人剧'
}
+function rowCategoryNames(row) {
+ const names = Array.isArray(row.categoryNames) ? row.categoryNames.filter(Boolean) : []
+ return names.length ? names.join(' / ') : row.genre || '—'
+}
+
+function rowDramaTypes(row) {
+ if (Array.isArray(row.dramaTypes) && row.dramaTypes.length) return row.dramaTypes
+ return row.dramaType ? [row.dramaType] : []
+}
+
async function saveDrama() {
if (saving.value || uploadingCover.value) return
try {
@@ -207,11 +231,13 @@ async function saveDrama() {
} catch {
return
}
- const category = categories.value.find((item) => item.id === form.categoryId)
+ const categoryNames = form.categoryIds
+ .map((id) => categories.value.find((item) => item.id === id)?.name)
+ .filter(Boolean)
const payload = {
...form,
title: form.title.trim(),
- genre: category?.name || ''
+ genre: categoryNames.join(' / ')
}
saving.value = true
try {
@@ -366,13 +392,27 @@ onMounted(async () => {
:key="item.code"
:label="item.text"
:value="item.code" /> {
>—
-
- {{ rowCategoryNames(row) }}
+
+ {{
- dramaTypeLabel(row.dramaType)
- }}{{ dramaTypeLabel(type) }}
{
-
-
+
+
{
/>
-
-
+
+
Date: Wed, 21 Oct 2026 17:16:40 +0800
Subject: [PATCH 6/6] =?UTF-8?q?fix(admin):=20=E5=A4=9A=E9=80=89=E7=B1=BB?=
=?UTF-8?q?=E5=9E=8B=E4=B8=8E=E5=86=85=E5=AE=B9=E7=B1=BB=E5=9E=8B=E5=AE=8C?=
=?UTF-8?q?=E6=95=B4=E5=B1=95=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../contentManagement/DramaManagementView.vue | 69 ++++++++++++++-----
1 file changed, 53 insertions(+), 16 deletions(-)
diff --git a/src/views/contentManagement/DramaManagementView.vue b/src/views/contentManagement/DramaManagementView.vue
index 439ebad..cba1ef5 100644
--- a/src/views/contentManagement/DramaManagementView.vue
+++ b/src/views/contentManagement/DramaManagementView.vue
@@ -214,9 +214,15 @@ function dramaTypeLabel(value) {
return dramaTypeOptions.find((item) => item.value === value)?.label || '真人剧'
}
-function rowCategoryNames(row) {
- const names = Array.isArray(row.categoryNames) ? row.categoryNames.filter(Boolean) : []
- return names.length ? names.join(' / ') : row.genre || '—'
+function rowCategoryList(row) {
+ if (Array.isArray(row.categoryNames) && row.categoryNames.length) {
+ return row.categoryNames.filter(Boolean)
+ }
+ if (!row.genre) return []
+ return row.genre
+ .split('/')
+ .map((name) => name.trim())
+ .filter(Boolean)
}
function rowDramaTypes(row) {
@@ -394,9 +400,8 @@ onMounted(async () => {
:value="item.code" /> {
:value="item.id" /> {
>—
- {{ rowCategoryNames(row) }}
-
- {{
+ name
+ }}
+
+
{
@@ -601,9 +610,8 @@ onMounted(async () => {
@@ -769,3 +777,32 @@ onMounted(async () => {
+
+