Browse Source

项目初始化

i18n
FC 3 years ago
commit
6c9e01bd6a
  1. 14
      .editorconfig
  2. 5
      .env.development
  3. 6
      .env.production
  4. 4
      .eslintignore
  5. 26
      .eslintrc.js
  6. 16
      .gitignore
  7. 42
      .prettierrc.js
  8. 8
      .stylelintrc.js
  9. 133
      README.md
  10. 11
      babel.config.js
  11. 35
      build/index.js
  12. 24
      gulpfile.js
  13. 9
      jsconfig.json
  14. 76
      package.json
  15. 6
      postcss.config.js
  16. BIN
      public/favicon.png
  17. 17
      public/index.html
  18. 21
      src/App.vue
  19. 17
      src/api/user.js
  20. BIN
      src/assets/logo/logo.png
  21. 69
      src/assets/logo/winbox-logo.svg
  22. 39
      src/assets/logo/winbox.svg
  23. BIN
      src/assets/setting-drawer/black.png
  24. BIN
      src/assets/setting-drawer/blue.png
  25. BIN
      src/assets/setting-drawer/cyan.png
  26. BIN
      src/assets/setting-drawer/default.png
  27. BIN
      src/assets/setting-drawer/horizontal.png
  28. BIN
      src/assets/setting-drawer/red.png
  29. BIN
      src/assets/setting-drawer/vertical.png
  30. BIN
      src/assets/setting-drawer/white.png
  31. 50
      src/components/Breadcrumb/index.vue
  32. 30
      src/components/Hamburger/index.vue
  33. 62
      src/components/SvgIcon/index.vue
  34. 145
      src/components/settingDrawer/index.vue
  35. 9
      src/icons/index.js
  36. 21
      src/icons/svgo.yml
  37. 50
      src/layout/components/domain.vue
  38. 31
      src/layout/components/horizontalMenu.vue
  39. 36
      src/layout/components/item.vue
  40. 43
      src/layout/components/link.vue
  41. 52
      src/layout/components/logo.vue
  42. 126
      src/layout/components/sidebarItem.vue
  43. 36
      src/layout/components/vertivalMenu.vue
  44. 40
      src/layout/index.vue
  45. 18
      src/layout/layout/appMain.vue
  46. 32
      src/layout/layout/horizontal.vue
  47. 45
      src/layout/layout/vertical.vue
  48. 16
      src/main.js
  49. 22
      src/permission.js
  50. 2
      src/plugins/component.js
  51. 25
      src/plugins/index.js
  52. 10
      src/plugins/prototype.js
  53. 8
      src/plugins/winbox-ui.js
  54. 125
      src/router/index.js
  55. 20
      src/settings.js
  56. 13
      src/store/getters.js
  57. 25
      src/store/index.js
  58. 58
      src/store/modules/app.js
  59. 32
      src/store/modules/settings.js
  60. 40
      src/store/modules/user.js
  61. 19
      src/styles/common/fonts.less
  62. 31
      src/styles/common/mixin.less
  63. 71
      src/styles/global.less
  64. 9
      src/styles/index.less
  65. 128
      src/styles/layout/horizontal.less
  66. 2
      src/styles/layout/index.less
  67. 135
      src/styles/layout/vertical.less
  68. 29
      src/styles/themes/black.less
  69. 29
      src/styles/themes/blue.less
  70. 29
      src/styles/themes/cyan.less
  71. 30
      src/styles/themes/default.less
  72. 29
      src/styles/themes/red.less
  73. 29
      src/styles/themes/white.less
  74. 16
      src/styles/variable.less
  75. 1
      src/styles/winbox-ui/css/black.css
  76. 1
      src/styles/winbox-ui/css/blue.css
  77. 1
      src/styles/winbox-ui/css/cyan.css
  78. 1
      src/styles/winbox-ui/css/default.css
  79. BIN
      src/styles/winbox-ui/css/iconfont.ttf
  80. BIN
      src/styles/winbox-ui/css/iconfont.woff
  81. BIN
      src/styles/winbox-ui/css/iconfont.woff2
  82. 1
      src/styles/winbox-ui/css/red.css
  83. 1
      src/styles/winbox-ui/css/white.css
  84. 6
      src/styles/winbox-ui/less/black.less
  85. 6
      src/styles/winbox-ui/less/blue.less
  86. 6
      src/styles/winbox-ui/less/cyan.less
  87. 6
      src/styles/winbox-ui/less/default.less
  88. 6
      src/styles/winbox-ui/less/red.less
  89. 6
      src/styles/winbox-ui/less/white.less
  90. 15
      src/utils/auth.js
  91. 59
      src/utils/axiosCancel.js
  92. 60
      src/utils/axiosStatus.js
  93. 31
      src/utils/common.js
  94. 17
      src/utils/filters.js
  95. 10
      src/utils/get-page-title.js
  96. 55
      src/utils/request.js
  97. 20
      src/utils/validate.js
  98. 28
      src/views/404.vue
  99. 81
      src/views/demo/form/index.vue
  100. 145
      src/views/demo/table/index.vue

14
.editorconfig

@ -0,0 +1,14 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

5
.env.development

@ -0,0 +1,5 @@
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/dev-api'

6
.env.production

@ -0,0 +1,6 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/prod-api'

4
.eslintignore

@ -0,0 +1,4 @@
build/*.js
src/assets
public
dist

26
.eslintrc.js

@ -0,0 +1,26 @@
module.exports = {
extends: [
'@uxd.wenge/wg',
// 这里是针对 vue2 的配置
'@uxd.wenge/wg/vue'
// 如果是 vue3 的项目工程,则推荐下面配置
// '@uxd.wenge/wg/vue3',
],
env: {
// 你的环境变量(包含多个预定义的全局变量)
//
// browser: true,
// node: true,
// mocha: true,
// jest: true,
// jquery: true
},
globals: {
// 你的全局变量(设置为 false 表示它不允许被重新赋值)
//
// myGlobal: false
},
rules: {
// 自定义你的规则
}
};

16
.gitignore

@ -0,0 +1,16 @@
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
tests/**/coverage/
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

42
.prettierrc.js

@ -0,0 +1,42 @@
// .prettierrc.js
module.exports = {
// 一行最多 160 字符
printWidth: 160,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'ignore',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
// 格式化嵌入的内容
embeddedLanguageFormatting: 'auto',
};

8
.stylelintrc.js

@ -0,0 +1,8 @@
module.exports = {
extends: [
'@uxd.wenge/stylelint-config-wg',
'stylelint-prettier/recommended'
],
rules: {},
plugins: [],
};

133
README.md

@ -0,0 +1,133 @@
## Build Setup
```bash
# 安装依赖
npm install
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev #pc版
```
浏览器访问 [http://localhost:8080](http://localhost:8080)
# src
```bash
# 接口
api
# 静态资源
assets
--- 404_images #404
--- logo #logo
nodata #缺省图
theme #主题图
# 自定义组件
components
# 字体(思源黑体)
fonts #默认系统font-weight: 400 , 加粗 500, 700
# layout
layout
--- components #组件
--- Navbar #上下布局
--- Sidebar #左右布局
--- mixin #混入
--- index
# 全局插件
plugins
--- component.js #全局组件注册
--- element.js #element注册
--- index.js
--- prototype.js #全局通用挂载
# 路由
router
#vuex
store
# 全局样式
styles
--- common #通用
--- element-ui.scss #全局饿了么样式
--- handle.scss #scss变量
--- index.scss
--- themes.scss #主题配置文件
# 工具集
utils
--- auth.js
--- common.js #全局通用js方法
--- filters.js #全局过滤器
--- get-page-title
--- request #axios请求封装
--- validata.js
# 视图
views
#入口文件
main.js
# 路由拦截
permission.js
# title、布局设置
settings.js
--- title 修改网站title和名字
--- fixedHeader layout头部是否固定在屏幕
--- sidebarLogo 侧边导航是否显示logo
--- prefix 本地存储主题、主题色、布局的前缀,防止多系统主题混乱
#开发环境地址配置
.env.development
#生产环境地址配置
.env.production
```
# theme
elementUI主题配置5种主题的源文件,用于配置修改对应的主题前缀名
# gulpfile.js
自动化构建处理ElementUI工具,用于给每个主题增加前缀名
```
npm run gulp
命令执行配置主题,生成对应的文件(记得修改该文件里的theme)
```
## 发布
```bash
# 构建测试环境
npm run build:stage
# 构建生产环境
npm run build:prod #pc版
```
## 其它
```bash
# 预览发布环境效果
npm run preview
# 预览发布环境效果 + 静态资源分析
npm run preview -- --report
# 代码格式检查
npm run lint
# 代码格式检查并自动修复
npm run lint -- --fix
```
更多信息请参考

11
babel.config.js

@ -0,0 +1,11 @@
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
env: {
development: {
plugins: ['dynamic-import-node']
}
}
};

35
build/index.js

@ -0,0 +1,35 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

24
gulpfile.js

@ -0,0 +1,24 @@
/* gulpfile.js */
const gulp = require('gulp');
const less = require('gulp-less');
const cleanCSS = require('gulp-clean-css');
const cssWrap = require('gulp-css-wrap');
const foreach = require('gulp-foreach');
gulp.task('gulp-winbox', done => {
// 编译src/styles/winbox-ui/less目录下的所有less文件
gulp.src(['src/styles/winbox-ui/less/*.less'])
.pipe(less())
.pipe(foreach((stream, file) => {
return stream
.pipe(cssWrap({ selector: `.${file.relative.replace('.css', '')}` }))
.pipe(cleanCSS())
.pipe(gulp.dest('src/styles/winbox-ui/css')); // 将会在src/css下生成对应的css文件;
}));
gulp.src(['node_modules/winbox-ui/themes/fonts/*', '!node_modules/winbox-ui/themes/fonts/*.less'])
.pipe(gulp.dest('src/styles/winbox-ui/css'));
done();
});

9
jsconfig.json

@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

76
package.json

@ -0,0 +1,76 @@
{
"name": "winbox-admin-template",
"version": "1.0.0",
"description": "A vue admin template with Winbox UI & axios & iconfont & permission control & lint",
"author": "fc <fengcheng2000000@qq.com>",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"preview": "node build/index.js --preview",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"lint:es": "eslint \"src/**/*.{vue,js,jsx}\" --fix",
"lint:style": "stylelint \"src/**/*.{vue,less,postcss,css,scss}\" --fix --cache --cache-location node_modules/.cache/stylelint/",
"gulp:css": "gulp gulp-winbox",
"release": "standard-version"
},
"dependencies": {
"axios": "0.24.0",
"core-js": "3.6.5",
"eslint": "^7.32.0",
"js-cookie": "3.0.1",
"less-to-json-loader": "^1.1.0",
"moment": "^2.29.1",
"normalize.css": "7.0.0",
"nprogress": "^0.2.0",
"path-to-regexp": "2.4.0",
"qs": "^6.10.1",
"vue": "2.6.14",
"vue-bus": "^1.2.1",
"vue-router": "3.0.6",
"vuex": "3.6.2",
"winbox-ui": "^0.9.2"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.3",
"@uxd.wenge/eslint-config-wg": "0.0.4",
"@uxd.wenge/stylelint-config-wg": "0.0.6",
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-service": "4.4.4",
"babel-eslint": "10.1.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"connect": "3.6.6",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-css-wrap": "^0.1.2",
"gulp-foreach": "^0.1.0",
"gulp-less": "^5.0.0",
"html-webpack-plugin": "3.2.0",
"less": "^4.1.2",
"less-loader": "^7.3.0",
"prettier": "^2.4.1",
"runjs": "4.3.2",
"serve-static": "1.13.2",
"standard-version": "^6.0.1",
"style-resources-loader": "^1.4.1",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^9.0.3",
"stylelint-prettier": "^2.0.0",
"svg-sprite-loader": "4.1.3",
"vue-cli-plugin-style-resources-loader": "^0.1.5",
"vue-eslint-parser": "^8.0.1",
"vue-template-compiler": "2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"license": "MIT"
}

6
postcss.config.js

@ -0,0 +1,6 @@
module.exports = {
plugins: [
require('autoprefixer')({ overrideBrowserslist: ['> 0.15% in CN'] })// 自动添加css前缀
]
};

BIN
public/favicon.png

After

Width: 84  |  Height: 84  |  Size: 3.5 KiB

17
public/index.html

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimum-scale=1.0, viewport-fit=cover" />
<link rel="icon" href="<%= BASE_URL %>favicon.png" />
<title><%= webpackConfig.name %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

21
src/App.vue

@ -0,0 +1,21 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
import '@/styles/winbox-ui/css/cyan.css';
import '@/styles/winbox-ui/css/default.css';
import '@/styles/winbox-ui/css/red.css';
import '@/styles/winbox-ui/css/blue.css';
import '@/styles/winbox-ui/css/white.css';
import '@/styles/winbox-ui/css/black.css';
export default {
name: 'App',
created() {
const theme = localStorage.getItem('theme') || 'default';
this.$store.dispatch('app/toggleTheme', theme);
}
};
</script>

17
src/api/user.js

@ -0,0 +1,17 @@
import request from '@/utils/request';
export function login(data) {
return request({
url: '/vue-admin-template/user/login',
method: 'post',
data
});
}
export function logout() {
return request({
url: '/login/ssoapplogout',
method: 'get'
});
}

BIN
src/assets/logo/logo.png

After

Width: 84  |  Height: 84  |  Size: 3.5 KiB

69
src/assets/logo/winbox-logo.svg

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="108px" height="36px" viewBox="0 0 108 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 4备份 11</title>
<defs>
<filter color-interpolation-filters="auto" id="filter-1">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix>
</filter>
<filter color-interpolation-filters="auto" id="filter-2">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 0.991893 0 0 0 0 0.991893 0 0 0 1.000000 0"></feColorMatrix>
</filter>
<polygon id="path-3" points="0 14.4497041 63.705727 14.4497041 63.705727 0.229633136 0 0.229633136"></polygon>
<filter color-interpolation-filters="auto" id="filter-5">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix>
</filter>
<polygon id="path-6" points="3.53460972e-05 0.00438639456 24.4344881 0.00438639456 24.4344881 21.9319728 3.53460972e-05 21.9319728"></polygon>
<polygon id="path-8" points="0.16449642 0.00438639456 27.8315169 0.00438639456 27.8315169 21.8995701 0.16449642 21.8995701"></polygon>
</defs>
<g id="Layout-布局-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-415.000000, -6628.000000)" id="编组-4备份-11" filter="url(#filter-1)">
<g transform="translate(415.000000, 6628.000000)">
<g filter="url(#filter-2)" id="编组-6">
<g transform="translate(39.000000, 7.000000)">
<rect id="矩形" x="0" y="0" width="69" height="22"></rect>
<g id="编组" transform="translate(2.587500, 3.384615)">
<polygon id="Fill-1" fill="#000000" points="9.00482991 0.390571598 4.68467522 9.4302284 2.11492116 1.15562485 -7.978125e-05 1.16109231 4.17726647 14.6106426 9.00482991 4.73680828 13.8327923 14.6106426 18.0105374 1.16109231 15.931438 1.16109231 13.3564982 9.45287929"></polygon>
<polygon id="Fill-2" fill="#000000" points="20.4322574 3.69463314 18.9527141 2.24614793 20.4007438 0.828514793 21.880287 2.277"></polygon>
<polygon id="Fill-3" fill="#000000" points="19.4215486 14.3451586 21.4108941 14.3451586 21.4108941 4.78570296 19.4215486 4.78570296"></polygon>
<g transform="translate(0.000000, 0.161017)">
<path d="M32.0698685,14.1840249 L30.1347743,14.1840249 L30.1347743,9.33322012 C30.1347743,8.38930296 29.8878513,7.63401302 29.3936065,7.0673503 C28.8993616,6.50107811 28.2782646,6.21755148 27.5311132,6.21755148 C26.7835629,6.21755148 26.2051488,6.4362497 25.7954721,6.87247456 C25.3008284,7.39188284 25.0539054,8.20653373 25.0539054,9.31564615 L25.0539054,14.1840249 L23.0829096,14.1840249 L23.0829096,4.6241787 L25.0539054,4.6241787 L25.0539054,5.07446272 C25.0539054,5.2361432 25.0323645,5.39508994 24.9904793,5.55091243 C24.9477963,5.70673491 24.8791845,5.92894793 24.7822502,6.21755148 C25.0235885,5.72001302 25.4093309,5.30331479 25.9398762,4.9674568 C26.5426235,4.59723195 27.2175729,4.41211953 27.9651232,4.41211953 C29.0864487,4.41211953 30.038239,4.82530296 30.8220898,5.65088876 C31.6538093,6.52451006 32.0698685,7.66330296 32.0698685,9.06765799 L32.0698685,14.1840249 Z" id="Fill-4" fill="#000000"></path>
<path d="M51.9801172,9.27128166 C51.9801172,8.44764852 51.6889156,7.73883195 51.1061136,7.14444142 C50.5229126,6.55044142 49.7984989,6.25285562 48.9332712,6.25285562 C48.0800108,6.25285562 47.3619795,6.5441929 46.7791775,7.12686746 C46.1959765,7.70915148 45.904775,8.43593254 45.904775,9.30682012 C45.904775,10.1777077 46.1927853,10.9013645 46.7696037,11.4777905 C47.3468211,12.0546071 48.0676447,12.3428201 48.9332712,12.3428201 C49.7625973,12.3428201 50.4774373,12.0253172 51.078589,11.3895302 C51.6797408,10.7541337 51.9801172,10.0480509 51.9801172,9.27128166 M54.0053642,9.27128166 C54.0053642,10.6026071 53.4899773,11.7542876 52.4592036,12.7263231 C51.4284298,13.697968 50.2684104,14.1841811 48.9787465,14.1841811 C47.6041156,14.1841811 46.4225553,13.7245243 45.4344645,12.8056012 C44.3973083,11.8511396 43.879129,10.6850095 43.879129,9.30682012 C43.879129,7.95206272 44.3673903,6.79764852 45.3439128,5.84357751 C46.3204353,4.88911598 47.5139628,4.41188521 48.9244953,4.41188521 C50.2983284,4.41188521 51.4890636,4.89224024 52.495504,5.85216923 C53.5019445,6.81248876 54.0053642,7.95206272 54.0053642,9.27128166" id="Fill-6" fill="#000000"></path>
<polygon id="Fill-8" fill="#000000" points="63.7054079 14.1817207 61.054277 14.1828923 58.6325171 11.0629278 56.2103584 14.1840639 53.5588285 14.1828923 57.3193178 9.41839527 53.5588285 4.62304615 56.2103584 4.62187456 58.632916 7.77503432 61.054277 4.62304615 63.7054079 4.62421775 59.9465143 9.4187858"></polygon>
<path d="M38.2560269,7.06117988 C39.1240469,7.03696686 39.7758597,6.85341657 40.1951101,6.48865917 C40.5561203,6.16959408 40.7368248,5.73610296 40.7368248,5.18896686 C40.7368248,4.0829787 39.8987228,3.52998462 38.2229176,3.52998462 L35.5255136,3.52998462 L35.5255136,12.5161385 L38.3625348,12.5161385 C39.2812159,12.5161385 39.952974,12.354458 40.3778092,12.031097 C40.8026444,11.7077361 41.0148625,11.2172272 41.0148625,10.5587893 C41.0148625,9.90074201 40.7962619,9.42429231 40.3598584,9.1290497 C39.923455,8.83458817 39.2161942,8.64752308 38.2380761,8.64752308 L38.2560269,7.06117988 Z M42.8574105,10.4736533 C42.8574105,11.6198663 42.5099631,12.510671 41.8146695,13.1456769 C41.0607367,13.8380911 39.9430014,14.184103 38.459469,14.184103 L33.5784522,14.184103 L33.5784522,1.91317988 L38.3896605,1.91317988 L38.7199548,1.91317988 C39.9948592,1.91317988 40.9685894,2.19983077 41.6403475,2.77274201 C42.3129034,3.34565325 42.6491814,4.15405562 42.6491814,5.19794911 C42.6491814,5.71970059 42.5330997,6.20474201 42.3013351,6.65307337 C42.0695706,7.10140473 41.7448609,7.47280118 41.327605,7.76765325 C41.791134,8.06289586 42.1621169,8.44835148 42.4401545,8.92480118 C42.7185911,9.40164142 42.8574105,9.91792544 42.8574105,10.4736533 L42.8574105,10.4736533 Z" id="Fill-10" fill="#000000"></path>
<mask id="mask-4" fill="white">
<use xlink:href="#path-3"></use>
</mask>
<g id="Clip-13"></g>
<polygon id="Fill-12" fill="#000000" mask="url(#mask-4)" points="36.0475622 8.64521893 37.6575478 8.64521893 37.6575478 7.06863905 36.0475622 7.06863905"></polygon>
</g>
</g>
</g>
</g>
<g filter="url(#filter-5)" id="编组-2">
<g>
<rect id="矩形备份-3" x="0" y="0" width="36" height="36"></rect>
<g id="编组" transform="translate(0.857143, 6.857143)">
<g transform="translate(0.000000, 0.349426)">
<mask id="mask-7" fill="white">
<use xlink:href="#path-6"></use>
</mask>
<g id="Clip-2"></g>
<path d="M23.9639823,5.05970612 C23.4804477,4.73921633 22.8265449,4.87186939 22.5048954,5.3561415 L14.0826274,18.0745633 L3.85099264,2.11091701 L13.0434521,2.11091701 L16.1524948,6.54400544 C16.4868689,7.01908027 17.1439529,7.13475374 17.6193579,6.80082177 C18.094056,6.4668898 18.2089308,5.80893061 17.8756171,5.3331483 L14.1374138,0.00438639456 L3.53460972e-05,0.00438639456 L14.0536436,21.9321143 L24.2601826,6.51959728 C24.5800648,6.03567891 24.4471635,5.3809034 23.9639823,5.05970612" id="Fill-1" fill="#0040FF" mask="url(#mask-7)"></path>
</g>
<g transform="translate(6.362297, 0.349426)">
<mask id="mask-9" fill="white">
<use xlink:href="#path-8"></use>
</mask>
<g id="Clip-4"></g>
<path d="M13.5372371,0.00438639456 L7.69806186,8.63355646 L2.08298085,0.479814966 C1.75320177,0.00155646259 1.0971782,-0.119423129 0.618592047,0.210617687 C0.141773196,0.53995102 0.0208895434,1.19720272 0.349608247,1.67546122 L7.71326068,12.3680054 L14.6538203,2.11091701 L23.94843,2.11091701 L13.5361767,18.1071075 L11.4405066,15.0451211 C11.2814492,14.8130667 11.0414492,14.6567129 10.7653962,14.6047129 C10.4886362,14.5537741 10.2090486,14.6124952 9.97753166,14.7716789 C9.49859205,15.0995973 9.37594109,15.7564952 9.70359941,16.235815 L13.5800059,21.8995701 L27.8315523,0.00438639456 L13.5372371,0.00438639456 Z" id="Fill-3" fill="#0040FF" mask="url(#mask-9)"></path>
</g>
<polygon id="Fill-5" fill="#FF6E00" points="22.468489 7.79653333 21.793025 5.12896871 24.523511 4.43315918 25.198975 7.10072381"></polygon>
<polygon id="Fill-7" fill="#141491" points="14.1374845 0.353812245 13.0435228 2.46034286 15.6411075 6.16437007 16.9991046 4.43315918"></polygon>
<polygon id="Fill-9" fill="#141491" points="16.9603299 17.8919973 18.2151163 15.9970041 19.898651 18.4565687 18.6392695 20.3455483"></polygon>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

39
src/assets/logo/winbox.svg

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 2</title>
<defs>
<filter color-interpolation-filters="auto" id="filter-1">
<feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix>
</filter>
<polygon id="path-2" points="3.53460972e-05 0.00438639456 24.4344881 0.00438639456 24.4344881 21.9319728 3.53460972e-05 21.9319728"></polygon>
<polygon id="path-4" points="0.16449642 0.00438639456 27.8315169 0.00438639456 27.8315169 21.8995701 0.16449642 21.8995701"></polygon>
</defs>
<g id="Layout-布局-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-424.000000, -8490.000000)" id="编组-4备份-6">
<g transform="translate(424.000000, 8490.000000)" filter="url(#filter-1)" id="编组-2">
<g>
<rect id="矩形备份-3" x="0" y="0" width="36" height="36"></rect>
<g id="编组" transform="translate(0.857143, 6.857143)">
<g transform="translate(0.000000, 0.349426)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Clip-2"></g>
<path d="M23.9639823,5.05970612 C23.4804477,4.73921633 22.8265449,4.87186939 22.5048954,5.3561415 L14.0826274,18.0745633 L3.85099264,2.11091701 L13.0434521,2.11091701 L16.1524948,6.54400544 C16.4868689,7.01908027 17.1439529,7.13475374 17.6193579,6.80082177 C18.094056,6.4668898 18.2089308,5.80893061 17.8756171,5.3331483 L14.1374138,0.00438639456 L3.53460972e-05,0.00438639456 L14.0536436,21.9321143 L24.2601826,6.51959728 C24.5800648,6.03567891 24.4471635,5.3809034 23.9639823,5.05970612" id="Fill-1" fill="#0040FF" mask="url(#mask-3)"></path>
</g>
<g transform="translate(6.362297, 0.349426)">
<mask id="mask-5" fill="white">
<use xlink:href="#path-4"></use>
</mask>
<g id="Clip-4"></g>
<path d="M13.5372371,0.00438639456 L7.69806186,8.63355646 L2.08298085,0.479814966 C1.75320177,0.00155646259 1.0971782,-0.119423129 0.618592047,0.210617687 C0.141773196,0.53995102 0.0208895434,1.19720272 0.349608247,1.67546122 L7.71326068,12.3680054 L14.6538203,2.11091701 L23.94843,2.11091701 L13.5361767,18.1071075 L11.4405066,15.0451211 C11.2814492,14.8130667 11.0414492,14.6567129 10.7653962,14.6047129 C10.4886362,14.5537741 10.2090486,14.6124952 9.97753166,14.7716789 C9.49859205,15.0995973 9.37594109,15.7564952 9.70359941,16.235815 L13.5800059,21.8995701 L27.8315523,0.00438639456 L13.5372371,0.00438639456 Z" id="Fill-3" fill="#0040FF" mask="url(#mask-5)"></path>
</g>
<polygon id="Fill-5" fill="#FF6E00" points="22.468489 7.79653333 21.793025 5.12896871 24.523511 4.43315918 25.198975 7.10072381"></polygon>
<polygon id="Fill-7" fill="#141491" points="14.1374845 0.353812245 13.0435228 2.46034286 15.6411075 6.16437007 16.9991046 4.43315918"></polygon>
<polygon id="Fill-9" fill="#141491" points="16.9603299 17.8919973 18.2151163 15.9970041 19.898651 18.4565687 18.6392695 20.3455483"></polygon>
</g>
</g>
</g>
</g>
</g>
</svg>

BIN
src/assets/setting-drawer/black.png

After

Width: 96  |  Height: 50  |  Size: 4.4 KiB

BIN
src/assets/setting-drawer/blue.png

After

Width: 96  |  Height: 50  |  Size: 6.5 KiB

BIN
src/assets/setting-drawer/cyan.png

After

Width: 96  |  Height: 50  |  Size: 2.3 KiB

BIN
src/assets/setting-drawer/default.png

After

Width: 96  |  Height: 50  |  Size: 6.0 KiB

BIN
src/assets/setting-drawer/horizontal.png

After

Width: 96  |  Height: 50  |  Size: 287 B

BIN
src/assets/setting-drawer/red.png

After

Width: 96  |  Height: 50  |  Size: 4.4 KiB

BIN
src/assets/setting-drawer/vertical.png

After

Width: 96  |  Height: 50  |  Size: 297 B

BIN
src/assets/setting-drawer/white.png

After

Width: 96  |  Height: 50  |  Size: 3.1 KiB

50
src/components/Breadcrumb/index.vue

@ -0,0 +1,50 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index === levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
import pathToRegexp from 'path-to-regexp';
export default {
data() {
return {
levelList: null
};
},
created() {
this.getBreadcrumb();
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
let matched = this.$route.matched.filter((item) => item.meta && item.meta.title);
const routeBefore = localStorage.getItem('routerBefore') ? JSON.parse(localStorage.getItem('routerBefore')) : { name: '素材库', path: '/shareMaterial' };
// const first = matched[0]
matched = [{ path: routeBefore.path, meta: { title: routeBefore.name } }].concat(matched);
this.levelList = matched.filter((item) => item.meta && item.meta.title);
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route;
const toPath = pathToRegexp.compile(path);
return toPath(params);
},
handleLink(item) {
const { redirect, path } = item;
if (redirect) {
this.$router.push(redirect);
return;
}
this.$router.push(this.pathCompile(path));
}
}
};
</script>

30
src/components/Hamburger/index.vue

@ -0,0 +1,30 @@
<template>
<div class="hamburger" @click="toggleClick">
<i :class="sidebar.opened ? 'w-icon-menu-fold' : 'w-icon-menu-unfold'" />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'Hamburger',
computed: {
...mapGetters(['sidebar'])
},
methods: {
toggleClick() {
this.$store.dispatch('app/toggleSideBar');
}
}
};
</script>
<style lang="less" scoped>
.hamburger {
i {
font-size: 18px;
cursor: pointer;
color: @layoutHeaderColor;
}
}
</style>

62
src/components/SvgIcon/index.vue

@ -0,0 +1,62 @@
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate';
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass);
},
iconName() {
return `#icon-${this.iconClass}`;
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className;
} else {
return 'svg-icon';
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
};
}
}
};
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>

145
src/components/settingDrawer/index.vue

@ -0,0 +1,145 @@
<template>
<w-drawer title="主题设置" :modal="false" size="308px" :show-close="false" :visible.sync="settingDrawer" :direction="direction">
<div class="setting-container">
<div class="layout-mode">
<w-divider>布局</w-divider>
<w-row>
<w-col :span="12" :class="layoutMode === 'horizontal' ? 'activeClass' : ''" @click.native="handleLayout('horizontal')">
<img src="~@/assets/setting-drawer/horizontal.png" alt="" />
<div>横向布局(默认)</div>
</w-col>
<w-col :span="12" :class="layoutMode === 'vertical' ? 'activeClass' : ''" @click.native="handleLayout('vertical')">
<img src="~@/assets/setting-drawer/vertical.png" alt="" />
<div>纵向布局</div>
</w-col>
</w-row>
</div>
<div class="theme-mode">
<w-divider>主题</w-divider>
<w-row>
<w-col
v-for="(item, index) in themeList"
:key="index"
:span="12"
:class="theme === item.theme ? 'activeClass' : ''"
@click.native="handleTheme(item)"
>
<img :src="item.url" :alt="item.theme" />
<div>{{ item.label }}</div>
</w-col>
</w-row>
</div>
</div>
</w-drawer>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'SettingDrawer',
data() {
return {
direction: 'rtl',
themeList: [
//
{
color: '#2055f4',
theme: 'default',
label: '海洋之心(默认)',
url: require('@/assets/setting-drawer/default.png')
},
{
color: '#169688',
theme: 'cyan',
label: '远黛青山',
url: require('@/assets/setting-drawer/cyan.png')
},
{
color: '#ed6363',
theme: 'red',
label: '朝阳如炬',
url: require('@/assets/setting-drawer/red.png')
},
{
color: '#207DFC',
theme: 'blue',
label: '智慧之心',
url: require('@/assets/setting-drawer/blue.png')
},
{
color: '#3252a5',
theme: 'white',
label: '素星白露',
url: require('@/assets/setting-drawer/white.png')
},
{
color: '#F7AB00',
theme: 'black',
label: '墨色轩辕',
url: require('@/assets/setting-drawer/black.png')
}
]
};
},
computed: {
...mapGetters(['theme', 'layoutMode']),
settingDrawer: {
get() {
return this.$store.getters.settingDrawer;
},
set(value) {
this.$store.dispatch('app/toggleVisible', value);
}
}
},
methods: {
//
handleLayout(layoutMode) {
this.$store.dispatch('app/toggleLayoutMode', layoutMode);
},
//
handleTheme(item) {
this.$store.dispatch('app/toggleTheme', item.theme);
}
}
};
</script>
<style lang="less" scoped>
.setting-container {
padding: 0px 33px;
.w-divider--horizontal {
margin: 36px 0 24px 0;
.w-divider__text {
padding: 0 2px;
}
}
.theme-mode {
margin-top: 48px;
}
.w-row {
.w-col {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
cursor: pointer;
padding: 8px;
border: 1px solid #e85a50;
border-color: transparent;
img {
width: 96px;
height: 50px;
}
div {
margin-top: 12px;
}
}
}
.activeClass {
border-color: @themeColor!important;
background-color: @themeTransparent;
}
}
</style>

9
src/icons/index.js

@ -0,0 +1,9 @@
import Vue from 'vue';
import SvgIcon from '@/components/SvgIcon';// svg component
// register globally
Vue.component('SvgIcon', SvgIcon);
const req = require.context('./svg', false, /\.svg$/);
const requireAll = requireContext => requireContext.keys().map(requireContext);
requireAll(req);

21
src/icons/svgo.yml

@ -0,0 +1,21 @@
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'

50
src/layout/components/domain.vue

@ -0,0 +1,50 @@
<!--
* @Description: 功能区
* @Date: 2021-11-25 11:17:33
-->
<template>
<div class="domain-wrapper">
<w-tooltip content="主题设置" placement="top" :enterable="false">
<i class="w-icon-codepen" @click="handleSetting"></i>
</w-tooltip>
<w-avatar icon="w-icon-user-login" size="medium"></w-avatar>
</div>
</template>
<script>
export default {
name: 'DoMain',
methods: {
handleSetting() {
this.$store.dispatch('app/toggleVisible', true);
}
}
};
</script>
<style lang="less" scoped>
.domain-wrapper {
height: 100%;
display: flex;
align-items: center;
color: @layoutHeaderColor;
i {
font-size: 18px;
cursor: pointer;
margin-right: 16px;
&:hover {
color: @themeColor;
}
}
}
.layout-horizontal {
.domain-wrapper {
i {
color: @layoutColor;
}
}
}
</style>

31
src/layout/components/horizontalMenu.vue

@ -0,0 +1,31 @@
<template>
<div class="horizontal-menu">
<w-menu ref="wMenu" :default-active="activeMenu" mode="horizontal">
<sidebar-item v-for="(route, index) in asyncRoutes" :key="index" :item="route" :base-path="route.path" :active-menu="activeMenu" />
</w-menu>
</div>
</template>
<script>
import SidebarItem from './sidebarItem';
import { asyncRoutes } from '@/router';
export default {
name: 'HorizontalMenu',
components: { SidebarItem },
data() {
return {
asyncRoutes
};
},
computed: {
activeMenu() {
const route = this.$route;
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) return meta.activeMenu;
return path;
}
}
};
</script>

36
src/layout/components/item.vue

@ -0,0 +1,36 @@
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
hoverIcon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
hover: {
type: Boolean,
default: false
}
},
render(h, context) {
const { icon, title, hover, hoverIcon } = context.props;
const vnodes = [];
if (icon) vnodes.push(<i class={[hover && hoverIcon ? hoverIcon : icon, 'iconfont']} />);
if (title)
vnodes.push(
<span slot="title" class="submenu-title">
{title}
</span>
);
return vnodes;
}
};
</script>

43
src/layout/components/link.vue

@ -0,0 +1,43 @@
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate';
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to);
},
type() {
if (this.isExternal) {
return 'a';
}
return 'router-link';
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
};
}
return {
to: to
};
}
}
};
</script>

52
src/layout/components/logo.vue

@ -0,0 +1,52 @@
<template>
<transition name="sidebarLogoFade">
<router-link v-if="isCollapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo" />
<span class="sidebar-title">{{ defaultSettings.title }}</span>
</router-link>
</transition>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: 'SidebarLogo',
data() {
return {
logo: require('@/assets/logo/winbox.svg'),
defaultSettings: require('@/settings.js')
};
},
computed: {
...mapGetters(['sidebar', 'theme', 'layoutMode']),
isCollapse() {
return !this.sidebar.opened && this.layoutMode !== 'horizontal';
}
}
};
</script>
<style lang="less" scoped>
.sidebarLogoFade-enter-active {
transition: opacity 1.5s;
}
.sidebarLogoFade-enter,
.sidebarLogoFade-leave-to {
opacity: 0;
}
.sidebar-logo-link {
display: flex;
align-items: center;
.sidebar-title {
color: @layoutColor;
font-size: 24px;
margin-left: 5px;
}
}
</style>

126
src/layout/components/sidebarItem.vue

@ -0,0 +1,126 @@
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<w-menu-item
:index="resolvePath(onlyOneChild.path)"
:class="{ 'submenu-title-noDropdown': !isNest }"
@mouseenter.native="hover = true"
@mouseleave.native="hover = false"
>
<item
:icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
:hover-icon="onlyOneChild.meta.hoverIcon || (item.meta && item.meta.hoverIcon)"
:title="onlyOneChild.meta.title"
:hover="activeMenu === resolvePath(onlyOneChild.path) ? true : hover"
/>
</w-menu-item>
</app-link>
</template>
<w-submenu
v-else
ref="subMenu"
:index="resolvePath(item.path)"
:popper-append-to-body="true"
popper-class="layout-horizontal-popper"
@mouseenter.native="hover = true"
@mouseleave.native="hover = false"
>
<template v-slot:title>
<item
v-if="item.meta"
:icon="item.meta && item.meta.icon"
:hover-icon="item.meta && item.meta.hoverIcon"
:title="item.meta.title"
:hover="resolveActiveRoot(item.children) ? true : hover"
/>
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:active-menu="activeMenu"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</w-submenu>
</div>
</template>
<script>
import path from 'path';
import { isExternal } from '@/utils/validate';
import Item from './item';
import AppLink from './link';
export default {
name: 'SidebarItem',
components: { Item, AppLink },
props: {
// route object
item: {
type: Object,
required: true
},
isNest: {
type: Boolean,
default: false
},
basePath: {
type: String,
default: ''
},
activeMenu: {
type: String,
default: ''
}
},
data() {
// To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
// TODO: refactor with render function
this.onlyOneChild = null;
return {
hover: false
};
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter((item) => {
if (item.hidden) {
return false;
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item;
return true;
}
});
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) return true;
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true };
return true;
}
return false;
},
//
resolveActiveRoot(children) {
let flag = false;
children.forEach((item) => {
if (this.activeMenu.includes(item.path)) flag = true;
});
return flag;
},
resolvePath(routePath) {
if (isExternal(routePath)) return routePath;
if (isExternal(this.basePath)) return this.basePath;
return path.resolve(this.basePath, routePath);
}
}
};
</script>

36
src/layout/components/vertivalMenu.vue

@ -0,0 +1,36 @@
<template>
<w-scrollbar wrap-class="scrollbar-wrapper">
<w-menu :default-active="activeMenu" :collapse="isCollapse" :unique-opened="false" :collapse-transition="false" mode="vertical">
<sidebar-item v-for="(route, index) in asyncRoutes" :key="index" :item="route" :base-path="route.path" :active-menu="activeMenu" />
</w-menu>
</w-scrollbar>
</template>
<script>
import { mapGetters } from 'vuex';
import SidebarItem from './sidebarItem';
import { asyncRoutes } from '@/router';
export default {
name: 'VerticalMenu',
components: { SidebarItem },
data() {
return {
asyncRoutes
};
},
computed: {
...mapGetters(['sidebar']),
activeMenu() {
const route = this.$route;
const { meta, path } = route;
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) return meta.activeMenu;
return path;
},
isCollapse() {
return !this.sidebar.opened;
}
}
};
</script>

40
src/layout/index.vue

@ -0,0 +1,40 @@
<template>
<div class="app-wrapper" :class="classObj">
<layout-horizontal v-if="layoutMode === 'horizontal'" />
<layout-vertical v-else />
<setting-drawer ref="settingDrawer" />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import LayoutHorizontal from './layout/horizontal.vue';
import LayoutVertical from './layout/vertical.vue';
import SettingDrawer from '@/components/settingDrawer';
export default {
name: 'LayoutIndex',
components: { LayoutHorizontal, LayoutVertical, SettingDrawer },
computed: {
...mapGetters(['layoutMode']),
sidebar() {
return this.$store.state.app.sidebar;
},
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened
};
}
}
};
</script>
<style lang="less" scoped>
@import '~@/styles/common/mixin.less';
.app-wrapper {
.relative();
.clearfix();
}
</style>

18
src/layout/layout/appMain.vue

@ -0,0 +1,18 @@
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<router-view :key="key" />
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain',
computed: {
key() {
return this.$route.path;
}
}
};
</script>

32
src/layout/layout/horizontal.vue

@ -0,0 +1,32 @@
<!--
* @Description:横向布局
* @Date: 2021-11-24 11:00:24
-->
<template>
<w-container class="layout-horizontal">
<w-header>
<div class="header-wrapper">
<logo />
<horizontal-menu />
</div>
<do-main />
</w-header>
<w-main>
<app-main />
</w-main>
</w-container>
</template>
<script>
import AppMain from './appMain';
import Logo from '../components/logo';
import horizontalMenu from '../components/horizontalMenu';
import DoMain from '../components/domain.vue';
export default {
name: 'LayoutHorizontal',
components: { AppMain, Logo, horizontalMenu, DoMain }
};
</script>
<style></style>

45
src/layout/layout/vertical.vue

@ -0,0 +1,45 @@
<!--
* @Description: 纵向布局
* @Date: 2021-11-24 11:01:43
-->
<template>
<w-container class="layout-vertical">
<w-aside :width="isCollapse ? '88px' : '200px'">
<w-header>
<logo />
</w-header>
<vertical-menu />
</w-aside>
<w-container>
<w-header>
<hamburger />
<do-main />
</w-header>
<w-main>
<app-main />
</w-main>
</w-container>
</w-container>
</template>
<script>
import { mapGetters } from 'vuex';
import AppMain from './appMain';
import Logo from '../components/logo';
import VerticalMenu from '../components/vertivalMenu';
import DoMain from '../components/domain.vue';
import Hamburger from '@/components/Hamburger';
export default {
name: 'LayoutVertical',
components: { AppMain, Logo, VerticalMenu, DoMain, Hamburger },
computed: {
...mapGetters(['sidebar']),
isCollapse() {
return !this.sidebar.opened;
}
}
};
</script>
<style></style>

16
src/main.js

@ -0,0 +1,16 @@
import Vue from 'vue';
import App from './App';
import store from './store';
import router from './router';
import './plugins';
Vue.config.productionTip = false;
const app = new Vue({
el: '#app',
router,
store,
render: (h) => h(App)
});
Vue.use(app);

22
src/permission.js

@ -0,0 +1,22 @@
import router from './router';
import NProgress from 'nprogress'; // progress bar
import 'nprogress/nprogress.css'; // progress bar style
// import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }); // NProgress Configuration
// const whiteList = ['/login'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start();
next();
// set page title
// document.title = getPageTitle(to.meta.title)
});
router.afterEach(() => {
// finish progress bar
NProgress.done();
});

2
src/plugins/component.js

@ -0,0 +1,2 @@
// import Vue from 'vue';

25
src/plugins/index.js

@ -0,0 +1,25 @@
// 公共引入
import Vue from 'vue';
// winbox-ui
import './winbox-ui';
// global prototype
import './prototype';
// global components
import './component';
// A modern alternative to CSS resets
import 'normalize.css/normalize.css';
// global css
import '@/styles/global.less';
// permission control
import '@/permission';
// bus
import VueBus from 'vue-bus';
// 全局过滤器
import * as custom from '@/utils/filters';
// 导出的是对象,可以直接通过 key 和 value 来获得过滤器的名和过滤器的方法
Object.keys(custom).forEach(key => Vue.filter(key, custom[key]));
Vue.use(VueBus);

10
src/plugins/prototype.js

@ -0,0 +1,10 @@
import Vue from 'vue';
// common
import common from '@/utils/common';
Vue.prototype.$common = common;
// 时间格式转换
import moment from 'moment/moment';
Vue.prototype.$moment = moment;

8
src/plugins/winbox-ui.js

@ -0,0 +1,8 @@
import Vue from 'vue';
import WinboxUI from 'winbox-ui';
// global theme
import '@/styles/index.less';
// import 'winbox-ui/themes/index.css';
Vue.use(WinboxUI);

125
src/router/index.js

@ -0,0 +1,125 @@
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
/* Layout */
import Layout from '@/layout';
/**
* constantRoutes
* a base page that does not have permission requirements
* all roles can be accessed
*/
export const constantRoutes = [
// 404 page must be placed at the end !!!
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
}
];
export const asyncRoutes = [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: 'home',
component: () => import('@/views/home'),
meta: { title: '首页', icon: 'w-icon-home' }
}
]
},
{
path: '/i18n',
component: Layout,
redirect: '/i18n',
children: [
{
path: 'i18n',
component: () => import('@/views/i18n'),
meta: { title: '国际化', icon: 'w-icon-linkedin' }
}
]
},
{
path: '/demo',
component: Layout,
meta: { title: '组件事例', icon: 'w-icon-codepen', hoverIcon: 'w-icon-stackblitz' },
children: [
{
path: 'form',
name: 'Fomr',
component: () => import('@/views/demo/form'),
meta: { title: '表单' }
},
{
path: 'table',
name: 'Table',
component: () => import('@/views/demo/table'),
meta: { title: '表格' }
}
]
},
{
path: '/routeNesting',
component: Layout,
meta: { title: '路由嵌套', icon: 'w-icon-codepen' },
children: [
{
path: 'primaryRouting',
name: 'PrimaryRouting',
component: () => import('@/views/routeNesting/primaryRouting'),
meta: { title: '一级路由' }
},
{
path: 'secondaryRouting',
name: 'SecondaryRouting',
component: () => import('@/views/routeNesting/secondaryRouting'),
meta: { title: '二级路由' },
children: [
{
path: 'thirdRouting',
name: 'thirdRouting',
component: () => import('@/views/routeNesting/secondaryRouting/thirdRouting'),
meta: { title: '三级路由-1' }
},
{
path: 'thirdRouting1',
name: 'thirdRouting1',
component: () => import('@/views/routeNesting/secondaryRouting/thirdRouting1'),
meta: { title: '三级路由-2' }
}
]
}
]
},
{
path: '/external-link',
component: Layout,
children: [
{
path: 'http://winbox.wengetech.com/',
meta: { title: '外链路由', icon: 'w-icon-document' }
}
]
}
];
const createRouter = () => new Router({
mode: 'hash', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes.concat(asyncRoutes)
});
const router = createRouter();
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter();
router.matcher = newRouter.matcher; // reset router
}
export default router;

20
src/settings.js

@ -0,0 +1,20 @@
module.exports = {
/**
* @type {string}
* @description Web title
*/
title: 'Winbox',
/**
* @type {boolean} true | false
* @description Whether fix the header
*/
fixedHeader: true,
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: true
};

13
src/store/getters.js

@ -0,0 +1,13 @@
const getters = {
// app
sidebar: state => state.app.sidebar,
layoutMode: state => state.app.layoutMode,
theme: state => state.app.theme,
themeColor: state => state.app.themeColor,
settingDrawer: state => state.app.settingDrawer,
// user
userData: state => state.user.userData,
token: state => state.user.token,
permission_routes: state => state.permission.routes
};
export default getters;

25
src/store/index.js

@ -0,0 +1,25 @@
import Vue from 'vue';
import Vuex from 'vuex';
import getters from './getters';
Vue.use(Vuex);
// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/);
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1');
const value = modulesFiles(modulePath);
modules[moduleName] = value.default;
return modules;
}, {});
const store = new Vuex.Store({
modules,
getters
});
export default store;

58
src/store/modules/app.js

@ -0,0 +1,58 @@
import Cookies from 'js-cookie';
import common from '@/utils/common';
const state = {
sidebar: {
opened: Cookies.get('sidebarStatus') ? !!Number(Cookies.get('sidebarStatus')) : false,
withoutAnimation: false
},
layoutMode: localStorage.getItem('layoutMode') || 'horizontal', // 当前布局
theme: localStorage.getItem('theme') || 'default', // 当前主题
settingDrawer: false // 主题抽屉显隐
};
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened;
state.sidebar.withoutAnimation = false;
if (state.sidebar.opened) {
Cookies.set('sidebarStatus', 1);
} else {
Cookies.set('sidebarStatus', 0);
}
},
TOGGLE_LAYOUTMODE: (state, layoutMode) => {
state.layoutMode = layoutMode;
localStorage.setItem('layoutMode', layoutMode);
},
TOGGLE_THEME: (state, theme) => {
localStorage.setItem('theme', theme);
state.theme = theme;
common.toggleClass(document.body, theme);
},
TOGGLE_VISIBLE: (state, visible) => {
state.settingDrawer = visible;
}
};
const actions = {
toggleLayoutMode({ commit }, layoutMode) {
commit('TOGGLE_LAYOUTMODE', layoutMode);
},
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR');
},
toggleTheme({ commit }, theme) {
commit('TOGGLE_THEME', theme);
},
toggleVisible({ commit }, visible) {
commit('TOGGLE_VISIBLE', visible);
}
};
export default {
namespaced: true,
state,
mutations,
actions
};

32
src/store/modules/settings.js

@ -0,0 +1,32 @@
import defaultSettings from '@/settings';
const { showSettings, fixedHeader, sidebarLogo } = defaultSettings;
const state = {
showSettings: showSettings,
fixedHeader: fixedHeader,
sidebarLogo: sidebarLogo
};
const mutations = {
CHANGE_SETTING: (state, { key, value }) => {
// eslint-disable-next-line no-prototype-builtins
if (state.hasOwnProperty(key)) {
state[key] = value;
}
}
};
const actions = {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data);
}
};
export default {
namespaced: true,
state,
mutations,
actions
};

40
src/store/modules/user.js

@ -0,0 +1,40 @@
import { getToken, removeToken } from '@/utils/auth';
const getDefaultState = () => {
return {
token: getToken(),
userData: ''
};
};
const state = getDefaultState();
const mutations = {
RESET_STATE: (state) => {
Object.assign(state, getDefaultState());
},
SET_USER: (state, data) => {
state.userData = data;
},
SET_TOKEN: (state, token) => {
state.token = token;
}
};
const actions = {
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
removeToken(); // must remove token first
commit('RESET_STATE');
resolve();
});
}
};
export default {
namespaced: true,
state,
mutations,
actions
};

19
src/styles/common/fonts.less

@ -0,0 +1,19 @@
/* Fonts size
-------------------------- */
@font12: 12px;
@font13: 13px;
@font14: 14px;
@font15: 15px;
@font16: 16px;
@font17: 17px;
@font18: 18px;
@font19: 19px;
@font20: 20px;
@font22: 22px;
@font24: 24px;
@font26: 26px;
@font28: 28px;
@font30: 30px;
@font32: 32px;
@font36: 36px;

31
src/styles/common/mixin.less

@ -0,0 +1,31 @@
.clearfix() {
&::after {
content: '';
display: table;
clear: both;
}
}
// 定位
.relative() {
position: relative;
width: 100%;
height: 100%;
}
// 英文、数字需要换行
.wordWrap() {
word-wrap: break-word;
white-space: normal;
word-break: break-all;
}
// 溢出出省略号
.overflowHidden(@number) {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: @number;
overflow: hidden;
}
// 导航收缩动画
.animateSiderbar(@direction) {
transition: @direction 0.28s;
}

71
src/styles/global.less

@ -0,0 +1,71 @@
@import url(./layout/index.less);
body {
height: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-size: 14px;
font-weight: 400;
font-family: Arial, '微软雅黑', Microsoft YaHei;
}
label {
font-weight: 500;
color: #fff;
}
html {
height: 100%;
box-sizing: border-box;
}
ul,
li {
list-style-type: none;
margin: 0;
padding: 0;
}
#app {
height: 100%;
}
*,
*::before,
*::after {
box-sizing: inherit;
outline: none;
}
a:focus,
a:active {
outline: none;
}
a,
a:focus,
a:hover {
cursor: pointer;
color: inherit;
text-decoration: none;
}
div:focus {
outline: none;
}
.clearfix {
&::after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
}
#nprogress .bar {
background: @themeColor !important;
}

9
src/styles/index.less

@ -0,0 +1,9 @@
/* All theme entry files
-------------------------- */
@import url('./themes/default.less');
@import url('./themes/cyan.less');
@import url('./themes/red.less');
@import url('./themes/blue.less');
@import url('./themes/white.less');
@import url('./themes/black.less');

128
src/styles/layout/horizontal.less

@ -0,0 +1,128 @@
.layout-horizontal-popper {
.w-menu--popup {
background-color: @layoutBgStart;
.w-menu-item {
//一级
color: @layoutColor;
}
.nest-menu {
//二级
.w-menu-item:not(.is-active):hover {
background-color: @layoutHover;
color: @layoutActiveColor;
}
// 三级
.w-submenu {
.w-submenu__title {
color: @layoutColor;
i {
color: @layoutColor;
}
&:hover {
background-color: @layoutHover;
color: @layoutActiveColor;
}
}
}
}
}
}
.layout-horizontal {
height: 100%;
.w-header {
background: linear-gradient(90deg, @layoutBgStart, @layoutBgEnd);
display: flex;
justify-content: space-between;
.header-wrapper {
display: flex;
align-items: center;
.horizontal-menu {
height: 100%;
.w-menu--horizontal {
height: 100%;
display: flex;
border-bottom: 0;
margin-left: 80px;
background-color: transparent;
box-shadow: none;
.w-submenu__icon-arrow {
position: static;
margin: 0 0 0 4px;
}
.w-submenu__title,
.submenu-title-noDropdown {
height: 100%;
display: flex;
align-items: center;
color: @layoutColor;
i {
color: @layoutColor;
}
&:focus,
&:hover,
&:hover i {
background-color: transparent;
color: @layoutColor;
}
&.is-active {
color: @layoutActiveColor;
i {
color: @layoutActiveColor;
}
}
}
.w-submenu {
height: 100%;
&.is-active {
background-color: @themeColor;
color: @layoutActiveColor;
.w-submenu__title {
color: @layoutActiveColor;
i {
color: @layoutActiveColor;
}
}
}
}
.router-link-active {
height: 100%;
background-color: @themeColor;
color: @layoutColor;
display: flex;
align-items: center;
&:hover .w-menu-item {
color: @layoutActiveColor;
}
}
}
}
}
}
.w-main {
background: @main-bg;
padding: 20px;
}
}

2
src/styles/layout/index.less

@ -0,0 +1,2 @@
@import url('./horizontal.less');
@import url('./vertical.less');

135
src/styles/layout/vertical.less

@ -0,0 +1,135 @@
@import url('../common/mixin.less');
.hideSidebar {
.w-menu--collapse {
.w-submenu {
& > .w-submenu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
& > .w-submenu__icon-arrow {
display: none;
}
}
}
}
}
.layout-vertical {
height: 100%;
.w-aside {
background: linear-gradient(180deg, @layoutBgStart, @layoutBgEnd);
display: flex;
.animateSiderbar(width);
flex-direction: column;
overflow: hidden;
.w-header {
display: flex;
align-items: center;
}
.w-scrollbar {
flex: 1;
margin-bottom: 24px;
.w-menu {
box-shadow: none;
background-color: transparent;
// 一级
.submenu-title-noDropdown {
color: @layoutColor;
i {
color: @layoutColor;
}
&.is-active {
color: @themeColor;
i {
color: @themeColor;
}
}
&:hover,
&:focus {
background: @layoutHover;
color: @layoutHoverColor;
i {
color: @layoutHoverColor;
}
}
}
// 二级
.w-submenu {
.w-submenu__title {
color: @layoutColor;
i {
color: @layoutColor;
}
&:hover,
&:focus {
background: @layoutHover;
color: @layoutHoverColor;
i {
color: @layoutHoverColor;
}
}
}
.w-menu-item {
color: @layoutColor;
&:hover,
&:focus {
background: @layoutHover;
color: @layoutHoverColor;
i {
color: @layoutHoverColor;
}
}
}
&.is-active {
.w-menu-item.is-active {
background: @themeColor;
color: @layoutActiveColor;
}
}
}
}
}
}
.w-container {
.w-header {
display: flex;
align-items: center;
justify-content: space-between;
background: @layoutHeader;
}
.w-main {
background: @main-bg;
padding: 20px;
display: flex;
flex-direction: column;
.app-main {
flex: 1;
}
}
}
}

29
src/styles/themes/black.less

@ -0,0 +1,29 @@
/* Theme: Black
-------------------------- */
@blackList: {
--themeColor: #f7ab00;
--themeTransparent: fade(#f7ab00, 30%);
// layout|color
--layoutHeader: #101840;
--layoutHeaderColor: #fff;
--layoutBgStart: #101840;
--layoutBgEnd: #101840;
--layoutHover: mix(#f7ab00, #fff, 80%);
--layoutColor: #fff;
//main
--mainBg: #eff2f5;
--colorWhite: #fff;
};
each(@blackList, {
.black {
@{key}: @value;
}
:export {
@{key}: @value;
}
});

29
src/styles/themes/blue.less

@ -0,0 +1,29 @@
/* Theme: Blue
-------------------------- */
@blueList: {
--themeColor: #207dfc;
--themeTransparent: fade(#207dfc, 30%);
// layout|color
--layoutHeader: #fff;
--layoutHeaderColor: #272a31;
--layoutBgStart: #1c56f4;
--layoutBgEnd: #3a9afd;
--layoutHover: mix(#1c56f4, #fff, 90%);
--layoutColor: #fff;
//main
--mainBg: #eff2f5;
--colorWhite: #fff;
};
each(@blueList, {
.blue {
@{key}: @value;
}
:export {
@{key}: @value;
}
});

29
src/styles/themes/cyan.less

@ -0,0 +1,29 @@
/* Theme: Cyan
-------------------------- */
@cyanList: {
--themeColor: #169688;
--themeTransparent: fade(#169688, 30%);
// layout|color
--layoutHeader: #fff;
--layoutHeaderColor: #272a31;
--layoutBgStart: #101840;
--layoutBgEnd: #101840;
--layoutHover: mix(#101840, #fff, 90%);
--layoutColor: #fff;
//main
--mainBg: #eff2f5;
--colorWhite: #fff;
};
each(@cyanList, {
.cyan {
@{key}: @value;
}
:export {
@{key}: @value;
}
});

30
src/styles/themes/default.less

@ -0,0 +1,30 @@
/* Theme: Default
-------------------------- */
@defaultList: {
// theme
--themeColor: #2055f4;
--themeTransparent: fade(#2055f4, 30%);
// layout|color
--layoutHeader: #fff;
--layoutHeaderColor: #272a31;
--layoutBgStart: #101840;
--layoutBgEnd: #101840;
--layoutHover: mix(#101840, #fff, 90%);
--layoutColor: #fff;
//main
--mainBg: #eff2f5;
--colorWhite: #fff;
};
each(@defaultList, {
.default {
@{key}: @value;
}
:export {
@{key}: @value;
}
});

29
src/styles/themes/red.less

@ -0,0 +1,29 @@
/* Theme: Red
-------------------------- */
@redList: {
--themeColor: #ed6363;
--themeTransparent: fade(#ed6363, 30%);
// layout|color
--layoutHeader: #fff;
--layoutHeaderColor: #272a31;
--layoutBgStart: #101840;
--layoutBgEnd: #101840;
--layoutHover: mix(#101840, #fff, 90%);
--layoutColor: #fff;
//main
--mainBg: #eff2f5;
--colorWhite: #fff;
};
each(@redList, {
.red {
@{key}: @value;
}
:export {
@{key}: @value;
}
});

29
src/styles/themes/white.less

@ -0,0 +1,29 @@
/* Theme: White
-------------------------- */
@whiteList: {
--themeColor: #3252a5;
--themeTransparent: fade(#3252a5, 30%);
// layout|color
--layoutHeader: #fff;
--layoutHeaderColor: #272a31;
--layoutBgStart: #fff;
--layoutBgEnd: #fff;
--layoutHover: mix(#3252a5, #fff, 80%);
--layoutColor: #272a31;
//main
--mainBg: #eff2f5;
--colorWhite: #fff;
};
each(@whiteList, {
.white {
@{key}: @value;
}
:export {
@{key}: @value;
}
});

16
src/styles/variable.less

@ -0,0 +1,16 @@
/* Subject variable
-------------------------- */
@themeColor: var(--themeColor);
@themeTransparent: var(--themeTransparent);
@layoutBgStart: var(--layoutBgStart);
@layoutBgEnd: var(--layoutBgEnd);
@layoutColor: var(--layoutColor);
@layoutHoverColor: var(--colorWhite);
@layoutActiveColor: var(--colorWhite);
@layoutHover: var(--layoutHover);
@layoutHeader: var(--layoutHeader);
@layoutHeaderColor: var(--layoutHeaderColor);
@main-bg: var(--mainBg);

1
src/styles/winbox-ui/css/black.css
File diff suppressed because it is too large
View File

1
src/styles/winbox-ui/css/blue.css
File diff suppressed because it is too large
View File

1
src/styles/winbox-ui/css/cyan.css
File diff suppressed because it is too large
View File

1
src/styles/winbox-ui/css/default.css
File diff suppressed because it is too large
View File

BIN
src/styles/winbox-ui/css/iconfont.ttf

BIN
src/styles/winbox-ui/css/iconfont.woff

BIN
src/styles/winbox-ui/css/iconfont.woff2

1
src/styles/winbox-ui/css/red.css
File diff suppressed because it is too large
View File

1
src/styles/winbox-ui/css/white.css
File diff suppressed because it is too large
View File

6
src/styles/winbox-ui/less/black.less

@ -0,0 +1,6 @@
/* Theme: Black
-------------------------- */
@import url('winbox-ui/themes/index.less');
@primary-color: #f7ab00;

6
src/styles/winbox-ui/less/blue.less

@ -0,0 +1,6 @@
/* Theme: Blue
-------------------------- */
@import url('winbox-ui/themes/index.less');
@primary-color: #207dfc;

6
src/styles/winbox-ui/less/cyan.less

@ -0,0 +1,6 @@
/* Theme: Cyan
-------------------------- */
@import url('winbox-ui/themes/index.less');
@primary-color: #169688;

6
src/styles/winbox-ui/less/default.less

@ -0,0 +1,6 @@
/* Theme: Default
-------------------------- */
@import url('winbox-ui/themes/index.less');
@primary-color: #2055f4;

6
src/styles/winbox-ui/less/red.less

@ -0,0 +1,6 @@
/* Theme: Red
-------------------------- */
@import url('winbox-ui/themes/index.less');
@primary-color: #ed6363;

6
src/styles/winbox-ui/less/white.less

@ -0,0 +1,6 @@
/* Theme: White
-------------------------- */
@import url('winbox-ui/themes/index.less');
@primary-color: #3252a5;

15
src/utils/auth.js

@ -0,0 +1,15 @@
import Cookies from 'js-cookie';
const TokenKey = 'winbox_admin_template_token';
export function getToken() {
return Cookies.get(TokenKey);
}
export function setToken(token) {
return Cookies.set(TokenKey, token);
}
export function removeToken() {
return Cookies.remove(TokenKey);
}

59
src/utils/axiosCancel.js

@ -0,0 +1,59 @@
import axios from 'axios';
import qs from 'qs';
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
let pendingMap = new Map();
export const getPendingUrl = (config) => [
config.method,
config.url,
qs.stringify(config.data),
qs.stringify(config.params)
].join('&');
export class AxiosCanceler {
/**
* 添加请求
* @param {Object} config
*/
addPending = (config) => {
this.removePending(config);
const url = getPendingUrl(config);
config.cancelToken = config.cancelToken ||
new axios.CancelToken((cancel) => {
// 如果 pendingMap 中不存在当前请求,则添加进去
if (!pendingMap.has(url)) pendingMap.set(url, cancel);
});
}
/**
* 移除请求
* @param {Object} config
*/
removePending = (config) => {
const url = getPendingUrl(config);
// 如果在 pendingMap 中存在当前请求标识,需要取消当前请求,并且移除
if (pendingMap.has(url)) {
const cancel = pendingMap.get(url);
cancel && cancel(url);
pendingMap.delete(url);
}
}
/**
* @description: 清空所有pending(在路由跳转时调用)
*/
clearPending() {
for (const [url, cancel] of pendingMap) {
cancel(url);
}
pendingMap.clear();
}
/**
* @description: 重置
*/
reset() {
pendingMap = new Map();
}
}

60
src/utils/axiosStatus.js

@ -0,0 +1,60 @@
import { Message } from 'winbox-ui';
const error = (msg) => {
Message({
message: msg || 'Error',
type: 'error',
duration: 5 * 1000
});
};
export function axiosStatus(axiosError) {
const status = axiosError.response.status || '';
const msg = axiosError.message;
switch (status) {
case 400:
error(`${msg}`);
break;
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
error('用户没有权限(令牌、用户名、密码错误)!');
// store.dispatch('user/loginOut', {
// goLogin: true,
// });
break;
case 403:
error('用户得到授权,但是访问是被禁止的。!');
break;
// 404请求不存在
case 404:
error('网络请求错误,未找到该资源!');
break;
case 405:
error('网络请求错误,请求方法未允许!');
break;
case 408:
error('网络请求超时!');
break;
case 500:
error('服务器错误,请联系管理员!');
break;
case 501:
error('网络未实现!');
break;
case 502:
error('网络错误!');
break;
case 503:
error('服务不可用,服务器暂时过载或维护!');
break;
case 504:
error('网络超时!');
break;
case 505:
error('http版本不支持该请求!');
break;
default:
error(msg);
}
}

31
src/utils/common.js

@ -0,0 +1,31 @@
/*
* @Description:常用js封装
*/
import store from '@/store';
const common = {
/**
* @description: 换肤加class函数
* @param {*}
* @return {*}
*/
toggleClass(element, className) {
if (!element || !className) return;
element.className = className;
},
/**
* @description: 获取主题对应色值(仅限于variable.less文件)
* @param {*} value (themeColor)
* @return {*} ( #ed6363)
*/
getThemeVars(value) {
const lessVars = require('!less-to-json-loader!../styles/variable.less');
const lessVar = lessVars[value];
const currentTheme = store.getters.theme;
const currentVars = require(`../styles/themes/${currentTheme}.less`);
const filterVar = lessVar.replace(/[(|)]/g, '').replace('var', '');
return currentVars[filterVar];
}
};
export default common;

17
src/utils/filters.js

@ -0,0 +1,17 @@
/*
* @Description: 通用过滤器
*/
// 截取.文件名前的字符串
const filterFileFormat = fileName => {
if (fileName.includes('.')) {
// 获取最后一个.的位置
const index = fileName.lastIndexOf('.');
// 获取前缀
return fileName.substr(0, index);
} else {
return fileName;
}
};
export { filterFileFormat };

10
src/utils/get-page-title.js

@ -0,0 +1,10 @@
import defaultSettings from '@/settings';
const title = defaultSettings.title || 'Winbox Admin Template';
export default function getPageTitle(pageTitle) {
if (pageTitle) {
return `${pageTitle} - ${title}`;
}
return `${title}`;
}

55
src/utils/request.js

@ -0,0 +1,55 @@
import axios from 'axios';
import { AxiosCanceler } from './axiosCancel';
import { axiosStatus } from './axiosStatus';
import store from '@/store';
import { getToken } from '@/utils/auth';
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: false, // send cookies when cross-domain requests
timeout: 30000 // request timeout
});
// axios cancel class
const axiosCancel = new AxiosCanceler();
// request interceptor
service.interceptors.request.use(
config => {
// judge whether the request is cancelled by ignorecanceltoken
const ignoreCancelToken = config.headers.ignoreCancelToken || false;
!ignoreCancelToken && axiosCancel.addPending(config);
// do something before request is sent,please modify it according to the actual situation
if (store.getters.token) config.headers['X-Token'] = getToken();
return config;
},
error => {
// do something with request error
return Promise.reject(error);
}
);
// response interceptor
service.interceptors.response.use(
response => {
// After the request ends, remove the request
response && axiosCancel.removePending(response.config);
// response data
const res = response.data;
// if the custom code is not success, it is judged as an error.
if (!res.success) {
axiosStatus(res);
return Promise.reject(new Error(res.message || 'Error'));
} else {
return res;
}
},
error => {
axiosStatus(error);
return Promise.reject(error);
}
);
export default service;

20
src/utils/validate.js

@ -0,0 +1,20 @@
/**
* 数据校验
*/
/**
* @param {string} path
* @returns {Boolean}
*/
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
/**
* @param {string} str
* @returns {Boolean}
*/
export function validUsername(str) {
const valid_map = ['admin', 'editor'];
return valid_map.indexOf(str.trim()) >= 0;
}

28
src/views/404.vue

@ -0,0 +1,28 @@
<template>
<div class="page-404-container">
<w-result icon="404" sub-title="请检查您输入的URL是否正确或者点击下面的按钮返回主页">
<template v-slot:extra>
<w-button type="primary" @click="$router.push('/')">返回首页</w-button>
</template>
</w-result>
</div>
</template>
<script>
export default {
name: 'Page404'
};
</script>
<style lang="less" scoped>
.page-404-container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
/deep/.icon-404 {
height: 280px;
width: 500px;
}
}
</style>

81
src/views/demo/form/index.vue

@ -0,0 +1,81 @@
<template>
<div class="form-container">
<w-form ref="form" :model="form" label-width="80px" class="demo-form">
<w-form-item label="活动名称">
<w-input v-model="form.name"></w-input>
</w-form-item>
<w-form-item label="活动区域">
<w-select v-model="form.region" placeholder="请选择活动区域">
<w-option label="区域一" value="shanghai"></w-option>
<w-option label="区域二" value="beijing"></w-option>
</w-select>
</w-form-item>
<w-form-item label="活动时间">
<w-col :span="11">
<w-date-picker v-model="form.date1" type="date" placeholder="选择日期" style="width: 100%"></w-date-picker>
</w-col>
<w-col class="line" :span="2">-</w-col>
<w-col :span="11">
<w-time-picker v-model="form.date2" placeholder="选择时间" style="width: 100%"></w-time-picker>
</w-col>
</w-form-item>
<w-form-item label="活动性质">
<w-checkbox-group v-model="form.type">
<w-checkbox-button label="美食/餐厅线上活动" name="type"></w-checkbox-button>
<w-checkbox-button label="地推活动" name="type"></w-checkbox-button>
<w-checkbox-button label="线下主题活动" name="type"></w-checkbox-button>
</w-checkbox-group>
</w-form-item>
<w-form-item label="特殊资源">
<w-radio-group v-model="form.resource">
<w-radio border label="线上品牌商赞助"></w-radio>
<w-radio border label="线下场地免费"></w-radio>
</w-radio-group>
</w-form-item>
<w-form-item>
<w-button type="primary" @click="onSubmit">立即创建</w-button>
<w-button>取消</w-button>
</w-form-item>
</w-form>
</div>
</template>
<script>
export default {
data() {
return {
form: {
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: ''
}
};
},
methods: {
onSubmit() {
console.log('submit!');
}
}
};
</script>
<style lang="less" scoped>
.form-container {
background: #fff;
height: 100%;
padding: 20px;
.demo-form {
width: 480px;
.line {
text-align: center;
}
}
}
</style>

145
src/views/demo/table/index.vue

@ -0,0 +1,145 @@
<template>
<div class="table-container">
<w-table :data="tableData" style="width: 100%" height="750px">
<w-table-column prop="name" label="Name" width="180">
<template v-slot="scope">
<span class="name">{{ scope.row.name }}</span>
</template>
</w-table-column>
<w-table-column prop="age" label="Age" width="180"></w-table-column>
<w-table-column prop="address" label="Address"></w-table-column>
<w-table-column prop="tags" label="Tags">
<template v-slot="scope">
<w-tag v-for="(tag, index) in scope.row.tags" :key="index" style="margin: 0 10px 5px 0" :type="tag">
{{ tag }}
</w-tag>
</template>
</w-table-column>
<w-table-column prop="action" label="Action">
<template v-slot="scope">
<span v-for="(action, index) in scope.row.action" :key="index" class="action">
{{ action }}
</span>
</template>
</w-table-column>
</w-table>
<w-pagination layout="total,prev, pager, next,sizes,jumper" :total="50"></w-pagination>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [
{
name: 'Wen Ge',
age: 32,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 38,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['danger'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 52,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'Wen Ge',
age: 32,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 38,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['danger'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 52,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'Wen Ge',
age: 32,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 38,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['danger'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 52,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'Wen Ge',
age: 32,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 38,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['danger'],
action: ['Invite', 'Jim Green', 'Delete']
},
{
name: 'John Brown',
age: 52,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['success', 'processing'],
action: ['Invite', 'Jim Green', 'Delete']
}
]
};
}
};
</script>
<style lang="less" scoped>
.table-container {
background: #fff;
height: 100%;
padding: 20px;
}
.name,
.action {
color: #2055f4;
cursor: pointer;
display: inline-block;
margin-right: 16px;
}
.w-pagination {
position: absolute;
bottom: 40px;
right: 20px;
}
</style>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save