index.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. <template>
  2. <el-form-item class="tenant-select-item">
  3. <el-select
  4. v-if="options.length > 0"
  5. v-model="value"
  6. class="tenant-select"
  7. placeholder="请选择租户"
  8. :loading="loading"
  9. @change="persist"
  10. >
  11. <el-option
  12. v-for="item in options"
  13. :key="item.tenantId"
  14. :label="item.tenantName"
  15. :value="item.tenantId"
  16. />
  17. </el-select>
  18. <el-input
  19. v-else
  20. v-model="manualValue"
  21. class="tenant-select"
  22. clearable
  23. placeholder="租户 ID"
  24. @change="persist"
  25. />
  26. </el-form-item>
  27. </template>
  28. <script>
  29. import request from '@/utils/request';
  30. import { getTenantId, setTenantId, removeTenantId } from '@/utils/tenant-util';
  31. /**
  32. * 登录页租户选择器(多租户)。
  33. *
  34. * 为什么必须在登录页加:库隔离下账号只存在于各租户库中,服务端要在认证之前
  35. * 就知道连哪个库,因此登录请求必须携带 X-Tenant-ID。
  36. * token 签发之后租户写入 claim,后续请求由服务端以 claim 为准,本组件不再参与。
  37. *
  38. * 行为约定:
  39. * - 挂载时拉取 /main/tenant/list(匿名接口,只返回启用且当前实例已生效的租户);
  40. * - 只有一个租户时自动选中 —— 单租户部署的体验与改造前完全一致(零点击);
  41. * - 多个租户时不预设默认值,强制用户显式选择,避免"默认登进某个租户"的误操作;
  42. * - 清单接口不可用时退化为手填租户 ID,保证异常情况下运维仍能进系统;
  43. * - 不提供"平台运营端"入口:该凭证只能访问 /sys/tenant/**,本前端没有对应界面,
  44. * 登录后所有业务接口都会被租户隔离切面拒绝,故登录页只允许选择真实租户。
  45. */
  46. export default {
  47. name: 'TenantSelect',
  48. data() {
  49. return {
  50. loading: false,
  51. options: [],
  52. value: '',
  53. manualValue: ''
  54. };
  55. },
  56. created() {
  57. this.restore();
  58. this.loadOptions();
  59. },
  60. methods: {
  61. /* 回填上次选择 */
  62. restore() {
  63. const stored = getTenantId();
  64. if (stored === '') {
  65. return;
  66. }
  67. this.manualValue = stored;
  68. // 与选项值保持同一类型:后端把 Long 序列化成字符串,平台项也是字符串 '0'。
  69. // 一旦 Number 化,applyDefault 的全等比较就会失配,刷新后下拉框被清空而请求仍带着旧租户。
  70. this.value = stored;
  71. },
  72. /* 拉取租户清单 */
  73. loadOptions() {
  74. this.loading = true;
  75. request
  76. .get('/main/tenant/list')
  77. .then((res) => {
  78. const data = res && res.data ? res.data.data : null;
  79. this.options = Array.isArray(data)
  80. ? data.filter((item) => item && item.tenantId !== null && item.tenantId !== undefined)
  81. : [];
  82. this.applyDefault();
  83. })
  84. .catch(() => {
  85. // 拉不到清单时退化为手填:接口异常不该把登录整个堵死
  86. this.options = [];
  87. })
  88. .then(() => {
  89. this.loading = false;
  90. });
  91. },
  92. /* 决定默认选中项 */
  93. applyDefault() {
  94. if (this.options.length === 0) {
  95. return;
  96. }
  97. const hit = this.options.some(
  98. (item) => String(item.tenantId) === String(this.value)
  99. );
  100. if (hit) {
  101. return;
  102. }
  103. if (this.options.length === 1) {
  104. // 单租户部署:自动选中,用户无需感知多租户
  105. this.value = this.options[0].tenantId;
  106. this.persist(this.value);
  107. return;
  108. }
  109. // 多租户:不预设默认值。上次选择的租户已不在清单里(停用/到期/本实例无数据源)时,
  110. // 必须把落盘的值一并清掉,否则界面显示"请选择租户"、请求却仍带着失效租户 ID 被服务端拒绝。
  111. this.value = '';
  112. removeTenantId();
  113. },
  114. /* 落盘选择结果 */
  115. persist(val) {
  116. if (val === null || val === undefined || String(val).trim() === '') {
  117. removeTenantId();
  118. return;
  119. }
  120. setTenantId(val);
  121. }
  122. }
  123. };
  124. </script>
  125. <style lang="scss" scoped>
  126. .tenant-select {
  127. width: 100%;
  128. }
  129. </style>