chore: 脚手架
This commit is contained in:
@@ -24,6 +24,12 @@ async function generateAccessible(
|
||||
|
||||
// 动态添加到router实例内
|
||||
accessibleRoutes.forEach((route) => {
|
||||
/**
|
||||
* 外链不应该被添加到路由 由menu处理
|
||||
*/
|
||||
if (/^http(s)?:\/\//.test(route.path)) {
|
||||
return;
|
||||
}
|
||||
router.addRoute(route);
|
||||
});
|
||||
|
||||
|
@@ -28,7 +28,13 @@ function useAccess() {
|
||||
*/
|
||||
function hasAccessByCodes(codes: string[]) {
|
||||
const userCodesSet = new Set(accessStore.accessCodes);
|
||||
|
||||
/**
|
||||
* 管理员权限
|
||||
*/
|
||||
if (userCodesSet.has('*:*:*')) {
|
||||
return true;
|
||||
}
|
||||
// 其他 判断是否存在
|
||||
const intersection = codes.filter((item) => userCodesSet.has(item));
|
||||
return intersection.length > 0;
|
||||
}
|
||||
|
@@ -1,11 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { AuthenticationProps, LoginEmits } from './typings';
|
||||
|
||||
import { computed, reactive } from 'vue';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
VbenButton,
|
||||
VbenCheckbox,
|
||||
VbenInput,
|
||||
@@ -15,13 +21,31 @@ import {
|
||||
import Title from './auth-title.vue';
|
||||
import ThirdPartyLogin from './third-party-login.vue';
|
||||
|
||||
interface Props extends AuthenticationProps {}
|
||||
interface Props extends AuthenticationProps {
|
||||
/**
|
||||
* @zh_CN 验证码图片base64
|
||||
*/
|
||||
captchaBase64?: string;
|
||||
/**
|
||||
* 租户信息options
|
||||
*/
|
||||
tenantOptions?: { companyName: string; domain?: string; tenantId: string }[];
|
||||
/**
|
||||
* @zh_CN 是否启用验证码
|
||||
*/
|
||||
useCaptcha?: boolean;
|
||||
/**
|
||||
* @zh_CN 是否启用租户
|
||||
*/
|
||||
useTenant?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationLogin',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
captchaBase64: '',
|
||||
codeLoginPath: '/auth/code-login',
|
||||
forgetPasswordPath: '/auth/forget-password',
|
||||
loading: false,
|
||||
@@ -35,11 +59,16 @@ withDefaults(defineProps<Props>(), {
|
||||
showRememberMe: true,
|
||||
showThirdPartyLogin: true,
|
||||
subTitle: '',
|
||||
tenantOptions: () => [],
|
||||
title: '',
|
||||
usernamePlaceholder: '',
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
/**
|
||||
* 验证码点击
|
||||
*/
|
||||
captchaClick: [];
|
||||
submit: LoginEmits['submit'];
|
||||
}>();
|
||||
|
||||
@@ -47,15 +76,28 @@ const router = useRouter();
|
||||
|
||||
const REMEMBER_ME_KEY = `REMEMBER_ME_USERNAME_${location.hostname}`;
|
||||
|
||||
const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';
|
||||
const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || 'admin';
|
||||
|
||||
const formState = reactive({
|
||||
password: '',
|
||||
code: '',
|
||||
password: 'admin123',
|
||||
rememberMe: !!localUsername,
|
||||
submitted: false,
|
||||
// 默认租户
|
||||
tenantId: '000000',
|
||||
username: localUsername,
|
||||
});
|
||||
|
||||
/**
|
||||
* 默认选中第一项租户
|
||||
*/
|
||||
const stop = watch(props.tenantOptions, (options) => {
|
||||
if (options.length > 0) {
|
||||
formState.tenantId = options[0]!.tenantId;
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
const usernameStatus = computed(() => {
|
||||
return formState.submitted && !formState.username ? 'error' : 'default';
|
||||
});
|
||||
@@ -64,6 +106,10 @@ const passwordStatus = computed(() => {
|
||||
return formState.submitted && !formState.password ? 'error' : 'default';
|
||||
});
|
||||
|
||||
const captchaStatus = computed(() => {
|
||||
return formState.submitted && !formState.code ? 'error' : 'default';
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formState.submitted = true;
|
||||
|
||||
@@ -74,13 +120,21 @@ function handleSubmit() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证码
|
||||
if (props.useCaptcha && captchaStatus.value !== 'default') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
REMEMBER_ME_KEY,
|
||||
formState.rememberMe ? formState.username : '',
|
||||
);
|
||||
|
||||
emit('submit', {
|
||||
code: formState.code,
|
||||
grantType: 'password',
|
||||
password: formState.password,
|
||||
tenantId: formState.tenantId,
|
||||
username: formState.username,
|
||||
});
|
||||
}
|
||||
@@ -88,6 +142,18 @@ function handleSubmit() {
|
||||
function handleGo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置验证码
|
||||
*/
|
||||
function resetCaptcha() {
|
||||
emit('captchaClick');
|
||||
formState.code = '';
|
||||
// todo 获取焦点
|
||||
// VbenInput并没有提供focus方法
|
||||
}
|
||||
|
||||
defineExpose({ resetCaptcha });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -101,6 +167,26 @@ function handleGo(path: string) {
|
||||
</template>
|
||||
</Title>
|
||||
|
||||
<!-- 租户 -->
|
||||
<div v-if="useTenant" class="mb-6">
|
||||
<Select v-model="formState.tenantId">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择公司" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="item in tenantOptions"
|
||||
:key="item.tenantId"
|
||||
:value="item.tenantId"
|
||||
>
|
||||
{{ item.companyName }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<VbenInput
|
||||
v-model="formState.username"
|
||||
:autofocus="false"
|
||||
@@ -123,6 +209,27 @@ function handleGo(path: string) {
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<!-- 图片验证码 -->
|
||||
<div v-if="useCaptcha" class="flex">
|
||||
<div class="flex-1">
|
||||
<VbenInput
|
||||
v-model="formState.code"
|
||||
:error-tip="$t('authentication.captchaTip')"
|
||||
:label="$t('authentication.captcha')"
|
||||
:placeholder="$t('authentication.captcha')"
|
||||
:status="captchaStatus"
|
||||
name="code"
|
||||
required
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
:src="captchaBase64"
|
||||
class="h-[38px] w-[115px] rounded-r-md"
|
||||
@click="emit('captchaClick')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 mt-4 flex justify-between">
|
||||
<div v-if="showRememberMe" class="flex-center">
|
||||
<VbenCheckbox v-model:checked="formState.rememberMe" name="rememberMe">
|
||||
|
@@ -3,6 +3,7 @@ interface AuthenticationProps {
|
||||
* @zh_CN 验证码登录路径
|
||||
*/
|
||||
codeLoginPath?: string;
|
||||
|
||||
/**
|
||||
* @zh_CN 忘记密码路径
|
||||
*/
|
||||
@@ -32,6 +33,7 @@ interface AuthenticationProps {
|
||||
* @zh_CN 是否显示验证码登录
|
||||
*/
|
||||
showCodeLogin?: boolean;
|
||||
|
||||
/**
|
||||
* @zh_CN 是否显示忘记密码
|
||||
*/
|
||||
@@ -66,7 +68,6 @@ interface AuthenticationProps {
|
||||
* @zh_CN 登录框标题
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* @zh_CN 用户名占位符
|
||||
*/
|
||||
@@ -74,8 +75,12 @@ interface AuthenticationProps {
|
||||
}
|
||||
|
||||
interface LoginAndRegisterParams {
|
||||
code?: string;
|
||||
grantType: string;
|
||||
password: string;
|
||||
tenantId: string;
|
||||
username: string;
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
interface LoginCodeParams {
|
||||
|
@@ -15,9 +15,26 @@ export function useAppConfig(
|
||||
? window._VBEN_ADMIN_PRO_APP_CONF_
|
||||
: (env as VbenAdminProAppConfigRaw);
|
||||
|
||||
const { VITE_GLOB_API_URL } = config;
|
||||
const {
|
||||
VITE_GLOB_API_URL,
|
||||
VITE_GLOB_APP_CLIENT_ID,
|
||||
VITE_GLOB_ENABLE_ENCRYPT,
|
||||
VITE_GLOB_RSA_PRIVATE_KEY,
|
||||
VITE_GLOB_RSA_PUBLIC_KEY,
|
||||
VITE_GLOB_WEBSOCKET_ENABLE,
|
||||
} = config;
|
||||
|
||||
return {
|
||||
// 后端地址
|
||||
apiURL: VITE_GLOB_API_URL,
|
||||
// 客户端key
|
||||
clientId: VITE_GLOB_APP_CLIENT_ID,
|
||||
enableEncrypt: VITE_GLOB_ENABLE_ENCRYPT === 'true',
|
||||
// RSA私钥
|
||||
rsaPrivateKey: VITE_GLOB_RSA_PRIVATE_KEY,
|
||||
// RSA公钥
|
||||
rsaPublicKey: VITE_GLOB_RSA_PUBLIC_KEY,
|
||||
// 是否开启websocket
|
||||
websocketEnable: VITE_GLOB_WEBSOCKET_ENABLE === 'true',
|
||||
};
|
||||
}
|
||||
|
@@ -99,7 +99,8 @@ class RequestClient {
|
||||
const authorization = this.makeAuthorization?.(config);
|
||||
if (authorization) {
|
||||
const { token } = authorization.tokenHandler?.() ?? {};
|
||||
config.headers[authorization.key || 'Authorization'] = token;
|
||||
config.headers[authorization.key || 'Authorization'] =
|
||||
`Bearer ${token}`;
|
||||
}
|
||||
|
||||
const requestHeader = this.makeRequestHeaders?.(config);
|
||||
|
@@ -43,13 +43,9 @@ interface RequestClientOptions extends CreateAxiosDefaults {
|
||||
}
|
||||
|
||||
interface HttpResponse<T = any> {
|
||||
/**
|
||||
* 0 表示成功 其他表示失败
|
||||
* 0 means success, others means fail
|
||||
*/
|
||||
code: number;
|
||||
data: T;
|
||||
message: string;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
export type {
|
||||
@@ -60,3 +56,44 @@ export type {
|
||||
RequestClientOptions,
|
||||
RequestContentType,
|
||||
};
|
||||
|
||||
export type ErrorMessageMode = 'message' | 'modal' | 'none' | undefined;
|
||||
export type SuccessMessageMode = ErrorMessageMode;
|
||||
|
||||
/**
|
||||
* 拓展axios的请求配置
|
||||
*/
|
||||
declare module 'axios' {
|
||||
interface AxiosRequestConfig {
|
||||
/** 是否加密请求参数 */
|
||||
encrypt?: boolean;
|
||||
/**
|
||||
* 错误弹窗类型
|
||||
*/
|
||||
errorMessageMode?: ErrorMessageMode;
|
||||
/**
|
||||
* 是否格式化日期
|
||||
*/
|
||||
formatDate?: boolean;
|
||||
/**
|
||||
* 是否返回原生axios响应
|
||||
*/
|
||||
isReturnNativeResponse?: boolean;
|
||||
/**
|
||||
* 是否需要转换响应 即只获取{code, msg, data}中的data
|
||||
*/
|
||||
isTransformResponse?: boolean;
|
||||
/**
|
||||
* param添加到url后
|
||||
*/
|
||||
joinParamsToUrl?: boolean;
|
||||
/**
|
||||
* 加入时间戳
|
||||
*/
|
||||
joinTime?: boolean;
|
||||
/**
|
||||
* 成功弹窗类型
|
||||
*/
|
||||
successMessageMode?: SuccessMessageMode;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user