feat(auth): 实现用户登录和注册功能
- 新增登录和注册接口调用 - 添加通行密钥认证支持 - 实现登录表单和注册表单界面 - 添加状态管理和错误处理 - 更新环境变量配置和代理设置 - 优化登录流程动画和样式布局
This commit is contained in:
parent
0c7aaa848d
commit
846c8ce882
2
.env
2
.env
@ -1 +1 @@
|
|||||||
BASE_URL=https://animo.alina-dace.info/api
|
VITE_BASE_URL=https://animo.alina-dace.info/api
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
BASE_URL=http://localhost:8080/
|
VITE_BASE_URL=http://localhost:5173/api
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
"node": "^20.19.0 || >=22.12.0"
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host --mode development",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
|
|||||||
149
src/App.vue
149
src/App.vue
@ -1,23 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<header :class="{ login: hideLoading }">
|
<header :class="{ login: hideLoading }">
|
||||||
<t-avatar alt="Vue logo" class="logo" :image="avatar" />
|
<div class="login-box">
|
||||||
|
<div class="logo">
|
||||||
|
<img alt="Logo" :src="avatar" style="object-fit: cover"/>
|
||||||
|
</div>
|
||||||
|
<div class="login-info">
|
||||||
|
<transition name="slide-up" mode="out-in">
|
||||||
|
<div v-if="!login">
|
||||||
|
<LoginProcess @login="handleLogin" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!weCome">
|
||||||
|
<transition name="slide-up" mode="out-in">
|
||||||
|
<div v-if="login">
|
||||||
|
<div class="loading" />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<div v-else></div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<transition name="slide-up" mode="out-in">
|
<transition name="slide-up" mode="out-in">
|
||||||
<span v-if="showTitle" class="title">你好,{{ name }}</span>
|
<span v-if="showTitle" class="title">你好,{{ name }}</span>
|
||||||
</transition>
|
</transition>
|
||||||
<div class="login-info">
|
<transition name="slide-up" mode="out-in">
|
||||||
<transition name="slide-up" mode="out-in">
|
<div v-if="showTitle" class="operating-area">
|
||||||
<div v-if="!login">
|
<refresh-icon :fill-color='"transparent"' :stroke-color='"currentColor"' :stroke-width="2"/>
|
||||||
<LoginProcess @login="handleLogin"/>
|
</div>
|
||||||
</div>
|
</transition>
|
||||||
<div v-else-if="!weCome">
|
|
||||||
<transition name="slide-up" mode="out-in">
|
|
||||||
<div v-if="login">
|
|
||||||
<div class="loading" />
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<div class="header-placeholder" />
|
<div class="header-placeholder" />
|
||||||
|
|
||||||
@ -33,8 +43,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import avatar from '@/assets/logo.svg'
|
import avatar from '@/assets/avater.jpg'
|
||||||
import LoginProcess from '@/components/login/LoginProcess.vue'
|
import LoginProcess from '@/components/login/LoginProcess.vue'
|
||||||
|
import { RefreshIcon } from 'tdesign-icons-vue-next'
|
||||||
|
|
||||||
const login = ref(false)
|
const login = ref(false)
|
||||||
const weCome = ref(false)
|
const weCome = ref(false)
|
||||||
@ -48,7 +59,7 @@ if (!navigator.credentials) {
|
|||||||
passkey.value = false
|
passkey.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin(){
|
function handleLogin() {
|
||||||
// navigator.credentials.create({
|
// navigator.credentials.create({
|
||||||
// publicKey: {
|
// publicKey: {
|
||||||
// challenge: new Uint8Array(32),
|
// challenge: new Uint8Array(32),
|
||||||
@ -76,7 +87,7 @@ function handleLogin(){
|
|||||||
// }).then(resp => {
|
// }).then(resp => {
|
||||||
// login.value = true
|
// login.value = true
|
||||||
// })
|
// })
|
||||||
login.value = true
|
login.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 登录动画流程
|
// 登录动画流程
|
||||||
@ -108,56 +119,78 @@ header {
|
|||||||
box-shadow: var(--td-shadow-4);
|
box-shadow: var(--td-shadow-4);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: start;
|
||||||
padding: 2vh;
|
padding: 2vh;
|
||||||
transition: all 0.5s ease-out;
|
transition: all 0.5s ease-in-out;
|
||||||
.logo {
|
.login-box{
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
flex-direction: column;
|
||||||
margin: 0 calc(50vw - 62.5px);
|
align-items: center;
|
||||||
transition: all 0.3s ease-out;
|
height: 100%;
|
||||||
position: relative;
|
width: 100%;
|
||||||
:deep(.t-avatar) {
|
.logo {
|
||||||
background-color: transparent;
|
flex: 4;
|
||||||
}
|
display: flex;
|
||||||
}
|
align-items: flex-end;
|
||||||
.login-info {
|
margin: 0 calc(50vw - 62.5px);
|
||||||
flex: 1;
|
transition: all 0.3s ease-in-out;
|
||||||
margin: 1rem;
|
position: relative;
|
||||||
line-height: 2rem;
|
img {
|
||||||
height: 2rem;
|
width: 12vh;
|
||||||
.loading {
|
height: 12vh;
|
||||||
height: 2rem;
|
object-fit: cover;
|
||||||
width: 2rem;
|
transition: all 0.3s ease-in-out;
|
||||||
border: var(--td-brand-color) 2px solid;
|
border-radius: 50%;
|
||||||
border-top-color: transparent;
|
|
||||||
border-radius: 50%;
|
|
||||||
@keyframes circle {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
animation: circle 1s linear infinite;
|
|
||||||
}
|
}
|
||||||
}
|
.login-info {
|
||||||
.title {
|
flex: 6;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
.loading {
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
border: var(--td-brand-color) 2px solid;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
@keyframes circle {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
animation: circle 1s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
margin: 1rem;
|
||||||
|
flex: 98;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.login {
|
&.login {
|
||||||
flex-direction: row;
|
|
||||||
justify-content: start;
|
|
||||||
height: 10vh;
|
height: 10vh;
|
||||||
|
.login-box {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
.logo {
|
.logo {
|
||||||
flex: none;
|
flex: 1;
|
||||||
height: 6vh;
|
|
||||||
width: 6vh;
|
|
||||||
margin: 0 0;
|
margin: 0 0;
|
||||||
|
> img{
|
||||||
|
height: 6vh;
|
||||||
|
width: 6vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.title{
|
||||||
|
flex: 98;
|
||||||
|
}
|
||||||
|
.operating-area{
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+ .header-placeholder {
|
+ .header-placeholder {
|
||||||
|
|||||||
43
src/api/auth.ts
Normal file
43
src/api/auth.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import request from '@/request/axios.ts'
|
||||||
|
|
||||||
|
export function login(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/auth/login',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function register(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/auth/register',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registration_options(){
|
||||||
|
return request({
|
||||||
|
url: '/passkey/registration/options',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function registration(credential: any){
|
||||||
|
return request({
|
||||||
|
url: '/passkey/registration',
|
||||||
|
method: 'post',
|
||||||
|
data: credential
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function assertion_options(){
|
||||||
|
return request({
|
||||||
|
url: '/passkey/assertion/options',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function assertion(credential: any){
|
||||||
|
return request({
|
||||||
|
url: '/passkey/assertion',
|
||||||
|
method: 'post',
|
||||||
|
data: credential
|
||||||
|
})
|
||||||
|
}
|
||||||
BIN
src/assets/avater.jpg
Normal file
BIN
src/assets/avater.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 KiB |
@ -1,32 +1,109 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<t-input v-model="username" placeholder="请输入用户名"></t-input>
|
<t-form labelWidth="0">
|
||||||
<t-input v-model="password" placeholder="请输入密码"></t-input>
|
<t-form-item>
|
||||||
<t-link @click="handleRegister">没有账号?去注册</t-link>
|
<t-input v-model="username" placeholder="请输入用户名"></t-input>
|
||||||
<t-button @click="handleLogin">登录</t-button>
|
</t-form-item>
|
||||||
<t-button @click="handleUsePasskey">取消</t-button>
|
<t-form-item>
|
||||||
|
<t-input v-model="password" placeholder="请输入密码"></t-input>
|
||||||
|
</t-form-item>
|
||||||
|
<t-space class="login-button-box" direction="vertical" size="small">
|
||||||
|
<t-button class="login-button" theme="primary" :loading="loading" @click="handleLogin"
|
||||||
|
>登录</t-button
|
||||||
|
>
|
||||||
|
<t-button
|
||||||
|
class="login-button"
|
||||||
|
theme="default"
|
||||||
|
:disabled="!passkey"
|
||||||
|
@click="handleUsePasskey"
|
||||||
|
>使用通行密钥</t-button
|
||||||
|
>
|
||||||
|
<t-link @click="handleRegister">没有账号?去注册</t-link>
|
||||||
|
</t-space>
|
||||||
|
</t-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, toRefs } from 'vue'
|
import { reactive, ref, toRefs } from 'vue'
|
||||||
|
import { assertion, assertion_options, login } from '@/api/auth.ts'
|
||||||
|
|
||||||
|
const emits = defineEmits(['complete', 'register'])
|
||||||
const { username, password } = toRefs(
|
const { username, password } = toRefs(
|
||||||
reactive({
|
reactive({
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const emits = defineEmits(['complete', 'register'])
|
|
||||||
|
const loading = ref(false)
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
emits('complete')
|
loading.value = true
|
||||||
|
login({ user: username.value, password: password.value })
|
||||||
|
.then((x: any) => {
|
||||||
|
if (x.code === 200) {
|
||||||
|
emits('complete')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e: any) => {
|
||||||
|
console.error(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const passkey = ref(true)
|
||||||
|
|
||||||
|
if (!navigator.credentials) {
|
||||||
|
passkey.value = false
|
||||||
|
}
|
||||||
|
function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||||
|
// 处理base64url格式:替换字符并添加填充
|
||||||
|
let normalizedBase64 = base64.replace(/-/g, '+').replace(/_/g, '/')
|
||||||
|
// 添加必要的填充
|
||||||
|
while (normalizedBase64.length % 4 !== 0) {
|
||||||
|
normalizedBase64 += '='
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const binaryString = atob(normalizedBase64)
|
||||||
|
const bytes = new Uint8Array(binaryString.length)
|
||||||
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return bytes.buffer
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Base64 decoding failed:', e, 'Input was:', base64)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function handleUsePasskey() {
|
function handleUsePasskey() {
|
||||||
emits('complete')
|
assertion_options().then((x: any) => {
|
||||||
|
const parse = JSON.parse(x.data)
|
||||||
|
parse.publicKey.challenge = base64ToArrayBuffer(parse.publicKey?.challenge)
|
||||||
|
console.log(parse)
|
||||||
|
navigator.credentials.get(parse).then((x: any) => {
|
||||||
|
assertion(x).then((x: any) => {
|
||||||
|
if (x.code === 200) {
|
||||||
|
emits('complete')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// emits('complete')
|
||||||
}
|
}
|
||||||
function handleRegister() {
|
function handleRegister() {
|
||||||
emits('register')
|
emits('register')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.login-button-box {
|
||||||
|
width: 100%;
|
||||||
|
.login-button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<transition name="slide-up">
|
<transition name="slide-up" mode="out-in">
|
||||||
<Login v-if="step === 'login'" @complete="handleLogin" @register="handleRegister"></Login>
|
<Login v-if="step === 'login'" @complete="handleLogin" @register="handleToRegister"></Login>
|
||||||
<Register v-else-if="step === 'register'" @complete="handleLogin"></Register>
|
<Register v-else-if="step === 'register'" @complete="handleLogin" @login="handleToLogin"></Register>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -18,9 +18,12 @@ const step = ref('login')
|
|||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
emits('login')
|
emits('login')
|
||||||
}
|
}
|
||||||
function handleRegister() {
|
function handleToRegister() {
|
||||||
step.value = 'register'
|
step.value = 'register'
|
||||||
}
|
}
|
||||||
|
function handleToLogin() {
|
||||||
|
step.value = 'login'
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,57 @@
|
|||||||
<template></template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<t-form labelWidth="0">
|
||||||
|
<t-form-item>
|
||||||
|
<t-input v-model="username" placeholder="请输入用户名"></t-input>
|
||||||
|
</t-form-item>
|
||||||
|
<t-form-item>
|
||||||
|
<t-input v-model="password" placeholder="请输入密码"></t-input>
|
||||||
|
</t-form-item>
|
||||||
|
<t-space class="login-button-box" direction="vertical" size="small">
|
||||||
|
<t-button class="login-button" theme="primary" :loading="loading" @click="handleRegister"
|
||||||
|
>注册</t-button
|
||||||
|
>
|
||||||
|
<t-link @click="handleLogin">已有帐号?去登陆</t-link>
|
||||||
|
</t-space>
|
||||||
|
</t-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, toRefs } from 'vue'
|
||||||
|
import { register } from '@/api/auth.ts'
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
const { username, password } = toRefs(
|
||||||
|
reactive({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const loading = ref(false)
|
||||||
|
const emits = defineEmits(['complete', 'login'])
|
||||||
|
|
||||||
|
function handleRegister() {
|
||||||
|
loading.value = true
|
||||||
|
register({ user: username.value, password: password.value }).then((x: any) => {
|
||||||
|
if (x.code === 200) {
|
||||||
|
emits('complete')
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogin() {
|
||||||
|
emits('login')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.login-button-box {
|
||||||
|
width: 100%;
|
||||||
|
.login-button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
90
src/enums/RespEnum.ts
Normal file
90
src/enums/RespEnum.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
export enum HttpStatus {
|
||||||
|
/**
|
||||||
|
* 操作成功
|
||||||
|
*/
|
||||||
|
SUCCESS = 200,
|
||||||
|
/**
|
||||||
|
* 对象创建成功
|
||||||
|
*/
|
||||||
|
CREATED = 201,
|
||||||
|
/**
|
||||||
|
* 请求已经被接受
|
||||||
|
*/
|
||||||
|
ACCEPTED = 202,
|
||||||
|
/**
|
||||||
|
* 操作已经执行成功,但是没有返回数据
|
||||||
|
*/
|
||||||
|
NO_CONTENT = 204,
|
||||||
|
/**
|
||||||
|
* 资源已经被移除
|
||||||
|
*/
|
||||||
|
MOVED_PERM = 301,
|
||||||
|
/**
|
||||||
|
* 重定向
|
||||||
|
*/
|
||||||
|
SEE_OTHER = 303,
|
||||||
|
/**
|
||||||
|
* 资源没有被修改
|
||||||
|
*/
|
||||||
|
NOT_MODIFIED = 304,
|
||||||
|
/**
|
||||||
|
* 参数列表错误(缺少,格式不匹配)
|
||||||
|
*/
|
||||||
|
PARAM_ERROR = 400,
|
||||||
|
/**
|
||||||
|
* 未授权
|
||||||
|
*/
|
||||||
|
UNAUTHORIZED = 401,
|
||||||
|
/**
|
||||||
|
* 访问受限,授权过期
|
||||||
|
*/
|
||||||
|
FORBIDDEN = 403,
|
||||||
|
/**
|
||||||
|
* 资源,服务未找到
|
||||||
|
*/
|
||||||
|
NOT_FOUND = 404,
|
||||||
|
/**
|
||||||
|
* 不允许的http方法
|
||||||
|
*/
|
||||||
|
BAD_METHOD = 405,
|
||||||
|
/**
|
||||||
|
* 资源冲突,或者资源被锁
|
||||||
|
*/
|
||||||
|
CONFLICT = 409,
|
||||||
|
/**
|
||||||
|
* 不支持的数据,媒体类型
|
||||||
|
*/
|
||||||
|
UNSUPPORTED_TYPE = 415,
|
||||||
|
/**
|
||||||
|
* 系统内部错误
|
||||||
|
*/
|
||||||
|
SERVER_ERROR = 500,
|
||||||
|
/**
|
||||||
|
* 接口未实现
|
||||||
|
*/
|
||||||
|
NOT_IMPLEMENTED = 501,
|
||||||
|
/**
|
||||||
|
* 服务不可用,过载或者维护
|
||||||
|
*/
|
||||||
|
BAD_GATEWAY = 502,
|
||||||
|
/**
|
||||||
|
* 网关超时
|
||||||
|
*/
|
||||||
|
GATEWAY_TIMEOUT = 504,
|
||||||
|
/**
|
||||||
|
* 未知错误
|
||||||
|
*/
|
||||||
|
UNKNOWN_ERROR = 520,
|
||||||
|
/**
|
||||||
|
* 服务未知错误
|
||||||
|
*/
|
||||||
|
SERVICE_ERROR = 521,
|
||||||
|
/**
|
||||||
|
* 数据库未知错误
|
||||||
|
*/
|
||||||
|
DATABASE_ERROR = 522,
|
||||||
|
/**
|
||||||
|
* 系统警告消息
|
||||||
|
*/
|
||||||
|
WARN = 601
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import './assets/main.css'
|
import './assets/main.css'
|
||||||
|
// import 'tdesign-mobile-vue/es/style/index.css'
|
||||||
import 'tdesign-vue-next/es/style/index.css'
|
import 'tdesign-vue-next/es/style/index.css'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|||||||
@ -1,15 +1,47 @@
|
|||||||
import axios, { type InternalAxiosRequestConfig } from 'axios'
|
import axios, { type AxiosResponse, type InternalAxiosRequestConfig } from 'axios'
|
||||||
|
import { HttpStatus } from '@/enums/RespEnum.ts'
|
||||||
|
import { MessagePlugin } from 'tdesign-vue-next'
|
||||||
|
|
||||||
|
const errorCode: any = {
|
||||||
|
'401': '认证失败,无法访问系统资源',
|
||||||
|
'403': '当前操作没有权限',
|
||||||
|
'404': '访问资源不存在',
|
||||||
|
default: '系统未知错误,请反馈给管理员'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
|
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
|
||||||
|
|
||||||
const request = axios.create({
|
const request = axios.create({
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
baseURL: import.meta.env.BASE_URL
|
baseURL: import.meta.env.VITE_BASE_URL
|
||||||
})
|
})
|
||||||
|
|
||||||
request.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
// request.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||||
|
//
|
||||||
return Promise.reject()
|
// return Promise.reject()
|
||||||
|
// })
|
||||||
|
request.interceptors.response.use((res: AxiosResponse) => {
|
||||||
|
// 未设置状态码则默认成功状态
|
||||||
|
const code = res.data.code || HttpStatus.SUCCESS;
|
||||||
|
// 获取错误信息
|
||||||
|
const msg = errorCode[code] || res.data.message || errorCode['default'];
|
||||||
|
// 二进制数据则直接返回
|
||||||
|
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
if (code === HttpStatus.SERVER_ERROR) {
|
||||||
|
MessagePlugin.error(msg);
|
||||||
|
return Promise.reject(new Error(msg));
|
||||||
|
} else if (code === HttpStatus.WARN) {
|
||||||
|
MessagePlugin.warning(msg);
|
||||||
|
return Promise.reject(new Error(msg));
|
||||||
|
} else if (code !== HttpStatus.SUCCESS) {
|
||||||
|
MessagePlugin.error(msg);
|
||||||
|
return Promise.reject('error');
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(res.data);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default request
|
export default request
|
||||||
|
|||||||
@ -28,4 +28,16 @@ export default defineConfig({
|
|||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
cors: true,
|
||||||
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:8080',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path: string) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user