├── .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 | ![preview](https://upload-images.jianshu.io/upload_images/17538702-a79fed6717f6f0eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 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 | ![JsonTable预览图](https://upload-images.jianshu.io/upload_images/17538702-2e524aab18777d8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 106 | 107 | 以下为组件使用示例 以及配置项示例 108 | 109 | ``` 110 | 118 | 119 | 124 | 125 | 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 | ![image.png](https://upload-images.jianshu.io/upload_images/17538702-a1c6540d90e96e19.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 430 | 431 | 由于之前组件设计问题,导致登录界面不太好加,所以找了一个比较trick的方案。将登录路由添加到页面中。 432 | ``` 433 | 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 | 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 | 262 | 263 | 1001 | 1002 | 1073 | 1086 | 1092 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 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 | ![JsonTable预览图](https://upload-images.jianshu.io/upload_images/17538702-2e524aab18777d8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 13 | 14 | 以下为组件使用示例 以及配置项示例 15 | 16 | ``` 17 | 25 | 26 | 31 | 32 | 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 | 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 | 31 | 65 | -------------------------------------------------------------------------------- /src/layout/components/Header/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 63 | 122 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 32 | 54 | 97 | -------------------------------------------------------------------------------- /src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 63 | -------------------------------------------------------------------------------- /src/layout/components/Tagsview/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 36 | 107 | -------------------------------------------------------------------------------- /src/layout/components/Wrapper/index.vue: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 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 | 39 | 40 | 48 | 49 | 54 | -------------------------------------------------------------------------------- /src/views/Component.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 77 | 78 | 81 | -------------------------------------------------------------------------------- /src/views/Date.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/views/Document.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 19 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 73 | 89 | -------------------------------------------------------------------------------- /src/views/Image.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /src/views/Inner.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 56 | 57 | 85 | 92 | -------------------------------------------------------------------------------- /src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 22 | -------------------------------------------------------------------------------- /src/views/Tab.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 40 | -------------------------------------------------------------------------------- /src/views/Table.vue: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | 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 | --------------------------------------------------------------------------------