Files
schoolNews/schoolNewsWeb/src/views/admin/manage/message/components/MessageAdd.vue
2025-11-13 19:00:27 +08:00

573 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>
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
<!-- 基本信息 -->
<el-divider content-position="left">基本信息</el-divider>
<el-form-item label="消息标题" prop="title">
<el-input
v-model="formData.title"
placeholder="请输入消息标题"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="消息类型" prop="messageType">
<el-radio-group v-model="formData.messageType">
<el-radio value="notice">通知</el-radio>
<el-radio value="announcement">公告</el-radio>
<el-radio value="warning">警告</el-radio>
<el-radio value="system">系统消息</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="优先级" prop="priority">
<el-radio-group v-model="formData.priority">
<el-radio value="urgent">紧急</el-radio>
<el-radio value="important">重要</el-radio>
<el-radio value="normal">普通</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="消息内容" prop="content">
<el-input
v-model="formData.content"
type="textarea"
:rows="6"
placeholder="请输入消息内容"
maxlength="2000"
show-word-limit
/>
</el-form-item>
<!-- 发送设置 -->
<el-divider content-position="left">发送设置</el-divider>
<el-form-item label="发送模式" prop="sendMode">
<el-radio-group v-model="formData.sendMode">
<el-radio value="immediate">立即发送</el-radio>
<el-radio value="scheduled">定时发送</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="formData.sendMode === 'scheduled'"
label="发送时间"
prop="scheduledTime"
>
<el-date-picker
v-model="formData.scheduledTime"
type="datetime"
placeholder="选择发送时间"
:disabled-date="disablePastDate"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DDTHH:mm:ss"
/>
</el-form-item>
<el-form-item label="最大重试次数" prop="maxRetryCount">
<el-input-number
v-model="formData.maxRetryCount"
:min="0"
:max="5"
controls-position="right"
/>
<span class="form-tip">发送失败后重试次数0-5</span>
</el-form-item>
<!-- 发送方式 -->
<el-divider content-position="left">发送方式</el-divider>
<el-form-item label="发送渠道" prop="sendMethods">
<el-checkbox-group v-model="formData.sendMethods">
<el-checkbox value="system">系统消息</el-checkbox>
<el-checkbox value="email">邮件通知</el-checkbox>
<el-checkbox value="sms">短信通知</el-checkbox>
</el-checkbox-group>
</el-form-item>
<!-- 接收对象 -->
<el-divider content-position="left">接收对象</el-divider>
<el-form-item label="选择对象">
<el-button type="primary" size="small" @click="deptSelectorVisible = true">
<el-icon><Plus /></el-icon>
选择部门
</el-button>
<el-button type="primary" size="small" @click="deptRoleSelectorVisible = true" style="margin-left: 8px">
<el-icon><Plus /></el-icon>
选择部门角色
</el-button>
<el-button type="primary" size="small" @click="userSelectorVisible = true" style="margin-left: 8px">
<el-icon><Plus /></el-icon>
选择用户
</el-button>
<span class="form-tip">已选择 {{ getTotalTargetCount() }} 个对象</span>
</el-form-item>
<div v-if="formData.targets && formData.targets.length > 0" class="selected-targets">
<el-tag
v-for="(target, index) in formData.targets"
:key="index"
closable
@close="removeTarget(index)"
style="margin: 4px"
>
{{ getTargetDisplayName(target) }}
</el-tag>
</div>
<!-- 部门选择器 -->
<GenericSelector
v-model:visible="deptSelectorVisible"
title="选择部门"
:fetch-available-api="fetchAllDepts"
:fetch-selected-api="fetchSelectedDepts"
:filter-selected="filterDepts"
:item-config="{ id: 'id', label: 'name' }"
unit-name="个部门"
@confirm="handleDeptConfirm"
/>
<!-- 部门角色选择器 -->
<GenericSelector
v-model:visible="deptRoleSelectorVisible"
title="选择部门角色"
:fetch-available-api="fetchAllDeptRoles"
:fetch-selected-api="fetchSelectedDeptRoles"
:filter-selected="filterDeptRoles"
:item-config="{ id: 'combinedId', label: 'displayName' }"
:use-tree="true"
:tree-transform="transformDeptRolesToTree"
:tree-props="{ children: 'children', label: 'displayName', id: 'combinedId' }"
:only-leaf-selectable="true"
unit-name="个部门角色"
@confirm="handleDeptRoleConfirm"
/>
<!-- 用户选择器 -->
<GenericSelector
v-model:visible="userSelectorVisible"
title="选择用户"
:fetch-available-api="fetchAllUsers"
:fetch-selected-api="fetchSelectedUsers"
:filter-selected="filterUsers"
:item-config="{ id: 'id', label: 'displayName', sublabel: 'deptName' }"
unit-name="个用户"
@confirm="handleUserConfirm"
/>
</el-form>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { Plus } from '@element-plus/icons-vue';
import type { TbSysMessage, TbSysMessageTarget } from '@/types';
import { GenericSelector } from '@/components/base';
import { deptApi, roleApi, userApi } from '@/apis/system';
interface Props {
modelValue?: TbSysMessage;
isEdit?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
isEdit: false
});
const formRef = ref<FormInstance>();
const formData = ref<TbSysMessage>({
title: '',
content: '',
messageType: 'notice',
priority: 'normal',
sendMode: 'immediate',
scheduledTime: '',
maxRetryCount: 3,
sendMethods: ['system'],
targets: []
});
// 表单验证规则
const formRules: FormRules = {
title: [{ required: true, message: '请输入消息标题', trigger: 'blur' }],
content: [{ required: true, message: '请输入消息内容', trigger: 'blur' }],
messageType: [{ required: true, message: '请选择消息类型', trigger: 'change' }],
priority: [{ required: true, message: '请选择优先级', trigger: 'change' }],
sendMode: [{ required: true, message: '请选择发送模式', trigger: 'change' }],
scheduledTime: [{ required: true, message: '请选择发送时间', trigger: 'change' }],
sendMethods: [
{
required: true,
message: '请至少选择一种发送方式',
trigger: 'change',
validator: (rule: any, value: any) => {
return Array.isArray(value) && value.length > 0;
}
}
]
};
// 目标选择相关
interface TargetOption {
id: string;
name: string;
deptName?: string;
}
// 选择器可见性状态
const deptSelectorVisible = ref(false);
const deptRoleSelectorVisible = ref(false);
const userSelectorVisible = ref(false);
/** 禁用过去日期 */
function disablePastDate(time: Date): boolean {
return time.getTime() < Date.now();
}
/** 获取总目标数量 */
function getTotalTargetCount(): number {
return formData.value.targets?.length || 0;
}
/** 获取目标显示名称 */
function getTargetDisplayName(target: TbSysMessageTarget): string {
if (target.targetType === 'dept') {
return `部门: ${target.targetName || target.targetID}`;
} else if (target.targetType === 'role') {
return `部门角色: ${target.targetName || target.targetID}`;
} else if (target.targetType === 'user') {
return `用户: ${target.targetName || target.targetID}`;
}
return target.targetName || target.targetID || '';
}
/** 移除目标 */
function removeTarget(index: number) {
if (formData.value.targets) {
formData.value.targets.splice(index, 1);
}
}
// ==================== 部门选择相关 ====================
/** 获取所有可选部门 */
async function fetchAllDepts() {
const result = await deptApi.getAllDepts();
if (result.success && result.dataList) {
return {
...result,
dataList: result.dataList.map((dept: any) => ({
id: dept.deptID,
name: dept.name
}))
};
}
return result;
}
/** 获取已选择的部门 */
async function fetchSelectedDepts() {
if (!formData.value.targets) {
return { success: true, dataList: [], code: 200, message: '', login: true, auth: true };
}
const selectedDepts = formData.value.targets
.filter(t => t.targetType === 'dept')
.map(t => ({
id: t.targetID,
name: t.targetName || t.targetID
}));
return {
success: true,
dataList: selectedDepts,
code: 200,
message: '',
login: true,
auth: true
};
}
/** 过滤已选择的部门 */
function filterDepts(available: any[], selected: any[]) {
const selectedIds = new Set(selected.map(item => item.id));
return available.filter(item => !selectedIds.has(item.id));
}
/** 部门选择确认 */
function handleDeptConfirm(items: any[]) {
// 移除旧的部门类型目标
if (!formData.value.targets) {
formData.value.targets = [];
}
formData.value.targets = formData.value.targets.filter(t => t.targetType !== 'dept');
// 添加新选择的部门
items.forEach(dept => {
formData.value.targets!.push({
targetType: 'dept',
targetID: dept.id,
targetName: dept.name,
scopeDeptID: dept.id, // 部门的作用域就是自己
sendMethod: formData.value.sendMethods?.[0] || 'system'
});
});
}
// ==================== 部门角色选择相关 ====================
/** 获取所有可选部门角色 */
async function fetchAllDeptRoles() {
const result = await deptApi.getDeptRoleList({} as any);
if (result.success && result.dataList) {
const transformed = result.dataList
.filter((item: any) => item.deptID && item.roleID)
.map((item: any) => ({
...item,
combinedId: `${item.deptID}-${item.roleID}`,
displayName: `${item.deptName || ''} - ${item.roleName || ''}`,
deptDescription: item.deptDescription || ''
}));
return { ...result, dataList: transformed };
}
return result;
}
/** 获取已选择的部门角色 */
async function fetchSelectedDeptRoles() {
if (!formData.value.targets) {
return { success: true, dataList: [], code: 200, message: '', login: true, auth: true };
}
const selectedRoles = formData.value.targets
.filter(t => t.targetType === 'role')
.map(t => ({
deptID: t.scopeDeptID,
roleID: t.targetID,
combinedId: `${t.scopeDeptID}-${t.targetID}`,
displayName: t.targetName || `${t.scopeDeptID}-${t.targetID}`
}));
return {
success: true,
dataList: selectedRoles,
code: 200,
message: '',
login: true,
auth: true
};
}
/** 过滤已选择的部门角色 */
function filterDeptRoles(available: any[], selected: any[]) {
const selectedIds = new Set(selected.map(item => item.combinedId));
return available.filter(item => !selectedIds.has(item.combinedId));
}
/** 转换部门角色为树形结构 */
function transformDeptRolesToTree(flatData: any[]) {
const deptMap = new Map<string, any>();
const tree: any[] = [];
flatData.forEach(item => {
const deptID = item.deptID;
if (!deptMap.has(deptID)) {
deptMap.set(deptID, {
combinedId: deptID,
displayName: item.deptName || deptID,
deptDescription: item.deptDescription,
children: [],
isDept: true
});
}
const deptNode = deptMap.get(deptID);
if (deptNode) {
deptNode.children.push({
...item,
isDept: false
});
}
});
deptMap.forEach(deptNode => {
tree.push(deptNode);
});
return tree;
}
/** 部门角色选择确认 */
function handleDeptRoleConfirm(items: any[]) {
// 移除旧的角色类型目标
if (!formData.value.targets) {
formData.value.targets = [];
}
formData.value.targets = formData.value.targets.filter(t => t.targetType !== 'role');
// 添加新选择的部门角色
items.forEach(role => {
formData.value.targets!.push({
targetType: 'role',
targetID: role.roleID,
targetName: role.displayName,
scopeDeptID: role.deptID,
sendMethod: formData.value.sendMethods?.[0] || 'system'
});
});
}
// ==================== 用户选择相关 ====================
/** 获取所有可选用户 */
async function fetchAllUsers() {
const result = await userApi.getUserList({} as any);
if (result.success && result.dataList) {
return {
...result,
dataList: result.dataList.map((user: any) => ({
id: user.id || user.userID,
displayName: user.realName || user.username,
deptID: user.deptID,
deptName: user.deptName
}))
};
}
return result;
}
/** 获取已选择的用户 */
async function fetchSelectedUsers() {
if (!formData.value.targets) {
return { success: true, dataList: [], code: 200, message: '', login: true, auth: true };
}
const selectedUsers = formData.value.targets
.filter(t => t.targetType === 'user')
.map(t => ({
id: t.targetID,
displayName: t.targetName || t.targetID,
deptID: t.scopeDeptID,
deptName: ''
}));
return {
success: true,
dataList: selectedUsers,
code: 200,
message: '',
login: true,
auth: true
};
}
/** 过滤已选择的用户 */
function filterUsers(available: any[], selected: any[]) {
const selectedIds = new Set(selected.map(item => item.id));
return available.filter(item => !selectedIds.has(item.id));
}
/** 用户选择确认 */
function handleUserConfirm(items: any[]) {
// 移除旧的用户类型目标
if (!formData.value.targets) {
formData.value.targets = [];
}
formData.value.targets = formData.value.targets.filter(t => t.targetType !== 'user');
// 添加新选择的用户
items.forEach(user => {
formData.value.targets!.push({
targetType: 'user',
targetID: user.id,
targetName: user.displayName,
scopeDeptID: user.deptID,
sendMethod: formData.value.sendMethods?.[0] || 'system'
});
});
}
// ==================== 表单操作 ====================
/** 表单验证 */
async function validate(): Promise<boolean> {
if (!formRef.value) return false;
try {
await formRef.value.validate();
// 验证目标
if (!formData.value.targets || formData.value.targets.length === 0) {
ElMessage.warning('请至少选择一个接收对象');
return false;
}
return true;
} catch {
return false;
}
}
/** 获取表单数据 */
function getFormData(): TbSysMessage {
return formData.value;
}
/** 重置表单 */
function reset() {
formData.value = {
title: '',
content: '',
messageType: 'notice',
priority: 'normal',
sendMode: 'immediate',
scheduledTime: '',
maxRetryCount: 3,
sendMethods: ['system'],
targets: []
};
formRef.value?.clearValidate();
}
// 监听modelValue变化同步到formData
watch(() => props.modelValue, (val) => {
if (val) {
formData.value = { ...val };
}
});
// 暴露方法给父组件
defineExpose({
validate,
getFormData,
reset
});
</script>
<style lang="scss" scoped>
.form-tip {
margin-left: 10px;
font-size: 12px;
color: #999;
}
.selected-targets {
margin-top: 10px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
min-height: 50px;
}
.target-list {
margin-top: 10px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
min-height: 100px;
}
</style>