Merge branch 'master' into deploy

This commit is contained in:
Pan 2018-11-28 14:25:01 +08:00
commit 42da911819
19 changed files with 239 additions and 140 deletions

View File

@ -69,6 +69,11 @@ Understanding and learning this knowledge in advance will greatly help the use o
<img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png"> <img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
</p> </p>
## Sponsors
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## Features ## Features
``` ```

View File

@ -81,6 +81,11 @@
<img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png"> <img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
</p> </p>
## Sponsors
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## 功能 ## 功能
``` ```

View File

@ -1,6 +1,6 @@
{ {
"name": "vue-element-admin", "name": "vue-element-admin",
"version": "3.9.2", "version": "3.9.3",
"description": "A magical vue admin. Typical templates for enterprise applications. Newest development stack of vue. Lots of awesome features", "description": "A magical vue admin. Typical templates for enterprise applications. Newest development stack of vue. Lots of awesome features",
"author": "Pan <panfree23@gmail.com>", "author": "Pan <panfree23@gmail.com>",
"license": "MIT", "license": "MIT",
@ -44,7 +44,6 @@
"echarts": "4.1.0", "echarts": "4.1.0",
"element-ui": "2.4.6", "element-ui": "2.4.6",
"file-saver": "1.3.8", "file-saver": "1.3.8",
"font-awesome": "4.7.0",
"js-cookie": "2.2.0", "js-cookie": "2.2.0",
"jsonlint": "1.6.3", "jsonlint": "1.6.3",
"jszip": "3.1.5", "jszip": "3.1.5",
@ -53,13 +52,13 @@
"nprogress": "0.2.0", "nprogress": "0.2.0",
"screenfull": "3.3.3", "screenfull": "3.3.3",
"showdown": "1.8.6", "showdown": "1.8.6",
"simplemde": "1.11.2",
"sortablejs": "1.7.0", "sortablejs": "1.7.0",
"tui-editor": "1.2.7",
"vue": "2.5.17", "vue": "2.5.17",
"vue-analytics": "5.16.0", "vue-analytics": "5.16.0",
"vue-count-to": "1.0.13", "vue-count-to": "1.0.13",
"vue-i18n": "7.3.2", "vue-i18n": "7.3.2",
"vue-router": "3.0.1", "vue-router": "3.0.2",
"vue-splitpane": "1.0.2", "vue-splitpane": "1.0.2",
"vuedraggable": "^2.16.0", "vuedraggable": "^2.16.0",
"vuex": "3.0.1", "vuex": "3.0.1",

View File

@ -3,7 +3,7 @@
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" v-if="item.meta.title" :key="item.path"> <el-breadcrumb-item v-for="(item,index) in levelList" v-if="item.meta.title" :key="item.path">
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{ generateTitle(item.meta.title) }}</span> <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{ generateTitle(item.meta.title) }}</span>
<router-link v-else :to="item.redirect||item.path">{{ generateTitle(item.meta.title) }}</router-link> <a v-else @click.prevent="handleLink(item)">{{ generateTitle(item.meta.title) }}</a>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
</el-breadcrumb> </el-breadcrumb>
@ -30,12 +30,8 @@ export default {
methods: { methods: {
generateTitle, generateTitle,
getBreadcrumb() { getBreadcrumb() {
const { params } = this.$route
let matched = this.$route.matched.filter(item => { let matched = this.$route.matched.filter(item => {
if (item.name) { if (item.name) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
var toPath = pathToRegexp.compile(item.path)
item.path = toPath(params)
return true return true
} }
}) })
@ -44,6 +40,20 @@ export default {
matched = [{ path: '/dashboard', meta: { title: 'dashboard' }}].concat(matched) matched = [{ path: '/dashboard', meta: { title: 'dashboard' }}].concat(matched)
} }
this.levelList = matched this.levelList = matched
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
} }
} }
} }

View File

@ -1,6 +1,11 @@
import { debounce } from '@/utils' import { debounce } from '@/utils'
export default { export default {
data() {
return {
sidebarElm: null
}
},
mounted() { mounted() {
this.__resizeHandler = debounce(() => { this.__resizeHandler = debounce(() => {
if (this.chart) { if (this.chart) {
@ -9,14 +14,13 @@ export default {
}, 100) }, 100)
window.addEventListener('resize', this.__resizeHandler) window.addEventListener('resize', this.__resizeHandler)
const sidebarElm = document.getElementsByClassName('sidebar-container')[0] this.sidebarElm = document.getElementsByClassName('sidebar-container')[0]
sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler) this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler)
}, },
beforeDestroy() { beforeDestroy() {
window.removeEventListener('resize', this.__resizeHandler) window.removeEventListener('resize', this.__resizeHandler)
const sidebarElm = document.getElementsByClassName('sidebar-container')[0] this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
}, },
methods: { methods: {
sidebarResizeHandler(e) { sidebarResizeHandler(e) {

View File

@ -1,5 +1,5 @@
<template> <template>
<el-select v-model="selectVal" v-bind="$attrs" class="drag-select" multiple> <el-select ref="dragSelect" v-model="selectVal" v-bind="$attrs" class="drag-select" multiple v-on="$listeners">
<slot/> <slot/>
</el-select> </el-select>
</template> </template>
@ -30,7 +30,7 @@ export default {
}, },
methods: { methods: {
setSort() { setSort() {
const el = document.querySelectorAll('.el-select__tags > span')[0] const el = this.$refs.dragSelect.$el.querySelectorAll('.el-select__tags > span')[0]
this.sortable = Sortable.create(el, { this.sortable = Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder, ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function(dataTransfer) { setData: function(dataTransfer) {

View File

@ -0,0 +1,31 @@
// doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
export default {
minHeight: '200px',
previewStyle: 'vertical',
useCommandShortcut: true,
useDefaultHTMLSanitizer: true,
usageStatistics: false,
hideModeSwitch: false,
toolbarItems: [
'heading',
'bold',
'italic',
'strike',
'divider',
'hr',
'quote',
'divider',
'ul',
'ol',
'task',
'indent',
'outdent',
'divider',
'table',
'image',
'link',
'divider',
'code',
'codeblock'
]
}

View File

@ -1,16 +1,18 @@
<template> <template>
<div :style="{height:height+'px',zIndex:zIndex}" class="simplemde-container"> <div :id="id"/>
<textarea :id="id"/>
</div>
</template> </template>
<script> <script>
import 'font-awesome/css/font-awesome.min.css' // deps for editor
import 'simplemde/dist/simplemde.min.css' import 'codemirror/lib/codemirror.css' // codemirror
import SimpleMDE from 'simplemde' import 'tui-editor/dist/tui-editor.css' // editor ui
import 'tui-editor/dist/tui-editor-contents.css' // editor content
import Editor from 'tui-editor'
import defaultOptions from './defaultOptions'
export default { export default {
name: 'SimplemdeMd', name: 'MarddownEditor',
props: { props: {
value: { value: {
type: String, type: String,
@ -19,105 +21,98 @@ export default {
id: { id: {
type: String, type: String,
required: false, required: false,
default: 'markdown-editor-' + +new Date() default() {
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
}, },
autofocus: { options: {
type: Boolean, type: Object,
default: false default() {
return defaultOptions
}
}, },
placeholder: { mode: {
type: String, type: String,
default: '' default: 'markdown'
}, },
height: { height: {
type: Number, type: String,
default: 150 required: false,
default: '300px'
}, },
zIndex: { language: {
type: Number, type: String,
default: 10 required: false,
}, default: 'en_US' // https://github.com/nhnent/tui.editor/tree/master/src/js/langs
toolbar: {
type: Array,
default: function() {
return []
}
} }
}, },
data() { data() {
return { return {
simplemde: null, editor: null
hasChange: false }
},
computed: {
editorOptions() {
const options = Object.assign({}, defaultOptions, this.options)
options.initialEditType = this.mode
options.height = this.height
options.language = this.language
return options
} }
}, },
watch: { watch: {
value(val) { value(newValue, preValue) {
if (val === this.simplemde.value() && !this.hasChange) return if (newValue !== preValue && newValue !== this.editor.getValue()) {
this.simplemde.value(val) this.editor.setValue(newValue)
}
},
language(val) {
this.destroyEditor()
this.initEditor()
},
height(newValue) {
this.editor.height(newValue)
},
mode(newValue) {
this.editor.changeMode(newValue)
} }
}, },
mounted() { mounted() {
this.simplemde = new SimpleMDE({ this.initEditor()
element: document.getElementById(this.id),
autoDownloadFontAwesome: false,
autofocus: this.autofocus,
toolbar: this.toolbar.length > 0 ? this.toolbar : undefined,
spellChecker: false,
insertTexts: {
link: ['[', ']( )']
},
// hideIcons: ['guide', 'heading', 'quote', 'image', 'preview', 'side-by-side', 'fullscreen'],
placeholder: this.placeholder
})
if (this.value) {
this.simplemde.value(this.value)
}
this.simplemde.codemirror.on('change', () => {
if (this.hasChange) {
this.hasChange = true
}
this.$emit('input', this.simplemde.value())
})
}, },
destroyed() { destroyed() {
this.simplemde.toTextArea() this.destroyEditor()
this.simplemde = null },
methods: {
initEditor() {
this.editor = new Editor({
el: document.getElementById(this.id),
...this.editorOptions
})
if (this.value) {
this.editor.setValue(this.value)
}
this.editor.on('change', () => {
this.$emit('input', this.editor.getValue())
})
},
destroyEditor() {
if (!this.editor) return
this.editor.off('change')
this.editor.remove()
},
setValue(value) {
this.editor.setValue(value)
},
getValue() {
return this.editor.getValue()
},
setHtml(value) {
this.editor.setHtml(value)
},
getHtml() {
return this.editor.getHtml()
}
} }
} }
</script> </script>
<style scoped>
.simplemde-container>>>.CodeMirror {
min-height: 150px;
line-height: 20px;
}
.simplemde-container>>>.CodeMirror-scroll {
min-height: 150px;
}
.simplemde-container>>>.CodeMirror-code {
padding-bottom: 40px;
}
.simplemde-container>>>.editor-statusbar {
display: none;
}
.simplemde-container>>>.CodeMirror .CodeMirror-code .cm-link {
color: #1890ff;
}
.simplemde-container>>>.CodeMirror .CodeMirror-code .cm-string.cm-url {
color: #2d3b4d;
}
.simplemde-container>>>.CodeMirror .CodeMirror-code .cm-formatting-link-string.cm-url {
padding: 0 2px;
color: #E61E1E;
}
.simplemde-container >>> .editor-toolbar.fullscreen,
.simplemde-container >>> .CodeMirror-fullscreen {
z-index: 1003;
}
</style>

View File

@ -5,6 +5,7 @@
:current-page.sync="currentPage" :current-page.sync="currentPage"
:page-size.sync="pageSize" :page-size.sync="pageSize"
:layout="layout" :layout="layout"
:page-sizes="pageSizes"
:total="total" :total="total"
v-bind="$attrs" v-bind="$attrs"
@size-change="handleSizeChange" @size-change="handleSizeChange"

View File

@ -1,6 +1,6 @@
// Here is a list of the toolbar // Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols // Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
const toolbar = ['bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen'] const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
export default toolbar export default toolbar

View File

@ -65,7 +65,7 @@
如果不提供,将使用默认的[evalFunc](./eval.js) 如果不提供,将使用默认的[evalFunc](./eval.js)
如果提供了evalFunc,那么会用提供的evalFunc去解析data并返回treeTable渲染所需要的值。如何编写一个evalFunc请参考[*eval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/TreeTable/eval.js)或[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/example/table/treeTable/customEval.js) 如果提供了evalFunc,那么会用提供的evalFunc去解析data并返回treeTable渲染所需要的值。如何编写一个evalFunc请参考[*eval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/components/TreeTable/eval.js)或[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)
#### evalArgs #### evalArgs
解析函数的参数,是一个数组 解析函数的参数,是一个数组
@ -76,12 +76,12 @@
如果你的解析函数参数只有`(this.data, this.expandAll)`,那么就可以不用填写evalArgs了 如果你的解析函数参数只有`(this.data, this.expandAll)`,那么就可以不用填写evalArgs了
具体可参考[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/example/table/treeTable/customEval.js)的函数参数和[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/example/table/treeTable/customTreeTable.vue)的`evalArgs`属性值 具体可参考[*customEval.js*](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customEval.js)的函数参数和[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue)的`evalArgs`属性值
## slot ## slot
这是一个自定义列的插槽。 这是一个自定义列的插槽。
默认情况下treeTable只有一行行展示数据的功能。但是一般情况下我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式这时我们就需要自定义列了。请参考[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/example/table/treeTable/customTreeTable.vue)[实例效果](http://panjiachen.github.io/vue-element-admin/#/example/table/custom-tree-table) 默认情况下treeTable只有一行行展示数据的功能。但是一般情况下我们会要给行加上一个操作按钮或者根据当行数据展示不同的样式这时我们就需要自定义列了。请参考[customTreeTable](https://github.com/PanJiaChen/vue-element-admin/blob/master/src/views/table/treeTable/customTreeTable.vue)[实例效果](https://panjiachen.github.io/vue-element-admin/#/table/tree-table)
`slot`和`columns属性`可同时存在,columns里面的数据列会在slot自定义列的左边展示 `slot`和`columns属性`可同时存在,columns里面的数据列会在slot自定义列的左边展示

View File

@ -83,7 +83,7 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
@import "src/styles/mixin.scss"; @import "~@/styles/mixin.scss";
.upload-container { .upload-container {
width: 100%; width: 100%;
position: relative; position: relative;

View File

@ -1,15 +1,40 @@
<template> <template>
<div class="components-container"> <div class="components-container">
<code>Markdown is based on <code>Markdown is based on
<a href="https://github.com/sparksuite/simplemde-markdown-editor" target="_blank">simplemde-markdown-editor</a> Simply encapsulated in Vue. <a href="https://github.com/nhnent/tui.editor" target="_blank">tui.editor</a> Simply encapsulated in Vue.
<a target="_blank" href="https://juejin.im/post/593121aa0ce4630057f70d35#heading-15"> <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/feature/component/markdown-editor.html">
相关文章 </a> Documentation </a>
</code> </code>
<div class="editor-container"> <div class="editor-container">
<markdown-editor id="contentEditor" ref="contentEditor" v-model="content" :height="300" :z-index="20"/> <el-tag class="tag-title">Basic:</el-tag>
<markdown-editor v-model="content" height="300px"/>
</div> </div>
<el-button style="margin-top:80px;" type="primary" icon="el-icon-document" @click="markdown2Html">To HTML</el-button>
<div class="editor-container">
<el-tag class="tag-title">Markdown Mode:</el-tag>
<markdown-editor ref="markdownEditor" v-model="content" :options="{hideModeSwitch:true,previewStyle:'tab'}" height="200px"/>
</div>
<div class="editor-container">
<el-tag class="tag-title">Customize Toolbar:</el-tag>
<markdown-editor
ref="markdownEditor"
v-model="content"
:options="{ toolbarItems: ['heading','bold','italic']}"
/>
</div>
<div class="editor-container">
<el-tag class="tag-title">I18n:</el-tag>
<el-alert :closable="false" title="You can change the language of the admin system to see the effect" type="success"/>
<markdown-editor v-model="content" :language="language" height="300px"/>
</div>
<el-button style="margin-top:80px;" type="primary" icon="el-icon-document" @click="getHtml">Get HTML</el-button>
<div v-html="html"/> <div v-html="html"/>
</div> </div>
</template> </template>
@ -17,32 +42,46 @@
import MarkdownEditor from '@/components/MarkdownEditor' import MarkdownEditor from '@/components/MarkdownEditor'
const content = ` const content = `
**this is test** **This is test**
* vue * vue
* element * element
* webpack * webpack
## Simplemde
` `
export default { export default {
name: 'MarkdownDemo', name: 'MarkdownDemo',
components: { MarkdownEditor }, components: { MarkdownEditor },
data() { data() {
return { return {
content: content, content: content,
html: '' html: '',
languageTypeList: {
'en': 'en_US',
'zh': 'zh_CN',
'es': 'es_ES'
}
}
},
computed: {
language() {
return this.languageTypeList[this.$store.getters.language]
} }
}, },
methods: { methods: {
markdown2Html() { getHtml() {
import('showdown').then(showdown => { this.html = this.$refs.markdownEditor.getHtml()
const converter = new showdown.Converter() console.log(this.html)
this.html = converter.makeHtml(this.content)
})
} }
} }
} }
</script> </script>
<style scoped>
.editor-container{
margin-bottom: 30px;
}
.tag-title{
margin-bottom: 5px;
}
</style>

View File

@ -32,7 +32,8 @@ export default {
}, },
data() { data() {
return { return {
chart: null chart: null,
sidebarElm: null
} }
}, },
watch: { watch: {
@ -55,8 +56,8 @@ export default {
} }
// //
const sidebarElm = document.getElementsByClassName('sidebar-container')[0] this.sidebarElm = document.getElementsByClassName('sidebar-container')[0]
sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler) this.sidebarElm && this.sidebarElm.addEventListener('transitionend', this.sidebarResizeHandler)
}, },
beforeDestroy() { beforeDestroy() {
if (!this.chart) { if (!this.chart) {
@ -66,8 +67,7 @@ export default {
window.removeEventListener('resize', this.__resizeHandler) window.removeEventListener('resize', this.__resizeHandler)
} }
const sidebarElm = document.getElementsByClassName('sidebar-container')[0] this.sidebarElm && this.sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
sidebarElm.removeEventListener('transitionend', this.sidebarResizeHandler)
this.chart.dispose() this.chart.dispose()
this.chart = null this.chart = null

View File

@ -234,7 +234,7 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
@import "src/styles/mixin.scss"; @import "~@/styles/mixin.scss";
.createPost-container { .createPost-container {
position: relative; position: relative;
.createPost-main-container { .createPost-main-container {

View File

@ -51,7 +51,7 @@ export default {
</script> </script>
<style rel="stylesheet/scss" lang="scss" scoped> <style rel="stylesheet/scss" lang="scss" scoped>
@import "src/styles/mixin.scss"; @import "~@/styles/mixin.scss";
.app-wrapper { .app-wrapper {
@include clearfix; @include clearfix;
position: relative; position: relative;

View File

@ -130,15 +130,16 @@ export default {
height: 50px; height: 50px;
margin-right: 30px; margin-right: 30px;
.avatar-wrapper { .avatar-wrapper {
cursor: pointer;
margin-top: 5px; margin-top: 5px;
position: relative; position: relative;
.user-avatar { .user-avatar {
cursor: pointer;
width: 40px; width: 40px;
height: 40px; height: 40px;
border-radius: 10px; border-radius: 10px;
} }
.el-icon-caret-bottom { .el-icon-caret-bottom {
cursor: pointer;
position: absolute; position: absolute;
right: -20px; right: -20px;
top: 25px; top: 25px;

View File

@ -121,11 +121,21 @@ export default {
this.$router.push('/') this.$router.push('/')
}, },
openMenu(tag, e) { openMenu(tag, e) {
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 15 // 15: margin right
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.top = e.clientY
this.visible = true this.visible = true
this.selectedTag = tag this.selectedTag = tag
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
this.left = e.clientX - offsetLeft + 15 // 15: margin right
this.top = e.clientY
}, },
closeMenu() { closeMenu() {
this.visible = false this.visible = false

View File

@ -107,7 +107,6 @@ export default {
}, },
immediate: true immediate: true
} }
}, },
created() { created() {
// window.addEventListener('hashchange', this.afterQRScan) // window.addEventListener('hashchange', this.afterQRScan)