265 lines
8.9 KiB
Vue
265 lines
8.9 KiB
Vue
<template>
|
|
<Modal v-model="show" :mask-closable="false" width="680" title="推送设置" @on-visible-change="onVisibleChange">
|
|
<div v-if="loading" class="loading-tip">加载中...</div>
|
|
<Form v-else ref="formRef" :model="form" :rules="rules" :label-width="110" autocomplete="off" class="push-setting-form">
|
|
<FormItem label="应用">
|
|
<span>{{ appName || '-' }}</span>
|
|
</FormItem>
|
|
<FormItem label="推送总开关">
|
|
<i-switch v-model="form.pushEnabled" />
|
|
<span class="switch-tip">{{ form.pushEnabled ? '开启' : '关闭' }}</span>
|
|
</FormItem>
|
|
<FormItem label="推送地址" prop="pushBaseUrl">
|
|
<Input v-model="form.pushBaseUrl" placeholder="如 http://host/notice/api/base/(不含接口后缀)" />
|
|
</FormItem>
|
|
<FormItem label="订阅接口" prop="pushEventTypes">
|
|
<Select v-model="form.pushEventTypes" multiple filterable transfer placeholder="请选择要订阅的接口(必选)">
|
|
<Option v-for="opt in interfaceOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
|
|
</Select>
|
|
</FormItem>
|
|
<FormItem label="关联学校">
|
|
<Select v-model="form.pushSchoolIds" multiple filterable transfer placeholder="留空=全局(监听所有学校变化)">
|
|
<Option v-for="opt in schoolOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
|
|
</Select>
|
|
</FormItem>
|
|
<FormItem label="推送角色">
|
|
<Select v-model="form.pushRoleIds" multiple filterable transfer placeholder="留空=全部角色">
|
|
<Option v-for="opt in roleOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
|
|
</Select>
|
|
</FormItem>
|
|
</Form>
|
|
<template #footer>
|
|
<div class="push-setting-footer">
|
|
<div v-if="form.pushEnabled" class="footer-left">
|
|
<Button type="primary" :loading="syncingGroup" @click="handleSyncGroup">组全量同步</Button>
|
|
<Button type="primary" :loading="syncingUser" @click="handleSyncUser">用户全量同步</Button>
|
|
</div>
|
|
<div class="footer-right">
|
|
<Button type="primary" :loading="saving" @click="handleSave">保存</Button>
|
|
<Button @click="show = false">取消</Button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Modal>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, ref, watch, type Ref } from 'vue';
|
|
import { Message } from 'view-ui-plus';
|
|
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
|
|
import MyConfirm from '@/components/MyConfirm';
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean;
|
|
appId: number;
|
|
appName?: string;
|
|
}>();
|
|
const emit = defineEmits(['update:modelValue', 'saved']);
|
|
|
|
const { $utils } = useCurrentInstance();
|
|
|
|
const show = computed({
|
|
get: () => props.modelValue,
|
|
set: val => emit('update:modelValue', val),
|
|
});
|
|
|
|
const formRef = ref<any>(null);
|
|
|
|
const interfaceOptions = [
|
|
{ value: 'group/AddGroup', label: '添加组' },
|
|
{ value: 'group/UpdateGroup', label: '更新组' },
|
|
{ value: 'group/DeleteGroup', label: '删除组' },
|
|
{ value: 'user/AddUser', label: '添加用户' },
|
|
{ value: 'user/UpdateUser', label: '更新用户' },
|
|
{ value: 'user/DeleteUser', label: '删除用户' },
|
|
];
|
|
|
|
const roleOptions = [
|
|
{ value: 1, label: '老师' },
|
|
{ value: 2, label: '校领导' },
|
|
{ value: 3, label: '学生' },
|
|
{ value: 4, label: '家长' },
|
|
{ value: 5, label: '局领导' },
|
|
{ value: 6, label: '工作号' },
|
|
{ value: 7, label: '管理员' },
|
|
];
|
|
|
|
const rules = {
|
|
pushBaseUrl: [{ required: true, message: '请填写推送地址', trigger: 'blur' }],
|
|
pushEventTypes: [{ required: true, type: 'array', message: '请至少选择一个订阅接口', trigger: 'change' }],
|
|
};
|
|
|
|
const schoolOptions = ref<{ value: number; label: string }[]>([]);
|
|
const loading = ref(false);
|
|
const saving = ref(false);
|
|
const syncingGroup = ref(false);
|
|
const syncingUser = ref(false);
|
|
|
|
const form = ref({
|
|
pushEnabled: false,
|
|
pushBaseUrl: '',
|
|
pushEventTypes: [] as string[],
|
|
pushSchoolIds: [] as number[],
|
|
pushRoleIds: [] as number[],
|
|
});
|
|
|
|
function flattenSchools(nodes: any[], prefix = ''): { value: number; label: string }[] {
|
|
const out: { value: number; label: string }[] = [];
|
|
(nodes || []).forEach((n) => {
|
|
const label = prefix ? `${prefix} / ${n.name}` : n.name;
|
|
out.push({ value: Number(n.id), label });
|
|
if (Array.isArray(n.children) && n.children.length) {
|
|
out.push(...flattenSchools(n.children, label));
|
|
}
|
|
});
|
|
return out;
|
|
}
|
|
|
|
async function loadSchools() {
|
|
try {
|
|
const data = await (Apis as any).SysGroup.getSchoolTree({ cacheFor: null });
|
|
schoolOptions.value = flattenSchools(Array.isArray(data) ? data : []);
|
|
} catch (e: any) {
|
|
schoolOptions.value = [];
|
|
}
|
|
}
|
|
|
|
async function loadSettings() {
|
|
loading.value = true;
|
|
try {
|
|
const data = await (Apis as any).AppSettings.getPushSetting({
|
|
pathParams: { id: props.appId },
|
|
cacheFor: null,
|
|
});
|
|
const d = data || {};
|
|
form.value.pushEnabled = d.pushEnabled === 1;
|
|
form.value.pushBaseUrl = d.pushBaseUrl || '';
|
|
form.value.pushEventTypes = d.pushEventTypes ? String(d.pushEventTypes).split(',').filter(Boolean) : [];
|
|
form.value.pushSchoolIds = d.pushSchoolIds
|
|
? String(d.pushSchoolIds).split(',').filter(Boolean).map(Number)
|
|
: [];
|
|
form.value.pushRoleIds = d.pushRoleIds
|
|
? String(d.pushRoleIds).split(',').filter(Boolean).map(Number)
|
|
: [];
|
|
} catch (e: any) {
|
|
Message.error(e?.message || '加载推送设置失败');
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(val) => {
|
|
if (val && props.appId) {
|
|
loadSchools();
|
|
loadSettings();
|
|
}
|
|
}
|
|
);
|
|
|
|
function onVisibleChange(val: boolean) {
|
|
if (!val) emit('update:modelValue', false);
|
|
}
|
|
|
|
async function handleSave() {
|
|
const valid = await formRef.value?.validate();
|
|
if (!valid) return;
|
|
|
|
saving.value = true;
|
|
const data: any = {
|
|
pushBaseUrl: form.value.pushBaseUrl.trim(),
|
|
pushEnabled: form.value.pushEnabled ? 1 : 0,
|
|
pushEventTypes: form.value.pushEventTypes.join(','),
|
|
pushSchoolIds: form.value.pushSchoolIds.length ? form.value.pushSchoolIds.join(',') : null,
|
|
pushRoleIds: form.value.pushRoleIds.length ? form.value.pushRoleIds.join(',') : null,
|
|
// 清空单独签名密钥,统一复用应用 appSecret
|
|
pushSecret: '',
|
|
};
|
|
(Apis as any)
|
|
.AppSettings.savePushSetting({
|
|
pathParams: { id: props.appId },
|
|
data,
|
|
cacheFor: null,
|
|
})
|
|
.then(() => {
|
|
Message.success('保存成功');
|
|
show.value = false;
|
|
emit('saved');
|
|
})
|
|
.catch((e: any) => Message.error(e?.message || '保存失败'))
|
|
.finally(() => (saving.value = false));
|
|
}
|
|
|
|
function handleSyncGroup() {
|
|
triggerSync('group/SyncGroup', syncingGroup, '组全量同步');
|
|
}
|
|
|
|
function handleSyncUser() {
|
|
triggerSync('user/SyncUser', syncingUser, '用户全量同步');
|
|
}
|
|
|
|
async function triggerSync(interfacePath: string, loadingRef: Ref<boolean>, name: string) {
|
|
await MyConfirm({
|
|
msg: `确定对本应用执行「${name}」吗?`,
|
|
info: '将向该订阅方投递一次全量数据,可在「推送日志」查看实际结果。',
|
|
confirmText: '确定推送',
|
|
async onOk() {
|
|
await doSync(interfacePath, loadingRef, name);
|
|
},
|
|
});
|
|
}
|
|
|
|
function doSync(interfacePath: string, loadingRef: Ref<boolean>, name: string): Promise<void> {
|
|
if (!props.appId) {
|
|
Message.error('应用不存在');
|
|
return Promise.resolve();
|
|
}
|
|
loadingRef.value = true;
|
|
return (Apis as any)
|
|
.Push.test({
|
|
data: { appId: props.appId, interfacePath, targetIds: null },
|
|
cacheFor: null,
|
|
})
|
|
.then((res: any) => Message.success(res?.message || `${name}已触发`))
|
|
.catch((e: any) => Message.error(e?.message || `${name}失败`))
|
|
.finally(() => (loadingRef.value = false));
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.push-setting-form {
|
|
:deep(.ivu-form-item) {
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.switch-tip {
|
|
margin-left: 8px;
|
|
color: #808695;
|
|
}
|
|
}
|
|
|
|
.loading-tip {
|
|
padding: 24px 0;
|
|
text-align: center;
|
|
color: #999;
|
|
}
|
|
|
|
.push-setting-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
.footer-left {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.footer-right {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-left: auto;
|
|
}
|
|
}
|
|
</style>
|