Merge branch 'master' into deploy

This commit is contained in:
Pan 2018-08-29 15:26:24 +08:00
commit 1cfcd49fe2
22 changed files with 229 additions and 43 deletions

View File

@ -18,6 +18,12 @@
<a href="https://github.com/PanJiaChen/vue-element-admin/releases"> <a href="https://github.com/PanJiaChen/vue-element-admin/releases">
<img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release"> <img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release">
</a> </a>
<a href="https://gitter.im/vue-element-admin/discuss">
<img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
</a>
<a href="https://panjiachen.github.io/vue-element-admin-site/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
</p> </p>
English | [简体中文](./README.zh-CN.md) English | [简体中文](./README.zh-CN.md)

View File

@ -18,6 +18,12 @@
<a href="https://github.com/PanJiaChen/vue-element-admin/releases"> <a href="https://github.com/PanJiaChen/vue-element-admin/releases">
<img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release"> <img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release">
</a> </a>
<a href="https://gitter.im/vue-element-admin/discuss">
<img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
</a>
<a href="https://panjiachen.gitee.io/vue-element-admin-site/zh/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
</p> </p>
简体中文 | [English](./README.md) 简体中文 | [English](./README.md)

View File

@ -57,7 +57,6 @@
"vue": "2.5.17", "vue": "2.5.17",
"vue-count-to": "1.0.13", "vue-count-to": "1.0.13",
"vue-i18n": "7.3.2", "vue-i18n": "7.3.2",
"vue-multiselect": "2.1.0",
"vue-router": "3.0.1", "vue-router": "3.0.1",
"vue-splitpane": "1.0.2", "vue-splitpane": "1.0.2",
"vuedraggable": "^2.16.0", "vuedraggable": "^2.16.0",

View File

@ -45,7 +45,8 @@ export default {
data() { data() {
return { return {
visible: false, visible: false,
interval: null interval: null,
isMoving: false
} }
}, },
mounted() { mounted() {
@ -62,13 +63,16 @@ export default {
this.visible = window.pageYOffset > this.visibilityHeight this.visible = window.pageYOffset > this.visibilityHeight
}, },
backToTop() { backToTop() {
if (this.isMoving) return
const start = window.pageYOffset const start = window.pageYOffset
let i = 0 let i = 0
this.isMoving = true
this.interval = setInterval(() => { this.interval = setInterval(() => {
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500)) const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
if (next <= this.backPosition) { if (next <= this.backPosition) {
window.scrollTo(0, this.backPosition) window.scrollTo(0, this.backPosition)
clearInterval(this.interval) clearInterval(this.interval)
this.isMoving = false
} else { } else {
window.scrollTo(0, next) window.scrollTo(0, next)
} }

View File

@ -22,7 +22,7 @@ export default {
this.$i18n.locale = lang this.$i18n.locale = lang
this.$store.dispatch('setLanguage', lang) this.$store.dispatch('setLanguage', lang)
this.$message({ this.$message({
message: 'switch language success', message: 'Switch Language Success',
type: 'success' type: 'success'
}) })
} }

View File

@ -0,0 +1,63 @@
<template>
<el-dropdown trigger="click" @command="handleSetSize">
<div>
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :disabled="size==='medium'" command="medium">Medium</el-dropdown-item>
<el-dropdown-item :disabled="size==='small'" command="small">Small</el-dropdown-item>
<el-dropdown-item :disabled="size==='mini'" command="mini">Mini</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
computed: {
size() {
return this.$store.getters.size
}
},
methods: {
handleSetSize(size) {
this.$ELEMENT.size = size
this.$store.dispatch('setSize', size)
this.refreshView()
this.$message({
message: 'Switch Size Success',
type: 'success'
})
},
refreshView() {
// In order to make the cached page re-rendered
const visitedViews = [...this.$store.getters.visitedViews].map(i => {
i.meta.noCache = true
return i
})
this.$store.dispatch('delAllViews', this.$route).then(() => {
console.log(visitedViews)
for (const i of visitedViews) {
this.$store.dispatch('addVisitedViews', i)
}
})
const { path } = this.$route
this.$router.replace({
path: '/redirect' + path
})
}
}
}
</script>
<style scoped>
.size-icon {
font-size: 20px;
cursor: pointer;
vertical-align: -4px!important;
}
</style>

View File

@ -46,7 +46,16 @@ export default {
hasChange: false, hasChange: false,
hasInit: false, hasInit: false,
tinymceId: this.id, tinymceId: this.id,
fullscreen: false fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN'
}
}
},
computed: {
language() {
return this.languageTypeList[this.$store.getters.language]
} }
}, },
watch: { watch: {
@ -55,6 +64,10 @@ export default {
this.$nextTick(() => this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || '')) window.tinymce.get(this.tinymceId).setContent(val || ''))
} }
},
language() {
this.destroyTinymce()
this.$nextTick(() => this.initTinymce())
} }
}, },
mounted() { mounted() {
@ -73,6 +86,7 @@ export default {
initTinymce() { initTinymce() {
const _this = this const _this = this
window.tinymce.init({ window.tinymce.init({
language: this.language,
selector: `#${this.tinymceId}`, selector: `#${this.tinymceId}`,
height: this.height, height: this.height,
body_class: 'panel-body ', body_class: 'panel-body ',

View File

@ -2,20 +2,18 @@ import Vue from 'vue'
import store from './store' import store from './store'
// you can set only in production env show the error-log // you can set only in production env show the error-log
// if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
Vue.config.errorHandler = function(err, vm, info, a) {
Vue.config.errorHandler = function(err, vm, info, a) {
// Don't ask me why I use Vue.nextTick, it just a hack. // Don't ask me why I use Vue.nextTick, it just a hack.
// detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500 // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500
Vue.nextTick(() => { Vue.nextTick(() => {
store.dispatch('addErrorLog', { store.dispatch('addErrorLog', {
err, err,
vm, vm,
info, info,
url: window.location.href url: window.location.href
})
console.error(err, info)
}) })
console.error(err, info) }
})
} }
// }

1
src/icons/svg/link.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1534846208920" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2941" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M912 1008.512H15.488V112h448.256v96H111.488v704.512H816V560.256h96z" p-id="2942"></path><path d="M918.208 37.888l67.904 67.904L545.984 545.92 478.08 478.08z" p-id="2943"></path><path d="M1007.168 310.656h-96V112h-208V16h304z" p-id="2944"></path></svg>

After

Width:  |  Height:  |  Size: 637 B

2
src/icons/svg/size.svg Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1535513021233" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13539" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M64 512l384 0 0 128-128 0 0 384-128 0 0-384-128 0zM960 256l-251.74016 0 0 768-136.4992 0 0-768-251.74016 0 0-128 640 0z" p-id="13540"></path></svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@ -60,14 +60,16 @@ export default {
exportZip: 'Export Zip', exportZip: 'Export Zip',
theme: 'Theme', theme: 'Theme',
clipboardDemo: 'Clipboard', clipboardDemo: 'Clipboard',
i18n: 'I18n' i18n: 'I18n',
externalLink: 'External Link'
}, },
navbar: { navbar: {
logOut: 'Log Out', logOut: 'Log Out',
dashboard: 'Dashboard', dashboard: 'Dashboard',
github: 'Github', github: 'Github',
screenfull: 'screenfull', screenfull: 'Screenfull',
theme: 'theme' theme: 'Theme',
size: 'Global Size'
}, },
login: { login: {
title: 'Login Form', title: 'Login Form',

View File

@ -60,14 +60,16 @@ export default {
exportZip: 'Export Zip', exportZip: 'Export Zip',
theme: '换肤', theme: '换肤',
clipboardDemo: 'Clipboard', clipboardDemo: 'Clipboard',
i18n: '国际化' i18n: '国际化',
externalLink: '外链'
}, },
navbar: { navbar: {
logOut: '退出登录', logOut: '退出登录',
dashboard: '首页', dashboard: '首页',
github: '项目地址', github: '项目地址',
screenfull: '全屏', screenfull: '全屏',
theme: '换肤' theme: '换肤',
size: '布局大小'
}, },
login: { login: {
title: '系统登录', title: '系统登录',

View File

@ -1,5 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import Cookies from 'js-cookie'
import 'normalize.css/normalize.css' // A modern alternative to CSS resets import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import Element from 'element-ui' import Element from 'element-ui'
@ -20,7 +22,7 @@ import './mock' // simulation data
import * as filters from './filters' // global filters import * as filters from './filters' // global filters
Vue.use(Element, { Vue.use(Element, {
size: 'medium', // set element-ui default size size: Cookies.get('size') || 'medium', // set element-ui default size
i18n: (key, value) => i18n.t(key, value) i18n: (key, value) => i18n.t(key, value)
}) })

View File

@ -31,6 +31,17 @@ import nestedRouter from './modules/nested'
} }
**/ **/
export const constantRouterMap = [ export const constantRouterMap = [
{
path: '/redirect',
component: Layout,
hidden: true,
children: [
{
path: '/redirect/:path*',
component: () => import('@/views/redirect/index')
}
]
},
{ {
path: '/login', path: '/login',
component: () => import('@/views/login/index'), component: () => import('@/views/login/index'),
@ -322,5 +333,16 @@ export const asyncRouterMap = [
] ]
}, },
{
path: 'external-link',
component: Layout,
children: [
{
path: 'https://github.com/PanJiaChen/vue-element-admin',
meta: { title: 'externalLink', icon: 'link' }
}
]
},
{ path: '*', redirect: '/404', hidden: true } { path: '*', redirect: '/404', hidden: true }
] ]

View File

@ -1,6 +1,7 @@
const getters = { const getters = {
sidebar: state => state.app.sidebar, sidebar: state => state.app.sidebar,
language: state => state.app.language, language: state => state.app.language,
size: state => state.app.size,
device: state => state.app.device, device: state => state.app.device,
visitedViews: state => state.tagsView.visitedViews, visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews, cachedViews: state => state.tagsView.cachedViews,

View File

@ -7,7 +7,8 @@ const app = {
withoutAnimation: false withoutAnimation: false
}, },
device: 'desktop', device: 'desktop',
language: Cookies.get('language') || 'en' language: Cookies.get('language') || 'en',
size: Cookies.get('size') || 'medium'
}, },
mutations: { mutations: {
TOGGLE_SIDEBAR: state => { TOGGLE_SIDEBAR: state => {
@ -30,6 +31,10 @@ const app = {
SET_LANGUAGE: (state, language) => { SET_LANGUAGE: (state, language) => {
state.language = language state.language = language
Cookies.set('language', language) Cookies.set('language', language)
},
SET_SIZE: (state, size) => {
state.size = size
Cookies.set('size', size)
} }
}, },
actions: { actions: {
@ -44,6 +49,9 @@ const app = {
}, },
setLanguage({ commit }, language) { setLanguage({ commit }, language) {
commit('SET_LANGUAGE', language) commit('SET_LANGUAGE', language)
},
setSize({ commit }, size) {
commit('SET_SIZE', size)
} }
} }
} }

View File

@ -25,7 +25,8 @@ export function parseTime(time, cFormat) {
} }
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key] let value = formatObj[key]
if (key === 'a') { return ['一', '二', '三', '四', '五', '六', '日'][value - 1] } // Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
if (result.length > 0 && value < 10) { if (result.length > 0 && value < 10) {
value = '0' + value value = '0' + value
} }

View File

@ -77,8 +77,6 @@
import Tinymce from '@/components/Tinymce' import Tinymce from '@/components/Tinymce'
import Upload from '@/components/Upload/singleImage3' import Upload from '@/components/Upload/singleImage3'
import MDinput from '@/components/MDinput' import MDinput from '@/components/MDinput'
import Multiselect from 'vue-multiselect'// 使element-uiselect
import 'vue-multiselect/dist/vue-multiselect.min.css'// css
import Sticky from '@/components/Sticky' // header import Sticky from '@/components/Sticky' // header
import { validateURL } from '@/utils/validate' import { validateURL } from '@/utils/validate'
import { fetchArticle } from '@/api/article' import { fetchArticle } from '@/api/article'
@ -102,7 +100,7 @@ const defaultForm = {
export default { export default {
name: 'ArticleDetail', name: 'ArticleDetail',
components: { Tinymce, MDinput, Upload, Multiselect, Sticky, Warning, CommentDropdown, PlatformDropdown, SourceUrlDropdown }, components: { Tinymce, MDinput, Upload, Sticky, Warning, CommentDropdown, PlatformDropdown, SourceUrlDropdown },
props: { props: {
isEdit: { isEdit: {
type: Boolean, type: Boolean,

View File

@ -11,6 +11,10 @@
<screenfull class="screenfull right-menu-item"/> <screenfull class="screenfull right-menu-item"/>
</el-tooltip> </el-tooltip>
<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
<size-select class="international right-menu-item"/>
</el-tooltip>
<lang-select class="international right-menu-item"/> <lang-select class="international right-menu-item"/>
<el-tooltip :content="$t('navbar.theme')" effect="dark" placement="bottom"> <el-tooltip :content="$t('navbar.theme')" effect="dark" placement="bottom">
@ -48,6 +52,7 @@ import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger' import Hamburger from '@/components/Hamburger'
import ErrorLog from '@/components/ErrorLog' import ErrorLog from '@/components/ErrorLog'
import Screenfull from '@/components/Screenfull' import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import LangSelect from '@/components/LangSelect' import LangSelect from '@/components/LangSelect'
import ThemePicker from '@/components/ThemePicker' import ThemePicker from '@/components/ThemePicker'
@ -57,6 +62,7 @@ export default {
Hamburger, Hamburger,
ErrorLog, ErrorLog,
Screenfull, Screenfull,
SizeSelect,
LangSelect, LangSelect,
ThemePicker ThemePicker
}, },

View File

@ -0,0 +1,29 @@
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
vnodes.push(<svg-icon icon-class={icon}/>)
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>

View File

@ -1,28 +1,27 @@
<template> <template>
<div v-if="!item.hidden&&item.children" class="menu-wrapper"> <div v-if="!item.hidden&&item.children" class="menu-wrapper">
<router-link v-if="hasOneShowingChild(item.children) && !onlyOneChild.children&&!item.alwaysShow" :to="resolvePath(onlyOneChild.path)"> <template v-if="hasOneShowingChild(item.children) && !onlyOneChild.children&&!item.alwaysShow">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}"> <a :href="onlyOneChild.path" target="_blank" @click="clickLink(onlyOneChild.path,$event)">
<svg-icon v-if="onlyOneChild.meta&&onlyOneChild.meta.icon" :icon-class="onlyOneChild.meta.icon"/> <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<span v-if="onlyOneChild.meta&&onlyOneChild.meta.title" slot="title">{{ generateTitle(onlyOneChild.meta.title) }}</span> <item v-if="onlyOneChild.meta" :icon="onlyOneChild.meta.icon" :title="generateTitle(onlyOneChild.meta.title)" />
</el-menu-item> </el-menu-item>
</router-link> </a>
</template>
<el-submenu v-else :index="item.name||item.path"> <el-submenu v-else :index="item.name||item.path">
<template slot="title"> <template slot="title">
<svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"/> <item v-if="item.meta" :icon="item.meta.icon" :title="generateTitle(item.meta.title)" />
<span v-if="item.meta&&item.meta.title" slot="title">{{ generateTitle(item.meta.title) }}</span>
</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 v-if="child.children&&child.children.length>0" :is-nest="true" :item="child" :key="child.path" :base-path="resolvePath(child.path)" class="nest-menu"/> <sidebar-item v-if="child.children&&child.children.length>0" :is-nest="true" :item="child" :key="child.path" :base-path="resolvePath(child.path)" class="nest-menu"/>
<router-link v-else :to="resolvePath(child.path)" :key="child.name"> <a v-else :href="child.path" :key="child.name" target="_blank" @click="clickLink(child.path,$event)">
<el-menu-item :index="resolvePath(child.path)"> <el-menu-item :index="resolvePath(child.path)">
<svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"/> <item v-if="child.meta" :icon="child.meta.icon" :title="generateTitle(child.meta.title)" />
<span v-if="child.meta&&child.meta.title" slot="title">{{ generateTitle(child.meta.title) }}</span>
</el-menu-item> </el-menu-item>
</router-link> </a>
</template> </template>
</el-submenu> </el-submenu>
@ -32,9 +31,12 @@
<script> <script>
import path from 'path' import path from 'path'
import { generateTitle } from '@/utils/i18n' import { generateTitle } from '@/utils/i18n'
import { validateURL } from '@/utils/validate'
import Item from './Item'
export default { export default {
name: 'SidebarItem', name: 'SidebarItem',
components: { Item },
props: { props: {
// route object // route object
item: { item: {
@ -71,11 +73,20 @@ export default {
} }
return false return false
}, },
resolvePath(...paths) { resolvePath(routePath) {
return path.resolve(this.basePath, ...paths) return path.resolve(this.basePath, routePath)
},
isExternalLink(routePath) {
return validateURL(routePath)
},
clickLink(routePath, e) {
if (!this.isExternalLink(routePath)) {
e.preventDefault()
const path = this.resolvePath(routePath)
this.$router.push(path)
}
}, },
generateTitle generateTitle
} }
} }
</script> </script>

View File

@ -0,0 +1,11 @@
<script>
export default {
beforeCreate() {
const path = this.$route.params.path
this.$router.replace('/' + path)
},
render: function(h) {
return h() // avoid warning message
}
}
</script>