chore(types): 同步用户身份字段(删 SysUserVo.roles,identityNames 口径注释订正)

- SysUserVo.roles 已停写(后端本次一并删除该字段),前端类型同步移除
- identityNames 注明:组上下文列表只含当前组范围解析出的学校下的身份(与编辑弹窗「所在组」同源)
This commit is contained in:
chenxing
2026-09-18 11:13:27 +08:00
parent d012a99e28
commit 7784492522
3 changed files with 6 additions and 245 deletions
+4 -2
View File
@@ -311,12 +311,14 @@ export interface SysUserVo {
email?: string;
gender?: number;
profilePicture?: string;
roles?: number[];
disabled?: number;
type?: number;
sortNo?: number;
groupStatus?: number;
/** 主校身份名(如「老师/校领导」):列表「用户身份」列展示字段(roles 列已停写) */
/**
* 用户身份名(如「老师、校领导」):列表「用户身份」列展示字段。
* 组上下文列表只含「当前组范围解析出的学校」下的身份(与编辑弹窗「所在组」同源);未选组才全量。
*/
identityNames?: string;
expired?: boolean;
createTime?: string;
+2 -1
View File
@@ -157,7 +157,8 @@ const columns = [
{ title: '手机号', key: 'phone', minWidth: 140 },
{
title: '用户身份', key: 'identityNames', minWidth: 180,
// 数据源:后端按页批量带出的主校身份名(is_main 行,roles 列已停写,不再直读旧列)
// 数据源:后端按页批量带出的身份名,且只取「当前所选组范围解析出的学校」下的身份行
// (与编辑弹窗「所在组」回显同口径,roles 列已停写不再直读旧列)
render: (h: any, { row }: any) => h('span', row.identityNames || '-'),
},
{
-242
View File
@@ -1,242 +0,0 @@
<template>
<div class="main-wrapper">
<div class="main-container">
<div class="test-card">
<h3 class="card-title">测试推送</h3>
<p class="card-desc">
手动触发一次推送(投递消息队列,由订阅方异步接收)。下方将展示本次请求预览(URL / 头 / 体),
实际响应结果与耗时请在「推送日志」中查看。
</p>
<Form :label-width="100" class="test-form">
<FormItem label="订阅方应用" required>
<Select v-model="form.appId" filterable placeholder="选择要推送的订阅方应用" style="width: 360px">
<Option v-for="opt in appOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
</Select>
</FormItem>
<FormItem label="订阅接口" required>
<Select v-model="form.interfacePath" filterable placeholder="选择接口" style="width: 360px"
@on-change="onInterfaceChange">
<Option v-for="opt in interfaceOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
</Select>
</FormItem>
<FormItem label="目标ID" :required="!isSync">
<Input v-model="targetIdsText" :disabled="isSync" :placeholder="isSync ? '全量同步无需指定' : '可选,逗号分隔,如 10,11,12'"
style="width: 360px" />
</FormItem>
<FormItem>
<Button type="primary" :loading="sending" @click="handleSend">发送测试推送</Button>
</FormItem>
</Form>
</div>
<div v-if="preview" class="result-card">
<div class="result-head">
<h3 class="card-title">请求预览</h3>
<Button type="text" @click="goLog">查看推送日志</Button>
</div>
<Alert v-if="preview.batch && preview.batch > 1" type="info" show-icon>
本次将拆分为 {{ preview.batch }} 条消息依次发送
</Alert>
<Form :label-width="100" class="result-form">
<FormItem label="请求URL:">
<span class="break-all">{{ preview.url }}</span>
</FormItem>
<FormItem label="请求方法:">
<span>{{ preview.method }}</span>
</FormItem>
<FormItem label="请求头:">
<pre class="kv-pre">{{ formatHeaders(preview.headers) }}</pre>
</FormItem>
<FormItem label="请求体:">
<pre class="json-pre">{{ formatJson(preview.body) }}</pre>
</FormItem>
</Form>
<Alert type="success" show-icon>{{ sendMessage }}</Alert>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from 'view-ui-plus';
import useCurrentInstance from '@/utils/hook/useCurrentInstance';
defineOptions({ name: 'PushTest' });
const router = useRouter();
const { $utils } = useCurrentInstance();
const interfaceOptions = [
{ value: 'group/SyncGroup', label: '组全量同步', sync: true },
{ value: 'user/SyncUser', label: '用户全量同步', sync: true },
{ value: 'group/AddGroup', label: '添加组', sync: false },
{ value: 'group/UpdateGroup', label: '更新组', sync: false },
{ value: 'group/DeleteGroup', label: '删除组', sync: false },
{ value: 'user/AddUser', label: '添加用户', sync: false },
{ value: 'user/UpdateUser', label: '更新用户', sync: false },
{ value: 'user/DeleteUser', label: '删除用户', sync: false },
];
const appOptions = ref<{ value: number; label: string }[]>([]);
const form = ref({ appId: undefined as number | undefined, interfacePath: '' });
const targetIdsText = ref('');
const sending = ref(false);
const preview = ref<Record<string, any> | null>(null);
const sendMessage = ref('');
const isSync = computed(() => {
const opt = interfaceOptions.find((o) => o.value === form.value.interfacePath);
return !!opt?.sync;
});
function onInterfaceChange() {
if (isSync.value) targetIdsText.value = '';
}
function goLog() {
router.push('/push-log');
}
function formatHeaders(headers: Record<string, string> | undefined): string {
if (!headers) return '-';
return Object.entries(headers)
.map(([k, v]) => `${k}: ${v}`)
.join('\n');
}
function formatJson(val: any): string {
if (val == null) return '-';
if (typeof val === 'string') {
try {
return JSON.stringify(JSON.parse(val), null, 2);
} catch {
return val;
}
}
return JSON.stringify(val, null, 2);
}
function parseTargetIds(): number[] | null {
if (isSync.value) return null;
const txt = targetIdsText.value.trim();
if (!txt) return null;
const ids = txt
.split(/[,,\s]+/)
.map((s) => Number(s.trim()))
.filter((n) => Number.isFinite(n));
return ids.length ? ids : null;
}
function handleSend() {
if (form.value.appId == null) {
Message.error('请选择订阅方应用');
return;
}
if (!form.value.interfacePath) {
Message.error('请选择订阅接口');
return;
}
sending.value = true;
preview.value = null;
sendMessage.value = '';
(Apis as any)
.PushSubscription.test({
data: {
appId: form.value.appId,
interfacePath: form.value.interfacePath,
targetIds: parseTargetIds(),
},
cacheFor: null,
})
.then((res: any) => {
preview.value = res?.preview || null;
sendMessage.value = res?.message || '已发送';
})
.catch((e: any) => console.warn(e?.message || '发送失败'))
.finally(() => (sending.value = false));
}
async function loadApps() {
try {
const res = await (Apis as any).AppSettings.appList({
params: { pageIndex: 1, pageSize: 9999 },
cacheFor: null,
});
const list = (res && res.content) || [];
appOptions.value = list.map((a: any) => ({ value: a.id, label: `${a.name}(${a.appKey})` }));
} catch (e: any) {
appOptions.value = [];
}
}
loadApps();
</script>
<style lang="scss" scoped>
.main-wrapper {
height: 100%;
}
.main-container {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.test-card,
.result-card {
background: #fff;
border-radius: 4px;
padding: 16px 20px;
}
.card-title {
margin: 0 0 6px;
font-size: 15px;
font-weight: 600;
}
.card-desc {
margin: 0 0 12px;
color: #808695;
font-size: 13px;
line-height: 1.6;
}
.test-form {
margin-top: 4px;
}
.result-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.break-all {
word-break: break-all;
}
.kv-pre,
.json-pre {
margin: 0;
padding: 10px 12px;
background: #f7f8fa;
border: 1px solid #e8eaec;
border-radius: 4px;
max-height: 280px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
font-size: 13px;
line-height: 1.6;
}
.kv-pre {
max-height: 140px;
}
</style>