二、第二篇整合了vue-routes. https://blog.csdn.net/ldy889/article/details/123527485https://blog.csdn.net/ldy889/article/details/123527485
三、本文在上面的基础上,增加登录页面,登录后后台会返回token,然后跳转到后台首页。验证是否有权限,如直接访问后台的路由地址,路由守卫会返回到login页面登录, 前面2节可以跳着看,本节会贴出完整的代码。
1、npm init vite@latest 我们会得到一个helloWorld的项目。
2、npm i 安装依赖,npm run dev测试看下结果
3、安装 npm install pinia ,element-plus, vue-router, sass, sass-loader
npm i vite-plugin-svg-icons -D 最终选用这个来使用svg-icon:参考:https://github.com/JetBrains/svg-sprite-loader/issues/434https://github.com/JetBrains/svg-sprite-loader/issues/434
4、修改main.ts文件。
import { createApp } from 'vue'
import App from './App.vue'
//引入 pinia store
import { createPinia } from 'pinia'
//引入 element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import Router from './router'
//引入 vite-plugin-svg-icons
import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon/index.vue'// svg component
const pinia = createPinia()
const app =createApp(App)
app.component('svg-icon', SvgIcon)
app.use(pinia)
app.use(ElementPlus)
app.use(Router)
app.mount('#app')
5、修改src/App.vue 这个就是添加个路由槽
<script setup lang="ts">
// src/App.vue
</script>
<template>
<router-view></router-view>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
6、添加 src/router/index.ts , 这里用到扫描import ,vite的功能,取代原来webpack下的插件。不知道的看下我上面第二个文章。或者去vite官方文档里看下懒加载。
import { createRouter, createWebHashHistory } from "vue-router";
const modules = import.meta.glob("../views/*/*.vue");
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod);
});
}
const Router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/",
component: modules["../views/login/index.vue"],
}
],
});
export default Router;
7、添加src/views/login/index.vue页面,写了一个按钮
<script setup lang="ts"></script>
<template>
<div>
<el-row class="mb-4">
<el-button type="primary">Hello</el-button>
<svg-icon icon-class="user" />
</el-row>
</div>
</template>
<style></style>
8、运行 npm run dev : 结果:
9、添加src/store/index.ts
import { defineStore} from 'pinia'
export const mainStore = defineStore('main',{
state:()=>{
return {
helloWorld:'Hello World',
count:0
}
},
getters:{},
actions:{
changeState(){
this.count++
this.helloWorld='haha'
}
}
})
10、修改src/views/login/index.vue页面
<script setup lang="ts">
import { mainStore } from "../../store/index";
const store = mainStore();
setInterval(function( ) {
store.$patch({
helloWorld: "helloWddorld"
});
store.changeState()
console.log(store.helloWorld);
},1000)
</script>
<template>
<div>
<el-row class="mb-4">
<el-button type="primary">Hello</el-button>
</el-row>
</div>
</template>
<style>
</style>
11、运行结果测试可以取到store里的值。到这里基本跑通了。下面开始写后台了。
12、修改src/views/login/index.vue页面 让他显示该有的样子。
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
<div class="title-container">
<h3 class="title">Login Form</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<span class="show-pwd" @click="showPwd">
<svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
</span>
</el-form-item>
<el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">Login</el-button>
<div class="tips">
<span style="margin-right:20px;">username: admin</span>
<span> password: any</span>
</div>
</el-form>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
export default {
name: 'Login',
data() {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('Please enter the correct user name'))
} else {
callback()
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()
}
}
return {
loginForm: {
username: 'admin',
password: '111111'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect
},
immediate: true
}
},
methods: {
showPwd() {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
this.$store.dispatch('user/login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
this.loading = false
})
} else {
console.log('error submit!!')
return false
}
})
}
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg:#283443;
$light_gray:#fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg:#2d3a4b;
$dark_gray:#889aa4;
$light_gray:#eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>
13、接下来是一键3连的广告时间。然后详细解读下login.vue.
--------广告位置------
--------广告位置------
14、第一是,图标显示不了了。 关于svg图标的使用,vite里无法使用webpack的svg-sprite-loader插件,而vue-svgicon又只支持vue2,vue3该如何使用svgicon呢。【2022-3-18】
安装: npm i vite-plugin-svg-icons -D
修改vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import {resolve} from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [resolve(process.cwd(), 'src/icons/svg')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
/**
* custom dom id
* @default: __svg__icons__dom__
*/
customDomId: '__svg__icons__dom__',
}),],
resolve: {
// 配置别名
alias: {
'@': resolve(__dirname, './src')
}
}
})
修改main.ts
import { createApp } from 'vue'
import App from './App.vue'
//引入 pinia store
import { createPinia } from 'pinia'
//引入 element-plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import Router from './router'
//引入 vite-plugin-svg-icons
import 'virtual:svg-icons-register'
import SvgIcon from '@/components/SvgIcon/index.vue'// svg component
const pinia = createPinia()
const app =createApp(App)
app.component('svg-icon', SvgIcon)
app.use(pinia)
app.use(ElementPlus)
app.use(Router)
app.mount('#app')
创建:src/components/SvgIcon/index.vue
<template>
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
/>
<svg v-else :class="svgClass" aria-hidden="true" >
<use :xlink:href="iconName" />
</svg>
</template>
<script>
import { defineComponent, computed } from "vue";
import { isExternal } from "@/utils/validate";
export default defineComponent({
name: "SvgIcon",
props: {
iconClass: {
type: String,
required: true,
},
className: {
type: String,
default: "",
},
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
});
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
更新 src/utils/validate.ts
export const validUsername = (str: string) => ['admin', 'editor'].indexOf(str.trim()) >= 0
export const isExternal = (path: string) => /^(https?:|mailto:|tel:)/.test(path)
export const isArray = (arg: any) => {
if (typeof Array.isArray === 'undefined') {
return Object.prototype.toString.call(arg) === '[object Array]'
}
return Array.isArray(arg)
}
export const isValidURL = (url: string) => {
const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
return reg.test(url)
}
添加一些svg文件到 src/icons/svg/...
运行结果:
当然点击登录会有问题。
接下来要用pinia了。先安装 vue-request,axios, nprogress进度条。
npm install vue-request
npm install axios
npm i --save-dev @types/nprogress
创建 service/http.ts
//http.ts
import axios, { AxiosRequestConfig } from 'axios'
import NProgress from 'nprogress'
// 设置请求头和请求路径
axios.defaults.baseURL = '/api'
axios.defaults.timeout = 10000
axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'
axios.interceptors.request.use(
(config): AxiosRequestConfig<any> => {
const token = window.sessionStorage.getItem('token')
if (token) {
//@ts-ignore
config.headers.token = token
}
return config
},
(error) => {
return error
}
)
// 响应拦截
axios.interceptors.response.use((res) => {
if (res.data.code === 111) {
sessionStorage.setItem('token', '')
// token过期操作
}
return res
})
interface ResType<T> {
code: number
data?: T
msg: string
err?: string
}
interface Http {
get<T>(url: string, params?: unknown): Promise<ResType<T>>
post<T>(url: string, params?: unknown): Promise<ResType<T>>
upload<T>(url: string, params: unknown): Promise<ResType<T>>
download(url: string): void
}
const http: Http = {
get(url, params) {
return new Promise((resolve, reject) => {
NProgress.start()
axios
.get(url, { params })
.then((res) => {
NProgress.done()
resolve(res.data)
})
.catch((err) => {
NProgress.done()
reject(err.data)
})
})
},
post(url, params) {
return new Promise((resolve, reject) => {
NProgress.start()
axios
.post(url, JSON.stringify(params))
.then((res) => {
NProgress.done()
resolve(res.data)
})
.catch((err) => {
NProgress.done()
reject(err.data)
})
})
},
upload(url, file) {
return new Promise((resolve, reject) => {
NProgress.start()
axios
.post(url, file, {
headers: { 'Content-Type': 'multipart/form-data' },
})
.then((res) => {
NProgress.done()
resolve(res.data)
})
.catch((err) => {
NProgress.done()
reject(err.data)
})
})
},
download(url) {
const iframe = document.createElement('iframe')
iframe.style.display = 'none'
iframe.src = url
iframe.onload = function () {
document.body.removeChild(iframe)
}
document.body.appendChild(iframe)
},
}
export default http
创建src/store/login.ts
import { defineStore } from "pinia";
import http from "@/service/http";
import loginApi from "@/service/api/login"
import { ILoginParams } from "@/service/api/types";
export interface IUserState {
token?: string;
name?: string;
avatar?: string;
introduction?: string;
roles?: string[];
email?: string;
}
export const useUserStore = defineStore("user", {
state: () => {
return {
userState: {},
};
},
getters: {
getUserState: (state) => state.userState ,
},
actions: {
async Login(params: ILoginParams) {
// const { data, error } = useRequest(http.post("/", { a: "" }));
this.userState = await loginApi.login(params).catch((error)=>{
return {error:"发送请求失败:Login"};
})
},
},
});
.添加src/api/login.ts
import http from '@/service/http'
import * as T from './types'
const loginApi: T.ILoginApi = {
login(params){
return http.post('/login', params)
}
}
export default loginApi
添加src/api/types.ts
export interface ILoginParams {
userName: string
passWord: string | number
}
export interface ILoginApi {
login: (params: ILoginParams)=> Promise<any>
}
.
好了到这里我们可以登录并跳转到另外一个页面了。
祝你成功。
一个admin后台。技术栈参考他的。
好的,目前我们能登录了。检查有token和用户信息后,跳转到dashboard页面“/”。所以我们的路由应该看起来是这样的。
import { createRouter, createWebHashHistory } from "vue-router";
const modules = import.meta.glob("../views/*/*.vue");
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod);
});
}
const Router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: "/login",
component: modules["../views/login/index.vue"],
},
{
path: "/",
component: modules["../views/dashboard/index.vue"],
},
],
});
export default Router;
添加 src/views/dashboard/index.vue
<template>
<div>dashborad</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data() {
return {};
},
watch: {},
mounted() {},
methods: {},
});
</script>
<style lang="scss"></style>