TS项目实战四:ts+vue3+elementplus实现登录注册功能

news/2024/5/20 4:36:19 标签: typescript, ts, 前端框架, 前端, js, 登录注册, elementplus

  使用vue3+elementplus+vite+pinia实现用户登录、注册相关界面及对应业务流程的开发,对接express后端服务,调用对应接口,实现完整的用户登录注册功能。
源码下载:点击下载
讲解视频:

TS实战项目三十:Vue3项目创建

B站视频:

TS实战项目三十:Vue3项目创建

西瓜视频:
https://www.ixigua.com/7340090758781174306

一、界面预览

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、相关知识点

  1. tsc编译
  2. tsconfig.json配置项
  3. 模块定义及导入导出
  4. 类定义
  5. 参数属性
  6. 存取器
  7. 继承
  8. 抽象类
  9. 抽象方法
  10. 接口
  11. 枚举
  12. 静态变量
  13. async/await
  14. vite
  15. vue3
  16. elementplus
  17. pinia
  18. router
  19. axios

三、功能规划

  使用vue3组合式开发,使用elementplus框架搭建界面,使用pinia实现数据状态管理,使用axios实现用户请求,搭建完整的用户登录、用户注册等相关界面,并实现相应的业务逻辑处理。

四、项目创建

  1. 安装npm create vue@latest:
    请添加图片描述
  2. 安装elementplus
    请添加图片描述
  3. 安装axios:
    请添加图片描述

请添加图片描述
请添加图片描述

  1. 安装pinia-plugin-persistedstate:
    请添加图片描述

  2. 项目目录结构:
    请添加图片描述

五、代码实现

  1. package.json
typescript">{
  "name": "demo5",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "run-p type-check \"build-only {@}\" --",
    "preview": "vite preview",
    "test:unit": "vitest",
    "build-only": "vite build",
    "type-check": "vue-tsc --build --force",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
    "format": "prettier --write src/"
  },
  "dependencies": {
    "@types/axios": "^0.14.0",
    "axios": "^1.6.7",
    "element-plus": "^2.5.6",
    "pinia": "^2.1.7",
    "pinia-plugin-persistedstate": "^3.2.1",
    "vue": "^3.4.15",
    "vue-router": "^4.2.5"
  },
  "devDependencies": {
    "@rushstack/eslint-patch": "^1.3.3",
    "@tsconfig/node20": "^20.1.2",
    "@types/jsdom": "^21.1.6",
    "@types/node": "^20.11.10",
    "@vitejs/plugin-vue": "^5.0.3",
    "@vitejs/plugin-vue-jsx": "^3.1.0",
    "@vue/eslint-config-prettier": "^8.0.0",
    "@vue/eslint-config-typescript": "^12.0.0",
    "@vue/test-utils": "^2.4.4",
    "@vue/tsconfig": "^0.5.1",
    "eslint": "^8.49.0",
    "eslint-plugin-vue": "^9.17.0",
    "jsdom": "^24.0.0",
    "npm-run-all2": "^6.1.1",
    "prettier": "^3.0.3",
    "typescript": "~5.3.0",
    "vite": "^5.0.11",
    "vitest": "^1.2.2",
    "vue-tsc": "^1.8.27"
  }
}
  1. vite.config.ts
typescript">import {
  fileURLToPath,
  URL,
} from 'node:url';

import { defineConfig } from 'vite';

import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueJsx(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    proxy: {
      // with options: http://localhost:5173/api/bar-> http://jsonplaceholder.typicode.com/bar
      '/api': {
        target: 'http://localhost:3000/',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      }
    },
  }
})
  1. index.html
typescript"><!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/favicon.ico">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>君君军管理系统</title>
</head>

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.ts"></script>
</body>

</html>
  1. /src/main.ts
typescript">import './assets/main.css';
import 'element-plus/dist/index.css';

import { createApp } from 'vue';

import ElementPlus from 'element-plus';

import * as ElementPlusIconsVue from '@element-plus/icons-vue';

import App from './App.vue';
import router from './router';
import pinia from './stores';

const app = createApp(App)
//注册elementplus
app.use(ElementPlus)
//注册elementplus的图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}
//注册pinia
app.use(pinia);
app.use(router)

app.mount('#app')
  1. /src/App.vue
typescript"><script setup lang="ts">
import {
  RouterLink,
  RouterView,
} from 'vue-router';
</script>

<template>
  <RouterView />
</template>

<style scoped></style>
  1. /src/views/index.vue
typescript"><script setup lang="ts">
import { reactive } from 'vue';

import {
    ElLoading,
    ElMessage,
} from 'element-plus';

import api from '@/utils/api';

//加载到的用户数据
const userData = reactive({});
/**
 * 加载用户信息
 */
const loadUserInfo = () => {
    //显示加载动画
    const loading = ElLoading.service({
        lock: true,
        text: '正常处理',
        background: 'rgba(0, 0, 0, 0.7)',
    });
    api.get('/api/get?id=1').then((response: AxiosResponse<any>) => {
        if (response.status != 200 || !response.data || response.data.code != 200) {
            if (response.data) {
                ElMessage.error('加载失败:' + response.data.msg)
            } else {
                ElMessage.error('加载失败!')
            }
            return;
        }
        let data = response.data.data;
        Object.assign(userData, data);
    }).catch((error: AxiosError<any>) => {
        ElMessage.error('操作异常:' + error.message)
    }).finally(() => {
        loading.close();
    });
}
</script>
<template>
    <div style="padding:20px">
        <el-result icon="success" title="登陆成功" sub-title="已登录,这是系统注册">
            <template #extra>
                <el-button type="primary" @click="loadUserInfo">获取用户</el-button>
            </template>
        </el-result>
        <div>
            用户信息:{{ userData }}
        </div>
    </div>
</template>
<style scoped></style>
  1. /src/views/register/register.vue
typescript"><script setup lang="ts">
import {
  reactive,
  ref,
} from 'vue';

//自组件的引入
import account from './components/account.vue';
import info from './components/info.vue';
import success from './components/success.vue';

//步骤条的进度
const step = ref(1);
//账号信息
const accountInfo = reactive({
    account: '',
    smscode: '',
    captcha: ''
});
//转到信息完善界面
const toInfo = (data) => {
    Object.assign(accountInfo, data);
    step.value = 2;
}
</script>

<template>
    <div class="registerPage">
        <el-card class="registerPanel">
            <!-- 步骤条 -->
            <el-steps class="registerStep" :active="step" align-center>
                <el-step title="账号验证" description="" />
                <el-step title="信息完善" description="" />
                <el-step title="注册完成" description="" />
            </el-steps>
            <!-- 每一步的内容 -->
            <div class="registerContent">
                <!-- 账号校验 -->
                <account v-if="step === 1" @next="toInfo"></account>
                <!-- 信息完善 -->
                <info v-else-if="step === 2" @pre="step = 1" @next="step = 3" :accountInfo="accountInfo"></info>
                <!-- 注册完成 -->
                <success v-else></success>
            </div>
        </el-card>
        <div class="footer">@Copyright 君君军通用管理系统 备案信息:陕432432432</div>
    </div>
</template>

<style scoped>
.registerPage {
    display: flex;
    justify-content: center;
    justify-items: center;
    align-items: center;
    height: 100%;
}

.registerPage .registerPanel {
    width: 80%;
    height: 60%;
    max-width: 1024px;
    max-height: 800px;
    margin: 0 auto;
}

.registerPage .registerPanel .registerStep {
    width: 80%;
    margin: 0 auto;
    margin-top: 40px;
}

.registerPage .registerPanel .registerContent {
    margin-top: 40px;
}

.footer {
    position: fixed;
    bottom: 0px;
    line-height: 40px;
    text-align: center;
    font-size: 14px;
    color: #999;
}
</style>
  1. /src/views/register/components/account.vue
typescript"><script setup lang="ts">
/**
 * 账号验证流程:
 * 
 * 1.获取到输入的电话号码及验证码
 * 
 * 2.调用获取短信验证码接口
 * 
 * 3.获取短信验证码之后,获取按钮需要暂时禁用
 * 
 * 4.获取到验证码之后,下一步的时候校验验证码是否合法,如果合法则进入下一步
 * 
 */
import {
  defineEmits,
  onUnmounted,
  reactive,
  ref,
} from 'vue';

import type {
  AxiosError,
  AxiosResponse,
} from 'axios';
import type {
  FormInstance,
  FormRules,
} from 'element-plus';
import {
  ElLoading,
  ElMessage,
} from 'element-plus';
import { useRouter } from 'vue-router';

import api from '@/utils/api';

// 路由控制七
const router = useRouter();

//自定义事件
const emit = defineEmits(['next']);
// 表单数据
const account = reactive({
    account: '',
    captcha: '',
    smscode: ''
});
//表单的实例
const formRef = ref();
//数据校验
const rules = reactive<FormRules<typeof account>>({
    account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
    captcha: [{ required: true, message: '请输入图像验证码', trigger: 'blur' }],
    smscode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }],
})
//验证码
const captchaSrc = ref('/api/register/captcha');
//验证码的刷新
const refreshCaptcha = () => {
    captchaSrc.value = '/api/register/captcha?t_=' + new Date().getTime()
}
//定义定时器
let timer: number | undefined;
//验证码倒计时
let smscodeTime = ref(0);
//获取短信验证码
const getSmscode = () => {
    if (!account.account || !account.captcha) {
        ElMessage.error('请输入电话号码及验证码');
        return;
    }
    //显示加载动画
    const loading = ElLoading.service({
        lock: true,
        text: '正在处理',
        background: 'rgba(0, 0, 0, 0.7)',
    });
    api.post('/api/register/getSmscode', {
        phone: account.account,
        captcha: account.captcha
    }).then((response: AxiosResponse<any>) => {
        if (response.status != 200 || !response.data || response.data.code != 200) {
            if (response.data) {
                ElMessage.error('获取失败:' + response.data.msg)
            } else {
                ElMessage.error('获取失败!')
            }
            return;
        }
        //提示短信验证码已发送
        ElMessage({
            message: '短信验证码已发送,3分钟之内有效!',
            type: 'success',
        })
        //限制获取验证码按钮一分钟之内不能重复点击
        smscodeTime.value = 60;
        timer = setInterval(() => {
            smscodeTime.value--;
            if (smscodeTime.value <= 0) {
                smscodeTime.value = 0;
                clearInterval(timer);
                timer = null;
            }
        }, 1000);
    }).catch((error: AxiosError<any>) => {
        if (error.response && error.response.data) {
            ElMessage.error(error.response.data.msg)
            return;
        }
        ElMessage.error('操作异常:' + error.message)
    }).finally(() => {
        loading.close();
    });
}
//提交表单
const submitForm = (formEl: FormInstance | undefined) => {
    if (!formEl) return;
    formEl.validate((isValid: boolean) => {
        if (!isValid) {
            return;
        }
        //显示加载动画
        const loading = ElLoading.service({
            lock: true,
            text: '正在处理',
            background: 'rgba(0, 0, 0, 0.7)',
        });
        //请求接口,进行登陆
        api.post('/api/register/validateSmscode', {
            smscode: account.smscode
        }).then((response: AxiosResponse<any>) => {
            if (response.status != 200 || !response.data || response.data.code != 200) {
                if (response.data) {
                    ElMessage.error('处理失败:' + response.data.msg)
                } else {
                    ElMessage.error('处理失败!')
                }
                return;
            }
            emit('next', {
                account: account.account,
                smscode: account.smscode,
                captcha: account.captcha
            });
        }).catch((error: AxiosError<any>) => {
            if (error.response && error.response.data) {
                ElMessage.error(error.response.data.msg)
                return;
            }
            ElMessage.error('操作异常:' + error.message)
        }).finally(() => {
            loading.close();
        });
    })
}
//取消
const cancel = () => {
    router.push('/login');
}
onUnmounted(() => {
    //清除定时器
    timer && clearInterval(timer);
})
</script>

<template>
    <!-- 电话号码校验 -->
    <el-form ref="formRef" :model="account" :rules="rules" size="large" label-width="120" class="accountForm"
        status-icon>
        <el-form-item label="电话号码:" prop="account">
            <el-input v-model="account.account" placeholder="请输入电话号码" suffix-icon="UserFilled" />
        </el-form-item>
        <el-form-item label="图像验证码:" prop="captcha">
            <div class="flex">
                <div class="flexItem">
                    <el-input v-model="account.captcha" placeholder="请输入图形验证码" />
                </div>
                <div class="captchaPanel">
                    <img :src="captchaSrc" @click="refreshCaptcha">
                </div>
            </div>
        </el-form-item>
        <el-form-item label="短信验证码:" prop="smscode">
            <div class="flex">
                <div class="flexItem">
                    <el-input v-model="account.smscode" placeholder="请输入短信验证码" suffix-icon="Message" />
                </div>
                <div class="smscodePanel">
                    <el-button @click="getSmscode" :disabled="smscodeTime > 0">{{ smscodeTime > 0 ? smscodeTime
        +
        '秒之后再试' : '获取验证码' }}</el-button>
                </div>
            </div>
        </el-form-item>
        <el-form-item label="">
            <div class="formBtns">
                <el-button type="danger" @click="cancel">取消</el-button>
                <el-button type="primary" @click="submitForm(formRef)">
                    下一步
                </el-button>
            </div>
        </el-form-item>
    </el-form>
</template>

<style scoped>
.accountForm {
    width: 50%;
    margin: 0 auto;
}

.accountForm .formBtns {
    margin: 0 auto;
}

.accountForm .formBtns .el-button {
    width: 150px;
}

.flex {
    display: flex;
    width: 100%;
}

.flex .flexItem {
    flex: 1
}

.captchaPanel {
    width: 110px;
    padding-left: 10px;
}

.captchaPanel img {
    width: 100px;
    cursor: pointer;
}

.smscodePanel {
    width: 110px;
    padding-left: 10px;
}

.smscodePanel .el-button {
    width: 100px;
}
</style>
  1. /src/views/register/components/info.vue
typescript"><script setup lang="ts">
import {
  reactive,
  ref,
} from 'vue';

import type {
  AxiosError,
  AxiosResponse,
} from 'axios';
import type {
  FormInstance,
  FormRules,
} from 'element-plus';
import {
  ElLoading,
  ElMessage,
} from 'element-plus';

import api from '@/utils/api';

//外部的参数
const prop = defineProps({
    accountInfo: {
        type: Object
    }
});

//自定义事件
const emit = defineEmits(['pre', 'next']);

//表单数据
const info = reactive({
    account: '',
    sex: '1',
    password: '',
    name: ''
})
//表单的实例
const formRef = ref();
//数据校验
const rules = reactive<FormRules<typeof info>>({
    name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
    account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
    sex: [{ required: true, message: '请选择性别', trigger: 'blur' }],
    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
})
//提交表单
const submitForm = (formEl: FormInstance | undefined) => {
    if (!formEl) return;
    formEl.validate((isValid: boolean) => {
        if (!isValid) {
            return;
        }
        //显示加载动画
        const loading = ElLoading.service({
            lock: true,
            text: '正在处理',
            background: 'rgba(0, 0, 0, 0.7)',
        });
        //请求接口,进行登陆
        api.post('/api/register', {
            account: info.account,
            sex: info.sex,
            password: info.password,
            phone: prop.accountInfo.account,
            captcha: prop.accountInfo.captcha,
            name: info.name
        }).then((response: AxiosResponse<any>) => {
            if (response.status != 200 || !response.data || response.data.code != 200) {
                if (response.data) {
                    ElMessage.error('处理失败:' + response.data.msg)
                } else {
                    ElMessage.error('处理失败!')
                }
                return;
            }
            emit('next');
        }).catch((error: AxiosError<any>) => {
            if (error.response && error.response.data) {
                ElMessage.error(error.response.data.msg)
                return;
            }
            ElMessage.error('操作异常:' + error.message)
        }).finally(() => {
            loading.close();
        });
    })
    return null;
}
</script>

<template>
    <el-form ref="formRef" :model="info" :rules="rules" size="large" label-width="120" class="infoForm" status-icon>
        <el-form-item label="账号:" prop="account">
            <el-input v-model="info.account" placeholder="请输入账号" suffix-icon="UserFilled" />
        </el-form-item>
        <el-form-item label="姓名:" prop="name">
            <el-input v-model="info.name" placeholder="请输入姓名" suffix-icon="UserFilled" />
        </el-form-item>
        <el-form-item label="密码:" prop="password">
            <el-input v-model="info.password" type="password" placeholder="请输入密码" suffix-icon="Lock" />
        </el-form-item>
        <el-form-item label="性别:" prop="sex">
            <el-radio-group v-model="info.sex">
                <el-radio label="1" value="1" size="large"></el-radio>
                <el-radio label="2" value="2" size="large"></el-radio>
                <el-radio label="3" value="3" size="large">保密</el-radio>
            </el-radio-group>
        </el-form-item>
        <el-form-item label="">
            <div class="formBtns">
                <el-button type="danger" @click="emit('pre')">上一步</el-button>
                <el-button type="primary" @click="submitForm(formRef)">
                    注册
                </el-button>
            </div>
        </el-form-item>
    </el-form>
</template>

<style scoped>
.infoForm {
    width: 50%;
    margin: 0 auto;
}

.infoForm .formBtns {
    margin: 0 auto;
}

.infoForm .formBtns .el-button {
    width: 150px;
}

.flex {
    display: flex;
    width: 100%;
}

.flex .flexItem {
    flex: 1
}
</style>
  1. /src/views/register/components/success.vue
typescript"><script setup lang="ts">
import { useRouter } from 'vue-router';

//路由状态
const router = useRouter();
</script>
<template>
    <el-result icon="success" title="注册成功" sub-title="恭喜,注册成功">
        <template #extra>
            <el-button type="primary" @click="router.push('/login')">立即登陆</el-button>
        </template>
    </el-result>
</template>
<style scoped></style>
  1. /src/views/login/login.vue
typescript"><script setup lang="ts">
import {
  reactive,
  ref,
} from 'vue';

import type {
  AxiosError,
  AxiosResponse,
} from 'axios';
import type {
  FormInstance,
  FormRules,
} from 'element-plus';
import {
  ElLoading,
  ElMessage,
} from 'element-plus';
import { useRouter } from 'vue-router';

import { useLoginStore } from '@/stores/login';
import api from '@/utils/api';

//登陆状态存储
const loginStore = useLoginStore();
//路由状态
const router = useRouter();

/**
 * 1.写登陆的界面组件实现
 * 
 * 2.界面逻辑实现
 * 
 * 3.登陆状态检测
 * 
 */
//登陆表单数据
const loginForm = reactive({
    account: '',
    password: '',
    captcha: ''
})
//校验规则
const rules = reactive<FormRules<typeof loginForm>>({
    account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
    password: [{
        validator: (rule: any, value: any, callback: any) => {
            if (value === '') {
                callback(new Error('密码不能为空'))
            } else if (loginForm.password.length < 6) {
                callback(new Error("密码不能小鱼6位3字符"))
            } else {
                callback()
            }
        }, trigger: 'blur'
    }],
    captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
})
//form表单的实例
const formRef = ref<FormInstance>()
/**
 * 提交
 */
const submitForm = (formEl: FormInstance | undefined) => {
    if (!formEl) return;
    formEl.validate((isValid: boolean) => {
        if (!isValid) {
            return;
        }
        //显示加载动画
        const loading = ElLoading.service({
            lock: true,
            text: '正在登陆',
            background: 'rgba(0, 0, 0, 0.7)',
        });
        //请求接口,进行登陆
        api.post('/api/login', loginForm).then((response: AxiosResponse<any>) => {
            if (response.status != 200 || !response.data || response.data.code != 200) {
                if (response.data) {
                    ElMessage.error('登陆失败:' + response.data.msg)
                } else {
                    ElMessage.error('登陆失败!')
                }
                return;
            }
            let token = response.headers.authorization;
            //存储token,后续使用
            loginStore.authorization = token;
            router.push('/index');
        }).catch((error: AxiosError<any>) => {
            if (error.response && error.response.data) {
                ElMessage.error(error.response.data.msg)
                return;
            }
            ElMessage.error('操作异常:' + error.message)
        }).finally(() => {
            loading.close();
        });
    })
}
// 验证码路径
const captchaSrc = ref('/api/login/captcha');
/**
 * 刷新验证码
 */
const changecaptchaSrc = () => {
    captchaSrc.value = '/api/login/captcha?t_=' + new Date().getTime();
}
</script>

<template>
    <div class="loginPage">
        <el-card class="loginPanel">
            <div class="loginPanelInner">
                <div class="logo">
                    <img src="../../assets/images/logo.png">
                </div>
                <el-divider direction="vertical" border-style="dashed" class="split" />
                <div class="loginForm">
                    <div class="systemName"> 用户登陆 </div>
                    <el-form ref="formRef" size="large" :model="loginForm" status-icon :rules="rules"
                        label-width="120px" class="form">
                        <el-form-item label="账号:" prop="account">
                            <el-input v-model="loginForm.account" placeholder="请输入账号" autocomplete="off"
                                suffix-icon="UserFilled" />
                        </el-form-item>
                        <el-form-item label="密码:" prop="password">
                            <el-input v-model="loginForm.password" placeholder="请输入密码" type="password"
                                autocomplete="off" suffix-icon="Lock" />
                        </el-form-item>
                        <el-form-item label="验证码:" prop="captcha">
                            <div style="display: flex;width: 100%;">
                                <div style="flex:1">
                                    <el-input v-model.number="loginForm.captcha" placeholder="请输入验证码" />
                                </div>
                                <div class="captchaSrc">
                                    <img :src="captchaSrc" @click="changecaptchaSrc">
                                </div>
                            </div>
                        </el-form-item>
                        <div class="registerBtn">
                            <el-link type="primary" href="/register" :underline="false">
                                去账户?点击注册 </el-link>
                        </div>
                        <el-form-item>
                            <el-button type="primary" @click="submitForm(formRef)" class="loginBtn">
                                登陆 </el-button>
                        </el-form-item>
                    </el-form>
                </div>
            </div>
        </el-card>
        <div class="footer">
            @Copyright 君君军通用管理系统 备案信息:陕432432432
        </div>
    </div>
</template>

<style scoped>
.loginPage {
    width: 100%;
    height: 100%;
    display: flex;
    justify-items: center;
    justify-content: center;
    align-items: center;
    background: linear-gradient(133deg, #1994bb, #19acbb, #19b4bb, #2a89db);
}

.loginPage .loginPanel {
    width: 60%;
    height: 60%;
    min-width: 600px;
    max-width: 1000px;
    min-height: 400px;
    max-height: 500px;
    margin: 0 auto;
}

.loginPage .loginPanel>>>.el-card__body {
    width: 100%;
    height: 100%;
}

.loginPage .loginPanel .loginPanelInner {
    width: 100%;
    height: 100%;
    display: flex;
}

.loginPage .loginPanel .loginPanelInner .logo {
    width: 40%;
    text-align: center;
    display: flex;
    justify-items: center;
    justify-content: center;
    align-items: center;
}

.loginPage .loginPanel .loginPanelInner .logo img {
    width: 50%;
}

.loginPage .loginPanel .loginPanelInner .split {
    height: calc(100% - 40px);
}

.loginPage .loginPanel .loginPanelInner .loginForm {
    flex: 1;
}

.loginPage .loginPanel .loginPanelInner .loginForm .systemName {
    text-align: center;
    font-size: 30px;
    line-height: 60px;
    letter-spacing: 3px;
    margin-bottom: 20px;
}

.loginPage .loginPanel .loginPanelInner .loginForm .form {
    width: 80%
}

.loginPage .loginPanel .loginPanelInner .loginForm .form .loginBtn {
    width: 100%;
}

.loginPage .loginPanel .loginPanelInner .loginForm .form .captchaSrc {
    width: 100px;
    height: 100%;
    padding-left: 10px;
}

.loginPage .loginPanel .loginPanelInner .loginForm .form .captchaSrc img {
    width: 100px;
    height: 100%;
    cursor: pointer;
}

.loginPage .loginPanel .loginPanelInner .loginForm .form .registerBtn {
    text-align: right;
    line-height: 40px;
    margin-bottom: 5px;
}

.footer {
    position: fixed;
    bottom: 0px;
    line-height: 40px;
    text-align: center;
    font-size: 14px;
    color: #fff;
}
</style>
  1. /src/utils/api.ts
typescript">import axios, { type AxiosResponse } from 'axios';
import { ElMessage } from 'element-plus';

import { useLoginStore } from '@/stores/login';

import router from '../router/index';

// 使用由库提供的配置的默认值来创建实例
// 此时超时配置的默认值是 `0`
const instance = axios.create({
    baseURL: import.meta.env.BASE_URL
});

// 覆写库的超时默认值
// 现在,在超时前,所有请求都会等待 2.5 秒
instance.defaults.timeout = 3000;

//请求时需附加上token
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    const loginStore = useLoginStore();
    config.headers.authorization = "Bearer " + loginStore.authorization;
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

//token过期拦截处理
instance.interceptors.response.use(function (response: AxiosResponse<any>) {
    return response;
}, function (error) {
    if (error.response.status == 401) {
        //提示登陆状态已失效,需要重新登陆
        ElMessage({
            message: '登陆状态已失效,请重新登陆',
            type: 'warning',
        });
        //自动转到登陆界面
        router.push('/login');
    }
    return Promise.reject(error);
});
export default instance;
  1. /src/stores/index.ts
typescript">import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;
  1. /src/stores/login.ts
typescript">import { defineStore } from 'pinia';

/**
 * 存储状态的处理
 */
export const useLoginStore = defineStore('loginStore', {
    state: () => {
        return {
            authorization: ''
        }
    },
    actions: {
        /**
         * 更新登陆凭证
         */
        setToken(value: string) {
            this.authorization = value
        },
        /**
         * 检测当前是否已经登陆
         */
        isLogined(): boolean {
            return this.authorization ? true : false;
        }
    },
    persist: true,
})
  1. /src/router/index.ts
typescript">import {
  createRouter,
  createWebHistory,
} from 'vue-router';

import { useLoginStore } from '@/stores/login';
import index from '@/views/index.vue';
import login from '@/views/login/login.vue';
import register from '@/views/register/register.vue';

//声明元数据
declare module 'vue-router' {
  interface RouteMeta {
    // 每个路由都必须声明
    requiresAuth: boolean
  }
}

//路由信息
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: '系统主页',
      redirect: '/index',
      // 只有经过身份验证的用户才能创建帖子
      meta: { requiresAuth: true }
    },
    {
      path: '/login',
      name: '用户登陆',
      component: login,
      // 只有经过身份验证的用户才能创建帖子
      meta: { requiresAuth: false }
    },
    {
      path: '/register',
      name: '用户注册',
      component: register,
      // 只有经过身份验证的用户才能创建帖子
      meta: { requiresAuth: false }
    },
    {
      path: '/index',
      name: '系统主页',
      component: index,
      // 只有经过身份验证的用户才能创建帖子
      meta: { requiresAuth: true }
    }
  ]
})
//添加路由的前置守卫,做登陆状态的检测
router.beforeEach(async (to, from) => {
  if (to.meta.requiresAuth) {//判断当前界面是否需要鉴权
    //登陆状态的存储器
    const loginStore = useLoginStore();
    if (!loginStore.isLogined()) {
      // 将用户重定向到登录页面
      return { path: '/login' }
    }
  }
})

export default router

http://www.niftyadmin.cn/n/5417280.html

相关文章

LeetCode-Hot100

哈希 1.两数之和&#xff1a; 给定一个整数数组nums和一个整数目标值target&#xff0c;请你再该数组中找出和为目标值target的那两个整数&#xff0c;并返回它们的数组下标。 思路&#xff1a;暴力解法是使用两层循环来遍历每一个数&#xff0c;然后找出两数之和等于target的…

Go微服务: 基于ProtoBuf实现序列化和反序列化

概述 关于 protoBuf 参考&#xff1a;电脑本机安装&#xff1a;https://github.com/protocolbuffers/protobuf 选择合适的版本安装后注意检查环境变量 安装后&#xff0c;查看版本 $ protoc --version 本机安装 protobuf的件protoc-gen-go插件和protoc-gen-go-grpc插件 这个插…

CubeMX使用教程(6)——ADC模拟输出

本篇将利用CubeMX开发工具学习ADC&#xff08;模拟输出&#xff09;的使用 我们还是利用上一章的工程进行二次开发&#xff0c;这样方便 首先打开CubeMX进行相关配置 通过查看G431RBT6开发板有关模拟输出部分的原理图可知&#xff0c;模拟输出用到的IO口是PB15和PB12 接着我…

ES分布式搜索-索引库操作

索引库操作 1、mapping映射属性 可以查看官方文档学习&#xff1a;ES官方手册 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; type&#xff1a;字段数据类型&#xff0c;常见的简单类型有&#xff1a; 字符串&#xff1a;text&#xff08;可…

代码随想录算法训练营第55天| 583. 两个字符串的删除操作、72. 编辑距离、编辑距离总结篇

583. 两个字符串的删除操作 完成 代码 class Solution {public int minDistance(String word1, String word2) {// dp[i][j] 代表以i-1结尾的word1 和 以j-1结尾的word2&#xff0c;使它们相同的最小步数int[][] dp new int[word1.length()1][word2.length()1];// 初始化&…

【git】总结

一般提交流程&#xff1a; git add 或者直接加号暂存 git status 检查状态 git commit -m “提交信息” git pull git push origin HEAD:refs/for/分支 git pull 拉代码时有冲突&#xff1a; git add . 暂存所有 git stash 暂存 git pull 解决冲突 git stash apply 放出暂存…

docker本地搭建spark yarn hive环境

docker本地搭建spark yarn hive环境 前言软件版本准备工作使用说明构建基础镜像spark on yarn模式构建on-yarn镜像启动on-yarn集群手动方式自动方式 spark on yarn with hive(derby server)模式构建on-yarn-hive镜像启动on-yarn-hive集群手动方式自动方式 常用示例spark执行sh脚…

大数据开发(Spark面试真题-卷三)

大数据开发&#xff08;Spark面试真题&#xff09; 1、Spark的阶段划分&#xff1f;2、Sparkjoin的分类&#xff1f;3、Spark map join的实现原理&#xff1f;4、介绍下Spark Shuffle及其优缺点&#xff1f;5、什么情况下产生Spark Shuffle&#xff1f;6、Spark为什么适合迭代处…