Compare commits

...

39 Commits

Author SHA1 Message Date
Pan
630d06bf76 [release] 1.2.0 2017-07-28 17:38:42 +08:00
Pan
8c9ebd7345 set productionSourceMap: false 2017-07-28 17:29:07 +08:00
Pan
89c72d277e refine css 2017-07-28 17:19:40 +08:00
Pan
108b380d20 menu 支持 icon 嵌套 2017-07-28 17:19:40 +08:00
Pan
69fc52d4e9 refine router.js 2017-07-28 17:19:40 +08:00
Pan
d78b49803d refine css 2017-07-28 17:19:40 +08:00
Pan
dd9ef5145d refine sidebar 2017-07-28 17:19:40 +08:00
Pan
ae44d0b8ca update element & vue 2017-07-28 17:19:40 +08:00
Pan
6ad03ac5f0 rm documentImg 2017-07-28 16:10:00 +08:00
Pan
553ef477ef fix typo 2017-07-28 09:50:52 +08:00
Pan
1083a17386 fix chart bug 2017-07-27 14:12:31 +08:00
Pan
535fe083e5 fix bug 2017-07-27 13:55:06 +08:00
spiritree
83e8dfc494 fix selectExcel bug 2017-07-27 12:20:04 +08:00
Pan
77ff823037 fix levelbar bug 2017-07-26 13:32:02 +08:00
Pan
696ecddc72 [release] 1.1.1 2017-07-25 17:54:14 +08:00
Pan
7969b6d33e refine code 2017-07-25 17:37:15 +08:00
spiritree
3488930e4d add new export-excel function 2017-07-25 17:26:17 +08:00
ginhom
4467420d31 fixed: catch 'GetInfo' exception 2017-07-25 16:19:09 +08:00
Pan
96b100d33c add editorconfig 2017-07-24 17:58:46 +08:00
Pan
d6868d7190 rm 2017-07-24 14:55:33 +08:00
Pan
a0a011e985 fix cookie token bug 2017-07-20 13:27:05 +08:00
花裤衩
c8856741c6 Update README.md 2017-07-19 16:23:40 +08:00
dongsuo
cbc5c18291 '代码格式化' 2017-07-17 18:01:09 +08:00
dongsuo
5069a4d1df '修改返回顶部功能' 2017-07-17 18:01:09 +08:00
dongsuo
bf08756644 '新增功能:返回顶部' 2017-07-17 18:01:09 +08:00
Pan
ba2e486099 fix typo 2017-07-17 15:26:40 +08:00
Pan
968d4026ec refine linchart height 2017-07-17 10:19:33 +08:00
Pan
06ed2c6cac refine dashboard charts animation 2017-07-17 10:16:28 +08:00
Pan
67c4a3b4ff fix case-sensitive bug 2017-07-16 15:19:42 +08:00
Pan
c339b626eb fix case-sensitive bug 2017-07-16 15:19:05 +08:00
Pan
6cb64bebdc refine tab-views 2017-07-14 17:40:34 +08:00
Pan
e3198fd47d update xlsx 2017-07-14 13:59:37 +08:00
Pan
29d28c3231 fix bug in vuex of strict model 2017-07-13 16:54:54 +08:00
Pan
62cb24c1a6 fix case-sensitive bug 2017-07-12 13:20:32 +08:00
Pan
16ce4f34e1 fix case-sensitive bug 2017-07-12 13:20:14 +08:00
Pan
8b4e70e6b2 refine css 2017-07-10 11:12:15 +08:00
Pan
4ad90406af echarts add watch sidebar resize 2017-07-10 10:53:03 +08:00
ttop5
84600696e3 correct README errors 2017-07-07 11:25:42 +08:00
Pan
81b78f9b73 [release] 1.1.0 2017-07-06 18:26:51 +08:00
33 changed files with 761 additions and 307 deletions

14
.editorconfig Normal file
View File

@@ -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

View File

@@ -5,12 +5,15 @@
[wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
**本项目的定位是后台集成方案,不适合当基础模板来开发,模板建议使用 [vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template)**
**本项目的定位是后台集成方案,不适合当基础模板来开发,模板建议使用 [vueAdmin-template](https://github.com/PanJiaChen/vueAdmin-template) 桌面端 [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)**
**注意该项目目前使用element-ui@1.3.3版本,所以最低兼容 Vue 2.3.0**
**注意该项目目前使用element-ui@1.3.3版本,所以最低兼容 Vue 2.3.0**
## 前言
> 这半年来一直在用vue写管理后台目前后台已经有百来个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包.由于是个人项目所以数据请求都是用了mockjs模拟。注意项目基础上改造开发时请移除mock文件。
> 这半年来一直在用vue写管理后台目前后台已经有百来个页面十几种权限但维护成本依然很低所以准备开源分享一下后台开发的经验和成果。目前的技术栈主要的采用vue+element+axios由webpack2打包由于是个人项目所以数据请求都是用了mockjs模拟。注意项目基础上改造开发时请移除mock文件。
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
@@ -21,13 +24,13 @@
- [手摸手带你用vue撸后台 系列四(vueAdmin 一个极简的后台基础模板)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
- [手摸手带你封装一个vue component](https://segmentfault.com/a/1190000009090836)
相应需求开了一个qq群 591724180 方便大家交流
相应需求开了一个qq群 `591724180` 方便大家交流
**如有问题请先看上述文章和Wiki,若不能满足欢迎issuepr~**
**如有问题请先看上述文章和Wiki若不能满足,欢迎 issuepr ~**
**该项目并不是一个脚手架,更倾向于是一个集成解决方案方案**
**该项目并不是一个脚手架,更倾向于是一个集成解决方案**
**该项目不支持低版本游览器有需求请自行添加polyfill[详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
**该项目不支持低版本游览器有需求请自行添加polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
## 功能
@@ -44,7 +47,7 @@
- Sticky
- CountTo
- echarts图表
- 401401错误页面
- 401404错误页面
- 错误日志
- 导出excel
- table example
@@ -182,5 +185,8 @@ Detailed changes for each release are documented in the [release notes](https://
![enter image description here](https://github.com/PanJiaChen/vue-element-admin/blob/master/gifs/excel.png)
## [更多demo](http://panjiachen.github.io/vue-element-admin)
## [查看更多demo](http://panjiachen.github.io/vue-element-admin)
## License
MIT

View File

@@ -10,7 +10,7 @@ module.exports = {
assetsSubDirectory: 'static',
assetsPublicPath: './', //请根据自己路径配置更改
staticPath:'./static/', //请根据自己路径配置更改
productionSourceMap: true,
productionSourceMap: false,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "juicy",
"version": "1.0.3",
"version": "1.2.0",
"description": "A Vue.js admin",
"author": "Pan <panfree23@gmail.com>",
"license": "MIT",
@@ -17,7 +17,7 @@
"codemirror": "5.26.0",
"dropzone": "5.1.0",
"echarts": "3.6.1",
"element-ui": "1.3.6",
"element-ui": "1.4.1",
"file-saver": "1.3.3",
"jquery": "3.1.1",
"js-cookie": "2.1.4",
@@ -29,13 +29,13 @@
"showdown": "1.7.1",
"simplemde": "1.11.2",
"sortablejs": "1.5.1",
"vue": "2.3.3",
"vue": "2.4.2",
"vue-count-to": "1.0.5",
"vue-multiselect": "2.0.0-beta.15",
"vue-router": "2.5.3",
"vuedraggable": "2.13.1",
"vuex": "2.3.1",
"xlsx": "0.8.1"
"xlsx": "^0.10.8"
},
"devDependencies": {
"autoprefixer": "7.1.1",
@@ -43,7 +43,7 @@
"babel-eslint": "7.2.3",
"babel-loader": "7.0.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-preset-env": "1.5.2",
"babel-preset-env": "1.5.2",
"babel-preset-stage-2": "6.24.1",
"babel-register": "6.24.1",
"chalk": "1.1.3",
@@ -78,7 +78,7 @@
"url-loader": "0.5.8",
"vue-loader": "12.2.1",
"vue-style-loader": "3.0.1",
"vue-template-compiler": "2.3.3",
"vue-template-compiler": "2.4.2",
"webpack": "2.6.1",
"webpack-bundle-analyzer": "2.8.2",
"webpack-dev-middleware": "1.10.2",

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,110 @@
<template>
<transition :name="transitionName">
<div class="back-to-top" @click="backToTop" v-show="visible" :style="customStyle">
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height: 16px; width: 16px;">
<title>回到顶部</title>
<g>
<path d="M12.036 15.59c0 .55-.453.995-.997.995H5.032c-.55 0-.997-.445-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29c.39-.39 1.026-.385 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" fill-rule="evenodd"></path>
</g>
</svg>
</div>
</transition>
</template>
<script>
export default {
name: 'BackToTop',
props: {
visibilityHeight: {
type: Number,
default: 400
},
backPosition: {
type: Number,
default: 0
},
customStyle: {
type: Object,
default: {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px',
background: '#e7eaf1'
}
},
transitionName: {
type: String,
default: 'fade'
}
},
data() {
return {
visible: false,
interval: null
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll);
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll);
if (this.interval) {
clearInterval(this.interval);
}
},
methods: {
handleScroll() {
this.visible = window.pageYOffset > this.visibilityHeight;
},
backToTop() {
const start = window.pageYOffset;
let i = 0;
this.interval = setInterval(() => {
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500));
if (next <= this.backPosition) {
window.scrollTo(0, this.backPosition);
clearInterval(this.interval)
} else {
window.scrollTo(0, next);
}
i++;
}, 16.7)
},
easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * (--t * (t - 2) - 1) + b;
}
}
}
</script>
<style scoped>
.back-to-top {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
.back-to-top:hover {
background: #d5dbe7;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0
}
.back-to-top .Icon {
fill: #9aaabf;
background: none;
}
</style>

View File

@@ -19,6 +19,7 @@ import IconSvg from 'components/Icon-svg';// svg 组件
import vueWaves from './directive/waves';// 水波纹指令
import errLog from 'store/errLog';// error log组件
import './mock/index.js'; // 该项目所有请求使用mockjs模拟
import { getToken } from 'utils/auth';
// register globally
Vue.component('multiselect', Multiselect);
@@ -43,7 +44,7 @@ function hasPermission(roles, permissionRoles) {
const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
router.beforeEach((to, from, next) => {
NProgress.start(); // 开启Progress
if (store.getters.token) { // 判断是否有token
if (getToken()) { // 判断是否有token
if (to.path === '/login') {
next({ path: '/' });
} else {
@@ -54,6 +55,10 @@ router.beforeEach((to, from, next) => {
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
next({ ...to }); // hack方法 确保addRoutes已完成
})
}).catch(() => {
store.dispatch('FedLogOut').then(() => {
next({ path: '/login' });
})
})
} else {
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
@@ -82,7 +87,7 @@ router.afterEach(() => {
Vue.config.productionTip = false;
// 生产环境错误日志
if (process.env === 'production') {
if (process.env.NODE_ENV === 'production') {
Vue.config.errorHandler = function(err, vm) {
console.log(err, window.location.href);
errLog.pushLog({

View File

@@ -3,87 +3,30 @@ import Router from 'vue-router';
const _import = require('./_import_' + process.env.NODE_ENV);
// in development env not use Lazy Loading,because Lazy Loading large page will cause webpack hot update too slow.so only in production use Lazy Loading
Vue.use(Router);
/* layout */
import Layout from '../views/layout/Layout';
/* login */
const Login = _import('login/index');
const authRedirect = _import('login/authredirect');
/* dashboard */
const dashboard = _import('dashboard/index');
/* Introduction */
const Introduction = _import('introduction/index');
/* components */
const componentsIndex = _import('components/index');
const Tinymce = _import('components/tinymce');
const Markdown = _import('components/markdown');
const JsonEditor = _import('components/jsoneditor');
const DndList = _import('components/dndlist');
const AvatarUpload = _import('components/avatarUpload');
const Dropzone = _import('components/dropzone');
const Sticky = _import('components/sticky');
const SplitPane = _import('components/splitpane');
const CountTo = _import('components/countTo');
const Mixin = _import('components/mixin');
/* charts */
const chartIndex = _import('charts/index');
const KeyboardChart = _import('charts/keyboard');
const KeyboardChart2 = _import('charts/keyboard2');
const LineMarker = _import('charts/line');
const MixChart = _import('charts/mixchart');
/* error page */
const Err404 = _import('error/404');
const Err401 = _import('error/401');
/* error log */
const ErrorLog = _import('errlog/index');
/* excel */
const ExcelDownload = _import('excel/index');
/* theme */
const Theme = _import('theme/index');
/* example*/
const TableLayout = _import('example/table/index');
const DynamicTable = _import('example/table/dynamictable');
const Table = _import('example/table/table');
const DragTable = _import('example/table/dragTable');
const InlineEditTable = _import('example/table/inlineEditTable');
const Form = _import('example/form');
const Tab = _import('example/tab/index');
/* permission */
const Permission = _import('permission/index');
Vue.use(Router);
/**
* icon : the icon show in the sidebar
* hidden : if hidden:true will not show in the sidebar
* redirect : if redirect:noredirect will not redirct in the levelbar
* noDropdown : if noDropdown:true will not has submenu
* meta : { role: ['admin'] } will control the page role
**/
/**
* icon : the icon show in the sidebar
* hidden : if `hidden:true` will not show in the sidebar
* redirect : if `redirect:noredirect` will no redirct in the levelbar
* noDropdown : if `noDropdown:true` will has no submenu
* meta : { role: ['admin'] } will control the page role
**/
export const constantRouterMap = [
{ path: '/login', component: Login, hidden: true },
{ path: '/authredirect', component: authRedirect, hidden: true },
{ path: '/404', component: Err404, hidden: true },
{ path: '/401', component: Err401, hidden: true },
{ path: '/login', component: _import('login/index'), hidden: true },
{ path: '/authredirect', component: _import('login/authredirect'), hidden: true },
{ path: '/404', component: _import('error/404'), hidden: true },
{ path: '/401', component: _import('error/401'), hidden: true },
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: '首页',
hidden: true,
children: [{ path: 'dashboard', component: dashboard }]
children: [{ path: 'dashboard', component: _import('dashboard/index') }]
},
{
path: '/introduction',
@@ -91,7 +34,7 @@ export const constantRouterMap = [
redirect: '/introduction/index',
icon: 'xinrenzhinan',
noDropdown: true,
children: [{ path: 'index', component: Introduction, name: '简述' }]
children: [{ path: 'index', component: _import('introduction/index'), name: '简述' }]
}
]
@@ -110,7 +53,7 @@ export const asyncRouterMap = [
icon: 'quanxian',
meta: { role: ['admin'] },
noDropdown: true,
children: [{ path: 'index', component: Permission, name: '权限测试页', meta: { role: ['admin'] } }]
children: [{ path: 'index', component: _import('permission/index'), name: '权限测试页', meta: { role: ['admin'] } }]
},
{
path: '/components',
@@ -119,17 +62,18 @@ export const asyncRouterMap = [
name: '组件',
icon: 'zujian',
children: [
{ path: 'index', component: componentsIndex, name: '介绍 ' },
{ path: 'tinymce', component: Tinymce, name: '富文本编辑器' },
{ path: 'markdown', component: Markdown, name: 'Markdown' },
{ path: 'jsoneditor', component: JsonEditor, name: 'JSON编辑器' },
{ path: 'dndlist', component: DndList, name: '列表拖拽' },
{ path: 'splitpane', component: SplitPane, name: 'SplitPane' },
{ path: 'avatarupload', component: AvatarUpload, name: '头像上传' },
{ path: 'dropzone', component: Dropzone, name: 'Dropzone' },
{ path: 'sticky', component: Sticky, name: 'Sticky' },
{ path: 'countto', component: CountTo, name: 'CountTo' },
{ path: 'mixin', component: Mixin, name: '小组件' }
{ path: 'index', component: _import('components/index'), name: '介绍 ' },
{ path: 'tinymce', component: _import('components/tinymce'), name: '富文本编辑器' },
{ path: 'markdown', component: _import('components/markdown'), name: 'Markdown' },
{ path: 'jsoneditor', component: _import('components/jsoneditor'), name: 'JSON编辑器' },
{ path: 'dndlist', component: _import('components/dndlist'), name: '列表拖拽' },
{ path: 'splitpane', component: _import('components/splitpane'), name: 'SplitPane' },
{ path: 'avatarupload', component: _import('components/avatarUpload'), name: '头像上传' },
{ path: 'dropzone', component: _import('components/dropzone'), name: 'Dropzone' },
{ path: 'sticky', component: _import('components/sticky'), name: 'Sticky' },
{ path: 'countto', component: _import('components/countTo'), name: 'CountTo' },
{ path: 'mixin', component: _import('components/mixin'), name: '小组件' },
{ path: 'backtotop', component: _import('components/backToTop'), name: '返回顶部' }
]
},
{
@@ -139,51 +83,13 @@ export const asyncRouterMap = [
name: '图表',
icon: 'tubiaoleixingzhengchang',
children: [
{ path: 'index', component: chartIndex, name: '介绍' },
{ path: 'keyboard', component: KeyboardChart, name: '键盘图表' },
{ path: 'keyboard2', component: KeyboardChart2, name: '键盘图表2' },
{ path: 'line', component: LineMarker, name: '折线图' },
{ path: 'mixchart', component: MixChart, name: '混合图表' }
{ path: 'index', component: _import('charts/index'), name: '介绍' },
{ path: 'keyboard', component: _import('charts/keyboard'), name: '键盘图表' },
{ path: 'keyboard2', component: _import('charts/keyboard2'), name: '键盘图表2' },
{ path: 'line', component: _import('charts/line'), name: '折线图' },
{ path: 'mixchart', component: _import('charts/mixChart'), name: '混合图表' }
]
},
{
path: '/errorpage',
component: Layout,
redirect: 'noredirect',
name: '错误页面',
icon: '404',
children: [
{ path: '401', component: Err401, name: '401' },
{ path: '404', component: Err404, name: '404' }
]
},
{
path: '/errlog',
component: Layout,
redirect: 'noredirect',
name: 'errlog',
icon: 'bug',
noDropdown: true,
children: [{ path: 'log', component: ErrorLog, name: '错误日志' }]
},
{
path: '/excel',
component: Layout,
redirect: 'noredirect',
name: 'excel',
icon: 'EXCEL',
noDropdown: true,
children: [{ path: 'download', component: ExcelDownload, name: '导出excel' }]
},
{
path: '/theme',
component: Layout,
redirect: 'noredirect',
name: 'theme',
icon: 'theme',
noDropdown: true,
children: [{ path: 'index', component: Theme, name: '换肤' }]
},
{
path: '/example',
component: Layout,
@@ -193,21 +99,63 @@ export const asyncRouterMap = [
children: [
{
path: '/example/table',
component: TableLayout,
component: _import('example/table/index'),
redirect: '/example/table/table',
name: 'Table',
icon: 'table',
children: [
{ path: 'dynamictable', component: DynamicTable, name: '动态table' },
{ path: 'dragtable', component: DragTable, name: '拖拽table' },
{ path: 'inline_edit_table', component: InlineEditTable, name: 'table内编辑' },
{ path: 'table', component: Table, name: '综合table' }
{ path: 'dynamictable', component: _import('example/table/dynamictable'), name: '动态table' },
{ path: 'dragtable', component: _import('example/table/dragTable'), name: '拖拽table' },
{ path: 'inline_edit_table', component: _import('example/table/inlineEditTable'), name: 'table内编辑' },
{ path: 'table', component: _import('example/table/table'), name: '综合table' }
]
},
{ path: 'form/edit', component: Form, name: '编辑Form', meta: { isEdit: true } },
{ path: 'form/create', component: Form, name: '创建Form' },
{ path: 'form/edit', icon: 'ziliaoshouce', component: _import('example/form'), name: '编辑Form', meta: { isEdit: true } },
{ path: 'form/create', icon: 'yinhangqia', component: _import('example/form'), name: '创建Form' },
{ path: 'tab/index', component: Tab, name: 'Tab' }
{ path: 'tab/index', icon: 'mobankuangjia', component: _import('example/tab/index'), name: 'Tab' }
]
},
{
path: '/errorpage',
component: Layout,
redirect: 'noredirect',
name: '错误页面',
icon: '404',
children: [
{ path: '401', component: _import('error/401'), name: '401' },
{ path: '404', component: _import('error/404'), name: '404' }
]
},
{
path: '/errlog',
component: Layout,
redirect: 'noredirect',
name: 'errlog',
icon: 'bug',
noDropdown: true,
children: [{ path: 'log', component: _import('errlog/index'), name: '错误日志' }]
},
{
path: '/excel',
component: Layout,
redirect: 'noredirect',
name: 'excel',
icon: 'EXCEL',
children: [
{ path: 'download', component: _import('excel/index'), name: '导出excel' },
{ path: 'download2', component: _import('excel/selectExcel'), name: '选择导出excel' }
]
},
{
path: '/theme',
component: Layout,
redirect: 'noredirect',
name: 'theme',
icon: 'theme',
noDropdown: true,
children: [{ path: 'index', component: _import('theme/index'), name: '换肤' }]
},
{ path: '*', redirect: '/404', hidden: true }
];

View File

@@ -19,11 +19,17 @@ const app = {
state.sidebar.opened = !state.sidebar.opened;
},
ADD_VISITED_VIEWS: (state, view) => {
if (state.visitedViews.includes(view)) return
state.visitedViews.push(view)
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push({ name: view.name, path: view.path })
},
DEL_VISITED_VIEWS: (state, view) => {
const index = state.visitedViews.indexOf(view)
let index
for (const [i, v] of state.visitedViews.entries()) {
if (v.path === view.path) {
index = i
break
}
}
state.visitedViews.splice(index, 1)
}
},

View File

@@ -1,4 +1,5 @@
import { asyncRouterMap, constantRouterMap } from 'src/router';
import { deepClone } from 'utils'
/**
* 通过meta.role判断是否与当前用户权限匹配
@@ -38,8 +39,8 @@ const permission = {
},
mutations: {
SET_ROUTERS: (state, routers) => {
state.addRouters = routers;
state.routers = constantRouterMap.concat(routers);
state.addRouters = deepClone(routers)
state.routers = deepClone(constantRouterMap.concat(routers))
}
},
actions: {

View File

@@ -1,12 +1,12 @@
import { loginByEmail, logout, getInfo } from 'api/login';
import Cookies from 'js-cookie';
import { getToken, setToken, removeToken } from 'utils/auth';
const user = {
state: {
user: '',
status: '',
code: '',
token: Cookies.get('Admin-Token'),
token: getToken(),
name: '',
avatar: '',
introduction: '',
@@ -56,7 +56,7 @@ const user = {
return new Promise((resolve, reject) => {
loginByEmail(email, userInfo.password).then(response => {
const data = response.data;
Cookies.set('Admin-Token', response.data.token);
setToken(response.data.token);
commit('SET_TOKEN', data.token);
resolve();
}).catch(error => {
@@ -87,7 +87,7 @@ const user = {
commit('SET_CODE', code);
loginByThirdparty(state.status, state.email, state.code).then(response => {
commit('SET_TOKEN', response.data.token);
Cookies.set('Admin-Token', response.data.token);
setToken(response.data.token);
resolve();
}).catch(error => {
reject(error);
@@ -101,7 +101,7 @@ const user = {
logout(state.token).then(() => {
commit('SET_TOKEN', '');
commit('SET_ROLES', []);
Cookies.remove('Admin-Token');
removeToken();
resolve();
}).catch(error => {
reject(error);
@@ -113,7 +113,7 @@ const user = {
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '');
Cookies.remove('Admin-Token');
removeToken();
resolve();
});
},
@@ -123,7 +123,7 @@ const user = {
return new Promise(resolve => {
commit('SET_ROLES', [role]);
commit('SET_TOKEN', role);
Cookies.set('Admin-Token', role);
setToken(role);
resolve();
})
}

View File

@@ -1,83 +1,82 @@
//覆盖一些element-ui样式
.block-checkbox {
display: block;
}
.block-checkbox {
display: block;
}
.operation-container {
.cell {
padding: 10px !important;
}
.el-button {
&:nth-child(3) {
margin-top: 10px;
margin-left: 0px;
}
&:nth-child(4) {
margin-top: 10px;
}
}
}
.operation-container {
.cell {
padding: 10px !important;
}
.el-button {
&:nth-child(3) {
margin-top: 10px;
margin-left: 0px;
}
&:nth-child(4) {
margin-top: 10px;
}
}
}
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.el-upload__input {
display: none;
}
.cell {
.el-tag {
margin-right: 8px;
}
}
.cell {
.el-tag {
margin-right: 8px;
}
}
.small-padding {
.cell {
padding-left: 8px;
padding-right: 8px;
}
}
.small-padding {
.cell {
padding-left: 8px;
padding-right: 8px;
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
.status-col {
.cell {
padding: 0 10px;
text-align: center;
.el-tag {
margin-right: 0px;
}
}
}
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
.el-dialog {
transform: none;
left: 0;
position: relative;
margin: 0 auto;
}
//文章页textarea修改样式
.article-textarea {
textarea {
padding-right: 40px;
resize: none;
border: none;
border-radius: 0px;
border-bottom: 1px solid #bfcbd9;
}
}
//文章页textarea修改样式
.article-textarea {
textarea {
padding-right: 40px;
resize: none;
border: none;
border-radius: 0px;
border-bottom: 1px solid #bfcbd9;
}
}
//element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
//element ui upload
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}

View File

@@ -1,6 +1,8 @@
@import './mixin.scss';
@import './btn.scss';
@import './element-ui.scss';
@import './mixin.scss';
@import './sidebar.scss';
body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;

76
src/styles/sidebar.scss Normal file
View File

@@ -0,0 +1,76 @@
// 侧边栏
.sidebar-container>.el-menu {
width: 100%!important;
}
.sidebar-container .svg-icon {
margin-right: 16px;
}
.hideSidebar .el-submenu>.el-submenu__title,
.hideSidebar .submenu-title-noDropdown {
padding-left: 10px!important;
}
.hideSidebar .submenu-title-noDropdown span,
.hideSidebar .el-submenu>.el-submenu__title>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
.hideSidebar .nest-menu .el-submenu__title {
text-align: initial!important;
padding-left: 20px!important;
span {
height: auto;
width: auto;
visibility: visible;
display: inline;
}
.el-submenu__icon-arrow {
display: block!important;
}
}
.hideSidebar .menu-wrapper>.el-menu-item,
.hideSidebar .submenu-title-noDropdown,
.hideSidebar .menu-wrapper>.el-submenu .el-submenu__title {
text-align: center;
}
.hideSidebar .el-menu-item .el-submenu__icon-arrow,
.hideSidebar .el-submenu .el-submenu__title .el-submenu__icon-arrow {
display: none;
}
.hideSidebar .submenu-title-noDropdown {
position: relative;
span {
transition: opacity .3s cubic-bezier(.55, 0, .1, 1);
opacity: 0;
}
&:hover {
span {
display: block;
border-radius: 3px;
z-index: 1002;
width: 140px;
height: 56px;
visibility: visible;
position: absolute;
right: -145px;
text-align: left;
text-indent: 20px;
top: 0px;
background-color: #1f2d3d;
opacity: 1;
}
}
}
.el-submenu .el-menu-item {
min-width: 180px!important;
}

15
src/utils/auth.js Normal file
View File

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

View File

@@ -1,7 +1,7 @@
import axios from 'axios';
import { Message } from 'element-ui';
import store from '../store';
// import router from '../router';
import { getToken } from 'utils/auth';
// 创建axios实例
const service = axios.create({
@@ -13,7 +13,7 @@ const service = axios.create({
service.interceptors.request.use(config => {
// Do something before request is sent
if (store.getters.token) {
config.headers['X-Token'] = store.getters.token; // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
config.headers['X-Token'] = getToken(); // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
}
return config;
}, error => {

View File

@@ -250,3 +250,21 @@
};
}
export function deepClone(source) {
if (!source && typeof source !== 'object') {
throw new Error('error arguments', 'shallowClone');
}
const targetObj = source.constructor === Array ? [] : {};
for (const keys in source) {
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepClone(source[keys]);
} else {
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}

View File

@@ -0,0 +1,155 @@
<template>
<div class="components-container">
<code>页面滚动到指定位置会在右下角出现返回顶部按钮</code>
<code>可自定义按钮的样式show/hide临界点返回的位置 如需文字提示可在外部使用Element的el-tooltip元素 </code>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<div>我是占位</div>
<!--可自定义按钮的样式show/hide临界点返回的位置 -->
<!--如需文字提示可在外部添加element的<el-tooltip></el-tooltip>元素 -->
<el-tooltip placement="top" content="文字提示">
<back-to-top transitionName="fade" :customStyle="myBackToTopStyle" :visibilityHeight="300" :backPosition="50"></back-to-top>
</el-tooltip>
</div>
</template>
<script>
import BackToTop from 'components/BackToTop';
export default {
components: { BackToTop },
data() {
return {
myBackToTopStyle: {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px', // 请保持与高度一致以垂直居中
background: '#e7eaf1'// 按钮的背景颜色
}
}
}
}
</script>

View File

@@ -5,7 +5,7 @@
<script>
import echarts from 'echarts';
require('echarts/theme/macarons'); // echarts 主题
const animationDuration = 3000;
export default {
props: {
className: {
@@ -68,19 +68,22 @@
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [79, 52, 200, 334, 390, 330, 220]
data: [79, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageB',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [80, 52, 200, 334, 390, 330, 220]
data: [80, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageC',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [30, 52, 200, 334, 390, 330, 220]
data: [30, 52, 200, 334, 390, 330, 220],
animationDuration
}]
})
}

View File

@@ -19,7 +19,7 @@
},
height: {
type: String,
default: '300px'
default: '350px'
},
autoResize: {
type: Boolean,
@@ -35,10 +35,16 @@
this.initChart();
if (this.autoResize) {
this.__resizeHanlder = debounce(() => {
this.chart.resize()
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHanlder)
}
// 监听侧边栏的变化
const sidebarElm = document.getElementsByClassName('sidebar-container')[0]
sidebarElm.addEventListener('transitionend', this.__resizeHanlder)
},
beforeDestroy() {
if (!this.chart) {
@@ -47,8 +53,12 @@
if (this.autoResize) {
window.removeEventListener('resize', this.__resizeHanlder)
}
this.chart.dispose();
this.chart = null;
const sidebarElm = document.getElementsByClassName('sidebar-container')[0]
sidebarElm.removeEventListener('transitionend', this.__resizeHanlder)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
@@ -82,7 +92,9 @@
},
smooth: true,
type: 'line',
data: [100, 120, 161, 134, 105, 160, 165]
data: [100, 120, 161, 134, 105, 160, 165],
animationDuration: 2600,
animationEasing: 'cubicInOut'
},
{
name: 'buyers',
@@ -99,7 +111,9 @@
}
}
},
data: [120, 82, 91, 154, 162, 140, 130]
data: [120, 82, 91, 154, 162, 140, 130],
animationDuration: 2000,
animationEasing: 'quadraticOut'
}]
})
}

View File

@@ -52,7 +52,7 @@
legend: {
x: 'center',
y: 'bottom',
data: ['industries', 'technology', 'gold', 'forex', 'forecasts', 'markets']
data: ['industries', 'technology', 'forex', 'gold', 'forecasts', 'markets']
},
calculable: true,
series: [
@@ -65,9 +65,11 @@
{ value: 240, name: 'technology' },
{ value: 149, name: 'forex' },
{ value: 100, name: 'gold' },
{ value: 59, name: 'forecastsx' },
{ value: 59, name: 'forecasts' },
{ value: 49, name: 'markets' }
]
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})

View File

@@ -6,7 +6,7 @@
<code>
现在的管理后台基本都是spa的形式了它增强了用户体验但同时也会怎增加页面出问题的可能性可能一个小小的疏忽就导致整个页面的死锁好在Vue官网提供了一个方法来捕获处理异常
</code>
<a href="#"><img src='../../../documentImg/code1.png'></a>
<a href="#"><img src='http://panjiachen.github.io/images/errHandler.png'></a>
</div>
</template>

View File

@@ -0,0 +1,91 @@
<template>
<div class="app-container">
<el-button style='margin-bottom:20px;float:right' type="primary" icon="document" @click="handleDownload">导出excel</el-button>
<el-table :data="list" v-loading.body="listLoading" element-loading-text="拼命加载中" border fit highlight-current-row @selection-change="handleSelectionChange" ref="multipleTable">
<el-table-column type="selection" align="center"></el-table-column>
<el-table-column align="center" label='ID' width="95">
<template scope="scope">
{{scope.$index}}
</template>
</el-table-column>
<el-table-column label="文章标题">
<template scope="scope">
{{scope.row.title}}
</template>
</el-table-column>
<el-table-column label="作者" width="110">
<template scope="scope">
<span>{{scope.row.author}}</span>
</template>
</el-table-column>
<el-table-column label="阅读数" width="105" align="center">
<template scope="scope">
{{scope.row.pageviews}}
</template>
</el-table-column>
<el-table-column align="center" prop="created_at" label="发布时间" width="200">
<template scope="scope">
<i class="el-icon-time"></i>
<span>{{scope.row.display_time}}</span>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import { getList } from 'api/article';
export default {
data() {
return {
list: null,
listLoading: true,
multipleSelection: []
}
},
created() {
this.fetchData();
},
methods: {
fetchData() {
this.listLoading = true;
getList(this.listQuery).then(response => {
this.list = response.data;
this.listLoading = false;
})
},
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleDownload() {
if (this.multipleSelection.length) {
require.ensure([], () => {
const { export_json_to_excel } = require('vendor/Export2Excel');
const tHeader = ['序号', '文章标题', '作者', '阅读数', '发布时间'];
const filterVal = ['id', 'title', 'author', 'pageviews', 'display_time'];
const list = this.multipleSelection;
const data = this.formatJson(filterVal, list);
export_json_to_excel(tHeader, data, '列表excel');
this.$refs.multipleTable.clearSelection();
})
} else {
this.$message({
message: '请选择一条或多条记录导出',
type: 'warning'
});
}
},
formatJson(filterVal, jsonData) {
return jsonData.map(v => filterVal.map(j => v[j]))
}
}
};
</script>

View File

@@ -1,8 +1,6 @@
<template>
<div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
<div class="sidebar-wrapper">
<sidebar class="sidebar-container"></sidebar>
</div>
<sidebar class="sidebar-container"></sidebar>
<div class="main-container">
<navbar></navbar>
<app-main></app-main>
@@ -36,44 +34,27 @@
height: 100%;
width: 100%;
&.hideSidebar {
.sidebar-wrapper {
transform: translate(-140px, 0);
.sidebar-container {
transform: translate(132px, 0);
}
&:hover {
transform: translate(0, 0);
.sidebar-container {
transform: translate(0, 0);
}
}
.sidebar-container{
width:36px;
}
.main-container {
margin-left: 40px;
margin-left: 36px;
}
}
.sidebar-wrapper {
.sidebar-container {
transition: width 0.28s ease-out;
width: 180px;
height: 100%;
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
transition: all .28s ease-out;
}
.sidebar-container {
transition: all .28s ease-out;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: -17px;
overflow-y: scroll;
background: red;
}
.main-container {
min-height: 100%;
transition: all .28s ease-out;
transition: margin-left 0.28s ease-out;
margin-left: 180px;
}
}

View File

@@ -1,8 +1,8 @@
<template>
<el-breadcrumb class="app-levelbar" separator="/">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<router-link v-if='item.redirect==="noredirect"||index==levelList.length-1' to="" class="no-redirect">{{item.name}}</router-link>
<router-link v-else :to="item.path">{{item.name}}</router-link>
<router-link v-else :to="item.redirect||item.path">{{item.name}}</router-link>
</el-breadcrumb-item>
</el-breadcrumb>
</template>

View File

@@ -1,7 +1,9 @@
<template>
<el-menu mode="vertical" theme="dark" :default-active="$route.path">
<sidebar-item :routes='permission_routers'></sidebar-item>
<div>
<el-menu mode="vertical" theme="dark" :default-active="$route.path" :collapse="isCollapse">
<sidebar-item :routes='permission_routers'></sidebar-item>
</el-menu>
</div>
</template>
<script>
@@ -11,8 +13,12 @@
components: { SidebarItem },
computed: {
...mapGetters([
'permission_routers'
])
'permission_routers',
'sidebar'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>

View File

@@ -1,24 +1,31 @@
<template>
<div>
<div class='menu-wrapper'>
<template v-for="item in routes">
<router-link v-if="!item.hidden&&item.noDropdown&&item.children.length>0" :to="item.path+'/'+item.children[0].path">
<el-menu-item :index="item.path+'/'+item.children[0].path">
<icon-svg v-if='item.icon' :icon-class="item.icon"></icon-svg>{{item.children[0].name}}
<el-menu-item :index="item.path+'/'+item.children[0].path" class='submenu-title-noDropdown'>
<icon-svg v-if='item.icon' :icon-class="item.icon"></icon-svg><span slot="title">{{item.children[0].name}}</span>
</el-menu-item>
</router-link>
<el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
<template slot="title">
<icon-svg v-if='item.icon' :icon-class="item.icon"></icon-svg> {{item.name}}
<icon-svg v-if='item.icon' :icon-class="item.icon"></icon-svg><span>{{item.name}}</span>
</template>
<template v-for="child in item.children" v-if='!child.hidden'>
<sidebar-item class='menu-indent' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item>
<router-link v-else class="menu-indent" :to="item.path+'/'+child.path">
<sidebar-item class='nest-menu' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item>
<router-link v-else :to="item.path+'/'+child.path">
<el-menu-item :index="item.path+'/'+child.path">
{{child.name}}
<icon-svg v-if='child.icon' :icon-class="child.icon"></icon-svg><span>{{child.name}}</span>
</el-menu-item>
</router-link>
</template>
</el-submenu>
</template>
</div>
</template>
@@ -35,13 +42,6 @@
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.svg-icon {
margin-right: 10px;
}
.hideSidebar .menu-indent {
display: block;
text-indent: 10px;
}
</style>

View File

@@ -1,7 +1,7 @@
<template>
<div class='tabs-view-container'>
<router-link class="tabs-view" v-for="tag in Array.from(visitedViews)" :to="tag.path" :key="tag.path">
<el-tag :closable="true" @close='closeViewTabs(tag,$event)'>
<el-tag :closable="true" :type="isActive(tag.path)?'primary':''" @close='closeViewTabs(tag,$event)'>
{{tag.name}}
</el-tag>
</router-link>
@@ -29,6 +29,9 @@
},
addViewTabs() {
this.$store.dispatch('addVisitedViews', this.generateRoute())
},
isActive(path) {
return path === this.$route.path
}
},
watch: {

View File

@@ -77,8 +77,7 @@
this.loading = false;
this.$router.push({ path: '/' });
// this.showDialog = true;
}).catch(err => {
this.$message.error(err);
}).catch(() => {
this.loading = false;
});
} else {