feat(creator): 内容审核展示创作者与分集信息
This commit is contained in:
1 parent
71ee469563
commit
837266ce7e
4 files changed
+325
-29
No files matched your search
@@ -98,6 +98,11 @@ export const pageSubmissionsApi = (params: Record<string, unknown>) => {
|
||||
return request.get<PageResult<SubmissionItem>>({ url: '/admin/submissions/page', params })
|
||||
}
|
||||
|
||||
/** 内容提交详情:附带短剧预览 + 分集列表,审核弹窗展示用 */
|
||||
export const detailSubmissionApi = (id: number) => {
|
||||
return request.get<SubmissionItem>({ url: `/admin/submissions/${id}` })
|
||||
}
|
||||
|
||||
/** action: APPROVE / REJECT / WITHDRAW */
|
||||
export const auditSubmissionApi = (id: number, data: Record<string, unknown>) => {
|
||||
return request.put<SubmissionItem>({ url: `/admin/submissions/${id}/audit`, data })
|
||||
|
||||
@@ -43,6 +43,7 @@ const {
|
||||
openAudit,
|
||||
confirmAudit,
|
||||
freeze,
|
||||
unfreeze,
|
||||
createVisible,
|
||||
createSaving,
|
||||
createForm,
|
||||
@@ -61,8 +62,8 @@ const {
|
||||
<template>
|
||||
<PageHeading
|
||||
eyebrow="创作者管理"
|
||||
title="创作者入驻"
|
||||
description="审核创作者主体与资质,通过后创作者才能提交内容。"
|
||||
title="创作者账号"
|
||||
description="录入创作者并直接开通后台账号,账号即开即用,无需入驻审核。"
|
||||
>
|
||||
<el-button v-if="canAudit" type="primary" @click="openCreate">新增创作者</el-button>
|
||||
</PageHeading>
|
||||
@@ -170,13 +171,13 @@ const {
|
||||
<template #default="{ row }">
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" size="small" @click="openDocuments(row)">资质</el-button>
|
||||
<!-- 状态机:通过可从 pending/rejected/frozen 发起,驳回只允许 pending,冻结只允许 approved。
|
||||
已通过的行走「冻结 / 重置密码」,不再给「审核」入口(点了必然被后端拒) -->
|
||||
<!-- 账号由后台直接发放:已通过 / 冻结行都不再给「审核」入口;
|
||||
C 端提交的入驻申请仍是 pending,可在这里一次性通过 -->
|
||||
<el-button
|
||||
v-if="canAudit && row.status !== 'approved'"
|
||||
v-if="canAudit && row.status === 'pending'"
|
||||
size="small"
|
||||
@click="openAudit(row, 'APPROVE')"
|
||||
>审核</el-button
|
||||
>通过</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="canAudit && row.status === 'approved'"
|
||||
@@ -185,12 +186,18 @@ const {
|
||||
@click="freeze(row)"
|
||||
>冻结</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="canAudit && row.status === 'frozen'"
|
||||
size="small"
|
||||
@click="unfreeze(row)"
|
||||
>解冻</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="canOpenAccount && !row.adminUserId"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="accountSaving"
|
||||
:disabled="row.status !== 'approved'"
|
||||
:disabled="row.status === 'frozen'"
|
||||
@click="openAccount(row)"
|
||||
>开通账号</el-button
|
||||
>
|
||||
@@ -299,16 +306,24 @@ const {
|
||||
<el-form-item label="国家 / 地区">
|
||||
<el-input v-model="createForm.countryCode" maxlength="8" placeholder="ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label="同时开通后台账号">
|
||||
<el-switch v-model="createForm.openAccount" />
|
||||
<span class="console-muted creator-switch-hint"
|
||||
>开启后保存即生成登录账号与初始密码,一次完成</span
|
||||
>
|
||||
</el-form-item>
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="编号自动生成(CR+日期+序号),也是开通账号时默认的登录名;录入后需要先通过入驻审核才能开通账号"
|
||||
title="编号自动生成(CR+日期+序号),也是开通账号时默认的登录名;无需入驻审核"
|
||||
/>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="createVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="createSaving" @click="confirmCreate">录入</el-button>
|
||||
<el-button type="primary" :loading="createSaving" @click="confirmCreate">{{
|
||||
createForm.openAccount ? '创建并开通账号' : '仅创建'
|
||||
}}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import PageHeading from '@/views/components/PageHeading.vue'
|
||||
import { useRbac } from '@/stores/rbac'
|
||||
import { dateCell } from '@/utils/format'
|
||||
import { detailSubmissionApi } from '@/api/admin/creator'
|
||||
import {
|
||||
SUBMISSION_STATUS,
|
||||
SUBMISSION_TYPE,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
submissionTypeText,
|
||||
useSubmissionAudit
|
||||
} from './composables/useSubmissionAudit'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { can } = useRbac()
|
||||
const canAudit = can('submission:audit')
|
||||
@@ -32,9 +34,25 @@ const {
|
||||
query,
|
||||
reset,
|
||||
handleSizeChange,
|
||||
openAudit,
|
||||
openAudit: rawOpenAudit,
|
||||
confirmAudit
|
||||
} = useSubmissionAudit()
|
||||
|
||||
const auditDetailLoading = ref(false)
|
||||
|
||||
async function openAudit(row, defaultAction = 'APPROVE') {
|
||||
rawOpenAudit(row, defaultAction)
|
||||
auditDetailLoading.value = true
|
||||
try {
|
||||
const detail = await detailSubmissionApi(row.id)
|
||||
// 用 detail 里的完整字段覆盖 auditTarget(保留原始 openAudit 设置的 action)
|
||||
Object.assign(auditTarget.value, detail)
|
||||
} catch (e) {
|
||||
console.warn('加载提交详情失败', e)
|
||||
} finally {
|
||||
auditDetailLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -94,9 +112,19 @@ const {
|
||||
class="drama-el-table"
|
||||
empty-text="没有符合条件的提交记录"
|
||||
>
|
||||
<el-table-column prop="id" label="提交ID" width="90" />
|
||||
<el-table-column prop="dramaId" label="短剧ID" width="90" />
|
||||
<el-table-column prop="creatorId" label="创作者ID" width="100" />
|
||||
<el-table-column prop="id" label="提交ID" width="140" />
|
||||
<el-table-column prop="dramaId" label="短剧ID" width="140" />
|
||||
<el-table-column label="创作者" min-width="180">
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; flex-direction: column; line-height: 1.4">
|
||||
<span style="font-weight: 500">{{ row.creatorName || '—' }}</span>
|
||||
<span style="font-size: 12px; color: var(--el-text-color-secondary)">
|
||||
{{ row.creatorCode || '' }}{{ row.creatorCode && row.creatorMobile ? ' · ' : ''
|
||||
}}{{ row.creatorMobile || '' }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="提交类型" width="110">
|
||||
<template #default="{ row }">{{ submissionTypeText(row.type) }}</template>
|
||||
</el-table-column>
|
||||
@@ -153,18 +181,93 @@ const {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="auditVisible" title="内容提交审核" width="480px">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="提交信息">
|
||||
<el-input
|
||||
:model-value="
|
||||
auditTarget
|
||||
? `提交 #${auditTarget.id} · 短剧 ${auditTarget.dramaId} · ${submissionTypeText(auditTarget.type)}`
|
||||
: ''
|
||||
"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-dialog v-model="auditVisible" title="内容提交审核" width="640px">
|
||||
<div v-loading="auditDetailLoading" class="audit-detail">
|
||||
<!-- 顶部:创作者 + 提交信息 -->
|
||||
<div class="audit-meta">
|
||||
<div class="audit-meta-item">
|
||||
<span class="audit-meta-label">创作者</span>
|
||||
<span class="audit-meta-value">
|
||||
{{ auditTarget?.creatorName || '—' }}
|
||||
<span v-if="auditTarget?.creatorCode" class="audit-meta-sub">
|
||||
· {{ auditTarget.creatorCode }}
|
||||
</span>
|
||||
<span v-if="auditTarget?.creatorMobile" class="audit-meta-sub">
|
||||
· {{ auditTarget.creatorMobile }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="audit-meta-item">
|
||||
<span class="audit-meta-label">提交</span>
|
||||
<span class="audit-meta-value">
|
||||
#{{ auditTarget?.id }} ·
|
||||
<el-tag size="small" effect="plain">
|
||||
{{ submissionTypeText(auditTarget?.type) }}
|
||||
</el-tag>
|
||||
·
|
||||
<el-tag size="small" :type="submissionStatusMeta(auditTarget?.status).tag">
|
||||
{{ submissionStatusMeta(auditTarget?.status).label }}
|
||||
</el-tag>
|
||||
<span v-if="auditTarget?.version" class="audit-meta-sub">
|
||||
· v{{ auditTarget.version }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 短剧预览卡片 -->
|
||||
<div v-if="auditTarget?.dramaTitle" class="audit-drama">
|
||||
<div class="audit-drama-cover">
|
||||
<img
|
||||
v-if="auditTarget.dramaCover"
|
||||
:src="auditTarget.dramaCover"
|
||||
:alt="auditTarget.dramaTitle"
|
||||
/>
|
||||
<div v-else class="audit-drama-cover-placeholder">无封面</div>
|
||||
</div>
|
||||
<div class="audit-drama-info">
|
||||
<h4 class="audit-drama-title">{{ auditTarget.dramaTitle }}</h4>
|
||||
<div class="audit-drama-tags">
|
||||
<el-tag v-if="auditTarget.dramaGenre" size="small">{{ auditTarget.dramaGenre }}</el-tag>
|
||||
<el-tag v-if="auditTarget.dramaLanguage" size="small" type="info">
|
||||
{{ auditTarget.dramaLanguage }}
|
||||
</el-tag>
|
||||
<el-tag v-if="auditTarget.dramaTotalEpisodes != null" size="small" type="success">
|
||||
共 {{ auditTarget.dramaTotalEpisodes }} 集
|
||||
</el-tag>
|
||||
</div>
|
||||
<p v-if="auditTarget.dramaDescription" class="audit-drama-desc">
|
||||
{{ auditTarget.dramaDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="audit-drama audit-drama-empty">短剧预览加载中…</div>
|
||||
|
||||
<!-- 分集预览 -->
|
||||
<div v-if="auditTarget?.episodePreviews?.length" class="audit-episodes">
|
||||
<div class="audit-section-title">分集列表({{ auditTarget.episodePreviews.length }})</div>
|
||||
<div class="audit-episode-list">
|
||||
<div v-for="ep in auditTarget.episodePreviews" :key="ep.id" class="audit-episode-item">
|
||||
<div class="audit-episode-no">第{{ ep.epNo }}集</div>
|
||||
<div class="audit-episode-title">{{ ep.title || '(未命名)' }}</div>
|
||||
<div class="audit-episode-meta">
|
||||
<span v-if="ep.durationSec">{{ ep.durationSec }}s</span>
|
||||
<el-tag
|
||||
v-if="ep.status"
|
||||
size="small"
|
||||
:type="
|
||||
ep.status === 'success' ? 'success' : ep.status === 'failed' ? 'danger' : 'info'
|
||||
"
|
||||
>
|
||||
{{ ep.status }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form label-position="top" style="margin-top: 16px">
|
||||
<el-form-item label="审核结果">
|
||||
<el-radio-group v-model="auditForm.action">
|
||||
<el-radio label="APPROVE">通过</el-radio>
|
||||
@@ -201,3 +304,148 @@ const {
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.audit-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
.audit-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 28px;
|
||||
padding: 10px 14px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.audit-meta-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.audit-meta-label {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.audit-meta-value {
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.audit-meta-sub {
|
||||
font-weight: 400;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.audit-drama {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.audit-drama-cover {
|
||||
width: 84px;
|
||||
height: 112px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
background: var(--el-fill-color-light);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.audit-drama-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.audit-drama-cover-placeholder {
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-size: 12px;
|
||||
}
|
||||
.audit-drama-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.audit-drama-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
.audit-drama-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.audit-drama-desc {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.audit-drama-empty {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--el-text-color-placeholder);
|
||||
font-size: 13px;
|
||||
}
|
||||
.audit-episodes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.audit-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.audit-episode-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
.audit-episode-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.audit-episode-no {
|
||||
flex-shrink: 0;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-weight: 500;
|
||||
min-width: 56px;
|
||||
}
|
||||
.audit-episode-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--el-text-color-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.audit-episode-meta {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -146,6 +146,22 @@ export function useCreatorAudit() {
|
||||
await load()
|
||||
}
|
||||
|
||||
/** 解冻:冻结是可控状态,解冻后即可恢复使用与提交 */
|
||||
async function unfreeze(row) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`解冻后「${row.name}」可继续使用,确认解冻?`, '解冻创作者', {
|
||||
confirmButtonText: '确认解冻',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
await auditCreatorApi(row.id, { action: 'APPROVE', reason: '运营解冻' })
|
||||
ElMessage.success('已解冻')
|
||||
await load()
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
|
||||
// ---------- 手工录入创作者主体 ----------
|
||||
@@ -156,7 +172,9 @@ export function useCreatorAudit() {
|
||||
type: 'COMPANY',
|
||||
contactName: '',
|
||||
contactEmail: '',
|
||||
countryCode: 'ID'
|
||||
countryCode: 'ID',
|
||||
// 录入即开号:默认勾选,保存后直接弹出账号密码
|
||||
openAccount: true
|
||||
})
|
||||
|
||||
function openCreate() {
|
||||
@@ -165,7 +183,8 @@ export function useCreatorAudit() {
|
||||
type: 'COMPANY',
|
||||
contactName: '',
|
||||
contactEmail: '',
|
||||
countryCode: 'ID'
|
||||
countryCode: 'ID',
|
||||
openAccount: true
|
||||
})
|
||||
createVisible.value = true
|
||||
}
|
||||
@@ -174,9 +193,17 @@ export function useCreatorAudit() {
|
||||
if (!createForm.name.trim()) return ElMessage.warning('请填写创作者名称')
|
||||
createSaving.value = true
|
||||
try {
|
||||
await createCreatorApi({ ...createForm, name: createForm.name.trim() })
|
||||
ElMessage.success('已录入,待入驻审核通过后才能开通账号')
|
||||
const created = await createCreatorApi({ ...createForm, name: createForm.name.trim() })
|
||||
const creatorId = created.data
|
||||
createVisible.value = false
|
||||
if (createForm.openAccount && creatorId) {
|
||||
// 录入后立刻开号,凭据只在这里出现一次
|
||||
const res = await openCreatorAccountApi(creatorId)
|
||||
accountResult.value = res.data || null
|
||||
accountVisible.value = true
|
||||
} else {
|
||||
ElMessage.success('创作者已创建')
|
||||
}
|
||||
await load()
|
||||
} finally {
|
||||
createSaving.value = false
|
||||
@@ -264,6 +291,7 @@ export function useCreatorAudit() {
|
||||
openAudit,
|
||||
confirmAudit,
|
||||
freeze,
|
||||
unfreeze,
|
||||
createVisible,
|
||||
createSaving,
|
||||
createForm,
|
||||
|
||||
Reference in new issue
Block a user