目录
完整项目请访问
gitee:vue3-TS-model: 用于从零搭建模板使用 (gitee.com)
github:vue3-TS-model: 用于从零搭建模板使用 (gitee.com)
一.各页面完整代码
1.浏览器缓存相关设置
这一部分使用了localstorage,session,cookies数据缓存方式
import Cookies from 'js-cookie';
/**
* window.localStorage 浏览器永久缓存
* @method set 设置永久缓存
* @method get 获取永久缓存
* @method remove 移除永久缓存
* @method clear 移除全部永久缓存
*/
export const Local = {
// 设置永久缓存
set(key: string, val: any) {
window.localStorage.setItem(key, JSON.stringify(val));
},
// 获取永久缓存
get(key: string) {
let json: any = window.localStorage.getItem(key);
return JSON.parse(json);
},
// 移除永久缓存
remove(key: string) {
window.localStorage.removeItem(key);
},
// 移除全部永久缓存
clear() {
window.localStorage.clear();
},
};
/**
* window.sessionStorage 浏览器临时缓存
* @method set 设置临时缓存
* @method get 获取临时缓存
* @method remove 移除临时缓存
* @method clear 移除全部临时缓存
*/
export const Session = {
// 设置临时缓存
set(key: string, val: any) {
if (key === 'token' || key === 'username'){
const expirationTime = new Date();
expirationTime.setTime(expirationTime.getTime() + 2 * 60 * 60 * 1000); // 2小时后过期
return Cookies.set(key, val, { expires: expirationTime });
}
window.sessionStorage.setItem(key, JSON.stringify(val));
},
// 获取临时缓存
get(key: string) {
if (key === 'token' || key === 'username') return Cookies.get(key);
let json: any = window.sessionStorage.getItem(key);
return JSON.parse(json);
},
// 移除临时缓存
remove(key: string) {
if (key === 'token' || key === 'username') return Cookies.remove(key);
window.sessionStorage.removeItem(key);
},
// 移除全部临时缓存
clear() {
Cookies.remove('token');
Cookies.remove('username');
window.sessionStorage.clear();
},
};
2.pinia状态管理应用
这一部分用于存储登陆后获取的一些信息,用于全局
// 在 src/store/index.js 中创建一个简单的 store
import { UserInfoState } from '@/api/login/types';
import { defineStore } from 'pinia'
interface State {
userForm: UserInfoState;
isLogin:boolean;
}
export const useMyStore = defineStore('myStore', {
state: (): State => ({
userForm: {
user: '',
ID: '',
age: '',
sex: ''
},
isLogin:false
// 其他状态
}),
actions: {
// 动作
setUserInfo(data:UserInfoState) {
this.userForm = data
},
setIsLogin(data:boolean) {
this.isLogin = data
},
},
})
3.request请求/响应拦截
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Session } from '@/utils/storage';
// 配置新建一个 axios 实例
const service = axios.create({
baseURL: 'https://www.fastmock.site/mock/b9536a1dea3fe4daeec18bc365e14a18/api',
timeout: 50000,
headers: { 'Content-Type': 'application/json' },
});
// 添加请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么 token
if (Session.get('token')) {
(<any>config.headers).common['token'] = `${Session.get('token')}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做点什么
const res = response.data;
if (res.code && res.code !== '2000') {
// `token` 过期或者账号已在别处登录
if (res.code === 401 || res.code === "4001") {
Session.clear(); // 清除浏览器全部临时缓存
window.location.href = '/'; // 去登录页
ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
.then(() => {})
.catch(() => {});
}
return response.data;
// return Promise.reject(service.interceptors.response);
} else {
return response.data;
}
},
(error) => {
// 对响应错误做点什么
if (error.message.indexOf('timeout') != -1) {
ElMessage.error('网络超时');
} else if (error.message == 'Network Error') {
ElMessage.error('网络连接错误');
} else {
if (error.response.data) ElMessage.error(error.response.statusText);
else ElMessage.error('接口路径找不到');
}
return Promise.reject(error);
}
);
// 导出 axios 实例
export default service;
4.路由守卫
// 导入router所需的方法
import { createRouter, createWebHashHistory } from 'vue-router'
// 导入路由页面的配置
import routes from './routes'
import { Session } from '@/utils/storage'
import { useMyStore } from '@/stores/states'
// 路由参数配置
const router = createRouter({
// 使用hash(createWebHashHistory)模式,(createWebHistory是HTML5历史模式,支持SEO)
history: createWebHashHistory(),
routes: routes,
})
// 全局前置守卫,这里可以加入用户登录判断
router.beforeEach((to, from, next) => {
if(Session.get('token') && (from.path === '/' || to.path === '/index')){
useMyStore().setIsLogin(true)
}
// 继续前进 next()
// 返回 false 以取消导航
next()
})
// 全局后置钩子,这里可以加入改变页面标题等操作
router.afterEach((to, from) => {
const _title = to.meta.title
if (_title) {
window.document.title = String(_title)
}
})
// 导出默认值
export default router
5.登录页代码
<template>
<div class="loginForm">
<div class="rloginTitle">登录</div>
<!-- 各个输入框 -->
<el-form :model="state.formData" :rules="formRules" ref="formRef" label-width="50px">
<el-form-item prop="username">
<div class="formInput">
<el-input v-model="state.formData.username" placeholder="username" clearable autocomplete="off"
prefix-icon="User"></el-input>
</div>
</el-form-item>
<el-form-item prop="password">
<div class="formInput">
<el-input v-model="state.formData.password" placeholder="password" type="password" show-password
autocomplete="off" prefix-icon="Lock"></el-input>
</div>
</el-form-item>
<!-- 验证码 -->
<el-form-item prop="code">
<div class="formInput codeLine">
<el-input class="codeInput" v-model="state.formData.code" placeholder="输入验证码" clearable maxlength="4" />
<slideVerify class="codeShow" v-model:identifyCode="identifyCode" @click="refresh()"></slideVerify>
</div>
</el-form-item>
<!-- 登陆界面 -->
<el-form-item>
<div class="formInput settings">
<label class="labelText">
<input type="checkbox" v-model="rem_pswd" /> 记住密码
</label>
<label class="labelText">
<span @click.stop="go_page('findPassword')">忘记密码?</span>
<span @click.stop="go_page('register')">注册</span>
</label>
</div>
</el-form-item>
<!-- 提交按钮 -->
<el-form-item label-width="0">
<el-button :loading="state.loading" type="primary" class="loginButton" round
@click="handleSubmita(formRef)">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { FormInstance, FormRules } from 'element-plus';
import router from '@/router';
// 导入生成验证码组件
import slideVerify from "@/components/slideVerify.vue"
// 导入登录 API 方法
import { UseLoginApi } from "@/api/login/index"
// 导入登录状态的类型
import { LoginState } from "@/api/login/types";
// 导入本地存储工具类
import { Session } from '@/utils/storage';
// 导入全局状态管理
import { useMyStore } from '@/stores/states';
// 生成随机验证码
const generateCode = () => {
const code = ref('');
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";
for (let i = 0; i < 4; i++) {
code.value += characters.charAt(Math.floor(Math.random() * characters.length));
}
return code.value;
}
// 传递验证码给子组件
const identifyCode = ref(generateCode())
// 刷新验证码
const refresh = () => {
identifyCode.value = generateCode()
}
// 检测输入的验证码,
const checkCodeFun = (rule: any, value: any, callback: any) => {
if ((value as string).toUpperCase() === identifyCode.value) {
return true
} else {
return false
}
}
// 使用登录 API 方法
const useLoginApi = UseLoginApi()
// 使用全局状态管理
const useUserInfoStore = useMyStore()
// 定义组件的响应式状态
interface State {
loading: boolean;
formData: LoginState;
}
const state: State = reactive({
loading: false,
formData: {
username: "admin",
password: "admin",
code: identifyCode.value,
},
})
// 定义登录逻辑
const login = () => {
let data: LoginState = {
username: state.formData.username,
password: state.formData.password,
code: state.formData.code
}
useLoginApi.login(data)
.then((res) => {
console.log(res);
// 登录成功后的处理逻辑
Session.set('token', res.data.token)
Session.set('username', res.data.userInfo.user)
useUserInfoStore.setUserInfo(res.data.userInfo)
useUserInfoStore.setIsLogin(true)
router.push({
name: 'index'
})
})
.catch((err) => {
console.log(err);
})
}
// 创建表单的引用
const formRef = ref<FormInstance>();
// 定义表单验证规则
const formRules = reactive<FormRules<typeof state.formData>>({
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
code: [{ required: true, validator: checkCodeFun, message: '请输入验证码', trigger: 'blur' }],
})
// 记住密码,true 或 false
const rem_pswd = ref(localStorage.getItem('rem_pswd'))
// 跳转页面的方法
const go_page = (path: string) => {
router.push({
name: 'login', // 替换为目标路由的名称或路径
})
}
// 处理表单提交的方法
const handleSubmita = (formEl: FormInstance | undefined) => {
if (!formEl) return
formEl.validate((valid: any, fields: any) => {
if (valid) {
// 表单验证通过,执行登录逻辑
login()
console.log('login');
} else {
console.log('error submit!', fields)
}
})
}
</script>
<style scoped>
/* 可以添加一些样式 */
.loginForm {
height: 400px;
border: 1px solid;
border-radius: 10px;
text-align: center;
background-color: #f3f3f3;
}
.loginForm .rloginTitle {
font-size: 23px;
margin: 5%;
}
.loginButton {
width: 250px;
margin: auto;
margin-top: 20px;
}
.formInput {
width: 80%;
min-width: 50px;
}
.codeLine {
display: flex;
}
.codeLine .codeInput {
flex: 2;
}
.codeLine .codeShow {
flex: 1;
}
.settings {
display: flex;
justify-content: space-between;
}
.settings label {
flex: 1;
font-size: 16px;
}
.settings .labelText {
cursor: pointer;
}
.btn-base {
width: 100%;
}
.btn-flex {
display: flex;
justify-content: center;
}
</style>
6.header登录部分代码
<template>
<div class="top1">
<div class="header">
<!-- 头部主体 -->
<div class="header_layout layout_width">
<!-- logo部分 -->
<div class="logo">
<div class="pic">
<!-- <img src=""> -->
</div>
<div class="pic_title" @click="goPage('index')">
<div class="title1">XXXXXXXXXXX</div>
<div class="title2">XXX系统</div>
</div>
</div>
<!-- 搜索,按钮部分 -->
<div class="options">
<div class="options_layout">
<div class="search_box">
<el-row>
<el-input v-model="search" class="w-50 m-2" placeholder="输入查找内容" :prefix-icon="Search" />
</el-row>
</div>
<div class="login_box">
<div class="user" v-if="useMyStore().isLogin">
<el-dropdown>
<span>{{ Session.get('username') || useMyStore().userForm.user }}</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="goPage('personalSetting')">个人中心</el-dropdown-item>
<el-dropdown-item divided @click="goPage('logout')">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div class="login" v-else>
<el-button type="primary" @click="goPage('login')">login</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Search } from '@element-plus/icons-vue';
import router from '@/router';
import { Session } from '@/utils/storage';
import { useMyStore } from '@/stores/states';
import { ElMessageBox, ElMessage } from 'element-plus';
import 'element-plus/dist/index.css';
// 搜索关键词的响应式引用
const search = ref('');
// 退出登录
const logout = (pageUrl: string) => {
console.log('logout', pageUrl);
// 弹出确认对话框
ElMessageBox.confirm(
'确认退出登录?',
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
// 清空本地存储
Session.clear();
// 设置全局状态中的登录状态为 false
useMyStore().setIsLogin(false);
// 跳转到指定页面
router.push({ name: pageUrl });
// 提示退出成功
ElMessage({
type: 'success',
message: '退出成功!',
});
})
.catch(() => {
// 提示取消退出
ElMessage({
type: 'info',
message: '取消退出!',
});
});
};
// 跳转页面的方法
const goPage = (pageUrl: string) => {
if (pageUrl === 'logout') {
// 如果是退出登录,调用退出登录函数
logout('index');
} else if (pageUrl === 'login') {
// 如果是登录页,带上额外的查询参数
router.push({
name: pageUrl,
query: {
path: 'login',
},
});
} else {
console.log(pageUrl);
try {
// 其他页面直接跳转
router.push({
name: pageUrl,
});
} catch (err) {
console.log(err);
}
}
};
</script>
<style scoped>
/* 整体头部颜色宽度设置 */
.header {
z-index: 9;
background-color: rgba(17, 101, 172, 1);
/* background: linear-gradient(rgba(17, 101, 172, 0.8), rgba(17, 101, 172, 0.5)); */
height: 80px;
}
/* 自定义 头部内容排版样式 */
.header_layout {
height: 80px;
position: relative;
}
.logo,
.content,
.options {
height: 100%;
}
.logo {
color: rgb(255, 255, 255);
width: 350px;
cursor: pointer;
}
.logo .pic {
display: inline-block;
width: 80px;
height: 80px;
position: absolute;
left: 0;
}
.logo .pic img {
height: 80px;
}
.logo .pic_title {
width: 250px;
height: 80px;
}
.title1,
.title2 {
position: absolute;
left: 85px;
/* 与 .logo .pic_title 的 left 值相同 */
}
.title1 {
font-size: 26px;
font-weight: 100;
height: 45px;
line-height: 45px;
}
.title2 {
top: 45px;
font-size: 16px;
font-weight: 100;
height: 35px;
line-height: 35px;
}
.options {
position: absolute;
top: 0;
right: 0;
width: 400px;
}
.options .options_layout {
display: flex;
align-items: center;
height: 100%;
}
.options .options_layout .search_box {
flex: 3;
}
.options .options_layout .login_box {
flex: 1;
}
.options .options_layout .login_box .login {}
.options .options_layout .login_box .user {
border: 1px solid;
border-radius: 50%;
width: 50px;
height: 50px;
background-color: rgb(80 188 221);
margin: auto;
overflow: hidden;
cursor: pointer;
}
.options .options_layout .login_box .user span {
line-height: 50px;
font-size: 18px;
border: none;
outline: none;
}
.example-showcase .el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
</style>
二.部分代码截取
1.login登录页
// 定义登录逻辑
const login = () => {
let data: LoginState = {
username: state.formData.username,
password: state.formData.password,
code: state.formData.code
}
useLoginApi.login(data)
.then((res) => {
console.log(res);
// 登录成功后的处理逻辑
Session.set('token', res.data.token)
Session.set('username', res.data.userInfo.user)
useUserInfoStore.setUserInfo(res.data.userInfo)
useUserInfoStore.setIsLogin(true)
router.push({
name: 'index'
})
})
.catch((err) => {
console.log(err);
})
}
2.header部分
// 退出登录
const logout = (pageUrl: string) => {
console.log('logout', pageUrl);
// 弹出确认对话框
ElMessageBox.confirm(
'确认退出登录?',
'提示',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
// 清空本地存储
Session.clear();
// 设置全局状态中的登录状态为 false
useMyStore().setIsLogin(false);
// 跳转到指定页面
router.push({ name: pageUrl });
// 提示退出成功
ElMessage({
type: 'success',
message: '退出成功!',
});
})
.catch(() => {
// 提示取消退出
ElMessage({
type: 'info',
message: '取消退出!',
});
});
};