From eaee7652396806b7181016ca3700678981f59e78 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Sat, 24 Oct 2026 17:30:29 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix(app-version):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E9=A1=B5=E7=B1=BB=E5=9E=8B=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/systemManagement/AppVersionView.vue | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/views/systemManagement/AppVersionView.vue b/src/views/systemManagement/AppVersionView.vue index 90da577..903f4f7 100644 --- a/src/views/systemManagement/AppVersionView.vue +++ b/src/views/systemManagement/AppVersionView.vue @@ -3,13 +3,12 @@ + + diff --git a/src/views/contentManagement/CategoryDramaView.vue b/src/views/contentManagement/CategoryDramaView.vue index 8107ff2..ed235bb 100644 --- a/src/views/contentManagement/CategoryDramaView.vue +++ b/src/views/contentManagement/CategoryDramaView.vue @@ -6,6 +6,12 @@ import { computed, onMounted, reactive, ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { AppIcon } from '@/components/AppIcon' import { AppIcons } from '@/constants/icons' +import { + languageCodes, + languageLabel, + localesFromColumns, + payloadLocales +} from '@/constants/contentLanguages' import { bindClassificationDramasApi, createClassificationApi, @@ -18,6 +24,8 @@ import { } from '@/api/admin/dramaClassification' import { statusClass } from '@/utils/status' import { useRbac } from '@/stores/rbac' +import ClassificationDramaDialogs from './ClassificationDramaDialogs.vue' +import ClassificationSideList from './ClassificationSideList.vue' import PageHeading from '@/views/components/PageHeading.vue' const { can } = useRbac() @@ -46,7 +54,9 @@ const keyword = ref('') const dialogVisible = ref(false) const saving = ref(false) const editingId = ref(null) -const form = reactive({ name: '', sort: 1, status: 1 }) +const languages = ref(['zh']) +const emptyForm = () => ({ locales: { zh: '' }, sort: 1, status: 1 }) +const form = reactive(emptyForm()) const candidateVisible = ref(false) const candidateLoading = ref(false) @@ -110,25 +120,39 @@ function changePage(value) { function openCreate() { editingId.value = null + languages.value = ['zh'] const maxSort = classifications.value.reduce((max, item) => Math.max(max, item.sort || 0), 0) - Object.assign(form, { name: '', sort: maxSort + 1, status: 1 }) + Object.assign(form, emptyForm(), { sort: maxSort + 1 }) dialogVisible.value = true } function openEdit(row) { editingId.value = row.id - Object.assign(form, { name: row.name, sort: row.sort ?? 0, status: row.status ?? 1 }) + const locales = localesFromColumns(row.locales, row.name, row.nameEn, row.nameId) + languages.value = languageCodes(locales) + Object.assign(form, { + locales, + sort: row.sort ?? 0, + status: row.status ?? 1 + }) dialogVisible.value = true } async function saveClassification() { - if (!form.name.trim()) { - ElMessage.warning('请填写分类名称') + const locales = payloadLocales(form.locales, languages.value) + const missing = languages.value.find((code) => !locales[code]) + if (!locales.zh || missing) { + ElMessage.warning(missing ? `请填写${languageLabel(missing)}名称` : '请填写中文名称') return } saving.value = true try { - const payload = { name: form.name.trim(), sort: form.sort, status: form.status } + const payload = { + name: locales.zh, + locales, + sort: form.sort, + status: form.status + } if (editingId.value) { await updateClassificationApi(editingId.value, payload) ElMessage.success('分类已更新') @@ -256,48 +280,13 @@ onMounted(loadClassifications)
- +
@@ -376,77 +365,27 @@ onMounted(loadClassifications)
- - - - - -
- - - - - - -
-
- -
- - - - - - - - - - - - - - -
- -
- -
+ diff --git a/src/views/contentManagement/CategoryManagementView.vue b/src/views/contentManagement/CategoryManagementView.vue index ea00632..7e6e67a 100644 --- a/src/views/contentManagement/CategoryManagementView.vue +++ b/src/views/contentManagement/CategoryManagementView.vue @@ -4,6 +4,12 @@ import { onMounted, reactive, ref } from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import { AppIcons } from '@/constants/icons' +import { + languageCodes, + languageLabel, + localesFromColumns, + payloadLocales +} from '@/constants/contentLanguages' import { createDramaCategoryApi, deleteDramaCategoryApi, @@ -11,6 +17,7 @@ import { updateDramaCategoryApi } from '@/api/admin/drama' import { useRbac } from '@/stores/rbac' +import LocaleTextFields from '@/views/components/LocaleTextFields.vue' import PageHeading from '@/views/components/PageHeading.vue' const { can } = useRbac() @@ -20,7 +27,8 @@ const list = ref([]) const dialogVisible = ref(false) const saving = ref(false) const editingId = ref(null) -const form = reactive({ name: '', sort: 1, status: 1 }) +const languages = ref(['zh']) +const form = reactive({ locales: { zh: '' }, sort: 1, status: 1 }) async function load() { loading.value = true @@ -38,21 +46,29 @@ function nextSort() { function openCreate() { editingId.value = null - Object.assign(form, { name: '', sort: nextSort(), status: 1 }) + languages.value = ['zh'] + Object.assign(form, { locales: { zh: '' }, sort: nextSort(), status: 1 }) dialogVisible.value = true } function openEdit(row) { editingId.value = row.id - Object.assign(form, { name: row.name, sort: row.sort ?? 0, status: row.status ?? 1 }) + const locales = localesFromColumns(row.locales, row.name, row.nameEn, row.nameId) + languages.value = languageCodes(locales) + Object.assign(form, { locales, sort: row.sort ?? 0, status: row.status ?? 1 }) dialogVisible.value = true } async function saveCategory() { - if (!form.name.trim()) return ElMessage.warning('请填写类型名称') + const locales = payloadLocales(form.locales, languages.value) + const missing = languages.value.find((code) => !locales[code]) + if (!locales.zh || missing) { + ElMessage.warning(missing ? `请填写${languageLabel(missing)}名称` : '请填写中文名称') + return + } saving.value = true try { - const payload = { name: form.name.trim(), sort: form.sort, status: form.status } + const payload = { name: locales.zh, locales, sort: form.sort, status: form.status } if (editingId.value) { await updateDramaCategoryApi(editingId.value, payload) ElMessage.success('类型已更新') @@ -115,7 +131,20 @@ onMounted(load) empty-text="还没有短剧类型" > - + + + + @@ -146,11 +175,14 @@ onMounted(load) - + - - - +
@@ -166,3 +198,9 @@ onMounted(load) + + diff --git a/src/views/contentManagement/ClassificationDramaDialogs.vue b/src/views/contentManagement/ClassificationDramaDialogs.vue new file mode 100644 index 0000000..abccbd1 --- /dev/null +++ b/src/views/contentManagement/ClassificationDramaDialogs.vue @@ -0,0 +1,158 @@ + + + + + + + + diff --git a/src/views/contentManagement/ClassificationSideList.vue b/src/views/contentManagement/ClassificationSideList.vue new file mode 100644 index 0000000..0c9abd3 --- /dev/null +++ b/src/views/contentManagement/ClassificationSideList.vue @@ -0,0 +1,140 @@ + + + + + + + + diff --git a/src/views/contentManagement/DramaManagementView.vue b/src/views/contentManagement/DramaManagementView.vue index ba65d0c..b46faf8 100644 --- a/src/views/contentManagement/DramaManagementView.vue +++ b/src/views/contentManagement/DramaManagementView.vue @@ -5,6 +5,13 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue' import { useRouter } from 'vue-router' import { ElMessage, ElMessageBox } from 'element-plus' import { AppIcons } from '@/constants/icons' +import { + languageLabel, + languageCodes, + localesFromColumns, + payloadLocales +} from '@/constants/contentLanguages' +import LocaleTextFields from '@/views/components/LocaleTextFields.vue' import { AppImage } from '@/components/AppImage' import { changeDramaStatusApi, @@ -16,6 +23,7 @@ import { } from '@/api/admin/drama' import { useRbac } from '@/stores/rbac' import { formatCount } from '@/utils/format' +import { listContentTagsApi } from '@/api/admin/contentTag' import { statusClass } from '@/utils/status' import { uploadFile } from '@/utils/uploader' @@ -30,8 +38,7 @@ const categories = ref([]) const keyword = ref('') const statusFilter = ref(undefined) const categoryIds = ref([]) -const dramaTypes = ref([]) -const hotFilter = ref(undefined) +const tags = ref([]) const pageIndex = ref(1) const pageSize = ref(10) @@ -45,11 +52,14 @@ const editingId = ref(null) const editingCommentCount = ref(0) const editingLikeCount = ref(0) const formRef = ref() +const textLanguages = ref(['zh']) const form = reactive({ title: '', + titleLocales: { zh: '' }, categoryIds: [], - dramaTypes: ['LIVE'], + tagId: null, description: '', + synopsisLocales: { zh: '' }, freeEpisodes: 3, unlockCoin: 0, favoriteCount: 0, @@ -62,16 +72,6 @@ const form = reactive({ const MAX_COVER_SIZE = 5 * 1024 * 1024 const COVER_TYPES = ['image/jpeg', 'image/png', 'image/webp'] -const formRules = { - title: [{ required: true, message: '请输入短剧名称', trigger: 'blur' }], - categoryIds: [ - { type: 'array', required: true, min: 1, message: '请至少选择一个类型', trigger: 'change' } - ], - dramaTypes: [ - { type: 'array', required: true, min: 1, message: '请至少选择一个内容类型', trigger: 'change' } - ] -} - const coverDisplayName = computed( () => coverFileName.value || (form.cover ? coverBasename(form.cover) : '尚未选择封面') ) @@ -90,11 +90,6 @@ const statusOptions = [ { code: 'offline', text: '已下线' } ] -const dramaTypeOptions = [ - { value: 'ANIME', label: '漫剧' }, - { value: 'LIVE', label: '真人剧' } -] - const actionMeta = { submit: { label: '提交审核', done: '已提交审核' }, approve: { label: '审核通过', done: '已通过审核' }, @@ -111,9 +106,7 @@ async function load() { pageSize: pageSize.value, keyword: keyword.value.trim() || undefined, status: statusFilter.value || undefined, - categoryIds: categoryIds.value.length ? categoryIds.value : undefined, - dramaTypes: dramaTypes.value.length ? dramaTypes.value : undefined, - isHot: hotFilter.value + categoryIds: categoryIds.value.length ? categoryIds.value : undefined }) list.value = res.data?.list || [] total.value = res.data?.total || 0 @@ -127,6 +120,11 @@ async function loadCategories() { categories.value = res.data || [] } +async function loadTags() { + const res = await listContentTagsApi(true) + tags.value = res.data || [] +} + function search() { pageIndex.value = 1 load() @@ -136,8 +134,6 @@ function resetFilters() { keyword.value = '' statusFilter.value = undefined categoryIds.value = [] - dramaTypes.value = [] - hotFilter.value = undefined search() } @@ -146,24 +142,50 @@ function handleSizeChange() { load() } +function selectedCategoryIds(row) { + const values = + Array.isArray(row.categoryIds) && row.categoryIds.length + ? row.categoryIds + : row.categoryId + ? [row.categoryId] + : [] + return values.filter((id) => Number(id) > 0) +} + function coverBasename(path) { if (!path) return '' const clean = path.split('?')[0].split('#')[0] return decodeURIComponent(clean.split('/').pop() || clean) } +/** 长整型计数字段在接口里是字符串,数字输入框只接受 number。 */ +function toCount(value) { + const count = Number(value) + return Number.isFinite(count) ? count : 0 +} + async function resetValidation() { await nextTick() formRef.value?.clearValidate() } +function changeTextLanguages(codes) { + textLanguages.value = codes + codes.forEach((code) => { + if (form.synopsisLocales[code] == null) form.synopsisLocales[code] = '' + }) +} + function openCreate() { editingId.value = null + textLanguages.value = ['zh'] Object.assign(form, { title: '', + titleLocales: { zh: '' }, categoryIds: [], - dramaTypes: ['LIVE'], + tagId: null, description: '', + synopsisLocales: { zh: '' }, freeEpisodes: 3, unlockCoin: 0, favoriteCount: 0, @@ -182,41 +204,35 @@ function openCreate() { function openEdit(row) { editingId.value = row.id + const titleLocales = localesFromColumns(row.titleLocales, row.title) + const synopsisLocales = localesFromColumns(row.synopsisLocales, row.description) + textLanguages.value = languageCodes(titleLocales) + textLanguages.value.forEach((code) => { + if (synopsisLocales[code] == null) synopsisLocales[code] = '' + }) Object.assign(form, { title: row.title, - 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'], + titleLocales, + categoryIds: selectedCategoryIds(row), + tagId: Number(row.tagId) > 0 ? row.tagId : null, description: row.description || '', - freeEpisodes: row.freeEpisodes ?? 3, - unlockCoin: row.unlockCoin ?? 0, - favoriteCount: row.favoriteCount ?? 0, - heat: row.views ?? 0, + synopsisLocales, + freeEpisodes: toCount(row.freeEpisodes ?? 3), + unlockCoin: toCount(row.unlockCoin), + favoriteCount: toCount(row.favoriteCount), + heat: toCount(row.views), isHot: !!row.isHot, - sort: row.sort ?? 0, + sort: toCount(row.sort), cover: row.cover || '' }) coverFileName.value = row.cover ? coverBasename(row.cover) : '' coverError.value = '' - editingCommentCount.value = row.commentCount ?? 0 - editingLikeCount.value = row.likeCount ?? 0 + editingCommentCount.value = toCount(row.commentCount) + editingLikeCount.value = toCount(row.likeCount) dialogVisible.value = true resetValidation() } -function dramaTypeLabel(value) { - return dramaTypeOptions.find((item) => item.value === value)?.label || '真人剧' -} - function rowCategoryList(row) { if (Array.isArray(row.categoryNames) && row.categoryNames.length) { return row.categoryNames.filter(Boolean) @@ -228,25 +244,26 @@ function rowCategoryList(row) { .filter(Boolean) } -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 { - await formRef.value?.validate() - } catch { + const titles = payloadLocales(form.titleLocales, textLanguages.value) + const missing = textLanguages.value.find((code) => !titles[code]) + if (!titles.zh || missing) { + ElMessage.warning(missing ? `请填写${languageLabel(missing)}名称` : '请输入短剧名称') return } + const synopses = payloadLocales(form.synopsisLocales, textLanguages.value) const categoryNames = form.categoryIds .map((id) => categories.value.find((item) => item.id === id)?.name) .filter(Boolean) const payload = { ...form, - title: form.title.trim(), - genre: categoryNames.join(' / ') + title: titles.zh, + titleLocales: titles, + description: synopses.zh || '', + synopsisLocales: synopses, + genre: categoryNames.join(' / '), + tagId: form.tagId || 0 } saving.value = true try { @@ -370,7 +387,7 @@ function reviewActions(row) { } onMounted(async () => { - await loadCategories() + await Promise.all([loadCategories(), loadTags()]) await load() }) @@ -413,22 +430,6 @@ onMounted(async () => { :key="item.id" :label="item.name" :value="item.id" />重置
共 {{ total }} 部短剧 @@ -465,24 +466,17 @@ onMounted(async () => { > — - {{ row.tagName }}— { }} -