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

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

B站视频:

<iframe id="DYOPnKmW-1709868857546" frameborder="0" src="https://player.bilibili.com/player.html?aid=1951025133" allowfullscreen="true" data-mediaembed="bilibili"></iframe>

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
{
  "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
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
<!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
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
<script setup lang="ts">
import {
  RouterLink,
  RouterView,
} from 'vue-router';
</script>

<template>
  <RouterView />
</template>

<style scoped></style>
  1. /src/views/index.vue
<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
<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
<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
<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
<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
<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
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
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;
  1. /src/stores/login.ts
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
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
GitHub 加速计划 / eleme / element
11
1
下载
A Vue.js 2.0 UI Toolkit for Web
最近提交(Master分支:8 个月前 )
c345bb45 1 年前
a07f3a59 * Update transition.md * Update table.md * Update transition.md * Update table.md * Update transition.md * Update table.md * Update table.md * Update transition.md * Update popover.md 1 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐