├── .eslintignore ├── favicon.ico ├── gifs ├── tabs.gif ├── 2login.gif ├── editor.gif ├── excel.png ├── login.png ├── order.gif ├── table.gif ├── theme.gif ├── echarts.gif ├── errorlog.gif ├── leftmenu.gif ├── upload1.gif ├── dynamictable.gif └── uploadAvatar.gif ├── src ├── views │ ├── example │ │ ├── table │ │ │ ├── index.vue │ │ │ ├── dynamictable │ │ │ │ ├── index.vue │ │ │ │ ├── unfixedThead.vue │ │ │ │ └── fixedThead.vue │ │ │ └── inlineEditTable.vue │ │ └── tab │ │ │ ├── index.vue │ │ │ └── components │ │ │ └── tabPane.vue │ ├── errlog │ │ ├── errcode.vue │ │ └── index.vue │ ├── svg-icons │ │ ├── generateIconsView.js │ │ └── index.vue │ ├── layout │ │ ├── index.js │ │ ├── AppMain.vue │ │ ├── Sidebar.vue │ │ ├── Levelbar.vue │ │ ├── Layout.vue │ │ ├── SidebarItem.vue │ │ ├── TabsView.vue │ │ └── Navbar.vue │ ├── login │ │ ├── authredirect.vue │ │ └── socialsignin.vue │ ├── components │ │ ├── index.vue │ │ ├── tinymce.vue │ │ ├── dropzone.vue │ │ ├── dndList.vue │ │ ├── markdown.vue │ │ ├── mixin.vue │ │ ├── jsonEditor.vue │ │ ├── avatarUpload.vue │ │ ├── splitpane.vue │ │ └── sticky.vue │ ├── charts │ │ ├── index.vue │ │ ├── line.vue │ │ ├── keyboard.vue │ │ ├── keyboard2.vue │ │ └── mixChart.vue │ ├── dashboard │ │ ├── index.vue │ │ ├── editor │ │ │ └── index.vue │ │ └── admin │ │ │ ├── pieChart.vue │ │ │ ├── barChart.vue │ │ │ └── lineChart.vue │ ├── permission │ │ └── index.vue │ ├── excel │ │ ├── uploadExcel.vue │ │ ├── index.vue │ │ └── selectExcel.vue │ ├── qiniu │ │ └── upload.vue │ ├── introduction │ │ └── index.vue │ ├── errorPage │ │ └── 401.vue │ └── theme │ │ └── index.vue ├── router │ ├── _import_production.js │ └── _import_development.js ├── assets │ ├── 401_images │ │ └── 401.gif │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ └── custom-theme │ │ └── fonts │ │ ├── element-icons.ttf │ │ └── element-icons.woff ├── api │ ├── qiniu.js │ ├── remoteSearch.js │ ├── article.js │ └── login.js ├── store │ ├── errLog.js │ ├── index.js │ ├── getters.js │ └── modules │ │ ├── app.js │ │ ├── permission.js │ │ └── user.js ├── utils │ ├── createUniqueString.js │ ├── auth.js │ ├── validate.js │ ├── openWindow.js │ └── fetch.js ├── App.vue ├── errorLog.js ├── components │ ├── Icon-svg │ │ └── index.vue │ ├── SplitPane │ │ ├── Pane.vue │ │ ├── Resizer.vue │ │ └── index.vue │ ├── ImageCropper │ │ ├── lang.js │ │ └── utils.js │ ├── Github │ │ └── index.vue │ ├── jsonEditor │ │ └── index.vue │ ├── TodoList │ │ ├── Todo.vue │ │ └── index.vue │ ├── Sticky │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── UploadExcel │ │ └── index.vue │ ├── ErrLog │ │ └── index.vue │ ├── MarkdownEditor │ │ └── index.vue │ ├── Screenfull │ │ └── index.vue │ ├── Charts │ │ ├── keyboard.vue │ │ └── keyboard2.vue │ ├── Upload │ │ ├── singleImage2.vue │ │ ├── singleImage.vue │ │ └── singleImage3.vue │ ├── BackToTop │ │ └── index.vue │ ├── PanThumb │ │ └── index.vue │ └── Tinymce │ │ └── components │ │ └── editorImage.vue ├── icons │ ├── svg │ │ ├── zujian.svg │ │ ├── tubiao.svg │ │ ├── tab.svg │ │ ├── from.svg │ │ ├── shouce.svg │ │ ├── c.svg │ │ ├── a.svg │ │ ├── b.svg │ │ ├── email.svg │ │ ├── yonghuming.svg │ │ ├── EXCEL.svg │ │ ├── tuozhuai.svg │ │ ├── icons.svg │ │ ├── theme.svg │ │ ├── zonghe.svg │ │ ├── quanxian.svg │ │ ├── mima.svg │ │ ├── wujiaoxing.svg │ │ ├── 404.svg │ │ ├── bug.svg │ │ ├── xinrenzhinan.svg │ │ ├── table.svg │ │ ├── yanjing.svg │ │ └── weixin.svg │ └── index.js ├── mock │ ├── remoteSearch.js │ ├── index.js │ ├── login.js │ └── article.js ├── main.js ├── directive │ ├── waves.css │ ├── waves.js │ └── sticky.js ├── styles │ ├── element-ui.scss │ ├── mixin.scss │ ├── sidebar.scss │ └── btn.scss ├── permission.js └── filters │ └── index.js ├── static └── tinymce │ └── skins │ └── lightgray │ ├── img │ ├── anchor.gif │ ├── loader.gif │ ├── object.gif │ └── trans.gif │ ├── fonts │ ├── tinymce.eot │ ├── tinymce.ttf │ ├── tinymce.woff │ ├── tinymce-small.eot │ ├── tinymce-small.ttf │ └── tinymce-small.woff │ └── content.inline.min.css ├── .babelrc ├── config ├── dev.env.js ├── prod.env.js ├── sit.env.js └── index.js ├── .gitignore ├── .postcssrc.js ├── .editorconfig ├── index.html ├── LICENSE └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/assets 4 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/favicon.ico -------------------------------------------------------------------------------- /gifs/tabs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/tabs.gif -------------------------------------------------------------------------------- /gifs/2login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/2login.gif -------------------------------------------------------------------------------- /gifs/editor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/editor.gif -------------------------------------------------------------------------------- /gifs/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/excel.png -------------------------------------------------------------------------------- /gifs/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/login.png -------------------------------------------------------------------------------- /gifs/order.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/order.gif -------------------------------------------------------------------------------- /gifs/table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/table.gif -------------------------------------------------------------------------------- /gifs/theme.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/theme.gif -------------------------------------------------------------------------------- /src/views/example/table/index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /gifs/echarts.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/echarts.gif -------------------------------------------------------------------------------- /gifs/errorlog.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/errorlog.gif -------------------------------------------------------------------------------- /gifs/leftmenu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/leftmenu.gif -------------------------------------------------------------------------------- /gifs/upload1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/upload1.gif -------------------------------------------------------------------------------- /src/router/_import_production.js: -------------------------------------------------------------------------------- 1 | module.exports = file => () => import('@/views/' + file + '.vue') 2 | -------------------------------------------------------------------------------- /gifs/dynamictable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/dynamictable.gif -------------------------------------------------------------------------------- /gifs/uploadAvatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/gifs/uploadAvatar.gif -------------------------------------------------------------------------------- /src/assets/401_images/401.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/src/assets/401_images/401.gif -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/router/_import_development.js: -------------------------------------------------------------------------------- 1 | module.exports = file => require('@/views/' + file + '.vue').default // vue-loader at least v13.0.0+ 2 | -------------------------------------------------------------------------------- /src/views/errlog/errcode.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/anchor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/img/anchor.gif -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/img/loader.gif -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/object.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/img/object.gif -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/img/trans.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/img/trans.gif -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/src/assets/custom-theme/fonts/element-icons.ttf -------------------------------------------------------------------------------- /src/assets/custom-theme/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/src/assets/custom-theme/fonts/element-icons.woff -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/fonts/tinymce.eot -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/fonts/tinymce.ttf -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/fonts/tinymce.woff -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false 8 | } 9 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"development"', 3 | BASE_API: '"https://api-dev"', 4 | APP_ORIGIN: '"https://wallstreetcn.com"' 5 | } 6 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | BASE_API: '"https://api-prod"', 4 | APP_ORIGIN: '"https://wallstreetcn.com"' 5 | }; 6 | -------------------------------------------------------------------------------- /config/sit.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | BASE_API: '"https://api-sit"', 4 | APP_ORIGIN: '"https://wallstreetcn.com"' 5 | }; 6 | -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce-small.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/fonts/tinymce-small.eot -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce-small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/fonts/tinymce-small.ttf -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/fonts/tinymce-small.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calamus0427/vue-element-admin/master/static/tinymce/skins/lightgray/fonts/tinymce-small.woff -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | static/ckeditor 5 | gifs/ 6 | npm-debug.log 7 | test/unit/coverage 8 | test/e2e/reports 9 | selenium-debug.log 10 | .idea 11 | -------------------------------------------------------------------------------- /src/api/qiniu.js: -------------------------------------------------------------------------------- 1 | import fetch from '@/utils/fetch' 2 | 3 | export function getToken() { 4 | return fetch({ 5 | url: '/qiniu/upload/token', // 假地址 自行替换 6 | method: 'get' 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/api/remoteSearch.js: -------------------------------------------------------------------------------- 1 | import fetch from '@/utils/fetch' 2 | 3 | export function userSearch(name) { 4 | return fetch({ 5 | url: '/search/user', 6 | method: 'get', 7 | params: { name } 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/views/svg-icons/generateIconsView.js: -------------------------------------------------------------------------------- 1 | const data = { 2 | state: { 3 | iconsMap: [] 4 | }, 5 | generate(iconsMap) { 6 | this.state.iconsMap = iconsMap 7 | } 8 | } 9 | 10 | export default data 11 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/views/layout/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | 3 | export { default as Sidebar } from './Sidebar' 4 | 5 | export { default as Levelbar } from './Levelbar' 6 | 7 | export { default as AppMain } from './AppMain' 8 | -------------------------------------------------------------------------------- /src/store/errLog.js: -------------------------------------------------------------------------------- 1 | const errLog = { 2 | state: { 3 | errLog: [] 4 | }, 5 | pushLog(log) { 6 | this.state.errLog.unshift(log) 7 | }, 8 | clearLog() { 9 | this.state.errLog = [] 10 | } 11 | } 12 | 13 | export default errLog 14 | -------------------------------------------------------------------------------- /src/utils/createUniqueString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 17/3/8. 3 | */ 4 | export default function createUniqueString() { 5 | const timestamp = +new Date() + '' 6 | const randomNum = parseInt((1 + Math.random()) * 65536) + '' 7 | return (+(randomNum + timestamp)).toString(32) 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/views/login/authredirect.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'Admin-Token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/errorLog.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import errLog from '@/store/errLog' 3 | 4 | // 生产环境错误日志 5 | if (process.env.NODE_ENV === 'production') { 6 | Vue.config.errorHandler = function(err, vm) { 7 | console.log(err, window.location.href) 8 | errLog.pushLog({ 9 | err, 10 | url: window.location.href, 11 | vm 12 | }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/views/components/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import user from './modules/user' 5 | import permission from './modules/permission' 6 | import getters from './getters' 7 | 8 | Vue.use(Vuex) 9 | 10 | const store = new Vuex.Store({ 11 | modules: { 12 | app, 13 | user, 14 | permission 15 | }, 16 | getters 17 | }) 18 | 19 | export default store 20 | -------------------------------------------------------------------------------- /src/views/charts/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/components/Icon-svg/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /src/views/layout/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /src/api/article.js: -------------------------------------------------------------------------------- 1 | import fetch from '@/utils/fetch' 2 | 3 | export function fetchList(query) { 4 | return fetch({ 5 | url: '/article/list', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function fetchArticle() { 12 | return fetch({ 13 | url: '/article/detail', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function fetchPv(pv) { 19 | return fetch({ 20 | url: '/article/pv', 21 | method: 'get', 22 | params: { pv } 23 | }) 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/icons/svg/zujian.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/example/table/dynamictable/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Juicy 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/views/charts/line.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import IconSvg from '@/components/Icon-svg'// svg组件 3 | import generateIconsView from '@/views/svg-icons/generateIconsView.js'// just for views/icons , you can delete it 4 | // register globally 5 | 6 | Vue.component('icon-svg', IconSvg) 7 | 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | const req = require.context('./svg', false, /\.svg$/) 10 | const iconMap = requireAll(req) 11 | 12 | generateIconsView.generate(iconMap) // just for views/icons , you can delete it 13 | -------------------------------------------------------------------------------- /src/icons/svg/tubiao.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/charts/keyboard.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/charts/keyboard2.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/charts/mixChart.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 25 | 26 | -------------------------------------------------------------------------------- /src/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import fetch from '@/utils/fetch' 2 | 3 | export function loginByUsername(username, password) { 4 | const data = { 5 | username, 6 | password 7 | } 8 | return fetch({ 9 | url: '/login/login', 10 | method: 'post', 11 | data 12 | }) 13 | } 14 | 15 | export function logout() { 16 | return fetch({ 17 | url: '/login/logout', 18 | method: 'post' 19 | }) 20 | } 21 | 22 | export function getUserInfo(token) { 23 | return fetch({ 24 | url: '/user/info', 25 | method: 'get', 26 | params: { token } 27 | }) 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | visitedViews: state => state.app.visitedViews, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name, 7 | introduction: state => state.user.introduction, 8 | status: state => state.user.status, 9 | roles: state => state.user.roles, 10 | setting: state => state.user.setting, 11 | permission_routers: state => state.permission.routers, 12 | addRouters: state => state.permission.addRouters 13 | } 14 | export default getters 15 | -------------------------------------------------------------------------------- /src/icons/svg/from.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/shouce.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/errlog/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /src/views/layout/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 24 | -------------------------------------------------------------------------------- /src/icons/svg/c.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/a.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/b.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/mock/remoteSearch.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { param2Obj } from '@/utils' 3 | 4 | const NameList = [] 5 | const count = 100 6 | 7 | for (let i = 0; i < count; i++) { 8 | NameList.push(Mock.mock({ 9 | name: '@first' 10 | })) 11 | } 12 | NameList.push({ name: 'mockPan' }) 13 | 14 | export default { 15 | searchUser: config => { 16 | const { name } = param2Obj(config.url) 17 | const mockNameList = NameList.filter(item => { 18 | const lowerCaseName = item.name.toLowerCase() 19 | if (name && lowerCaseName.indexOf(name.toLowerCase()) < 0) return false 20 | return true 21 | }) 22 | return { items: mockNameList } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import loginAPI from './login' 3 | import articleAPI from './article' 4 | import remoteSearchAPI from './remoteSearch' 5 | 6 | Mock.setup({ 7 | timeout: '350-600' 8 | }) 9 | 10 | // 登录相关 11 | Mock.mock(/\/login\/login/, 'post', loginAPI.loginByUsername) 12 | Mock.mock(/\/login\/logout/, 'post', loginAPI.logout) 13 | Mock.mock(/\/user\/info\.*/, 'get', loginAPI.getUserInfo) 14 | 15 | // 文章相关 16 | Mock.mock(/\/article\/list/, 'get', articleAPI.getList) 17 | Mock.mock(/\/article\/detail/, 'get', articleAPI.getArticle) 18 | Mock.mock(/\/article\/pv/, 'get', articleAPI.getPv) 19 | 20 | // 搜索相关 21 | Mock.mock(/\/search\/user/, 'get', remoteSearchAPI.searchUser) 22 | 23 | export default Mock 24 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import ElementUI from 'element-ui' 3 | import 'element-ui/lib/theme-default/index.css' 4 | import App from './App' 5 | import router from './router' 6 | import store from './store' 7 | import * as filters from './filters' // 全局filter 8 | import './icons' // icon 9 | import './errorLog'// error log 10 | import './permission' // 权限 11 | import './mock' // 该项目所有请求使用mockjs模拟 12 | 13 | Vue.use(ElementUI) 14 | 15 | // register global utility filters. 16 | Object.keys(filters).forEach(key => { 17 | Vue.filter(key, filters[key]) 18 | }) 19 | 20 | Vue.config.productionTip = false 21 | 22 | new Vue({ 23 | el: '#app', 24 | router, 25 | store, 26 | template: '', 27 | components: { App } 28 | }) 29 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | -------------------------------------------------------------------------------- /src/views/permission/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 34 | -------------------------------------------------------------------------------- /src/icons/svg/yonghuming.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/excel/uploadExcel.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | -------------------------------------------------------------------------------- /src/views/components/tinymce.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/icons/svg/EXCEL.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/SplitPane/Pane.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 45 | -------------------------------------------------------------------------------- /src/views/components/dropzone.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/directive/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /src/icons/svg/tuozhuai.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/components/dndList.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/icons/svg/icons.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/zonghe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function isvalidUsername(str) { 6 | const valid_map = ['admin', 'editor'] 7 | return valid_map.indexOf(str.trim()) >= 0 8 | } 9 | 10 | /* 合法uri*/ 11 | export function validateURL(textval) { 12 | const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 13 | return urlregex.test(textval) 14 | } 15 | 16 | /* 小写字母*/ 17 | export function validateLowerCase(str) { 18 | const reg = /^[a-z]+$/ 19 | return reg.test(str) 20 | } 21 | 22 | /* 大写字母*/ 23 | export function validateUpperCase(str) { 24 | const reg = /^[A-Z]+$/ 25 | return reg.test(str) 26 | } 27 | 28 | /* 大小写字母*/ 29 | export function validatAlphabets(str) { 30 | const reg = /^[A-Za-z]+$/ 31 | return reg.test(str) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/icons/svg/quanxian.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ImageCropper/lang.js: -------------------------------------------------------------------------------- 1 | const langBag = { 2 | zh: { 3 | hint: '点击,或拖动图片至此处', 4 | loading: '正在上传……', 5 | noSupported: '浏览器不支持该功能,请使用IE10以上或其他现在浏览器!', 6 | success: '上传成功', 7 | fail: '图片上传失败', 8 | preview: '头像预览', 9 | btn: { 10 | off: '取消', 11 | close: '关闭', 12 | back: '上一步', 13 | save: '保存' 14 | }, 15 | error: { 16 | onlyImg: '仅限图片格式', 17 | outOfSize: '单文件大小不能超过 ', 18 | lowestPx: '图片最低像素为(宽*高):' 19 | } 20 | }, 21 | en: { 22 | hint: 'Click, or drag the file here', 23 | loading: 'Uploading……', 24 | noSupported: 'Browser does not support, please use IE10+ or other browsers', 25 | success: 'Upload success', 26 | fail: 'Upload failed', 27 | preview: 'Preview', 28 | btn: { 29 | off: 'Cancel', 30 | close: 'Close', 31 | back: 'Back', 32 | save: 'Save' 33 | }, 34 | error: { 35 | onlyImg: 'Image only', 36 | outOfSize: 'Image exceeds size limit: ', 37 | lowestPx: 'The lowest pixel in the image: ' 38 | } 39 | } 40 | } 41 | export default langBag 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/mock/login.js: -------------------------------------------------------------------------------- 1 | import { param2Obj } from '@/utils' 2 | 3 | const userMap = { 4 | admin: { 5 | role: ['admin'], 6 | token: 'admin', 7 | introduction: '我是超级管理员', 8 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 9 | name: 'Super Admin' 10 | }, 11 | editor: { 12 | role: ['editor'], 13 | token: 'editor', 14 | introduction: '我是编辑', 15 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 16 | name: 'Normal Editor' 17 | }, 18 | developer: { 19 | role: ['develop'], 20 | token: 'develop', 21 | introduction: '我是开发', 22 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 23 | name: '工程师小王' 24 | } 25 | } 26 | 27 | export default { 28 | loginByUsername: config => { 29 | const { username } = JSON.parse(config.body) 30 | return userMap[username] 31 | }, 32 | getUserInfo: config => { 33 | const { token } = param2Obj(config.url) 34 | if (userMap[token]) { 35 | return userMap[token] 36 | } else { 37 | return Promise.reject('error') 38 | } 39 | }, 40 | logout: () => 'success' 41 | } 42 | -------------------------------------------------------------------------------- /src/views/qiniu/upload.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 41 | -------------------------------------------------------------------------------- /src/icons/svg/mima.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/wujiaoxing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/components/markdown.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/example/tab/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /src/views/components/mixin.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 41 | 42 | 50 | -------------------------------------------------------------------------------- /src/utils/openWindow.js: -------------------------------------------------------------------------------- 1 | /** 2 | *Created by jiachenpan on 16/11/29. 3 | * @param {Sting} url 4 | * @param {Sting} title 5 | * @param {Number} w 6 | * @param {Number} h 7 | */ 8 | 9 | export default function openWindow(url, title, w, h) { 10 | // Fixes dual-screen position Most browsers Firefox 11 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 12 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 13 | 14 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width 15 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height 16 | 17 | const left = ((width / 2) - (w / 2)) + dualScreenLeft 18 | const top = ((height / 2) - (h / 2)) + dualScreenTop 19 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left) 20 | 21 | // Puts focus on the newWindow 22 | if (window.focus) { 23 | newWindow.focus() 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/components/Github/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/example/table/dynamictable/unfixedThead.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /src/views/layout/Levelbar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 37 | 38 | 50 | -------------------------------------------------------------------------------- /src/views/introduction/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 27 | -------------------------------------------------------------------------------- /src/views/svg-icons/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 59 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const app = { 4 | state: { 5 | sidebar: { 6 | opened: !+Cookies.get('sidebarStatus') 7 | }, 8 | visitedViews: [] 9 | }, 10 | mutations: { 11 | TOGGLE_SIDEBAR: state => { 12 | if (state.sidebar.opened) { 13 | Cookies.set('sidebarStatus', 1) 14 | } else { 15 | Cookies.set('sidebarStatus', 0) 16 | } 17 | state.sidebar.opened = !state.sidebar.opened 18 | }, 19 | ADD_VISITED_VIEWS: (state, view) => { 20 | if (state.visitedViews.some(v => v.path === view.path)) return 21 | state.visitedViews.push({ name: view.name, path: view.path }) 22 | }, 23 | DEL_VISITED_VIEWS: (state, view) => { 24 | let index 25 | for (const [i, v] of state.visitedViews.entries()) { 26 | if (v.path === view.path) { 27 | index = i 28 | break 29 | } 30 | } 31 | state.visitedViews.splice(index, 1) 32 | } 33 | }, 34 | actions: { 35 | ToggleSideBar({ commit }) { 36 | commit('TOGGLE_SIDEBAR') 37 | }, 38 | addVisitedViews({ commit }, view) { 39 | commit('ADD_VISITED_VIEWS', view) 40 | }, 41 | delVisitedViews({ commit, state }, view) { 42 | return new Promise((resolve) => { 43 | commit('DEL_VISITED_VIEWS', view) 44 | resolve([...state.visitedViews]) 45 | }) 46 | } 47 | } 48 | } 49 | 50 | export default app 51 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 28 | 29 | 64 | -------------------------------------------------------------------------------- /src/views/components/jsonEditor.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/layout/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 43 | 44 | -------------------------------------------------------------------------------- /src/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | //覆盖一些element-ui样式 2 | .block-checkbox { 3 | display: block; 4 | } 5 | 6 | .operation-container { 7 | .cell { 8 | padding: 10px !important; 9 | } 10 | .el-button { 11 | &:nth-child(3) { 12 | margin-top: 10px; 13 | margin-left: 0px; 14 | } 15 | &:nth-child(4) { 16 | margin-top: 10px; 17 | } 18 | } 19 | } 20 | 21 | .el-upload { 22 | input[type="file"] { 23 | display: none !important; 24 | } 25 | } 26 | 27 | .el-upload__input { 28 | display: none; 29 | } 30 | 31 | .cell { 32 | .el-tag { 33 | margin-right: 8px; 34 | } 35 | } 36 | 37 | .small-padding { 38 | .cell { 39 | padding-left: 8px; 40 | padding-right: 8px; 41 | } 42 | } 43 | 44 | .status-col { 45 | .cell { 46 | padding: 0 10px; 47 | text-align: center; 48 | .el-tag { 49 | margin-right: 0px; 50 | } 51 | } 52 | } 53 | 54 | //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461 55 | .el-dialog { 56 | transform: none; 57 | left: 0; 58 | position: relative; 59 | margin: 0 auto; 60 | } 61 | 62 | //文章页textarea修改样式 63 | .article-textarea { 64 | textarea { 65 | padding-right: 40px; 66 | resize: none; 67 | border: none; 68 | border-radius: 0px; 69 | border-bottom: 1px solid #bfcbd9; 70 | } 71 | } 72 | 73 | //element ui upload 74 | .upload-container { 75 | .el-upload { 76 | width: 100%; 77 | .el-upload-dragger { 78 | width: 100%; 79 | height: 200px; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | &::-webkit-scrollbar { 14 | width: 6px; 15 | } 16 | &::-webkit-scrollbar-thumb { 17 | background: #99a9bf; 18 | border-radius: 20px; 19 | } 20 | } 21 | 22 | @mixin relative { 23 | position: relative; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | @mixin pct($pct) { 29 | width: #{$pct}; 30 | position: relative; 31 | margin: 0 auto; 32 | } 33 | 34 | @mixin triangle($width, $height, $color, $direction) { 35 | $width: $width/2; 36 | $color-border-style: $height solid $color; 37 | $transparent-border-style: $width solid transparent; 38 | height: 0; 39 | width: 0; 40 | @if $direction==up { 41 | border-bottom: $color-border-style; 42 | border-left: $transparent-border-style; 43 | border-right: $transparent-border-style; 44 | } 45 | @else if $direction==right { 46 | border-left: $color-border-style; 47 | border-top: $transparent-border-style; 48 | border-bottom: $transparent-border-style; 49 | } 50 | @else if $direction==down { 51 | border-top: $color-border-style; 52 | border-left: $transparent-border-style; 53 | border-right: $transparent-border-style; 54 | } 55 | @else if $direction==left { 56 | border-right: $color-border-style; 57 | border-top: $transparent-border-style; 58 | border-bottom: $transparent-border-style; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/views/components/avatarUpload.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | 42 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/components/splitpane.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 37 | 38 | 66 | -------------------------------------------------------------------------------- /src/components/jsonEditor/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 55 | 56 | 65 | -------------------------------------------------------------------------------- /src/directive/waves.js: -------------------------------------------------------------------------------- 1 | import './waves.css' 2 | 3 | export default{ 4 | bind(el, binding) { 5 | el.addEventListener('click', e => { 6 | const customOpts = Object.assign({}, binding.value) 7 | const opts = Object.assign({ 8 | ele: el, // 波纹作用元素 9 | type: 'hit', // hit点击位置扩散center中心点扩展 10 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 11 | }, customOpts) 12 | const target = opts.ele 13 | if (target) { 14 | target.style.position = 'relative' 15 | target.style.overflow = 'hidden' 16 | const rect = target.getBoundingClientRect() 17 | let ripple = target.querySelector('.waves-ripple') 18 | if (!ripple) { 19 | ripple = document.createElement('span') 20 | ripple.className = 'waves-ripple' 21 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' 22 | target.appendChild(ripple) 23 | } else { 24 | ripple.className = 'waves-ripple' 25 | } 26 | switch (opts.type) { 27 | case 'center': 28 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px' 29 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px' 30 | break 31 | default: 32 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px' 33 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px' 34 | } 35 | ripple.style.backgroundColor = opts.color 36 | ripple.className = 'waves-ripple z-active' 37 | return false 38 | } 39 | }, false) 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRouterMap, constantRouterMap } from '@/router' 2 | 3 | /** 4 | * 通过meta.role判断是否与当前用户权限匹配 5 | * @param roles 6 | * @param route 7 | */ 8 | function hasPermission(roles, route) { 9 | if (route.meta && route.meta.role) { 10 | return roles.some(role => route.meta.role.indexOf(role) >= 0) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | /** 17 | * 递归过滤异步路由表,返回符合用户角色权限的路由表 18 | * @param asyncRouterMap 19 | * @param roles 20 | */ 21 | function filterAsyncRouter(asyncRouterMap, roles) { 22 | const accessedRouters = asyncRouterMap.filter(route => { 23 | if (hasPermission(roles, route)) { 24 | if (route.children && route.children.length) { 25 | route.children = filterAsyncRouter(route.children, roles) 26 | } 27 | return true 28 | } 29 | return false 30 | }) 31 | return accessedRouters 32 | } 33 | 34 | const permission = { 35 | state: { 36 | routers: constantRouterMap, 37 | addRouters: [] 38 | }, 39 | mutations: { 40 | SET_ROUTERS: (state, routers) => { 41 | state.addRouters = routers 42 | state.routers = constantRouterMap.concat(routers) 43 | } 44 | }, 45 | actions: { 46 | GenerateRoutes({ commit }, data) { 47 | return new Promise(resolve => { 48 | const { roles } = data 49 | let accessedRouters 50 | if (roles.indexOf('admin') >= 0) { 51 | accessedRouters = asyncRouterMap 52 | } else { 53 | accessedRouters = filterAsyncRouter(asyncRouterMap, roles) 54 | } 55 | commit('SET_ROUTERS', accessedRouters) 56 | resolve() 57 | }) 58 | } 59 | } 60 | } 61 | 62 | export default permission 63 | -------------------------------------------------------------------------------- /src/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/xinrenzhinan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | sitEnv: require('./sit.env'), 7 | prodEnv: require('./prod.env'), 8 | index: path.resolve(__dirname, '../dist/index.html'), 9 | assetsRoot: path.resolve(__dirname, '../dist'), 10 | assetsSubDirectory: 'static', 11 | assetsPublicPath: './', //请根据自己路径配置更改 12 | productionSourceMap: false, 13 | // Gzip off by default as many popular static hosts such as 14 | // Surge or Netlify already gzip all static assets for you. 15 | // Before setting to `true`, make sure to: 16 | // npm install --save-dev compression-webpack-plugin 17 | productionGzip: false, 18 | productionGzipExtensions: ['js', 'css'], 19 | // Run the build command with an extra argument to 20 | // View the bundle analyzer report after build finishes: 21 | // `npm run build --report` 22 | // Set to `true` or `false` to always turn it on or off 23 | bundleAnalyzerReport: process.env.npm_config_report 24 | }, 25 | dev: { 26 | env: require('./dev.env'), 27 | port: 9527, 28 | autoOpenBrowser: true, 29 | assetsSubDirectory: 'static', 30 | assetsPublicPath: '/', 31 | proxyTable: {}, 32 | // CSS Sourcemaps off by default because relative paths are "buggy" 33 | // with this option, according to the CSS-Loader README 34 | // (https://github.com/webpack/css-loader#sourcemaps) 35 | // In our experience, they generally work as expected, 36 | // just be aware of this issue when enabling this option. 37 | cssSourceMap: false 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/SplitPane/Resizer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | 28 | 73 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/example/table/dynamictable/fixedThead.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/TodoList/Todo.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 71 | -------------------------------------------------------------------------------- /src/components/Sticky/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 75 | -------------------------------------------------------------------------------- /src/icons/svg/yanjing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/layout/TabsView.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 53 | 54 | 64 | -------------------------------------------------------------------------------- /src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | // 侧边栏 2 | .sidebar-container>.el-menu { 3 | width: 100%!important; 4 | min-height: 100%; 5 | } 6 | 7 | .sidebar-container .svg-icon { 8 | margin-right: 16px; 9 | } 10 | 11 | .hideSidebar .el-submenu>.el-submenu__title, 12 | .hideSidebar .submenu-title-noDropdown { 13 | padding-left: 10px!important; 14 | } 15 | 16 | .hideSidebar .submenu-title-noDropdown span, 17 | .hideSidebar .el-submenu>.el-submenu__title>span { 18 | height: 0; 19 | width: 0; 20 | overflow: hidden; 21 | visibility: hidden; 22 | display: inline-block; 23 | } 24 | 25 | .hideSidebar .nest-menu .el-submenu__title { 26 | text-align: initial!important; 27 | padding-left: 20px!important; 28 | span { 29 | height: auto; 30 | width: auto; 31 | visibility: visible; 32 | display: inline; 33 | } 34 | .el-submenu__icon-arrow { 35 | display: block!important; 36 | } 37 | } 38 | 39 | .hideSidebar .menu-wrapper>.el-menu-item, 40 | .hideSidebar .submenu-title-noDropdown, 41 | .hideSidebar .menu-wrapper>.el-submenu .el-submenu__title { 42 | text-align: center; 43 | } 44 | 45 | .hideSidebar .el-menu-item .el-submenu__icon-arrow, 46 | .hideSidebar .el-submenu .el-submenu__title .el-submenu__icon-arrow { 47 | display: none; 48 | } 49 | 50 | .hideSidebar .submenu-title-noDropdown { 51 | position: relative; 52 | span { 53 | transition: opacity .3s cubic-bezier(.55, 0, .1, 1); 54 | opacity: 0; 55 | } 56 | &:hover { 57 | span { 58 | display: block; 59 | border-radius: 3px; 60 | z-index: 1002; 61 | width: 140px; 62 | height: 56px; 63 | visibility: visible; 64 | position: absolute; 65 | right: -145px; 66 | text-align: left; 67 | text-indent: 20px; 68 | top: 0px; 69 | background-color: #1f2d3d; 70 | opacity: 1; 71 | } 72 | } 73 | } 74 | 75 | .el-submenu .el-menu-item { 76 | min-width: 180px!important; 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 47 | -------------------------------------------------------------------------------- /src/views/dashboard/editor/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 41 | 42 | 75 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' // Progress 进度条 4 | import 'nprogress/nprogress.css'// Progress 进度条样式 5 | import { getToken } from '@/utils/auth' // 验权 6 | 7 | // permissiom judge 8 | function hasPermission(roles, permissionRoles) { 9 | if (roles.indexOf('admin') >= 0) return true // admin权限 直接通过 10 | if (!permissionRoles) return true 11 | return roles.some(role => permissionRoles.indexOf(role) >= 0) 12 | } 13 | 14 | // register global progress. 15 | const whiteList = ['/login', '/authredirect']// 不重定向白名单 16 | router.beforeEach((to, from, next) => { 17 | NProgress.start() // 开启Progress 18 | if (getToken()) { // 判断是否有token 19 | if (to.path === '/login') { 20 | next({ path: '/' }) 21 | } else { 22 | if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息 23 | store.dispatch('GetUserInfo').then(res => { // 拉取user_info 24 | const roles = res.data.role 25 | store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表 26 | router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表 27 | next({ ...to }) // hack方法 确保addRoutes已完成 28 | }) 29 | }).catch(() => { 30 | store.dispatch('FedLogOut').then(() => { 31 | next({ path: '/login' }) 32 | }) 33 | }) 34 | } else { 35 | // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓ 36 | if (hasPermission(store.getters.roles, to.meta.role)) { 37 | next()// 38 | } else { 39 | next({ path: '/401', query: { noGoBack: true }}) 40 | } 41 | // 可删 ↑ 42 | } 43 | } 44 | } else { 45 | if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入 46 | next() 47 | } else { 48 | next('/login') // 否则全部重定向到登录页 49 | NProgress.done() // 在hash模式下 改变手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行! 50 | } 51 | } 52 | }) 53 | 54 | router.afterEach(() => { 55 | NProgress.done() // 结束Progress 56 | }) 57 | -------------------------------------------------------------------------------- /src/styles/btn.scss: -------------------------------------------------------------------------------- 1 | $blue:#324157; 2 | $light-blue:#3A71A8; 3 | $red:#C03639; 4 | $pink: #E65D6E; 5 | $green: #30B08F; 6 | $tiffany: #4AB7BD; 7 | $yellow:#FEC171; 8 | $panGreen: #30B08F; 9 | 10 | @mixin colorBtn($color) { 11 | background: $color; 12 | &:hover { 13 | color: $color; 14 | &:before, 15 | &:after { 16 | background: $color; 17 | } 18 | } 19 | } 20 | 21 | .blue-btn { 22 | @include colorBtn($blue) 23 | } 24 | 25 | .light-blue-btn { 26 | @include colorBtn($light-blue) 27 | } 28 | 29 | .red-btn { 30 | @include colorBtn($red) 31 | } 32 | 33 | .pink-btn { 34 | @include colorBtn($pink) 35 | } 36 | 37 | .green-btn { 38 | @include colorBtn($green) 39 | } 40 | 41 | .tiffany-btn { 42 | @include colorBtn($tiffany) 43 | } 44 | 45 | .yellow-btn { 46 | @include colorBtn($yellow) 47 | } 48 | 49 | .pan-btn { 50 | font-size: 14px; 51 | color: #fff; 52 | padding: 14px 36px; 53 | border-radius: 8px; 54 | border: none; 55 | outline: none; 56 | margin-right: 25px; 57 | transition: 600ms ease all; 58 | position: relative; 59 | display: inline-block; 60 | &:hover { 61 | background: #fff; 62 | &:before, 63 | &:after { 64 | width: 100%; 65 | transition: 600ms ease all; 66 | } 67 | } 68 | &:before, 69 | &:after { 70 | content: ''; 71 | position: absolute; 72 | top: 0; 73 | right: 0; 74 | height: 2px; 75 | width: 0; 76 | transition: 400ms ease all; 77 | } 78 | &::after { 79 | right: inherit; 80 | top: inherit; 81 | left: 0; 82 | bottom: 0; 83 | } 84 | } 85 | 86 | .custom-button { 87 | display: inline-block; 88 | line-height: 1; 89 | white-space: nowrap; 90 | cursor: pointer; 91 | background: #fff; 92 | color: #fff; 93 | -webkit-appearance: none; 94 | text-align: center; 95 | box-sizing: border-box; 96 | outline: 0; 97 | margin: 0; 98 | padding: 10px 15px; 99 | font-size: 14px; 100 | border-radius: 4px; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/pieChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 80 | -------------------------------------------------------------------------------- /src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // 创建axios实例 7 | const service = axios.create({ 8 | baseURL: process.env.BASE_API, // api的base_url 9 | timeout: 5000 // 请求超时时间 10 | }) 11 | 12 | // request拦截器 13 | service.interceptors.request.use(config => { 14 | // Do something before request is sent 15 | if (store.getters.token) { 16 | config.headers['X-Token'] = getToken() // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改 17 | } 18 | return config 19 | }, error => { 20 | // Do something with request error 21 | console.log(error) // for debug 22 | Promise.reject(error) 23 | }) 24 | 25 | // respone拦截器 26 | service.interceptors.response.use( 27 | response => response, 28 | /** 29 | * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页 30 | * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中 31 | */ 32 | // const res = response.data; 33 | // if (res.code !== 20000) { 34 | // Message({ 35 | // message: res.message, 36 | // type: 'error', 37 | // duration: 5 * 1000 38 | // }); 39 | // // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 40 | // if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 41 | // MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', { 42 | // confirmButtonText: '重新登录', 43 | // cancelButtonText: '取消', 44 | // type: 'warning' 45 | // }).then(() => { 46 | // store.dispatch('FedLogOut').then(() => { 47 | // location.reload();// 为了重新实例化vue-router对象 避免bug 48 | // }); 49 | // }) 50 | // } 51 | // return Promise.reject('error'); 52 | // } else { 53 | // return response.data; 54 | // } 55 | error => { 56 | console.log('err' + error)// for debug 57 | Message({ 58 | message: error.message, 59 | type: 'error', 60 | duration: 5 * 1000 61 | }) 62 | return Promise.reject(error) 63 | } 64 | ) 65 | 66 | export default service 67 | -------------------------------------------------------------------------------- /src/components/ImageCropper/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | /** 4 | * 5 | * @param e 6 | * @param arg_opts 7 | * @returns {boolean} 8 | */ 9 | export function effectRipple(e, arg_opts) { 10 | let opts = Object.assign({ 11 | ele: e.target, // 波纹作用元素 12 | type: 'hit', // hit点击位置扩散 center中心点扩展 13 | bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 14 | }, arg_opts), 15 | target = opts.ele; 16 | if (target) { 17 | let rect = target.getBoundingClientRect(), 18 | ripple = target.querySelector('.e-ripple'); 19 | if (!ripple) { 20 | ripple = document.createElement('span'); 21 | ripple.className = 'e-ripple'; 22 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'; 23 | target.appendChild(ripple); 24 | } else { 25 | ripple.className = 'e-ripple'; 26 | } 27 | switch (opts.type) { 28 | case 'center': 29 | ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'; 30 | ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'; 31 | break; 32 | default: 33 | ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'; 34 | ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'; 35 | } 36 | ripple.style.backgroundColor = opts.bgc; 37 | ripple.className = 'e-ripple z-active'; 38 | return false; 39 | } 40 | } 41 | // database64文件格式转换为2进制 42 | /** 43 | * 44 | * @param data 45 | * @param mime 46 | * @returns {*} 47 | */ 48 | export function data2blob(data, mime) { 49 | // dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了 50 | data = data.split(',')[1]; 51 | data = window.atob(data); 52 | var ia = new Uint8Array(data.length); 53 | for (var i = 0; i < data.length; i++) { 54 | ia[i] = data.charCodeAt(i); 55 | } 56 | // canvas.toDataURL 返回的默认格式就是 image/png 57 | return new Blob([ia], {type: mime}); 58 | }; 59 | -------------------------------------------------------------------------------- /src/icons/svg/weixin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/mock/article.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { param2Obj } from '@/utils' 3 | 4 | const List = [] 5 | const count = 100 6 | 7 | for (let i = 0; i < count; i++) { 8 | List.push(Mock.mock({ 9 | id: '@increment', 10 | timestamp: +Mock.Random.date('T'), 11 | author: '@cname', 12 | auditor: '@cname', 13 | title: '@ctitle(10, 20)', 14 | forecast: '@float(0, 100, 2, 2)', 15 | importance: '@integer(1, 3)', 16 | 'type|1': ['CN', 'US', 'JP', 'EU'], 17 | 'status|1': ['published', 'draft', 'deleted'], 18 | display_time: '@datetime', 19 | pageviews: '@integer(300, 5000)' 20 | })) 21 | } 22 | 23 | export default { 24 | getList: config => { 25 | const { importance, type, title, page = 1, limit = 20, sort } = param2Obj(config.url) 26 | 27 | let mockList = List.filter(item => { 28 | if (importance && item.importance !== +importance) return false 29 | if (type && item.type !== type) return false 30 | if (title && item.title.indexOf(title) < 0) return false 31 | return true 32 | }) 33 | 34 | if (sort === '-id') { 35 | mockList = mockList.reverse() 36 | } 37 | 38 | const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1)) 39 | 40 | return { 41 | total: mockList.length, 42 | items: pageList 43 | } 44 | }, 45 | getPv: () => ({ 46 | pvData: [{ key: 'PC网站', pv: 1024 }, { key: 'mobile网站', pv: 1024 }, { key: 'ios', pv: 1024 }, { key: 'android', pv: 1024 }] 47 | }), 48 | getArticle: () => ({ 49 | id: 120000000001, 50 | author: { key: 'mockPan' }, 51 | source_name: '原创作者', 52 | category_item: [{ key: 'global', name: '全球' }], 53 | comment_disabled: false, 54 | content: '

我是测试数据我是测试数据

"', 55 | content_short: '我是测试数据', 56 | display_time: +new Date(), 57 | image_uri: 'https://wpimg.wallstcn.com/e4558086-631c-425c-9430-56ffb46e70b3', 58 | platforms: ['a-platform'], 59 | source_uri: 'https://github.com/PanJiaChen/vue-element-admin', 60 | status: 'published', 61 | tags: [], 62 | title: '' 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/barChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 94 | -------------------------------------------------------------------------------- /src/views/login/socialsignin.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 69 | -------------------------------------------------------------------------------- /src/views/errorPage/401.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 51 | 52 | 88 | -------------------------------------------------------------------------------- /src/views/theme/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 46 | 71 | 72 | 90 | -------------------------------------------------------------------------------- /src/views/excel/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 75 | -------------------------------------------------------------------------------- /src/components/UploadExcel/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 72 | 73 | 79 | -------------------------------------------------------------------------------- /static/tinymce/skins/lightgray/content.inline.min.css: -------------------------------------------------------------------------------- 1 | .mce-content-body .mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:black;font-family:Arial;font-size:11px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;line-height:normal;font-weight:normal;text-align:left;-webkit-tap-highlight-color:transparent;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-object{border:1px dotted #3A3A3A;background:#D5D5D5 url(img/object.gif) no-repeat center}.mce-preview-object{display:inline-block;position:relative;margin:0 2px 0 2px;line-height:0;border:1px solid gray}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-preview-object .mce-shim{position:absolute;top:0;left:0;width:100%;height:100%;background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}figure.align-left{float:left}figure.align-right{float:right}figure.image.align-center{display:table;margin-left:auto;margin-right:auto}figure.image{display:inline-block;border:1px solid gray;margin:0 2px 0 1px;background:#f5f2f0}figure.image img{margin:8px 8px 0 8px}figure.image figcaption{margin:6px 8px 6px 8px;text-align:center}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px;page-break-before:always}@media print{.mce-pagebreak{border:0}}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3A3A3A;background:#D5D5D5 url(img/anchor.gif) no-repeat center}.mce-nbsp,.mce-shy{background:#AAA}.mce-shy::after{content:'-'}hr{cursor:default}.mce-match-marker{background:#AAA;color:#fff}.mce-match-marker-selected{background:#3399ff;color:#fff}.mce-spellchecker-word{border-bottom:2px solid #F00;cursor:default}.mce-spellchecker-grammar{border-bottom:2px solid #008000;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td[data-mce-selected],th[data-mce-selected]{background-color:#3399ff !important}.mce-edit-focus{outline:1px dotted #333}.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus{outline:2px solid #2d8ac7}.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover{outline:2px solid #7ACAFF}.mce-content-body *[contentEditable=false][data-mce-selected]{outline:2px solid #2d8ac7}.mce-resize-bar-dragging{background-color:blue;opacity:.25;filter:alpha(opacity=25);zoom:1} -------------------------------------------------------------------------------- /src/components/ErrLog/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 47 | 48 | 57 | -------------------------------------------------------------------------------- /src/views/example/tab/components/tabPane.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 98 | 99 | -------------------------------------------------------------------------------- /src/components/MarkdownEditor/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 79 | 80 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/directive/sticky.js: -------------------------------------------------------------------------------- 1 | const vueSticky = {} 2 | let listenAction 3 | vueSticky.install = Vue => { 4 | Vue.directive('sticky', { 5 | inserted(el, binding) { 6 | const params = binding.value || {} 7 | const stickyTop = params.stickyTop || 0 8 | const zIndex = params.zIndex || 1000 9 | const elStyle = el.style 10 | 11 | elStyle.position = '-webkit-sticky' 12 | elStyle.position = 'sticky' 13 | // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary) 14 | // if (~elStyle.position.indexOf('sticky')) { 15 | // elStyle.top = `${stickyTop}px`; 16 | // elStyle.zIndex = zIndex; 17 | // return 18 | // } 19 | const elHeight = el.getBoundingClientRect().height 20 | const elWidth = el.getBoundingClientRect().width 21 | elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}` 22 | 23 | const parentElm = el.parentNode || document.documentElement 24 | const placeholder = document.createElement('div') 25 | placeholder.style.display = 'none' 26 | placeholder.style.width = `${elWidth}px` 27 | placeholder.style.height = `${elHeight}px` 28 | parentElm.insertBefore(placeholder, el) 29 | 30 | let active = false 31 | 32 | const getScroll = (target, top) => { 33 | const prop = top ? 'pageYOffset' : 'pageXOffset' 34 | const method = top ? 'scrollTop' : 'scrollLeft' 35 | let ret = target[prop] 36 | if (typeof ret !== 'number') { 37 | ret = window.document.documentElement[method] 38 | } 39 | return ret 40 | } 41 | 42 | const sticky = () => { 43 | if (active) { 44 | return 45 | } 46 | if (!elStyle.height) { 47 | elStyle.height = `${el.offsetHeight}px` 48 | } 49 | 50 | elStyle.position = 'fixed' 51 | elStyle.width = `${elWidth}px` 52 | placeholder.style.display = 'inline-block' 53 | active = true 54 | } 55 | 56 | const reset = () => { 57 | if (!active) { 58 | return 59 | } 60 | 61 | elStyle.position = '' 62 | placeholder.style.display = 'none' 63 | active = false 64 | } 65 | 66 | const check = () => { 67 | const scrollTop = getScroll(window, true) 68 | const offsetTop = el.getBoundingClientRect().top 69 | if (offsetTop < stickyTop) { 70 | sticky() 71 | } else { 72 | if (scrollTop < elHeight + stickyTop) { 73 | reset() 74 | } 75 | } 76 | } 77 | listenAction = () => { 78 | check() 79 | } 80 | 81 | window.addEventListener('scroll', listenAction) 82 | }, 83 | 84 | unbind() { 85 | window.removeEventListener('scroll', listenAction) 86 | } 87 | }) 88 | } 89 | 90 | export default vueSticky 91 | 92 | -------------------------------------------------------------------------------- /src/components/Screenfull/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /src/components/Charts/keyboard.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 114 | -------------------------------------------------------------------------------- /src/components/Upload/singleImage2.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 66 | 67 | 119 | -------------------------------------------------------------------------------- /src/views/example/table/inlineEditTable.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 96 | -------------------------------------------------------------------------------- /src/views/excel/selectExcel.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 89 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | function pluralize(time, label) { 2 | if (time === 1) { 3 | return time + label 4 | } 5 | return time + label + 's' 6 | } 7 | export function timeAgo(time) { 8 | const between = Date.now() / 1000 - Number(time) 9 | if (between < 3600) { 10 | return pluralize(~~(between / 60), ' minute') 11 | } else if (between < 86400) { 12 | return pluralize(~~(between / 3600), ' hour') 13 | } else { 14 | return pluralize(~~(between / 86400), ' day') 15 | } 16 | } 17 | 18 | export function parseTime(time, cFormat) { 19 | if (arguments.length === 0) { 20 | return null 21 | } 22 | 23 | if ((time + '').length === 10) { 24 | time = +time * 1000 25 | } 26 | 27 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 28 | let date 29 | if (typeof time === 'object') { 30 | date = time 31 | } else { 32 | date = new Date(parseInt(time)) 33 | } 34 | const formatObj = { 35 | y: date.getFullYear(), 36 | m: date.getMonth() + 1, 37 | d: date.getDate(), 38 | h: date.getHours(), 39 | i: date.getMinutes(), 40 | s: date.getSeconds(), 41 | a: date.getDay() 42 | } 43 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 44 | let value = formatObj[key] 45 | if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1] 46 | if (result.length > 0 && value < 10) { 47 | value = '0' + value 48 | } 49 | return value || 0 50 | }) 51 | return time_str 52 | } 53 | 54 | export function formatTime(time, option) { 55 | time = +time * 1000 56 | const d = new Date(time) 57 | const now = Date.now() 58 | 59 | const diff = (now - d) / 1000 60 | 61 | if (diff < 30) { 62 | return '刚刚' 63 | } else if (diff < 3600) { // less 1 hour 64 | return Math.ceil(diff / 60) + '分钟前' 65 | } else if (diff < 3600 * 24) { 66 | return Math.ceil(diff / 3600) + '小时前' 67 | } else if (diff < 3600 * 24 * 2) { 68 | return '1天前' 69 | } 70 | if (option) { 71 | return parseTime(time, option) 72 | } else { 73 | return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分' 74 | } 75 | } 76 | 77 | /* 数字 格式化*/ 78 | export function nFormatter(num, digits) { 79 | const si = [ 80 | { value: 1E18, symbol: 'E' }, 81 | { value: 1E15, symbol: 'P' }, 82 | { value: 1E12, symbol: 'T' }, 83 | { value: 1E9, symbol: 'G' }, 84 | { value: 1E6, symbol: 'M' }, 85 | { value: 1E3, symbol: 'k' } 86 | ] 87 | for (let i = 0; i < si.length; i++) { 88 | if (num >= si[i].value) { 89 | return (num / si[i].value + 0.1).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol 90 | } 91 | } 92 | return num.toString() 93 | } 94 | 95 | export function html2Text(val) { 96 | const div = document.createElement('div') 97 | div.innerHTML = val 98 | return div.textContent || div.innerText 99 | } 100 | 101 | export function toThousandslsFilter(num) { 102 | return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')) 103 | } 104 | -------------------------------------------------------------------------------- /src/components/BackToTop/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 83 | 84 | 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "juicy", 3 | "version": "2.1.0", 4 | "description": "A Vue.js admin", 5 | "author": "Pan ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "node build/dev-server.js", 10 | "build:prod": "cross-env NODE_ENV=production node build/build.js", 11 | "build:sit": "cross-env NODE_ENV=sit node build/build.js", 12 | "build:sit-preview": "cross-env NODE_ENV=sit npm_config_preview=true npm_config_report=true node build/build.js", 13 | "lint": "eslint --ext .js,.vue src" 14 | }, 15 | "dependencies": { 16 | "axios": "0.16.2", 17 | "codemirror": "5.26.0", 18 | "dropzone": "5.1.0", 19 | "echarts": "3.6.2", 20 | "element-ui": "1.4.2", 21 | "file-saver": "1.3.3", 22 | "js-cookie": "2.1.4", 23 | "jsonlint": "1.6.2", 24 | "mockjs": "1.0.1-beta3", 25 | "normalize.css": "7.0.0", 26 | "nprogress": "0.2.0", 27 | "screenfull": "3.2.2", 28 | "showdown": "1.7.1", 29 | "simplemde": "1.11.2", 30 | "sortablejs": "1.5.1", 31 | "vue": "2.4.2", 32 | "vue-count-to": "1.0.5", 33 | "vue-multiselect": "2.0.2", 34 | "vue-router": "2.7.0", 35 | "vue-splitpane": "^1.0.0", 36 | "vuedraggable": "2.14.1", 37 | "vuex": "2.3.1", 38 | "xlsx": "^0.10.8" 39 | }, 40 | "devDependencies": { 41 | "autoprefixer": "7.1.1", 42 | "babel-core": "6.25.0", 43 | "babel-eslint": "7.2.3", 44 | "babel-loader": "7.0.0", 45 | "babel-plugin-transform-runtime": "6.23.0", 46 | "babel-preset-env": "1.5.2", 47 | "babel-preset-stage-2": "6.24.1", 48 | "babel-register": "6.24.1", 49 | "chalk": "1.1.3", 50 | "connect-history-api-fallback": "1.3.0", 51 | "copy-webpack-plugin": "4.0.1", 52 | "cross-env": "5.0.1", 53 | "css-loader": "0.28.4", 54 | "eslint": "3.19.0", 55 | "eslint-friendly-formatter": "3.0.0", 56 | "eslint-import-resolver-webpack": "0.8.1", 57 | "eslint-loader": "1.7.1", 58 | "eslint-plugin-html": "3.0.0", 59 | "eslint-plugin-import": "2.3.0", 60 | "eventsource-polyfill": "0.9.6", 61 | "express": "4.15.3", 62 | "extract-text-webpack-plugin": "2.1.2", 63 | "file-loader": "0.11.2", 64 | "friendly-errors-webpack-plugin": "1.6.1", 65 | "function-bind": "1.1.0", 66 | "html-webpack-plugin": "2.28.0", 67 | "http-proxy-middleware": "0.17.4", 68 | "node-sass": "^4.5.0", 69 | "opn": "4.0.2", 70 | "optimize-css-assets-webpack-plugin": "1.3.0", 71 | "ora": "1.1.0", 72 | "pushstate-server": "2.1.0", 73 | "rimraf": "2.6.0", 74 | "sass-loader": "6.0.5", 75 | "script-loader": "0.7.0", 76 | "semver": "5.3.0", 77 | "style-loader": "0.17.0", 78 | "svg-sprite-loader": "3.2.4", 79 | "url-loader": "0.5.8", 80 | "vue-loader": "13.0.4", 81 | "vue-style-loader": "3.0.1", 82 | "vue-template-compiler": "2.4.2", 83 | "webpack": "2.6.1", 84 | "webpack-bundle-analyzer": "2.8.2", 85 | "webpack-dev-middleware": "1.10.2", 86 | "webpack-hot-middleware": "2.18.0", 87 | "webpack-merge": "4.1.0" 88 | }, 89 | "engines": { 90 | "node": ">= 4.0.0", 91 | "npm": ">= 3.0.0" 92 | }, 93 | "browserlist": [ 94 | "> 1%", 95 | "last 2 versions", 96 | "not ie <= 8" 97 | ] 98 | } 99 | -------------------------------------------------------------------------------- /src/components/SplitPane/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 95 | 96 | 112 | -------------------------------------------------------------------------------- /src/views/dashboard/admin/lineChart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 123 | -------------------------------------------------------------------------------- /src/components/PanThumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 35 | 36 | 141 | -------------------------------------------------------------------------------- /src/views/layout/Navbar.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 71 | 72 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/components/Tinymce/components/editorImage.vue: -------------------------------------------------------------------------------- 1 | 23 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /src/components/TodoList/index.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 112 | 113 | 116 | -------------------------------------------------------------------------------- /src/views/components/sticky.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 96 | 120 | 121 | 126 | 127 | -------------------------------------------------------------------------------- /src/components/Upload/singleImage.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 68 | 69 | 124 | -------------------------------------------------------------------------------- /src/components/Upload/singleImage3.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 76 | 77 | 147 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { loginByUsername, logout, getUserInfo } from '@/api/login' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | 4 | const user = { 5 | state: { 6 | user: '', 7 | status: '', 8 | code: '', 9 | token: getToken(), 10 | name: '', 11 | avatar: '', 12 | introduction: '', 13 | roles: [], 14 | setting: { 15 | articlePlatform: [] 16 | } 17 | }, 18 | 19 | mutations: { 20 | SET_CODE: (state, code) => { 21 | state.code = code 22 | }, 23 | SET_TOKEN: (state, token) => { 24 | state.token = token 25 | }, 26 | SET_INTRODUCTION: (state, introduction) => { 27 | state.introduction = introduction 28 | }, 29 | SET_SETTING: (state, setting) => { 30 | state.setting = setting 31 | }, 32 | SET_STATUS: (state, status) => { 33 | state.status = status 34 | }, 35 | SET_NAME: (state, name) => { 36 | state.name = name 37 | }, 38 | SET_AVATAR: (state, avatar) => { 39 | state.avatar = avatar 40 | }, 41 | SET_ROLES: (state, roles) => { 42 | state.roles = roles 43 | } 44 | }, 45 | 46 | actions: { 47 | // 用户名登录 48 | LoginByUsername({ commit }, userInfo) { 49 | const username = userInfo.username.trim() 50 | return new Promise((resolve, reject) => { 51 | loginByUsername(username, userInfo.password).then(response => { 52 | const data = response.data 53 | setToken(response.data.token) 54 | commit('SET_TOKEN', data.token) 55 | resolve() 56 | }).catch(error => { 57 | reject(error) 58 | }) 59 | }) 60 | }, 61 | 62 | // 获取用户信息 63 | GetUserInfo({ commit, state }) { 64 | return new Promise((resolve, reject) => { 65 | getUserInfo(state.token).then(response => { 66 | const data = response.data 67 | commit('SET_ROLES', data.role) 68 | commit('SET_NAME', data.name) 69 | commit('SET_AVATAR', data.avatar) 70 | commit('SET_INTRODUCTION', data.introduction) 71 | resolve(response) 72 | }).catch(error => { 73 | reject(error) 74 | }) 75 | }) 76 | }, 77 | 78 | // 第三方验证登录 79 | // LoginByThirdparty({ commit, state }, code) { 80 | // return new Promise((resolve, reject) => { 81 | // commit('SET_CODE', code) 82 | // loginByThirdparty(state.status, state.email, state.code).then(response => { 83 | // commit('SET_TOKEN', response.data.token) 84 | // setToken(response.data.token) 85 | // resolve() 86 | // }).catch(error => { 87 | // reject(error) 88 | // }) 89 | // }) 90 | // }, 91 | 92 | // 登出 93 | LogOut({ commit, state }) { 94 | return new Promise((resolve, reject) => { 95 | logout(state.token).then(() => { 96 | commit('SET_TOKEN', '') 97 | commit('SET_ROLES', []) 98 | removeToken() 99 | resolve() 100 | }).catch(error => { 101 | reject(error) 102 | }) 103 | }) 104 | }, 105 | 106 | // 前端 登出 107 | FedLogOut({ commit }) { 108 | return new Promise(resolve => { 109 | commit('SET_TOKEN', '') 110 | removeToken() 111 | resolve() 112 | }) 113 | }, 114 | 115 | // 动态修改权限 116 | ChangeRole({ commit }, role) { 117 | return new Promise(resolve => { 118 | commit('SET_TOKEN', role) 119 | setToken(role) 120 | getUserInfo(role).then(response => { 121 | const data = response.data 122 | commit('SET_ROLES', data.role) 123 | commit('SET_NAME', data.name) 124 | commit('SET_AVATAR', data.avatar) 125 | commit('SET_INTRODUCTION', data.introduction) 126 | resolve() 127 | }) 128 | }) 129 | } 130 | } 131 | } 132 | 133 | export default user 134 | -------------------------------------------------------------------------------- /src/components/Charts/keyboard2.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 151 | --------------------------------------------------------------------------------