├── .editorconfig
├── .env.development
├── .env.production
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── README.md
├── babel.config.js
├── data.json
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── api
│ └── index.ts
├── assets
│ └── logo.png
├── components
│ ├── CommonList.vue
│ ├── HelloWorld.vue
│ └── JsonTable
│ │ ├── README.md
│ │ ├── composition
│ │ └── useSearchForm.js
│ │ ├── config.js
│ │ ├── index.vue
│ │ └── tableDataMock.js
├── composition
│ ├── useTagViewApi.ts
│ └── useThemeApi.ts
├── layout
│ ├── components
│ │ ├── Drawer
│ │ │ └── index.vue
│ │ ├── Header
│ │ │ └── index.vue
│ │ ├── Sidebar
│ │ │ ├── Sidebar.vue
│ │ │ └── index.vue
│ │ ├── Tagsview
│ │ │ └── index.vue
│ │ └── Wrapper
│ │ │ └── index.vue
│ └── index.vue
├── main.ts
├── mock
│ ├── data copy.json
│ ├── data.json
│ └── server.js
├── request
│ ├── example.js
│ └── index.js
├── router
│ ├── defaultRoutes
│ │ └── index.ts
│ ├── index.ts
│ └── staticRoutes
│ │ └── index.ts
├── shims-vue.d.ts
├── store
│ ├── controls
│ │ └── index.ts
│ └── index.ts
├── style
│ ├── style.less
│ ├── theme.less
│ ├── transition.less
│ └── variable.less
├── typed-request.d.ts
├── typed-scss.d.ts
└── views
│ ├── Button.vue
│ ├── Component.vue
│ ├── Date.vue
│ ├── Document.vue
│ ├── Home.vue
│ ├── Image.vue
│ ├── Inner.vue
│ ├── Login.vue
│ ├── NotFound.vue
│ ├── Tab.vue
│ └── Table.vue
├── tsconfig.json
└── vue.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | VUE_APP_TITLE=APP_DEVELOPMENT
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | VUE_APP_TITLE=APP_PRODUCTION
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/essential',
8 | 'eslint:recommended',
9 | '@vue/typescript/recommended',
10 | ],
11 | parserOptions: {
12 | ecmaVersion: 2020,
13 | parser: "@typescript-eslint/parser"
14 | },
15 | rules: {
16 | "@typescript-eslint/no-unused-vars": "off",
17 | "@typescript-eslint/no-explicit-any": "off",
18 | "prefer-const": 'off',
19 | "semi": ["error", "always"]
20 | }
21 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 |
5 | install:
6 | - npm install
7 |
8 | script:
9 | - npm run build
10 |
11 | after_success:
12 | - cd ./dist
13 | - git init
14 | - git config --global user.name "${U_NAME}"
15 | - git config --global user.email "${U_EMAIL}"
16 | - git add .
17 | - git commit -m "Automatically update from travis-ci"
18 | - git push --quiet --force "https://${GH_TOKEN}@${GH_REF}" master:${P_BRANCH}
19 |
20 | branches:
21 | only:
22 | - master
23 | env:
24 | global:
25 | - GH_REF: github.com/Mstian/Vue-Onepiece-Admin.git
26 | - GH_TOKEN:d3eafcdeb01474f512d0b6066edca4257d606980
27 | - P_BRANCH:gh-pages
28 | - U_EMAIL:1404746239@qq.com
29 | - U_NAME:Mstian
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > 源码地址:[https://github.com/Mstian/Vue-Onepiece-Admin](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FMstian%2FVue-Onepiece-Admin)
2 |
3 | # Vue3.0 ElementPlus
4 |
5 | ## Project setup
6 | ```
7 | npm install
8 | ```
9 |
10 | ## Start Mock Server
11 | ```
12 | npm run mock
13 | ```
14 |
15 | ### Compiles and hot-reloads for development
16 | ```
17 | npm run serve
18 | ```
19 |
20 | ### Compiles and minifies for production
21 | ```
22 | npm run build
23 | ```
24 |
25 | #### 欢迎使用VUE3.0 + ElementPlus 后台管理模板
26 |
27 | UI库文档: https://element-plus.gitee.io/#/zh-CN
28 |
29 | 该项目基于Vuecli 使用Vue3 + 最新版ElementPlus 构建简单版后台管理系统
30 |
31 | 基本功能:(Home页面有详细介绍可参考)
32 | 通过路由自动生成侧边栏menu
33 | 打开menu可以自动生成标签页
34 | 可以使用less变量控制激活颜色
35 | 还可以通过less变量控制主题色
36 |
37 | 有需要生成menu的路由单独可以配置在一个文件,将不需要生成menu的路由配置在另外一个文件。
38 | 没有权限系统,没有登录功能等等。
39 |
40 | 简单,开箱即用。可以完全胡乱修改。可以作为Vue3项目基本参考。
41 |
42 | 最后,求个star。源码地址:[https://github.com/Mstian/Vue-Onepiece-Admin](https://github.com/Mstian/Vue-Onepiece-Admin)
43 |
44 | 
45 |
46 | ##### 在线预览地址:http://admin.tianleilei.cn/#/
47 |
48 | ### 开发过程中遇到问题以及解决方法
49 |
50 | 1. scss in typescript (sass问题较多,现已采用less)
51 | 报错:`Cannot find module ‘./index.module.scss‘ or its corresponding type declarations.ts(2307)`
52 | 解决: 需要添加类型声明文件
53 | https://blog.csdn.net/qq_41804324/article/details/109388570
54 | https://juejin.cn/post/6844903560056930311
55 |
56 | 2. 递归组件
57 | 组件自身调用自身 参考sideBar组件
58 |
59 | 3. pug(jade)模板
60 | 文档:https://www.pugjs.cn/api/getting-started.html
61 |
62 | 4. resolvePath
63 | 使用path模块高效的处理路径拼接
64 |
65 | 5. watch 与watchEffect区别
66 | watch不会立即执行 watchEffect会立即执行 并且可以手动取消监听
67 |
68 | 6. 新版transition使用
69 | 参考 layout /plan/src/layout/index.vue
70 |
71 | 7. 浏览器控制台警告Source Map
72 | https://blog.csdn.net/sundacheng1989/article/details/51118865
73 |
74 | 8. 浏览器控制台警告 HMR API usage is out of date.
75 | https://forum.vuejs.org/t/hmr-api-usage-is-out-of-date-mutiple-warnings/107633
76 |
77 | 9. es-lint快速修复
78 | npm run lint -- --fix
79 |
80 | 10. vue-router4 404页面*配置 参考router->defaultRouter.ts
81 |
82 | 11. typescript中导入模块几种方式
83 | https://segmentfault.com/a/1190000018249137
84 |
85 | 12. json-server mock数据
86 | json-server文档地址:https://github.com/typicode/json-server
87 |
88 |
89 | ### 更新:
90 |
91 | ##### 2021-01-09周六
92 |
93 | 新增配置化生成表单查询数据生成表格组件,以下为组件使用说明文档:
94 | ### 功能1:表单查询
95 | 表单支持类型:
96 | 1. input
97 | 2. select
98 | 3. cascader
99 | 4. date (date datetimerange)
100 |
101 | 表格支持功能:
102 | 字符串,图片预览(hover),json解析,数字解析,操作栏,select选择
103 |
104 | 功能预览图:
105 | 
106 |
107 | 以下为组件使用示例 以及配置项示例
108 |
109 | ```
110 |
118 |
119 |
120 |
121 | 新增
122 |
123 |
124 |
125 |
126 | 删除
127 | 查看
128 |
129 |
130 | ```
131 | Props:
132 | `searchColumns`: 表单查询属性配置
133 | `tableColumns`: 查询结果行字段配置
134 | `service`: 请求,主要是配置查询请求
135 | `options`: 表格设置
136 | `onformchange`: 表单查询项change事件监听 参数为查询属性
137 | `rowselectchange`: 带有select的表格选择时的select监听事件 参数为当前选中项
138 |
139 | #### 表单查询属性:searchColumns Array
140 |
141 | 配置项说明:
142 |
143 | `label`: String 表单项显示名称
144 | `prop`: String 表单项属性(传给后端的字段)
145 | `clearable`: Boolean 表单项内容是否可清除
146 | `placeholder`: String 表单项placeholder
147 | `isSelect`: Boolean 是否是select框(默认是input)
148 | `options`: Array Select框和Cascader的options
149 | `isCascader`: Boolean 是否是级联
150 | `isTime`: String [date, datetimerange] 日期 或者日期时间范围(默认时间是Date格式内部已做转换处理为YYYY-MM-DD HH:MM:SS格式 如果需要时间戳格式可在组件源码中自行修改)
151 |
152 | 配置示例:
153 | ```
154 | [
155 | {
156 | label: '姓名',
157 | prop: 'name',
158 | clearable: true,
159 | placeholder: "请输入姓名"
160 | },
161 | {
162 | label: '性别',
163 | prop: 'sex',
164 | clearable: true,
165 | placeholder: "请选择",
166 | isSelect: true,
167 | options: [
168 | {
169 | prop: 'male',
170 | name: '男'
171 | },
172 | {
173 | prop: 'female',
174 | name: '女'
175 | }
176 | ]
177 | },
178 | {
179 | label: '技能',
180 | prop: 'skill',
181 | clearable: true,
182 | placeholder: "请选择",
183 | isCascader: true,
184 | options: [
185 | {
186 | value: "basic",
187 | label: "Basic",
188 | children: [
189 | {
190 | value: "layout",
191 | label: "Layout 布局"
192 | },
193 | {
194 | value: "color",
195 | label: "Color 色彩"
196 | },
197 | {
198 | value: "typography",
199 | label: "Typography 字体"
200 | },
201 | {
202 | value: "icon",
203 | label: "Icon 图标"
204 | },
205 | {
206 | value: "button",
207 | label: "Button 按钮"
208 | }
209 | ]
210 | }
211 | ]
212 | },
213 | {
214 | label: '出生日期',
215 | prop: 'born',
216 | clearable: true,
217 | placeholder: "选择日期",
218 | isTime: 'date',
219 | valueFormat: '',
220 | defaultTime: []
221 | }
222 | ]
223 | ```
224 |
225 |
226 | #### table表格各属性:
227 | 1. options Object 表格展现形式配置
228 |
229 | 配置项说明:
230 | `canCheck`: Boolean 表格是否可勾选
231 | `hasIndex`: Boolean 表格是否有序号
232 | `checkFixed`: String [left right] 勾选checkbox固定位置
233 | `indexFixed`: String [left right] 序号固定位置
234 | `opW`: Number 操作栏宽度(当操作栏按钮较多时需要较宽宽度,默认为150)
235 | `autoRequest`: Boolean 是否进入页面执行一次数据请求 默认为false
236 | `startUpdate`: Date.now() // 监听该项有变化时更新请求
237 | 配置示例:
238 | ```
239 | {
240 | canCheck: true, // 是否可选择
241 | hasIndex: true, // 是否有序号
242 | checkFixed: 'left', // 选择固定位置
243 | indexFixed: 'left', // 表序号固定位置
244 | opW: 150,// 操作栏宽度
245 | autoRequest: true, // 自动请求(第一次加载默认请求)
246 | startUpdate: Date.now()
247 | }
248 | ```
249 | 2. tableColumns 查询结果行字段配置
250 | 属性:tableColumns Array
251 |
252 | 配置项说明:
253 | `prop`: String 对应后端返回表格数据字段
254 | `label`: String 表格当前列名称
255 | `width`: Number 当前列宽度
256 | `expandFunc`: Boolean 是否有扩展功能(扩展功能包括图片预览,重写数据等等,当传递imgW,isMultiCell,render时该属性必传)
257 | `imgW`: Number 图片预览时传递此参数,为预览图片宽度(图片默认是以tooltip展示的)
258 | `isMultiCell`: Boolean 是否要重写数据(场景:后端返回json字符串,前端需要取其中某一个属性,或者后端给的是0,1这样的标识,需要前端转义为汉字 是 或者否)
259 | `render`: Function 配合isMultiCell使用参数为当前表格的行数据,可以return可渲染数据,可以参考配置示例
260 |
261 | 配置示例
262 | ```
263 | [
264 | {
265 | prop: 'name',
266 | label: '姓名',
267 | width: 150,
268 | overflow: true
269 | },
270 | {
271 | prop: 'age',
272 | label: '年龄',
273 | width: 150,
274 | overflow: true
275 | },
276 | { // 图片预览
277 | prop: 'avatar',
278 | label: '头像',
279 | width: 150,
280 | imgW: 300, // 设置该项表示预览图片
281 | expandFunc: true // 是否有扩展功能,启用表格列插槽
282 | },
283 | {
284 | prop: 'sex',
285 | label: '性别',
286 | width: 150,
287 | overflow: true
288 | },
289 | {
290 | prop: 'born',
291 | label: '出生日期',
292 | width: 150,
293 | overflow: true
294 | },
295 | {
296 | prop: 'phone',
297 | label: '电话',
298 | width: 150,
299 | overflow: true
300 | },
301 | {
302 | prop: 'zip',
303 | label: '邮编',
304 | width: 150,
305 | overflow: true
306 | },
307 | {
308 | prop: 'province',
309 | label: '省份',
310 | width: 150,
311 | overflow: true
312 | },
313 | {
314 | prop: 'city',
315 | label: '市区',
316 | width: 150,
317 | overflow: true
318 | },
319 | {
320 | prop: 'address',
321 | label: '地址',
322 | width: 150,
323 | overflow: true
324 | },
325 | {
326 | prop: 'loc',
327 | label: '工位',
328 | width: 150,
329 | overflow: true
330 | },
331 | {
332 | prop: 'createUser',
333 | label: '创建人',
334 | width: 150,
335 | overflow: true
336 | },
337 | {
338 | prop: 'auditUser',
339 | label: '审核人',
340 | width: 150,
341 | overflow: true
342 | },
343 | {
344 | prop: 'order',
345 | label: '订单号',
346 | width: 150,
347 | overflow: true
348 | },
349 | { // 场景: 后端字段是json字符串,需要前端解析其中某个字段
350 | prop: 'jsonStr',
351 | label: 'json解析',
352 | width: 150,
353 | overflow: true,
354 | expandFunc: true,
355 | isMultiCell: true,
356 | render: (scope) => {
357 | if (JSON.stringify(scope.row) !== '{}') {
358 | return JSON.parse(scope.row.jsonStr).json;
359 | }
360 | return "--";
361 | }
362 | },
363 | { // 场景: 后端字段是数字0或1, 前端需要自己将数字转成汉字 比如0 待审核 1 已审核
364 | prop: 'status',
365 | label: '状态(0 1)',
366 | width: 150,
367 | overflow: true,
368 | expandFunc: true,
369 | isMultiCell: true,
370 | render: (scope) => {
371 | let status = scope.row.status;
372 | if( status === 0) {
373 | return "待审核";
374 | } else if(status === 1) {
375 | return "已审核";
376 | }
377 | return "--";
378 | }
379 | }
380 | ]
381 | ```
382 |
383 | #### 请求接口配置属性
384 |
385 | service Obejct
386 | 属性用于请求接口的配置,用于在组件内部进行表格数据的请求 默认是一个对象,get属性是默认的请求,参数为如下格式
387 | ```
388 | {
389 | page: 1,
390 | psize: 2,
391 | params: {
392 | name: 'leilei',
393 | age: 18
394 | }
395 | }
396 | ```
397 |
398 | ```
399 | export const localService = {
400 | /**
401 | * {
402 | * page: 1,
403 | * psize: 20,
404 | * params: {}
405 | * }
406 | */
407 | get(data) {
408 | console.log(data);
409 | // return axios.get(url, data); 这里是实际发请求的地方
410 | return new Promise((resolve, reject) => {
411 | resolve({
412 | data: {
413 | code: 0,
414 | data: {
415 | totalCount: 1,
416 | list: tableData
417 | }
418 | }
419 | });
420 | });
421 | }
422 | };
423 | ```
424 | ##### 2021-04-06周二
425 | 增加登录界面
426 |
427 | 主要变化文件如下图
428 |
429 | 
430 |
431 | 由于之前组件设计问题,导致登录界面不太好加,所以找了一个比较trick的方案。将登录路由添加到页面中。
432 | ```
433 |
434 |
435 |
436 |
437 |
438 |
439 | ```
440 |
441 |
442 | 持续更新中。。。比较慢。。。
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | };
6 |
--------------------------------------------------------------------------------
/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "deviceinfo": {
3 | "model": "model",
4 | "hwVer": "hwVer",
5 | "swVer": "swVer",
6 | "SN": "SN",
7 | "currentTime": "2020-12-12 13:23:09",
8 | "deviceName": "中国移动路由器",
9 | "vendor": "qwewasdasx",
10 | "deviceUpTime": "1608188204153",
11 | "IPUpTime": "1608188204153",
12 | "cpuUsage": 12,
13 | "memoryUsage": 100,
14 | "configuredState": true,
15 | "attachedDeviceNum": 32,
16 | "meshRouterNum": 2,
17 | "routerIP": "192.168.2.1",
18 | "upRate": 29210,
19 | "downRate": 12
20 | },
21 | "wans": [
22 | {
23 | "id": "0",
24 | "IPStatus": "CONNECTING",
25 | "currentIP": "192.168.2.1",
26 | "currentMask": "255.255.255.0",
27 | "currentGateway": "192.168.2.1",
28 | "currentDNS": "192.168.2.1",
29 | "enable": true,
30 | "IPMode": "DHCP",
31 | "connectionTrigger": "auto",
32 | "keepAliveTime": "1608188204153",
33 | "address": "192.168.2.1",
34 | "mask": "255.255.255.0",
35 | "gateway": "192.168.2.1",
36 | "staticDns": true,
37 | "DNS": "192.168.2.1",
38 | "username": "tianleilei",
39 | "password": "11000000"
40 | },
41 | {
42 | "id": "1",
43 | "IPStatus": "UP",
44 | "currentIP": "192.168.2.1",
45 | "currentMask": "255.255.255.0",
46 | "currentGateway": "192.168.2.1",
47 | "currentDNS": "192.168.2.1",
48 | "enable": true,
49 | "IPMode": "PPPoE",
50 | "connectionTrigger": "always",
51 | "keepAliveTime": "1608188204153",
52 | "address": "192.168.2.1",
53 | "mask": "255.255.255.0",
54 | "gateway": "192.168.2.1",
55 | "staticDns": false,
56 | "DNS": "192.168.2.1",
57 | "username": "LIUSIWEI",
58 | "password": "11000000"
59 | },
60 | {
61 | "id": "2",
62 | "IPStatus": "DOWN",
63 | "currentIP": "192.168.2.1",
64 | "currentMask": "255.255.255.0",
65 | "currentGateway": "192.168.2.1",
66 | "currentDNS": "192.168.2.1",
67 | "enable": true,
68 | "IPMode": "static",
69 | "connectionTrigger": "always",
70 | "keepAliveTime": "1608188204153",
71 | "address": "192.168.2.1",
72 | "mask": "255.255.255.0",
73 | "gateway": "192.168.2.1",
74 | "staticDns": false,
75 | "DNS": "192.168.2.1",
76 | "username": "jinming",
77 | "password": "11000000"
78 | },
79 | {
80 | "id": "3",
81 | "IPStatus": "CONNECTING",
82 | "currentIP": "192.168.2.1",
83 | "currentMask": "255.255.255.0",
84 | "currentGateway": "192.168.2.1",
85 | "currentDNS": "192.168.2.1",
86 | "enable": true,
87 | "IPMode": "DHCP",
88 | "connectionTrigger": "auto",
89 | "keepAliveTime": "1608188204153",
90 | "address": "192.168.2.1",
91 | "mask": "255.255.255.0",
92 | "gateway": "192.168.2.1",
93 | "staticDns": true,
94 | "DNS": "192.168.2.1",
95 | "username": "tianleilei",
96 | "password": "11000000"
97 | },
98 | {
99 | "id": "99999",
100 | "IPStatus": "CONNECTING",
101 | "currentIP": "192.168.2.1",
102 | "currentMask": "255.255.255.0",
103 | "currentGateway": "192.168.2.1",
104 | "currentDNS": "192.168.2.1",
105 | "enable": true,
106 | "IPMode": "DHCP",
107 | "connectionTrigger": "auto",
108 | "keepAliveTime": "1608188204153",
109 | "address": "192.168.2.1",
110 | "mask": "255.255.255.0",
111 | "gateway": "192.168.2.1",
112 | "staticDns": true,
113 | "DNS": "192.168.2.1",
114 | "username": "tianleilei",
115 | "password": "11000000"
116 | },
117 | {
118 | "id": "100",
119 | "IPStatus": "CONNECTING",
120 | "currentIP": "192.168.2.1",
121 | "currentMask": "255.255.255.0",
122 | "currentGateway": "192.168.2.1",
123 | "currentDNS": "192.168.2.1",
124 | "enable": true,
125 | "IPMode": "DHCP",
126 | "connectionTrigger": "auto",
127 | "keepAliveTime": "1608188204153",
128 | "address": "192.168.2.1",
129 | "mask": "255.255.255.0",
130 | "gateway": "192.168.2.1",
131 | "staticDns": true,
132 | "DNS": "192.168.2.1",
133 | "username": "tianleilei",
134 | "password": "11000000"
135 | },
136 | {
137 | "id": "101",
138 | "IPStatus": "CONNECTING",
139 | "currentIP": "192.168.2.1",
140 | "currentMask": "255.255.255.0",
141 | "currentGateway": "192.168.2.1",
142 | "currentDNS": "192.168.2.1",
143 | "enable": true,
144 | "IPMode": "DHCP",
145 | "connectionTrigger": "auto",
146 | "keepAliveTime": "1608188204153",
147 | "address": "192.168.2.1",
148 | "mask": "255.255.255.0",
149 | "gateway": "192.168.2.1",
150 | "staticDns": true,
151 | "DNS": "192.168.2.1",
152 | "username": "tianleilei",
153 | "password": "11000000"
154 | }
155 | ]
156 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plan",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "mock": "node ./src/mock/server.js",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "dependencies": {
12 | "axios": "^0.21.0",
13 | "core-js": "^3.6.5",
14 | "element-plus": "^1.0.2-beta.32",
15 | "json-server": "^0.16.3",
16 | "less": "^3.12.2",
17 | "less-loader": "^7.1.0",
18 | "pug": "^3.0.0",
19 | "pug-plain-loader": "^1.0.0",
20 | "vue": "^3.0.0",
21 | "vue-class-component": "^8.0.0-0",
22 | "vue-router": "^4.0.0-0",
23 | "vuex": "^4.0.0-0"
24 | },
25 | "devDependencies": {
26 | "@typescript-eslint/eslint-plugin": "^2.33.0",
27 | "@typescript-eslint/parser": "^2.33.0",
28 | "@vue/cli-plugin-babel": "~4.5.0",
29 | "@vue/cli-plugin-eslint": "~4.5.0",
30 | "@vue/cli-plugin-router": "~4.5.0",
31 | "@vue/cli-plugin-typescript": "~4.5.0",
32 | "@vue/cli-plugin-vuex": "~4.5.0",
33 | "@vue/cli-service": "~4.5.0",
34 | "@vue/compiler-sfc": "^3.0.0",
35 | "@vue/eslint-config-standard": "^5.1.2",
36 | "@vue/eslint-config-typescript": "^5.0.2",
37 | "eslint": "^6.7.2",
38 | "eslint-plugin-import": "^2.20.2",
39 | "eslint-plugin-node": "^11.1.0",
40 | "eslint-plugin-promise": "^4.2.1",
41 | "eslint-plugin-standard": "^4.0.0",
42 | "eslint-plugin-vue": "^7.0.0-0",
43 | "typescript": "~3.9.3"
44 | },
45 | "eslintConfig": {
46 | "root": true,
47 | "env": {
48 | "node": true
49 | },
50 | "extends": [
51 | "plugin:vue/vue3-essential",
52 | "@vue/standard",
53 | "@vue/typescript/recommended"
54 | ],
55 | "parserOptions": {
56 | "ecmaVersion": 2020
57 | },
58 | "rules": {}
59 | },
60 | "browserslist": [
61 | "> 1%",
62 | "last 2 versions",
63 | "not dead"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mstian/Vue-Onepiece-Admin/76003a01025aede8877d74d8c0fc665ff100bad9/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
35 |
--------------------------------------------------------------------------------
/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import request from "@/request/index.js";
2 |
3 | export const getTableList = () => {
4 | return request.get('http://localhost:3000/wans');
5 | };
6 | export const getItem = (data: any) => {
7 | return request.get(`http://localhost:3000/wans/${data.id}`);
8 | };
9 | export const deleteItem = (data: any) => {
10 | return request.delete(`http://localhost:3000/wans/${data.id}`);
11 | };
12 |
13 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mstian/Vue-Onepiece-Admin/76003a01025aede8877d74d8c0fc665ff100bad9/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/CommonList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 | 查询
10 |
11 |
14 |
15 |
20 |
21 |
22 |
34 |
36 |
37 |
50 |
51 |
62 |
63 |
64 |
70 |
71 |
72 |
73 |
74 | 查询配置
75 |
76 |
78 | {{item.label}}
80 |
81 |
82 |
83 |
84 |
85 |
86 | 查询
87 |
88 |
89 |
90 |
91 |
92 | 全部导出
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
111 |
113 |
115 |
121 |
122 |
123 |
124 |
127 |
128 | {{column.inputAppend}}
129 |
130 |
131 |
135 |
136 |
137 |
138 |
142 | 预览
143 |
144 |
145 |
148 | {{column.buttonText || scope.row[column.prop]}}
149 |
150 |
151 |
153 | {{item[column.urlText]}}
154 |
155 |
156 |
157 |
159 |
160 |
![此图不存在,请检查url]()
162 |
163 | 预览
164 |
165 |
167 | {{column.label}}
169 |
170 |
171 |
172 |
173 |
174 | {{key + ':' + item}}
175 |
176 |
177 | 查看
178 |
179 |
180 |
181 |
182 |
183 |
187 | {{scope.row[column.prop] | formatCol(column.formatter)}}
188 |
189 |
190 |
191 |
194 |
195 |
196 |
197 |
198 | -
200 | {{diffKey}}:
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
211 |
212 |
213 |
214 |
216 |
217 |
218 | {{ scope.row[column.prop] | arr2Str(column.formatMap, column.showKey)}}
220 |
221 |
222 |
223 |
224 | {{ item.val}}
226 |
227 |
228 |
229 | 空
231 |
232 |
233 |
234 |
235 |
237 |
238 |
239 |
240 |
242 | {{ options.delTip || '' }}
243 |
244 |
245 |
246 |
247 |
253 |
254 |
255 |
256 |
258 |
259 |
260 |
261 |
262 |
263 |
1001 |
1002 |
1073 |
1086 |
1092 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg + " " + obj.title }}
4 |
5 |
这是一个子组件,子父组件通信使用 provide inject 非常方便 {{obj.title}}就是从父组件中接收到的
6 |
7 |
8 |
9 |
24 |
25 |
26 |
42 |
--------------------------------------------------------------------------------
/src/components/JsonTable/README.md:
--------------------------------------------------------------------------------
1 | ### 功能1:表单查询
2 | 表单支持类型:
3 | 1. input
4 | 2. select
5 | 3. cascader
6 | 4. date (date datetimerange)
7 |
8 | 表格支持功能:
9 | 字符串,图片预览(hover),json解析,数字解析,操作栏,select选择
10 |
11 | 功能预览图:
12 | 
13 |
14 | 以下为组件使用示例 以及配置项示例
15 |
16 | ```
17 |
25 |
26 |
27 |
28 | 新增
29 |
30 |
31 |
32 |
33 | 删除
34 | 查看
35 |
36 |
37 | ```
38 | Props:
39 | `searchColumns`: 表单查询属性配置
40 | `tableColumns`: 查询结果行字段配置
41 | `service`: 请求,主要是配置查询请求
42 | `options`: 表格设置
43 | `onformchange`: 表单查询项change事件监听 参数为查询属性
44 | `rowselectchange`: 带有select的表格选择时的select监听事件 参数为当前选中项
45 |
46 | #### 表单查询属性:searchColumns Array
47 |
48 | 配置项说明:
49 |
50 | label: String 表单项显示名称
51 | prop: String 表单项属性(传给后端的字段)
52 | clearable: Boolean 表单项内容是否可清除
53 | placeholder: String 表单项placeholder
54 | isSelect: Boolean 是否是select框(默认是input)
55 | options: Array Select框和Cascader的options
56 | isCascader: Boolean 是否是级联
57 | isTime: String [date, datetimerange] 日期 或者日期时间范围(默认时间是Date格式内部已做转换处理为YYYY-MM-DD HH:MM:SS格式 如果需要时间戳格式可在组件源码中自行修改)
58 |
59 | 配置示例:
60 | ```
61 | [
62 | {
63 | label: '姓名',
64 | prop: 'name',
65 | clearable: true,
66 | placeholder: "请输入姓名"
67 | },
68 | {
69 | label: '性别',
70 | prop: 'sex',
71 | clearable: true,
72 | placeholder: "请选择",
73 | isSelect: true,
74 | options: [
75 | {
76 | prop: 'male',
77 | name: '男'
78 | },
79 | {
80 | prop: 'female',
81 | name: '女'
82 | }
83 | ]
84 | },
85 | {
86 | label: '技能',
87 | prop: 'skill',
88 | clearable: true,
89 | placeholder: "请选择",
90 | isCascader: true,
91 | options: [
92 | {
93 | value: "basic",
94 | label: "Basic",
95 | children: [
96 | {
97 | value: "layout",
98 | label: "Layout 布局"
99 | },
100 | {
101 | value: "color",
102 | label: "Color 色彩"
103 | },
104 | {
105 | value: "typography",
106 | label: "Typography 字体"
107 | },
108 | {
109 | value: "icon",
110 | label: "Icon 图标"
111 | },
112 | {
113 | value: "button",
114 | label: "Button 按钮"
115 | }
116 | ]
117 | }
118 | ]
119 | },
120 | {
121 | label: '出生日期',
122 | prop: 'born',
123 | clearable: true,
124 | placeholder: "选择日期",
125 | isTime: 'date',
126 | valueFormat: '',
127 | defaultTime: []
128 | }
129 | ]
130 |
131 | ```
132 |
133 |
134 | #### table表格各属性:
135 | 1. options Object 表格展现形式配置
136 |
137 | 配置项说明:
138 | canCheck: Boolean 表格是否可勾选
139 | hasIndex: Boolean 表格是否有序号
140 | checkFixed: String [left right] 勾选checkbox固定位置
141 | indexFixed: String [left right] 序号固定位置
142 | opW: Number 操作栏宽度(当操作栏按钮较多时需要较宽宽度,默认为150)
143 | autoRequest: Boolean 是否进入页面执行一次数据请求 默认为false
144 | startUpdate: Date.now() // 监听该项有变化时更新请求
145 | 配置示例:
146 | ```
147 | {
148 | canCheck: true, // 是否可选择
149 | hasIndex: true, // 是否有序号
150 | checkFixed: 'left', // 选择固定位置
151 | indexFixed: 'left', // 表序号固定位置
152 | opW: 150,// 操作栏宽度
153 | autoRequest: true, // 自动请求(第一次加载默认请求)
154 | startUpdate: Date.now()
155 | }
156 | ```
157 | 2. tableColumns 查询结果行字段配置
158 | 属性:tableColumns Array
159 |
160 | 配置项说明:
161 | prop: String 对应后端返回表格数据字段
162 | label: String 表格当前列名称
163 | width: Number 当前列宽度
164 | expandFunc: Boolean 是否有扩展功能(扩展功能包括图片预览,重写数据等等,当传递imgW,isMultiCell,render时该属性必传)
165 | imgW: Number 图片预览时传递此参数,为预览图片宽度(图片默认是以tooltip展示的)
166 | isMultiCell: Boolean 是否要重写数据(场景:后端返回json字符串,前端需要取其中某一个属性,或者后端给的是0,1这样的标识,需要前端转义为汉字 是 或者否)
167 | render: Function 配合isMultiCell使用参数为当前表格的行数据,可以return可渲染数据,可以参考配置示例
168 |
169 | 配置示例
170 | ```
171 | [
172 | {
173 | prop: 'name',
174 | label: '姓名',
175 | width: 150,
176 | overflow: true
177 | },
178 | {
179 | prop: 'age',
180 | label: '年龄',
181 | width: 150,
182 | overflow: true
183 | },
184 | { // 图片预览
185 | prop: 'avatar',
186 | label: '头像',
187 | width: 150,
188 | imgW: 300, // 设置该项表示预览图片
189 | expandFunc: true // 是否有扩展功能,启用表格列插槽
190 | },
191 | {
192 | prop: 'sex',
193 | label: '性别',
194 | width: 150,
195 | overflow: true
196 | },
197 | {
198 | prop: 'born',
199 | label: '出生日期',
200 | width: 150,
201 | overflow: true
202 | },
203 | {
204 | prop: 'phone',
205 | label: '电话',
206 | width: 150,
207 | overflow: true
208 | },
209 | {
210 | prop: 'zip',
211 | label: '邮编',
212 | width: 150,
213 | overflow: true
214 | },
215 | {
216 | prop: 'province',
217 | label: '省份',
218 | width: 150,
219 | overflow: true
220 | },
221 | {
222 | prop: 'city',
223 | label: '市区',
224 | width: 150,
225 | overflow: true
226 | },
227 | {
228 | prop: 'address',
229 | label: '地址',
230 | width: 150,
231 | overflow: true
232 | },
233 | {
234 | prop: 'loc',
235 | label: '工位',
236 | width: 150,
237 | overflow: true
238 | },
239 | {
240 | prop: 'createUser',
241 | label: '创建人',
242 | width: 150,
243 | overflow: true
244 | },
245 | {
246 | prop: 'auditUser',
247 | label: '审核人',
248 | width: 150,
249 | overflow: true
250 | },
251 | {
252 | prop: 'order',
253 | label: '订单号',
254 | width: 150,
255 | overflow: true
256 | },
257 | { // 场景: 后端字段是json字符串,需要前端解析其中某个字段
258 | prop: 'jsonStr',
259 | label: 'json解析',
260 | width: 150,
261 | overflow: true,
262 | expandFunc: true,
263 | isMultiCell: true,
264 | render: (scope) => {
265 | if (JSON.stringify(scope.row) !== '{}') {
266 | return JSON.parse(scope.row.jsonStr).json;
267 | }
268 | return "--";
269 | }
270 | },
271 | { // 场景: 后端字段是数字0或1, 前端需要自己将数字转成汉字 比如0 待审核 1 已审核
272 | prop: 'status',
273 | label: '状态(0 1)',
274 | width: 150,
275 | overflow: true,
276 | expandFunc: true,
277 | isMultiCell: true,
278 | render: (scope) => {
279 | let status = scope.row.status;
280 | if( status === 0) {
281 | return "待审核";
282 | } else if(status === 1) {
283 | return "已审核";
284 | }
285 | return "--";
286 | }
287 | }
288 | ]
289 | ```
290 |
291 | #### 请求接口配置属性
292 |
293 | service Obejct
294 | 属性用于请求接口的配置,用于在组件内部进行表格数据的请求 默认是一个对象,get属性是默认的请求,参数为如下格式
295 | ```
296 | {
297 | page: 1,
298 | psize: 2,
299 | params: {
300 | name: 'leilei',
301 | age: 18
302 | }
303 | }
304 | ```
305 |
306 | ```
307 | export const localService = {
308 | /**
309 | * {
310 | * page: 1,
311 | * psize: 20,
312 | * params: {}
313 | * }
314 | */
315 | get(data) {
316 | console.log(data);
317 | // return axios.get(url, data); 这里是实际发请求的地方
318 | return new Promise((resolve, reject) => {
319 | resolve({
320 | data: {
321 | code: 0,
322 | data: {
323 | totalCount: 1,
324 | list: tableData
325 | }
326 | }
327 | });
328 | });
329 | }
330 | };
331 | ```
--------------------------------------------------------------------------------
/src/components/JsonTable/composition/useSearchForm.js:
--------------------------------------------------------------------------------
1 | import {reactive, ref, nextTick} from 'vue';
2 | import dayjs from 'dayjs';
3 | export function useInitSearchForm(props) {
4 | let searchForm = reactive({}); // 初始化表单项
5 | const isLoading = ref(false); // 控制loading
6 | const tableData = ref([]); // 表格数据
7 | props.searchColumns.forEach((item, index) => {
8 | searchForm[item.prop] = "";
9 | });
10 | let pagination = reactive({ // 分页相关
11 | page: 1,
12 | psize: 20,
13 | total: 0
14 | });
15 | let formatDate = (val, searchForm, prop) => { // 格式化日期
16 | if (Array.isArray(val)) {
17 | let temp = [dayjs(val[0]).format('YYYY-MM-DD HH:mm:ss'), dayjs(val[1]).format('YYYY-MM-DD HH:mm:ss')];
18 | searchForm[prop] = temp;
19 | } else {
20 | searchForm[prop] = dayjs(val).format('YYYY-MM-DD');
21 | }
22 | };
23 |
24 | let handleSubmit = () => { // 提交
25 | isLoading.value = true;
26 | let temp = {
27 | page: pagination.page,
28 | psize: pagination.psize,
29 | params: searchForm
30 | };
31 | props.service.get(temp).then((res) => {
32 | console.log(res);
33 | if (res.code === 0) {
34 | isLoading.value = false; // 控制loading
35 | tableData.value = res.data;
36 | pagination.total = res.data.length;
37 | }
38 | });
39 | };
40 |
41 | let caculateTableHeight = (tableHeight, tableRef) => {
42 | nextTick().then(() => {
43 | let total = tableRef.value.$el.offsetTop + tableRef.value.$el.offsetParent.offsetTop + tableRef.value.$el.nextElementSibling.offsetHeight + 20;
44 | tableHeight.value = window.innerHeight - total;
45 | });
46 | };
47 |
48 | return {
49 | searchForm,
50 | formatDate,
51 | handleSubmit,
52 | tableData,
53 | caculateTableHeight,
54 | pagination,
55 | isLoading
56 | };
57 | }
--------------------------------------------------------------------------------
/src/components/JsonTable/config.js:
--------------------------------------------------------------------------------
1 | // import request from '@/server/request';
2 | import {tableData} from './tableDataMock';
3 | export const searchColumns = [
4 | {
5 | label: '姓名',
6 | prop: 'name',
7 | clearable: true,
8 | placeholder: "请输入姓名"
9 | },
10 | {
11 | label: '性别',
12 | prop: 'sex',
13 | clearable: true,
14 | placeholder: "性别",
15 | isSelect: true,
16 | options: [
17 | {
18 | prop: 'male',
19 | name: '男'
20 | },
21 | {
22 | prop: 'female',
23 | name: '女'
24 | }
25 | ]
26 | },
27 | {
28 | label: '技能',
29 | prop: 'skill',
30 | clearable: true,
31 | placeholder: "请选择",
32 | isCascader: true,
33 | options: [
34 | {
35 | value: "basic",
36 | label: "Basic",
37 | children: [
38 | {
39 | value: "layout",
40 | label: "Layout 布局"
41 | },
42 | {
43 | value: "color",
44 | label: "Color 色彩"
45 | },
46 | {
47 | value: "typography",
48 | label: "Typography 字体"
49 | },
50 | {
51 | value: "icon",
52 | label: "Icon 图标"
53 | },
54 | {
55 | value: "button",
56 | label: "Button 按钮"
57 | }
58 | ]
59 | }
60 | ]
61 | },
62 | {
63 | label: '出生日期',
64 | prop: 'born',
65 | clearable: true,
66 | placeholder: "选择日期",
67 | isTime: 'date'
68 | },
69 | {
70 | label: '工作日期',
71 | prop: 'working',
72 | clearable: true,
73 | placeholder: "选择日期",
74 | isTime: 'datetimerange'
75 | }
76 | ];
77 |
78 | export const tableColumns = [
79 | {
80 | prop: 'name',
81 | label: '姓名',
82 | width: 150,
83 | overflow: true
84 | },
85 | {
86 | prop: 'age',
87 | label: '年龄',
88 | width: 150,
89 | overflow: true
90 | },
91 | { // 图片预览
92 | prop: 'avatar',
93 | label: '头像',
94 | width: 150,
95 | imgW: 300, // 设置该项表示预览图片
96 | expandFunc: true // 是否有扩展功能,启用表格列插槽
97 | },
98 | {
99 | prop: 'sex',
100 | label: '性别',
101 | width: 150,
102 | overflow: true
103 | },
104 | {
105 | prop: 'born',
106 | label: '出生日期',
107 | width: 150,
108 | overflow: true
109 | },
110 | {
111 | prop: 'phone',
112 | label: '电话',
113 | width: 150,
114 | overflow: true
115 | },
116 | {
117 | prop: 'zip',
118 | label: '邮编',
119 | width: 150,
120 | overflow: true
121 | },
122 | {
123 | prop: 'province',
124 | label: '省份',
125 | width: 150,
126 | overflow: true
127 | },
128 | {
129 | prop: 'city',
130 | label: '市区',
131 | width: 150,
132 | overflow: true
133 | },
134 | {
135 | prop: 'address',
136 | label: '地址',
137 | width: 100,
138 | overflow: true
139 | },
140 | {
141 | prop: 'loc',
142 | label: '工位',
143 | width: 150,
144 | overflow: true
145 | },
146 | {
147 | prop: 'createUser',
148 | label: '创建人',
149 | width: 150,
150 | overflow: true
151 | },
152 | {
153 | prop: 'auditUser',
154 | label: '审核人',
155 | width: 150,
156 | overflow: true
157 | },
158 | {
159 | prop: 'order',
160 | label: '订单号',
161 | width: 150,
162 | overflow: true
163 | },
164 | { // 场景: 后端字段是json字符串,需要前端解析其中某个字段
165 | prop: 'jsonStr',
166 | label: 'json解析',
167 | width: 150,
168 | overflow: true,
169 | expandFunc: true,
170 | isMultiCell: true,
171 | render: (scope) => {
172 | if (JSON.stringify(scope.row) !== '{}') {
173 | return JSON.parse(scope.row.jsonStr).json;
174 | }
175 | return "--";
176 | }
177 | },
178 | { // 场景: 后端字段是数字0或1, 前端需要自己将数字转成汉字 比如0 待审核 1 已审核
179 | prop: 'status',
180 | label: '状态(0 1)',
181 | width: 150,
182 | overflow: true,
183 | expandFunc: true,
184 | isMultiCell: true,
185 | render: (scope) => {
186 | let status = scope.row.status;
187 | if( status === 0) {
188 | return "待审核";
189 | } else if(status === 1) {
190 | return "已审核";
191 | }
192 | return "--";
193 | }
194 | }
195 | ];
196 |
197 | export const localService = {
198 | /**
199 | * {
200 | * page: 1,
201 | * psize: 20,
202 | * params: {}
203 | * }
204 | */
205 | get(data) {
206 | // return request.get("http://localhost:3000/list", {_page: data.page, _limit: data.psize}); // 这里是实际发请求的地方
207 | return new Promise((resolve, reject) => {
208 | setTimeout(() => {
209 | resolve(tableData);
210 | }, 1000);
211 | });
212 | }
213 | };
214 |
215 | export const options = {
216 | canCheck: true, // 是否可选择
217 | hasIndex: true, // 是否有序号
218 | checkFixed: 'left', // 选择固定位置
219 | indexFixed: 'left', // 表序号固定位置
220 | opW: 150,// 操作栏宽度
221 | autoRequest: true, // 自动请求
222 | startUpdate: Date.now()
223 | };
224 |
225 | // 以上配置文件可以根据业务需要分布配置在不同的文件里
--------------------------------------------------------------------------------
/src/components/JsonTable/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
20 |
21 |
28 |
29 |
30 |
37 |
43 |
44 |
45 |
46 |
54 |
55 |
56 | {
65 | formatDate(val, searchForm, item.prop);
66 | }
67 | "
68 | >
69 |
70 |
71 |
72 | {
82 | formatDate(val, searchForm, item.prop);
83 | }
84 | "
85 | >
86 |
87 |
88 |
89 |
90 | 查询
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
108 |
114 |
121 |
129 |
130 |
131 |
132 |
133 |
140 |
141 | 预览
142 |
143 |
147 |
148 |
149 |
150 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
173 |
174 |
175 |
176 |
177 |
264 |
316 |
--------------------------------------------------------------------------------
/src/components/JsonTable/tableDataMock.js:
--------------------------------------------------------------------------------
1 | // 模拟的表格数据
2 | export const tableData = {
3 | "code":0,
4 | "data":[
5 | {
6 | "name":"Kristy Emard",
7 | "age":18,
8 | "sex":"女",
9 | "born":"1995-08-02",
10 | "phone":"1-562-942-3595 x034",
11 | "zip":"46678",
12 | "province":"West Aronfurt",
13 | "city":"Lake",
14 | "address":"22450 Daugherty Forest",
15 | "loc":"A098",
16 | "createUser":"tianleilei",
17 | "auditUser":"tianleilei",
18 | "order":32494,
19 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
20 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
21 | "status":1
22 | },
23 | {
24 | "name":"Glen Jacobi",
25 | "age":18,
26 | "sex":"女",
27 | "born":"1995-08-02",
28 | "phone":"896-893-8511",
29 | "zip":"03480",
30 | "province":"MacGyverberg",
31 | "city":"Port",
32 | "address":"20271 Gerhold Rapids",
33 | "loc":"A098",
34 | "createUser":"tianleilei",
35 | "auditUser":"tianleilei",
36 | "order":36640,
37 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
38 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
39 | "status":1
40 | },
41 | {
42 | "name":"Juan Doyle",
43 | "age":18,
44 | "sex":"女",
45 | "born":"1995-08-02",
46 | "phone":"911-885-7970 x0500",
47 | "zip":"00763-1043",
48 | "province":"McDermottstad",
49 | "city":"New",
50 | "address":"7381 Harvey Corners",
51 | "loc":"A098",
52 | "createUser":"tianleilei",
53 | "auditUser":"tianleilei",
54 | "order":85389,
55 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
56 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
57 | "status":1
58 | },
59 | {
60 | "name":"Rhonda Olson",
61 | "age":18,
62 | "sex":"女",
63 | "born":"1995-08-02",
64 | "phone":"1-634-841-5713",
65 | "zip":"39892",
66 | "province":"Lake Cathryn",
67 | "city":"West",
68 | "address":"94225 Cade Walks",
69 | "loc":"A098",
70 | "createUser":"tianleilei",
71 | "auditUser":"tianleilei",
72 | "order":80113,
73 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
74 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
75 | "status":1
76 | },
77 | {
78 | "name":"Wilbert Auer",
79 | "age":18,
80 | "sex":"女",
81 | "born":"1995-08-02",
82 | "phone":"751.775.6480 x4323",
83 | "zip":"21416-6025",
84 | "province":"Walterville",
85 | "city":"Port",
86 | "address":"544 Wilson Drives",
87 | "loc":"A098",
88 | "createUser":"tianleilei",
89 | "auditUser":"tianleilei",
90 | "order":22886,
91 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
92 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
93 | "status":1
94 | },
95 | {
96 | "name":"Roberta Weber",
97 | "age":18,
98 | "sex":"女",
99 | "born":"1995-08-02",
100 | "phone":"1-793-933-2489 x952",
101 | "zip":"10681-0458",
102 | "province":"East Karsonmouth",
103 | "city":"West",
104 | "address":"24630 Kautzer Inlet",
105 | "loc":"A098",
106 | "createUser":"tianleilei",
107 | "auditUser":"tianleilei",
108 | "order":93330,
109 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
110 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
111 | "status":1
112 | },
113 | {
114 | "name":"Mr. Nadine Smith",
115 | "age":18,
116 | "sex":"女",
117 | "born":"1995-08-02",
118 | "phone":"1-777-753-8368 x98072",
119 | "zip":"93220-6459",
120 | "province":"Mireyafort",
121 | "city":"East",
122 | "address":"592 Reinger Knolls",
123 | "loc":"A098",
124 | "createUser":"tianleilei",
125 | "auditUser":"tianleilei",
126 | "order":85110,
127 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
128 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
129 | "status":1
130 | },
131 | {
132 | "name":"Marilyn Hansen",
133 | "age":18,
134 | "sex":"女",
135 | "born":"1995-08-02",
136 | "phone":"872-736-2545 x030",
137 | "zip":"53958",
138 | "province":"Lake Merritt",
139 | "city":"Lake",
140 | "address":"156 Trudie Lodge",
141 | "loc":"A098",
142 | "createUser":"tianleilei",
143 | "auditUser":"tianleilei",
144 | "order":35812,
145 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
146 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
147 | "status":1
148 | },
149 | {
150 | "name":"William Kovacek Jr.",
151 | "age":18,
152 | "sex":"女",
153 | "born":"1995-08-02",
154 | "phone":"811.509.3342 x1961",
155 | "zip":"64426",
156 | "province":"North Pasquale",
157 | "city":"Lake",
158 | "address":"64615 DuBuque Light",
159 | "loc":"A098",
160 | "createUser":"tianleilei",
161 | "auditUser":"tianleilei",
162 | "order":42982,
163 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
164 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
165 | "status":1
166 | },
167 | {
168 | "name":"Jim Ratke",
169 | "age":18,
170 | "sex":"女",
171 | "born":"1995-08-02",
172 | "phone":"619-707-7902 x6306",
173 | "zip":"75331",
174 | "province":"Florencestad",
175 | "city":"South",
176 | "address":"1954 Corkery Road",
177 | "loc":"A098",
178 | "createUser":"tianleilei",
179 | "auditUser":"tianleilei",
180 | "order":33782,
181 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
182 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
183 | "status":1
184 | },
185 | {
186 | "name":"Debbie Walsh",
187 | "age":18,
188 | "sex":"女",
189 | "born":"1995-08-02",
190 | "phone":"516-508-7783 x5691",
191 | "zip":"04965-4675",
192 | "province":"Port Gabeside",
193 | "city":"North",
194 | "address":"652 Rippin Path",
195 | "loc":"A098",
196 | "createUser":"tianleilei",
197 | "auditUser":"tianleilei",
198 | "order":50757,
199 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
200 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
201 | "status":1
202 | },
203 | {
204 | "name":"Rickey Cassin V",
205 | "age":18,
206 | "sex":"女",
207 | "born":"1995-08-02",
208 | "phone":"(894) 812-3429",
209 | "zip":"03592",
210 | "province":"Croninburgh",
211 | "city":"New",
212 | "address":"727 Torphy Road",
213 | "loc":"A098",
214 | "createUser":"tianleilei",
215 | "auditUser":"tianleilei",
216 | "order":88475,
217 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
218 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
219 | "status":1
220 | },
221 | {
222 | "name":"Leonard Bartoletti",
223 | "age":18,
224 | "sex":"女",
225 | "born":"1995-08-02",
226 | "phone":"(416) 275-7756 x296",
227 | "zip":"32427",
228 | "province":"East Dantefort",
229 | "city":"Port",
230 | "address":"692 Padberg Loop",
231 | "loc":"A098",
232 | "createUser":"tianleilei",
233 | "auditUser":"tianleilei",
234 | "order":37441,
235 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
236 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
237 | "status":1
238 | },
239 | {
240 | "name":"Chris Mertz",
241 | "age":18,
242 | "sex":"女",
243 | "born":"1995-08-02",
244 | "phone":"424-474-6619 x7432",
245 | "zip":"16352-9526",
246 | "province":"Lake Elzachester",
247 | "city":"South",
248 | "address":"673 Daniella Tunnel",
249 | "loc":"A098",
250 | "createUser":"tianleilei",
251 | "auditUser":"tianleilei",
252 | "order":33111,
253 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
254 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
255 | "status":1
256 | },
257 | {
258 | "name":"Bridget Lakin",
259 | "age":18,
260 | "sex":"女",
261 | "born":"1995-08-02",
262 | "phone":"(566) 868-1646 x4707",
263 | "zip":"51047-5707",
264 | "province":"Catalinaport",
265 | "city":"Port",
266 | "address":"3002 Leannon Lodge",
267 | "loc":"A098",
268 | "createUser":"tianleilei",
269 | "auditUser":"tianleilei",
270 | "order":36273,
271 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
272 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
273 | "status":1
274 | },
275 | {
276 | "name":"Olga Ledner",
277 | "age":18,
278 | "sex":"女",
279 | "born":"1995-08-02",
280 | "phone":"1-563-722-4878 x00656",
281 | "zip":"52275",
282 | "province":"Pagacberg",
283 | "city":"New",
284 | "address":"73542 Jast Forest",
285 | "loc":"A098",
286 | "createUser":"tianleilei",
287 | "auditUser":"tianleilei",
288 | "order":69058,
289 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
290 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
291 | "status":1
292 | },
293 | {
294 | "name":"Melanie Carroll",
295 | "age":18,
296 | "sex":"女",
297 | "born":"1995-08-02",
298 | "phone":"582.584.3151",
299 | "zip":"07326-3894",
300 | "province":"New Bertram",
301 | "city":"South",
302 | "address":"3457 Hayes Shoal",
303 | "loc":"A098",
304 | "createUser":"tianleilei",
305 | "auditUser":"tianleilei",
306 | "order":8988,
307 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
308 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
309 | "status":1
310 | },
311 | {
312 | "name":"Kristine Dibbert",
313 | "age":18,
314 | "sex":"女",
315 | "born":"1995-08-02",
316 | "phone":"(938) 689-0498",
317 | "zip":"99644-0890",
318 | "province":"Adrainhaven",
319 | "city":"Lake",
320 | "address":"75414 Lucienne Cove",
321 | "loc":"A098",
322 | "createUser":"tianleilei",
323 | "auditUser":"tianleilei",
324 | "order":19351,
325 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
326 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
327 | "status":1
328 | },
329 | {
330 | "name":"Billie Goyette",
331 | "age":18,
332 | "sex":"女",
333 | "born":"1995-08-02",
334 | "phone":"(928) 933-8477",
335 | "zip":"43354-1815",
336 | "province":"O'Harafort",
337 | "city":"West",
338 | "address":"02200 Savion Mission",
339 | "loc":"A098",
340 | "createUser":"tianleilei",
341 | "auditUser":"tianleilei",
342 | "order":35106,
343 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
344 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
345 | "status":1
346 | },
347 | {
348 | "name":"Gordon Gleichner",
349 | "age":18,
350 | "sex":"女",
351 | "born":"1995-08-02",
352 | "phone":"835.702.0268",
353 | "zip":"02390",
354 | "province":"Moenbury",
355 | "city":"West",
356 | "address":"67963 Johann Harbors",
357 | "loc":"A098",
358 | "createUser":"tianleilei",
359 | "auditUser":"tianleilei",
360 | "order":68428,
361 | "avatar":"https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3166639134,1440270722&fm=26&gp=0.jpg",
362 | "jsonStr":`{"json":"这是json字符串", "ext": "滴滴滴多"}`,
363 | "status":1
364 | }
365 | ]
366 | };
--------------------------------------------------------------------------------
/src/composition/useTagViewApi.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "vue";
2 | import { staticRoutes } from "@/router/staticRoutes";
3 | import {useRouter} from 'vue-router';
4 |
5 | let dynamic = reactive({
6 | dRoutes: [{ path: "/", name: "首页" }]
7 | });
8 |
9 | export function useDynamicRoutesHook() {
10 | const router = useRouter();
11 | /**
12 | * @param value String 当前menu对应的路由path
13 | * @param parentPath string 当前路由中父级路由
14 | */
15 | function dynamicRouteTags(value: any, parentPath: any) {
16 | const hasValue = dynamic.dRoutes.some((item, index) => {
17 | return item.path === value;
18 | });
19 | function concatPath(arr: any, value: any, parentPath: any){
20 | if (!hasValue) {
21 | arr.forEach((constItem: any, constIndex: any) => {
22 | let pathConcat = parentPath + '/' + constItem.path;
23 | if (constItem.path === value || pathConcat === value) {
24 | dynamic.dRoutes.push({ path: value, name: constItem.name });
25 | } else {
26 | if (constItem.children.length > 0) {
27 | concatPath(constItem.children, value, parentPath);
28 | }
29 | }
30 | });
31 | }
32 | }
33 | concatPath(staticRoutes, value, parentPath);
34 | }
35 | /**
36 | * @param value String 当前删除tag路由
37 | * @param current Objct 当前激活路由对象
38 | */
39 | function deleteDynamicTag(value: any, current: any) {
40 | new Promise((resolve, reject) => {
41 | let valueIndex = dynamic.dRoutes.findIndex((item, index) => {
42 | return item.path === value.path;
43 | });
44 | dynamic.dRoutes.splice(valueIndex, 1);
45 | resolve();
46 | }).then(() => {
47 | if (current === value.path) { // 如果删除当前激活tag就自动切换到最后一个tag
48 | let newRoute = dynamic.dRoutes.slice(-1);
49 | router.push({
50 | path: newRoute[0].path
51 | });
52 | }
53 | });
54 | }
55 | return {
56 | dynamic, // 动态路由
57 | dynamicRouteTags, // tagviews动态生成
58 | deleteDynamicTag // 删除tagview
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/src/composition/useThemeApi.ts:
--------------------------------------------------------------------------------
1 | import {reactive} from 'vue';
2 | import themeVariables from '@/style/theme.less';
3 | const theme = reactive({customTheme:themeVariables.customTheme});
4 | export function useTheme() {
5 | function setTheme(color: string){
6 | theme.customTheme = color;
7 | }
8 | return {
9 | theme,
10 | setTheme
11 | };
12 | }
--------------------------------------------------------------------------------
/src/layout/components/Drawer/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | 系统设置!
9 |
10 |
11 |
主题色:
12 |
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 | 退出登录
26 |
27 |
28 |
29 |
30 |
31 |
65 |
--------------------------------------------------------------------------------
/src/layout/components/Header/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
63 |
122 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Sidebar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 | {{ routeItem.name }}
11 |
12 |
29 |
30 |
31 |
32 |
54 |
97 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 | el-menu(
3 | class="el-menu-vertical-demo"
4 | :collapse="state.controls.isCollapse"
5 | :collapse-transition="false"
6 | router
7 | :default-active="currentActiveRoute"
8 | :background-color="variables.menuBg"
9 | @select="menuSelect"
10 | :text-color="variables.menuText"
11 | :active-text-color="variables.menuActiveText"
12 | )
13 | Sidebar(:item="routes" basePath="")
14 |
15 |
16 |
63 |
--------------------------------------------------------------------------------
/src/layout/components/Tagsview/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
36 |
107 |
--------------------------------------------------------------------------------
/src/layout/components/Wrapper/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/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 |
60 |
90 |
113 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import App from './App.vue';
3 | import router from './router';
4 | import store from './store';
5 | import ElementPlus from 'element-plus';
6 | import 'element-plus/lib/theme-chalk/index.css';
7 | import locale from 'element-plus/lib/locale/lang/zh-cn';
8 | import request from "@/request/index";
9 |
10 | // 这里监听请求的错误统一处理(做弹窗提示提示)
11 | request.on("HttpStatusFaild", () => {
12 | // console.log("Capture status");
13 | alert("请求失败,请检查接口问题");
14 | });
15 |
16 | createApp(App).use(store).use(router).use(ElementPlus, {locale}).mount('#app');
17 |
--------------------------------------------------------------------------------
/src/mock/data copy.json:
--------------------------------------------------------------------------------
1 | {
2 | "deviceinfo": {
3 | "model": "model",
4 | "hwVer": "hwVer",
5 | "swVer": "swVer",
6 | "SN": "SN",
7 | "currentTime": "2020-12-12 13:23:09",
8 | "deviceName": "中国移动路由器",
9 | "vendor": "qwewasdasx",
10 | "deviceUpTime": "1608188204153",
11 | "IPUpTime": "1608188204153",
12 | "cpuUsage": 12,
13 | "memoryUsage": 100,
14 | "configuredState": true,
15 | "attachedDeviceNum": 32,
16 | "meshRouterNum": 2,
17 | "routerIP": "192.168.2.1",
18 | "upRate": 29210,
19 | "downRate": 12
20 | },
21 | "wans": [
22 | {
23 | "id": "99999",
24 | "IPStatus": "CONNECTING",
25 | "currentIP": "192.168.2.1",
26 | "currentMask": "255.255.255.0",
27 | "currentGateway": "192.168.2.1",
28 | "currentDNS": "192.168.2.1",
29 | "enable": true,
30 | "IPMode": "DHCP",
31 | "connectionTrigger": "auto",
32 | "keepAliveTime": "1608188204153",
33 | "address": "192.168.2.1",
34 | "mask": "255.255.255.0",
35 | "gateway": "192.168.2.1",
36 | "staticDns": true,
37 | "DNS": "192.168.2.1",
38 | "username": "tianleilei",
39 | "password": "11000000"
40 | },
41 | {
42 | "id": "101",
43 | "IPStatus": "CONNECTING",
44 | "currentIP": "192.168.2.1",
45 | "currentMask": "255.255.255.0",
46 | "currentGateway": "192.168.2.1",
47 | "currentDNS": "192.168.2.1",
48 | "enable": true,
49 | "IPMode": "DHCP",
50 | "connectionTrigger": "auto",
51 | "keepAliveTime": "1608188204153",
52 | "address": "192.168.2.1",
53 | "mask": "255.255.255.0",
54 | "gateway": "192.168.2.1",
55 | "staticDns": true,
56 | "DNS": "192.168.2.1",
57 | "username": "tianleilei",
58 | "password": "11000000"
59 | },
60 | {
61 | "id": "1011",
62 | "IPStatus": "CONNECTING",
63 | "currentIP": "192.168.2.1",
64 | "currentMask": "255.255.255.0",
65 | "currentGateway": "192.168.2.1",
66 | "currentDNS": "192.168.2.1",
67 | "enable": true,
68 | "IPMode": "DHCP",
69 | "connectionTrigger": "auto",
70 | "keepAliveTime": "1608188204153",
71 | "address": "192.168.2.1",
72 | "mask": "255.255.255.0",
73 | "gateway": "192.168.2.1",
74 | "staticDns": true,
75 | "DNS": "192.168.2.1",
76 | "username": "zhansan",
77 | "password": "11000000"
78 | },
79 | {
80 | "id": "1012",
81 | "IPStatus": "CONNECTING",
82 | "currentIP": "192.168.2.1",
83 | "currentMask": "255.255.255.0",
84 | "currentGateway": "192.168.2.1",
85 | "currentDNS": "192.168.2.1",
86 | "enable": true,
87 | "IPMode": "DHCP",
88 | "connectionTrigger": "auto",
89 | "keepAliveTime": "1608188204153",
90 | "address": "192.168.2.1",
91 | "mask": "255.255.255.0",
92 | "gateway": "192.168.2.1",
93 | "staticDns": true,
94 | "DNS": "192.168.2.1",
95 | "username": "lisi",
96 | "password": "11000000"
97 | },
98 | {
99 | "id": "1013",
100 | "IPStatus": "CONNECTING",
101 | "currentIP": "192.168.2.1",
102 | "currentMask": "255.255.255.0",
103 | "currentGateway": "192.168.2.1",
104 | "currentDNS": "192.168.2.1",
105 | "enable": true,
106 | "IPMode": "DHCP",
107 | "connectionTrigger": "auto",
108 | "keepAliveTime": "1608188204153",
109 | "address": "192.168.2.1",
110 | "mask": "255.255.255.0",
111 | "gateway": "192.168.2.1",
112 | "staticDns": true,
113 | "DNS": "192.168.2.1",
114 | "username": "lisi",
115 | "password": "11000000"
116 | },
117 | {
118 | "id": "1014",
119 | "IPStatus": "CONNECTING",
120 | "currentIP": "192.168.2.1",
121 | "currentMask": "255.255.255.0",
122 | "currentGateway": "192.168.2.1",
123 | "currentDNS": "192.168.2.1",
124 | "enable": true,
125 | "IPMode": "DHCP",
126 | "connectionTrigger": "auto",
127 | "keepAliveTime": "1608188204153",
128 | "address": "192.168.2.1",
129 | "mask": "255.255.255.0",
130 | "gateway": "192.168.2.1",
131 | "staticDns": true,
132 | "DNS": "192.168.2.1",
133 | "username": "lisi",
134 | "password": "11000000"
135 | },
136 | {
137 | "id": "1015",
138 | "IPStatus": "CONNECTING",
139 | "currentIP": "192.168.2.1",
140 | "currentMask": "255.255.255.0",
141 | "currentGateway": "192.168.2.1",
142 | "currentDNS": "192.168.2.1",
143 | "enable": true,
144 | "IPMode": "DHCP",
145 | "connectionTrigger": "auto",
146 | "keepAliveTime": "1608188204153",
147 | "address": "192.168.2.1",
148 | "mask": "255.255.255.0",
149 | "gateway": "192.168.2.1",
150 | "staticDns": true,
151 | "DNS": "192.168.2.1",
152 | "username": "lisi",
153 | "password": "11000000"
154 | },
155 | {
156 | "id": "1016",
157 | "IPStatus": "CONNECTING",
158 | "currentIP": "192.168.2.1",
159 | "currentMask": "255.255.255.0",
160 | "currentGateway": "192.168.2.1",
161 | "currentDNS": "192.168.2.1",
162 | "enable": true,
163 | "IPMode": "DHCP",
164 | "connectionTrigger": "auto",
165 | "keepAliveTime": "1608188204153",
166 | "address": "192.168.2.1",
167 | "mask": "255.255.255.0",
168 | "gateway": "192.168.2.1",
169 | "staticDns": true,
170 | "DNS": "192.168.2.1",
171 | "username": "lisi",
172 | "password": "11000000"
173 | },
174 | {
175 | "id": "1017",
176 | "IPStatus": "CONNECTING",
177 | "currentIP": "192.168.2.1",
178 | "currentMask": "255.255.255.0",
179 | "currentGateway": "192.168.2.1",
180 | "currentDNS": "192.168.2.1",
181 | "enable": true,
182 | "IPMode": "DHCP",
183 | "connectionTrigger": "auto",
184 | "keepAliveTime": "1608188204153",
185 | "address": "192.168.2.1",
186 | "mask": "255.255.255.0",
187 | "gateway": "192.168.2.1",
188 | "staticDns": true,
189 | "DNS": "192.168.2.1",
190 | "username": "lisi",
191 | "password": "11000000"
192 | },
193 | {
194 | "id": "090",
195 | "IPStatus": "CONNECTING",
196 | "currentIP": "192.168.2.1",
197 | "currentMask": "255.255.255.0",
198 | "currentGateway": "192.168.2.1",
199 | "currentDNS": "192.168.2.1",
200 | "enable": true,
201 | "IPMode": "DHCP",
202 | "connectionTrigger": "auto",
203 | "keepAliveTime": "1608188204153",
204 | "address": "192.168.2.1",
205 | "mask": "255.255.255.0",
206 | "gateway": "192.168.2.1",
207 | "staticDns": true,
208 | "DNS": "192.168.2.1",
209 | "username": "tianleilei",
210 | "password": "11000000"
211 | },
212 | {
213 | "id": "789",
214 | "IPStatus": "CONNECTING",
215 | "currentIP": "192.168.2.1",
216 | "currentMask": "255.255.255.0",
217 | "currentGateway": "192.168.2.1",
218 | "currentDNS": "192.168.2.1",
219 | "enable": true,
220 | "IPMode": "DHCP",
221 | "connectionTrigger": "auto",
222 | "keepAliveTime": "1608188204153",
223 | "address": "192.168.2.1",
224 | "mask": "255.255.255.0",
225 | "gateway": "192.168.2.1",
226 | "staticDns": true,
227 | "DNS": "192.168.2.1",
228 | "username": "tianleilei",
229 | "password": "11000000"
230 | },
231 | {
232 | "id": "1234",
233 | "IPStatus": "CONNECTING",
234 | "currentIP": "192.168.2.1",
235 | "currentMask": "255.255.255.0",
236 | "currentGateway": "192.168.2.1",
237 | "currentDNS": "192.168.2.1",
238 | "enable": true,
239 | "IPMode": "DHCP",
240 | "connectionTrigger": "auto",
241 | "keepAliveTime": "1608188204153",
242 | "address": "192.168.2.1",
243 | "mask": "255.255.255.0",
244 | "gateway": "192.168.2.1",
245 | "staticDns": true,
246 | "DNS": "192.168.2.1",
247 | "username": "tianleilei",
248 | "password": "11000000"
249 | },
250 | {
251 | "id": "666",
252 | "IPStatus": "CONNECTING",
253 | "currentIP": "192.168.2.1",
254 | "currentMask": "255.255.255.0",
255 | "currentGateway": "192.168.2.1",
256 | "currentDNS": "192.168.2.1",
257 | "enable": true,
258 | "IPMode": "DHCP",
259 | "connectionTrigger": "auto",
260 | "keepAliveTime": "1608188204153",
261 | "address": "192.168.2.1",
262 | "mask": "255.255.255.0",
263 | "gateway": "192.168.2.1",
264 | "staticDns": true,
265 | "DNS": "192.168.2.1",
266 | "username": "tianleilei",
267 | "password": "11000000"
268 | },
269 | {
270 | "id": "8789",
271 | "IPStatus": "CONNECTING",
272 | "currentIP": "192.168.2.1",
273 | "currentMask": "255.255.255.0",
274 | "currentGateway": "192.168.2.1",
275 | "currentDNS": "192.168.2.1",
276 | "enable": true,
277 | "IPMode": "DHCP",
278 | "connectionTrigger": "auto",
279 | "keepAliveTime": "1608188204153",
280 | "address": "192.168.2.1",
281 | "mask": "255.255.255.0",
282 | "gateway": "192.168.2.1",
283 | "staticDns": true,
284 | "DNS": "192.168.2.1",
285 | "username": "tianleilei",
286 | "password": "11000000"
287 | }
288 | ]
289 | }
--------------------------------------------------------------------------------
/src/mock/data.json:
--------------------------------------------------------------------------------
1 | {
2 | "deviceinfo": {
3 | "model": "model",
4 | "hwVer": "hwVer",
5 | "swVer": "swVer",
6 | "SN": "SN",
7 | "currentTime": "2020-12-12 13:23:09",
8 | "deviceName": "中国移动路由器",
9 | "vendor": "qwewasdasx",
10 | "deviceUpTime": "1608188204153",
11 | "IPUpTime": "1608188204153",
12 | "cpuUsage": 12,
13 | "memoryUsage": 100,
14 | "configuredState": true,
15 | "attachedDeviceNum": 32,
16 | "meshRouterNum": 2,
17 | "routerIP": "192.168.2.1",
18 | "upRate": 29210,
19 | "downRate": 12
20 | },
21 | "wans": [
22 | {
23 | "id": "99999",
24 | "IPStatus": "CONNECTING",
25 | "currentIP": "192.168.2.1",
26 | "currentMask": "255.255.255.0",
27 | "currentGateway": "192.168.2.1",
28 | "currentDNS": "192.168.2.1",
29 | "enable": true,
30 | "IPMode": "DHCP",
31 | "connectionTrigger": "auto",
32 | "keepAliveTime": "1608188204153",
33 | "address": "192.168.2.1",
34 | "mask": "255.255.255.0",
35 | "gateway": "192.168.2.1",
36 | "staticDns": true,
37 | "DNS": "192.168.2.1",
38 | "username": "tianleilei",
39 | "password": "11000000"
40 | },
41 | {
42 | "id": "101",
43 | "IPStatus": "CONNECTING",
44 | "currentIP": "192.168.2.1",
45 | "currentMask": "255.255.255.0",
46 | "currentGateway": "192.168.2.1",
47 | "currentDNS": "192.168.2.1",
48 | "enable": true,
49 | "IPMode": "DHCP",
50 | "connectionTrigger": "auto",
51 | "keepAliveTime": "1608188204153",
52 | "address": "192.168.2.1",
53 | "mask": "255.255.255.0",
54 | "gateway": "192.168.2.1",
55 | "staticDns": true,
56 | "DNS": "192.168.2.1",
57 | "username": "tianleilei",
58 | "password": "11000000"
59 | },
60 | {
61 | "id": "1011",
62 | "IPStatus": "CONNECTING",
63 | "currentIP": "192.168.2.1",
64 | "currentMask": "255.255.255.0",
65 | "currentGateway": "192.168.2.1",
66 | "currentDNS": "192.168.2.1",
67 | "enable": true,
68 | "IPMode": "DHCP",
69 | "connectionTrigger": "auto",
70 | "keepAliveTime": "1608188204153",
71 | "address": "192.168.2.1",
72 | "mask": "255.255.255.0",
73 | "gateway": "192.168.2.1",
74 | "staticDns": true,
75 | "DNS": "192.168.2.1",
76 | "username": "zhansan",
77 | "password": "11000000"
78 | },
79 | {
80 | "id": "1012",
81 | "IPStatus": "CONNECTING",
82 | "currentIP": "192.168.2.1",
83 | "currentMask": "255.255.255.0",
84 | "currentGateway": "192.168.2.1",
85 | "currentDNS": "192.168.2.1",
86 | "enable": true,
87 | "IPMode": "DHCP",
88 | "connectionTrigger": "auto",
89 | "keepAliveTime": "1608188204153",
90 | "address": "192.168.2.1",
91 | "mask": "255.255.255.0",
92 | "gateway": "192.168.2.1",
93 | "staticDns": true,
94 | "DNS": "192.168.2.1",
95 | "username": "lisi",
96 | "password": "11000000"
97 | },
98 | {
99 | "id": "1013",
100 | "IPStatus": "CONNECTING",
101 | "currentIP": "192.168.2.1",
102 | "currentMask": "255.255.255.0",
103 | "currentGateway": "192.168.2.1",
104 | "currentDNS": "192.168.2.1",
105 | "enable": true,
106 | "IPMode": "DHCP",
107 | "connectionTrigger": "auto",
108 | "keepAliveTime": "1608188204153",
109 | "address": "192.168.2.1",
110 | "mask": "255.255.255.0",
111 | "gateway": "192.168.2.1",
112 | "staticDns": true,
113 | "DNS": "192.168.2.1",
114 | "username": "lisi",
115 | "password": "11000000"
116 | },
117 | {
118 | "id": "1014",
119 | "IPStatus": "CONNECTING",
120 | "currentIP": "192.168.2.1",
121 | "currentMask": "255.255.255.0",
122 | "currentGateway": "192.168.2.1",
123 | "currentDNS": "192.168.2.1",
124 | "enable": true,
125 | "IPMode": "DHCP",
126 | "connectionTrigger": "auto",
127 | "keepAliveTime": "1608188204153",
128 | "address": "192.168.2.1",
129 | "mask": "255.255.255.0",
130 | "gateway": "192.168.2.1",
131 | "staticDns": true,
132 | "DNS": "192.168.2.1",
133 | "username": "lisi",
134 | "password": "11000000"
135 | },
136 | {
137 | "id": "1015",
138 | "IPStatus": "CONNECTING",
139 | "currentIP": "192.168.2.1",
140 | "currentMask": "255.255.255.0",
141 | "currentGateway": "192.168.2.1",
142 | "currentDNS": "192.168.2.1",
143 | "enable": true,
144 | "IPMode": "DHCP",
145 | "connectionTrigger": "auto",
146 | "keepAliveTime": "1608188204153",
147 | "address": "192.168.2.1",
148 | "mask": "255.255.255.0",
149 | "gateway": "192.168.2.1",
150 | "staticDns": true,
151 | "DNS": "192.168.2.1",
152 | "username": "lisi",
153 | "password": "11000000"
154 | },
155 | {
156 | "id": "1016",
157 | "IPStatus": "CONNECTING",
158 | "currentIP": "192.168.2.1",
159 | "currentMask": "255.255.255.0",
160 | "currentGateway": "192.168.2.1",
161 | "currentDNS": "192.168.2.1",
162 | "enable": true,
163 | "IPMode": "DHCP",
164 | "connectionTrigger": "auto",
165 | "keepAliveTime": "1608188204153",
166 | "address": "192.168.2.1",
167 | "mask": "255.255.255.0",
168 | "gateway": "192.168.2.1",
169 | "staticDns": true,
170 | "DNS": "192.168.2.1",
171 | "username": "lisi",
172 | "password": "11000000"
173 | },
174 | {
175 | "id": "1017",
176 | "IPStatus": "CONNECTING",
177 | "currentIP": "192.168.2.1",
178 | "currentMask": "255.255.255.0",
179 | "currentGateway": "192.168.2.1",
180 | "currentDNS": "192.168.2.1",
181 | "enable": true,
182 | "IPMode": "DHCP",
183 | "connectionTrigger": "auto",
184 | "keepAliveTime": "1608188204153",
185 | "address": "192.168.2.1",
186 | "mask": "255.255.255.0",
187 | "gateway": "192.168.2.1",
188 | "staticDns": true,
189 | "DNS": "192.168.2.1",
190 | "username": "lisi",
191 | "password": "11000000"
192 | },
193 | {
194 | "id": "090",
195 | "IPStatus": "CONNECTING",
196 | "currentIP": "192.168.2.1",
197 | "currentMask": "255.255.255.0",
198 | "currentGateway": "192.168.2.1",
199 | "currentDNS": "192.168.2.1",
200 | "enable": true,
201 | "IPMode": "DHCP",
202 | "connectionTrigger": "auto",
203 | "keepAliveTime": "1608188204153",
204 | "address": "192.168.2.1",
205 | "mask": "255.255.255.0",
206 | "gateway": "192.168.2.1",
207 | "staticDns": true,
208 | "DNS": "192.168.2.1",
209 | "username": "tianleilei",
210 | "password": "11000000"
211 | },
212 | {
213 | "id": "789",
214 | "IPStatus": "CONNECTING",
215 | "currentIP": "192.168.2.1",
216 | "currentMask": "255.255.255.0",
217 | "currentGateway": "192.168.2.1",
218 | "currentDNS": "192.168.2.1",
219 | "enable": true,
220 | "IPMode": "DHCP",
221 | "connectionTrigger": "auto",
222 | "keepAliveTime": "1608188204153",
223 | "address": "192.168.2.1",
224 | "mask": "255.255.255.0",
225 | "gateway": "192.168.2.1",
226 | "staticDns": true,
227 | "DNS": "192.168.2.1",
228 | "username": "tianleilei",
229 | "password": "11000000"
230 | },
231 | {
232 | "id": "1234",
233 | "IPStatus": "CONNECTING",
234 | "currentIP": "192.168.2.1",
235 | "currentMask": "255.255.255.0",
236 | "currentGateway": "192.168.2.1",
237 | "currentDNS": "192.168.2.1",
238 | "enable": true,
239 | "IPMode": "DHCP",
240 | "connectionTrigger": "auto",
241 | "keepAliveTime": "1608188204153",
242 | "address": "192.168.2.1",
243 | "mask": "255.255.255.0",
244 | "gateway": "192.168.2.1",
245 | "staticDns": true,
246 | "DNS": "192.168.2.1",
247 | "username": "tianleilei",
248 | "password": "11000000"
249 | },
250 | {
251 | "id": "666",
252 | "IPStatus": "CONNECTING",
253 | "currentIP": "192.168.2.1",
254 | "currentMask": "255.255.255.0",
255 | "currentGateway": "192.168.2.1",
256 | "currentDNS": "192.168.2.1",
257 | "enable": true,
258 | "IPMode": "DHCP",
259 | "connectionTrigger": "auto",
260 | "keepAliveTime": "1608188204153",
261 | "address": "192.168.2.1",
262 | "mask": "255.255.255.0",
263 | "gateway": "192.168.2.1",
264 | "staticDns": true,
265 | "DNS": "192.168.2.1",
266 | "username": "tianleilei",
267 | "password": "11000000"
268 | },
269 | {
270 | "id": "8789",
271 | "IPStatus": "CONNECTING",
272 | "currentIP": "192.168.2.1",
273 | "currentMask": "255.255.255.0",
274 | "currentGateway": "192.168.2.1",
275 | "currentDNS": "192.168.2.1",
276 | "enable": true,
277 | "IPMode": "DHCP",
278 | "connectionTrigger": "auto",
279 | "keepAliveTime": "1608188204153",
280 | "address": "192.168.2.1",
281 | "mask": "255.255.255.0",
282 | "gateway": "192.168.2.1",
283 | "staticDns": true,
284 | "DNS": "192.168.2.1",
285 | "username": "tianleilei",
286 | "password": "11000000"
287 | }
288 | ]
289 | }
--------------------------------------------------------------------------------
/src/mock/server.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const jsonServer = require('json-server');
3 | const server = jsonServer.create();
4 | // eslint-disable-next-line @typescript-eslint/no-var-requires
5 | const path = require("path");
6 | const router = jsonServer.router(path.join(__dirname, './data.json'));
7 | const middlewares = jsonServer.defaults();
8 | server.use(middlewares);
9 |
10 | server.use(jsonServer.rewriter({
11 | "/api/*": "/$1",
12 | "/wans/:id/ipaddrs/": "/wans/:id",
13 | "/wans/:id/ipaddrs/:ipaddrs": "/wans/:id/?ipaddrs=:ipaddrs"
14 | }));
15 |
16 | router.render = (req, res) => {
17 | let errcode = "";
18 | let message = "";
19 | let status = 200;
20 | let result = res.locals.data;
21 |
22 | if(Object.getOwnPropertyNames(result).length < 1) {
23 | errcode = 3;
24 | }
25 | switch (errcode){
26 | case 0:
27 | message="Fail to alloc memory";
28 | status=400;
29 | result={};
30 | break;
31 | case 1:
32 | message="Out of memory";
33 | status=400;
34 | result={};
35 | break;
36 | case 2:
37 | message="invalid json request";
38 | status=400;
39 | result={};
40 | break;
41 | case 3:
42 | message="No Content-length";
43 | status=400;
44 | result={};
45 | break;
46 | case 4:
47 | message="unable to parse cookie";
48 | status=400;
49 | result={};
50 | break;
51 | case 5:
52 | message="Fail to init URL pattern";
53 | status=400;
54 | result={};
55 | break;
56 | case 6:
57 | message="Api is not suppord";
58 | status=404;
59 | result={};
60 | break;
61 | case 7:
62 | message="parameter format check failed";
63 | status=400;
64 | result={};
65 | break;
66 | case 8:
67 | message="Please login";
68 | status=401;
69 | result={};
70 | break;
71 | case 9:
72 | message="Anti Brute Attack: Please Wait 20s";
73 | status=403;
74 | result={};
75 | break;
76 | default:
77 | message="";
78 | status=200;
79 | }
80 |
81 | res.status(status);
82 | res.jsonp({
83 | err: errcode,
84 | msg: message,
85 | result: result
86 | });
87 | };
88 |
89 | server.use(router);
90 | server.listen(3000, () => { // http://localhost:3000/wans
91 | console.log('json server is running');
92 | });
--------------------------------------------------------------------------------
/src/request/example.js:
--------------------------------------------------------------------------------
1 | const request = require('./index');
2 | // import request from "./index";
3 |
4 | request.get('http://localhost:3001/api/wans').then((data) => {
5 | console.log(data, 'datasssss');
6 | });
7 |
8 | let params = {
9 | id: '101',
10 | IPStatus: 'CONNECTING',
11 | currentIP: '192.168.2.1',
12 | currentMask: '255.255.255.0',
13 | currentGateway: '192.168.2.1',
14 | currentDNS: '192.168.2.1',
15 | enable: true,
16 | IPMode: 'DHCP',
17 | connectionTrigger: 'auto',
18 | keepAliveTime: '1608188204153',
19 | address: '192.168.2.1',
20 | mask: '255.255.255.0',
21 | gateway: '192.168.2.1',
22 | staticDns: true,
23 | DNS: '192.168.2.1',
24 | username: 'tianleilei',
25 | password: '11000000'
26 | };
27 | // request.post('http://localhost:3000/api/wans', params).then((data) => {
28 | // console.log(data, 'datasssss');
29 | // })
30 |
31 | request.on('HttpStatusSuccess', () => {
32 | // 这里进行自定义弹窗提示
33 | console.log("现在是请求成功了");
34 | });
35 | request.on('HttpStatusCodeError', () => {
36 | // 这里进行自定义弹窗错误提示
37 | console.log("现在是请求失败了");
38 | });
--------------------------------------------------------------------------------
/src/request/index.js:
--------------------------------------------------------------------------------
1 | import * as axios from "axios";
2 | import * as EventEmitter from 'events';
3 | class Request extends EventEmitter{
4 | constructor(){
5 | super();
6 | this.interceptors();
7 | }
8 | interceptors(){
9 | axios.interceptors.request.use(
10 | config => {
11 | return config;
12 | },
13 | error => {
14 | return Promise.reject(error);
15 | }
16 | );
17 | axios.interceptors.response.use(
18 | response => {
19 | const code = response.status;
20 | if ((code >= 200 && code < 300) || code === 304) {
21 | this.emit("HttpStatusSuccess");
22 | // response.data
23 | return Promise.resolve(response.data);
24 | } else {
25 | // 响应错误逻辑处理 5xx 4xx 等等
26 | this.emit("HttpStatusFaild");
27 | return Promise.reject(response);
28 | }
29 | },
30 | error => {
31 | // 响应错误逻辑处理
32 | // const code = response.status;
33 | console.log(error);
34 | this.emit("HttpStatusFaild");
35 | return Promise.reject(error);
36 | }
37 | );
38 | }
39 | get(url, params){
40 | return axios({
41 | method: 'get',
42 | url,
43 | params
44 | });
45 | }
46 | post(url, data){
47 | return axios({
48 | method: 'post',
49 | url,
50 | data
51 | });
52 | }
53 | delete(url, data){
54 | return axios({
55 | method: 'delete',
56 | url,
57 | data
58 | });
59 | }
60 | put(url, data){
61 | return axios({
62 | method: 'put',
63 | url,
64 | data
65 | });
66 | }
67 | patch(url, data){
68 | return axios({
69 | method: 'patch',
70 | url,
71 | data
72 | });
73 | }
74 | }
75 |
76 | let request = new Request();
77 |
78 | export default request;
79 |
80 |
--------------------------------------------------------------------------------
/src/router/defaultRoutes/index.ts:
--------------------------------------------------------------------------------
1 | import Inner from "@/views/Inner.vue";
2 | import NotFound from '@/views/NotFound.vue';
3 | import Login from "@/views/Login.vue";
4 | const defaultRoutes: any = [
5 | {
6 | path: "/inner",
7 | name: "内部页面",
8 | component: Inner,
9 | meta: {
10 | activePath: '/' // 打开非Menu页面选择当前激活menu
11 | }
12 | },
13 | {
14 | path: '/:pathMatch(.*)*',
15 | name: '404',
16 | component: NotFound
17 | },
18 | {
19 | path: '/login',
20 | name: '登录',
21 | component: Login
22 | }
23 | ];
24 |
25 | export default defaultRoutes;
26 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
2 | import { staticRoutes } from './staticRoutes';
3 | import defaultRoutes from './defaultRoutes';
4 |
5 | const routes: any = staticRoutes.concat(defaultRoutes);
6 |
7 | const router = createRouter({
8 | history: createWebHashHistory(),
9 | routes
10 | });
11 |
12 | router.beforeEach((to, from, next) => {
13 | let userInfo = localStorage.getItem('user');
14 | if (to.path === "/login") {
15 | next();
16 | } else {
17 | if (userInfo) {
18 | next();
19 | } else {
20 | next({
21 | path: '/login'
22 | });
23 | }
24 | }
25 | });
26 | export default router;
27 |
--------------------------------------------------------------------------------
/src/router/staticRoutes/index.ts:
--------------------------------------------------------------------------------
1 | import Wrapper from '@/layout/components/Wrapper/index.vue';
2 | import Home from '@/views/Home.vue';
3 | import Document from '@/views/Document.vue';
4 | import Tab from '@/views/Tab.vue';
5 | import Image from '@/views/Image.vue';
6 | import Button from '@/views/Button.vue';
7 | import Date from '@/views/Date.vue';
8 | import Component from '@/views/Component.vue';
9 | /**
10 | *
11 | * 路由配置规则:
12 | *
13 | * {
14 | * path:'',路径
15 | * name:'',路由名称,生成menu时menu name
16 | * meta:{},额外信息,icon为menu中的icon
17 | * children: [], 子路由,menu中的子menu 没有时可为空数组
18 | * }
19 | *
20 | */
21 |
22 |
23 | export const staticRoutes = [
24 | {
25 | path: '/',
26 | name: '首页',
27 | component: Home,
28 | children: [],
29 | meta: {
30 | icon: 'el-icon-s-home'
31 | }
32 | },
33 | {
34 | path: '/doc',
35 | name: '文档',
36 | redirect: '/doc/doctxt',
37 | component: Wrapper,
38 | meta: {
39 | icon: 'el-icon-s-order'
40 | },
41 | children: [
42 | {
43 | path: 'doctxt',
44 | name: '文本',
45 | component: Wrapper,
46 | meta: {
47 | icon: 'el-icon-s-data'
48 | },
49 | children: [
50 | {
51 | path: 'doctxtooo',
52 | name: '文本1',
53 | component: Wrapper,
54 | meta: {
55 | icon: ''
56 | },
57 | children: [
58 | {
59 | path: 'docimg1',
60 | name: '文本内容',
61 | component: Image,
62 | children: [],
63 | meta: {
64 | icon: ''
65 | }
66 | }
67 | ]
68 | },
69 | {
70 | path: 'doctxtiii',
71 | name: '文本2',
72 | component: Document,
73 | children: [],
74 | meta: {
75 | icon: ''
76 | }
77 | }
78 | ]
79 | },
80 | {
81 | path: 'docimg',
82 | name: '图像',
83 | component: Image,
84 | children: [],
85 | meta: {
86 | icon: 'el-icon-camera'
87 | }
88 | }
89 | ]
90 | },
91 | {
92 | path: '/tab',
93 | name: '选项',
94 | component: Tab,
95 | children: [],
96 | meta: {
97 | icon: 'el-icon-s-release'
98 | }
99 | },
100 | {
101 | path: '/button',
102 | name: '按钮',
103 | component: Button,
104 | children: [],
105 | meta: {
106 | icon: 'el-icon-s-grid'
107 | }
108 | },
109 | {
110 | path: '/date',
111 | name: '日期',
112 | component: Date,
113 | children: [],
114 | meta: {
115 | icon: 'el-icon-date'
116 | }
117 | },
118 | {
119 | path: '/component',
120 | name: '组件',
121 | component: Component,
122 | children: [],
123 | meta: {
124 | icon: 'el-icon-orange'
125 | }
126 | }
127 | ];
128 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue';
3 | const component: DefineComponent<{}, {}, any>;
4 | export default component;
5 | }
6 | declare module '*'
7 |
--------------------------------------------------------------------------------
/src/store/controls/index.ts:
--------------------------------------------------------------------------------
1 | import { staticRoutes } from '../../router/staticRoutes';
2 | export default {
3 | state: {
4 | isCollapse: false, // 控制菜单展开与折叠
5 | staticRoutes: staticRoutes
6 | },
7 | mutations: {
8 | TOOGLESIDEBAR (state: any) {
9 | state.isCollapse = !(state.isCollapse);
10 | }
11 | },
12 | actions: {
13 | },
14 | getters: {
15 | },
16 | modules: {
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex';
2 | import controls from './controls';
3 | export default createStore({
4 | modules: {
5 | controls
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/src/style/style.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mstian/Vue-Onepiece-Admin/76003a01025aede8877d74d8c0fc665ff100bad9/src/style/style.less
--------------------------------------------------------------------------------
/src/style/theme.less:
--------------------------------------------------------------------------------
1 | @customTheme:#FFFFFF;
2 |
3 | :export {
4 | customTheme: @customTheme
5 | }
--------------------------------------------------------------------------------
/src/style/transition.less:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .5s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .5s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .5s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/src/style/variable.less:
--------------------------------------------------------------------------------
1 | // base color
2 | @blue:#324157;
3 | @light-blue:#3A71A8;
4 | @red:#C03639;
5 | @pink: #E65D6E;
6 | @green: #30B08F;
7 | @tiffany: #4AB7BD;
8 | @yellow:#FEC171;
9 | @panGreen: #30B08F;
10 | // ['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]
11 |
12 | // sidebar
13 | @menuText:#ddd;
14 | @menuActiveText:@tiffany; // 激活项颜色
15 | @subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951
16 |
17 | @menuBg:#304156;
18 | @menuHover:#263445;
19 |
20 | @subMenuBg:#1f2d3d;
21 | @subMenuHover:#001528;
22 |
23 | @sideBarWidth: 210px;
24 |
25 | // the :export directive is the magic sauce for webpack
26 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
27 | :export {
28 | menuText: @menuText;
29 | menuActiveText: @menuActiveText;
30 | subMenuActiveText: @subMenuActiveText;
31 | menuBg: @menuBg;
32 | menuHover: @menuHover;
33 | subMenuBg: @subMenuBg;
34 | subMenuHover: @subMenuHover;
35 | sideBarWidth: @sideBarWidth;
36 | }
37 |
--------------------------------------------------------------------------------
/src/typed-request.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.js' {
2 | const content: any;
3 | export default content;
4 | }
--------------------------------------------------------------------------------
/src/typed-scss.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.scss' {
2 | const content: any;
3 | export default content;
4 | }
5 | declare module '*.less' {
6 | const content: any;
7 | export default content;
8 | }
--------------------------------------------------------------------------------
/src/views/Button.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 |
48 |
49 |
54 |
--------------------------------------------------------------------------------
/src/views/Component.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 | 新增
14 |
15 |
16 |
17 | 删除
18 | 查看
19 |
20 |
21 |
22 |
23 |
24 |
77 |
78 |
81 |
--------------------------------------------------------------------------------
/src/views/Date.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
--------------------------------------------------------------------------------
/src/views/Document.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 青春是一个短暂的美梦, 当你醒来时, 它早已消失无踪
4 |
5 | 少量的邪恶足以抵消全部高贵的品质, 害得人声名狼藉
6 |
7 |
8 |
9 |
14 |
19 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
欢迎使用VUE3.0 + ElementPlus 后台管理模板
4 |
1. 环境变量:{{ envName.title }}
5 |
2. {{ "当前主题色值:" + themeApi.theme.customTheme }}
6 |
7 |
8 | 3.
切换theme
9 |
theme切换采用 将less变量存储在响应式变量中,动态切换该变量达到切换theme(右侧抽屉中可体验)核心逻辑在compisition/useThemeApi
10 |
11 |
12 | 4.
跳转内部页面
13 |
跳转页面是非menu页面
14 |
15 |
16 |
5. 自动menu生成:根据路由生成menu 在router->staticRoutes文件中按照路由规则配置,该menu采用递归组件,可以放心嵌套children自动生成submenu
17 |
6. 非参与menu路由生成:在router->defaultRoutes文件中配置,包括404路由等
18 |
7. 头部标签根据menu自动生成核心逻辑在compisition/useTagViewApi
19 |
8. 当前激活菜单和头部标签激活项颜色在less变量中可设置 style/variable.less/@menuActiveText
20 |

21 |
22 |
9. mock数据:采用json-server基于RESTfulapi风格模拟数据 参考:src/mock/ 启动mock服务 npm run mock
23 |
点击加载数据
24 |
25 | {{JSON.stringify(mockData.data, null, " ")}}
26 |
27 |
28 |
29 |
30 |
73 |
89 |
--------------------------------------------------------------------------------
/src/views/Image.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 默认
5 |
6 |
7 |
8 |
自定义
9 |
10 |
11 | 加载中...
12 |
13 |
14 |
15 |
16 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/src/views/Inner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 这是一个不参与Menu的路由,左侧menu中没有,也不会在头部标签中存在
4 |
5 |
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
56 |
57 |
85 |
92 |
--------------------------------------------------------------------------------
/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
14 |
22 |
--------------------------------------------------------------------------------
/src/views/Tab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item }}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
23 |
40 |
--------------------------------------------------------------------------------
/src/views/Table.vue:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-empty-function */
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 |
35 | 移除
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
90 |
91 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "experimentalDecorators": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "sourceMap": true,
14 | "baseUrl": ".",
15 | "types": [
16 | "webpack-env"
17 | ],
18 | "paths": {
19 | "@/*": [
20 | "src/*"
21 | ]
22 | },
23 | "lib": [
24 | "esnext",
25 | "dom",
26 | "dom.iterable",
27 | "scripthost"
28 | ]
29 | },
30 | "include": [
31 | "src/**/*.ts",
32 | "src/**/*.scss",
33 | "src/**/*.less",
34 | "src/**/*.tsx",
35 | "src/**/*.vue",
36 | "tests/**/*.ts",
37 | "tests/**/*.tsx"
38 | ],
39 | "exclude": [
40 | "node_modules",
41 | "src/**/*.js"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: './',
3 | devServer: {
4 | host: 'localhost'
5 | },
6 | configureWebpack: {
7 | devtool: "inline-source-map"
8 | },
9 | css: {
10 | extract: false
11 | }
12 | };
13 |
--------------------------------------------------------------------------------