数据结构优化

This commit is contained in:
李琦
2026-01-23 16:05:23 +08:00
parent 889247b33a
commit 9d39b7c22b
11 changed files with 881 additions and 87 deletions

View File

@@ -96,7 +96,16 @@
<!-- Category Field -->
<div class="space-y-2">
<label for="category" class="block text-xs font-medium text-art-muted uppercase tracking-wider">分类</label>
<div class="flex items-center justify-between">
<label for="category" class="block text-xs font-medium text-art-muted uppercase tracking-wider">分类</label>
<button
type="button"
@click="showCreateCategory = true"
class="text-xs text-art-accent hover:text-white transition-colors"
>
+ 新建分类
</button>
</div>
<CustomSelect
v-model="form.categoryId"
:options="categories"
@@ -105,21 +114,79 @@
<div class="text-art-error text-xs mt-1" v-if="errors.categoryId">
{{ errors.categoryId }}
</div>
<!-- Quick Create Category -->
<div v-if="showCreateCategory" class="admin-card p-3 space-y-2 mt-2">
<input
v-model="newCategory.name"
placeholder="分类名称"
class="admin-input text-sm"
@keyup.enter="createCategory"
/>
<div class="flex gap-2">
<button type="button" @click="createCategory" class="admin-btn-primary text-xs px-3 py-1">创建</button>
<button type="button" @click="showCreateCategory = false" class="admin-btn-secondary text-xs px-3 py-1">取消</button>
</div>
</div>
</div>
<!-- Column Field -->
<div class="space-y-2">
<label for="column" class="block text-xs font-medium text-art-muted uppercase tracking-wider">专栏 <span class="text-art-muted text-xs">(可选)</span></label>
<div class="flex items-center justify-between">
<label for="column" class="block text-xs font-medium text-art-muted uppercase tracking-wider">专栏 <span class="text-art-muted text-xs">(可选)</span></label>
<button
type="button"
@click="showCreateColumn = true"
class="text-xs text-art-accent hover:text-white transition-colors"
>
+ 新建专栏
</button>
</div>
<CustomSelect
v-model="form.columnId"
:options="columns"
placeholder="选择专栏(可选)"
/>
<!-- Quick Create Column -->
<div v-if="showCreateColumn" class="admin-card p-3 space-y-2 mt-2">
<input
v-model="newColumn.name"
placeholder="专栏名称"
class="admin-input text-sm"
/>
<div>
<label class="block text-xs text-art-muted mb-2">封面图片</label>
<ImageUpload
v-model="newColumn.cover"
:categoryId="1"
/>
</div>
<textarea
v-model="newColumn.description"
placeholder="描述"
rows="2"
class="admin-input text-sm"
/>
<div class="flex gap-2">
<button type="button" @click="createColumn" class="admin-btn-primary text-xs px-3 py-1">创建</button>
<button type="button" @click="showCreateColumn = false" class="admin-btn-secondary text-xs px-3 py-1">取消</button>
</div>
</div>
</div>
<!-- Tags Field -->
<div class="space-y-2">
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider">标签</label>
<div class="flex items-center justify-between">
<label class="block text-xs font-medium text-art-muted uppercase tracking-wider">标签</label>
<button
type="button"
@click="showCreateTag = true"
class="text-xs text-art-accent hover:text-white transition-colors"
>
+ 新建标签
</button>
</div>
<div class="flex flex-wrap gap-2">
<button
type="button"
@@ -135,6 +202,20 @@
暂无可用标签
</div>
</div>
<!-- Quick Create Tag -->
<div v-if="showCreateTag" class="admin-card p-3 space-y-2 mt-2">
<input
v-model="newTag.name"
placeholder="标签名称"
class="admin-input text-sm"
@keyup.enter="createTag"
/>
<div class="flex gap-2">
<button type="button" @click="createTag" class="admin-btn-primary text-xs px-3 py-1">创建</button>
<button type="button" @click="showCreateTag = false" class="admin-btn-secondary text-xs px-3 py-1">取消</button>
</div>
</div>
</div>
<!-- Date Field -->
@@ -187,9 +268,21 @@ import { MdEditor } from 'md-editor-v3'
import 'md-editor-v3/lib/style.css'
import { useToast } from '../../composables/useToast'
import { createPost, updatePost, fetchPost, fetchCategories, fetchColumns, fetchTags, Tag } from '../../services/api'
import {
createPost,
updatePost,
fetchPost,
fetchCategories,
fetchColumns,
fetchTags,
createCategory as createCategoryApi,
createColumn as createColumnApi,
createTag as createTagApi,
Tag
} from '../../services/api'
import CustomSelect from '../../components/CustomSelect.vue'
import AccessLogModal from '../../components/admin/AccessLogModal.vue'
import ImageUpload from '../../components/admin/ImageUpload.vue'
const router = useRouter()
const route = useRoute()
@@ -217,6 +310,15 @@ const categories = ref<{value: number, label: string}[]>([])
const columns = ref<{value: number, label: string}[]>([])
const availableTags = ref<Tag[]>([])
// Create form states
const showCreateCategory = ref(false)
const showCreateColumn = ref(false)
const showCreateTag = ref(false)
const newCategory = ref({ name: '', slug: '', description: '' })
const newColumn = ref({ name: '', cover: '', description: '', isActive: 1, sortOrder: 0 })
const newTag = ref({ name: '', slug: '' })
// Form data
const form = reactive({
title: '',
@@ -283,6 +385,86 @@ const toggleTag = (tagId: number) => {
}
}
// Create category
const createCategory = async () => {
if (!newCategory.value.name) {
toast.showToast('请输入分类名称', 'error')
return
}
try {
await createCategoryApi({
name: newCategory.value.name,
slug: newCategory.value.slug || newCategory.value.name,
description: newCategory.value.description || '',
sortOrder: 0
})
toast.showToast('分类创建成功', 'success')
const createdName = newCategory.value.name
newCategory.value = { name: '', slug: '', description: '' }
showCreateCategory.value = false
await loadData()
// 自动选择新创建的分类
const newCat = categories.value.find(c => c.label === createdName)
if (newCat) {
form.categoryId = newCat.value
}
} catch (err: any) {
toast.showToast(err.message || '创建失败', 'error')
}
}
// Create column
const createColumn = async () => {
if (!newColumn.value.name) {
toast.showToast('请输入专栏名称', 'error')
return
}
try {
await createColumnApi(newColumn.value)
toast.showToast('专栏创建成功', 'success')
const createdName = newColumn.value.name
newColumn.value = { name: '', cover: '', description: '', isActive: 1, sortOrder: 0 }
showCreateColumn.value = false
await loadData()
// 自动选择新创建的专栏
const newCol = columns.value.find(c => c.label === createdName)
if (newCol) {
form.columnId = newCol.value
}
} catch (err: any) {
toast.showToast(err.message || '创建失败', 'error')
}
}
// Create tag
const createTag = async () => {
if (!newTag.value.name) {
toast.showToast('请输入标签名称', 'error')
return
}
try {
await createTagApi({
name: newTag.value.name,
slug: newTag.value.slug || newTag.value.name
})
toast.showToast('标签创建成功', 'success')
const createdName = newTag.value.name
newTag.value = { name: '', slug: '' }
showCreateTag.value = false
await loadData()
// 自动选择新创建的标签
const newTagItem = availableTags.value.find(t => t.name === createdName)
if (newTagItem && !form.tagIds.includes(newTagItem.id)) {
form.tagIds.push(newTagItem.id)
}
} catch (err: any) {
toast.showToast(err.message || '创建失败', 'error')
}
}
// Submit handler
const handleSubmit = async () => {
if (!validateForm()) {