| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- <template>
- <el-form-item class="tenant-select-item">
- <el-select
- v-if="options.length > 0"
- v-model="value"
- class="tenant-select"
- placeholder="请选择租户"
- :loading="loading"
- @change="persist"
- >
- <el-option
- v-for="item in options"
- :key="item.tenantId"
- :label="item.tenantName"
- :value="item.tenantId"
- />
- </el-select>
- <el-input
- v-else
- v-model="manualValue"
- class="tenant-select"
- clearable
- placeholder="租户 ID"
- @change="persist"
- />
- </el-form-item>
- </template>
- <script>
- import request from '@/utils/request';
- import { getTenantId, setTenantId, removeTenantId } from '@/utils/tenant-util';
- /**
- * 登录页租户选择器(多租户)。
- *
- * 为什么必须在登录页加:库隔离下账号只存在于各租户库中,服务端要在认证之前
- * 就知道连哪个库,因此登录请求必须携带 X-Tenant-ID。
- * token 签发之后租户写入 claim,后续请求由服务端以 claim 为准,本组件不再参与。
- *
- * 行为约定:
- * - 挂载时拉取 /main/tenant/list(匿名接口,只返回启用且当前实例已生效的租户);
- * - 只有一个租户时自动选中 —— 单租户部署的体验与改造前完全一致(零点击);
- * - 多个租户时不预设默认值,强制用户显式选择,避免"默认登进某个租户"的误操作;
- * - 清单接口不可用时退化为手填租户 ID,保证异常情况下运维仍能进系统;
- * - 不提供"平台运营端"入口:该凭证只能访问 /sys/tenant/**,本前端没有对应界面,
- * 登录后所有业务接口都会被租户隔离切面拒绝,故登录页只允许选择真实租户。
- */
- export default {
- name: 'TenantSelect',
- data() {
- return {
- loading: false,
- options: [],
- value: '',
- manualValue: ''
- };
- },
- created() {
- this.restore();
- this.loadOptions();
- },
- methods: {
- /* 回填上次选择 */
- restore() {
- const stored = getTenantId();
- if (stored === '') {
- return;
- }
- this.manualValue = stored;
- // 与选项值保持同一类型:后端把 Long 序列化成字符串,平台项也是字符串 '0'。
- // 一旦 Number 化,applyDefault 的全等比较就会失配,刷新后下拉框被清空而请求仍带着旧租户。
- this.value = stored;
- },
- /* 拉取租户清单 */
- loadOptions() {
- this.loading = true;
- request
- .get('/main/tenant/list')
- .then((res) => {
- const data = res && res.data ? res.data.data : null;
- this.options = Array.isArray(data)
- ? data.filter((item) => item && item.tenantId !== null && item.tenantId !== undefined)
- : [];
- this.applyDefault();
- })
- .catch(() => {
- // 拉不到清单时退化为手填:接口异常不该把登录整个堵死
- this.options = [];
- })
- .then(() => {
- this.loading = false;
- });
- },
- /* 决定默认选中项 */
- applyDefault() {
- if (this.options.length === 0) {
- return;
- }
- const hit = this.options.some(
- (item) => String(item.tenantId) === String(this.value)
- );
- if (hit) {
- return;
- }
- if (this.options.length === 1) {
- // 单租户部署:自动选中,用户无需感知多租户
- this.value = this.options[0].tenantId;
- this.persist(this.value);
- return;
- }
- // 多租户:不预设默认值。上次选择的租户已不在清单里(停用/到期/本实例无数据源)时,
- // 必须把落盘的值一并清掉,否则界面显示"请选择租户"、请求却仍带着失效租户 ID 被服务端拒绝。
- this.value = '';
- removeTenantId();
- },
- /* 落盘选择结果 */
- persist(val) {
- if (val === null || val === undefined || String(val).trim() === '') {
- removeTenantId();
- return;
- }
- setTenantId(val);
- }
- }
- };
- </script>
- <style lang="scss" scoped>
- .tenant-select {
- width: 100%;
- }
- </style>
|