Files
userbase/src/views/app/PushSettingModal.vue
T
2026-08-17 19:49:00 +08:00

264 lines
8.9 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>
<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 :label-width="110" 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="推送地址" required>
<Input v-model="form.pushBaseUrl" placeholder="如 http://host/notice/api/base/(不含接口后缀)" />
</FormItem>
<FormItem label="订阅接口" required>
<Select v-model="form.pushEventTypes" multiple filterable 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 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 placeholder="留空=全部角色">
<Option v-for="opt in roleOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
</Select>
</FormItem>
<FormItem label="签名密钥">
<Input v-model="form.pushSecret" type="password" password placeholder="留空=复用应用密钥 appSecret;清空则改为复用" />
<p class="field-tip">不修改请保持原值;清空输入框表示改用应用密钥。</p>
</FormItem>
</Form>
<template #footer>
<div class="push-setting-footer">
<div class="footer-left">
<Button type="primary" icon="ios-people" :loading="syncingGroup" @click="handleSyncGroup">组全量同步</Button>
<Button type="primary" icon="ios-person" :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';
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 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 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[],
pushSecret: '',
});
const originalSecret = ref<string>('');
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)
: [];
form.value.pushSecret = d.pushSecret || '';
originalSecret.value = d.pushSecret || '';
} 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);
}
function handleSave() {
if (!form.value.pushBaseUrl.trim()) {
Message.error('请填写推送地址');
return;
}
if (!form.value.pushEventTypes.length) {
Message.error('请至少选择一个订阅接口');
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,
// 与原值相同=不修改(传 null 保留);清空=复用应用密钥(传 "")
pushSecret: form.value.pushSecret === originalSecret.value ? null : form.value.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() {
doSync('group/SyncGroup', syncingGroup, '组全量同步');
}
function handleSyncUser() {
doSync('user/SyncUser', syncingUser, '用户全量同步');
}
function doSync(interfacePath: string, loadingRef: Ref<boolean>, name: string) {
if (!props.appId) {
Message.error('应用不存在');
return;
}
loadingRef.value = true;
(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;
}
.field-tip {
margin: 4px 0 0;
color: #999;
font-size: 12px;
}
}
.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;
}
}
</style>