|
|
|
|
@@ -0,0 +1,684 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="announcements-page">
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
<h1 class="page-title">通知公告管理</h1>
|
|
|
|
|
<p class="page-subtitle">管理系统中的所有通知公告</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 搜索和操作栏 -->
|
|
|
|
|
<el-card class="search-card" shadow="never">
|
|
|
|
|
<el-row :gutter="20" class="search-row">
|
|
|
|
|
<el-col :xs="24" :sm="12" :md="8" :lg="6">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="searchForm.keyword"
|
|
|
|
|
placeholder="搜索公告标题或内容"
|
|
|
|
|
:prefix-icon="Search"
|
|
|
|
|
clearable
|
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
/>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xs="24" :sm="12" :md="8" :lg="6">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="searchForm.status"
|
|
|
|
|
placeholder="选择状态"
|
|
|
|
|
clearable
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
>
|
|
|
|
|
<el-option label="全部" value="" />
|
|
|
|
|
<el-option label="草稿" value="draft" />
|
|
|
|
|
<el-option label="已发布" value="published" />
|
|
|
|
|
<el-option label="已归档" value="archived" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xs="24" :sm="12" :md="8" :lg="6">
|
|
|
|
|
<el-select
|
|
|
|
|
v-model="searchForm.type"
|
|
|
|
|
placeholder="选择类型"
|
|
|
|
|
clearable
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
>
|
|
|
|
|
<el-option label="全部" value="" />
|
|
|
|
|
<el-option label="系统通知" value="system" />
|
|
|
|
|
<el-option label="维护公告" value="maintenance" />
|
|
|
|
|
<el-option label="活动推广" value="promotion" />
|
|
|
|
|
<el-option label="重要警告" value="warning" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xs="24" :sm="12" :md="8" :lg="6" class="button-group">
|
|
|
|
|
<el-button type="primary" @click="handleSearch">
|
|
|
|
|
<el-icon><Search /></el-icon>
|
|
|
|
|
搜索
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button @click="handleReset">
|
|
|
|
|
<el-icon><Refresh /></el-icon>
|
|
|
|
|
重置
|
|
|
|
|
</el-button>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- 公告列表 -->
|
|
|
|
|
<el-card class="table-card" shadow="never">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<span class="card-title">公告列表 ({{ pagination.total }})</span>
|
|
|
|
|
<el-button type="primary" @click="showCreateDialog">
|
|
|
|
|
<el-icon><Plus /></el-icon>
|
|
|
|
|
新建公告
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<el-table
|
|
|
|
|
v-loading="loading"
|
|
|
|
|
:data="announcements"
|
|
|
|
|
stripe
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
@sort-change="handleSortChange"
|
|
|
|
|
>
|
|
|
|
|
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="title" label="标题" min-width="200">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<div class="title-cell">
|
|
|
|
|
<el-tag v-if="row.is_pinned" type="danger" size="small" class="pin-tag">
|
|
|
|
|
置顶
|
|
|
|
|
</el-tag>
|
|
|
|
|
<span class="title-text">{{ row.title }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="type" label="类型" width="120">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="getTypeTagType(row.type)" size="small">
|
|
|
|
|
{{ getTypeLabel(row.type) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="priority" label="优先级" width="100">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="getPriorityTagType(row.priority)" size="small">
|
|
|
|
|
{{ getPriorityLabel(row.priority) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="status" label="状态" width="100">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-tag :type="getStatusTagType(row.status)" size="small">
|
|
|
|
|
{{ getStatusLabel(row.status) }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="creator_name" label="创建者" width="120" />
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="publish_time" label="发布时间" width="180">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ row.publish_time ? formatDateTime(row.publish_time) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column prop="created_at" label="创建时间" width="180" sortable="custom">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
{{ formatDateTime(row.created_at) }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
|
|
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button type="primary" size="small" @click="showEditDialog(row)">
|
|
|
|
|
编辑
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button
|
|
|
|
|
v-if="row.status === 'draft'"
|
|
|
|
|
type="success"
|
|
|
|
|
size="small"
|
|
|
|
|
@click="publishAnnouncement(row)"
|
|
|
|
|
>
|
|
|
|
|
发布
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-button type="danger" size="small" @click="deleteAnnouncement(row)">
|
|
|
|
|
删除
|
|
|
|
|
</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<div class="pagination-wrapper">
|
|
|
|
|
<el-pagination
|
|
|
|
|
v-model:current-page="pagination.page"
|
|
|
|
|
v-model:page-size="pagination.limit"
|
|
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
:total="pagination.total"
|
|
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
@size-change="handleSizeChange"
|
|
|
|
|
@current-change="handleCurrentChange"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<!-- 创建/编辑公告对话框 -->
|
|
|
|
|
<el-dialog
|
|
|
|
|
v-model="dialogVisible"
|
|
|
|
|
:title="isEdit ? '编辑公告' : '新建公告'"
|
|
|
|
|
width="800px"
|
|
|
|
|
:close-on-click-modal="false"
|
|
|
|
|
>
|
|
|
|
|
<el-form
|
|
|
|
|
ref="formRef"
|
|
|
|
|
:model="form"
|
|
|
|
|
:rules="formRules"
|
|
|
|
|
label-width="100px"
|
|
|
|
|
>
|
|
|
|
|
<el-form-item label="公告标题" prop="title">
|
|
|
|
|
<el-input v-model="form.title" placeholder="请输入公告标题" maxlength="255" show-word-limit />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-form-item label="公告内容" prop="content">
|
|
|
|
|
<el-input
|
|
|
|
|
v-model="form.content"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="6"
|
|
|
|
|
placeholder="请输入公告内容"
|
|
|
|
|
maxlength="2000"
|
|
|
|
|
show-word-limit
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="公告类型" prop="type">
|
|
|
|
|
<el-select v-model="form.type" placeholder="选择公告类型" style="width: 100%">
|
|
|
|
|
<el-option label="系统通知" value="system" />
|
|
|
|
|
<el-option label="维护公告" value="maintenance" />
|
|
|
|
|
<el-option label="活动推广" value="promotion" />
|
|
|
|
|
<el-option label="重要警告" value="warning" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="优先级" prop="priority">
|
|
|
|
|
<el-select v-model="form.priority" placeholder="选择优先级" style="width: 100%">
|
|
|
|
|
<el-option label="低" value="low" />
|
|
|
|
|
<el-option label="中" value="medium" />
|
|
|
|
|
<el-option label="高" value="high" />
|
|
|
|
|
<el-option label="紧急" value="urgent" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="状态" prop="status">
|
|
|
|
|
<el-select v-model="form.status" placeholder="选择状态" style="width: 100%">
|
|
|
|
|
<el-option label="草稿" value="draft" />
|
|
|
|
|
<el-option label="发布" value="published" />
|
|
|
|
|
<el-option label="归档" value="archived" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="是否置顶">
|
|
|
|
|
<el-switch v-model="form.is_pinned" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="发布时间">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="form.publish_time"
|
|
|
|
|
type="datetime"
|
|
|
|
|
placeholder="选择发布时间"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
<el-form-item label="过期时间">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="form.expire_time"
|
|
|
|
|
type="datetime"
|
|
|
|
|
placeholder="选择过期时间"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
|
|
|
<el-button type="primary" @click="handleSubmit" :loading="submitting">
|
|
|
|
|
{{ isEdit ? '更新' : '创建' }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { ref, reactive, onMounted, computed } from 'vue'
|
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
import { Search, Refresh, Plus } from '@element-plus/icons-vue'
|
|
|
|
|
import { useUserStore } from '@/stores/user'
|
|
|
|
|
import api from '@/utils/api'
|
|
|
|
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
|
|
|
|
// 响应式数据
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const submitting = ref(false)
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
|
|
const isEdit = ref(false)
|
|
|
|
|
const formRef = ref()
|
|
|
|
|
|
|
|
|
|
// 公告列表
|
|
|
|
|
const announcements = ref([])
|
|
|
|
|
|
|
|
|
|
// 搜索表单
|
|
|
|
|
const searchForm = reactive({
|
|
|
|
|
keyword: '',
|
|
|
|
|
status: '',
|
|
|
|
|
type: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 分页
|
|
|
|
|
const pagination = reactive({
|
|
|
|
|
page: 1,
|
|
|
|
|
limit: 10,
|
|
|
|
|
total: 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
const form = reactive({
|
|
|
|
|
id: null,
|
|
|
|
|
title: '',
|
|
|
|
|
content: '',
|
|
|
|
|
type: 'system',
|
|
|
|
|
priority: 'medium',
|
|
|
|
|
status: 'draft',
|
|
|
|
|
is_pinned: false,
|
|
|
|
|
publish_time: '',
|
|
|
|
|
expire_time: ''
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 表单验证规则
|
|
|
|
|
const formRules = {
|
|
|
|
|
title: [
|
|
|
|
|
{ required: true, message: '请输入公告标题', trigger: 'blur' },
|
|
|
|
|
{ min: 1, max: 255, message: '标题长度在 1 到 255 个字符', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
content: [
|
|
|
|
|
{ required: true, message: '请输入公告内容', trigger: 'blur' },
|
|
|
|
|
{ min: 1, max: 2000, message: '内容长度在 1 到 2000 个字符', trigger: 'blur' }
|
|
|
|
|
],
|
|
|
|
|
type: [
|
|
|
|
|
{ required: true, message: '请选择公告类型', trigger: 'change' }
|
|
|
|
|
],
|
|
|
|
|
priority: [
|
|
|
|
|
{ required: true, message: '请选择优先级', trigger: 'change' }
|
|
|
|
|
],
|
|
|
|
|
status: [
|
|
|
|
|
{ required: true, message: '请选择状态', trigger: 'change' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算属性
|
|
|
|
|
const totalPages = computed(() => Math.ceil(pagination.total / pagination.limit))
|
|
|
|
|
|
|
|
|
|
// 方法
|
|
|
|
|
const fetchAnnouncements = async () => {
|
|
|
|
|
try {
|
|
|
|
|
loading.value = true
|
|
|
|
|
const params = {
|
|
|
|
|
page: pagination.page,
|
|
|
|
|
limit: pagination.limit,
|
|
|
|
|
...searchForm
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await api.get('/announcements', { params })
|
|
|
|
|
if (response.data.success) {
|
|
|
|
|
announcements.value = response.data.data.announcements
|
|
|
|
|
pagination.total = response.data.data.total
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取公告列表失败:', error)
|
|
|
|
|
ElMessage.error('获取公告列表失败')
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSearch = () => {
|
|
|
|
|
pagination.page = 1
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
Object.assign(searchForm, {
|
|
|
|
|
keyword: '',
|
|
|
|
|
status: '',
|
|
|
|
|
type: ''
|
|
|
|
|
})
|
|
|
|
|
pagination.page = 1
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSizeChange = (size) => {
|
|
|
|
|
pagination.limit = size
|
|
|
|
|
pagination.page = 1
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleCurrentChange = (page) => {
|
|
|
|
|
pagination.page = page
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSortChange = ({ prop, order }) => {
|
|
|
|
|
// 实现排序逻辑
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showCreateDialog = () => {
|
|
|
|
|
isEdit.value = false
|
|
|
|
|
resetForm()
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showEditDialog = (row) => {
|
|
|
|
|
isEdit.value = true
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
id: row.id,
|
|
|
|
|
title: row.title,
|
|
|
|
|
content: row.content,
|
|
|
|
|
type: row.type,
|
|
|
|
|
priority: row.priority,
|
|
|
|
|
status: row.status,
|
|
|
|
|
is_pinned: row.is_pinned,
|
|
|
|
|
publish_time: row.publish_time,
|
|
|
|
|
expire_time: row.expire_time
|
|
|
|
|
})
|
|
|
|
|
dialogVisible.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
id: null,
|
|
|
|
|
title: '',
|
|
|
|
|
content: '',
|
|
|
|
|
type: 'system',
|
|
|
|
|
priority: 'medium',
|
|
|
|
|
status: 'draft',
|
|
|
|
|
is_pinned: false,
|
|
|
|
|
publish_time: '',
|
|
|
|
|
expire_time: ''
|
|
|
|
|
})
|
|
|
|
|
if (formRef.value) {
|
|
|
|
|
formRef.value.resetFields()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await formRef.value.validate()
|
|
|
|
|
submitting.value = true
|
|
|
|
|
|
|
|
|
|
const data = { ...form }
|
|
|
|
|
delete data.id
|
|
|
|
|
|
|
|
|
|
let response
|
|
|
|
|
if (isEdit.value) {
|
|
|
|
|
response = await api.put(`/announcements/${form.id}`, data)
|
|
|
|
|
} else {
|
|
|
|
|
response = await api.post('/announcements', data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (response.data.success) {
|
|
|
|
|
ElMessage.success(isEdit.value ? '公告更新成功' : '公告创建成功')
|
|
|
|
|
dialogVisible.value = false
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('提交失败:', error)
|
|
|
|
|
ElMessage.error(isEdit.value ? '公告更新失败' : '公告创建失败')
|
|
|
|
|
} finally {
|
|
|
|
|
submitting.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const publishAnnouncement = async (row) => {
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm('确定要发布这个公告吗?', '确认发布', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const response = await api.put(`/announcements/${row.id}`, {
|
|
|
|
|
status: 'published',
|
|
|
|
|
publish_time: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (response.data.success) {
|
|
|
|
|
ElMessage.success('公告发布成功')
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
console.error('发布公告失败:', error)
|
|
|
|
|
ElMessage.error('发布公告失败')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const deleteAnnouncement = async (row) => {
|
|
|
|
|
try {
|
|
|
|
|
await ElMessageBox.confirm('确定要删除这个公告吗?删除后无法恢复。', '确认删除', {
|
|
|
|
|
confirmButtonText: '确定',
|
|
|
|
|
cancelButtonText: '取消',
|
|
|
|
|
type: 'warning'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const response = await api.delete(`/announcements/${row.id}`)
|
|
|
|
|
if (response.data.success) {
|
|
|
|
|
ElMessage.success('公告删除成功')
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error !== 'cancel') {
|
|
|
|
|
console.error('删除公告失败:', error)
|
|
|
|
|
ElMessage.error('删除公告失败')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 辅助方法
|
|
|
|
|
const getTypeLabel = (type) => {
|
|
|
|
|
const labels = {
|
|
|
|
|
system: '系统通知',
|
|
|
|
|
maintenance: '维护公告',
|
|
|
|
|
promotion: '活动推广',
|
|
|
|
|
warning: '重要警告'
|
|
|
|
|
}
|
|
|
|
|
return labels[type] || type
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getTypeTagType = (type) => {
|
|
|
|
|
const types = {
|
|
|
|
|
system: 'info',
|
|
|
|
|
maintenance: 'warning',
|
|
|
|
|
promotion: 'success',
|
|
|
|
|
warning: 'danger'
|
|
|
|
|
}
|
|
|
|
|
return types[type] || 'info'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getPriorityLabel = (priority) => {
|
|
|
|
|
const labels = {
|
|
|
|
|
low: '低',
|
|
|
|
|
medium: '中',
|
|
|
|
|
high: '高',
|
|
|
|
|
urgent: '紧急'
|
|
|
|
|
}
|
|
|
|
|
return labels[priority] || priority
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getPriorityTagType = (priority) => {
|
|
|
|
|
const types = {
|
|
|
|
|
low: 'info',
|
|
|
|
|
medium: '',
|
|
|
|
|
high: 'warning',
|
|
|
|
|
urgent: 'danger'
|
|
|
|
|
}
|
|
|
|
|
return types[priority] || ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getStatusLabel = (status) => {
|
|
|
|
|
const labels = {
|
|
|
|
|
draft: '草稿',
|
|
|
|
|
published: '已发布',
|
|
|
|
|
archived: '已归档'
|
|
|
|
|
}
|
|
|
|
|
return labels[status] || status
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getStatusTagType = (status) => {
|
|
|
|
|
const types = {
|
|
|
|
|
draft: 'info',
|
|
|
|
|
published: 'success',
|
|
|
|
|
archived: 'warning'
|
|
|
|
|
}
|
|
|
|
|
return types[status] || 'info'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formatDateTime = (dateTime) => {
|
|
|
|
|
if (!dateTime) return '-'
|
|
|
|
|
return new Date(dateTime).toLocaleString('zh-CN', {
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
month: '2-digit',
|
|
|
|
|
day: '2-digit',
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
minute: '2-digit',
|
|
|
|
|
second: '2-digit'
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生命周期
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
fetchAnnouncements()
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.announcements-page {
|
|
|
|
|
padding: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-header {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-title {
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #303133;
|
|
|
|
|
margin: 0 0 8px 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-subtitle {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #909399;
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-card {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-row {
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-group {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-card {
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.card-title {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
color: #303133;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title-cell {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pin-tag {
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.title-text {
|
|
|
|
|
flex: 1;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pagination-wrapper {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dialog-footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 */
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
.announcements-page {
|
|
|
|
|
padding: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.search-row .el-col {
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-group {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.button-group .el-button {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|