mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-08 12:57:11 +08:00
refactor(tenant): 优化租户相关代码
This commit is contained in:
@@ -5,19 +5,32 @@ export type * from './type'
|
||||
|
||||
const BASE_URL = '/auth'
|
||||
|
||||
/** @desc 账号登录 */
|
||||
export function accountLogin(req: T.AccountLoginReq) {
|
||||
return http.post<T.LoginResp>(`${BASE_URL}/login`, req)
|
||||
const login = (req: T.AccountLoginReq | T.PhoneLoginReq | T.EmailLoginReq, tenantCode?: string) => {
|
||||
const headers = {}
|
||||
if (tenantCode) {
|
||||
headers['X-Tenant-Code'] = tenantCode
|
||||
}
|
||||
return http.requestNative({
|
||||
url: `${BASE_URL}/login`,
|
||||
data: req,
|
||||
method: 'post',
|
||||
headers,
|
||||
})
|
||||
}
|
||||
|
||||
/** @desc 手机号登录 */
|
||||
export function phoneLogin(req: T.PhoneLoginReq) {
|
||||
return http.post<T.LoginResp>(`${BASE_URL}/login`, req)
|
||||
/** @desc 账号登录 */
|
||||
export function accountLogin(req: T.AccountLoginReq, tenantCode?: string) {
|
||||
return login(req, tenantCode)
|
||||
}
|
||||
|
||||
/** @desc 邮箱登录 */
|
||||
export function emailLogin(req: T.EmailLoginReq) {
|
||||
return http.post<T.LoginResp>(`${BASE_URL}/login`, req)
|
||||
export function emailLogin(req: T.EmailLoginReq, tenantCode?: string) {
|
||||
return login(req, tenantCode)
|
||||
}
|
||||
|
||||
/** @desc 手机号登录 */
|
||||
export function phoneLogin(req: T.PhoneLoginReq, tenantCode?: string) {
|
||||
return login(req, tenantCode)
|
||||
}
|
||||
|
||||
/** @desc 三方账号登录 */
|
||||
|
@@ -335,6 +335,7 @@ export interface BasicConfig {
|
||||
SITE_TITLE: string
|
||||
SITE_COPYRIGHT: string
|
||||
SITE_BEIAN: string
|
||||
TENANT_ENABLED: boolean
|
||||
}
|
||||
|
||||
/** 基础配置类型 */
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type * as T from './type'
|
||||
import http from '@/utils/http'
|
||||
import type { TenantCommon } from '@/utils/tenant'
|
||||
|
||||
export type * from './type'
|
||||
|
||||
@@ -31,11 +30,6 @@ export function deleteTenant(id: string) {
|
||||
return http.del(`${BASE_URL}/${id}`)
|
||||
}
|
||||
|
||||
/** @desc 多租户通用信息查询 */
|
||||
export const getTenantCommon = () => {
|
||||
return http.get<TenantCommon>(`${BASE_URL}/common`)
|
||||
}
|
||||
|
||||
/** @desc 修改租户管理员密码 */
|
||||
export const updateTenantAdminUserPwd = (data: any, id: string) => {
|
||||
return http.put(`${BASE_URL}/${id}/admin/pwd`, data)
|
||||
|
@@ -1,168 +0,0 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:visible="visible"
|
||||
title="选择租户"
|
||||
hide-cancel
|
||||
:closable="false"
|
||||
:mask-closable="false"
|
||||
:ok-loading="okLoading"
|
||||
draggable
|
||||
:width="width >= 500 ? 500 : '90%'"
|
||||
@before-ok="confirm"
|
||||
>
|
||||
<div class="scrollable-container">
|
||||
<a-radio-group v-model="tenantId" direction="vertical" class="scrollable-container-radio-group" @change="value => { setTenantId(value) }">
|
||||
<a-radio :value="0" class="scrollable-container-radio">
|
||||
<template #radio="{ checked }">
|
||||
<a-space
|
||||
align="start"
|
||||
class="custom-radio-card"
|
||||
:class="{ 'custom-radio-card-checked': checked }"
|
||||
>
|
||||
<div className="custom-radio-card-mask">
|
||||
<div className="custom-radio-card-mask-dot" />
|
||||
</div>
|
||||
系统默认租户
|
||||
</a-space>
|
||||
</template>
|
||||
</a-radio>
|
||||
<template v-for="item in tenantList" :key="item.id">
|
||||
<a-radio :value="item.id" class="scrollable-container-radio">
|
||||
<template #radio="{ checked }">
|
||||
<a-space
|
||||
align="start"
|
||||
class="custom-radio-card"
|
||||
:class="{ 'custom-radio-card-checked': checked }"
|
||||
>
|
||||
<div className="custom-radio-card-mask">
|
||||
<div className="custom-radio-card-mask-dot" />
|
||||
</div>
|
||||
{{ item.name }}
|
||||
</a-space>
|
||||
</template>
|
||||
</a-radio>
|
||||
</template>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { getTenantId, setTenantId } from '@/utils/tenant'
|
||||
import { getTenantCommon } from '@/apis/tenant/management'
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const visible = ref(false)
|
||||
const okLoading = ref(false)
|
||||
const tenantList = ref()
|
||||
const tenantId = ref(getTenantId())
|
||||
|
||||
const confirm = async () => {
|
||||
if (!getTenantId()) {
|
||||
Message.error('请确认需要登录的租户')
|
||||
return false
|
||||
} else {
|
||||
visible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const tenantCommon = await getTenantCommon()
|
||||
// 需要开启了多租户才显示租户选择款
|
||||
if (tenantCommon && tenantCommon.data.isEnabled) {
|
||||
if (tenantCommon.data.availableList.length > 0) {
|
||||
const hostname = window.location.hostname
|
||||
for (const item of tenantCommon.data.availableList) {
|
||||
// 如果有域名匹配则直接设置对应租户
|
||||
if (item.domain === hostname) {
|
||||
setTenantId(item.id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果后台没有配置租户则是默认系统租户
|
||||
setTenantId(0)
|
||||
return false
|
||||
}
|
||||
tenantList.value = tenantCommon.data.availableList
|
||||
visible.value = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tenant-select{
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.scrollable-container {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
text-align: center;
|
||||
&-radio-group{
|
||||
width: 90%
|
||||
}
|
||||
&-radio{
|
||||
padding: 7px 0
|
||||
}
|
||||
}
|
||||
|
||||
.custom-radio-card {
|
||||
padding: 10px 16px;
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-radio-card-mask {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 100%;
|
||||
border: 1px solid var(--color-border-2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.custom-radio-card-mask-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.custom-radio-card-title {
|
||||
color: var(--color-text-1);
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.custom-radio-card:hover,
|
||||
.custom-radio-card-checked,
|
||||
.custom-radio-card:hover .custom-radio-card-mask,
|
||||
.custom-radio-card-checked .custom-radio-card-mask{
|
||||
border-color: rgb(var(--primary-6));
|
||||
}
|
||||
|
||||
.custom-radio-card-checked {
|
||||
background-color: var(--color-primary-light-1);
|
||||
}
|
||||
|
||||
.custom-radio-card:hover .custom-radio-card-title,
|
||||
.custom-radio-card-checked .custom-radio-card-title {
|
||||
color: rgb(var(--primary-6));
|
||||
}
|
||||
|
||||
.custom-radio-card-checked .custom-radio-card-mask-dot {
|
||||
background-color: rgb(var(--primary-6));
|
||||
}
|
||||
</style>
|
@@ -68,6 +68,7 @@ const storeSetup = () => {
|
||||
siteConfig.SITE_TITLE = resMap.get('SITE_TITLE')
|
||||
siteConfig.SITE_COPYRIGHT = resMap.get('SITE_COPYRIGHT')
|
||||
siteConfig.SITE_BEIAN = resMap.get('SITE_BEIAN')
|
||||
siteConfig.TENANT_ENABLED = resMap.get('TENANT_ENABLED') === 'true'
|
||||
document.title = resMap.get('SITE_TITLE')
|
||||
document
|
||||
.querySelector('link[rel="shortcut icon"]')
|
||||
@@ -122,6 +123,10 @@ const storeSetup = () => {
|
||||
const getForRecord = () => {
|
||||
return siteConfig.SITE_BEIAN
|
||||
}
|
||||
|
||||
const getTenantEnabled = () => {
|
||||
return siteConfig.TENANT_ENABLED
|
||||
}
|
||||
return {
|
||||
...toRefs(settingConfig),
|
||||
...toRefs(siteConfig),
|
||||
@@ -138,6 +143,7 @@ const storeSetup = () => {
|
||||
getTitle,
|
||||
getCopyright,
|
||||
getForRecord,
|
||||
getTenantEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -50,22 +50,22 @@ const storeSetup = () => {
|
||||
}
|
||||
|
||||
// 登录
|
||||
const accountLogin = async (req: AccountLoginReq) => {
|
||||
const res = await accountLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.ACCOUNT })
|
||||
const accountLogin = async (req: AccountLoginReq, tenantCode?: string) => {
|
||||
const res = await accountLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.ACCOUNT }, tenantCode)
|
||||
setToken(res.data.token)
|
||||
token.value = res.data.token
|
||||
}
|
||||
|
||||
// 邮箱登录
|
||||
const emailLogin = async (req: EmailLoginReq) => {
|
||||
const res = await emailLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.EMAIL })
|
||||
const emailLogin = async (req: EmailLoginReq, tenantCode?: string) => {
|
||||
const res = await emailLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.EMAIL }, tenantCode)
|
||||
setToken(res.data.token)
|
||||
token.value = res.data.token
|
||||
}
|
||||
|
||||
// 手机号登录
|
||||
const phoneLogin = async (req: PhoneLoginReq) => {
|
||||
const res = await phoneLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.PHONE })
|
||||
const phoneLogin = async (req: PhoneLoginReq, tenantCode?: string) => {
|
||||
const res = await phoneLoginApi({ ...req, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeConstants.PHONE }, tenantCode)
|
||||
setToken(res.data.token)
|
||||
token.value = res.data.token
|
||||
}
|
||||
|
@@ -52,17 +52,14 @@ const handleError = (msg: string) => {
|
||||
http.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
const token = getToken()
|
||||
if (!config.headers) {
|
||||
config.headers = {}
|
||||
}
|
||||
if (token) {
|
||||
if (!config.headers) {
|
||||
config.headers = {}
|
||||
}
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
const tenantId = getTenantId()
|
||||
if (tenantId) {
|
||||
if (!config.headers) {
|
||||
config.headers = {}
|
||||
}
|
||||
config.headers['X-Tenant-Id'] = tenantId
|
||||
}
|
||||
return config
|
||||
|
@@ -8,6 +8,9 @@
|
||||
size="large"
|
||||
@submit="handleLogin"
|
||||
>
|
||||
<a-form-item v-if="tenantEnabled" field="tenantCode" hide-label>
|
||||
<a-input v-model="tenantCode" placeholder="请输入租户编码" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item field="username" hide-label>
|
||||
<a-input v-model="form.username" placeholder="请输入用户名" allow-clear />
|
||||
</a-form-item>
|
||||
@@ -40,12 +43,17 @@
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message } from '@arco-design/web-vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { getImageCaptcha } from '@/apis/common'
|
||||
import { useTabsStore, useUserStore } from '@/stores'
|
||||
import { useAppStore, useTabsStore, useUserStore } from '@/stores'
|
||||
import { encryptByRsa } from '@/utils/encrypt'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const tenantEnabled = computed(() => appStore.getTenantEnabled())
|
||||
|
||||
const loginConfig = useStorage('login-config', {
|
||||
rememberMe: true,
|
||||
tenantCode: '',
|
||||
username: 'admin', // 演示默认值
|
||||
password: 'admin123', // 演示默认值
|
||||
// username: debug ? 'admin' : '', // 演示默认值
|
||||
@@ -64,6 +72,7 @@ const form = reactive({
|
||||
uuid: '',
|
||||
expired: false,
|
||||
})
|
||||
const tenantCode = ref()
|
||||
const rules: FormInstance['rules'] = {
|
||||
username: [{ required: true, message: '请输入用户名' }],
|
||||
password: [{ required: true, message: '请输入密码' }],
|
||||
@@ -119,10 +128,11 @@ const handleLogin = async () => {
|
||||
password: encryptByRsa(form.password) || '',
|
||||
captcha: form.captcha,
|
||||
uuid: form.uuid,
|
||||
})
|
||||
}, tenantCode.value)
|
||||
tabsStore.reset()
|
||||
const { redirect, ...othersQuery } = router.currentRoute.value.query
|
||||
const { rememberMe } = loginConfig.value
|
||||
loginConfig.value.tenantCode = rememberMe ? tenantCode.value : ''
|
||||
loginConfig.value.username = rememberMe ? form.username : ''
|
||||
|
||||
// 如果有重定向参数,解码并直接跳转到完整路径
|
||||
|
@@ -8,6 +8,9 @@
|
||||
size="large"
|
||||
@submit="handleLogin"
|
||||
>
|
||||
<a-form-item v-if="tenantEnabled" field="tenantCode" hide-label>
|
||||
<a-input v-model="tenantCode" placeholder="请输入租户编码" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item field="email" hide-label>
|
||||
<a-input v-model="form.email" placeholder="请输入邮箱" allow-clear />
|
||||
</a-form-item>
|
||||
@@ -40,16 +43,21 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message } from '@arco-design/web-vue'
|
||||
import { computed } from 'vue'
|
||||
import type { BehaviorCaptchaReq } from '@/apis'
|
||||
// import { type BehaviorCaptchaReq, getEmailCaptcha } from '@/apis'
|
||||
import { useTabsStore, useUserStore } from '@/stores'
|
||||
import { useAppStore, useTabsStore, useUserStore } from '@/stores'
|
||||
import * as Regexp from '@/utils/regexp'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const tenantEnabled = computed(() => appStore.getTenantEnabled())
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
email: '',
|
||||
captcha: '',
|
||||
})
|
||||
const tenantCode = ref()
|
||||
|
||||
const rules: FormInstance['rules'] = {
|
||||
email: [
|
||||
@@ -69,7 +77,7 @@ const handleLogin = async () => {
|
||||
const isInvalid = await formRef.value?.validate()
|
||||
if (isInvalid) return
|
||||
loading.value = true
|
||||
await userStore.emailLogin(form)
|
||||
await userStore.emailLogin(form, tenantCode.value)
|
||||
tabsStore.reset()
|
||||
const { redirect, ...othersQuery } = router.currentRoute.value.query
|
||||
|
||||
|
@@ -8,6 +8,9 @@
|
||||
size="large"
|
||||
@submit="handleLogin"
|
||||
>
|
||||
<a-form-item v-if="tenantEnabled" field="tenantCode" hide-label>
|
||||
<a-input v-model="tenantCode" placeholder="请输入租户编码" allow-clear />
|
||||
</a-form-item>
|
||||
<a-form-item field="phone" hide-label>
|
||||
<a-input v-model="form.phone" placeholder="请输入手机号" :max-length="11" allow-clear />
|
||||
</a-form-item>
|
||||
@@ -41,15 +44,20 @@
|
||||
<script setup lang="ts">
|
||||
import { type FormInstance, Message } from '@arco-design/web-vue'
|
||||
// import type { BehaviorCaptchaReq } from '@/apis'
|
||||
import { computed } from 'vue'
|
||||
import { type BehaviorCaptchaReq, getSmsCaptcha } from '@/apis'
|
||||
import { useTabsStore, useUserStore } from '@/stores'
|
||||
import { useAppStore, useTabsStore, useUserStore } from '@/stores'
|
||||
import * as Regexp from '@/utils/regexp'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const tenantEnabled = computed(() => appStore.getTenantEnabled())
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
phone: '',
|
||||
captcha: '',
|
||||
})
|
||||
const tenantCode = ref()
|
||||
|
||||
const rules: FormInstance['rules'] = {
|
||||
phone: [
|
||||
@@ -69,7 +77,7 @@ const handleLogin = async () => {
|
||||
if (isInvalid) return
|
||||
try {
|
||||
loading.value = true
|
||||
await userStore.phoneLogin(form)
|
||||
await userStore.phoneLogin(form, tenantCode.value)
|
||||
tabsStore.reset()
|
||||
const { redirect, ...othersQuery } = router.currentRoute.value.query
|
||||
|
||||
|
Reference in New Issue
Block a user