网站这么做优化,国家正规网站查询,网站改版建议策划书,多光营销软件网站前面几篇文章我们介绍的都是ViteVue3TypeScript项目中环境相关的配置#xff0c;接下来我们开始进入系统搭建部分。本篇我们来介绍登录界面搭建及动态路由配置#xff0c;大家一起撸起来......搭建登录界面登陆接口api项目登陆接口是通过mockjs前端来模拟的模拟服务接口Login…前面几篇文章我们介绍的都是ViteVue3TypeScript项目中环境相关的配置接下来我们开始进入系统搭建部分。本篇我们来介绍登录界面搭建及动态路由配置大家一起撸起来......搭建登录界面登陆接口api项目登陆接口是通过mockjs前端来模拟的模拟服务接口LoginApi首先在src/mock文件夹下新建login.ts文件模拟两个服务接口验证码获取用户登录import { MockMethod } from vite-plugin-mock;export const LoginApi: ArrayMockMethod [{url: /api/captchaImage,method: get,response: () {return {msg: OK,img: /9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAA8AKADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4Tl5ufo6erx8vP09fb3Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3Pn6/9oADAMBAAIRAxEAPwDtrW1ga1hZoIySikkoOeKsCztvfeL/vgU2z/484Pua/yqyKiMY8q0IjGPKtCIWdr/wA0P8A3wKeLK1/59of/YqUU2e4htYWmuJUiiX7zuwAH1JqlBPoPlj2EFlaf8APrD/ANxThY2n/PrB/37FY2qMdC0a3MtzqELHHyxxMHdvoB/M8VzmnfF3SrzUo7WSxuoI5HCJKcNyTgbgOn4Zrso5Xia1N1KdJuK62/q/yJbgnZnfiws/8An1g/79inCws/fSD/v2KlRgwyKkFcXLHsVyx7EI0y/59Lf/AL9j/CnDTrL/AJ87f/v0vFLJdW8BxLNGhxnDMBXM6x8SfDmjSS94Libutv836kcf1rejhKleXJSg5PyQmoLc6gadY/wDPnb/9l/wpw02x/58rf8A79L/AIVX0fWbLXNPivrGUSQydD3B9D71pCspUuSTjJWaHyx7FcaZYf8APlbf9l/wp40yw/58bb/AL9L/hVgU2WeK3iaSWRY0UZLMcAfjS5F2Dlj2Ixpen/8Nt/35X/AAp40rT/APnwtf8AvyvFcJf/GLw7Z3/ANngW4uo1bElxGmEX6Z5P5fTNd3pupWuq2MN5ZzLLBKu5HU9RXTXwFahFTq03FPa6EuR7DhpWnf8Fr/ANV/wAKcNJ07/oH2v8A35X/AAq0KeK5eWPYfLHsVRpOm/8AQPtP/K/4VW1PS9Pj0i9dLG1V1gcqwhUEHaeRxWsKq6t/wAgW/8AveT/wBBNKUY8r0FKMeV6HJWf/HnB/1zXVWRVez/wCPOD/rmv8AKrIpxFDj8KFFZHiCBL3SrmymB8qeMo2OoyOo962QKint1mXBq4ycWpLdFHi0XgwsJS8nmXRXor8LIHWsS8UXniqC32LFGhVQsahcfTFe23mjxCNm2ivGvEyf2X4sjuAMLkNjdfT5NjcTi8XL2s3KThJRv39NjCpFRjoj3jSJmltkLHJxzWoTgVgH7mObTIZozlWQEVy2taz47bVJ00203tInIjLOGMo9TnpQ/Gvn6GHdWTjzKNu7sbN2L/jPw/Z69Lby3DSpJAGUNG2MqccH8v51xup6Po2maRPCLKNFKHMrcvnHBya2I/iKlufsvifS57G7A/EuN/cc/yLfWs3V7LTvGEUWp2l1dG2UFDB90bgepHY/8A1q9WnHGYZQjXnKNFPRx1XfRrR39TN8r23K3wi1uay1S402Qt9nnAdPQOP8R/IV7tG4KA14DpdhqXh6412EIvIkOWtn4fHqh9a7G48Vaf4x0Q6bY6vc6XeMQXQDbIQPvL7gjPQn0N5pCOOxDxdHG7Xau7ecluvyfRhD3Vyvc9JXULN7hrZLqFp16xCQFhHWuP8caL/wAJB9kje7mit4nJmijbAlU9j9Mfqa85fQfB8dwbODVJ4NQjbAmM2GDj8AM59MGtNT49jxZwarZ3MXRLmYDeo98gkn/vqsYYWFKaqUK3LJfzrlaJP8w3JtWaLusWVpBoM9ilnFFaiNgAqAYOOv196yvhB4qlsNUfQriQm2ny0OT914hH6j3re1kCTw8dLudQtp9WFvmTy8KzH9tzkdueM3SvNPBgEPi62SY7JFkxg8civRy2EauX4qnWfM17y36X95X116W5E9JxaPquJt6g1KKoaa5e3Un0rQFfKG44VV1b/kCX/wD17SfgmrYqrq//IEv/wDr2k/9BNTL4WTL4WclZ/8AHlB/1zXVWRVey/48oPua/yqyKI/Cgj8KHCngU0U8VRRDcpuiYe1eNfEbTmIW4VSTG3OB2Ne2Fdy4rmNd0b7Vkgc11YLFSwmIjXhvF/0iZR5lY534X6i134ezs2TA5QfTqK6u70ySUlhmqPhrRI9MdzFCsfmHLbRjJ9a7JUBXkUsbWhXxE6sFZSd7eoRVlZnEy6XI48uaJZUzna65H61ow6YrW4QqEUDACgDH0rpTbI3YUkluNhCiua5R5JPrF14fvGt9etNkJYiKgXKMP9oDkVx/iS4tNT8QWs2jnfdMQWeLjJB4P1969j1jTDPG8ckSyRt1V1yDFc3pvhK3trzfb2iREnkgV7mDzPD4aXt1TanZqyfuu66rf5LT0MpQb0voVL/AMNWGqoZbm1DTsoDSr8rZx1yKxF8M61bN9ns9enjtegBzuQe2D/LFex2eip9nAZe1B8PRl87a4aOY4mlHkUrrs0pJeiadvkW4JnnekeCrCzQTJHJNeck3EjHdk9eOnrWdrOgafpx/tLUYp0SJ1JntHQ54P54r2a20mOJcbazNd0WC8tpLeaBZYZBhkPQipjja0q6rVZyb6tPW3VLtQcqtZE3grxBYIdI02MjvHG5iYyLtbcAOohB/GuqFcn4Y0q20i3z2VpHbxE5KxrjJ9T6n611idK56zpuo/ZX5el9xq9tR4qrq//IEv/wDr2k/9BNWxVXV/QJf/wDXtJ/6Caxl8LFL4WclZf8AHlb/APXNf5VZFczFrVzFEkapEQihRkHtNSf2/df884fT/AI1lGtGyM41Y2R0opwrmfEhu/8AnnB/3yf8aX/hIrv/AJ5wf98n/Gq9tEftonUCholfqK5j/hJLz/nlB/3yf8aX/hJbz/nlB/3yf8aPbRD20Tp44FQ8CpwK5L/hJ73/AJ5W/wD3y3NL/wlF7/zyt/W/xo9tEPbROvFPAzXHf8JVff88rf/vlv8aX/hK77/nlbf8AfLf40e2iHtonWvbpJ1FNjso0bIUVyv8Awlt//wA8bb/vlv8AGl/4S/UPeNt/wB8t/8AFUe2iHtonaogAxUgArh/Ew1D/nja/8AfLf/ABVL/wAJlqP/ADxtfW/wDiqPbRD20TugKa8CydRXEf8JnqP/PG1/74b/4ql/4TXUveFp/3w3/AMVR7aIe2idvFbrH0FWQK4D/AITbUv8Anhaf98N/8VS/8Jxqf/PC0/74b/4qj20Q9tE9BFVdX/5Aeof9e0n/AKCa4r/hOdT/AOeFp/3w3/xVR3PjPUbq1mt3htQkqMjFVbIBGOPmqZVo2YpVY2Z//9k,code: 200,uuid: 37e7a189a9b14be6a5cbae80af43abaa,};},},{url: /api/login,method: post,response: () {return {msg: OK,code: 200,token:eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6Ijc2YzYzNzczLWY2ZWEtNDlkMC05MDIyLTg4ZmUxNjI1NmMzNyJ9.XUeTaH-VZu_Mm0Rm1m_lST4YH1-ovX5Gg9w_Z4nA04agzxzeTdb5XxKCIhMr8pPatCKmiCql9E7afMY96oGYfQ,};},},
];LoginApi引入修改src/mock文件夹下的index.ts文件添加内容如下import { LoginApi } from ./login;
const mock: ArrayMockMethod [...LoginApi];创建登录API在src/api文件夹下新建login.ts文件创建获取验证码接口captchaImage和用户登录api接口loginimport request from /utils/request;export const captchaImage () {return request.get(/api/captchaImage);
};
export const login () {return request.post(/api/login);
};登录界面Login.vue在src/views路径下新建Login.vue文件这个文件就是我们的登录界面文件templatediv classlogin-mainel-form refruleFormRef :modelruleForm status-icon :rulesrules label-width120px classruleFormel-form-item propuserel-input :prefix-iconUser v-modelruleForm.user clearable //el-form-itemel-form-item proppassel-input :prefix-iconLock v-modelruleForm.pass typepassword //el-form-itemel-form-item propcodeel-input :prefix-iconLock v-modelruleForm.code classcode-value /img :srcimg alt classcode-img/el-form-itemel-form-itemel-button typeprimary clicksubmitForm(ruleFormRef)登录/el-buttonel-button clickresetForm(ruleFormRef)重置/el-button/el-form-item/el-form/div
/templatescript setup langts
// 方法引入
import { reactive, ref, onMounted } from vue
import router from /router;
import { setLocalStorage } from /utils/localstorage;
import type { FormInstance } from element-plus
// 组件引入
import { User, Lock } from element-plus/icons-vue
// 接口引入
import { captchaImage, login } from /api/login;onMounted(() {captchaImage().then(datas {console.log(datas)img.value data:image/gif;base64, datas.imguuid.value datas.uuid})
})
let img refany()
let uuid refstring()
const ruleFormRef refFormInstance()
const validateUser (rule: any, value: any, callback: any) {if (value ) {callback(new Error(用户名不能为空))} else {callback()}
}
const validatePass (rule: any, value: any, callback: any) {if (value ) {callback(new Error(密码不能为空))} else {callback()}
}
const validateCode (rule: any, value: any, callback: any) {if (value ) {callback(new Error(验证码不能为空))} else {callback()}
}
const ruleForm reactive({user: wangjianlei,pass: 123456,code: 4
})const rules reactive({pass: [{ validator: validatePass, trigger: blur }],user: [{ validator: validateUser, trigger: blur }],code: [{ validator: validateCode, trigger: blur }],
})const submitForm (formEl: FormInstance | undefined) {if (!formEl) returnformEl.validate((valid) {if (valid) {login().then(res {console.log(res)setLocalStorage(LH_TOKEN, res.token)router.push(/)}).catch(err {throw new Error(err);})} else {return false}})
}const resetForm (formEl: FormInstance | undefined) {if (!formEl) returnformEl.resetFields()
}
/scriptstyle langscss scoped
.login-main {display: flex;padding: 25px;.ruleForm {width: 500px;.code-value {width: 260px;}.code-img {margin-left: 10px;width: 75px;height: 30px;}}
}
/style配置动态路由动态路由接口项目动态路由接口同样是通过mockjs前端来模拟的模拟服务接口HomeApi首先在src/mock文件夹下新建home.ts文件模拟动态路由返回服务接口import { MockMethod } from vite-plugin-mock;export const HomeApi: ArrayMockMethod [{url: /api/routerList,method: get,response: () {const routes [{path: /main/PageOne,name: PageOne,component: PageOne.vue,},{path: /main/PageTwo,name: PageTwo,component: PageTwo.vue,},{path: /main/PageThree,name: PageThree,component: PageThree.vue,},];return {msg: OK,code: 200,data: routes,};},},
];这里我们可以看到共返回了三个路由“PageOne”“PageTwo”和“PageThree”。HomeApi引入修改src/mock文件夹下的index.ts文件添加内容如下import { MockMethod } from vite-plugin-mock;
import { HomeApi } from ./home;
import { LoginApi } from ./login;const mock: ArrayMockMethod [...LoginApi, ...HomeApi];export default mock;创建动态路由API在src/api文件夹下新建home.ts文件创建获取动态路由接口GetDynamicRoutesimport request from /utils/request;export const GetDynamicRoutes () {return request.get(/api/routerList);
};新增路由界面根据我们利用mockjs模拟的动态路由接口返回的数据在src/views文件夹下创建modules文件夹新建PageOne.vuePageTwo.vue和PageThree.vue界面文件。动态路由配置我们的动态路由数据应由一个公共的地方进行管理所以呢这里我选择利用vue的状态管理器pinia来实现这个功能。首先我们在pinia的state中添加一个路由项routesRouteRecordRaw类型的数组state: (): storeHome {return {//路由表routes: [],};
},然后在action中还需要添加一个根据路由数据加载动态路由的方法updateRoutes方法所需的路由数据和router对象由外部传入。这里用外部传入router是为了避免循环调用router毕竟需要进行加载动态路由的地方基本都有个router的示例对象而外部传入的话只需要调用然后传入一次router就可以了在src/store/type/home.ts添加状态项routes类型定义import { RouteRecordRaw } from vue-router;export type storeHome {routes: ArrayRouteRecordRaw;
};加载路由的思路其实也很简单首先解析咱调用接口后传入的路由数据根据路由的数据类型生成对应的路由表并存储到pinia中然后直接遍历这个pinia中的路由表使用router.addRoute()方法将路由加载进去。router.addRoute()方法支持传如两个参数方便我们在指定位置的路由中插入children这种情况下第一个参数是父级路由的name第二个参数就是要添加的children路由对象。需要我们注意的是vite使用动态路由在动态导入路由组件的时候需要特别注意不能将页面路径直接作为component导入虽然开发环境一般是能正常加载但是打包到生产环境的时候十有八九会报错所以我们需要添加以下代码//根据自己项目实际目录结构组织注意这里的“../../”不能用“”别名代替
let modules import.meta.glob(../../views/modules/*.vue);然后用modules形式引入完整动态路由的pinia代码如下import { defineStore } from pinia;
import { storeHome } from ../types/home;let modules import.meta.glob(../../views/modules/*.vue);export const useHomeStore defineStore(index, {state: (): storeHome {return {//路由表routes: [],};},getters: {},actions: {updateRoutes(data: Arrayany, router: any) {this.routes [];data.forEach((el) {this.routes.push({path: el.path,name: el.name,component: modules[../../views/modules/${el.component}],});});this.routes.forEach((el) {router.addRoute(Home, el);// router.addRoute();});},},
});加载动态路由上面我们已经配置好了路由接口和加载路由的方法加载动态路由的思路也很简单在我们的初始页面中调用路由的数据接口在获取到数据之后调用加载的方法即可。// 方法引入
import router from /router;
// 接口引入
import { GetDynamicRoutes } from /api/home
// 状态管理器引入
import { useHomeStore } from /store/modules/home;const homeStore useHomeStore()
onBeforeMount(() {if (!getLocalStorage(LH_TOKEN)) {router.push(/login)} else {alert(登陆成功)GetDynamicRoutes().then(res {homeStore.updateRoutes(res.data, router)})finish.value true}
})为验证我们的路由是否被加载成功我们可以在调用动态路由同时创建对应的按钮以便我们进行路由跳转。el-button v-foritem in routes :keyitem.name clickhandleClick(item.path){{ item.name }}/el-buttonconst routes computed(() homeStore.routes)
// 路由按钮点击事件
const handleClick (path: string) {router.push(path)
}完整页面代码如下templatediv classhome-main v-iffinishdivel-button v-foritem in routes :keyitem.name clickhandleClick(item.path){{ item.name }}/el-button/divRouterView //div/template
script setup langts
// 方法引入
import { reactive, ref, onBeforeMount, onMounted } from vue
import { computed } from vue/reactivity;
import { getLocalStorage } from /utils/localstorage
import router from /router;
// 组件引入
import HomeHeader from ./home/HomeHeader.vue;
// 接口引入
import { GetDynamicRoutes } from /api/home
// 状态管理器引入
import { useHomeStore } from /store/modules/home;const homeStore useHomeStore()
onBeforeMount(() {if (!getLocalStorage(LH_TOKEN)) {router.push(/login)} else {alert(登陆成功)GetDynamicRoutes().then(res {homeStore.updateRoutes(res.data, router)})finish.value true}
})
const routes computed(() homeStore.routes)
// 路由按钮点击事件
const handleClick (path: string) {router.push(path)
}
let finish ref(false)
/script
style langscss scoped
.home-main {}
/style动态路由效果配置路由守卫其实截止上面我们的动态路由已经加载成功了。不过仔细测试会发现还会有一个问题bug假如我们刷新跳转后的页面或者直接使用动态路由的路径进行跳转就会出现报错“no match found for location with path /PageOne ”。添加的动态路由失效了页面也没有显示这是因为我们的路由和状态管理器pinia在刷新之后都会被重置而我们加载路由的方法是在系统初始页面被调用的当我们直接F5刷新页面或者直接输入路由路径的时候初始页面其实并没有被加载也就是说我们的动态路由并没有被加载上去自然这个动态的页面也就丢失了。这里我们可以通过添加路由守卫的方式来解决这个问题大致思路假如我们的页面请求路径不是我们定义的初始路径的时候我们就在路由守卫中要求在跳转之前先去查询状态管理器中是否存在我们的动态路由或者该动态路由是否满足我们的初始页面跳转要求若不满足则请求动态路由接口并加载我们的动态路由在加载完成后再继续执行页面跳转操作。路由守卫代码如下// 路由守卫
router.beforeEach((to, from, next) {if (to.path ! /main to.path ! /) {const store useHomeStore();if (store.routes.length 1) {GetDynamicRoutes().then((res) {store.updateRoutes(res.data, router);next({ path: to.path, replace: true });}).catch((_) {next();});} else {next();}} else {next();}
});最终效果至此登录界面和动态路由基本搭建就完成了。我相信每天学习一点点收获成长亿点点