Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
696ecddc72 | ||
|
7969b6d33e | ||
|
3488930e4d | ||
|
4467420d31 | ||
|
96b100d33c | ||
|
d6868d7190 | ||
|
a0a011e985 | ||
|
c8856741c6 | ||
|
cbc5c18291 | ||
|
5069a4d1df | ||
|
bf08756644 | ||
|
ba2e486099 | ||
|
968d4026ec | ||
|
06ed2c6cac | ||
|
67c4a3b4ff | ||
|
c339b626eb | ||
|
6cb64bebdc | ||
|
e3198fd47d | ||
|
29d28c3231 | ||
|
62cb24c1a6 | ||
|
16ce4f34e1 | ||
|
8b4e70e6b2 | ||
|
4ad90406af | ||
|
84600696e3 | ||
|
81b78f9b73 |
14
.editorconfig
Normal file
14
.editorconfig
Normal 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
|
22
README.md
22
README.md
@@ -5,12 +5,15 @@
|
|||||||
|
|
||||||
[wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
|
[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撸后台 系列四(vueAdmin 一个极简的后台基础模板)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
|
||||||
- [手摸手,带你封装一个vue component](https://segmentfault.com/a/1190000009090836)
|
- [手摸手,带你封装一个vue component](https://segmentfault.com/a/1190000009090836)
|
||||||
|
|
||||||
相应需求,开了一个qq群 591724180 方便大家交流
|
相应需求,开了一个qq群 `591724180` 方便大家交流
|
||||||
|
|
||||||
**如有问题请先看上述文章和Wiki,若不能满足,欢迎issue和pr~**
|
**如有问题请先看上述文章和Wiki,若不能满足,欢迎 issue 和 pr ~**
|
||||||
|
|
||||||
**该项目并不是一个脚手架,更倾向于是一个集成解决方案方案**
|
**该项目并不是一个脚手架,更倾向于是一个集成解决方案**
|
||||||
|
|
||||||
**该项目不支持低版本游览器,有需求请自行添加polyfill[详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
|
**该项目不支持低版本游览器,有需求请自行添加polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
|
||||||
|
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
@@ -182,5 +185,8 @@ Detailed changes for each release are documented in the [release notes](https://
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## [更多demo](http://panjiachen.github.io/vue-element-admin)
|
## [查看更多demo](http://panjiachen.github.io/vue-element-admin)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "juicy",
|
"name": "juicy",
|
||||||
"version": "1.0.3",
|
"version": "1.1.1",
|
||||||
"description": "A Vue.js admin",
|
"description": "A Vue.js admin",
|
||||||
"author": "Pan <panfree23@gmail.com>",
|
"author": "Pan <panfree23@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"vue-router": "2.5.3",
|
"vue-router": "2.5.3",
|
||||||
"vuedraggable": "2.13.1",
|
"vuedraggable": "2.13.1",
|
||||||
"vuex": "2.3.1",
|
"vuex": "2.3.1",
|
||||||
"xlsx": "0.8.1"
|
"xlsx": "^0.10.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "7.1.1",
|
"autoprefixer": "7.1.1",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"babel-eslint": "7.2.3",
|
"babel-eslint": "7.2.3",
|
||||||
"babel-loader": "7.0.0",
|
"babel-loader": "7.0.0",
|
||||||
"babel-plugin-transform-runtime": "6.23.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-preset-stage-2": "6.24.1",
|
||||||
"babel-register": "6.24.1",
|
"babel-register": "6.24.1",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
|
110
src/components/BackToTop/index.vue
Normal file
110
src/components/BackToTop/index.vue
Normal 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>
|
@@ -19,6 +19,7 @@ import IconSvg from 'components/Icon-svg';// svg 组件
|
|||||||
import vueWaves from './directive/waves';// 水波纹指令
|
import vueWaves from './directive/waves';// 水波纹指令
|
||||||
import errLog from 'store/errLog';// error log组件
|
import errLog from 'store/errLog';// error log组件
|
||||||
import './mock/index.js'; // 该项目所有请求使用mockjs模拟
|
import './mock/index.js'; // 该项目所有请求使用mockjs模拟
|
||||||
|
import { getToken } from 'utils/auth';
|
||||||
|
|
||||||
// register globally
|
// register globally
|
||||||
Vue.component('multiselect', Multiselect);
|
Vue.component('multiselect', Multiselect);
|
||||||
@@ -43,7 +44,7 @@ function hasPermission(roles, permissionRoles) {
|
|||||||
const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
|
const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
NProgress.start(); // 开启Progress
|
NProgress.start(); // 开启Progress
|
||||||
if (store.getters.token) { // 判断是否有token
|
if (getToken()) { // 判断是否有token
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' });
|
next({ path: '/' });
|
||||||
} else {
|
} else {
|
||||||
@@ -54,6 +55,10 @@ router.beforeEach((to, from, next) => {
|
|||||||
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
|
router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
|
||||||
next({ ...to }); // hack方法 确保addRoutes已完成
|
next({ ...to }); // hack方法 确保addRoutes已完成
|
||||||
})
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
store.dispatch('FedLogOut').then(() => {
|
||||||
|
next({ path: '/login' });
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
|
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
|
||||||
|
@@ -28,13 +28,14 @@ const Sticky = _import('components/sticky');
|
|||||||
const SplitPane = _import('components/splitpane');
|
const SplitPane = _import('components/splitpane');
|
||||||
const CountTo = _import('components/countTo');
|
const CountTo = _import('components/countTo');
|
||||||
const Mixin = _import('components/mixin');
|
const Mixin = _import('components/mixin');
|
||||||
|
const BackToTop = _import('components/backToTop')
|
||||||
|
|
||||||
/* charts */
|
/* charts */
|
||||||
const chartIndex = _import('charts/index');
|
const chartIndex = _import('charts/index');
|
||||||
const KeyboardChart = _import('charts/keyboard');
|
const KeyboardChart = _import('charts/keyboard');
|
||||||
const KeyboardChart2 = _import('charts/keyboard2');
|
const KeyboardChart2 = _import('charts/keyboard2');
|
||||||
const LineMarker = _import('charts/line');
|
const LineMarker = _import('charts/line');
|
||||||
const MixChart = _import('charts/mixchart');
|
const MixChart = _import('charts/mixChart');
|
||||||
|
|
||||||
/* error page */
|
/* error page */
|
||||||
const Err404 = _import('error/404');
|
const Err404 = _import('error/404');
|
||||||
@@ -45,11 +46,12 @@ const ErrorLog = _import('errlog/index');
|
|||||||
|
|
||||||
/* excel */
|
/* excel */
|
||||||
const ExcelDownload = _import('excel/index');
|
const ExcelDownload = _import('excel/index');
|
||||||
|
const SelectExcelDownload = _import('excel/selectExcel');
|
||||||
|
|
||||||
/* theme */
|
/* theme */
|
||||||
const Theme = _import('theme/index');
|
const Theme = _import('theme/index');
|
||||||
|
|
||||||
/* example*/
|
/* example */
|
||||||
const TableLayout = _import('example/table/index');
|
const TableLayout = _import('example/table/index');
|
||||||
const DynamicTable = _import('example/table/dynamictable');
|
const DynamicTable = _import('example/table/dynamictable');
|
||||||
const Table = _import('example/table/table');
|
const Table = _import('example/table/table');
|
||||||
@@ -129,7 +131,8 @@ export const asyncRouterMap = [
|
|||||||
{ path: 'dropzone', component: Dropzone, name: 'Dropzone' },
|
{ path: 'dropzone', component: Dropzone, name: 'Dropzone' },
|
||||||
{ path: 'sticky', component: Sticky, name: 'Sticky' },
|
{ path: 'sticky', component: Sticky, name: 'Sticky' },
|
||||||
{ path: 'countto', component: CountTo, name: 'CountTo' },
|
{ path: 'countto', component: CountTo, name: 'CountTo' },
|
||||||
{ path: 'mixin', component: Mixin, name: '小组件' }
|
{ path: 'mixin', component: Mixin, name: '小组件' },
|
||||||
|
{ path: 'backtotop', component: BackToTop, name: '返回顶部' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -172,8 +175,10 @@ export const asyncRouterMap = [
|
|||||||
redirect: 'noredirect',
|
redirect: 'noredirect',
|
||||||
name: 'excel',
|
name: 'excel',
|
||||||
icon: 'EXCEL',
|
icon: 'EXCEL',
|
||||||
noDropdown: true,
|
children: [
|
||||||
children: [{ path: 'download', component: ExcelDownload, name: '导出excel' }]
|
{ path: 'download', component: ExcelDownload, name: '导出excel' },
|
||||||
|
{ path: 'download2', component: SelectExcelDownload, name: '选择导出excel' }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/theme',
|
path: '/theme',
|
||||||
|
@@ -19,11 +19,17 @@ const app = {
|
|||||||
state.sidebar.opened = !state.sidebar.opened;
|
state.sidebar.opened = !state.sidebar.opened;
|
||||||
},
|
},
|
||||||
ADD_VISITED_VIEWS: (state, view) => {
|
ADD_VISITED_VIEWS: (state, view) => {
|
||||||
if (state.visitedViews.includes(view)) return
|
if (state.visitedViews.some(v => v.path === view.path)) return
|
||||||
state.visitedViews.push(view)
|
state.visitedViews.push({ name: view.name, path: view.path })
|
||||||
},
|
},
|
||||||
DEL_VISITED_VIEWS: (state, view) => {
|
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)
|
state.visitedViews.splice(index, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { asyncRouterMap, constantRouterMap } from 'src/router';
|
import { asyncRouterMap, constantRouterMap } from 'src/router';
|
||||||
|
import { deepClone } from 'utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过meta.role判断是否与当前用户权限匹配
|
* 通过meta.role判断是否与当前用户权限匹配
|
||||||
@@ -38,8 +39,8 @@ const permission = {
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_ROUTERS: (state, routers) => {
|
SET_ROUTERS: (state, routers) => {
|
||||||
state.addRouters = routers;
|
state.addRouters = deepClone(routers)
|
||||||
state.routers = constantRouterMap.concat(routers);
|
state.routers = deepClone(constantRouterMap.concat(routers))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { loginByEmail, logout, getInfo } from 'api/login';
|
import { loginByEmail, logout, getInfo } from 'api/login';
|
||||||
import Cookies from 'js-cookie';
|
import { getToken, setToken, removeToken } from 'utils/auth';
|
||||||
|
|
||||||
const user = {
|
const user = {
|
||||||
state: {
|
state: {
|
||||||
user: '',
|
user: '',
|
||||||
status: '',
|
status: '',
|
||||||
code: '',
|
code: '',
|
||||||
token: Cookies.get('Admin-Token'),
|
token: getToken(),
|
||||||
name: '',
|
name: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
introduction: '',
|
introduction: '',
|
||||||
@@ -56,7 +56,7 @@ const user = {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
loginByEmail(email, userInfo.password).then(response => {
|
loginByEmail(email, userInfo.password).then(response => {
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
Cookies.set('Admin-Token', response.data.token);
|
setToken(response.data.token);
|
||||||
commit('SET_TOKEN', data.token);
|
commit('SET_TOKEN', data.token);
|
||||||
resolve();
|
resolve();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -87,7 +87,7 @@ const user = {
|
|||||||
commit('SET_CODE', code);
|
commit('SET_CODE', code);
|
||||||
loginByThirdparty(state.status, state.email, state.code).then(response => {
|
loginByThirdparty(state.status, state.email, state.code).then(response => {
|
||||||
commit('SET_TOKEN', response.data.token);
|
commit('SET_TOKEN', response.data.token);
|
||||||
Cookies.set('Admin-Token', response.data.token);
|
setToken(response.data.token);
|
||||||
resolve();
|
resolve();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -101,7 +101,7 @@ const user = {
|
|||||||
logout(state.token).then(() => {
|
logout(state.token).then(() => {
|
||||||
commit('SET_TOKEN', '');
|
commit('SET_TOKEN', '');
|
||||||
commit('SET_ROLES', []);
|
commit('SET_ROLES', []);
|
||||||
Cookies.remove('Admin-Token');
|
removeToken();
|
||||||
resolve();
|
resolve();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -113,7 +113,7 @@ const user = {
|
|||||||
FedLogOut({ commit }) {
|
FedLogOut({ commit }) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
commit('SET_TOKEN', '');
|
commit('SET_TOKEN', '');
|
||||||
Cookies.remove('Admin-Token');
|
removeToken();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -123,7 +123,7 @@ const user = {
|
|||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
commit('SET_ROLES', [role]);
|
commit('SET_ROLES', [role]);
|
||||||
commit('SET_TOKEN', role);
|
commit('SET_TOKEN', role);
|
||||||
Cookies.set('Admin-Token', role);
|
setToken(role);
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
15
src/utils/auth.js
Normal file
15
src/utils/auth.js
Normal 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)
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Message } from 'element-ui';
|
import { Message } from 'element-ui';
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
// import router from '../router';
|
import { getToken } from 'utils/auth';
|
||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
@@ -13,7 +13,7 @@ const service = axios.create({
|
|||||||
service.interceptors.request.use(config => {
|
service.interceptors.request.use(config => {
|
||||||
// Do something before request is sent
|
// Do something before request is sent
|
||||||
if (store.getters.token) {
|
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;
|
return config;
|
||||||
}, error => {
|
}, error => {
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
155
src/views/components/backToTop.vue
Normal file
155
src/views/components/backToTop.vue
Normal 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>
|
@@ -5,7 +5,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import echarts from 'echarts';
|
import echarts from 'echarts';
|
||||||
require('echarts/theme/macarons'); // echarts 主题
|
require('echarts/theme/macarons'); // echarts 主题
|
||||||
|
const animationDuration = 3000;
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
className: {
|
className: {
|
||||||
@@ -68,19 +68,22 @@
|
|||||||
type: 'bar',
|
type: 'bar',
|
||||||
stack: 'vistors',
|
stack: 'vistors',
|
||||||
barWidth: '60%',
|
barWidth: '60%',
|
||||||
data: [79, 52, 200, 334, 390, 330, 220]
|
data: [79, 52, 200, 334, 390, 330, 220],
|
||||||
|
animationDuration
|
||||||
}, {
|
}, {
|
||||||
name: 'pageB',
|
name: 'pageB',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
stack: 'vistors',
|
stack: 'vistors',
|
||||||
barWidth: '60%',
|
barWidth: '60%',
|
||||||
data: [80, 52, 200, 334, 390, 330, 220]
|
data: [80, 52, 200, 334, 390, 330, 220],
|
||||||
|
animationDuration
|
||||||
}, {
|
}, {
|
||||||
name: 'pageC',
|
name: 'pageC',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
stack: 'vistors',
|
stack: 'vistors',
|
||||||
barWidth: '60%',
|
barWidth: '60%',
|
||||||
data: [30, 52, 200, 334, 390, 330, 220]
|
data: [30, 52, 200, 334, 390, 330, 220],
|
||||||
|
animationDuration
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '300px'
|
default: '350px'
|
||||||
},
|
},
|
||||||
autoResize: {
|
autoResize: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -39,6 +39,10 @@
|
|||||||
}, 100)
|
}, 100)
|
||||||
window.addEventListener('resize', this.__resizeHanlder)
|
window.addEventListener('resize', this.__resizeHanlder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听侧边栏的变化
|
||||||
|
const sidebarElm = document.getElementsByClassName('sidebar-container')[0]
|
||||||
|
sidebarElm.addEventListener('transitionend', this.__resizeHanlder)
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (!this.chart) {
|
if (!this.chart) {
|
||||||
@@ -47,8 +51,12 @@
|
|||||||
if (this.autoResize) {
|
if (this.autoResize) {
|
||||||
window.removeEventListener('resize', this.__resizeHanlder)
|
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: {
|
methods: {
|
||||||
initChart() {
|
initChart() {
|
||||||
@@ -82,7 +90,9 @@
|
|||||||
},
|
},
|
||||||
smooth: true,
|
smooth: true,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: [100, 120, 161, 134, 105, 160, 165]
|
data: [100, 120, 161, 134, 105, 160, 165],
|
||||||
|
animationDuration: 2600,
|
||||||
|
animationEasing: 'cubicInOut'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'buyers',
|
name: 'buyers',
|
||||||
@@ -99,7 +109,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: [120, 82, 91, 154, 162, 140, 130]
|
data: [120, 82, 91, 154, 162, 140, 130],
|
||||||
|
animationDuration: 2000,
|
||||||
|
animationEasing: 'quadraticOut'
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -67,7 +67,9 @@
|
|||||||
{ value: 100, name: 'gold' },
|
{ value: 100, name: 'gold' },
|
||||||
{ value: 59, name: 'forecastsx' },
|
{ value: 59, name: 'forecastsx' },
|
||||||
{ value: 49, name: 'markets' }
|
{ value: 49, name: 'markets' }
|
||||||
]
|
],
|
||||||
|
animationEasing: 'cubicInOut',
|
||||||
|
animationDuration: 2600
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
84
src/views/excel/selectExcel.vue
Normal file
84
src/views/excel/selectExcel.vue
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<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() {
|
||||||
|
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();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formatJson(filterVal, jsonData) {
|
||||||
|
return jsonData.map(v => filterVal.map(j => v[j]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@@ -8,7 +8,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
<el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
|
<el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
|
||||||
<template slot="title">
|
<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>{{item.name}}
|
||||||
</template>
|
</template>
|
||||||
<template v-for="child in item.children" v-if='!child.hidden'>
|
<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>
|
<sidebar-item class='menu-indent' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
margin-right: 10px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hideSidebar .menu-indent {
|
.hideSidebar .menu-indent {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class='tabs-view-container'>
|
<div class='tabs-view-container'>
|
||||||
<router-link class="tabs-view" v-for="tag in Array.from(visitedViews)" :to="tag.path" :key="tag.path">
|
<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}}
|
{{tag.name}}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -29,6 +29,9 @@
|
|||||||
},
|
},
|
||||||
addViewTabs() {
|
addViewTabs() {
|
||||||
this.$store.dispatch('addVisitedViews', this.generateRoute())
|
this.$store.dispatch('addVisitedViews', this.generateRoute())
|
||||||
|
},
|
||||||
|
isActive(path) {
|
||||||
|
return path === this.$route.path
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@@ -77,8 +77,7 @@
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.$router.push({ path: '/' });
|
this.$router.push({ path: '/' });
|
||||||
// this.showDialog = true;
|
// this.showDialog = true;
|
||||||
}).catch(err => {
|
}).catch(() => {
|
||||||
this.$message.error(err);
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
Reference in New Issue
Block a user