mirror of
https://gitee.com/ssssssss-team/magic-boot.git
synced 2026-07-28 00:00:12 +08:00
删除magic-boot-ui(基于vue-element-admin vue2版本)
This commit is contained in:
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb() {
|
||||
// only show routes with meta.title
|
||||
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
|
||||
if (!this.isDashboard(first)) {
|
||||
matched = [{ path: '/dashboard', meta: { title: '首页' }}].concat(matched)
|
||||
}
|
||||
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
isDashboard(route) {
|
||||
const name = route && route.name
|
||||
if (!name) {
|
||||
return false
|
||||
}
|
||||
return name.trim().toLocaleLowerCase() === '首页'.toLocaleLowerCase()
|
||||
},
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-breadcrumb.el-breadcrumb {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 50px;
|
||||
margin-left: 8px;
|
||||
|
||||
.no-redirect {
|
||||
color: #97a8be;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div v-if="errorLogs.length>0">
|
||||
<el-badge :is-dot="true" style="line-height: 25px;margin-top: -5px;" @click.native="dialogTableVisible=true">
|
||||
<el-button style="padding: 8px 10px;" size="small" type="danger">
|
||||
<svg-icon icon-class="bug" />
|
||||
</el-button>
|
||||
</el-badge>
|
||||
|
||||
<el-dialog :visible.sync="dialogTableVisible" width="80%" append-to-body>
|
||||
<div slot="title">
|
||||
<span style="padding-right: 10px;">Error Log</span>
|
||||
<el-button size="mini" type="primary" icon="el-icon-delete" @click="clearAll">Clear All</el-button>
|
||||
</div>
|
||||
<el-table :data="errorLogs" border>
|
||||
<el-table-column label="Message">
|
||||
<template slot-scope="{row}">
|
||||
<div>
|
||||
<span class="message-title">Msg:</span>
|
||||
<el-tag type="danger">
|
||||
{{ row.err.message }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="message-title" style="padding-right: 10px;">Info: </span>
|
||||
<el-tag type="warning">
|
||||
{{ row.vm.$vnode.tag }} error in {{ row.info }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<span class="message-title" style="padding-right: 16px;">Url: </span>
|
||||
<el-tag type="success">
|
||||
{{ row.url }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Stack">
|
||||
<template slot-scope="scope">
|
||||
{{ scope.row.err.stack }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ErrorLog',
|
||||
data() {
|
||||
return {
|
||||
dialogTableVisible: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
errorLogs() {
|
||||
return this.$store.getters.errorLogs
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clearAll() {
|
||||
this.dialogTableVisible = false
|
||||
this.$store.dispatch('errorLog/clearErrorLog')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.message-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
padding-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<div style="padding: 0 15px;" @click="toggleClick">
|
||||
<svg
|
||||
:class="{'is-active':isActive}"
|
||||
class="hamburger"
|
||||
viewBox="0 0 1024 1024"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
>
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleClick() {
|
||||
this.$emit('toggleClick')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
</style>
|
||||
@@ -1,180 +0,0 @@
|
||||
<template>
|
||||
<div :class="{'show':show}" class="header-search">
|
||||
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
|
||||
<el-select
|
||||
ref="headerSearchSelect"
|
||||
v-model="search"
|
||||
:remote-method="querySearch"
|
||||
filterable
|
||||
default-first-option
|
||||
remote
|
||||
placeholder="Search"
|
||||
class="header-search-select"
|
||||
@change="change"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// fuse is a lightweight fuzzy-search module
|
||||
// make search results more in line with expectations
|
||||
import Fuse from 'fuse.js'
|
||||
import path from 'path'
|
||||
|
||||
export default {
|
||||
name: 'HeaderSearch',
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
options: [],
|
||||
searchPool: [],
|
||||
show: false,
|
||||
fuse: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
routes() {
|
||||
return this.$store.getters.permission_routes
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
routes() {
|
||||
this.searchPool = this.generateRoutes(this.routes)
|
||||
},
|
||||
searchPool(list) {
|
||||
this.initFuse(list)
|
||||
},
|
||||
show(value) {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', this.close)
|
||||
} else {
|
||||
document.body.removeEventListener('click', this.close)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.searchPool = this.generateRoutes(this.routes)
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.show = !this.show
|
||||
if (this.show) {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
|
||||
this.options = []
|
||||
this.show = false
|
||||
},
|
||||
change(val) {
|
||||
this.$router.push(val.path)
|
||||
this.search = ''
|
||||
this.options = []
|
||||
this.$nextTick(() => {
|
||||
this.show = false
|
||||
})
|
||||
},
|
||||
initFuse(list) {
|
||||
this.fuse = new Fuse(list, {
|
||||
shouldSort: true,
|
||||
threshold: 0.4,
|
||||
location: 0,
|
||||
distance: 100,
|
||||
maxPatternLength: 32,
|
||||
minMatchCharLength: 1,
|
||||
keys: [{
|
||||
name: 'title',
|
||||
weight: 0.7
|
||||
}, {
|
||||
name: 'path',
|
||||
weight: 0.3
|
||||
}]
|
||||
})
|
||||
},
|
||||
// Filter out the routes that can be displayed in the sidebar
|
||||
// And generate the internationalized title
|
||||
generateRoutes(routes, basePath = '/', prefixTitle = []) {
|
||||
let res = []
|
||||
|
||||
for (const router of routes) {
|
||||
// skip hidden router
|
||||
if (router.hidden) { continue }
|
||||
|
||||
const data = {
|
||||
path: path.resolve(basePath, router.path),
|
||||
title: [...prefixTitle]
|
||||
}
|
||||
|
||||
if (router.meta && router.meta.title) {
|
||||
data.title = [...data.title, router.meta.title]
|
||||
|
||||
if (router.redirect !== 'noRedirect') {
|
||||
// only push the routes with title
|
||||
// special case: need to exclude parent router without redirect
|
||||
res.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
// recursive child routes
|
||||
if (router.children) {
|
||||
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
|
||||
if (tempRoutes.length >= 1) {
|
||||
res = [...res, ...tempRoutes]
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
querySearch(query) {
|
||||
if (query !== '') {
|
||||
this.options = this.fuse.search(query)
|
||||
} else {
|
||||
this.options = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header-search {
|
||||
font-size: 0 !important;
|
||||
|
||||
.search-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.header-search-select {
|
||||
font-size: 18px;
|
||||
transition: width 0.2s;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
::v-deep .el-input__inner {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&.show {
|
||||
.header-search-select {
|
||||
width: 210px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div ref="jsoneditor" style="height: 800px" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JSONEditor from 'jsoneditor/dist/jsoneditor.js'
|
||||
import 'jsoneditor/dist/jsoneditor.css'
|
||||
|
||||
export default {
|
||||
name: 'JsonEditor',
|
||||
props: {
|
||||
json: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
json(newJson) {
|
||||
if (this.editor) {
|
||||
this.editor.destroy()
|
||||
this.editor = new JSONEditor(this.$refs.jsoneditor, this.options, newJson)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.editor = new JSONEditor(this.$refs.jsoneditor, this.options, this.json)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -1,143 +0,0 @@
|
||||
<template>
|
||||
<el-button
|
||||
v-bind="el_"
|
||||
@click="buttonClick"
|
||||
>
|
||||
{{ el_.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from '@/scripts/auth'
|
||||
|
||||
export default {
|
||||
name: 'MbButton',
|
||||
props: {
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
btnType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
requestMethod: {
|
||||
type: String,
|
||||
default: 'get'
|
||||
},
|
||||
requestUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
requestData: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
beforeConfirm: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
successTips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
failTips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'warning'
|
||||
},
|
||||
afterHandler: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el_: this.el || {},
|
||||
requestMethod_: this.requestMethod,
|
||||
beforeConfirm_: this.beforeConfirm,
|
||||
successTips_: this.successTips,
|
||||
failTips_: this.failTips
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.btnType) {
|
||||
if (this.btnType === 'delete') {
|
||||
this.requestMethod_ = 'post'
|
||||
this.el_.type = 'danger'
|
||||
this.el_.text = '删除'
|
||||
this.el_.icon = 'el-icon-delete'
|
||||
this.beforeConfirm_ = '确定删除吗?'
|
||||
this.successTips_ = '删除成功!'
|
||||
this.failTips_ = '删除失败!'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async buttonClick() {
|
||||
if (this.beforeConfirm_) {
|
||||
this.$confirm(this.beforeConfirm_, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: this.confirmType
|
||||
}).then((res) => {
|
||||
this.buttonClickRequest().then(() => {
|
||||
this.afterHandler()
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
buttonClickRequest() {
|
||||
// var requestOptions = {}
|
||||
// requestOptions.url = this.requestUrl
|
||||
// requestOptions.method = this.requestMethod_
|
||||
// if (requestOptions.method === 'get') {
|
||||
// requestOptions.params = this.requestData
|
||||
// } else {
|
||||
// requestOptions.data = this.requestData
|
||||
// }
|
||||
if (this.isOpen) {
|
||||
return new Promise(() => {
|
||||
window.open(this.$common.getUrl(process.env.VUE_APP_BASE_API + this.requestUrl, this.requestData) + '&token=' + getToken())
|
||||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.requestMethod_ === 'get') {
|
||||
this.$get(this.requestUrl, this.requestData).then(res => {
|
||||
const { data } = res
|
||||
if (data) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.successTips_
|
||||
})
|
||||
} else {
|
||||
this.$message.error(this.failTips_)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
} else {
|
||||
this.$post(this.requestUrl, this.requestData).then(res => {
|
||||
const { data } = res
|
||||
if (data) {
|
||||
this.$message({
|
||||
type: 'success',
|
||||
message: this.successTips_
|
||||
})
|
||||
} else {
|
||||
this.$message.error(this.failTips_)
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<el-dialog v-el-drag-dialog :fullscreen="fullscreen" :width="width" :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" :append-to-body="true" @opened="opened">
|
||||
<slot name="content" />
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<slot name="btns">
|
||||
<el-button @click="dialogVisible = false">
|
||||
关闭
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="confirmLoading" @click="confirmClick">
|
||||
确认
|
||||
</el-button>
|
||||
</slot>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MbDialog',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '50%'
|
||||
},
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
opened: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
confirmLoading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.fullscreen) {
|
||||
document.body.style.setProperty('--el-dialog__wrapper-bottom', '0vh')
|
||||
document.body.style.setProperty('--el-dialog__wrapper-top', '0vh')
|
||||
document.body.style.setProperty('--el-dialog__body-max-height', '100vh')
|
||||
} else {
|
||||
document.body.style.setProperty('--el-dialog__wrapper-bottom', '15vh')
|
||||
document.body.style.setProperty('--el-dialog__wrapper-top', '15vh')
|
||||
document.body.style.setProperty('--el-dialog__body-max-height', '60vh')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirmClick() {
|
||||
this.$emit('confirm-click', this)
|
||||
},
|
||||
loading(){
|
||||
this.confirmLoading = true
|
||||
},
|
||||
hideLoading(){
|
||||
this.confirmLoading = false
|
||||
},
|
||||
show() {
|
||||
this.dialogVisible = true
|
||||
},
|
||||
hide() {
|
||||
this.dialogVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-dialog__wrapper{
|
||||
padding-bottom: var(--el-dialog__wrapper-bottom);
|
||||
padding-top: var(--el-dialog__wrapper-top);
|
||||
overflow: hidden;
|
||||
}
|
||||
.el-dialog__wrapper >>> .el-dialog{
|
||||
margin-top: 0vh!important;
|
||||
}
|
||||
.el-dialog__wrapper >>> .el-dialog__body{
|
||||
max-height: var(--el-dialog__body-max-height);
|
||||
overflow: auto;
|
||||
padding: 25px!important;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row style="margin-bottom: 6px">
|
||||
<el-button type="primary" @click="tableOptions.data.push({})">添加一行</el-button>
|
||||
</el-row>
|
||||
<mb-table v-bind="tableOptions">
|
||||
<template v-for="col in cols" #[col.field]="{ index }">
|
||||
<el-input v-if="col.type === 'input'" v-bind="col.properties" v-model="tableOptions.data[index][col.field]" @change="dataChange" />
|
||||
<mb-select v-else-if="col.type === 'select'" v-bind="col.properties" v-model="tableOptions.data[index][col.field]" @change="dataChange" />
|
||||
</template>
|
||||
</mb-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'MbEditorTable',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
cols: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
showNo: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tableOptions: {
|
||||
data: [],
|
||||
cols: [],
|
||||
showNo: this.showNo
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
for (var i in this.cols) {
|
||||
var col = this.cols[i]
|
||||
this.tableOptions.cols.push({
|
||||
type: 'dynamic',
|
||||
field: col.field,
|
||||
title: col.title
|
||||
})
|
||||
}
|
||||
this.tableOptions.cols.push({
|
||||
title: '操作',
|
||||
type: 'btns',
|
||||
width: 85,
|
||||
fixed: 'right',
|
||||
btns: [{
|
||||
title: '删除',
|
||||
type: 'danger',
|
||||
click: (row, index) => {
|
||||
this.tableOptions.data.splice(index, 1)
|
||||
}
|
||||
}]
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
dataChange() {
|
||||
console.log('更新')
|
||||
this.$emit('update:value', this.tableOptions.data)
|
||||
this.$emit('change', this.tableOptions.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<div class="filter-container">
|
||||
<el-form :inline="true" @keyup.enter.native="search">
|
||||
<el-form-item :label="it.label" v-for="it in where">
|
||||
<el-input v-if="it.type == 'input'" @input="input(it.input)" v-model="it.value" :placeholder="it.placeholder || ('请输入' + it.label)" style="width: 200px;" class="filter-item" />
|
||||
<mb-select v-else-if="it.type == 'select'" v-model="it.value" :placeholder="'请输入' + it.label" v-bind="it.properties" />
|
||||
<el-date-picker
|
||||
v-else-if="it.type == 'date' || it.type == 'datetime' || it.type == 'daterange' || it.type == 'datetimerange'"
|
||||
v-model="it.value"
|
||||
align="right"
|
||||
:format="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:value-format="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:type="it.type"
|
||||
:start-placeholder="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:end-placeholder="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
:placeholder="it.type.startsWith('datetime') ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd'"
|
||||
>
|
||||
</el-date-picker>
|
||||
<component v-else :is="it.type" v-model="it.value" v-bind="it.properties" />
|
||||
</el-form-item>
|
||||
<el-button class="filter-item" type="primary" icon="el-icon-search" @click="search">
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button class="filter-item" icon="el-icon-delete" @click="reset">
|
||||
清空
|
||||
</el-button>
|
||||
<slot name="btns" />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MbSearch",
|
||||
props: {
|
||||
where: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
notReset: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
input(input){
|
||||
if(input){
|
||||
this.$emit('search')
|
||||
}
|
||||
},
|
||||
search(){
|
||||
for(var key in this.where){
|
||||
if(this.where[key] instanceof Object){
|
||||
if(this.where[key].type.startsWith('date') && this.where[key].value instanceof Array){
|
||||
this.where[key].value = this.where[key].value.join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$emit('search')
|
||||
for(var key in this.where){
|
||||
if(this.where[key] instanceof Object){
|
||||
if(this.where[key].type.startsWith('date')){
|
||||
this.where[key].value = this.where[key].value.split(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
reset() {
|
||||
for(var key in this.where){
|
||||
if(this.notReset.indexOf(key) == -1){
|
||||
if(this.where[key] instanceof Object){
|
||||
this.where[key].value = null
|
||||
}else{
|
||||
this.where[key] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => this.$emit('search'))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-select v-if="mbType === 'select'" v-model="selectValue" v-bind="el" :style="{ width }" :placeholder="placeholder || '请选择'" filterable clearable>
|
||||
<el-option
|
||||
v-for="item in selectList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-radio-group v-if="mbType === 'radio'" v-model="selectValue">
|
||||
<el-radio v-for="item in selectList" :key="item.value" :label="item.value">
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'MbSelect',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// eslint-disable-next-line vue/require-prop-types
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
},
|
||||
allOption: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
labelField: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
mbType: {
|
||||
type: String,
|
||||
default: 'select'
|
||||
},
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectList: [],
|
||||
selectValue: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
type(newVal) {
|
||||
if (this.value instanceof Array || this.value.toString().indexOf(',') !== -1) {
|
||||
this.selectValue = []
|
||||
} else {
|
||||
this.selectValue = ''
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
value(value) {
|
||||
this.loadData()
|
||||
},
|
||||
selectValue(value) {
|
||||
if (this.el && this.el.multiple && value.length > 0) {
|
||||
value = value.join(',')
|
||||
}
|
||||
this.$emit('update:value', value)
|
||||
this.$emit('change', value)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
if (this.value || this.value == '') {
|
||||
if ((!(this.value instanceof Array) && this.value.toString().indexOf(',') !== -1)) {
|
||||
this.selectValue = this.value.split(',')
|
||||
} else {
|
||||
if (this.el && this.el.multiple && !(this.value instanceof Array)) {
|
||||
this.selectValue = [this.value]
|
||||
} else {
|
||||
this.selectValue = this.value
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this.el && this.el.multiple) {
|
||||
this.selectValue = []
|
||||
}
|
||||
}
|
||||
if (this.data && this.data.length > 0) {
|
||||
this.listConcat(this.handlerData(this.data))
|
||||
} else if (this.url) {
|
||||
this.$get(this.url, this.params).then(res => {
|
||||
this.listConcat(this.handlerData(res.data.list))
|
||||
})
|
||||
} else {
|
||||
this.listConcat(this.$common.getDictType(this.type))
|
||||
}
|
||||
},
|
||||
listConcat(dictData) {
|
||||
if (this.allOption) {
|
||||
this.selectList = [{
|
||||
value: '',
|
||||
label: '全部'
|
||||
}]
|
||||
this.selectList = this.selectList.concat(dictData)
|
||||
} else {
|
||||
this.selectList = dictData
|
||||
}
|
||||
},
|
||||
handlerData(data) {
|
||||
var newData = []
|
||||
data.forEach(it => {
|
||||
newData.push({
|
||||
label: it[this.labelField],
|
||||
value: it[this.valueField].toString()
|
||||
})
|
||||
})
|
||||
return newData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -1,57 +0,0 @@
|
||||
<template>
|
||||
<el-table-column
|
||||
:key="col.field"
|
||||
:label="col.title"
|
||||
:prop="col.field"
|
||||
:align="col.align || 'center'"
|
||||
:width="col.width"
|
||||
:fixed="col.fixed"
|
||||
:sortable="col.sortable"
|
||||
>
|
||||
<template v-if="!col.cols" slot-scope="scope">
|
||||
<span v-if="col.templet" v-html="col.templet(scope.row)" />
|
||||
<span v-else-if="col.dictType">
|
||||
{{ $common.getDictLabel(col.dictType, scope.row[col.field] + '') }}
|
||||
</span>
|
||||
<slot v-else-if="col.type == 'dynamic'" :name="col.field" :row="scope.row" :index="scope.$index" />
|
||||
<el-switch
|
||||
v-else-if="col.type == 'switch'"
|
||||
v-model="scope.row[col.field]"
|
||||
:active-value="col.activeValue || 1"
|
||||
:inactive-value="col.inactiveValue || 0"
|
||||
@change="col.change(scope.row)"
|
||||
/>
|
||||
<div v-else-if="col.type == 'btns'">
|
||||
<template v-for="btn in col.btns">
|
||||
<el-button v-if="btn.if === undefined ? true : btn.if(scope.row)" :icon="btn.icon" :key="btn.title" v-permission="btn.permission" :type="btn.type" :size="btn.size || 'mini'" :class="btn.class" @click="btn.click(scope.row, scope.$index)">
|
||||
{{ btn.title }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
<el-image v-else-if="col.type === 'image'" :src="scope.row[col.field]" :preview-src-list="[scope.row[col.field]]" />
|
||||
<span v-else-if="col.type === 'html'" v-html="scope.row[col.field]"></span>
|
||||
<span v-else-if="col.click">
|
||||
<a style="color: blue" @click="col.click(scope.row)">{{ scope.row[col.field] }}</a>
|
||||
</span>
|
||||
<span v-else-if="col.field">{{ scope.row[col.field] }}</span>
|
||||
</template>
|
||||
<mb-table-column v-for="(col2, j) in col.cols" :key="j" :col="col2">
|
||||
<template v-for="(value, key) in $scopedSlots" #[key]="{ row, index }">
|
||||
<slot :row="row" :index="index" :name="key" />
|
||||
</template>
|
||||
</mb-table-column>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'MbTableColumn',
|
||||
props: {
|
||||
col: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,236 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
:key="tableKey"
|
||||
v-loading="listLoading"
|
||||
:data="list"
|
||||
border
|
||||
fit
|
||||
highlight-current-row
|
||||
v-bind="el"
|
||||
style="width: 100%;"
|
||||
@sort-change="sortChange"
|
||||
@selection-change="selectionChange"
|
||||
>
|
||||
|
||||
<el-table-column v-if="selection" align="center" type="selection" width="50" />
|
||||
|
||||
<el-table-column v-if="showNo" label="序号" prop="num" align="center" width="65">
|
||||
<template slot-scope="row">
|
||||
<span>{{ row.$index+1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<mb-table-column v-for="(col, i) in cols" :key="i" :col="col">
|
||||
<template v-for="(value, key) in $scopedSlots" #[key]="{ row, index }">
|
||||
<slot :row="row" :index="index" :name="key" />
|
||||
</template>
|
||||
</mb-table-column>
|
||||
|
||||
<el-empty :description="emptyText" slot="empty" />
|
||||
|
||||
</el-table>
|
||||
<pagination v-show="total > 0 && page" :total="total || 0" :page.sync="listCurrent" :limit.sync="limit" @pagination="handlerPagination" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/scripts/request'
|
||||
import Pagination from '@/components/Pagination'
|
||||
import MbTableColumn from './mb-table-column.vue'
|
||||
|
||||
export default {
|
||||
name: 'MbTable',
|
||||
components: { Pagination, MbTableColumn },
|
||||
props: {
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
page: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
done: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
where: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
showNo: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selection: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
method: {
|
||||
type: String,
|
||||
default: 'get'
|
||||
},
|
||||
cols: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
listCurrent: 1,
|
||||
total: 0,
|
||||
list: [],
|
||||
listLoading: false,
|
||||
tableKey: 0,
|
||||
newWhere: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data() {
|
||||
this.listCurrent = 1
|
||||
this.handlerData()
|
||||
},
|
||||
where: {
|
||||
handler(){
|
||||
this.renderWhere()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.renderWhere()
|
||||
},
|
||||
mounted() {
|
||||
this.keyup()
|
||||
if (this.data) {
|
||||
this.handlerData()
|
||||
}
|
||||
if (this.url) {
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderWhere(){
|
||||
this.newWhere = this.$common.renderWhere(this.where)
|
||||
},
|
||||
getList() {
|
||||
this.renderWhere()
|
||||
this.listLoading = true
|
||||
if (this.page) {
|
||||
this.newWhere.current = this.listCurrent
|
||||
this.newWhere.size = this.limit
|
||||
} else {
|
||||
this.newWhere.size = 99999999
|
||||
}
|
||||
request({
|
||||
url: this.url,
|
||||
method: this.method,
|
||||
params: this.newWhere
|
||||
}).then(res => {
|
||||
const { data } = res
|
||||
this.total = data.total
|
||||
this.list = data.list
|
||||
this.listLoading = false
|
||||
this.done()
|
||||
})
|
||||
},
|
||||
sortChange(column) {
|
||||
let order = column.order
|
||||
if (order) {
|
||||
order = order === 'descending' ? 'desc' : ''
|
||||
order = column.prop + ' ' + order
|
||||
} else {
|
||||
order = null
|
||||
}
|
||||
this.newWhere.orderBy = order
|
||||
this.reloadList()
|
||||
},
|
||||
selectionChange(columns) {
|
||||
this.$emit('selection-change', columns)
|
||||
},
|
||||
reloadList() {
|
||||
if (this.url) {
|
||||
this.newWhere.current = 1
|
||||
this.listCurrent = 1
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
handlerData() {
|
||||
this.listLoading = true
|
||||
this.total = this.data.length
|
||||
var currPageData = []
|
||||
this.data.forEach((it, i) => {
|
||||
if (i >= ((this.listCurrent - 1) * this.limit) && i < (this.listCurrent * this.limit) && currPageData.length < this.limit) {
|
||||
currPageData.push(it)
|
||||
}
|
||||
})
|
||||
this.list = currPageData
|
||||
this.done()
|
||||
this.listLoading = false
|
||||
},
|
||||
handlerPagination() {
|
||||
if (this.url) {
|
||||
this.getList()
|
||||
}
|
||||
if (this.data) {
|
||||
this.handlerData()
|
||||
}
|
||||
},
|
||||
keyup(){
|
||||
document.onkeyup = (e) => {
|
||||
if(e.target.nodeName != 'INPUT'){
|
||||
if (e && e.keyCode == 37) {
|
||||
if(this.listCurrent != 1){
|
||||
this.listCurrent -= 1
|
||||
this.handlerPagination()
|
||||
}
|
||||
} else if (e && e.keyCode == 39) {
|
||||
if(this.listCurrent != parseInt((this.total + this.limit - 1) / this.limit)){
|
||||
this.listCurrent += 1
|
||||
this.handlerPagination()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-image >>> .el-image__inner {
|
||||
max-height: 100px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.el-table-column--selection .cell {
|
||||
padding:0px 15px!important;
|
||||
}
|
||||
.el-table th {
|
||||
background: #F5F7FA;
|
||||
}
|
||||
</style>
|
||||
@@ -1,149 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 5px;" v-if="expand || checked">
|
||||
<el-button v-if="expand" type="primary" size="mini" icon="el-icon-sort" plain @click="doExpand">展开/折叠</el-button>
|
||||
<el-button v-if="checked" type="primary" size="mini" icon="el-icon-check" plain @click="() => { treeAllChecked = !treeAllChecked; checkedAll(searchData, treeAllChecked) }">全选/全不选</el-button>
|
||||
</div>
|
||||
<div style="margin-bottom: 5px;" v-if="search">
|
||||
<el-input v-model="searchValue" placeholder="输入关键字进行过滤" @input="searchTree" :style="{ width: searchWidth }" />
|
||||
</div>
|
||||
<el-tree
|
||||
v-if="refreshTree"
|
||||
ref="tree"
|
||||
:data="searchData"
|
||||
v-bind="el"
|
||||
node-key="id"
|
||||
:default-expand-all="defaultExpandAll"
|
||||
:default-checked-keys="checkedIds"
|
||||
@check-change="checkChange"
|
||||
@node-click="nodeClick"
|
||||
:props="defaultProps"
|
||||
:style="{ 'max-height': maxHeight ? maxHeight : '100%' }"
|
||||
style="overflow: auto"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MbTree',
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
selectValues: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
el: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
expand: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
search: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchWidth: {
|
||||
type: String,
|
||||
default: '230px'
|
||||
},
|
||||
checkedIds: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async selectValues() {
|
||||
await this.loadTreeData()
|
||||
this.checkedAll(this.searchData, false)
|
||||
var values = this.selectValues.split(',');
|
||||
for(var i in values){
|
||||
this.$refs.tree.setChecked(values[i], true, false)
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
treeData: [],
|
||||
searchData: [],
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'name'
|
||||
},
|
||||
defaultExpandAll: true,
|
||||
refreshTree: true,
|
||||
treeAllChecked: false,
|
||||
searchValue: ''
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadTreeData()
|
||||
},
|
||||
methods: {
|
||||
searchTree() {
|
||||
if(this.searchValue){
|
||||
this.searchData = this.$treeTable.recursionSearch(['name'], this.$common.copyNew(this.treeData), this.searchValue, false)
|
||||
}else{
|
||||
this.searchData = this.treeData
|
||||
}
|
||||
},
|
||||
doExpand() {
|
||||
this.refreshTree = false
|
||||
this.defaultExpandAll = !this.defaultExpandAll
|
||||
this.$nextTick(() => this.refreshTree = true)
|
||||
},
|
||||
async loadTreeData() {
|
||||
if(this.treeData.length == 0){
|
||||
await this.$get(this.url, this.params).then((res) => {
|
||||
this.treeData = res.data.list
|
||||
this.searchData = this.treeData
|
||||
})
|
||||
}
|
||||
},
|
||||
getTree() {
|
||||
return this.$refs.tree
|
||||
},
|
||||
checkChange(node) {
|
||||
var selectMenus = []
|
||||
var checkedNodes = this.$refs.tree.getCheckedNodes(false, true)
|
||||
for (var i = 0; i < checkedNodes.length; i++) {
|
||||
selectMenus.push(checkedNodes[i].id)
|
||||
}
|
||||
this.$emit('update:select-values', selectMenus.join(','))
|
||||
this.$emit('check-change', selectMenus.join(','))
|
||||
},
|
||||
nodeClick(param1, param2, param3){
|
||||
this.$emit('node-click', param1, param2, param3)
|
||||
},
|
||||
checkedAll(children, checked) {
|
||||
if (this.$refs.tree) {
|
||||
for (var i in children) {
|
||||
var id = children[i].id
|
||||
if(children[i].children && children[i].children.length > 0){
|
||||
this.checkedAll(children[i].children, checked)
|
||||
}
|
||||
this.$refs.tree.setChecked(id, checked, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<vue-ueditor-wrap v-model="content" :config="editorConfig" editor-id="mb-ueditor" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueUeditorWrap from 'vue-ueditor-wrap'
|
||||
|
||||
export default {
|
||||
name: 'MbUeditor',
|
||||
components: {
|
||||
VueUeditorWrap
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
content: '',
|
||||
editorConfig: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
content(value) {
|
||||
this.$emit('change', value)
|
||||
},
|
||||
value(value) {
|
||||
this.content = value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.content = this.value
|
||||
this.editorConfig = this.config || {}
|
||||
this.editorConfig.UEDITOR_HOME_URL = this.editorConfig.UEDITOR_HOME_URL || '/UEditor/'
|
||||
this.editorConfig.serverUrl = this.editorConfig.serverUrl || process.env.VUE_APP_BASE_API + 'ueditor/main'
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -1,230 +0,0 @@
|
||||
<template>
|
||||
<el-upload
|
||||
:id="uploadDomId"
|
||||
class="upload-demo"
|
||||
:action="action"
|
||||
:headers="headers"
|
||||
:on-preview="handlePreview"
|
||||
:on-remove="handleRemove"
|
||||
:before-remove="beforeRemove"
|
||||
:multiple="multiple"
|
||||
:limit="limit"
|
||||
:on-exceed="handleExceed"
|
||||
:file-list="fileList"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
:on-success="handleAvatarSuccess"
|
||||
>
|
||||
<el-button size="small" type="primary" :disabled="!multiple && fileList.length == 1">点击上传</el-button>
|
||||
<div slot="tip" class="el-upload__tip">支持上传{{ getSettingSuffixs().replaceAll(',', ',') }}文件,且不超过{{ maxFileSize }}MB</div>
|
||||
</el-upload>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getToken } from '@/scripts/auth'
|
||||
export default {
|
||||
name: 'MbUploadFile',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
maxFileSize: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
externalId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
externalType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
formats: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
acceptList: {
|
||||
image: 'png,jpg,gif,jpeg',
|
||||
wps: 'pdf,pptx,xls,xlsx,csv,docx,doc',
|
||||
compress: 'zip,rar,7z',
|
||||
video: 'avi,flv,mp4,mpeg,mov'
|
||||
},
|
||||
imageUrl: '',
|
||||
action: process.env.VUE_APP_BASE_API + 'file/upload',
|
||||
headers: {
|
||||
token: getToken()
|
||||
},
|
||||
urls: [],
|
||||
uploadDomId: Math.random(),
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.renderFile()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.externalId) {
|
||||
this.$get('file/files', { externalId: this.externalId, externalType: this.externalType }).then(res => {
|
||||
const { data } = res
|
||||
this.fileList = data
|
||||
})
|
||||
this.action = this.action + `?externalId=${this.externalId}&externalType=${this.externalType}`
|
||||
} else {
|
||||
this.renderFile()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderFile() {
|
||||
if (this.value instanceof Array && this.value.length > 0) {
|
||||
this.fileList = this.value.map(it => {
|
||||
return {
|
||||
name: it.substring(it.lastIndexOf('/') + 1),
|
||||
response: {
|
||||
data: {
|
||||
url: it
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (this.value) {
|
||||
this.fileList.push({
|
||||
name: this.value.substring(this.value.lastIndexOf('/') + 1),
|
||||
response: {
|
||||
data: {
|
||||
url: this.value
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
handleRemove(file, fileList) {
|
||||
var url = file.response.data.url
|
||||
this.urls.splice(this.urls.indexOf(url), 1)
|
||||
this.fileList.forEach((it, i) => {
|
||||
if (it && it.response.data.url.indexOf(url) !== -1) {
|
||||
this.fileList.splice(i, 1)
|
||||
}
|
||||
})
|
||||
if (this.multiple) {
|
||||
this.$emit('update:value', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
document.getElementById(this.uploadDomId).getElementsByClassName('el-upload__input')[0].removeAttribute('disabled')
|
||||
this.$emit('update:value', '')
|
||||
this.$emit('change', '')
|
||||
}
|
||||
this.$get('file/delete', { url: encodeURI(url) })
|
||||
},
|
||||
handlePreview(file) {
|
||||
window.open(this.$filePrefix + file.response.data.url)
|
||||
},
|
||||
handleExceed(files, fileList) {
|
||||
this.$message.warning(`当前限制选择 ${this.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
|
||||
},
|
||||
beforeRemove(file, fileList) {
|
||||
return this.$confirm(`确定移除 ${file.name}?`)
|
||||
},
|
||||
handleAvatarSuccess(res, file, fileList) {
|
||||
if (res.data) {
|
||||
if (this.multiple) {
|
||||
this.urls.push(res.data.url)
|
||||
this.$emit('update:value', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
document.getElementById(this.uploadDomId).getElementsByClassName('el-upload__input')[0].setAttribute('disabled', '')
|
||||
this.$emit('update:value', res.data.url)
|
||||
this.$emit('change', res.data.url)
|
||||
}
|
||||
}
|
||||
},
|
||||
getSettingSuffixs() {
|
||||
if (this.formats) {
|
||||
return this.formats
|
||||
}
|
||||
var suffixs = this.acceptList[this.accept]
|
||||
if (!suffixs) {
|
||||
suffixs = this.getAllSuffixs()
|
||||
}
|
||||
return suffixs
|
||||
},
|
||||
beforeAvatarUpload(file, fileList) {
|
||||
var fileName = file.name
|
||||
var accepts = this.accept.split(',')
|
||||
if (accepts) {
|
||||
for (var i = 0; i < accepts.length; i++) {
|
||||
if (!this.validAccept(fileName, accepts[i])) {
|
||||
this.$message.error('上传文件格式只能为:' + this.getSettingSuffixs().replaceAll(',', ','))
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.validAccept(fileName, 'null')) {
|
||||
this.$message.error('上传文件格式只能为:' + this.getAllSuffixs().replaceAll(',', ','))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const isLt2M = file.size / 1024 / 1024 < this.maxFileSize
|
||||
if (!isLt2M) {
|
||||
this.$message.error(`上传文件大小不能超过 ${this.maxFileSize}MB!`)
|
||||
return isLt2M
|
||||
}
|
||||
},
|
||||
getAllSuffixs() {
|
||||
var suffixs = ''
|
||||
for (const key in this.acceptList) {
|
||||
suffixs += this.acceptList[key] + ','
|
||||
}
|
||||
suffixs = suffixs.substring(0, suffixs.length - 1)
|
||||
return suffixs
|
||||
},
|
||||
validAccept(fileName, accept) {
|
||||
if (this.formats) {
|
||||
return this.validEndsWith(fileName, this.formats)
|
||||
}
|
||||
if (accept && this.acceptList[accept]) {
|
||||
return this.validEndsWith(fileName, this.acceptList[accept])
|
||||
} else {
|
||||
return this.validEndsWith(fileName, this.getAllSuffixs())
|
||||
}
|
||||
},
|
||||
validEndsWith(fileName, suffixs) {
|
||||
suffixs = suffixs.split(',')
|
||||
for (var i = 0; i < suffixs.length; i++) {
|
||||
const suffix = suffixs[i]
|
||||
if (fileName.toLowerCase().endsWith('.' + suffix)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,358 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<vuedraggable
|
||||
v-model="urls"
|
||||
class="vue-draggable"
|
||||
tag="div"
|
||||
draggable=".draggable-item"
|
||||
@end="onDragEnd"
|
||||
>
|
||||
<div
|
||||
v-for="(url, index) in urls"
|
||||
:key="index"
|
||||
class="draggable-item"
|
||||
:style="{ width: width.replace('px', '') + 'px', height: height.replace('px', '') + 'px' }"
|
||||
>
|
||||
<el-image
|
||||
:src="$filePrefix + url"
|
||||
:preview-src-list="[$filePrefix + url]"
|
||||
/>
|
||||
<div class="tools">
|
||||
<div class="shadow" @click="handleRemove(url)">
|
||||
<i class="el-icon-delete" />
|
||||
</div>
|
||||
<div class="shadow" @click="beforeCropper(url)">
|
||||
<i class="el-icon-scissors" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
v-if="(!multiple && urls.length == 0) || (multiple && urls.length < limit)"
|
||||
slot="footer"
|
||||
ref="uploadRef"
|
||||
class="uploadBox"
|
||||
:style="{ width: width.replace('px', '') + 'px', height: height.replace('px', '') + 'px' }"
|
||||
:action="action"
|
||||
:file-list="fileList"
|
||||
:headers="headers"
|
||||
accept=".jpg,.jpeg,.png,.gif"
|
||||
:show-file-list="false"
|
||||
:multiple="multiple"
|
||||
:limit="limit"
|
||||
:on-success="handleAvatarSuccess"
|
||||
:on-exceed="onExceed"
|
||||
>
|
||||
<i class="el-icon-plus uploadIcon">
|
||||
<span v-show="isUploading" class="uploading">正在上传...</span>
|
||||
<span
|
||||
v-if="!isUploading && limit && limit!==99 && multiple"
|
||||
class="limitTxt"
|
||||
>最多{{ limit }}张</span>
|
||||
</i>
|
||||
</el-upload>
|
||||
</vuedraggable>
|
||||
<mb-dialog ref="cropperDialog" @confirm-click="cropper">
|
||||
<template #content>
|
||||
<div class="cropper-content">
|
||||
<div class="cropper" style="text-align:center">
|
||||
<vue-cropper
|
||||
ref="cropper"
|
||||
v-bind="cropperOption"
|
||||
:output-size="cropperOption.outputSize === undefined ? 0.8 : cropperOption.outputSize"
|
||||
:output-type="cropperOption.outputType === undefined ? 'jpeg' : cropperOption.outputType"
|
||||
:can-move="cropperOption.canMove === undefined ? true : cropperOption.canMove"
|
||||
:can-move-box="cropperOption.canMoveBox === undefined ? true : cropperOption.canMoveBox"
|
||||
:auto-crop="cropperOption.autoCrop === undefined ? true : cropperOption.autoCrop"
|
||||
:center-box="cropperOption.centerBox === undefined ? true : cropperOption.centerBox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</mb-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueCropper } from 'vue-cropper'
|
||||
import vuedraggable from 'vuedraggable'
|
||||
import { getToken } from '@/scripts/auth'
|
||||
|
||||
export default {
|
||||
name: 'MbUploadImage',
|
||||
components: { vuedraggable, VueCropper },
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false
|
||||
},
|
||||
externalId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
externalType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
cropperConfig: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '100'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
action: process.env.VUE_APP_BASE_API + 'file/upload',
|
||||
headers: {
|
||||
token: getToken()
|
||||
},
|
||||
dialogImageUrl: '',
|
||||
dialogVisible: false,
|
||||
disabled: false,
|
||||
isUploading: false,
|
||||
cropperOption: {},
|
||||
urls: [],
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
if (newValue instanceof Array) {
|
||||
this.urls = newValue
|
||||
this.fileList = this.urls.map(it => { return { response: { data: { url: it }}} })
|
||||
} else {
|
||||
if (newValue && this.urls.length === 0) {
|
||||
this.urls.push(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cropperOption = this.cropperConfig || {}
|
||||
this.cropperOption.img = ''
|
||||
if (this.externalId) {
|
||||
this.$get('file/files', { externalId: this.externalId, externalType: this.externalType }).then(res => {
|
||||
this.urls = res.data
|
||||
})
|
||||
this.action = this.action + `?externalId=${this.externalId}&externalType=${this.externalType}`
|
||||
} else {
|
||||
if (this.value instanceof Array) {
|
||||
this.urls = this.value
|
||||
this.fileList = this.urls.map(it => { return { response: { data: { url: it }}} })
|
||||
} else {
|
||||
if (this.value) {
|
||||
this.urls.push(this.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRemove(url) {
|
||||
this.urls.splice(this.urls.indexOf(url), 1)
|
||||
this.fileList.forEach((it, i) => {
|
||||
if (it && it.response.data.url.indexOf(url) !== -1) {
|
||||
this.fileList.splice(i, 1)
|
||||
}
|
||||
})
|
||||
this.$get('file/delete', { url: encodeURI(url) })
|
||||
if (this.multiple) {
|
||||
this.$emit('update:value', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
this.$emit('update:value', '')
|
||||
this.$emit('change', '')
|
||||
}
|
||||
},
|
||||
handlePictureCardPreview(file) {
|
||||
this.dialogImageUrl = file.url
|
||||
this.dialogVisible = true
|
||||
},
|
||||
handleDownload(file) {
|
||||
console.log(file)
|
||||
},
|
||||
handleAvatarSuccess(res, file, fileList) {
|
||||
this.fileList = fileList
|
||||
if (res.data) {
|
||||
this.urls.push(res.data.url)
|
||||
if (this.multiple) {
|
||||
this.$emit('update:value', this.urls)
|
||||
this.$emit('change', this.urls)
|
||||
} else {
|
||||
this.$emit('update:value', res.data.url)
|
||||
this.$emit('change', res.data.url)
|
||||
}
|
||||
this.onDragEnd()
|
||||
} else {
|
||||
this.$message({ type: 'error', message: res.msg })
|
||||
}
|
||||
this.isUploading = false
|
||||
},
|
||||
onDragEnd() {
|
||||
var newUrls = []
|
||||
this.urls.forEach(url => {
|
||||
newUrls.push(encodeURI(url))
|
||||
})
|
||||
this.$get('file/resort', { urls: newUrls.join(',') })
|
||||
},
|
||||
onExceed() {
|
||||
this.$message({
|
||||
type: 'warning',
|
||||
message: `图片超限,最多可上传${this.limit}张图片`
|
||||
})
|
||||
},
|
||||
beforeCropper(url) {
|
||||
this.cropperOption.img = this.$filePrefix + url
|
||||
this.cropperOption.relativeImg = url
|
||||
this.$refs.cropperDialog.show()
|
||||
},
|
||||
cropper() {
|
||||
this.$refs.cropper.getCropBlob((data) => {
|
||||
var dataFile = new File([data], this.cropperOption.relativeImg.substring(this.cropperOption.relativeImg.lastIndexOf('/') + 1), { type: data.type, lastModified: Date.now() })
|
||||
var formData = new FormData()
|
||||
formData.append('file', dataFile)
|
||||
formData.append('url', encodeURI(this.cropperOption.relativeImg))
|
||||
this.$request({
|
||||
url: 'file/cropper',
|
||||
method: 'post',
|
||||
data: formData
|
||||
}).then(res => {
|
||||
this.urls.forEach((it, i) => {
|
||||
if (this.cropperOption.img.indexOf(it) !== -1) {
|
||||
this.$set(this.urls, i, res.data.url)
|
||||
this.$refs.cropperDialog.hide()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.vue-draggable >>> .el-upload {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 上传按钮
|
||||
.uploadIcon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #c0ccda;
|
||||
background-color: #fbfdff;
|
||||
border-radius: 6px;
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
|
||||
.limitTxt,
|
||||
.uploading {
|
||||
position: absolute;
|
||||
bottom: 10%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽
|
||||
.vue-draggable {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
.draggable-item {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.el-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.tools {
|
||||
position: absolute;
|
||||
top:0px;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
}
|
||||
.shadow {
|
||||
display: inline-block;
|
||||
background-color: rgba(0,0,0,.5);
|
||||
opacity: 0;
|
||||
transition: opacity .3s;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
.shadow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.hideShadow {
|
||||
.shadow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.single {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.draggable-item {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
&.maxHidden {
|
||||
.uploadBox {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
// el-image
|
||||
.el-image-viewer__wrapper {
|
||||
.el-image-viewer__mask {
|
||||
opacity: .8;
|
||||
}
|
||||
.el-icon-circle-close {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.cropper-content {
|
||||
.cropper {
|
||||
width: auto;
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<div :class="{'hidden':hidden}" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
:current-page.sync="currentPage"
|
||||
:page-size.sync="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
v-bind="$attrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { scrollTo } from '@/scripts/scroll-to'
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:page', val)
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:limit', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { page: this.currentPage, limit: val })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { page: val, limit: this.pageSize })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
|
||||
<div class="rightPanel-background" />
|
||||
<div class="rightPanel">
|
||||
<div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
|
||||
<i :class="show?'el-icon-close':'el-icon-setting'" />
|
||||
</div>
|
||||
<div class="rightPanel-items">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addClass, removeClass } from '@/scripts'
|
||||
|
||||
export default {
|
||||
name: 'RightPanel',
|
||||
props: {
|
||||
clickNotClose: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
buttonTop: {
|
||||
default: 250,
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
theme() {
|
||||
return this.$store.state.settings.theme
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(value) {
|
||||
if (value && !this.clickNotClose) {
|
||||
this.addEventClick()
|
||||
}
|
||||
if (value) {
|
||||
addClass(document.body, 'showRightPanel')
|
||||
} else {
|
||||
removeClass(document.body, 'showRightPanel')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.insertToBody()
|
||||
},
|
||||
beforeDestroy() {
|
||||
const elx = this.$refs.rightPanel
|
||||
elx.remove()
|
||||
},
|
||||
methods: {
|
||||
addEventClick() {
|
||||
window.addEventListener('click', this.closeSidebar)
|
||||
},
|
||||
closeSidebar(evt) {
|
||||
const parent = evt.target.closest('.rightPanel')
|
||||
if (!parent) {
|
||||
this.show = false
|
||||
window.removeEventListener('click', this.closeSidebar)
|
||||
}
|
||||
},
|
||||
insertToBody() {
|
||||
const elx = this.$refs.rightPanel
|
||||
const body = document.querySelector('body')
|
||||
body.insertBefore(elx, body.firstChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.showRightPanel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.rightPanel-background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
|
||||
background: rgba(0, 0, 0, .2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
width: 100%;
|
||||
max-width: 260px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
|
||||
transition: all .25s cubic-bezier(.7, .3, .1, 1);
|
||||
transform: translate(100%);
|
||||
background: #fff;
|
||||
z-index: 40000;
|
||||
}
|
||||
|
||||
.show {
|
||||
transition: all .3s cubic-bezier(.7, .3, .1, 1);
|
||||
|
||||
.rightPanel-background {
|
||||
z-index: 20000;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rightPanel {
|
||||
transform: translate(0);
|
||||
}
|
||||
}
|
||||
|
||||
.handle-button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
z-index: 0;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
i {
|
||||
font-size: 24px;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.destroy()
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.enabled) {
|
||||
this.$message({
|
||||
message: 'you browser can not work',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
},
|
||||
change() {
|
||||
this.isFullscreen = screenfull.isFullscreen
|
||||
},
|
||||
init() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.on('change', this.change)
|
||||
}
|
||||
},
|
||||
destroy() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.off('change', this.change)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.screenfull-svg {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
fill: #5a5e66;;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,62 +0,0 @@
|
||||
<template>
|
||||
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
|
||||
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||
<use :href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
|
||||
import { isExternal } from '@/scripts/validate'
|
||||
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isExternal() {
|
||||
return isExternal(this.iconClass)
|
||||
},
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass() {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
},
|
||||
styleExternalIcon() {
|
||||
return {
|
||||
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.svg-external-icon {
|
||||
background-color: currentColor;
|
||||
mask-size: cover!important;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -1,175 +0,0 @@
|
||||
<template>
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const version = require('element-ui/package.json').version // element-ui version from node_modules
|
||||
const ORIGINAL_THEME = '#409EFF' // default color
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chalk: '', // content of theme-chalk css
|
||||
theme: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultTheme() {
|
||||
return this.$store.state.settings.theme
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
defaultTheme: {
|
||||
handler: function(val, oldVal) {
|
||||
this.theme = val
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
async theme(val) {
|
||||
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
|
||||
if (typeof val !== 'string') return
|
||||
const themeCluster = this.getThemeCluster(val.replace('#', ''))
|
||||
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
|
||||
console.log(themeCluster, originalCluster)
|
||||
|
||||
const $message = this.$message({
|
||||
message: ' Compiling the theme',
|
||||
customClass: 'theme-message',
|
||||
type: 'success',
|
||||
duration: 0,
|
||||
iconClass: 'el-icon-loading'
|
||||
})
|
||||
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
|
||||
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
|
||||
|
||||
let styleTag = document.getElementById(id)
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.setAttribute('id', id)
|
||||
document.head.appendChild(styleTag)
|
||||
}
|
||||
styleTag.innerText = newStyle
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.chalk) {
|
||||
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
|
||||
await this.getCSSString(url, 'chalk')
|
||||
}
|
||||
|
||||
const chalkHandler = getHandler('chalk', 'chalk-style')
|
||||
|
||||
chalkHandler()
|
||||
|
||||
const styles = [].slice.call(document.querySelectorAll('style'))
|
||||
.filter(style => {
|
||||
const text = style.innerText
|
||||
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
|
||||
})
|
||||
styles.forEach(style => {
|
||||
const { innerText } = style
|
||||
if (typeof innerText !== 'string') return
|
||||
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
|
||||
})
|
||||
|
||||
this.$emit('change', val)
|
||||
|
||||
$message.close()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateStyle(style, oldCluster, newCluster) {
|
||||
let newStyle = style
|
||||
oldCluster.forEach((color, index) => {
|
||||
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
|
||||
})
|
||||
return newStyle
|
||||
},
|
||||
|
||||
getCSSString(url, variable) {
|
||||
return new Promise(resolve => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
})
|
||||
},
|
||||
|
||||
getThemeCluster(theme) {
|
||||
const tintColor = (color, tint) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
if (tint === 0) { // when primary color is in its rgb space
|
||||
return [red, green, blue].join(',')
|
||||
} else {
|
||||
red += Math.round(tint * (255 - red))
|
||||
green += Math.round(tint * (255 - green))
|
||||
blue += Math.round(tint * (255 - blue))
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
}
|
||||
|
||||
const shadeColor = (color, shade) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
red = Math.round((1 - shade) * red)
|
||||
green = Math.round((1 - shade) * green)
|
||||
blue = Math.round((1 - shade) * blue)
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
|
||||
const clusters = [theme]
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
|
||||
}
|
||||
clusters.push(shadeColor(theme, 0.1))
|
||||
return clusters
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.theme-message,
|
||||
.theme-picker-dropdown {
|
||||
z-index: 99999 !important;
|
||||
}
|
||||
|
||||
.theme-picker .el-color-picker__trigger {
|
||||
height: 26px !important;
|
||||
width: 26px !important;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.theme-picker-dropdown .el-color-dropdown__link-btn {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user