0bbac7d228
客户在验收里点出来的问题,一次改完。两段改动:
一、平台运营与租户范围(后端为主)
- 新增请求头 X-Tenant-Id:平台运营是唯一跨租户身份,租户拦截器对它整体关掉了过滤,
于是「组织架构 / 账号管理 / 工作台」拿到的是所有租户混在一起的数据 —— 而只有一所
学校有数据时,它和学校管理员看到的完全一样,看不出错(这是最危险的一点)。
现在这些页面必须先选定学校,服务端再把本次请求的租户上下文换成目标租户:
只认平台身份(其它身份带头一律忽略)、目标租户必须存在且启用。
- OrgService.tree / UserService.list 补显式 tenant_id 条件(原来只靠拦截器,而拦截器对
平台不生效);TaskService.seesWholeTenant 改用新的 LoginUser.isPlatformStaff() ——
isPlatformAdmin 回答"当前租户是不是平台租户",代管时它为 false,会让平台退化成普通
用户(任务列表掉进"班主任只看自己带的班"分支,得到空列表);PLATFORM_ADMIN 补
TASK_VIEW(PRD §3.2「查看填报进度看板:平台运营 ✅」)。
- 操作日志对平台永远是空的:SQL 写死 tenant_id = 当前租户,而平台的 tenantId 是 0。
按契约 §13.4 补 tenantId 参数与 log:viewAll:平台默认看全量、可指定某所学校;
学校管理员恒为本租户,传别家返回 40301(不静默忽略,否则会让人以为"筛过了")。
- 开放接口"推送重试"从未生效(F-112):定时扫描缺 @InterceptorIgnore,异常被 log.warn
吞掉;同一方法里"接入方已停用"那条 update 也没有租户上下文,会每 10 秒重试到永远。
E2E 补了"真的重试了"的断言(原来只断言"安排了下次重试")。
二、PC 端走查体验
- 去掉 3 条无用菜单(名单管理 / 表单设计器 / 材料规则):功能都在「收集任务」行内入口,
菜单里那一条点进去只有指路;后端菜单键同步删除,entryAt 那套机制一并删掉。
- 班主任点工作台必弹 403:工作台无条件调了要 org:view / progress:viewAll 的两个接口。
403 走 HTTP 状态码,try/catch 拦不住它的弹窗 —— 改成按权限决定发不发请求。
- 操作日志:分页器被裁在可视区外(新增 .g-list-card 让表体自己滚、表头钉住、分页器留在
卡片底部)+ 去掉页面上那段免责声明;收集任务的状态切换改成单独一行 tab;去掉顶栏假数据
铃铛;配色按客户要求还原。
顺带修两处"检查器自己不严":check-wiring 新增第 ⑦ 项(PC 端引用的权限码必须存在于
PermissionDef —— 本轮我自己把 progress:viewAll 写成 progress:view:all,判断恒为 false,
而编译 / typecheck / lint / E2E 全都不红);verify-docs 的表格列数检查不再把 GFM 转义
竖线当成列分隔符。两个检查器都拿"故意写错的样例"试过,确认真的会红。
验证:typecheck / mvn package / check-wiring(未接线声明 0 条)/ verify-docs(22 项)全过;
e2e-m0 36、e2e-m1-orgs 48、e2e-m0-user 55、e2e-m5-logs 44、e2e-m5-notify-wiring 43、
e2e-m1-tenant-domain 6、e2e-m8-open 61 全绿;浏览器按角色逐个真点复验。
详见 docs/交接摘要.md §一·三十三 与 §一·三十四。
75 lines
2.3 KiB
Vue
75 lines
2.3 KiB
Vue
<template>
|
||
<a-select
|
||
v-model="model"
|
||
class="gi-tenant-picker"
|
||
:placeholder="props.placeholder"
|
||
:loading="loading"
|
||
:disabled="props.disabled"
|
||
:style="{ width: props.width }"
|
||
allow-search
|
||
allow-clear
|
||
>
|
||
<a-option v-for="t in options" :key="t.id" :value="t.id" :disabled="t.status === 0">
|
||
{{ t.tenantName }}({{ t.tenantCode }}){{ t.status === 0 ? ' · 已停用' : '' }}
|
||
</a-option>
|
||
</a-select>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import type { TenantVO } from '@/apis/xt/tenant'
|
||
import { getTenantList } from '@/apis/xt/tenant'
|
||
|
||
/**
|
||
* 「选择学校」——平台运营的当前租户。
|
||
*
|
||
* 为什么要有这个东西:平台运营是跨租户身份,服务端对它的租户过滤是**关掉的**,
|
||
* 所以它必须**先指明看哪所学校**,否则「组织架构」「账号管理」会拿到所有租户混在一起的数据。
|
||
* 选中的值由页面带进请求头 `X-Tenant-Id`(见 `@/apis/xt/acting-tenant`)。
|
||
*
|
||
* ⚠️ 只有平台运营的页面才渲染它 —— 学校管理员的租户由 token 决定,给他一个
|
||
* "选择学校"的下拉框是纯粹的困惑(而且服务端会忽略他传的值)。
|
||
*
|
||
* ⚠️ 租户列表接口只对平台运营开放(学校管理员调它得 40301),所以本组件
|
||
* **不要**在非平台页面里渲染。
|
||
*/
|
||
defineOptions({ name: 'GiTenantPicker' })
|
||
|
||
const model = defineModel<number | undefined>({ default: undefined })
|
||
|
||
const props = withDefaults(defineProps<{
|
||
placeholder?: string
|
||
disabled?: boolean
|
||
width?: string
|
||
}>(), {
|
||
placeholder: '选择学校',
|
||
disabled: false,
|
||
width: '100%'
|
||
})
|
||
|
||
const loading = ref(false)
|
||
const options = ref<TenantVO[]>([])
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
// 一期租户规模是"几十所"量级(接口上限 200),一次取完比做分页下拉简单得多。
|
||
// 已停用的也列出来但不可选 —— 服务端会拒("目标单位不存在或已停用"),
|
||
// 与其让人选完才知道,不如在选择时就说明。
|
||
const res = await getTenantList({ pageNum: 1, pageSize: 200 })
|
||
options.value = res.data?.list ?? []
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(load)
|
||
|
||
defineExpose({ reload: load })
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.gi-tenant-picker {
|
||
min-width: 180px;
|
||
}
|
||
</style>
|