删除magic-boot-ui(基于vue-element-admin vue2版本)

This commit is contained in:
吕金泽
2022-03-05 10:34:47 +08:00
parent 5b2e870ce6
commit 4b2134065e
425 changed files with 0 additions and 139816 deletions
@@ -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>