添加管理后台公告管理功能
## 新增功能 - 管理后台公告管理页面(/admin/announcements) - 公告 CRUD 操作(创建、编辑、删除) - 公告发布和归档功能 - 按状态和分类筛选 - 在侧边栏添加公告管理入口 ## 功能特性 - 支持设置优先级、置顶、重要标记 - 富文本编辑器(支持 Markdown/HTML) - 实时查看浏览次数 - 权限控制(announcement:view 和 announcement:manage) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Bell, Document, QuestionFilled, Warning } from '@element-plus/icons-vue'
|
||||
import type { Announcement } from '@/features/announcement'
|
||||
import type { CreateAnnouncementRequest, UpdateAnnouncementRequest } from '@/features/admin/api/adminAnnouncements'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
mode: 'create' | 'edit'
|
||||
announcement?: Announcement | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
save: [data: CreateAnnouncementRequest | UpdateAnnouncementRequest]
|
||||
}>()
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const formData = ref<CreateAnnouncementRequest>({
|
||||
title: '',
|
||||
content: '',
|
||||
category: 'notice',
|
||||
priority: 0,
|
||||
is_pinned: false,
|
||||
is_important: false,
|
||||
})
|
||||
|
||||
const categories = [
|
||||
{ value: 'notice', label: '通知公告', icon: Bell },
|
||||
{ value: 'tutorial', label: '使用教程', icon: Document },
|
||||
{ value: 'rule', label: '规则说明', icon: Warning },
|
||||
{ value: 'faq', label: '常见问题', icon: QuestionFilled },
|
||||
]
|
||||
|
||||
const rules: FormRules = {
|
||||
title: [
|
||||
{ required: true, message: '请输入公告标题', trigger: 'blur' },
|
||||
{ max: 255, message: '标题不能超过255个字符', trigger: 'blur' },
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '请输入公告内容', trigger: 'blur' },
|
||||
],
|
||||
category: [
|
||||
{ required: true, message: '请选择公告分类', trigger: 'change' },
|
||||
],
|
||||
}
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
return props.mode === 'create' ? '新建公告' : '编辑公告'
|
||||
})
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
if (props.mode === 'edit' && props.announcement) {
|
||||
formData.value = {
|
||||
title: props.announcement.title,
|
||||
content: props.announcement.content,
|
||||
category: props.announcement.category as any,
|
||||
priority: props.announcement.priority,
|
||||
is_pinned: props.announcement.is_pinned,
|
||||
is_important: props.announcement.is_important,
|
||||
}
|
||||
} else {
|
||||
formData.value = {
|
||||
title: '',
|
||||
content: '',
|
||||
category: 'notice',
|
||||
priority: 0,
|
||||
is_pinned: false,
|
||||
is_important: false,
|
||||
}
|
||||
}
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
emit('save', formData.value)
|
||||
} catch (error) {
|
||||
console.error('表单验证失败', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="visible"
|
||||
:title="dialogTitle"
|
||||
width="800px"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="公告标题" prop="title">
|
||||
<el-input
|
||||
v-model="formData.title"
|
||||
placeholder="请输入公告标题"
|
||||
maxlength="255"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="公告分类" prop="category">
|
||||
<el-select v-model="formData.category" placeholder="请选择分类">
|
||||
<el-option
|
||||
v-for="cat in categories"
|
||||
:key="cat.value"
|
||||
:label="cat.label"
|
||||
:value="cat.value"
|
||||
>
|
||||
<el-icon style="margin-right: 8px"><component :is="cat.icon" /></el-icon>
|
||||
{{ cat.label }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="公告内容" prop="content">
|
||||
<el-input
|
||||
v-model="formData.content"
|
||||
type="textarea"
|
||||
:rows="12"
|
||||
placeholder="支持 Markdown 或 HTML 格式,可以使用标题、列表、链接等"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="优先级">
|
||||
<el-input-number
|
||||
v-model="formData.priority"
|
||||
:min="0"
|
||||
:max="999"
|
||||
placeholder="数值越大越靠前"
|
||||
/>
|
||||
<span style="margin-left: 12px; color: #909399; font-size: 13px">数值越大越靠前,默认为0</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="标记">
|
||||
<el-checkbox v-model="formData.is_pinned">置顶</el-checkbox>
|
||||
<el-checkbox v-model="formData.is_important" style="margin-left: 20px">重要</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-textarea__inner) {
|
||||
font-family: 'SF Mono', 'Menlo', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user