Преглед на файлове

feat(tenant): 新增租户切换功能模块

- 新增租户切换页面及相关组件,实现租户列表展示与切换操作
- 新增租户切换相关接口,包括获取可切换租户、切换租户、获取当前租户、清除切换
- 在路由、导入、类型定义中添加租户切换页面支持
- 在国际化资源中新增租户切换相关文案
- 调整登录模块租户选择项校验为非必填
- 优化租户套餐页面表格配置,禁用搜索功能
zouzexu преди 2 дни
родител
ревизия
ecb71297d1

+ 2 - 1
src/locales/langs/en-us.ts

@@ -301,7 +301,8 @@ const local: App.I18n.Schema = {
     'member-center_member-type': '',
     tenant: 'Tenant Management',
     tenant_tenant: 'Tenant Management',
-    'tenant_tenant-package': 'Tenant Package'
+    'tenant_tenant-package': 'Tenant Package',
+    'tenant_tenant-change': ''
   },
   page: {
     login: {

+ 2 - 1
src/locales/langs/zh-cn.ts

@@ -298,7 +298,8 @@ const local: App.I18n.Schema = {
     'member-center_member-type': '',
     tenant: '租户管理',
     tenant_tenant: '租户管理',
-    'tenant_tenant-package': '租户套餐'
+    'tenant_tenant-package': '租户套餐',
+    'tenant_tenant-change': '租户切换'
   },
   page: {
     login: {

+ 1 - 0
src/router/elegant/imports.ts

@@ -62,6 +62,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   "order-manage_order-detail": () => import("@/views/order-manage/order-detail/index.vue"),
   "order-manage_ponits-details": () => import("@/views/order-manage/ponits-details/index.vue"),
   "order-manage_recharge-records": () => import("@/views/order-manage/recharge-records/index.vue"),
+  "tenant_tenant-change": () => import("@/views/tenant/tenant-change/index.vue"),
   "tenant_tenant-package": () => import("@/views/tenant/tenant-package/index.vue"),
   tenant_tenant: () => import("@/views/tenant/tenant/index.vue"),
   "user-center": () => import("@/views/user-center/index.vue"),

+ 9 - 0
src/router/elegant/routes.ts

@@ -584,6 +584,15 @@ export const generatedRoutes: GeneratedRoute[] = [
           i18nKey: 'route.tenant_tenant'
         }
       },
+      {
+        name: 'tenant_tenant-change',
+        path: '/tenant/tenant-change',
+        component: 'view.tenant_tenant-change',
+        meta: {
+          title: 'tenant_tenant-change',
+          i18nKey: 'route.tenant_tenant-change'
+        }
+      },
       {
         name: 'tenant_tenant-package',
         path: '/tenant/tenant-package',

+ 1 - 0
src/router/elegant/transform.ts

@@ -237,6 +237,7 @@ const routeMap: RouteMap = {
   "order-manage_recharge-records": "/order-manage/recharge-records",
   "tenant": "/tenant",
   "tenant_tenant": "/tenant/tenant",
+  "tenant_tenant-change": "/tenant/tenant-change",
   "tenant_tenant-package": "/tenant/tenant-package",
   "user-center": "/user-center",
   "user-management": "/user-management",

+ 55 - 0
src/service/api/tenant/index.ts

@@ -90,3 +90,58 @@ export function fetchDeleteTenantPackage(ids: string) {
     method: 'delete'
   });
 }
+
+// ============ 租户切换 ============
+
+/** 获取可切换的租户列表(适配ZTable分页格式) */
+export async function fetchGetActiveTenants(_params?: any) {
+  const res = await request<any[]>({
+    url: '/smqjh-system/api/v1/tenants/active',
+    method: 'get'
+  });
+  // ZTable的defaultTransform期望 data 格式为 { list, total, current, size }
+  if (!res.error && Array.isArray(res.data)) {
+    return {
+      ...res,
+      data: {
+        list: res.data,
+        total: res.data.length,
+        current: 1,
+        size: res.data.length
+      }
+    };
+  }
+  return {
+    ...res,
+    data: {
+      list: [],
+      total: 0,
+      current: 1,
+      size: 10
+    }
+  };
+}
+
+/** 切换租户 */
+export function fetchSwitchTenant(tenantCode: string) {
+  return request({
+    url: `/smqjh-system/api/v1/tenants/switch/${tenantCode}`,
+    method: 'post'
+  });
+}
+
+/** 获取当前切换的租户 */
+export function fetchGetCurrentSwitchTenant() {
+  return request<any>({
+    url: '/smqjh-system/api/v1/tenants/switch/current',
+    method: 'get'
+  });
+}
+
+/** 清除租户切换 */
+export function fetchClearSwitchTenant() {
+  return request({
+    url: '/smqjh-system/api/v1/tenants/switch/clear',
+    method: 'post'
+  });
+}

+ 2 - 0
src/typings/elegant-router.d.ts

@@ -91,6 +91,7 @@ declare module "@elegant-router/types" {
     "order-manage_recharge-records": "/order-manage/recharge-records";
     "tenant": "/tenant";
     "tenant_tenant": "/tenant/tenant";
+    "tenant_tenant-change": "/tenant/tenant-change";
     "tenant_tenant-package": "/tenant/tenant-package";
     "user-center": "/user-center";
     "user-management": "/user-management";
@@ -231,6 +232,7 @@ declare module "@elegant-router/types" {
     | "order-manage_order-detail"
     | "order-manage_ponits-details"
     | "order-manage_recharge-records"
+    | "tenant_tenant-change"
     | "tenant_tenant-package"
     | "tenant_tenant"
     | "user-center"

+ 1 - 1
src/views/_builtin/login/modules/pwd-login.vue

@@ -98,7 +98,7 @@ watch(
 
 <template>
   <NForm ref="formRef" :model="model" :rules="rules" size="large" :show-label="false" @keyup.enter="handleSubmit">
-    <NFormItem :rule="{ required: true, message: '请选择租户', trigger: 'change' }">
+    <NFormItem :rule="{ required: false, message: '请选择租户', trigger: 'change' }">
       <NSelect v-model:value="selectedTenantCode" :options="tenantOptions" placeholder="请选择租户" />
     </NFormItem>
     <NFormItem path="userName">

+ 181 - 0
src/views/tenant/tenant-change/index.vue

@@ -0,0 +1,181 @@
+<script setup lang="tsx">
+import { onMounted, ref } from 'vue';
+import { NButton, NCard, NInput, NSpin, NTag } from 'naive-ui';
+import {
+  fetchClearSwitchTenant,
+  fetchGetActiveTenants,
+  fetchGetCurrentSwitchTenant,
+  fetchSwitchTenant
+} from '@/service/api/tenant';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+import { $t } from '@/locales';
+
+interface TenantData {
+  tenantId: number;
+  tenantCode: string;
+  tenantDomain: string;
+  tenantName: string;
+  packageId: number;
+  status: number;
+  expireTime: string;
+}
+
+const columns: NaiveUI.TableColumn<any>[] = [
+  {
+    key: 'index',
+    title: $t('common.index'),
+    align: 'center',
+    width: 64,
+    render: (_, index) => index + 1
+  },
+  {
+    key: 'tenantCode',
+    title: '租户编号',
+    align: 'center',
+    minWidth: 120
+  },
+  {
+    key: 'tenantName',
+    title: '企业名称',
+    align: 'center',
+    minWidth: 150
+  },
+  {
+    key: 'tenantDomain',
+    title: '绑定域名',
+    align: 'center',
+    minWidth: 150
+  },
+  {
+    key: 'expireTime',
+    title: '过期时间',
+    align: 'center',
+    minWidth: 170
+  },
+  {
+    key: 'status',
+    title: '租户状态',
+    align: 'center',
+    width: 100,
+    render: row => {
+      if (row.status === null || row.status === undefined) {
+        return null;
+      }
+      const tagMap: Record<number, NaiveUI.ThemeColor> = {
+        1: 'success',
+        0: 'error'
+      };
+      const labelMap: Record<number, string> = {
+        1: '正常',
+        0: '停用'
+      };
+      return <NTag type={tagMap[row.status]}>{labelMap[row.status]}</NTag>;
+    }
+  }
+];
+
+const [registerTable, { setTableLoading }] = useTable({
+  searchFormConfig: {
+    schemas: [],
+    inline: false,
+    size: 'small',
+    labelPlacement: 'left',
+    isFull: false
+  },
+  tableConfig: {
+    keyField: 'tenantId',
+    title: '租户列表',
+    showAddButton: false,
+    scrollX: 800,
+    showSearch: false
+  }
+});
+
+// 当前选中的租户
+const currentTenant = ref<TenantData | null>(null);
+const currentTenantDisplay = ref('');
+const loadingCurrentTenant = ref(false);
+
+// 获取当前切换的租户
+async function loadCurrentTenant() {
+  loadingCurrentTenant.value = true;
+  try {
+    const { data, error } = await fetchGetCurrentSwitchTenant();
+    if (!error && data) {
+      currentTenant.value = data;
+      currentTenantDisplay.value = data.tenantName || '';
+    } else {
+      currentTenant.value = null;
+      currentTenantDisplay.value = '';
+    }
+  } catch (err) {
+    console.error(err);
+    currentTenant.value = null;
+    currentTenantDisplay.value = '';
+  } finally {
+    loadingCurrentTenant.value = false;
+  }
+}
+
+// 清除租户切换
+async function handleClearTenant() {
+  try {
+    const { error } = await fetchClearSwitchTenant();
+    if (!error) {
+      currentTenant.value = null;
+      currentTenantDisplay.value = '';
+      window.$message?.success('已清除');
+    }
+  } catch (err) {
+    console.error(err);
+    window.$message?.error('清除失败');
+  }
+}
+
+// 切换租户
+async function handleSwitch(row: TenantData) {
+  setTableLoading(true);
+  try {
+    const { error } = await fetchSwitchTenant(row.tenantCode);
+    if (!error) {
+      window.$message?.success('切换成功');
+      await loadCurrentTenant();
+    }
+  } catch (err) {
+    console.error(err);
+    window.$message?.error('切换失败');
+  } finally {
+    setTableLoading(false);
+  }
+}
+
+onMounted(() => {
+  loadCurrentTenant();
+});
+</script>
+
+<template>
+  <LayoutTable>
+    <NCard size="small" class="mb-16px">
+      <div class="flex items-center gap-12px">
+        <span class="whitespace-nowrap text-14px">当前租户:</span>
+        <NSpin :show="loadingCurrentTenant" size="small" class="w-300px">
+          <NInput
+            :value="currentTenantDisplay"
+            placeholder="暂未选择租户"
+            readonly
+            clearable
+            @clear="handleClearTenant"
+          />
+        </NSpin>
+      </div>
+    </NCard>
+    <ZTable :columns="columns as any" :api="fetchGetActiveTenants" @register="registerTable">
+      <template #op="{ row }">
+        <NButton size="small" ghost type="primary" @click="handleSwitch(row)">切换</NButton>
+      </template>
+    </ZTable>
+  </LayoutTable>
+</template>
+
+<style scoped></style>

+ 2 - 1
src/views/tenant/tenant-package/index.vue

@@ -62,7 +62,8 @@ const [registerTable, { refresh, setTableLoading }] = useTable({
     keyField: 'id',
     title: '租户套餐列表',
     showAddButton: true,
-    scrollX: 800
+    scrollX: 800,
+    showSearch: false
   }
 });