Files
vue3-element-admin/src/views/system/notice/index.vue

556 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>