556 lines
16 KiB
Vue
556 lines
16 KiB
Vue
<template>
|
||
<div class="app-container">
|
||
<div class="filter-section">
|
||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-suffix=":">
|
||
<el-form-item label="标题" prop="title">
|
||
<el-input
|
||
v-model="queryParams.title"
|
||
placeholder="标题"
|
||
clearable
|
||
@keyup.enter="handleQuery()"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="发布状态" prop="publishStatus">
|
||
<el-select
|
||
v-model="queryParams.publishStatus"
|
||
clearable
|
||
placeholder="全部"
|
||
style="width: 100px"
|
||
>
|
||
<el-option :value="0" label="未发布" />
|
||
<el-option :value="1" label="已发布" />
|
||
<el-option :value="-1" label="已撤回" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item class="search-buttons">
|
||
<el-button type="primary" icon="search" @click="handleQuery()">搜索</el-button>
|
||
<el-button icon="refresh" @click="handleResetQuery()">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<el-card shadow="hover" class="table-section">
|
||
<div class="table-section__toolbar">
|
||
<div class="table-section__toolbar--actions">
|
||
<el-button
|
||
v-hasPerm="['sys:notice:create']"
|
||
type="success"
|
||
icon="plus"
|
||
@click="openDialog()"
|
||
>
|
||
新增通知
|
||
</el-button>
|
||
<el-button
|
||
v-hasPerm="['sys:notice:delete']"
|
||
type="danger"
|
||
:disabled="selectIds.length === 0"
|
||
icon="delete"
|
||
@click="handleDelete()"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-table
|
||
ref="dataTableRef"
|
||
v-loading="loading"
|
||
:data="pageData"
|
||
highlight-current-row
|
||
class="table-section__content"
|
||
@selection-change="handleSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="55" align="center" />
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column label="通知标题" prop="title" min-width="200" />
|
||
<el-table-column align="center" label="通知类型" width="150">
|
||
<template #default="scope">
|
||
<DictTag v-model="scope.row.type" :code="'notice_type'" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column align="center" label="发布人" prop="publisherName" width="150" />
|
||
<el-table-column align="center" label="通知等级" width="100">
|
||
<template #default="scope">
|
||
<DictTag v-model="scope.row.level" code="notice_level" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column align="center" label="通告目标类型" prop="targetType" min-width="100">
|
||
<template #default="scope">
|
||
<el-tag v-if="scope.row.targetType == 1" type="warning">全体</el-tag>
|
||
<el-tag v-if="scope.row.targetType == 2" type="success">指定</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column align="center" label="发布状态" min-width="100">
|
||
<template #default="scope">
|
||
<el-tag v-if="scope.row.publishStatus == 0" type="info">未发布</el-tag>
|
||
<el-tag v-if="scope.row.publishStatus == 1" type="success">已发布</el-tag>
|
||
<el-tag v-if="scope.row.publishStatus == -1" type="warning">已撤回</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作时间" width="250">
|
||
<template #default="scope">
|
||
<div class="flex-x-start">
|
||
<span>创建时间:</span>
|
||
<span>{{ scope.row.createTime || "-" }}</span>
|
||
</div>
|
||
|
||
<div v-if="scope.row.publishStatus === 1" class="flex-x-start">
|
||
<span>发布时间:</span>
|
||
<span>{{ scope.row.publishTime || "-" }}</span>
|
||
</div>
|
||
<div v-else-if="scope.row.publishStatus === -1" class="flex-x-start">
|
||
<span>撤回时间:</span>
|
||
<span>{{ scope.row.revokeTime || "-" }}</span>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column align="center" fixed="right" label="操作" width="150">
|
||
<template #default="scope">
|
||
<el-button type="primary" size="small" link @click="openDetailDialog(scope.row.id)">
|
||
查看
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.publishStatus != 1"
|
||
v-hasPerm="['sys:notice:publish']"
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="handlePublish(scope.row.id)"
|
||
>
|
||
发布
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.publishStatus == 1"
|
||
v-hasPerm="['sys:notice:revoke']"
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="handleRevoke(scope.row.id)"
|
||
>
|
||
撤回
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.publishStatus != 1"
|
||
v-hasPerm="['sys:notice:update']"
|
||
type="primary"
|
||
size="small"
|
||
link
|
||
@click="openDialog(scope.row.id)"
|
||
>
|
||
编辑
|
||
</el-button>
|
||
<el-button
|
||
v-if="scope.row.publishStatus != 1"
|
||
v-hasPerm="['sys:notice:delete']"
|
||
type="danger"
|
||
size="small"
|
||
link
|
||
@click="handleDelete(scope.row.id)"
|
||
>
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<pagination
|
||
v-if="total > 0"
|
||
v-model:total="total"
|
||
v-model:page="queryParams.pageNum"
|
||
v-model:limit="queryParams.pageSize"
|
||
@pagination="fetchData()"
|
||
/>
|
||
</el-card>
|
||
|
||
<el-dialog
|
||
v-model="dialogState.visible"
|
||
:show-close="false"
|
||
:fullscreen="dialogState.fullscreen"
|
||
top="6vh"
|
||
width="70%"
|
||
custom-class="notice-dialog"
|
||
@close="closeDialog"
|
||
>
|
||
<template #header>
|
||
<div class="flex-x-between">
|
||
<span>{{ dialogState.title }}</span>
|
||
<div class="dialog-toolbar">
|
||
<el-button circle @click="toggleDialogFullscreen">
|
||
<template #icon>
|
||
<FullScreen v-if="!dialogState.fullscreen" />
|
||
<CopyDocument v-else />
|
||
</template>
|
||
</el-button>
|
||
<el-button circle @click="closeDialog">
|
||
<template #icon>
|
||
<Close />
|
||
</template>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-width="100px">
|
||
<el-form-item label="通知标题" prop="title">
|
||
<el-input v-model="formData.title" placeholder="通知标题" clearable />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="通知类型" prop="type">
|
||
<DictSelect v-model="formData.type" code="notice_type" />
|
||
</el-form-item>
|
||
<el-form-item label="通知等级" prop="level">
|
||
<DictSelect v-model="formData.level" code="notice_level" />
|
||
</el-form-item>
|
||
<el-form-item label="目标类型" prop="targetType">
|
||
<el-radio-group v-model="formData.targetType">
|
||
<el-radio :value="1">全体</el-radio>
|
||
<el-radio :value="2">指定</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item v-if="formData.targetType == 2" label="指定用户" prop="targetUsers">
|
||
<el-select v-model="formData.targetUsers" multiple search placeholder="请选择指定用户">
|
||
<el-option
|
||
v-for="item in userOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="通知内容" prop="content">
|
||
<WangEditor v-model="formData.content" height="350px" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button type="primary" @click="handleSubmit()">确定</el-button>
|
||
<el-button @click="closeDialog()">取消</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
<el-dialog
|
||
v-model="detailDialog.visible"
|
||
:show-close="false"
|
||
width="50%"
|
||
append-to-body
|
||
@close="closeDetailDialog"
|
||
>
|
||
<template #header>
|
||
<div class="flex-x-between">
|
||
<span>通知公告详情</span>
|
||
<div class="dialog-toolbar">
|
||
<el-button circle @click="closeDetailDialog">
|
||
<template #icon>
|
||
<Close />
|
||
</template>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<el-descriptions :column="1">
|
||
<el-descriptions-item label="标题:">
|
||
{{ currentNotice.title }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="发布状态:">
|
||
<el-tag v-if="currentNotice.publishStatus == 0" type="info">未发布</el-tag>
|
||
<el-tag v-else-if="currentNotice.publishStatus == 1" type="success">已发布</el-tag>
|
||
<el-tag v-else-if="currentNotice.publishStatus == -1" type="warning">已撤回</el-tag>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="发布人:">
|
||
{{ currentNotice.publisherName }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="发布时间:">
|
||
{{ currentNotice.publishTime }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="公告内容:">
|
||
<div class="notice-content" v-html="currentNotice.content" />
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
defineOptions({
|
||
name: "Notice",
|
||
inheritAttrs: false,
|
||
});
|
||
|
||
import NoticeAPI from "@/api/system/notice";
|
||
import type { NoticeItem, NoticeForm, NoticeQueryParams, NoticeDetail } from "@/types/api";
|
||
import UserAPI from "@/api/system/user";
|
||
import type { FormInstance, FormRules } from "element-plus";
|
||
|
||
// 表单引用
|
||
const queryFormRef = ref<FormInstance>();
|
||
const dataFormRef = ref<FormInstance>();
|
||
|
||
// 查询参数
|
||
const queryParams = reactive<NoticeQueryParams>({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
});
|
||
|
||
// 列表数据
|
||
const pageData = ref<NoticeItem[]>([]);
|
||
const userOptions = ref<OptionItem[]>([]);
|
||
const total = ref(0);
|
||
const loading = ref(false);
|
||
const selectIds = ref<number[]>([]);
|
||
|
||
// 弹窗状态
|
||
const dialogState = reactive({
|
||
title: "",
|
||
visible: false,
|
||
fullscreen: false,
|
||
});
|
||
|
||
// 表单数据
|
||
const formData = reactive<NoticeForm>({
|
||
level: "L",
|
||
targetType: 1,
|
||
});
|
||
|
||
// 验证规则
|
||
const rules: FormRules = {
|
||
title: [{ required: true, message: "请输入通知标题", trigger: "blur" }],
|
||
content: [
|
||
{
|
||
required: true,
|
||
message: "请输入通知内容",
|
||
trigger: "blur",
|
||
validator: (rule, value: string, callback) => {
|
||
if (!value.replace(/<[^>]+>/g, "").trim()) {
|
||
callback(new Error("请输入通知内容"));
|
||
} else {
|
||
callback();
|
||
}
|
||
},
|
||
},
|
||
],
|
||
type: [{ required: true, message: "请选择通知类型", trigger: "change" }],
|
||
};
|
||
|
||
// 详情弹窗状态
|
||
const detailDialog = reactive({
|
||
visible: false,
|
||
});
|
||
const currentNotice = ref<NoticeDetail>({});
|
||
|
||
/**
|
||
* 查询按钮点击事件
|
||
*/
|
||
function handleQuery(): void {
|
||
queryParams.pageNum = 1;
|
||
fetchData();
|
||
}
|
||
|
||
/**
|
||
* 加载通知公告列表数据
|
||
*/
|
||
function fetchData(): void {
|
||
loading.value = true;
|
||
NoticeAPI.getPage(queryParams)
|
||
.then((data) => {
|
||
pageData.value = data.list;
|
||
total.value = data.total ?? 0;
|
||
})
|
||
.finally(() => {
|
||
loading.value = false;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 重置查询
|
||
*/
|
||
function handleResetQuery(): void {
|
||
queryFormRef.value?.resetFields();
|
||
queryParams.pageNum = 1;
|
||
fetchData();
|
||
}
|
||
|
||
/**
|
||
* 表格选择变化事件
|
||
*/
|
||
function handleSelectionChange(selection: NoticeItem[]): void {
|
||
selectIds.value = selection.map((item) => item.id);
|
||
}
|
||
|
||
/**
|
||
* 打开弹窗
|
||
* @param id 通知ID(编辑时传入)
|
||
*/
|
||
function openDialog(id?: string): void {
|
||
dialogState.fullscreen = false;
|
||
UserAPI.getOptions().then((data) => {
|
||
userOptions.value = data;
|
||
});
|
||
|
||
dialogState.visible = true;
|
||
if (id) {
|
||
dialogState.title = "修改公告";
|
||
NoticeAPI.getFormData(id).then((data) => {
|
||
Object.assign(formData, {
|
||
...data,
|
||
targetUsers: normalizeTargetUsers(
|
||
(data as NoticeForm & { targetUserIds?: unknown }).targetUserIds
|
||
),
|
||
});
|
||
});
|
||
} else {
|
||
Object.assign(formData, { level: "L", targetType: 1, targetUsers: [] });
|
||
dialogState.title = "新增公告";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发布通知公告
|
||
* @param id 通知ID
|
||
*/
|
||
function handlePublish(id: string): void {
|
||
NoticeAPI.publish(id).then(() => {
|
||
ElMessage.success("发布成功");
|
||
fetchData();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 撤回通知公告
|
||
* @param id 通知ID
|
||
*/
|
||
function handleRevoke(id: string): void {
|
||
NoticeAPI.revoke(id).then(() => {
|
||
ElMessage.success("撤回成功");
|
||
fetchData();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 提交表单
|
||
*/
|
||
function handleSubmit(): void {
|
||
dataFormRef.value?.validate((valid) => {
|
||
if (valid) {
|
||
loading.value = true;
|
||
const payload = {
|
||
...formData,
|
||
targetUserIds: formData.targetType === 2 ? (formData.targetUsers ?? []) : [],
|
||
} as NoticeForm & { targetUserIds: number[] };
|
||
delete (payload as NoticeForm).targetUsers;
|
||
const id = formData.id;
|
||
if (id) {
|
||
NoticeAPI.update(id, payload)
|
||
.then(() => {
|
||
ElMessage.success("修改成功");
|
||
closeDialog();
|
||
handleResetQuery();
|
||
})
|
||
.finally(() => (loading.value = false));
|
||
} else {
|
||
NoticeAPI.create(payload)
|
||
.then(() => {
|
||
ElMessage.success("新增成功");
|
||
closeDialog();
|
||
handleResetQuery();
|
||
})
|
||
.finally(() => (loading.value = false));
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 关闭弹窗
|
||
*/
|
||
function closeDialog(): void {
|
||
dialogState.visible = false;
|
||
dialogState.fullscreen = false;
|
||
dataFormRef.value?.resetFields();
|
||
dataFormRef.value?.clearValidate();
|
||
formData.id = undefined;
|
||
formData.targetType = 1;
|
||
formData.targetUsers = [];
|
||
formData.content = "";
|
||
}
|
||
|
||
/**
|
||
* 标准化目标用户数据
|
||
*/
|
||
function normalizeTargetUsers(value?: unknown): number[] {
|
||
if (!value) {
|
||
return [];
|
||
}
|
||
if (Array.isArray(value)) {
|
||
return value;
|
||
}
|
||
if (typeof value === "string") {
|
||
try {
|
||
const parsed = JSON.parse(value);
|
||
return Array.isArray(parsed) ? parsed : value.split(",").filter(Boolean);
|
||
} catch {
|
||
return value.split(",").filter(Boolean);
|
||
}
|
||
}
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* 弹窗全屏切换
|
||
*/
|
||
function toggleDialogFullscreen(): void {
|
||
dialogState.fullscreen = !dialogState.fullscreen;
|
||
}
|
||
|
||
/**
|
||
* 删除通知公告
|
||
* @param id 通知ID
|
||
*/
|
||
function handleDelete(id?: number): void {
|
||
const deleteIds = [id || selectIds.value].join(",");
|
||
if (!deleteIds) {
|
||
ElMessage.warning("请勾选删除项");
|
||
return;
|
||
}
|
||
|
||
ElMessageBox.confirm("确认删除已选中的数据项吗?", "警告", {
|
||
confirmButtonText: "确定",
|
||
cancelButtonText: "取消",
|
||
type: "warning",
|
||
}).then(
|
||
() => {
|
||
loading.value = true;
|
||
NoticeAPI.deleteByIds(deleteIds)
|
||
.then(() => {
|
||
ElMessage.success("删除成功");
|
||
handleResetQuery();
|
||
})
|
||
.finally(() => (loading.value = false));
|
||
},
|
||
() => {
|
||
ElMessage.info("已取消删除");
|
||
}
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 打开详情弹窗
|
||
*/
|
||
async function openDetailDialog(id: string): Promise<void> {
|
||
const noticeDetail = await NoticeAPI.getDetail(id);
|
||
currentNotice.value = noticeDetail;
|
||
detailDialog.visible = true;
|
||
}
|
||
|
||
/**
|
||
* 关闭详情弹窗
|
||
*/
|
||
function closeDetailDialog(): void {
|
||
detailDialog.visible = false;
|
||
}
|
||
|
||
onMounted(() => {
|
||
handleQuery();
|
||
});
|
||
</script>
|