feat: 新增可配置通知中心,支持扫描式/埋点式通知规则与手动群发
新增通知规则实体、扫描任务、API与管理界面,重构站内通知支持用户/电工双类型,移除硬编码的证照到期提醒,新增工单派单、电工审核结果等通知触发场景,调整菜单与数据库结构适配新通知体系。
This commit is contained in:
1 parent
91aa3c06f0
commit
6cf6a3d5b9
36 files changed
+3309
-246
No files matched your search
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="receiver-editor">
|
||||
<div class="re-section">
|
||||
<div class="re-section-title">管理端用户</div>
|
||||
<div class="re-field">
|
||||
<label class="re-label">按角色</label>
|
||||
<a-select
|
||||
v-model:value="config.users.roles"
|
||||
mode="multiple"
|
||||
placeholder="选择角色编码(可多选)"
|
||||
allow-clear
|
||||
:options="roleOptions"
|
||||
:loading="roleLoading"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="re-field">
|
||||
<label class="re-label">按用户</label>
|
||||
<a-select
|
||||
v-model:value="config.users.ids"
|
||||
mode="multiple"
|
||||
placeholder="选择具体用户(可多选)"
|
||||
allow-clear
|
||||
show-search
|
||||
option-filter-prop="label"
|
||||
:options="userOptions"
|
||||
:loading="userLoading"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="re-section">
|
||||
<div class="re-section-title">电工</div>
|
||||
<div class="re-field re-field-block">
|
||||
<label class="re-label">接收方式</label>
|
||||
<a-radio-group v-model:value="config.workers.mode" class="re-radio">
|
||||
<a-radio value="none">不发送</a-radio>
|
||||
<a-radio v-if="allowEventMode" value="event">事件关联电工</a-radio>
|
||||
<a-radio value="all">全部在岗电工</a-radio>
|
||||
<a-radio value="ids">指定电工</a-radio>
|
||||
<a-radio value="status">按状态筛选</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<div v-if="config.workers.mode === 'ids'" class="re-field">
|
||||
<label class="re-label">指定电工</label>
|
||||
<a-select
|
||||
v-model:value="config.workers.ids"
|
||||
mode="multiple"
|
||||
placeholder="选择电工(可多选)"
|
||||
allow-clear
|
||||
show-search
|
||||
option-filter-prop="label"
|
||||
:options="workerOptions"
|
||||
:loading="workerLoading"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="config.workers.mode === 'status'" class="re-field">
|
||||
<label class="re-label">审核状态</label>
|
||||
<a-select
|
||||
v-model:value="config.workers.auditStatus"
|
||||
placeholder="全部审核状态"
|
||||
allow-clear
|
||||
:options="auditOptions"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="config.workers.mode === 'status'" class="re-field">
|
||||
<label class="re-label">在岗状态</label>
|
||||
<a-select
|
||||
v-model:value="config.workers.status"
|
||||
placeholder="全部在岗状态"
|
||||
allow-clear
|
||||
:options="statusOptions"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { roleApi } from '@/api/role';
|
||||
import { userApi } from '@/api/user';
|
||||
import { workerApi } from '@/api/worker';
|
||||
import type { ReceiverConfig } from '@/api/noticeRule';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 直接持有的响应式接收对象配置(嵌套字段就地修改,父级实时感知)
|
||||
config: ReceiverConfig;
|
||||
// 手动群发无「事件关联」上下文,可关闭该选项
|
||||
allowEventMode?: boolean;
|
||||
}>(),
|
||||
{ allowEventMode: true }
|
||||
);
|
||||
|
||||
const roleOptions = ref<{ value: string; label: string }[]>([]);
|
||||
const userOptions = ref<{ value: number; label: string }[]>([]);
|
||||
const workerOptions = ref<{ value: number; label: string }[]>([]);
|
||||
|
||||
const roleLoading = ref(false);
|
||||
const userLoading = ref(false);
|
||||
const workerLoading = ref(false);
|
||||
|
||||
const auditOptions = [
|
||||
{ value: 0, label: '待审核' },
|
||||
{ value: 1, label: '已通过' },
|
||||
{ value: 2, label: '已驳回' },
|
||||
];
|
||||
|
||||
const statusOptions = [
|
||||
{ value: 1, label: '在岗' },
|
||||
{ value: 0, label: '离职' },
|
||||
];
|
||||
|
||||
// 角色/用户/电工下拉为可选增强项,拉取失败(如无对应菜单权限)不阻断配置
|
||||
async function loadRoles() {
|
||||
roleLoading.value = true;
|
||||
try {
|
||||
const res = await roleApi.getAllRoles();
|
||||
if (res.code === 200) {
|
||||
roleOptions.value = (res.data || []).map((r) => ({
|
||||
value: r.roleCode,
|
||||
label: `${r.roleName}(${r.roleCode})`,
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// 忽略:无权限时由用户手动维护角色编码
|
||||
} finally {
|
||||
roleLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
userLoading.value = true;
|
||||
try {
|
||||
const res = await userApi.getUserList({ page: 1, pageSize: 200 });
|
||||
if (res.code === 200) {
|
||||
userOptions.value = (res.data?.list || []).map((u) => ({
|
||||
value: u.id,
|
||||
label: `${u.realName || u.username}(${u.username})`,
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// 忽略
|
||||
} finally {
|
||||
userLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWorkers() {
|
||||
workerLoading.value = true;
|
||||
try {
|
||||
const res = await workerApi.getOptions();
|
||||
if (res.code === 200) {
|
||||
workerOptions.value = (res.data || []).map((w) => ({
|
||||
value: w.id,
|
||||
label: w.workerTitle ? `${w.workerName}(${w.workerTitle})` : w.workerName,
|
||||
}));
|
||||
}
|
||||
} catch {
|
||||
// 忽略
|
||||
} finally {
|
||||
workerLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadRoles();
|
||||
loadUsers();
|
||||
loadWorkers();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.receiver-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.re-section {
|
||||
border: 1px solid #eef1f6;
|
||||
border-radius: 8px;
|
||||
padding: 12px 14px;
|
||||
background: #f8faff;
|
||||
}
|
||||
|
||||
.re-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #0d1c2c;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.re-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.re-field:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.re-field-block {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.re-label {
|
||||
flex-shrink: 0;
|
||||
width: 72px;
|
||||
font-size: 12px;
|
||||
color: #5b6b7e;
|
||||
}
|
||||
|
||||
.re-radio {
|
||||
line-height: 2;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user