23-前端核心技术-ElementsUI框架
第23章-前端核心技术-ElementsUI框架
学习目标
- 掌握
vuecli的特征 - 掌握
vuerouter的使用重点难点 - 掌握
vuevuex的使用重点
环境搭建
cli 创建项目
(1)创建项目
1
2
3
vue create elements
# 或者 使用 图形化界面创建
vue ui
(2)运行创建好的项目
1
2
cd elements
npm run serve
element-plus 安装
(1)安装 element-plus 组件库
1
2
3
npm install element-plus --save
# 或者
npm i element-plus -S
安装好就可以使用了,无需配置
(2)使用的时候直接引入
1
2
3
4
5
6
7
8
import { ElMessage } from "element-plus"
...
ElMessage({
type: "error",
showClose: true,
message: res.message || "Error",
duration: 5 * 1000
})
(3)全局引入
在 main.js 文件中直接全局引入,在任何组件中都可以直接使用
文件位置:main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { createApp } from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"
// ElementPlus
import ElementPlus from "element-plus"
import "element-plus/dist/index.css"
createApp(App)
.use(store)
.use(router)
.use(ElementPlus)
.mount("#app")
mock 安装
(1)安装mock
1
2
3
yarn add mockjs --save-dev
// 或者
npm i mockjs -D
(2)在根目录中 .env.development 配置文件,添加如下配置
文件位置:.env.development
1
2
3
4
5
6
7
8
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'
# enable mock
VUE_APP_MOCK = true
(3)在根目录中 .env.production 配置文件,添加如下配置
文件位置:.env.production
1
2
3
4
5
6
7
8
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/prod-api'
# enable mock
VUE_APP_MOCK = true
(4)在项目根目录中创建 mock 目录,在 mock 文件中创建 index.js 文件统一挂你所有的服务
文件位置:mock/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Mock = require("mockjs")
const baeUrl = process.env.VUE_APP_BASE_API
// 加载 modules/? 下的所有 js 文件
// param1: 表示检索的目录
// param2: 表示是否检索子文件夹
// param3: 匹配文件的正则表达式,一般是文件名
const modulesFiles = require.context("./modules", true, /.js$/)
const mocks = []
modulesFiles.keys().forEach(key => {
mocks.push(...modulesFiles(key))
})
mocks.forEach(item => {
Mock.mock(baeUrl.concat(item.url), item.type, item.response)
})
(5)在mock 文件下创建 modules 目录,并且创建 user.js 用于创建用户相关的 服务
文件位置:mock/modules/user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
const tokens = {
admin: "admin",
guest: "guest"
}
const users = [
{
id: 1,
roles: ["admin", "guest"],
introduction: "超级管理员",
avatar: "../../assets/avatar.gif",
username: "admin",
password: "admin",
birthday: "2025-06-12",
sex: "男"
},
{
id: 2,
roles: ["guest"],
introduction: "访客",
avatar: "../../assets/avatar.gif",
username: "guest",
password: "guest",
birthday: "2025-05-15",
sex: "女"
}
]
const list = {
url: "/user/list",
type: "get",
response: () => {
return {
code: 20000,
message: "获取用户列表成功",
data: users
}
}
}
const login = {
url: "/user/login",
type: "post",
response: config => {
const { username, password } = JSON.parse(config.body)
const token = tokens[username]
<span class="hljs-keyword">if</span> (!token) {
<span class="hljs-keyword">return</span> {
code: <span class="hljs-number">60204</span>,
message: <span class="hljs-string">"用户不存在"</span>
}
}
<span class="hljs-keyword">if</span> (username !== password) {
<span class="hljs-keyword">return</span> {
code: <span class="hljs-number">60205</span>,
message: <span class="hljs-string">"密码错误"</span>
}
}
<span class="hljs-keyword">return</span> {
code: <span class="hljs-number">20000</span>,
message: <span class="hljs-string">"登录成功"</span>,
token: token
}
}
}
const logout = {
url: "/user/logout",
type: "post",
response: _ => {
return {
code: 20000,
message: "退出成功"
}
}
}
const info = {
url: "/user/info",
type: "post",
response: config => {
const { token } = JSON.parse(config.body)
const userinfo = users.filter(user => user.username === token)
<span class="hljs-keyword">if</span> (!userinfo) {
<span class="hljs-keyword">return</span> {
code: <span class="hljs-number">50008</span>,
message: <span class="hljs-string">"获取用户信息失败"</span>,
query: config.query
}
}
<span class="hljs-keyword">return</span> {
code: <span class="hljs-number">20000</span>,
message: <span class="hljs-string">"获取成功"</span>,
data: userinfo
}
}
}
module.exports = [list, login, info, logout]
(6)在 main.js 文件中引入,开启 mock
文件位置:src/main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createApp } from "vue"
import App from "./App.vue"
import router from "./router"
import store from "./store"
// ElementPlus
import ElementPlus from "element-plus"
import "element-plus/dist/index.css"
// 在开发环境下并且 VUE_APP_MOCK 的值为true,使用 mock
if (process.env.VUE_APP_MOCK && process.env.NODE_ENV === "development") {
require("../mock")
}
createApp(App)
.use(store)
.use(router)
.use(ElementPlus)
.mount("#app")
axios 安装
(1)安装 axios
1
2
3
npm install axios --save
# 或者
npm i axios -S
(2)在 src 中创建 utils 工具包,在 utils 中创建 request.js 请求文件
文件位置:src/utils/request.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import axios from "axios"
import router from "../router"
import store from "../store"
import { ElMessage } from "element-plus"
// 创建 axios
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // api的base_url
// withCredentials: true, // 发生 cookies 当是跨域请求时 cross-domain requests
// headers: {'X-Custom-Header': 'foobar'}, // 设置请求头
timeout: 50000 // 请求超时时间
})
// 1.request 请求拦截器
service.interceptors.request.use(
config => {
if (store.state.user.token) {
config.headers["Authorization"] = "token"
}
return config
},
error => {
return Promise.reject(error).catch(error => console.log(error))
}
)
// 2.response 响应拦截器
service.interceptors.response.use(
response => {
const result = response.data
if (result.code === 20000) {
// 成功时回调
// return Promise.resolve(result)
return result
} else {
ElMessage({
type: "error",
showClose: true,
message: result.message || "未知异常",
duration: 5 * 1000
})
// token 过期时回调
if (result.code === 10004) {
// 跳转到登录
router.replace({ path: "/login" })
}
return Promise.reject(result).catch(error => console.log(error))
}
},
error => {
ElMessage({
type: "error",
showClose: true,
message: error.message,
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
(3)使用的时候直接引入即可发送请求
一般使用的场景在 api 或者 store 中
例如:在 api 中使用,在 src 目录中创建 api 文件夹,并在 api 文件下创建 user.js 文件用于写 api 函数
文件位置:src/api/user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import request from "@/utils/request"
// 使用 import { list, login, logout, getInfo } from "@/api/user"
export function getList() {
return request({
url: "/user/list",
method: "get"
})
}
export function login(data) {
return request({
url: "/user/login",
method: "post",
data
})
}
export function getInfo(token) {
return request({
url: "/user/info",
method: "post",
data: { token }
})
}
export function logout() {
return request({
url: "/user/logout",
method: "post"
})
}
(4)api 一般在组件或者 store 的 action 操作中使用,使用是引入即可使用
1
2
3
4
5
6
7
8
9
10
11
12
13
import { login } from "@/api/user"
export default {
name: "Home",
mounted() {
// 使用 api 函数
login({ username: "admin", password: "admin" })
.then(response => {
const { data } = response
})
.catch(error => {})
}
}
store 重构
(1)修改 store/index.js 文件,实现模块化分离
文件位置:src/store/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { createStore } from "vuex"
// 加载 modules/? 下的所有 js 文件
// param1: 表示检索的目录
// param2: 表示是否检索子文件夹
// param3: 匹配文件的正则表达式,一般是文件名
const modulesFiles = require.context("./modules", true, /.js$/)
// 合并多个文件中的 store 对象
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// 设置 './app.js' => 'app'
const moduleName = modulePath.replace(/^./(.*).\w+$/, "$1")
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules
}, {})
const store = createStore({
modules
})
export default store
(2)在 store 文件夹中创建 modules 文件夹,用于分模块存放其他 store 文件。
如:src/store/modules/layout.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
export default {
state: {
// 左侧1级菜单 打开/关闭 状态
isCollapseFirst: localStorage.getItem("isCollapseFirst") === "true",
// 左侧2级菜单 打开/关闭 状态
isCollapseSecond: localStorage.getItem("isCollapseSecond") === "true",
// 左侧1级菜单默认激活
defaultActiveIndexFirst: localStorage.getItem("defaultActiveIndexFirst"),
// 左侧2级菜单默认激活
defaultActiveIndexSecond: localStorage.getItem("defaultActiveIndexSecond"),
// 左侧1级菜单当前激活选项
currentActiveItemFirst: []
},
getters: {},
mutations: {
// 左侧1级菜单 打开/关闭 状态修改 handler
toggleCollapseFirst(state, value) {
// 内部使用无需添加前缀 layout :直接访问
state.isCollapseFirst = value
localStorage.setItem("isCollapseFirst", value)
},
// 左侧2级菜单 打开/关闭 状态修改 handler
toggleCollapseSecond(state, value) {
state.isCollapseSecond = value
localStorage.setItem("isCollapseSecond", value)
},
// 左侧1级菜单默认激活
changeDefaultActiveIndexFirst(state, value) {
state.defaultActiveIndexFirst = value
localStorage.setItem("defaultActiveIndexFirst", value)
},
// 左侧2级菜单默认激活
changeDefaultActiveIndexSecond(state, value) {
state.defaultActiveIndexSecond = value
localStorage.setItem("defaultActiveIndexSecond", value)
},
// 左侧1级菜单当前激活选项
changeCurrentActiveItemFirst(state, value) {
state.currentActiveItemFirst = value
}
},
actions: {},
modules: {}
}
(3)在 store 文件夹中创建 modules 文件夹,用于分模块存放其他 store 文件。
如:src/store/modules/menu.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { constantRoutes, asyncRoutes } from "@/router"
export default {
state: {
// 所有路由数组
routes: []
},
getters: {
// 获取所有的路由
allRoutes: state => state.routes
},
mutations: {
// 设置静态路由 + 动态路由
setRoutes(state, routes) {
state.routes = constantRoutes.concat(routes)
}
},
actions: {
// 异步加载动态路由
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
const accessedRoutes = roles
? asyncRoutes.concat(roles)
: asyncRoutes || []
commit("setRoutes", accessedRoutes)
resolve(accessedRoutes)
})
}
},
modules: {}
}
router 重构
(1)在 router 文件夹中创建 modules 文件夹,用于分模块存放其他 router 文件。
如:src/router/modules/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { createRouter, createWebHashHistory } from "vue-router"
/* Layout */
import Layout from "@/layout"
// 加载 modules/? 下的所有 js 文件
// param1: 表示检索的目录
// param2: 表示是否检索子文件夹
// param3: 匹配文件的正则表达式,一般是文件名
const modulesFiles = require.context("./modules", true, /.js$/)
// 合并多个文件中的 store 对象
const modules = []
modulesFiles.keys().reduce((mod, modulePath) => {
const value = modulesFiles(modulePath)
modules.push(value.default)
}, {})
export const constantRoutes = [
{
path: "/",
component: Layout,
redirect: "/disboard",
name: "Root",
meta: {
title: "主面板",
icon: "el-icon-menu"
},
children: [
{
path: "/disboard",
name: "Disboard",
meta: { title: "主面板1", icon: "el-icon-menu" },
component: () => import("../views/Home.vue")
}
]
},
...modules
]
export const asyncRoutes = []
const router = createRouter({
history: createWebHashHistory(),
routes: constantRoutes
})
export default router
(2)在 router 文件夹中创建 modules 文件夹,用于分模块存放其他 router 文件。
如:src/router/modules/system.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* Layout */
import Layout from "@/layout"
import Default from "@/layout/modules/default"
const systemRouter = {
path: "/sysmod",
component: Layout,
redirect: "/sysmod/system/user",
name: "SystemModule",
meta: { title: "系统模块", icon: "el-icon-menu" },
children: [
{
path: "/sysmod/system",
component: Default,
redirect: "/sysmod/system/user",
name: "System",
meta: { title: "系统设置", icon: "el-icon-menu" },
children: [
{
path: "/sysmod/system/user",
name: "SysUser",
meta: { title: "用户管理", icon: "el-icon-menu" },
component: () => import("../../views/About.vue")
},
{
path: "/sysmod/system/role",
name: "SysRole",
meta: { title: "角色管理", icon: "el-icon-menu" },
component: () => import("../../views/About.vue")
},
{
path: "/sysmod/system/menu",
name: "SysMenu",
meta: { title: "菜单管理", icon: "el-icon-menu" },
component: () => import("../../views/About.vue")
},
{
path: "/sysmod/system/dept",
name: "SysDept",
meta: { title: "部门管理", icon: "el-icon-menu" },
component: () => import("../../views/About.vue")
}
]
}
]
}
export default systemRouter
(3)在 router/test.js 中引入其他模块的路由
文件位置:src/router/test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* Layout */
import Layout from "@/layout"
import Default from "@/layout/modules/default"
const testRouter = {
path: "/testmod",
component: Layout,
redirect: "/testmod/test/about",
name: "TestModule",
meta: { title: "测试模块", icon: "el-icon-menu" },
children: [
{
path: "/testmod/test",
component: Default,
redirect: "/testmod/test/about",
name: "Test",
meta: { title: "测试菜单", icon: "el-icon-menu" },
children: [
{
path: "/testmod/test/home",
name: "Home",
meta: { title: "主页", icon: "el-icon-menu" },
component: () => import("../../views/Home.vue")
},
{
path: "/testmod/test/about",
name: "About",
meta: { title: "关于", icon: "el-icon-menu" },
component: () => import("../../views/About.vue")
}
]
}
]
}
export default testRouter
icon 引入
(1)安装element-plus icon
npm install @element-plus/icons
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<Expand style="width: 20px;" />
<Fold style="width: 20px;" />
</template>
<script>
import { Expand, Fold } from "@element-plus/icons"
export default {
components: {
Expand,
Fold
},
}
</script>
(2)安装fortawesome 图标(可选)
1 2 3 4npm i --save @fortawesome/fontawesome-svg-core npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/vue-fontawesome@prerelease
使用
文件位置:src/main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from 'vue'
import App from './App'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faUserSecret } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
library.add(faUserSecret)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
文件位置:src/App.vue
1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="app">
<font-awesome-icon icon="user-secret" />
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
搭建框架
(1)创建根布局
跟布局用于展示顶部和左侧菜单。
在 src 目录下新建 layout 目录,并在内创建 index.vue 试图文件
文件位置:src/layout/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<template>
<el-container style="height: 100%;">
<!-- 顶部横向菜单 -->
<el-header class="top">
<!-- 顶部左侧菜单 -->
<el-menu
default-active="1"
mode="horizontal"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-menu-item index="1">处理中心</el-menu-item>
<el-sub-menu index="2">
<template #title>我的工作台</template>
<el-menu-item index="2-1">选项1</el-menu-item>
<el-menu-item index="2-2">选项2</el-menu-item>
<el-menu-item index="2-3">选项3</el-menu-item>
<el-sub-menu index="2-4">
<template #title>选项4</template>
<el-menu-item index="2-4-1">选项1</el-menu-item>
<el-menu-item index="2-4-2">选项2</el-menu-item>
<el-menu-item index="2-4-3">选项3</el-menu-item>
</el-sub-menu>
</el-sub-menu>
<el-menu-item index="3">订单管理</el-menu-item>
</el-menu>
<!-- 顶部右侧头像 -->
<el-dropdown>
<el-avatar
icon="el-icon-user-solid"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
class="user-dropdown"
></el-avatar>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>编辑信息</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item>退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-header>
<!-- 华丽分割线 -->
<el-container style="height: 100%;">
<el-menu
:default-active="defaultActiveIndexFirst"
class="el-menu-vertical-first"
:collapse="isCollapseFirst"
>
<el-menu-item
index=""
@click="toggleCollapseFirst(!isCollapseFirst)"
class="el-menu-item-first"
>
<Expand style="width: 20px;" v-if="isCollapseFirst" />
<Fold style="width: 20px;" v-else />
<template #title>{{
isCollapseFirst ? "打开菜单" : "关闭菜单"
}}</template>
</el-menu-item>
<!-- 菜单循环 开始 -->
<template v-for="(e, i) in allRoutes" :key="i">
<el-menu-item
:index="e?.path"
@click="changeCurrentActiveItemFirst(e)"
>
<i :class="e.meta?.icon"></i>
<template #title>
<span>{{ e.meta?.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 菜单循环 介绍 -->
</el-menu>
<!-- 二级菜单 -->
<el-menu
:default-active="defaultActiveIndexSecond"
class="el-menu-vertical-second"
@open="handleOpen"
@close="handleClose"
:collapse="isCollapseSecond"
:router="true"
>
<el-menu-item
@click="toggleCollapseSecond(!isCollapseSecond)"
class="el-menu-item-first"
index=""
>
<Expand style="width: 20px;" v-if="isCollapseSecond" />
<Fold style="width: 20px;" v-else />
<template #title>{{
isCollapseSecond ? "打开菜单" : "关闭菜单"
}}</template>
</el-menu-item>
<!-- 一层菜单循环 开始 -->
<template v-for="(e, i) in currentActiveItemFirst.children" :key="i">
<el-sub-menu :index="e?.path" v-if="e.children">
<template #title>
<i :class="e.meta?.icon"></i>
<span>{{ e.meta?.title }}</span>
</template>
<!-- 二层菜单循环 开始 -->
<template v-for="(ee, ii) in e.children" :key="ii">
<el-sub-menu :index="ee?.path" v-if="ee.children">
<template #title>
<i :class="ee.meta?.icon"></i>
<span>{{ ee.meta?.title }}有</span>
</template>
<!-- 三层菜单循环 开始 -->
<template v-for="(eee, iii) in ee.children" :key="iii">
<el-sub-menu :index="eee?.path" v-if="eee.children">
<template #title>
<i :class="eee.meta?.icon"></i>
<span>{{ eee.meta?.title }}</span>
</template>
</el-sub-menu>
<el-menu-item :index="eee?.path" v-else>
<i :class="eee.meta?.icon"></i>
<template #title>
<span>{{ eee.meta?.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 三层菜单循环 结束 -->
</el-sub-menu>
<el-menu-item :index="ee?.path" v-else>
<i :class="ee.meta?.icon"></i>
<template #title>
<span>{{ ee.meta?.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 二层菜单循环 结束 -->
</el-sub-menu>
<el-menu-item :index="e?.path" v-else>
<i :class="e.meta?.icon"></i>
<template #title>
<span>{{ e.meta?.title }}</span>
</template>
</el-menu-item>
</template>
<!-- 一层菜单循环 结束 -->
</el-menu>
<!-- 右下角内容容器 -->
<el-container style="height: 100%;">
<el-main style="height: 100%;overflow: auto;">
<el-breadcrumb class="breadcrumb">
<el-breadcrumb-item v-for="(e, i) in $route?.matched" :key="i">
{{ e?.meta?.title }}
</el-breadcrumb-item>
</el-breadcrumb>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</el-container>
</template>
<script>
import { mapState, mapMutations, mapGetters } from "vuex"
import { Expand, Fold } from "@element-plus/icons"
export default {
components: {
Expand,
Fold
},
data() {
return {
defaultA: 0
}
},
computed: {
...mapState({
// 外部使用需要添加前缀 layout :表示获取 layout.js 中的 store
isCollapseFirst: state => state.layout.isCollapseFirst,
isCollapseSecond: state => state.layout.isCollapseSecond,
defaultActiveIndexFirst: state => state.layout.defaultActiveIndexFirst,
defaultActiveIndexSecond: state => state.layout.defaultActiveIndexSecond,
currentActiveItemFirst: state => state.layout.currentActiveItemFirst
}),
...mapGetters(["allRoutes"])
},
methods: {
...mapMutations([
"toggleCollapseFirst",
"toggleCollapseSecond",
"changeDefaultActiveIndexFirst",
"changeDefaultActiveIndexSecond",
"changeCurrentActiveItemFirst"
]),
handleOpen(key, keyPath) {
// console.log(key, keyPath)
},
handleClose(key, keyPath) {
// console.log(key, keyPath)
}
}
}
</script>
<style>
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
overflow: hidden;
}
#app {
height: 100%;
}
.el-header.top {
background-color: #545c64;
color: var(--el-text-color-primary);
text-align: center;
line-height: 60px;
height: 60px;
display: flex;
justify-content: space-between;
}
.breadcrumb {
color: var(--el-text-color-primary);
position: absolute;
left: 20px;
top: 3px;
}
.el-menu-item-first {
background-color: rgba(0, 0, 0, 0.05);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
color: var(--el-menu-active-color) !important;
}
.el-aside {
color: var(--el-text-color-primary);
}
.el-main {
color: var(--el-text-color-primary);
position: relative;
}
.el-menu-vertical-first:not(.el-menu--collapse) {
width: 160px;
height: 100%;
}
.el-menu-vertical-second:not(.el-menu--collapse) {
width: 200px;
height: 100%;
}
.toggle-menu {
position: relative;
top: 2px;
margin: 10px;
}
.user-dropdown {
position: relative;
top: 10px;
}
</style>
(2)创建默认内容试图
内容试图用于展示各种内容,可以创建多种风格的样式
先创建一个默认样式的试图
在layout中新建modules文件夹,在其中创建default.vue文件
文件位置:src/layout/modules/default.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<template>
<section class="app-main">
<el-tabs
v-model="editableTabsValue"
type="card"
@tab-remove="removeTab"
@tab-click="handleClick"
>
<template v-for="(k, v, i) in editableTabs" :key="i">
<el-tab-pane :label="k" :name="v" v-if="i === 0"> </el-tab-pane>
<el-tab-pane :label="k" :name="v" v-else closable> </el-tab-pane>
</template>
</el-tabs>
<router-view v-slot="{ Component }">
<template v-if="Component">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</template>
</router-view>
</section>
</template>
<script>
export default {
data() {
return {
editableTabsValue: "1",
editableTabs: {
"/": "首页"
},
tabIndex: 0
}
},
computed: {
key() {
return this.$route.path
}
},
watch: {
$route(to, from) {
this.addTab()
}
},
methods: {
addTab() {
const newTabIndex = ${++this.tabIndex}
this.editableTabs[this.$route.path] = this.$route.meta.title
this.editableTabsValue = newTabIndex
},
removeTab(targetName) {
delete this.editableTabs[targetName]
},
handleClick(tab, event) {
this.$router.push(tab.props.name)
}
},
created() {
this.addTab()
}
}
</script>
<style>
.el-tabs__item {
line-height: 34px !important;
height: 34px !important;
padding: 0 10px !important;
}
</style>
(3)安装进度条
一个在页面顶部显示的进度条。官网
npm install --save nprogress
(4)动态加载路由
通过读取路由动态设置菜单,在util文件夹中新建generate-routes.js文件,动态加载菜单
文件位置:src/util/generate-routes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import router from "@/router"
import store from "@/store"
import NProgress from "nprogress" // 引入进度条
import "nprogress/nprogress.css" // 引入进度条样式
NProgress.configure({ showSpinner: false }) // 进度条配置
// 每次切换路由时动态加载路由,可以加入权限验证
router.beforeEach(async(to, from, next) => {
// 开启进度条
NProgress.start()
// 登录认证
// localStorage.removeItem("token") // 清除登录
if (to.path === "/login") {
next()
return true
}
if (!store.state.user.token) {
next({ path: "/login", replace: true })
return true
}
// 远程获取 router
// const { roles } = await store.dispatch("user/getInfo")
// 获取动议路由数据
const accessRoutes = await store.dispatch("generateRoutes", null)
// 动态添加路由
if (accessRoutes && accessRoutes?.length) {
router.addRoutes(accessRoutes)
}
// 修改左侧1级菜单默认激活
store.commit("changeDefaultActiveIndexFirst", to.matched[0].path)
// 修改左侧2级菜单默认激活
store.commit("changeDefaultActiveIndexSecond", to.path)
const currentActiveItemFirst = router
.getRoutes()
.filter(e => e?.path === to?.matched[0]?.path)
// 修改左侧1级菜单当前激活选项
store.commit("changeCurrentActiveItemFirst", currentActiveItemFirst[0])
next()
})
router.afterEach(() => {
// 关闭进度条
NProgress.done()
})
到这里就搭建好了一个统一的后台框架,只需要按照需要引入自己需要的组件即可。
登录系统
(1)创建 store 存储
在 src/store/modules 文件中创建 user.js
文件位置:src/store/modules/user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { login } from "@/api/user"
import { ElMessage } from "element-plus"
export default {
state: {
// Token
token: localStorage.getItem("token")
},
getters: {},
mutations: {
// 设置 Token
setToken(state, token) {
state.token = token
localStorage.setItem("token", token)
}
},
actions: {
// 要不获取 Token
asyncGetToken({ commit }, data) {
return new Promise(resolve => {
login(data)
.then(response => {
const { token, message } = response
if (token) {
commit("setToken", token)
ElMessage({
type: "success",
showClose: true,
message: message || "成功",
duration: 5 * 1000
})
resolve(message)
} else {
ElMessage({
type: "error",
showClose: true,
message: message || "失败",
duration: 5 * 1000
})
}
})
.catch(error => {
console.log(error)
})
})
}
},
modules: {}
}
(2)创建 router 路由
在 src/router/modules 文件中创建 common.js
文件位置:src/store/modules/common.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const testRouter = {
path: "/loginmod",
component: () => import("../../views/login/Login.vue"),
name: "LoginModule",
meta: { title: "登录页", icon: "el-icon-menu" },
children: [
{
path: "/login",
component: () => import("../../views/login/Login.vue"),
name: "Login",
meta: { title: "登录页", icon: "el-icon-menu" }
}
]
}
export default testRouter
(3)创建 login 视图
在 src/views 目录下创建 login.vue 文件
文件位置:src/views/login.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<template>
<div id="login1">
<el-form
ref="formData"
:model="formData"
:rules="formRule"
class="login-form"
>
<el-header class="form-header">登录</el-header>
<el-form-item label="账户" prop="username">
<el-input v-model="formData.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password"></el-input>
</el-form-item>
<el-form-item class="button-group">
<el-button type="primary" @click="submitForm('formData')">
登录
</el-button>
<el-button @click="resetForm('formData')">重置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapActions } from "vuex"
export default {
data() {
return {
formData: { username: "admin", password: "admin" },
formRule: {
username: [
{
required: true,
message: "账户不能空",
trigger: "blur"
},
{
min: 5,
max: 16,
required: true,
message: "账户长度[5-16]",
trigger: "change"
}
],
password: [
{
required: true,
message: "密码不能空",
trigger: "blur"
},
{
min: 5,
max: 16,
required: true,
message: "密码长度[5-16]",
trigger: "change"
}
]
}
}
},
methods: {
...mapActions(["asyncGetToken"]),
submitForm(formData) {
this.$refs[formData].validate(valid => {
if (valid) {
this.asyncGetToken(this.formData).then(message => {
this.$router.replace("/")
})
} else {
console.log("请重新输入")
return false
}
})
},
resetForm(formData) {
this.$refs[formData].resetFields()
}
}
}
</script>
<style scoped>
#login1 {
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
background-color: rgb(240, 240, 240);
}
.login-form {
width: 360px;
position: fixed;
left: 50%;
top: 50%;
overflow: hidden;
padding: 10px 80px;
border-radius: 8px;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0px 0px 4px 2px rgba(205, 205, 205, 0.8);
animation: mymove 1s ease-in-out alternate;
}
.form-header {
text-align: center;
font-weight: 900;
font-size: 25px;
margin-top: 20px;
}
.button-group {
text-align: center;
}
@keyframes mymove {
0% {
width: 0px;
height: 5px;
}
10% {
width: 50px;
height: 5px;
}
15% {
width: 100px;
height: 5px;
}
20% {
width: 150px;
height: 5px;
}
25% {
width: 200px;
height: 5px;
}
30% {
width: 250px;
height: 5px;
}
35% {
width: 300px;
height: 5px;
}
40% {
width: 350px;
height: 5px;
}
45% {
width: 450px;
height: 5px;
}
50% {
width: 500px;
height: 5px;
}
55% {
height: 30px;
}
60% {
height: 60px;
}
65% {
height: 90px;
}
70% {
height: 120px;
}
75% {
height: 150px;
}
80% {
height: 180px;
}
85% {
height: 210px;
}
90% {
height: 240px;
}
95% {
height: 240px;
}
100% {
height: 300px;
}
}
</style>
退出系统
(1)添加退出store
在 store 中添加清除 token 的方法,并且发送请求
文件位置:src/store/modules/user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { login, logout } from "@/api/user"
import { ElMessage } from "element-plus"
import router from "@/router"
export default {
state: {
// Token
token: localStorage.getItem("token")
},
getters: {},
mutations: {
// 设置 Token
setToken(state, token) {
...
},
// 清除 Token
removeToken(state) {
state.token = null
localStorage.removeItem("token")
}
},
actions: {
// 异步获取 Token
asyncGetToken({ commit }, data) {
...
},
// 异步 退出
asyncRemoveToken({ commit }, data) {
return new Promise(resolve => {
logout()
.then(response => {
const { message } = response
commit("removeToken")
router.replace("/login")
ElMessage({
type: "success",
showClose: true,
message: message || "成功",
duration: 5 * 1000
})
resolve(message)
})
.catch(error => {
console.log(error)
})
})
}
},
modules: {}
}
(2)添加退出事件
找到主界面中的退出按钮,并为其添加点击事件
文件位置:src/layout/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
<!-- 顶部右侧头像 -->
<el-dropdown>
<el-avatar
icon="el-icon-user-solid"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
class="user-dropdown"
></el-avatar>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>编辑信息</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
...
(3)添加退出函数
在 js 中添加 logout 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { mapState, mapMutations, mapGetters } from "vuex"
import { Expand, Fold } from "@element-plus/icons"
export default {
components: {
...
},
data() {
...
},
computed: {
...
...mapGetters(["allRoutes"])
},
methods: {
...
}
}
获取用户信息
(1)添加 store 存储
在登录成功后获取用户信息
在 store 中添加清除 token 的方法,并且发送请求
文件位置:src/store/modules/user.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { login, logout, getInfo } from "@/api/user"
import { ElMessage } from "element-plus"
import router from "@/router"
export default {
state: {
// Token
token: localStorage.getItem("token"),
// 用户信息
userinfo: localStorage.getItem("userinfo")
},
getters: {},
mutations: {
// 设置 Token
setToken(state, token) {
...
},
// 清除 Token
removeToken(state) {
...
},
// 设置 userinfo
setUserinfo(state, userinfo) {
state.userinfo = userinfo
localStorage.setItem("userinfo", userinfo)
},
// 清除 userinfo
removeUserInfo(state) {
state.token = null
localStorage.removeItem("userinfo")
}
},
actions: {
// 异步获取 Token
asyncGetToken({ commit }, data) {
return new Promise(resolve => {
logout()
.then(response => {
const { message } = response
commit("removeToken")
commit("removeUserInfo")
router.replace("/login")
ElMessage({
type: "success",
showClose: true,
message: message || "成功",
duration: 5 * 1000
})
resolve(message)
})
.catch(error => {
console.log(error)
})
})
},
// 异步 退出
asyncRemoveToken({ commit }) {
...
},
// 异步 获取用户信息
asyncGetUserinfo({ state, commit }) {
return new Promise(resolve => {
getInfo(state.token)
.then(response => {
const { data, message } = response
commit("setUserinfo", data)
resolve(message)
})
.catch(error => {
console.log(error)
})
})
}
},
modules: {}
}
(2)添加事件
在 login.vue 中添加事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
...
</template>
<script>
import { mapActions } from "vuex"
export default {
data() {
return {
...
}
},
methods: {
...mapActions(["asyncGetToken", "asyncGetUserinfo"]),
submitForm(formData) {
this.$refs[formData].validate(valid => {
if (valid) {
this.asyncGetToken(this.formData).then(message => {
// 获取用户信息
this.asyncGetUserinfo().then(message2 => {
console.log(message2)
})
this.$router.replace("/")
})
} else {
console.log("请重新输入")
return false
}
})
},
resetForm(formData) {
this.$refs[formData].resetFields()
}
}
}
</script>
<style scoped>
...
</style>
表格数据
(1)新建 index.vue 文件
文件位置:src/views/user/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<template>
<el-button-group>
<el-button type="primary" icon="el-icon-edit" size="mini"></el-button>
<el-button type="success" icon="el-icon-plus" size="mini"></el-button>
<el-button
type="info"
icon="el-icon-brush"
size="mini"
@click="clearSelection"
></el-button>
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="deleteRows"
></el-button>
</el-button-group>
<el-table
fit
border
resizable
stripe
show-header
highlight-current-row
ref="multipleTable"
tooltip-effect="dark"
style="width: 100%"
:data="searchFilter ? filterData : tableData"
@current-change="handleCurrentChange"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="35" fixed="left">
</el-table-column>
<template v-for="(e, i) in tableDataFormat" :key="i">
<!-- 字符串或者数字类型 -->
<el-table-column
sortable
width="70"
:prop="i"
:label="e.label"
v-if="e.type === 'number' || e.label === 'ID'"
></el-table-column>
<!-- 图片类型 -->
<el-table-column v-else-if="e.type === 'image'" label="头像">
<img src="../../assets/avatar.gif" height="30" />
</el-table-column>
<!-- 日期类型 -->
<el-table-column
v-else-if="e.type === 'date'"
label="生日"
min-width="120"
:prop="i"
:formatter="dateformatter"
>
</el-table-column>
<!-- 字符串或者数字类型 -->
<el-table-column
sortable
show-overflow-tooltip
:prop="i"
:label="e.label"
v-else-if="e.type === 'string' || e.type === 'number'"
></el-table-column>
<!-- 枚举类型 -->
<el-table-column
show-overflow-tooltip
v-else-if="e.type === 'enum'"
:label="e.label"
>
<template #default="scope">
<div style="display:flex;">
<el-tag v-for="(ee, ii) in scope.row[i]" :key="ii">{{ ee }}</el-tag>
</div>
</template>
</el-table-column>
</template>
<!-- 操作 -->
<el-table-column fixed="right" label="操作" width="50">
<template #default="scope">
<el-button
@click.prevent="deleteRow(scope.$index)"
type="text"
size="small"
>
移除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 表格数据 结束 -->
</template>
<script>
import { getList } from "@/api/user"
export default {
data() {
return {
tableDataFormat: {
id: { label: "ID", type: "number" },
roles: { label: "角色", type: "enum" },
introduction: { label: "简介", type: "string" },
avatar: { label: "头像", type: "image" },
username: { label: "账户", type: "string" },
password: { label: "密码", type: "string" },
birthday: { label: "生日", type: "date" },
sex: { label: "性别", type: "enum" }
},
filterData: [],
tableData: [],
currentRow: [],
multipleSelection: []
}
},
methods: {
// 多选
handleSelectionChange(val) {
this.multipleSelection = val
},
// 单选
handleCurrentChange(val) {
this.currentRow = val
},
// 删除单行选择
deleteRow(index) {
this.tableData.splice(index, 1)
},
// 删除多行选择
deleteRows() {
this.tableData = this.tableData.filter(
e => !this.multipleSelection.includes(e)
)
},
// 多选表格,清空用户的选择
clearSelection() {
this.$refs.multipleTable.clearSelection()
},
// 日期类型数据格式化
dateformatter(row, column, cellValue, index) {
const col = cellValue.split("-")
return col[0] + "年" + col[1] + "月" + col[2] + "日"
}
},
created() {
// 加载用户数据
getList().then(result => {
this.tableData = result.data
})
}
}
</script>
<style scoped></style>
(2)添加路由
将 用户管理 的component属性绑定到 上面的视图上
文件位置:src/router/modules/system.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Layout */
import Layout from "@/layout"
import Default from "@/layout/modules/default"
const systemRouter = {
...
children: [
{
...
children: [
{
path: "/sysmod/system/user",
name: "SysUser",
meta: { title: "用户管理", icon: "el-icon-menu" },
component: () => import("../../views/user/index.vue")
},
{
...
},
{
...
},
{
...
}
]
}
]
}
export default systemRouter
(3)搜索数据
在按钮组上添加 搜索 输入框,并且添加过滤数据的属性
文件位置:src/views/user/index.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template>
<el-row>
<el-input
size="mini"
clearable
placeholder="输入搜索关键词"
style="width: 200px;position:relative;bottom:1px;"
v-model="searchFilter"
@input="filterHandler"
/>
<el-button-group>
...
</el-button-group>
</el-row>
<el-table
...
</el-table>
</template>
<script>
import { getList } from "@/api/user"
export default {
data() {
return {
filterData: [],
tableData: [],
currentRow: [],
multipleSelection: [],
searchFilter: ""
}
},
methods: {
...
,
// 数据筛选
filterHandler() {
this.filterData = this.tableData.filter(e => {
for (const k in e) {
if (Object.hasOwnProperty.call(e, k)) {
const element = e[k]
if (k === "avatar") {
// 排除无需筛选字段
continue
}
if (
typeof element === "number" &&
element === parseInt(this.searchFilter)
) {
return true
}
if (
typeof element === "string" &&
element.match(this.searchFilter)
) {
return true
}
}
}
})
}
},
created() {
...
}
}
</script>
<style scoped></style>
(4)添加数据
在页面添加 表单 数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!-- 添加修改对话框 开始 -->
<el-dialog v-model="dialogOuterVisible" :title="dialogOuterTitle" center>
<template #default>
<!-- 添加修改表单 -->
<el-form ref="dialogFormData" label-width="60px">
<template v-for="(e, key) in tableDataFormat" :key="key">
<!-- 数字类型 -->
<el-form-item
:label="e.label"
v-if="e.type === 'number' && e.label !== 'ID'"
>
<el-input v-model="dialogFormData[key]" type="number"></el-input>
</el-form-item>
<!-- 字符串类型 -->
<el-form-item :label="e.label" v-else-if="e.type === 'string'">
<el-input v-model="dialogFormData[key]" type="text"></el-input>
</el-form-item>
<!-- 数组类型 -->
<el-form-item :label="e.label" v-else-if="e.type === 'array'">
<el-select
multiple
v-model="dialogFormData[key]"
placeholder="请选择"
>
<el-option
v-for="(ee, ii) in tableCurrentRow[key]"
:key="ii"
:label="ee"
:value="ee"
></el-option>
</el-select>
</el-form-item>
<!-- 日期类型 -->
<el-form-item :label="e.label" v-else-if="e.type === 'date'">
<el-date-picker
v-model="dialogFormData[key]"
type="date"
placeholder="请选择日期"
style="width: 100%"
></el-date-picker>
</el-form-item>
</template>
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogOuterVisible = false">取消</el-button>
<el-button type="primary" @click="dialogFormSubmit">
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 添加修改对话框 结束 -->
添加变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { getList } from "@/api/user"
export default {
data() {
return {
// 表格数据结构
...
// 筛选后的表格数据
...
dialogOuterTitle: "",
dialogOuterVisible: false,
dialogFormData: {}
}
},
methods: {
...
// 添加数据
plusData() {
this.dialogFormData = {}
this.dialogOuterVisible = true
this.dialogOuterTitle = "新增"
},
// 修改数据
editRow(row, index) {
this.dialogFormData = Object.create(this.tableCurrentRow)
this.dialogOuterVisible = true
this.dialogOuterTitle = "编辑"
},
// 新增或者修改提交
dialogFormSubmit() {
if (this.dialogOuterTitle === "新增") {
// 新增
console.log("新增")
} else {
// 修改
console.log("修改")
}
this.dialogOuterVisible = false
}
},
created() {
// 加载用户数据
...
}
}
加载组件
可以按需加载组件
(1)安装 js-cookie
用于读写 cookie 。官网
npm install --save js-cookie
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import Cookies from 'js-cookie'
import Cookies from "js-cookie"
export default {
mounted() {
// 写入 cookie
Cookies.set("name", "value") // 无过期时间
Cookies.set("name", "value", { expires: 7 }) // 7天过期
Cookies.set("name", "value", { expires: 7, path: "" }) // 设置支队当前路径有效
<span class="hljs-comment">// 获取 cookie</span>
Cookies.get(<span class="hljs-string">"name"</span>) <span class="hljs-comment">// => 'value'</span>
Cookies.get(<span class="hljs-string">"nothing"</span>) <span class="hljs-comment">// => undefined</span>
Cookies.get() <span class="hljs-comment">// => { name: 'value' } // 获取全部</span>
<span class="hljs-comment">// 删除 Cookie</span>
Cookies.remove(<span class="hljs-string">"name"</span>)
<span class="hljs-comment">// path 一个字符串,指示cookie可见的路径。</span>
Cookies.set(<span class="hljs-string">"name"</span>, <span class="hljs-string">"value"</span>, { path: <span class="hljs-string">""</span> })
Cookies.remove(<span class="hljs-string">"name"</span>) <span class="hljs-comment">// fail!</span>
Cookies.remove(<span class="hljs-string">"name"</span>, { path: <span class="hljs-string">""</span> }) <span class="hljs-comment">// removed!</span>
<span class="hljs-comment">// domain 一个字符串,指示cookie应该可见的有效域。</span>
Cookies.set(<span class="hljs-string">"name"</span>, <span class="hljs-string">"value"</span>, { domain: <span class="hljs-string">"localhost"</span> })
Cookies.get(<span class="hljs-string">"name"</span>) <span class="hljs-comment">// => undefined (need to read at 'subdomain.site.com')</span>
Cookies.get(<span class="hljs-string">"name"</span>) <span class="hljs-comment">// 在 subdomain.site.com 中课获取</span>
Cookies.remove(<span class="hljs-string">"name"</span>, { domain: <span class="hljs-string">"subdomain.site.com"</span> })
<span class="hljs-comment">// secure true or false, 表示cookie传输是否需要安全协议(https)。默认 false</span>
Cookies.set(<span class="hljs-string">"name"</span>, <span class="hljs-string">"value"</span>, { secure: <span class="hljs-literal">true</span> })
Cookies.get(<span class="hljs-string">"name"</span>) <span class="hljs-comment">// => 'value'</span>
Cookies.remove(<span class="hljs-string">"name"</span>)
<span class="hljs-comment">// sameSite 一个字符串,用于控制浏览器是否在发送跨站点请求时同时发送cookie。默认 None</span>
<span class="hljs-comment">// 可以设置三个值:</span>
<span class="hljs-comment">// Strict 最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。</span>
<span class="hljs-comment">// Lax 规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外</span>
<span class="hljs-comment">// None Chrome 将Lax 为默认设置。显式关闭SameSite。必须同时设置Secure属性(通过 HTTPS 发送),否则无效。</span>
Cookies.set(<span class="hljs-string">"name"</span>, <span class="hljs-string">"value"</span>, { sameSite: <span class="hljs-string">"strict"</span> })
Cookies.get(<span class="hljs-string">"name"</span>) <span class="hljs-comment">// => 'value'</span>
Cookies.remove(<span class="hljs-string">"name"</span>)
}
}
(2)安装 jszip 和 FileSaver
jszip 一个压缩插件。官网
FileSaver 一个文件保存插件。官网
1 2npm install jszip --save npm install file-saver --save
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import FileSaver from 'jszip/vendor/FileSaver';
var zip = new JSZip();
zip.file("Hello.txt", "Hello World\n");
var img = zip.folder("images"); // 新建文件夹
img.file("smile.gif", imgData, {base64: true}); // 文件夹中放 smile.gif 图片
zip.generateAsync({type:"blob"})
.then(function(content) {
// see FileSaver.js
saveAs(content, "example.zip"); // 一次性下载多个文件
});
(3)安装 Echarts
超牛的图标组件库 。官网
npm install echarts --save
使用
全部引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import * as echarts from 'echarts';
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('main'));
// 绘制图表
myChart.setOption({
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
});
按需引入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入柱状图图表,图表后缀都为 Chart
import { BarChart } from 'echarts/charts';
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
DatasetComponentOption,
TransformComponent
} from 'echarts/components';
// 标签自动布局,全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);
// 接下来的使用就跟之前一样,初始化图表,设置配置项
var myChart = echarts.init(document.getElementById('main'));
myChart.setOption({
// ...
});
(4)安装 clipboard-polyfill
剪切板工具,clipboard的升级版。官网
npm install clipboard-polyfill --save
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Using the `clipboard/text` build saves code size.
// This is recommended if you only need to copy text.
import * as clipboard from "clipboard-polyfill/text";
function handler() {
clipboard.writeText("This text is plain.").then(
() => { console.log("success!"); },
() => { console.log("error!"); }
);
}
window.addEventListener("DOMContentLoaded", function () {
const button = document.body.appendChild(document.createElement("button"));
button.textContent = "Copy";
button.addEventListener("click", handler);
});
指定MIME 格式写入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import * as clipboard from "clipboard-polyfill";
async function handler() {
console.log("Previous clipboard contents:", await clipboard.read());
const item = new clipboard.ClipboardItem({
"text/html": new Blob(
["<i>Markup</i> <b>text</b>. Paste me into a rich text editor."],
{ type: "text/html" }
),
"text/plain": new Blob(
["Fallback markup text. Paste me into a rich text editor."],
{ type: "text/plain" }
),
});
await clipboard.write([item]);
}
window.addEventListener("DOMContentLoaded", function () {
const button = document.body.appendChild(document.createElement("button"));
button.textContent = "Copy";
button.addEventListener("click", handler);
});
(5)安装 dropzone
一个拖拽上传插件 官网
npm install --save dropzone
页面
<form action="/target" class="dropzone"></form>
使用
1
2
3
4
5
6
7
8
9
10
11
import Dropzone from "dropzone";
// Make sure Dropzone doesn't try to attach itself to the
// element automatically.
// This behaviour will change in future versions.
Dropzone.autoDiscover = false;
let myDropzone = new Dropzone("#my-form");
myDropzone.on("addedfile", file => {
console.log(File added: ${file.name});
});
常用事件:
- addedfile : 添加文件是发生
- removefile : 手动从服务器删除文件时发生
- success : 上传成功后发生
- complete:当文件上传成功或失败之后发生。
- canceled:当文件在上传时被取消的时候发生。
- maxfilesreached:当文件数量达到最大时发生。
- maxfilesexceeded:当文件数量超过限制时发生。
(6)安装 fusejs
一个模糊搜素的强大工具。官网
npm install --save fuse.js
使用
文件 list.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Fuse from 'fuse.js'
[
{
"title": "Old Man's War",
"author": {
"firstName": "John",
"lastName": "Scalzi"
}
},
{
"title": "The Lock Artist",
"author": {
"firstName": "Steve",
"lastName": "Hamilton"
}
},
{
"title": "HTML5",
"author": {
"firstName": "Remy",
"lastName": "Sharp"
}
},
{
"title": "Right Ho Jeeves",
"author": {
"firstName": "P.D",
"lastName": "Woodhouse"
}
}
]
页面
<input type="search">
脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const options = {
// isCaseSensitive: false,
// includeScore: false,
// shouldSort: true,
// includeMatches: false,
// findAllMatches: false,
// minMatchCharLength: 1,
// location: 0,
// threshold: 0.6,
// distance: 100,
// useExtendedSearch: false,
// ignoreLocation: false,
// ignoreFieldNorm: false,
keys: [ // 搜索的关键词
"title",
"author.firstName"
]
};
const fuse = new Fuse(list, options);
// Change the pattern
const pattern = "关键词"
return fuse.search(pattern) // 搜索关键词
(7)安装 tui-editor
一个富文本、markdown文档编辑工具。官网 github
Vue的组件:TOAST UI Editor
安装
npm install --save @toast-ui/vue-editor
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<editor />
</template>
<script>
import '@toast-ui/editor/dist/toastui-editor.css';
import { Editor } from '@toast-ui/vue-editor';
export default {
components: {
editor: Editor
}
};
</script>
Props参数
| Name | Type | Default | Description |
|---|---|---|---|
| initialValue | String | ’’ | 初始值 |
| initialEditType | String | ‘markdown’ | 初始时编辑器的类型 (markdown, wysiwyg). |
| options | Object | following defaultOptions |
初始化编辑器的初始参数选项对象 |
| height | String | ‘300px’ | 高度 |
| previewStyle | String | ‘vertical’ | Markdown 编辑器的预览模式 (tab, vertical). |
默认参数选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const defaultOptions = {
minHeight: '200px',
language: 'en-US',
useCommandShortcut: true,
usageStatistics: true,
hideModeSwitch: false,
toolbarItems: [
['heading', 'bold', 'italic', 'strike'],
['hr', 'quote'],
['ul', 'ol', 'task', 'indent', 'outdent'],
['table', 'image', 'link'],
['code', 'codeblock'],
['scrollSync'],
]
};
如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<editor
:initialValue="editorText"
:options="editorOptions"
height="500px"
initialEditType="wysiwyg"
previewStyle="vertical"
/>
</template>
<script>
import '@toast-ui/editor/dist/toastui-editor.css';
import { Editor } from '@toast-ui/vue-editor';
export default {
components: {
editor: Editor
},
data() {
return {
editorText: 'This is initialValue.',
editorOptions: {
hideModeSwitch: true
}
};
}
};
</script>
方法
- setValue
- getValue
- setHtml
- getHtml
如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<markdown-editor v-model="content" />
</template>
<script>
import MarkdownEditor from '@/components/MarkdownEditor'
export default {
data() {
return {
content: '',
}
}
}
</script>
作业
按照课件搭建框架
</article>

浙公网安备 33010602011771号