Browse Source

feat(架构): 增加切换租户功能

shizhongming 3 years ago
parent
commit
b1574c7828
  1. 26
      src/api/sys/user.ts
  2. 2
      src/hooks/web/usePermission.ts
  3. 95
      src/layouts/default/header/components/ChangeTenant/ChangeTenantModal.vue
  4. 30
      src/layouts/default/header/components/user-dropdown/index.vue
  5. 4
      src/locales/lang/en/layout.json
  6. 4
      src/locales/lang/zh-CN/layout.json
  7. 13
      src/modules/smart-system/views/tenant/tenantManager/SysTenantListView.api.ts
  8. 22
      src/modules/smart-system/views/tenant/tenantManager/SysTenantListView.config.ts
  9. 54
      src/modules/smart-system/views/tenant/tenantManager/components/TenantUserList.vue
  10. 10
      src/modules/smart-system/views/tenant/tenantManager/lang/zh_CN.ts
  11. 26
      src/store/modules/user.ts
  12. 13
      types/store.d.ts

26
src/api/sys/user.ts

@ -15,6 +15,8 @@ enum Api {
GetPermCode = '/getPermCode',
TestRetry = '/testRetry',
changePassword = 'sys/auth/changePassword',
listCurrentUserTenant = 'sys/tenant/manager/listCurrentUserTenant',
changeTenant = '/auth/tenant/change',
}
/**
@ -72,3 +74,27 @@ export const changePasswordApi = (params: ChangePasswordParams) => {
data: params,
});
};
/**
* 查询当前用户租户列表
*/
export const listCurrentUserTenantApi = () => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.listCurrentUserTenant,
});
};
/**
* 切换租户API
* @param tenantId
*/
export const changeTenantApi = (tenantId: number): Promise<LoginResultModel> => {
return defHttp.postForm({
service: ApiServiceEnum.SMART_AUTH,
url: Api.changeTenant,
params: {
tenantId,
},
});
};

2
src/hooks/web/usePermission.ts

@ -115,7 +115,7 @@ export function usePermission() {
* refresh menu data
*/
async function refreshMenu() {
resume();
return resume();
}
const getNoPermissionMode = computed(() => appStore.getProjectConfig.noPermissionMode);

95
src/layouts/default/header/components/ChangeTenant/ChangeTenantModal.vue

@ -0,0 +1,95 @@
<template>
<BasicModal
v-bind="$attrs"
:title="t('layout.header.changeTenant')"
@register="registerModal"
@open-change="handleOpenChange"
@ok="handleOk"
>
<BasicForm @register="registerForm">
<template #tenant="{ model, field }">
<RadioGroup v-model:value="model[field]">
<Radio v-for="item in tenantListRef" :key="item.id" :value="item.id" :style="radioStyle">
{{ item.tenantCode }}: {{ item.tenantShortName || item.tenantName }}
</Radio>
</RadioGroup>
</template>
</BasicForm>
</BasicModal>
</template>
<script setup lang="ts">
import { nextTick, ref, unref } from 'vue';
import { storeToRefs } from 'pinia';
import { Radio } from 'ant-design-vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { useI18n } from '@/hooks/web/useI18n';
import { BasicForm, useForm } from '@/components/Form';
import { useUserStore } from '@/store/modules/user';
import { listCurrentUserTenantApi } from '@/api/sys/user';
import { successMessage } from '@/utils/message/SystemNotice';
const RadioGroup = Radio.Group;
const { t } = useI18n();
const userStore = useUserStore();
const { changeTenant } = userStore;
const { getUserTenant } = storeToRefs(userStore);
const tenantListRef = ref<Recordable[]>();
const handleOpenChange = async (visible: boolean) => {
if (visible) {
nextTick(() => {
setFieldsValue({
tenantId: unref(getUserTenant)?.tenantId,
});
});
try {
changeLoading(true);
tenantListRef.value = await listCurrentUserTenantApi();
} finally {
changeLoading(false);
}
} else {
tenantListRef.value = [];
}
};
const handleOk = async () => {
const { tenantId } = await validate();
if (tenantId === unref(getUserTenant)?.tenantId) {
closeModal();
return false;
}
try {
changeOkLoading(true);
await changeTenant(tenantId);
successMessage(t('layout.header.changeTenantSuccess'));
closeModal();
} finally {
changeOkLoading(false);
}
};
const [registerModal, { changeLoading, closeModal, changeOkLoading }] = useModalInner();
const [registerForm, { setFieldsValue, validate }] = useForm({
showActionButtonGroup: false,
schemas: [
{
field: 'tenantId',
label: t('layout.header.changeTenant'),
slot: 'tenant',
required: true,
},
],
});
const radioStyle = ref({
display: 'flex',
height: '30px',
lineHeight: '30px',
});
</script>
<style scoped lang="less"></style>

30
src/layouts/default/header/components/user-dropdown/index.vue

@ -4,7 +4,7 @@
<img :class="`${prefixCls}__header`" :src="getUserInfo.avatar" />
<span :class="`${prefixCls}__info hidden md:block`">
<span :class="`${prefixCls}__name`" class="truncate">
{{ getUserInfo.realName }}
{{ computedUsername }}
</span>
</span>
</span>
@ -18,6 +18,11 @@
v-if="getShowDoc"
/>
<Menu.Divider v-if="getShowDoc" />
<MenuItem
key="changeTenant"
:text="t('layout.header.changeTenant')"
icon="ant-design:usergroup-add-outlined"
/>
<MenuItem
v-if="getShowApi"
key="api"
@ -46,11 +51,12 @@
<LockAction @register="register" />
<ChangeApi @register="registerApi" />
<ChangePasswordModal @register="registerChangePasswordModal" />
<ChangeTenantModal @register="registerTenantModal" />
</template>
<script lang="ts" setup>
import { Dropdown, Menu } from 'ant-design-vue';
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { computed } from 'vue';
import { computed, unref } from 'vue';
import { DOC_URL } from '@/settings/siteSetting';
import { useUserStore } from '@/store/modules/user';
import { useHeaderSetting } from '@/hooks/setting/useHeaderSetting';
@ -62,8 +68,9 @@
import { openWindow } from '@/utils';
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
import ChangePasswordModal from './ChangePasswordModal.vue';
import ChangeTenantModal from '../ChangeTenant/ChangeTenantModal.vue';
type MenuEvent = 'logout' | 'doc' | 'lock' | 'api' | 'changePassword';
type MenuEvent = 'logout' | 'doc' | 'lock' | 'api' | 'changePassword' | 'changeTenant';
const MenuItem = createAsyncComponent(() => import('./DropMenuItem.vue'));
const LockAction = createAsyncComponent(() => import('../lock/LockModal.vue'));
@ -81,12 +88,22 @@
const userStore = useUserStore();
const getUserInfo = computed(() => {
const { realName = '', avatar, desc } = userStore.getUserInfo || {};
return { realName, avatar: avatar || headerImg, desc };
const { realName = '', avatar, desc, userTenant } = userStore.getUserInfo || {};
return { realName, avatar: avatar || headerImg, desc, userTenant };
});
const computedUsername = computed(() => {
const { realName, userTenant } = unref(getUserInfo);
const list = [realName];
if (userTenant) {
list.push(userTenant.tenantShortName || userTenant.tenantName);
}
return list.join('/');
});
const [register, { openModal }] = useModal();
const [registerApi, { openModal: openApiModal }] = useModal();
const [registerTenantModal, { openModal: openTenantModal }] = useModal();
function handleLock() {
openModal(true);
@ -123,6 +140,9 @@
case 'changePassword':
handleChangePassword();
break;
case 'changeTenant':
openTenantModal();
break;
}
}

4
src/locales/lang/en/layout.json

@ -16,7 +16,9 @@
"lockScreen": "Lock screen",
"lockScreenBtn": "Locking",
"home": "Home",
"changePassword": "Change password"
"changePassword": "Change password",
"changeTenant": "Change tenant",
"changeTenantSuccess": "Change tenant success"
},
"multipleTab": {
"reload": "Refresh current",

4
src/locales/lang/zh-CN/layout.json

@ -16,7 +16,9 @@
"lockScreen": "锁定屏幕",
"lockScreenBtn": "锁定",
"home": "首页",
"changePassword": "修改密码"
"changePassword": "修改密码",
"changeTenant": "切换租户",
"changeTenantSuccess": "切换租户成功"
},
"multipleTab": {
"reload": "重新加载",

13
src/modules/smart-system/views/tenant/tenantManager/SysTenantListView.api.ts

@ -17,6 +17,7 @@ enum Api {
getSubscribeById = '/sys/tenant/subscribe/getById',
batchSaveUpdateSubscribe = '/sys/tenant/subscribe/saveUpdateBatch',
listNoBindPackageByTenantId = '/sys/tenant/manager/listNoBindPackageByTenantId',
createTenantUserAccount = '/sys/tenant/manager/createTenantUserAccount',
}
export const listApi = (params) => {
@ -163,3 +164,15 @@ export const batchSaveUpdateSubscribeApi = (data: Recordable) => {
data,
});
};
/**
* 根据租户ID查询没有绑定的套餐
* @param data
*/
export const createTenantUserAccountApi = (data: Recordable) => {
return defHttp.post({
service: ApiServiceEnum.SMART_SYSTEM,
url: Api.createTenantUserAccount,
data,
});
};

22
src/modules/smart-system/views/tenant/tenantManager/SysTenantListView.config.ts

@ -346,7 +346,7 @@ export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
/**
* 绑定用户tab
*/
export const getTabUserListColumns = (): SmartColumn[] => {
export const getTabUserListColumns = (t: Function): SmartColumn[] => {
return [
{
type: 'checkbox',
@ -367,6 +367,26 @@ export const getTabUserListColumns = (): SmartColumn[] => {
width: 120,
fixed: 'left',
},
{
field: 'accountId',
title: '{system.views.tenant.manager.title.user.hasAccount}',
formatter({ row }) {
const accountId = row.accountId;
if (accountId) {
return t('common.form.yes');
}
return t('common.form.no');
},
align: 'center',
className({ row }) {
const accountId = row.accountId;
if (accountId) {
return 'text-color--success-bold';
}
return 'text-color--danger-bold';
},
width: 100,
},
{
title: '{system.views.tenant.manager.title.user.email}',
field: 'email',

54
src/modules/smart-system/views/tenant/tenantManager/components/TenantUserList.vue

@ -17,11 +17,18 @@
getTabUserListSearchSchemas,
Permission,
} from '../SysTenantListView.config';
import { listTenantUserApi, removeBindUserApi } from '../SysTenantListView.api';
import {
listTenantUserApi,
removeBindUserApi,
createTenantUserAccountApi,
} from '../SysTenantListView.api';
import { useDesign } from '@/hooks/web/useDesign';
import { useI18n } from '@/hooks/web/useI18n';
import TenantAddUserModal from './TenantAddUserModal.vue';
import { hasPermission } from '@/utils/auth';
import { useUserStore } from '@/store/modules/user';
import { storeToRefs } from 'pinia';
import { createConfirm, successMessage, warnMessage } from '@/utils/message/SystemNotice';
const props = defineProps({
tenantId: propTypes.number,
@ -31,6 +38,7 @@
() => query(),
);
const computedChoseTenant = computed(() => props.tenantId !== undefined);
const { getIsPlatformTenant } = storeToRefs(useUserStore());
const { tableSizeConfig } = useSizeSetting();
const { prefixCls } = useDesign('system-tenant-manager-userTab');
@ -38,9 +46,39 @@
const [registerAddUserModal, { openModal: openAddUserModal }] = useModal();
const [register, { query }] = useSmartTable({
const handleCreateAccount = () => {
const selectRows = getCheckboxRecords();
if (selectRows.length === 0) {
warnMessage(t('system.views.tenant.manager.message.selectUser'));
return false;
}
// 验证是否已经存在创建账户的
const createAccountList = selectRows.filter((item) => item.accountId === null);
if (createAccountList.length === 0) {
warnMessage(t('system.views.tenant.manager.message.所选用户已全部创建账户'));
return false;
}
let i18nKey = 'system.views.tenant.manager.message.createAccountConfirm';
if (createAccountList.length < selectRows.length) {
i18nKey = 'system.views.tenant.manager.message.hasCreateAccount';
}
createConfirm({
content: t(i18nKey),
iconType: 'warning',
async onOk() {
await createTenantUserAccountApi({
tenantId: props.tenantId,
userIdList: createAccountList.map((item) => item.userId),
});
successMessage(t('system.views.tenant.manager.message.createAccountSuccess'));
query();
},
});
};
const [register, { query, getCheckboxRecords }] = useSmartTable({
id: 'system-tenant-manager-userList',
columns: getTabUserListColumns(),
columns: getTabUserListColumns(t),
border: true,
height: 'auto',
customConfig: { storage: true },
@ -115,6 +153,16 @@
};
}),
},
{
name: t('system.views.tenant.manager.button.user.createAccount'),
customRender: 'ant',
visible: unref(getIsPlatformTenant),
props: {
type: 'primary',
preIcon: 'ant-design:user-add-outlined',
onClick: handleCreateAccount,
},
},
],
},
});

10
src/modules/smart-system/views/tenant/tenantManager/lang/zh_CN.ts

@ -33,6 +33,7 @@ export default {
mobile: '电话',
bindTime: '绑定时间',
bindBy: '绑定人',
hasAccount: '是否创建账户',
},
subscribe: {
tenantId: '租户',
@ -74,6 +75,15 @@ export default {
selectOneRow: '请选择一条数据',
bindUserSuccess: '绑定用户成功',
selectUser: '请先选择用户',
hasCreateAccount: '选择用户存在已创建账户,是否跳过?',
createAccountConfirm: '确定要创建账户吗?',
noCreateAccountUser: '所选用户已全部创建账户',
createAccountSuccess: '创建账户成功',
},
button: {
user: {
createAccount: '创建账户',
},
},
},
};

26
src/store/modules/user.ts

@ -1,4 +1,4 @@
import type { UserInfo } from '#/store';
import type { UserInfo, UserTenant } from '#/store';
import type { ErrorMessageMode } from '#/axios';
import { defineStore } from 'pinia';
import { store } from '@/store';
@ -12,11 +12,12 @@ import {
LoginParams,
LoginResultModel,
} from '@/api/sys/model/userModel';
import { changePasswordApi, doLogout, loginApi } from '@/api/sys/user';
import { changePasswordApi, doLogout, loginApi, changeTenantApi } from '@/api/sys/user';
import { useI18n } from '@/hooks/web/useI18n';
import { useMessage } from '@/hooks/web/useMessage';
import { router } from '@/router';
import { usePermissionStore } from '@/store/modules/permission';
import { usePermission } from '@/hooks/web/usePermission';
import { RouteRecordRaw } from 'vue-router';
import { PAGE_NOT_FOUND_ROUTE } from '@/router/routes/basic';
import { h } from 'vue';
@ -59,6 +60,15 @@ export const useUserStore = defineStore({
getLastUpdateTime(state): number {
return state.lastUpdateTime;
},
getUserTenant(): UserTenant | undefined {
return this.getUserInfo.userTenant;
},
/**
* 是否是平台租户
*/
getIsPlatformTenant(): boolean {
return this.getUserTenant?.platformYn || false;
},
},
actions: {
setToken(info: string | undefined) {
@ -83,6 +93,17 @@ export const useUserStore = defineStore({
this.roleList = [];
this.sessionTimeout = false;
},
/**
* 切换租户
*/
async changeTenant(tenantId: number) {
const data = await changeTenantApi(tenantId);
// 刷新菜单
await this.afterLoginAction(data, false);
const { refreshMenu } = usePermission();
await refreshMenu();
router.replace(data.user?.homePath || PageEnum.BASE_HOME);
},
/**
* @description: login
*/
@ -114,7 +135,6 @@ export const useUserStore = defineStore({
this.setUserInfo({
...userInfo,
});
const sessionTimeout = this.sessionTimeout;
if (sessionTimeout) {
this.setSessionTimeout(false);

13
types/store.d.ts

@ -41,6 +41,19 @@ export interface UserInfo {
avatar: string;
desc?: string;
homePath?: string;
// 租户信息
userTenant?: UserTenant;
}
/**
* 用户租户信息
*/
export interface UserTenant {
tenantId: number;
tenantCode: string;
tenantName: string;
tenantShortName?: string;
platformYn: boolean;
}
export interface BeforeMiniState {

Loading…
Cancel
Save