This commit is contained in:
Jane
2023-12-22 10:59:10 +08:00
parent 751c43e199
commit d1ede2d4aa
2774 changed files with 291509 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
<template>
<transition :name="transitionName">
<div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height:16px;width:16px"><path d="M12.036 15.59a1 1 0 0 1-.997.995H5.032a.996.996 0 0 1-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" /></svg>
</div>
</transition>
</template>
<script>
export default {
name: 'BackToTop',
props: {
visibilityHeight: {
type: Number,
default: 400
},
backPosition: {
type: Number,
default: 0
},
customStyle: {
type: Object,
default: function() {
return {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px',
background: '#e7eaf1'
}
}
},
transitionName: {
type: String,
default: 'fade'
}
},
data() {
return {
visible: false,
interval: null,
isMoving: false
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
if (this.interval) {
clearInterval(this.interval)
}
},
methods: {
handleScroll() {
this.visible = window.pageYOffset > this.visibilityHeight
},
backToTop() {
if (this.isMoving) return
const start = window.pageYOffset
let i = 0
this.isMoving = true
this.interval = setInterval(() => {
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
if (next <= this.backPosition) {
window.scrollTo(0, this.backPosition)
clearInterval(this.interval)
this.isMoving = false
} else {
window.scrollTo(0, next)
}
i++
}, 16.7)
},
easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b
return -c / 2 * (--t * (t - 2) - 1) + b
}
}
}
</script>
<style scoped>
.back-to-ceiling {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
.back-to-ceiling:hover {
background: #d5dbe7;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0
}
.back-to-ceiling .Icon {
fill: #9aaabf;
background: none;
}
</style>

View File

@@ -0,0 +1,81 @@
<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(route) {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
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() === 'Dashboard'.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>

View File

@@ -0,0 +1,155 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xAxisData = []
const data = []
const data2 = []
for (let i = 0; i < 50; i++) {
xAxisData.push(i)
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
}
this.chart.setOption({
backgroundColor: '#08263a',
grid: {
left: '5%',
right: '5%'
},
xAxis: [{
show: false,
data: xAxisData
}, {
show: false,
data: xAxisData
}],
visualMap: {
show: false,
min: 0,
max: 50,
dimension: 0,
inRange: {
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
}
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#4a657a'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#08263f'
}
},
axisTick: {
show: false
}
},
series: [{
name: 'back',
type: 'bar',
data: data2,
z: 1,
itemStyle: {
normal: {
opacity: 0.4,
barBorderRadius: 5,
shadowBlur: 3,
shadowColor: '#111'
}
}
}, {
name: 'Simulate Shadow',
type: 'line',
data,
z: 2,
showSymbol: false,
animationDelay: 0,
animationEasing: 'linear',
animationDuration: 1200,
lineStyle: {
normal: {
color: 'transparent'
}
},
areaStyle: {
normal: {
color: '#08263a',
shadowBlur: 50,
shadowColor: '#000'
}
}
}, {
name: 'front',
type: 'bar',
data,
xAxisIndex: 1,
z: 3,
itemStyle: {
normal: {
barBorderRadius: 5
}
}
}],
animationEasing: 'elasticOut',
animationEasingUpdate: 'elasticOut',
animationDelay(idx) {
return idx * 20
},
animationDelayUpdate(idx) {
return idx * 20
}
})
}
}
}
</script>

View File

@@ -0,0 +1,227 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
this.chart.setOption({
backgroundColor: '#394056',
title: {
top: 20,
text: 'Requests',
textStyle: {
fontWeight: 'normal',
fontSize: 16,
color: '#F1F1F3'
},
left: '1%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
legend: {
top: 20,
icon: 'rect',
itemWidth: 14,
itemHeight: 5,
itemGap: 13,
data: ['CMCC', 'CTCC', 'CUCC'],
right: '4%',
textStyle: {
fontSize: 12,
color: '#F1F1F3'
}
},
grid: {
top: 100,
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#57617B'
}
},
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
}],
yAxis: [{
type: 'value',
name: '(%)',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#57617B'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 14
}
},
splitLine: {
lineStyle: {
color: '#57617B'
}
}
}],
series: [{
name: 'CMCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
}, {
name: 'CTCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(0, 136, 212, 0.3)'
}, {
offset: 0.8,
color: 'rgba(0, 136, 212, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(0,136,212)',
borderColor: 'rgba(0,136,212,0.2)',
borderWidth: 12
}
},
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
}, {
name: 'CUCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(219, 50, 51, 0.3)'
}, {
offset: 0.8,
color: 'rgba(219, 50, 51, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(219,50,51)',
borderColor: 'rgba(219,50,51,0.2)',
borderWidth: 12
}
},
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,271 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xData = (function() {
const data = []
for (let i = 1; i < 13; i++) {
data.push(i + 'month')
}
return data
}())
this.chart.setOption({
backgroundColor: '#344b58',
title: {
text: 'statistics',
x: '20',
top: '20',
textStyle: {
color: '#fff',
fontSize: '22'
},
subtextStyle: {
color: '#90979c',
fontSize: '16'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
textStyle: {
color: '#fff'
}
}
},
grid: {
left: '5%',
right: '5%',
borderWidth: 0,
top: 150,
bottom: 95,
textStyle: {
color: '#fff'
}
},
legend: {
x: '5%',
top: '10%',
textStyle: {
color: '#90979c'
},
data: ['female', 'male', 'average']
},
calculable: true,
xAxis: [{
type: 'category',
axisLine: {
lineStyle: {
color: '#90979c'
}
},
splitLine: {
show: false
},
axisTick: {
show: false
},
splitArea: {
show: false
},
axisLabel: {
interval: 0
},
data: xData
}],
yAxis: [{
type: 'value',
splitLine: {
show: false
},
axisLine: {
lineStyle: {
color: '#90979c'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: 0
},
splitArea: {
show: false
}
}],
dataZoom: [{
show: true,
height: 30,
xAxisIndex: [
0
],
bottom: 30,
start: 10,
end: 80,
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
handleSize: '110%',
handleStyle: {
color: '#d3dee5'
},
textStyle: {
color: '#fff' },
borderColor: '#90979c'
}, {
type: 'inside',
show: true,
height: 15,
start: 1,
end: 35
}],
series: [{
name: 'female',
type: 'bar',
stack: 'total',
barMaxWidth: 35,
barGap: '10%',
itemStyle: {
normal: {
color: 'rgba(255,144,128,1)',
label: {
show: true,
textStyle: {
color: '#fff'
},
position: 'insideTop',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
709,
1917,
2455,
2610,
1719,
1433,
1544,
3285,
5208,
3372,
2484,
4078
]
},
{
name: 'male',
type: 'bar',
stack: 'total',
itemStyle: {
normal: {
color: 'rgba(0,191,183,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
327,
1776,
507,
1200,
800,
482,
204,
1390,
1001,
951,
381,
220
]
}, {
name: 'average',
type: 'line',
stack: 'total',
symbolSize: 10,
symbol: 'circle',
itemStyle: {
normal: {
color: 'rgba(252,230,48,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
1036,
3693,
2962,
3810,
2519,
1915,
1748,
4675,
6209,
4323,
2865,
4298
]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,34 @@
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null
}
},
mounted() {
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
beforeDestroy() {
window.removeEventListener('resize', this.__resizeHandler)
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.__resizeHandler()
}
}
}
}

View File

@@ -0,0 +1,173 @@
<template lang="html">
<div :val="value_">
<div>
<el-radio v-model="type" label="1" size="mini" border>每日</el-radio>
</div>
<div>
<el-radio v-model="type" label="5" size="mini" border>不指定</el-radio>
</div>
<div>
<el-radio v-model="type" label="2" size="mini" border>周期</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.start" :min="1" :max="31" size="mini" style="width: 100px;" @change="type = '2'" />
<span style="margin-left: 5px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.end" :min="2" :max="31" size="mini" style="width: 100px;" @change="type = '2'" />
</div>
<div>
<el-radio v-model="type" label="3" size="mini" border>循环</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="loop.start" :min="1" :max="31" size="mini" style="width: 100px;" @change="type = '3'" />
<span style="margin-left: 5px; margin-right: 5px;">日开始</span>
<el-input-number v-model="loop.end" :min="1" :max="31" size="mini" style="width: 100px;" @change="type = '3'" />
日执行一次
</div>
<div>
<el-radio v-model="type" label="8" size="mini" border>工作日</el-radio>
<span style="margin-left: 10px; margin-right: 5px;">本月</span>
<el-input-number v-model="work" :min="1" :max="7" size="mini" style="width: 100px;" @change="type = '8'" />
最近的工作日
</div>
<div>
<el-radio v-model="type" label="6" size="mini" border>本月最后一天</el-radio>
</div>
<div>
<el-radio v-model="type" label="4" size="mini" border>指定</el-radio>
<el-checkbox-group v-model="appoint">
<div v-for="i in 4" :key="i" style="margin-left: 10px; line-height: 25px;">
<span v-for="j in 10" :key="j">
<el-checkbox v-if="parseInt((i - 1) + '' + (j - 1)) < 32 && !(i === 1 && j === 1)" :label="(i - 1) + '' + (j - 1)" style="margin-right: 30px;" @change="type = '4'" />
</span>
</div>
</el-checkbox-group>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '?'
}
},
data() {
return {
type: '5', // 类型
cycle: {
// 周期
start: 0,
end: 0
},
loop: {
// 循环
start: 0,
end: 0
},
week: {
// 指定周
start: 0,
end: 0
},
work: 0,
last: 0,
appoint: [] // 指定
}
},
computed: {
value_() {
const result = []
switch (this.type) {
case '1': // 每秒
result.push('*')
break
case '2': // 周期
result.push(`${this.cycle.start}-${this.cycle.end}`)
break
case '3': // 循环
result.push(`${this.loop.start}/${this.loop.end}`)
break
case '4': // 指定
result.push(this.appoint.join(','))
break
case '6': // 最后
result.push(`${this.last === 0 ? '' : this.last}L`)
break
case '7': // 指定周
result.push(`${this.week.start}#${this.week.end}`)
break
case '8': // 工作日
result.push(`${this.work}W`)
break
default:
// 不指定
result.push('?')
break
}
this.$emit('input', result.join(''))
return result.join('')
}
},
watch: {
value(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
if (this.value === '?') {
this.type = '5'
} else if (this.value.indexOf('-') !== -1) {
// 2周期
if (this.value.split('-').length === 2) {
this.type = '2'
this.cycle.start = this.value.split('-')[0]
this.cycle.end = this.value.split('-')[1]
}
} else if (this.value.indexOf('/') !== -1) {
// 3循环
if (this.value.split('/').length === 2) {
this.type = '3'
this.loop.start = this.value.split('/')[0]
this.loop.end = this.value.split('/')[1]
}
} else if (this.value.indexOf('*') !== -1) {
// 1每
this.type = '1'
} else if (this.value.indexOf('L') !== -1) {
// 6最后
this.type = '6'
this.last = this.value.replace('L', '')
} else if (this.value.indexOf('#') !== -1) {
// 7指定周
if (this.value.split('#').length === 2) {
this.type = '7'
this.week.start = this.value.split('#')[0]
this.week.end = this.value.split('#')[1]
}
} else if (this.value.indexOf('W') !== -1) {
// 8工作日
this.type = '8'
this.work = this.value.replace('W', '')
} else {
// *
this.type = '4'
this.appoint = this.value.split(',')
}
}
}
}
</script>
<style lang="css">
.el-checkbox + .el-checkbox {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,144 @@
<template lang="html">
<div :val="value_">
<div>
<el-radio v-model="type" label="1" size="mini" border>每时</el-radio>
</div>
<div>
<el-radio v-model="type" label="2" size="mini" border>周期</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.start" :min="0" :max="23" size="mini" style="width: 100px;" @change="type = '2'" />
<span style="margin-left: 5px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.end" :min="2" :max="23" size="mini" style="width: 100px;" @change="type = '2'" />
</div>
<div>
<el-radio v-model="type" label="3" size="mini" border>循环</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="loop.start" :min="0" :max="23" size="mini" style="width: 100px;" @change="type = '3'" />
<span style="margin-left: 5px; margin-right: 5px;">时开始</span>
<el-input-number v-model="loop.end" :min="1" :max="23" size="mini" style="width: 100px;" @change="type = '3'" />
时执行一次
</div>
<div>
<el-radio v-model="type" label="4" size="mini" border>指定</el-radio>
<el-checkbox-group v-model="appoint">
<div v-for="i in 3" :key="i" style="margin-left: 10px; line-height: 25px;">
<span v-for="j in 10" :key="j" style="display:inline-block;margin-right:30px;">
<el-checkbox v-if="parseInt((i - 1) + '' + (j - 1)) < 24" :key="j" :label="(i - 1) + '' + (j - 1)" @change="type = '4'" />
</span>
</div>
</el-checkbox-group>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '*'
}
},
data() {
return {
type: '1', // 类型
cycle: { // 周期
start: 0,
end: 0
},
loop: { // 循环
start: 0,
end: 0
},
week: { // 指定周
start: 0,
end: 0
},
work: 0,
last: 0,
appoint: [] // 指定
}
},
computed: {
value_() {
const result = []
switch (this.type) {
case '1': // 每秒
result.push('*')
break
case '2': // 年期
result.push(`${this.cycle.start}-${this.cycle.end}`)
break
case '3': // 循环
result.push(`${this.loop.start}/${this.loop.end}`)
break
case '4': // 指定
result.push(this.appoint.join(','))
break
case '6': // 最后
result.push(`${this.last === 0 ? '' : this.last}L`)
break
default: // 不指定
result.push('?')
break
}
this.$emit('input', result.join(''))
return result.join('')
}
},
watch: {
'value'(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
if (this.value === '?') {
this.type = '5'
} else if (this.value.indexOf('-') !== -1) { // 2周期
if (this.value.split('-').length === 2) {
this.type = '2'
this.cycle.start = this.value.split('-')[0]
this.cycle.end = this.value.split('-')[1]
}
} else if (this.value.indexOf('/') !== -1) { // 3循环
if (this.value.split('/').length === 2) {
this.type = '3'
this.loop.start = this.value.split('/')[0]
this.loop.end = this.value.split('/')[1]
}
} else if (this.value.indexOf('*') !== -1) { // 1每
this.type = '1'
} else if (this.value.indexOf('L') !== -1) { // 6最后
this.type = '6'
this.last = this.value.replace('L', '')
} else if (this.value.indexOf('#') !== -1) { // 7指定周
if (this.value.split('#').length === 2) {
this.type = '7'
this.week.start = this.value.split('#')[0]
this.week.end = this.value.split('#')[1]
}
} else if (this.value.indexOf('W') !== -1) { // 8工作日
this.type = '8'
this.work = this.value.replace('W', '')
} else { // *
this.type = '4'
this.appoint = this.value.split(',')
}
}
}
}
</script>
<style lang="css">
.el-checkbox+.el-checkbox {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,143 @@
<template lang="html">
<div :val="value_">
<div>
<el-radio v-model="type" label="1" size="mini" border>每月</el-radio>
</div>
<div>
<el-radio v-model="type" label="5" size="mini" border>不指定</el-radio>
</div>
<div>
<el-radio v-model="type" label="2" size="mini" border>周期</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.start" :min="1" :max="12" size="mini" style="width: 100px;" @change="type = '2'" />
<span style="margin-left: 5px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.end" :min="2" :max="12" size="mini" style="width: 100px;" @change="type = '2'" />
</div>
<div>
<el-radio v-model="type" label="3" size="mini" border>循环</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="loop.start" :min="1" :max="12" size="mini" style="width: 100px;" @change="type = '3'" />
<span style="margin-left: 5px; margin-right: 5px;">月开始</span>
<el-input-number v-model="loop.end" :min="1" :max="12" size="mini" style="width: 100px;" @change="type = '3'" />
月执行一次
</div>
<div>
<el-radio v-model="type" label="4" size="mini" border>指定</el-radio>
<el-checkbox-group v-model="appoint" style="margin-left: 0px; line-height: 25px;">
<el-checkbox v-for="i in 12" :key="i" :label="i" @change="type = '4'" />
</el-checkbox-group>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '*'
}
},
data() {
return {
type: '1', // 类型
cycle: { // 周期
start: 0,
end: 0
},
loop: { // 循环
start: 0,
end: 0
},
week: { // 指定周
start: 0,
end: 0
},
work: 0,
last: 0,
appoint: [] // 指定
}
},
computed: {
value_() {
const result = []
switch (this.type) {
case '1': // 每秒
result.push('*')
break
case '2': // 年期
result.push(`${this.cycle.start}-${this.cycle.end}`)
break
case '3': // 循环
result.push(`${this.loop.start}/${this.loop.end}`)
break
case '4': // 指定
result.push(this.appoint.join(','))
break
case '6': // 最后
result.push(`${this.last === 0 ? '' : this.last}L`)
break
default: // 不指定
result.push('?')
break
}
this.$emit('input', result.join(''))
return result.join('')
}
},
watch: {
'value'(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
if (this.value === '?') {
this.type = '5'
} else if (this.value.indexOf('-') !== -1) { // 2周期
if (this.value.split('-').length === 2) {
this.type = '2'
this.cycle.start = this.value.split('-')[0]
this.cycle.end = this.value.split('-')[1]
}
} else if (this.value.indexOf('/') !== -1) { // 3循环
if (this.value.split('/').length === 2) {
this.type = '3'
this.loop.start = this.value.split('/')[0]
this.loop.end = this.value.split('/')[1]
}
} else if (this.value.indexOf('*') !== -1) { // 1每
this.type = '1'
} else if (this.value.indexOf('L') !== -1) { // 6最后
this.type = '6'
this.last = this.value.replace('L', '')
} else if (this.value.indexOf('#') !== -1) { // 7指定周
if (this.value.split('#').length === 2) {
this.type = '7'
this.week.start = this.value.split('#')[0]
this.week.end = this.value.split('#')[1]
}
} else if (this.value.indexOf('W') !== -1) { // 8工作日
this.type = '8'
this.work = this.value.replace('W', '')
} else { // *
this.type = '4'
this.appoint = this.value.split(',')
}
}
}
}
</script>
<style lang="css">
.el-checkbox+.el-checkbox {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,146 @@
<!-- ,分钟 -->
<template lang="html">
<div :val="value_">
<div>
<el-radio v-model="type" label="1" size="mini" border>{{ lable }}</el-radio>
</div>
<div>
<el-radio v-model="type" label="2" size="mini" border>周期</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.start" :min="1" :max="59" size="mini" style="width: 100px;" @change="type = '2'" />
<span style="margin-left: 5px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.end" :min="2" :max="59" size="mini" style="width: 100px;" @change="type = '2'" />
{{ lable }}
</div>
<div>
<el-radio v-model="type" label="3" size="mini" border>循环</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="loop.start" :min="0" :max="59" size="mini" style="width: 100px;" @change="type = '3'" />
<span style="margin-left: 5px; margin-right: 5px;">{{ lable }}开始</span>
<el-input-number v-model="loop.end" :min="1" :max="59" size="mini" style="width: 100px;" @change="type = '3'" />
{{ lable }}执行一次
</div>
<div>
<el-radio v-model="type" label="4" size="mini" border>指定</el-radio>
<el-checkbox-group v-model="appoint">
<div v-for="i in 6" :key="i" style="margin-left: 10px; line-height: 25px;">
<el-checkbox v-for="j in 10" :key="j" :label="(i - 1) + '' + (j - 1)" @change="type = '4'" />
</div>
</el-checkbox-group>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '*'
},
lable: {
type: String
}
},
data() {
return {
type: '1', // 类型
cycle: { // 周期
start: 0,
end: 0
},
loop: { // 循环
start: 0,
end: 0
},
week: { // 指定周
start: 0,
end: 0
},
work: 0,
last: 0,
appoint: [] // 指定
}
},
computed: {
value_() {
const result = []
switch (this.type) {
case '1': // 每秒
result.push('*')
break
case '2': // 年期
result.push(`${this.cycle.start}-${this.cycle.end}`)
break
case '3': // 循环
result.push(`${this.loop.start}/${this.loop.end}`)
break
case '4': // 指定
result.push(this.appoint.join(','))
break
case '6': // 最后
result.push(`${this.last === 0 ? '' : this.last}L`)
break
default: // 不指定
result.push('?')
break
}
this.$emit('input', result.join(''))
return result.join('')
}
},
watch: {
'value'(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
if (this.value === '?') {
this.type = '5'
} else if (this.value.indexOf('-') !== -1) { // 2周期
if (this.value.split('-').length === 2) {
this.type = '2'
this.cycle.start = this.value.split('-')[0]
this.cycle.end = this.value.split('-')[1]
}
} else if (this.value.indexOf('/') !== -1) { // 3循环
if (this.value.split('/').length === 2) {
this.type = '3'
this.loop.start = this.value.split('/')[0]
this.loop.end = this.value.split('/')[1]
}
} else if (this.value.indexOf('*') !== -1) { // 1每
this.type = '1'
} else if (this.value.indexOf('L') !== -1) { // 6最后
this.type = '6'
this.last = this.value.replace('L', '')
} else if (this.value.indexOf('#') !== -1) { // 7指定周
if (this.value.split('#').length === 2) {
this.type = '7'
this.week.start = this.value.split('#')[0]
this.week.end = this.value.split('#')[1]
}
} else if (this.value.indexOf('W') !== -1) { // 8工作日
this.type = '8'
this.work = this.value.replace('W', '')
} else { // *
this.type = '4'
this.appoint = this.value.split(',')
}
}
}
}
</script>
<style lang="css">
.el-checkbox+.el-checkbox {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,157 @@
<template lang="html">
<div :val="value_">
<div>
<el-radio v-model="type" label="1" size="mini" border>每周</el-radio>
</div>
<div>
<el-radio v-model="type" label="5" size="mini" border>不指定</el-radio>
</div>
<div>
<el-radio v-model="type" label="2" size="mini" border>周期</el-radio>
<span style="margin-left: 10px; margin-right: 5px;">从星期</span>
<el-input-number v-model="cycle.start" :min="1" :max="7" size="mini" style="width: 100px;" @change="type = '2'" />
<span style="margin-left: 5px; margin-right: 5px;">至星期</span>
<el-input-number v-model="cycle.end" :min="2" :max="7" size="mini" style="width: 100px;" @change="type = '2'" />
</div>
<div>
<el-radio v-model="type" label="3" size="mini" border>循环</el-radio>
<span style="margin-left: 10px; margin-right: 5px;">从星期</span>
<el-input-number v-model="loop.start" :min="1" :max="7" size="mini" style="width: 100px;" @change="type = '3'" />
<span style="margin-left: 5px; margin-right: 5px;">开始</span>
<el-input-number v-model="loop.end" :min="1" :max="7" size="mini" style="width: 100px;" @change="type = '3'" />
天执行一次
</div>
<div>
<el-radio v-model="type" label="7" size="mini" border>指定周</el-radio>
<span style="margin-left: 10px; margin-right: 5px;">本月第</span>
<el-input-number v-model="week.start" :min="1" :max="4" size="mini" style="width: 100px;" @change="type = '7'" />
<span style="margin-left: 5px; margin-right: 5px;">星期</span>
<el-input-number v-model="week.end" :min="1" :max="7" size="mini" style="width: 100px;" @change="type = '7'" />
</div>
<div>
<el-radio v-model="type" label="6" size="mini" border>本月最后一个</el-radio>
<span style="margin-left: 10px; margin-right: 5px;">星期</span>
<el-input-number v-model="last" :min="1" :max="7" size="mini" style="width: 100px;" @change="type = '6'" />
</div>
<div>
<el-radio v-model="type" label="4" size="mini" border>指定</el-radio>
<el-checkbox-group v-model="appoint" style="margin-left: 50px; line-height: 25px;">
<el-checkbox v-for="i in 7" :key="i" :label="i" @change="type = '4'" />
</el-checkbox-group>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '*'
}
},
data() {
return {
type: '1', // 类型
cycle: { // 周期
start: 0,
end: 0
},
loop: { // 循环
start: 0,
end: 0
},
week: { // 指定周
start: 0,
end: 0
},
work: 0,
last: 0,
appoint: [] // 指定
}
},
computed: {
value_() {
const result = []
switch (this.type) {
case '1': // 每秒
result.push('*')
break
case '2': // 年期
result.push(`${this.cycle.start}-${this.cycle.end}`)
break
case '3': // 循环
result.push(`${this.loop.start}/${this.loop.end}`)
break
case '4': // 指定
result.push(this.appoint.join(','))
break
case '6': // 最后
result.push(`${this.last === 0 ? '' : this.last}L`)
break
case '7': // 指定周
result.push(`${this.week.start}#${this.week.end}`)
break
default: // 不指定
result.push('?')
break
}
this.$emit('input', result.join(''))
return result.join('')
}
},
watch: {
'value'(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
if (this.value === '?') {
this.type = '5'
} else if (this.value.indexOf('-') !== -1) { // 2周期
if (this.value.split('-').length === 2) {
this.type = '2'
this.cycle.start = this.value.split('-')[0]
this.cycle.end = this.value.split('-')[1]
}
} else if (this.value.indexOf('/') !== -1) { // 3循环
if (this.value.split('/').length === 2) {
this.type = '3'
this.loop.start = this.value.split('/')[0]
this.loop.end = this.value.split('/')[1]
}
} else if (this.value.indexOf('*') !== -1) { // 1每
this.type = '1'
} else if (this.value.indexOf('L') !== -1) { // 6最后
this.type = '6'
this.last = this.value.replace('L', '')
} else if (this.value.indexOf('#') !== -1) { // 7指定周
if (this.value.split('#').length === 2) {
this.type = '7'
this.week.start = this.value.split('#')[0]
this.week.end = this.value.split('#')[1]
}
} else if (this.value.indexOf('W') !== -1) { // 8工作日
this.type = '8'
this.work = this.value.replace('W', '')
} else { // *
this.type = '4'
this.appoint = this.value.split(',')
}
}
}
}
</script>
<style lang="css">
.el-checkbox+.el-checkbox {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,130 @@
<template lang="html">
<div :val="value_">
<div>
<el-radio v-model="type" label="1" size="mini" border>每年</el-radio>
</div>
<div>
<el-radio v-model="type" label="5" size="mini" border>不指定</el-radio>
</div>
<div>
<el-radio v-model="type" label="2" size="mini" border>周期</el-radio>
<span style="margin-left: 10px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.start" :min="2000" size="mini" style="width: 100px;" @change="type = '2'" />
<span style="margin-left: 5px; margin-right: 5px;"></span>
<el-input-number v-model="cycle.end" :min="2000" size="mini" style="width: 100px;" @change="type = '2'" />
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '*'
}
},
data() {
const year = new Date().getFullYear()
return {
type: '1', // 类型
cycle: { // 周期
start: year,
end: year
},
loop: { // 循环
start: 0,
end: 0
},
week: { // 指定周
start: 0,
end: 0
},
work: 0,
last: 0,
appoint: [] // 指定
}
},
computed: {
value_() {
const result = []
switch (this.type) {
case '1': // 每秒
result.push('*')
break
case '2': // 年期
result.push(`${this.cycle.start}-${this.cycle.end}`)
break
case '3': // 循环
result.push(`${this.loop.start}/${this.loop.end}`)
break
case '4': // 指定
result.push(this.appoint.join(','))
break
case '6': // 最后
result.push(`${this.last === 0 ? '' : this.last}L`)
break
default: // 不指定
result.push('?')
break
}
this.$emit('input', result.join(''))
return result.join('')
}
},
watch: {
'value'(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
if (this.value === '?') {
this.type = '5'
} else if (this.value.indexOf('-') !== -1) { // 2周期
if (this.value.split('-').length === 2) {
this.type = '2'
this.cycle.start = this.value.split('-')[0]
this.cycle.end = this.value.split('-')[1]
}
} else if (this.value.indexOf('/') !== -1) { // 3循环
if (this.value.split('/').length === 2) {
this.type = '3'
this.loop.start = this.value.split('/')[0]
this.loop.end = this.value.split('/')[1]
}
} else if (this.value.indexOf('*') !== -1) { // 1每
this.type = '1'
} else if (this.value.indexOf('L') !== -1) { // 6最后
this.type = '6'
this.last = this.value.replace('L', '')
} else if (this.value.indexOf('#') !== -1) { // 7指定周
if (this.value.split('#').length === 2) {
this.type = '7'
this.week.start = this.value.split('#')[0]
this.week.end = this.value.split('#')[1]
}
} else if (this.value.indexOf('W') !== -1) { // 8工作日
this.type = '8'
this.work = this.value.replace('W', '')
} else { // *
this.type = '4'
this.appoint = this.value.split(',')
}
}
}
}
</script>
<style lang="css">
.el-checkbox+.el-checkbox {
margin-left: 10px;
}
</style>

View File

@@ -0,0 +1,138 @@
<template lang="html">
<div class="cron" :val="value_">
<el-tabs v-model="activeName">
<el-tab-pane label="秒" name="s">
<second-and-minute v-model="sVal" lable="秒" />
</el-tab-pane>
<el-tab-pane label="分" name="m">
<second-and-minute v-model="mVal" lable="分" />
</el-tab-pane>
<el-tab-pane label="时" name="h">
<hour v-model="hVal" lable="时" />
</el-tab-pane>
<el-tab-pane label="日" name="d">
<day v-model="dVal" lable="日" />
</el-tab-pane>
<el-tab-pane label="月" name="month">
<month v-model="monthVal" lable="月" />
</el-tab-pane>
<el-tab-pane label="周" name="week">
<week v-model="weekVal" lable="周" />
</el-tab-pane>
<el-tab-pane label="年" name="year">
<year v-model="yearVal" lable="年" />
</el-tab-pane>
</el-tabs>
<!-- table -->
<el-table :data="tableData" size="mini" border style="width: 100%;">
<el-table-column prop="sVal" label="秒" width="70" />
<el-table-column prop="mVal" label="分" width="70" />
<el-table-column prop="hVal" label="时" width="70" />
<el-table-column prop="dVal" label="日" width="70" />
<el-table-column prop="monthVal" label="月" width="70" />
<el-table-column prop="weekVal" label="周" width="70" />
<el-table-column prop="yearVal" label="年" />
</el-table>
</div>
</template>
<script>
import SecondAndMinute from './component/secondAndMinute'
import hour from './component/hour'
import day from './component/day'
import month from './component/month'
import week from './component/week'
import year from './component/year'
export default {
components: {
SecondAndMinute,
hour,
day,
month,
week,
year
},
props: {
value: {
type: String
}
},
data() {
return {
//
activeName: 's',
sVal: '',
mVal: '',
hVal: '',
dVal: '',
monthVal: '',
weekVal: '',
yearVal: ''
}
},
computed: {
tableData() {
return [
{
sVal: this.sVal,
mVal: this.mVal,
hVal: this.hVal,
dVal: this.dVal,
monthVal: this.monthVal,
weekVal: this.weekVal,
yearVal: this.yearVal
}
]
},
value_() {
if (!this.dVal && !this.weekVal) {
return ''
}
if (this.dVal === '?' && this.weekVal === '?') {
this.$message.error('日期与星期不可以同时为“不指定”')
}
if (this.dVal !== '?' && this.weekVal !== '?') {
this.$message.error('日期与星期必须有一个为“不指定”')
}
const v = `${this.sVal} ${this.mVal} ${this.hVal} ${this.dVal} ${this.monthVal} ${this.weekVal} ${this.yearVal}`
if (v !== this.value) {
this.$emit('input', v)
}
return v
}
},
watch: {
value(a, b) {
this.updateVal()
}
},
created() {
this.updateVal()
},
methods: {
updateVal() {
if (!this.value) {
return
}
const arrays = this.value.split(' ')
this.sVal = arrays[0]
this.mVal = arrays[1]
this.hVal = arrays[2]
this.dVal = arrays[3]
this.monthVal = arrays[4]
this.weekVal = arrays[5]
this.yearVal = arrays[6]
}
}
}
</script>
<style lang="css">
.cron {
text-align: left;
padding: 10px;
background: #fff;
border: 1px solid #dcdfe6;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 0 6px 0 rgba(0, 0, 0, 0.04);
}
</style>

View File

@@ -0,0 +1,268 @@
<template>
<div class="crud-opts">
<span class="crud-opts-left">
<!--左侧插槽-->
<slot name="left" />
<el-button
v-if="crud.optShow.add"
v-permission="permission.add"
class="filter-item"
size="mini"
type="primary"
icon="el-icon-plus"
@click="crud.toAdd"
>
新增
</el-button>
<el-button
v-if="crud.optShow.edit"
v-permission="permission.edit"
class="filter-item"
size="mini"
type="success"
icon="el-icon-edit"
:disabled="crud.selections.length !== 1"
@click="crud.toEdit(crud.selections[0])"
>
修改
</el-button>
<el-button
v-if="crud.optShow.del"
slot="reference"
v-permission="permission.del"
class="filter-item"
type="danger"
icon="el-icon-delete"
size="mini"
:loading="crud.delAllLoading"
:disabled="crud.selections.length === 0"
@click="toDelete(crud.selections)"
>
删除
</el-button>
<el-button
v-if="crud.optShow.download"
:loading="crud.downloadLoading"
:disabled="!crud.data.length"
class="filter-item"
size="mini"
type="warning"
icon="el-icon-download"
@click="crud.doExport"
>导出</el-button>
<!--右侧-->
<slot name="right" />
</span>
<el-button-group class="crud-opts-right">
<el-button
size="mini"
plain
type="info"
icon="el-icon-search"
@click="toggleSearch()"
/>
<el-button
size="mini"
icon="el-icon-refresh"
@click="crud.refresh()"
/>
<el-popover
placement="bottom-end"
width="150"
trigger="click"
>
<el-button
slot="reference"
size="mini"
icon="el-icon-s-grid"
>
<i
class="fa fa-caret-down"
aria-hidden="true"
/>
</el-button>
<el-checkbox
v-model="allColumnsSelected"
:indeterminate="allColumnsSelectedIndeterminate"
@change="handleCheckAllChange"
>
全选
</el-checkbox>
<el-checkbox
v-for="item in tableColumns"
:key="item.property"
v-model="item.visible"
@change="handleCheckedTableColumnsChange(item)"
>
{{ item.label }}
</el-checkbox>
</el-popover>
</el-button-group>
</div>
</template>
<script>
import CRUD, { crud } from '@crud/crud'
function sortWithRef(src, ref) {
const result = Object.assign([], ref)
let cursor = -1
src.forEach(e => {
const idx = result.indexOf(e)
if (idx === -1) {
cursor += 1
result.splice(cursor, 0, e)
} else {
cursor = idx
}
})
return result
}
export default {
mixins: [crud()],
props: {
permission: {
type: Object,
default: () => { return {} }
},
hiddenColumns: {
type: Array,
default: () => { return [] }
},
ignoreColumns: {
type: Array,
default: () => { return [] }
}
},
data() {
return {
tableColumns: [],
allColumnsSelected: true,
allColumnsSelectedIndeterminate: false,
tableUnwatcher: null,
// 忽略下次表格列变动
ignoreNextTableColumnsChange: false
}
},
watch: {
'crud.props.table'() {
this.updateTableColumns()
this.tableColumns.forEach(column => {
if (this.hiddenColumns.indexOf(column.property) !== -1) {
column.visible = false
this.updateColumnVisible(column)
}
})
},
'crud.props.table.store.states.columns'() {
this.updateTableColumns()
}
},
created() {
this.crud.updateProp('searchToggle', true)
},
methods: {
updateTableColumns() {
const table = this.crud.getTable()
if (!table) {
this.tableColumns = []
return
}
let cols = null
const columnFilter = e => e && e.type === 'default' && e.property && this.ignoreColumns.indexOf(e.property) === -1
const refCols = table.columns.filter(columnFilter)
if (this.ignoreNextTableColumnsChange) {
this.ignoreNextTableColumnsChange = false
return
}
this.ignoreNextTableColumnsChange = false
const columns = []
const fullTableColumns = table.$children.map(e => e.columnConfig).filter(columnFilter)
cols = sortWithRef(fullTableColumns, refCols)
cols.forEach(config => {
const column = {
property: config.property,
label: config.label,
visible: refCols.indexOf(config) !== -1
}
columns.push(column)
})
this.tableColumns = columns
},
toDelete(datas) {
this.$confirm(`确认删除选中的${datas.length}条数据?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.crud.delAllLoading = true
this.crud.doDelete(datas)
}).catch(() => {
})
},
handleCheckAllChange(val) {
if (val === false) {
this.allColumnsSelected = true
return
}
this.tableColumns.forEach(column => {
if (!column.visible) {
column.visible = true
this.updateColumnVisible(column)
}
})
this.allColumnsSelected = val
this.allColumnsSelectedIndeterminate = false
},
handleCheckedTableColumnsChange(item) {
let totalCount = 0
let selectedCount = 0
this.tableColumns.forEach(column => {
++totalCount
selectedCount += column.visible ? 1 : 0
})
if (selectedCount === 0) {
this.crud.notify('请至少选择一列', CRUD.NOTIFICATION_TYPE.WARNING)
this.$nextTick(function() {
item.visible = true
})
return
}
this.allColumnsSelected = selectedCount === totalCount
this.allColumnsSelectedIndeterminate = selectedCount !== totalCount && selectedCount !== 0
this.updateColumnVisible(item)
},
updateColumnVisible(item) {
const table = this.crud.props.table
const vm = table.$children.find(e => e.prop === item.property)
const columnConfig = vm.columnConfig
if (item.visible) {
// 找出合适的插入点
const columnIndex = this.tableColumns.indexOf(item)
vm.owner.store.commit('insertColumn', columnConfig, columnIndex + 1, null)
} else {
vm.owner.store.commit('removeColumn', columnConfig, null)
}
this.ignoreNextTableColumnsChange = true
},
toggleSearch() {
this.crud.props.searchToggle = !this.crud.props.searchToggle
}
}
}
</script>
<style>
.crud-opts {
padding: 4px 0;
display: -webkit-flex;
display: flex;
align-items: center;
}
.crud-opts .crud-opts-right {
margin-left: auto;
}
.crud-opts .crud-opts-right span {
float: left;
}
</style>

View File

@@ -0,0 +1,18 @@
<!--分页-->
<template>
<el-pagination
:page-size.sync="page.size"
:total="page.total"
:current-page.sync="page.page"
style="margin-top: 8px;"
layout="total, prev, pager, next, sizes"
@size-change="crud.sizeChangeHandler($event)"
@current-change="crud.pageChangeHandler"
/>
</template>
<script>
import { pagination } from '@crud/crud'
export default {
mixins: [pagination()]
}
</script>

View File

@@ -0,0 +1,20 @@
<!--搜索与重置-->
<template>
<span>
<el-button class="filter-item" size="mini" type="success" icon="el-icon-search" @click="crud.toQuery">搜索</el-button>
<el-button v-if="crud.optShow.reset" class="filter-item" size="mini" type="warning" icon="el-icon-refresh-left" @click="crud.resetQuery()">重置</el-button>
</span>
</template>
<script>
import { crud } from '@crud/crud'
export default {
mixins: [crud()],
props: {
itemClass: {
type: String,
required: false,
default: ''
}
}
}
</script>

View File

@@ -0,0 +1,71 @@
<template>
<div>
<el-button v-permission="permission.edit" :loading="crud.status.cu === 2" :disabled="disabledEdit" size="mini" type="primary" icon="el-icon-edit" @click="crud.toEdit(data)" />
<el-popover v-model="pop" v-permission="permission.del" placement="top" width="180" trigger="manual" @show="onPopoverShow" @hide="onPopoverHide">
<p>{{ msg }}</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="doCancel">取消</el-button>
<el-button :loading="crud.dataStatus[crud.getDataId(data)].delete === 2" type="primary" size="mini" @click="crud.doDelete(data)">确定</el-button>
</div>
<el-button slot="reference" :disabled="disabledDle" type="danger" icon="el-icon-delete" size="mini" @click="toDelete" />
</el-popover>
</div>
</template>
<script>
import CRUD, { crud } from '@crud/crud'
export default {
mixins: [crud()],
props: {
data: {
type: Object,
required: true
},
permission: {
type: Object,
required: true
},
disabledEdit: {
type: Boolean,
default: false
},
disabledDle: {
type: Boolean,
default: false
},
msg: {
type: String,
default: '确定删除本条数据吗?'
}
},
data() {
return {
pop: false
}
},
methods: {
doCancel() {
this.pop = false
this.crud.cancelDelete(this.data)
},
toDelete() {
this.pop = true
},
[CRUD.HOOK.afterDelete](crud, data) {
if (data === this.data) {
this.pop = false
}
},
onPopoverShow() {
setTimeout(() => {
document.addEventListener('click', this.handleDocumentClick)
}, 0)
},
onPopoverHide() {
document.removeEventListener('click', this.handleDocumentClick)
},
handleDocumentClick(event) {
this.pop = false
}
}
}
</script>

View File

@@ -0,0 +1,864 @@
import { initData, download } from '@/api/data'
import { parseTime, downloadFile } from '@/utils/index'
import Vue from 'vue'
/**
* CRUD配置
* @author moxun
* @param {*} options <br>
* @return crud instance.
* @example
* 要使用多crud时请在关联crud的组件处使用crud-tag进行标记<jobForm :job-status="dict.job_status" crud-tag="job" />
*/
function CRUD(options) {
const defaultOptions = {
tag: 'default',
// id字段名
idField: 'id',
// 标题
title: '',
// 请求数据的url
url: '',
// 表格数据
data: [],
// 选择项
selections: [],
// 待查询的对象
query: {},
// 查询数据的参数
params: {},
// Form 表单
form: {},
// 重置表单
defaultForm: () => {},
// 排序规则,默认 id 降序, 支持多字段排序 ['id,desc', 'createTime,asc']
sort: ['id,desc'],
// 等待时间
time: 50,
// CRUD Method
crudMethod: {
add: (form) => {},
del: (id) => {},
edit: (form) => {},
get: (id) => {}
},
// 主页操作栏显示哪些按钮
optShow: {
add: true,
edit: true,
del: true,
download: true,
reset: true
},
// 自定义一些扩展属性
props: {},
// 在主页准备
queryOnPresenterCreated: true,
// 调试开关
debug: false
}
options = mergeOptions(defaultOptions, options)
const data = {
...options,
// 记录数据状态
dataStatus: {},
status: {
add: CRUD.STATUS.NORMAL,
edit: CRUD.STATUS.NORMAL,
// 添加或编辑状态
get cu() {
if (this.add === CRUD.STATUS.NORMAL && this.edit === CRUD.STATUS.NORMAL) {
return CRUD.STATUS.NORMAL
} else if (this.add === CRUD.STATUS.PREPARED || this.edit === CRUD.STATUS.PREPARED) {
return CRUD.STATUS.PREPARED
} else if (this.add === CRUD.STATUS.PROCESSING || this.edit === CRUD.STATUS.PROCESSING) {
return CRUD.STATUS.PROCESSING
}
throw new Error('wrong crud\'s cu status')
},
// 标题
get title() {
return this.add > CRUD.STATUS.NORMAL ? `新增${crud.title}` : this.edit > CRUD.STATUS.NORMAL ? `编辑${crud.title}` : crud.title
}
},
msg: {
submit: '提交成功',
add: '新增成功',
edit: '编辑成功',
del: '删除成功'
},
page: {
// 页码
page: 0,
// 每页数据条数
size: 10,
// 总数据条数
total: 0
},
// 整体loading
loading: false,
// 导出的 Loading
downloadLoading: false,
// 删除的 Loading
delAllLoading: false
}
const methods = {
/**
* 通用的提示
*/
submitSuccessNotify() {
crud.notify(crud.msg.submit, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
addSuccessNotify() {
crud.notify(crud.msg.add, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
editSuccessNotify() {
crud.notify(crud.msg.edit, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
delSuccessNotify() {
crud.notify(crud.msg.del, CRUD.NOTIFICATION_TYPE.SUCCESS)
},
// 搜索
toQuery() {
crud.page.page = 1
crud.refresh()
},
// 刷新
refresh() {
if (!callVmHook(crud, CRUD.HOOK.beforeRefresh)) {
return
}
return new Promise((resolve, reject) => {
crud.loading = true
// 请求数据
initData(crud.url, crud.getQueryParams()).then(data => {
const table = crud.getTable()
if (table && table.lazy) { // 懒加载子节点数据,清掉已加载的数据
table.store.states.treeData = {}
table.store.states.lazyTreeNodeMap = {}
}
crud.page.total = data.totalElements
crud.data = data.content
crud.resetDataStatus()
// time 毫秒后显示表格
setTimeout(() => {
crud.loading = false
callVmHook(crud, CRUD.HOOK.afterRefresh)
}, crud.time)
resolve(data)
}).catch(err => {
crud.loading = false
reject(err)
})
})
},
/**
* 启动添加
*/
toAdd() {
crud.resetForm()
if (!(callVmHook(crud, CRUD.HOOK.beforeToAdd, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) {
return
}
crud.status.add = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterToAdd, crud.form)
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form)
},
/**
* 启动编辑
* @param {*} data 数据项
*/
toEdit(data) {
crud.resetForm(JSON.parse(JSON.stringify(data)))
if (!(callVmHook(crud, CRUD.HOOK.beforeToEdit, crud.form) && callVmHook(crud, CRUD.HOOK.beforeToCU, crud.form))) {
return
}
crud.status.edit = CRUD.STATUS.PREPARED
crud.getDataStatus(crud.getDataId(data)).edit = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterToEdit, crud.form)
callVmHook(crud, CRUD.HOOK.afterToCU, crud.form)
},
/**
* 启动删除
* @param {*} data 数据项
*/
toDelete(data) {
crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.PREPARED
},
/**
* 取消删除
* @param {*} data 数据项
*/
cancelDelete(data) {
if (!callVmHook(crud, CRUD.HOOK.beforeDeleteCancel, data)) {
return
}
crud.getDataStatus(crud.getDataId(data)).delete = CRUD.STATUS.NORMAL
callVmHook(crud, CRUD.HOOK.afterDeleteCancel, data)
},
/**
* 取消新增/编辑
*/
cancelCU() {
const addStatus = crud.status.add
const editStatus = crud.status.edit
if (addStatus === CRUD.STATUS.PREPARED) {
if (!callVmHook(crud, CRUD.HOOK.beforeAddCancel, crud.form)) {
return
}
crud.status.add = CRUD.STATUS.NORMAL
}
if (editStatus === CRUD.STATUS.PREPARED) {
if (!callVmHook(crud, CRUD.HOOK.beforeEditCancel, crud.form)) {
return
}
crud.status.edit = CRUD.STATUS.NORMAL
crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL
}
crud.resetForm()
if (addStatus === CRUD.STATUS.PREPARED) {
callVmHook(crud, CRUD.HOOK.afterAddCancel, crud.form)
}
if (editStatus === CRUD.STATUS.PREPARED) {
callVmHook(crud, CRUD.HOOK.afterEditCancel, crud.form)
}
// 清除表单验证
if (crud.findVM('form').$refs['form']) {
crud.findVM('form').$refs['form'].clearValidate()
}
},
/**
* 提交新增/编辑
*/
submitCU() {
if (!callVmHook(crud, CRUD.HOOK.beforeValidateCU)) {
return
}
crud.findVM('form').$refs['form'].validate(valid => {
if (!valid) {
return
}
if (!callVmHook(crud, CRUD.HOOK.afterValidateCU)) {
return
}
if (crud.status.add === CRUD.STATUS.PREPARED) {
crud.doAdd()
} else if (crud.status.edit === CRUD.STATUS.PREPARED) {
crud.doEdit()
}
})
},
/**
* 执行添加
*/
doAdd() {
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) {
return
}
crud.status.add = CRUD.STATUS.PROCESSING
crud.crudMethod.add(crud.form).then(() => {
crud.status.add = CRUD.STATUS.NORMAL
crud.resetForm()
crud.addSuccessNotify()
callVmHook(crud, CRUD.HOOK.afterSubmit)
crud.toQuery()
}).catch(() => {
crud.status.add = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterAddError)
})
},
/**
* 执行编辑
*/
doEdit() {
if (!callVmHook(crud, CRUD.HOOK.beforeSubmit)) {
return
}
crud.status.edit = CRUD.STATUS.PROCESSING
crud.crudMethod.edit(crud.form).then(() => {
crud.status.edit = CRUD.STATUS.NORMAL
crud.getDataStatus(crud.getDataId(crud.form)).edit = CRUD.STATUS.NORMAL
crud.editSuccessNotify()
crud.resetForm()
callVmHook(crud, CRUD.HOOK.afterSubmit)
crud.refresh()
}).catch(() => {
crud.status.edit = CRUD.STATUS.PREPARED
callVmHook(crud, CRUD.HOOK.afterEditError)
})
},
/**
* 执行删除
* @param {*} data 数据项
*/
doDelete(data) {
let delAll = false
let dataStatus
const ids = []
if (data instanceof Array) {
delAll = true
data.forEach(val => {
ids.push(this.getDataId(val))
})
} else {
ids.push(this.getDataId(data))
dataStatus = crud.getDataStatus(this.getDataId(data))
}
if (!callVmHook(crud, CRUD.HOOK.beforeDelete, data)) {
return
}
if (!delAll) {
dataStatus.delete = CRUD.STATUS.PROCESSING
}
return crud.crudMethod.del(ids).then(() => {
if (delAll) {
crud.delAllLoading = false
} else dataStatus.delete = CRUD.STATUS.PREPARED
crud.dleChangePage(1)
crud.delSuccessNotify()
callVmHook(crud, CRUD.HOOK.afterDelete, data)
crud.refresh()
}).catch(() => {
if (delAll) {
crud.delAllLoading = false
} else dataStatus.delete = CRUD.STATUS.PREPARED
})
},
/**
* 通用导出
*/
doExport() {
crud.downloadLoading = true
download(crud.url + '/download', crud.getQueryParams()).then(result => {
downloadFile(result, crud.title + '数据', 'xlsx')
crud.downloadLoading = false
}).catch(() => {
crud.downloadLoading = false
})
},
/**
* 获取查询参数
*/
getQueryParams: function() {
// 清除参数无值的情况
Object.keys(crud.query).length !== 0 && Object.keys(crud.query).forEach(item => {
if (crud.query[item] === null || crud.query[item] === '') crud.query[item] = undefined
})
Object.keys(crud.params).length !== 0 && Object.keys(crud.params).forEach(item => {
if (crud.params[item] === null || crud.params[item] === '') crud.params[item] = undefined
})
return {
page: crud.page.page - 1,
size: crud.page.size,
sort: crud.sort,
...crud.query,
...crud.params
}
},
// 当前页改变
pageChangeHandler(e) {
crud.page.page = e
crud.refresh()
},
// 每页条数改变
sizeChangeHandler(e) {
crud.page.size = e
crud.page.page = 1
crud.refresh()
},
// 预防删除第二页最后一条数据时,或者多选删除第二页的数据时,页码错误导致请求无数据
dleChangePage(size) {
if (crud.data.length === size && crud.page.page !== 1) {
crud.page.page -= 1
}
},
// 选择改变
selectionChangeHandler(val) {
crud.selections = val
},
/**
* 重置查询参数
* @param {Boolean} toQuery 重置后进行查询操作
*/
resetQuery(toQuery = true) {
const defaultQuery = JSON.parse(JSON.stringify(crud.defaultQuery))
const query = crud.query
Object.keys(query).forEach(key => {
query[key] = defaultQuery[key]
})
// 重置参数
this.params = {}
if (toQuery) {
crud.toQuery()
}
},
/**
* 重置表单
* @param {Array} data 数据
*/
resetForm(data) {
const form = data || (typeof crud.defaultForm === 'object' ? JSON.parse(JSON.stringify(crud.defaultForm)) : crud.defaultForm.apply(crud.findVM('form')))
const crudFrom = crud.form
for (const key in form) {
if (crudFrom.hasOwnProperty(key)) {
crudFrom[key] = form[key]
} else {
Vue.set(crudFrom, key, form[key])
}
}
// add by ghl 2020-10-04 页面重复添加信息时,下拉框的校验会存在,需要找工取消
if (crud.findVM('form').$refs['form']) {
crud.findVM('form').$refs['form'].clearValidate()
}
},
/**
* 重置数据状态
*/
resetDataStatus() {
const dataStatus = {}
function resetStatus(datas) {
datas.forEach(e => {
dataStatus[crud.getDataId(e)] = {
delete: 0,
edit: 0
}
if (e.children) {
resetStatus(e.children)
}
})
}
resetStatus(crud.data)
crud.dataStatus = dataStatus
},
/**
* 获取数据状态
* @param {Number | String} id 数据项id
*/
getDataStatus(id) {
return crud.dataStatus[id]
},
/**
* 用于树形表格多选, 选中所有
* @param selection
*/
selectAllChange(selection) {
// 如果选中的数目与请求到的数目相同就选中子节点,否则就清空选中
if (selection && selection.length === crud.data.length) {
selection.forEach(val => {
crud.selectChange(selection, val)
})
} else {
crud.getTable().clearSelection()
}
},
/**
* 用于树形表格多选,单选的封装
* @param selection
* @param row
*/
selectChange(selection, row) {
// 如果selection中存在row代表是选中否则是取消选中
if (selection.find(val => { return crud.getDataId(val) === crud.getDataId(row) })) {
if (row.children) {
row.children.forEach(val => {
crud.getTable().toggleRowSelection(val, true)
selection.push(val)
if (val.children) {
crud.selectChange(selection, val)
}
})
}
} else {
crud.toggleRowSelection(selection, row)
}
},
/**
* 切换选中状态
* @param selection
* @param data
*/
toggleRowSelection(selection, data) {
if (data.children) {
data.children.forEach(val => {
selection.splice(selection.findIndex(item => this.getDataId(item) === this.getDataId(val)), 1)
crud.getTable().toggleRowSelection(val, false)
if (val.children) {
crud.toggleRowSelection(selection, val)
}
})
}
},
findVM(type) {
return crud.vms.find(vm => vm && vm.type === type).vm
},
notify(title, type = CRUD.NOTIFICATION_TYPE.INFO) {
crud.vms[0].vm.$notify({
title,
type,
duration: 2500
})
},
updateProp(name, value) {
Vue.set(crud.props, name, value)
},
getDataId(data) {
return data[this.idField]
},
getTable() {
return this.findVM('presenter').$refs.table
},
attchTable() {
const table = this.getTable()
this.updateProp('table', table)
const that = this
table.$on('expand-change', (row, expanded) => {
if (!expanded) {
return
}
const lazyTreeNodeMap = table.store.states.lazyTreeNodeMap
row.children = lazyTreeNodeMap[crud.getDataId(row)]
if (row.children) {
row.children.forEach(ele => {
const id = crud.getDataId(ele)
if (that.dataStatus[id] === undefined) {
that.dataStatus[id] = {
delete: 0,
edit: 0
}
}
})
}
})
}
}
const crud = Object.assign({}, data)
// 可观测化
Vue.observable(crud)
// 附加方法
Object.assign(crud, methods)
// 记录初始默认的查询参数,后续重置查询时使用
Object.assign(crud, {
defaultQuery: JSON.parse(JSON.stringify(data.query)),
// 预留4位存储组件 主页、头部、分页、表单,调试查看也方便找
vms: Array(4),
/**
* 注册组件实例
* @param {String} type 类型
* @param {*} vm 组件实例
* @param {Number} index 该参数内部使用
*/
registerVM(type, vm, index = -1) {
const vmObj = {
type,
vm: vm
}
if (index < 0) {
this.vms.push(vmObj)
return
}
if (index < 4) { // 内置预留vm数
this.vms[index] = vmObj
return
}
this.vms.length = Math.max(this.vms.length, index)
this.vms.splice(index, 1, vmObj)
},
/**
* 取消注册组件实例
* @param {*} vm 组件实例
*/
unregisterVM(type, vm) {
for (let i = this.vms.length - 1; i >= 0; i--) {
if (this.vms[i] === undefined) {
continue
}
if (this.vms[i].type === type && this.vms[i].vm === vm) {
if (i < 4) { // 内置预留vm数
this.vms[i] = undefined
} else {
this.vms.splice(i, 1)
}
break
}
}
}
})
// 冻结处理需要扩展数据的话使用crud.updateProp(name, value)以crud.props.name形式访问这个是响应式的可以做数据绑定
Object.freeze(crud)
return crud
}
// hook VM
function callVmHook(crud, hook) {
if (crud.debug) {
console.log('callVmHook: ' + hook)
}
const tagHook = crud.tag ? hook + '$' + crud.tag : null
let ret = true
const nargs = [crud]
for (let i = 2; i < arguments.length; ++i) {
nargs.push(arguments[i])
}
// 有些组件扮演了多个角色,调用钩子时,需要去重
const vmSet = new Set()
crud.vms.forEach(vm => vm && vmSet.add(vm.vm))
vmSet.forEach(vm => {
if (vm[hook]) {
ret = vm[hook].apply(vm, nargs) !== false && ret
}
if (tagHook && vm[tagHook]) {
ret = vm[tagHook].apply(vm, nargs) !== false && ret
}
})
return ret
}
function mergeOptions(src, opts) {
const optsRet = {
...src
}
for (const key in src) {
if (opts.hasOwnProperty(key)) {
optsRet[key] = opts[key]
}
}
return optsRet
}
/**
* 查找crud
* @param {*} vm
* @param {string} tag
*/
function lookupCrud(vm, tag) {
tag = tag || vm.$attrs['crud-tag'] || 'default'
// function lookupCrud(vm, tag) {
if (vm.$crud) {
const ret = vm.$crud[tag]
if (ret) {
return ret
}
}
return vm.$parent ? lookupCrud(vm.$parent, tag) : undefined
}
/**
* crud主页
*/
function presenter(crud) {
if (crud) {
console.warn('[CRUD warn]: ' + 'please use $options.cruds() { return CRUD(...) or [CRUD(...), ...] }')
}
return {
data() {
// 在data中返回crud是为了将crud与当前实例关联组件观测crud相关属性变化
return {
crud: this.crud
}
},
beforeCreate() {
this.$crud = this.$crud || {}
let cruds = this.$options.cruds instanceof Function ? this.$options.cruds() : crud
if (!(cruds instanceof Array)) {
cruds = [cruds]
}
cruds.forEach(ele => {
if (this.$crud[ele.tag]) {
console.error('[CRUD error]: ' + 'crud with tag [' + ele.tag + ' is already exist')
}
this.$crud[ele.tag] = ele
ele.registerVM('presenter', this, 0)
})
this.crud = this.$crud['defalut'] || cruds[0]
},
methods: {
parseTime
},
created() {
for (const k in this.$crud) {
if (this.$crud[k].queryOnPresenterCreated) {
this.$crud[k].toQuery()
}
}
},
destroyed() {
for (const k in this.$crud) {
this.$crud[k].unregisterVM('presenter', this)
}
},
mounted() {
// 如果table未实例化例如使用了v-if请稍后在适当时机crud.attchTable刷新table信息
if (this.$refs.table !== undefined) {
this.crud.attchTable()
}
}
}
}
/**
* 头部
*/
function header() {
return {
data() {
return {
crud: this.crud,
query: this.crud.query
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM('header', this, 1)
},
destroyed() {
this.crud.unregisterVM('header', this)
}
}
}
/**
* 分页
*/
function pagination() {
return {
data() {
return {
crud: this.crud,
page: this.crud.page
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM('pagination', this, 2)
},
destroyed() {
this.crud.unregisterVM('pagination', this)
}
}
}
/**
* 表单
*/
function form(defaultForm) {
return {
data() {
return {
crud: this.crud,
form: this.crud.form
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM('form', this, 3)
},
created() {
this.crud.defaultForm = defaultForm
this.crud.resetForm()
},
destroyed() {
this.crud.unregisterVM('form', this)
}
}
}
/**
* crud
*/
function crud(options = {}) {
const defaultOptions = {
type: undefined
}
options = mergeOptions(defaultOptions, options)
return {
data() {
return {
crud: this.crud
}
},
beforeCreate() {
this.crud = lookupCrud(this)
this.crud.registerVM(options.type, this)
},
destroyed() {
this.crud.unregisterVM(options.type, this)
}
}
}
/**
* CRUD钩子
*/
CRUD.HOOK = {
/** 刷新 - 之前 */
beforeRefresh: 'beforeCrudRefresh',
/** 刷新 - 之后 */
afterRefresh: 'afterCrudRefresh',
/** 删除 - 之前 */
beforeDelete: 'beforeCrudDelete',
/** 删除 - 之后 */
afterDelete: 'afterCrudDelete',
/** 删除取消 - 之前 */
beforeDeleteCancel: 'beforeCrudDeleteCancel',
/** 删除取消 - 之后 */
afterDeleteCancel: 'afterCrudDeleteCancel',
/** 新建 - 之前 */
beforeToAdd: 'beforeCrudToAdd',
/** 新建 - 之后 */
afterToAdd: 'afterCrudToAdd',
/** 编辑 - 之前 */
beforeToEdit: 'beforeCrudToEdit',
/** 编辑 - 之后 */
afterToEdit: 'afterCrudToEdit',
/** 开始 "新建/编辑" - 之前 */
beforeToCU: 'beforeCrudToCU',
/** 开始 "新建/编辑" - 之后 */
afterToCU: 'afterCrudToCU',
/** "新建/编辑" 验证 - 之前 */
beforeValidateCU: 'beforeCrudValidateCU',
/** "新建/编辑" 验证 - 之后 */
afterValidateCU: 'afterCrudValidateCU',
/** 添加取消 - 之前 */
beforeAddCancel: 'beforeCrudAddCancel',
/** 添加取消 - 之后 */
afterAddCancel: 'afterCrudAddCancel',
/** 编辑取消 - 之前 */
beforeEditCancel: 'beforeCrudEditCancel',
/** 编辑取消 - 之后 */
afterEditCancel: 'afterCrudEditCancel',
/** 提交 - 之前 */
beforeSubmit: 'beforeCrudSubmitCU',
/** 提交 - 之后 */
afterSubmit: 'afterCrudSubmitCU',
afterAddError: 'afterCrudAddError',
afterEditError: 'afterCrudEditError'
}
/**
* CRUD状态
*/
CRUD.STATUS = {
NORMAL: 0,
PREPARED: 1,
PROCESSING: 2
}
/**
* CRUD通知类型
*/
CRUD.NOTIFICATION_TYPE = {
SUCCESS: 'success',
WARNING: 'warning',
INFO: 'info',
ERROR: 'error'
}
export default CRUD
export {
presenter,
header,
form,
pagination,
crud
}

View File

@@ -0,0 +1,45 @@
<script>
import { DatePicker, DatePickerOptions } from 'element-ui'
import { calendarShortcuts } from '@/utils/shortcuts'
export default {
name: 'DateRangePicker',
mixins: [DatePicker],
props: {
type: {
type: String,
default: 'daterange'
},
valueFormat: {
type: String,
default: 'yyyy-MM-dd HH:mm:ss'
},
defaultTime: {
type: Array,
default: _ => ['00:00:00', '23:59:59']
},
pickerOptions: {
type: DatePickerOptions,
default: _ => {
return { shortcuts: calendarShortcuts, firstDayOfWeek: 1 }
}
},
size: {
type: String,
default: 'small'
},
rangeSeparator: {
type: String,
default: ':'
},
startPlaceholder: {
type: String,
default: '开始日期'
},
endPlaceholder: {
type: String,
default: '结束日期'
}
}
}
</script>

View File

@@ -0,0 +1,29 @@
import Vue from 'vue'
import { get as getDictDetail } from '@/api/system/dictDetail'
export default class Dict {
constructor(dict) {
this.dict = dict
}
async init(names, completeCallback) {
if (names === undefined || name === null) {
throw new Error('need Dict names')
}
const ps = []
names.forEach(n => {
Vue.set(this.dict.dict, n, {})
Vue.set(this.dict.label, n, {})
Vue.set(this.dict, n, [])
ps.push(getDictDetail(n).then(data => {
this.dict[n].splice(0, 0, ...data.content)
data.content.forEach(d => {
Vue.set(this.dict.dict[n], d.value, d)
Vue.set(this.dict.label[n], d.value, d.label)
})
}))
})
await Promise.all(ps)
completeCallback()
}
}

View File

@@ -0,0 +1,29 @@
import Dict from './Dict'
const install = function(Vue) {
Vue.mixin({
data() {
if (this.$options.dicts instanceof Array) {
const dict = {
dict: {},
label: {}
}
return {
dict
}
}
return {}
},
created() {
if (this.$options.dicts instanceof Array) {
new Dict(this.dict).init(this.$options.dicts, () => {
this.$nextTick(() => {
this.$emit('dictReady')
})
})
}
}
})
}
export default { install }

View File

@@ -0,0 +1,166 @@
<template>
<div class="dndList">
<div :style="{width:width1}" class="dndList-list">
<h3>{{ list1Title }}</h3>
<draggable :set-data="setData" :list="list1" group="article" class="dragArea">
<div v-for="element in list1" :key="element.id" class="list-complete-item">
<div class="list-complete-item-handle">
{{ element.id }}[{{ element.author }}] {{ element.title }}
</div>
<div style="position:absolute;right:0px;">
<span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
<i style="color:#ff4949" class="el-icon-delete" />
</span>
</div>
</div>
</draggable>
</div>
<div :style="{width:width2}" class="dndList-list">
<h3>{{ list2Title }}</h3>
<draggable :list="list2" group="article" class="dragArea">
<div v-for="element in list2" :key="element.id" class="list-complete-item">
<div class="list-complete-item-handle2" @click="pushEle(element)">
{{ element.id }} [{{ element.author }}] {{ element.title }}
</div>
</div>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'DndList',
components: { draggable },
props: {
list1: {
type: Array,
default() {
return []
}
},
list2: {
type: Array,
default() {
return []
}
},
list1Title: {
type: String,
default: 'list1'
},
list2Title: {
type: String,
default: 'list2'
},
width1: {
type: String,
default: '48%'
},
width2: {
type: String,
default: '48%'
}
},
methods: {
isNotInList1(v) {
return this.list1.every(k => v.id !== k.id)
},
isNotInList2(v) {
return this.list2.every(k => v.id !== k.id)
},
deleteEle(ele) {
for (const item of this.list1) {
if (item.id === ele.id) {
const index = this.list1.indexOf(item)
this.list1.splice(index, 1)
break
}
}
if (this.isNotInList2(ele)) {
this.list2.unshift(ele)
}
},
pushEle(ele) {
for (const item of this.list2) {
if (item.id === ele.id) {
const index = this.list2.indexOf(item)
this.list2.splice(index, 1)
break
}
}
if (this.isNotInList1(ele)) {
this.list1.push(ele)
}
},
setData(dataTransfer) {
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
}
}
}
</script>
<style lang="scss" scoped>
.dndList {
background: #fff;
padding-bottom: 40px;
&:after {
content: "";
display: table;
clear: both;
}
.dndList-list {
float: left;
padding-bottom: 30px;
&:first-of-type {
margin-right: 2%;
}
.dragArea {
margin-top: 15px;
min-height: 50px;
padding-bottom: 30px;
}
}
}
.list-complete-item {
cursor: pointer;
position: relative;
font-size: 14px;
padding: 5px 12px;
margin-top: 4px;
border: 1px solid #bfcbd9;
transition: all 1s;
}
.list-complete-item-handle {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 50px;
}
.list-complete-item-handle2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 20px;
}
.list-complete-item.sortable-chosen {
background: #4AB7BD;
}
.list-complete-item.sortable-ghost {
background: #30B08F;
}
.list-complete-enter,
.list-complete-leave-active {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,16 @@
<template>
<div>
<svg-icon icon-class="doc" @click="click" />
</div>
</template>
<script>
export default {
name: 'Doc',
methods: {
click() {
window.open('https://alldata.readthedocs.io/', '_blank')
}
}
}
</script>

View File

@@ -0,0 +1,61 @@
<template>
<el-select ref="dragSelect" v-model="selectVal" v-bind="$attrs" class="drag-select" multiple v-on="$listeners">
<slot />
</el-select>
</template>
<script>
import Sortable from 'sortablejs'
export default {
name: 'DragSelect',
props: {
value: {
type: Array,
required: true
}
},
computed: {
selectVal: {
get() {
return [...this.value]
},
set(val) {
this.$emit('input', [...val])
}
}
},
mounted() {
this.setSort()
},
methods: {
setSort() {
const el = this.$refs.dragSelect.$el.querySelectorAll('.el-select__tags > span')[0]
this.sortable = Sortable.create(el, {
ghostClass: 'sortable-ghost', // Class name for the drop placeholder,
setData: function(dataTransfer) {
dataTransfer.setData('Text', '')
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
},
onEnd: evt => {
const targetRow = this.value.splice(evt.oldIndex, 1)[0]
this.value.splice(evt.newIndex, 0, targetRow)
}
})
}
}
}
</script>
<style scoped>
.drag-select >>> .sortable-ghost {
opacity: .8;
color: #fff!important;
background: #42b983!important;
}
.drag-select >>> .el-tag {
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,297 @@
<template>
<div :id="id" :ref="id" :action="url" class="dropzone">
<input type="file" name="file">
</div>
</template>
<script>
import Dropzone from 'dropzone'
import 'dropzone/dist/dropzone.css'
// import { getToken } from 'api/qiniu';
Dropzone.autoDiscover = false
export default {
props: {
id: {
type: String,
required: true
},
url: {
type: String,
required: true
},
clickable: {
type: Boolean,
default: true
},
defaultMsg: {
type: String,
default: '上传图片'
},
acceptedFiles: {
type: String,
default: ''
},
thumbnailHeight: {
type: Number,
default: 200
},
thumbnailWidth: {
type: Number,
default: 200
},
showRemoveLink: {
type: Boolean,
default: true
},
maxFilesize: {
type: Number,
default: 2
},
maxFiles: {
type: Number,
default: 3
},
autoProcessQueue: {
type: Boolean,
default: true
},
useCustomDropzoneOptions: {
type: Boolean,
default: false
},
defaultImg: {
default: '',
type: [String, Array]
},
couldPaste: {
type: Boolean,
default: false
}
},
data() {
return {
dropzone: '',
initOnce: true
}
},
watch: {
defaultImg(val) {
if (val.length === 0) {
this.initOnce = false
return
}
if (!this.initOnce) return
this.initImages(val)
this.initOnce = false
}
},
mounted() {
const element = document.getElementById(this.id)
const vm = this
this.dropzone = new Dropzone(element, {
clickable: this.clickable,
thumbnailWidth: this.thumbnailWidth,
thumbnailHeight: this.thumbnailHeight,
maxFiles: this.maxFiles,
maxFilesize: this.maxFilesize,
dictRemoveFile: 'Remove',
addRemoveLinks: this.showRemoveLink,
acceptedFiles: this.acceptedFiles,
autoProcessQueue: this.autoProcessQueue,
dictDefaultMessage: '<i style="margin-top: 3em;display: inline-block" class="material-icons">' + this.defaultMsg + '</i><br>Drop files here to upload',
dictMaxFilesExceeded: '只能一个图',
previewTemplate: '<div class="dz-preview dz-file-preview"> <div class="dz-image" style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" ><img style="width:' + this.thumbnailWidth + 'px;height:' + this.thumbnailHeight + 'px" data-dz-thumbnail /></div> <div class="dz-details"><div class="dz-size"><span data-dz-size></span></div> <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div> <div class="dz-error-message"><span data-dz-errormessage></span></div> <div class="dz-success-mark"> <i class="material-icons">done</i> </div> <div class="dz-error-mark"><i class="material-icons">error</i></div></div>',
init() {
const val = vm.defaultImg
if (!val) return
if (Array.isArray(val)) {
if (val.length === 0) return
val.map((v, i) => {
const mockFile = { name: 'name' + i, size: 12345, url: v }
this.options.addedfile.call(this, mockFile)
this.options.thumbnail.call(this, mockFile, v)
mockFile.previewElement.classList.add('dz-success')
mockFile.previewElement.classList.add('dz-complete')
vm.initOnce = false
return true
})
} else {
const mockFile = { name: 'name', size: 12345, url: val }
this.options.addedfile.call(this, mockFile)
this.options.thumbnail.call(this, mockFile, val)
mockFile.previewElement.classList.add('dz-success')
mockFile.previewElement.classList.add('dz-complete')
vm.initOnce = false
}
},
accept: (file, done) => {
/* 七牛*/
// const token = this.$store.getters.token;
// getToken(token).then(response => {
// file.token = response.data.qiniu_token;
// file.key = response.data.qiniu_key;
// file.url = response.data.qiniu_url;
// done();
// })
done()
},
sending: (file, xhr, formData) => {
// formData.append('token', file.token);
// formData.append('key', file.key);
vm.initOnce = false
}
})
if (this.couldPaste) {
document.addEventListener('paste', this.pasteImg)
}
this.dropzone.on('success', file => {
vm.$emit('dropzone-success', file, vm.dropzone.element)
})
this.dropzone.on('addedfile', file => {
vm.$emit('dropzone-fileAdded', file)
})
this.dropzone.on('removedfile', file => {
vm.$emit('dropzone-removedFile', file)
})
this.dropzone.on('error', (file, error, xhr) => {
vm.$emit('dropzone-error', file, error, xhr)
})
this.dropzone.on('successmultiple', (file, error, xhr) => {
vm.$emit('dropzone-successmultiple', file, error, xhr)
})
},
destroyed() {
document.removeEventListener('paste', this.pasteImg)
this.dropzone.destroy()
},
methods: {
removeAllFiles() {
this.dropzone.removeAllFiles(true)
},
processQueue() {
this.dropzone.processQueue()
},
pasteImg(event) {
const items = (event.clipboardData || event.originalEvent.clipboardData).items
if (items[0].kind === 'file') {
this.dropzone.addFile(items[0].getAsFile())
}
},
initImages(val) {
if (!val) return
if (Array.isArray(val)) {
val.map((v, i) => {
const mockFile = { name: 'name' + i, size: 12345, url: v }
this.dropzone.options.addedfile.call(this.dropzone, mockFile)
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, v)
mockFile.previewElement.classList.add('dz-success')
mockFile.previewElement.classList.add('dz-complete')
return true
})
} else {
const mockFile = { name: 'name', size: 12345, url: val }
this.dropzone.options.addedfile.call(this.dropzone, mockFile)
this.dropzone.options.thumbnail.call(this.dropzone, mockFile, val)
mockFile.previewElement.classList.add('dz-success')
mockFile.previewElement.classList.add('dz-complete')
}
}
}
}
</script>
<style scoped>
.dropzone {
border: 2px solid #E5E5E5;
font-family: 'Roboto', sans-serif;
color: #777;
transition: background-color .2s linear;
padding: 5px;
}
.dropzone:hover {
background-color: #F6F6F6;
}
i {
color: #CCC;
}
.dropzone .dz-image img {
width: 100%;
height: 100%;
}
.dropzone input[name='file'] {
display: none;
}
.dropzone .dz-preview .dz-image {
border-radius: 0px;
}
.dropzone .dz-preview:hover .dz-image img {
transform: none;
filter: none;
width: 100%;
height: 100%;
}
.dropzone .dz-preview .dz-details {
bottom: 0px;
top: 0px;
color: white;
background-color: rgba(33, 150, 243, 0.8);
transition: opacity .2s linear;
text-align: left;
}
.dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span {
background-color: transparent;
}
.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span {
border: none;
}
.dropzone .dz-preview .dz-details .dz-filename:hover span {
background-color: transparent;
border: none;
}
.dropzone .dz-preview .dz-remove {
position: absolute;
z-index: 30;
color: white;
margin-left: 15px;
padding: 10px;
top: inherit;
bottom: 15px;
border: 2px white solid;
text-decoration: none;
text-transform: uppercase;
font-size: 0.8rem;
font-weight: 800;
letter-spacing: 1.1px;
opacity: 0;
}
.dropzone .dz-preview:hover .dz-remove {
opacity: 1;
}
.dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark {
margin-left: -40px;
margin-top: -50px;
}
.dropzone .dz-preview .dz-success-mark i, .dropzone .dz-preview .dz-error-mark i {
color: white;
font-size: 5rem;
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 6000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
grid: {
top: 10,
left: '2%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: [{
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisTick: {
alignWithLabel: true
}
}],
yAxis: [{
type: 'value',
axisTick: {
show: false
}
}],
series: [{
name: 'pageA',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [79, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageB',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [80, 52, 200, 334, 390, 330, 220],
animationDuration
}, {
name: 'pageC',
type: 'bar',
stack: 'vistors',
barWidth: '60%',
data: [30, 52, 200, 334, 390, 330, 220],
animationDuration
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,438 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '500px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const dataMap = {}
function dataFormatter(obj) {
const pList = ['北京', '天津', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '上海', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '重庆', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆']
let temp
for (let year = 2002; year <= 2011; year++) {
let max = 0
let sum = 0
temp = obj[year]
for (let i = 0, l = temp.length; i < l; i++) {
max = Math.max(max, temp[i])
sum += temp[i]
obj[year][i] = {
name: pList[i],
value: temp[i]
}
}
obj[year + 'max'] = Math.floor(max / 100) * 100
obj[year + 'sum'] = sum
}
return obj
}
dataMap.dataGDP = dataFormatter({
2011: [16251.93, 11307.28, 24515.76, 11237.55, 14359.88, 22226.7, 10568.83, 12582, 19195.69, 49110.27, 32318.85, 15300.65, 17560.18, 11702.82, 45361.85, 26931.03, 19632.26, 19669.56, 53210.28, 11720.87, 2522.66, 10011.37, 21026.68, 5701.84, 8893.12, 605.83, 12512.3, 5020.37, 1670.44, 2102.21, 6610.05],
2010: [14113.58, 9224.46, 20394.26, 9200.86, 11672, 18457.27, 8667.58, 10368.6, 17165.98, 41425.48, 27722.31, 12359.33, 14737.12, 9451.26, 39169.92, 23092.36, 15967.61, 16037.96, 46013.06, 9569.85, 2064.5, 7925.58, 17185.48, 4602.16, 7224.18, 507.46, 10123.48, 4120.75, 1350.43, 1689.65, 5437.47],
2009: [12153.03, 7521.85, 17235.48, 7358.31, 9740.25, 15212.49, 7278.75, 8587, 15046.45, 34457.3, 22990.35, 10062.82, 12236.53, 7655.18, 33896.65, 19480.46, 12961.1, 13059.69, 39482.56, 7759.16, 1654.21, 6530.01, 14151.28, 3912.68, 6169.75, 441.36, 8169.8, 3387.56, 1081.27, 1353.31, 4277.05],
2008: [11115, 6719.01, 16011.97, 7315.4, 8496.2, 13668.58, 6426.1, 8314.37, 14069.87, 30981.98, 21462.69, 8851.66, 10823.01, 6971.05, 30933.28, 18018.53, 11328.92, 11555, 36796.71, 7021, 1503.06, 5793.66, 12601.23, 3561.56, 5692.12, 394.85, 7314.58, 3166.82, 1018.62, 1203.92, 4183.21],
2007: [9846.81, 5252.76, 13607.32, 6024.45, 6423.18, 11164.3, 5284.69, 7104, 12494.01, 26018.48, 18753.73, 7360.92, 9248.53, 5800.25, 25776.91, 15012.46, 9333.4, 9439.6, 31777.01, 5823.41, 1254.17, 4676.13, 10562.39, 2884.11, 4772.52, 341.43, 5757.29, 2703.98, 797.35, 919.11, 3523.16],
2006: [8117.78, 4462.74, 11467.6, 4878.61, 4944.25, 9304.52, 4275.12, 6211.8, 10572.24, 21742.05, 15718.47, 6112.5, 7583.85, 4820.53, 21900.19, 12362.79, 7617.47, 7688.67, 26587.76, 4746.16, 1065.67, 3907.23, 8690.24, 2338.98, 3988.14, 290.76, 4743.61, 2277.35, 648.5, 725.9, 3045.26],
2005: [6969.52, 3905.64, 10012.11, 4230.53, 3905.03, 8047.26, 3620.27, 5513.7, 9247.66, 18598.69, 13417.68, 5350.17, 6554.69, 4056.76, 18366.87, 10587.42, 6590.19, 6596.1, 22557.37, 3984.1, 918.75, 3467.72, 7385.1, 2005.42, 3462.73, 248.8, 3933.72, 1933.98, 543.32, 612.61, 2604.19],
2004: [6033.21, 3110.97, 8477.63, 3571.37, 3041.07, 6672, 3122.01, 4750.6, 8072.83, 15003.6, 11648.7, 4759.3, 5763.35, 3456.7, 15021.84, 8553.79, 5633.24, 5641.94, 18864.62, 3433.5, 819.66, 3034.58, 6379.63, 1677.8, 3081.91, 220.34, 3175.58, 1688.49, 466.1, 537.11, 2209.09],
2003: [5007.21, 2578.03, 6921.29, 2855.23, 2388.38, 6002.54, 2662.08, 4057.4, 6694.23, 12442.87, 9705.02, 3923.11, 4983.67, 2807.41, 12078.15, 6867.7, 4757.45, 4659.99, 15844.64, 2821.11, 713.96, 2555.72, 5333.09, 1426.34, 2556.02, 185.09, 2587.72, 1399.83, 390.2, 445.36, 1886.35],
2002: [4315, 2150.76, 6018.28, 2324.8, 1940.94, 5458.22, 2348.54, 3637.2, 5741.03, 10606.85, 8003.67, 3519.72, 4467.55, 2450.48, 10275.5, 6035.48, 4212.82, 4151.54, 13502.42, 2523.73, 642.73, 2232.86, 4725.01, 1243.43, 2312.82, 162.04, 2253.39, 1232.03, 340.65, 377.16, 1612.6]
})
dataMap.dataPI = dataFormatter({
2011: [136.27, 159.72, 2905.73, 641.42, 1306.3, 1915.57, 1277.44, 1701.5, 124.94, 3064.78, 1583.04, 2015.31, 1612.24, 1391.07, 3973.85, 3512.24, 2569.3, 2768.03, 2665.2, 2047.23, 659.23, 844.52, 2983.51, 726.22, 1411.01, 74.47, 1220.9, 678.75, 155.08, 184.14, 1139.03],
2010: [124.36, 145.58, 2562.81, 554.48, 1095.28, 1631.08, 1050.15, 1302.9, 114.15, 2540.1, 1360.56, 1729.02, 1363.67, 1206.98, 3588.28, 3258.09, 2147, 2325.5, 2286.98, 1675.06, 539.83, 685.38, 2482.89, 625.03, 1108.38, 68.72, 988.45, 599.28, 134.92, 159.29, 1078.63],
2009: [118.29, 128.85, 2207.34, 477.59, 929.6, 1414.9, 980.57, 1154.33, 113.82, 2261.86, 1163.08, 1495.45, 1182.74, 1098.66, 3226.64, 2769.05, 1795.9, 1969.69, 2010.27, 1458.49, 462.19, 606.8, 2240.61, 550.27, 1067.6, 63.88, 789.64, 497.05, 107.4, 127.25, 759.74],
2008: [112.83, 122.58, 2034.59, 313.58, 907.95, 1302.02, 916.72, 1088.94, 111.8, 2100.11, 1095.96, 1418.09, 1158.17, 1060.38, 3002.65, 2658.78, 1780, 1892.4, 1973.05, 1453.75, 436.04, 575.4, 2216.15, 539.19, 1020.56, 60.62, 753.72, 462.27, 105.57, 118.94, 691.07],
2007: [101.26, 110.19, 1804.72, 311.97, 762.1, 1133.42, 783.8, 915.38, 101.84, 1816.31, 986.02, 1200.18, 1002.11, 905.77, 2509.14, 2217.66, 1378, 1626.48, 1695.57, 1241.35, 361.07, 482.39, 2032, 446.38, 837.35, 54.89, 592.63, 387.55, 83.41, 97.89, 628.72],
2006: [88.8, 103.35, 1461.81, 276.77, 634.94, 939.43, 672.76, 750.14, 93.81, 1545.05, 925.1, 1011.03, 865.98, 786.14, 2138.9, 1916.74, 1140.41, 1272.2, 1532.17, 1032.47, 323.48, 386.38, 1595.48, 382.06, 724.4, 50.9, 484.81, 334, 67.55, 79.54, 527.8],
2005: [88.68, 112.38, 1400, 262.42, 589.56, 882.41, 625.61, 684.6, 90.26, 1461.51, 892.83, 966.5, 827.36, 727.37, 1963.51, 1892.01, 1082.13, 1100.65, 1428.27, 912.5, 300.75, 463.4, 1481.14, 368.94, 661.69, 48.04, 435.77, 308.06, 65.34, 72.07, 509.99],
2004: [87.36, 105.28, 1370.43, 276.3, 522.8, 798.43, 568.69, 605.79, 83.45, 1367.58, 814.1, 950.5, 786.84, 664.5, 1778.45, 1649.29, 1020.09, 1022.45, 1248.59, 817.88, 278.76, 428.05, 1379.93, 334.5, 607.75, 44.3, 387.88, 286.78, 60.7, 65.33, 461.26],
2003: [84.11, 89.91, 1064.05, 215.19, 420.1, 615.8, 488.23, 504.8, 81.02, 1162.45, 717.85, 749.4, 692.94, 560, 1480.67, 1198.7, 798.35, 886.47, 1072.91, 658.78, 244.29, 339.06, 1128.61, 298.69, 494.6, 40.7, 302.66, 237.91, 48.47, 55.63, 412.9],
2002: [82.44, 84.21, 956.84, 197.8, 374.69, 590.2, 446.17, 474.2, 79.68, 1110.44, 685.2, 783.66, 664.78, 535.98, 1390, 1288.36, 707, 847.25, 1015.08, 601.99, 222.89, 317.87, 1047.95, 281.1, 463.44, 39.75, 282.21, 215.51, 47.31, 52.95, 305]
})
dataMap.dataSI = dataFormatter({
2011: [3752.48, 5928.32, 13126.86, 6635.26, 8037.69, 12152.15, 5611.48, 5962.41, 7927.89, 25203.28, 16555.58, 8309.38, 9069.2, 6390.55, 24017.11, 15427.08, 9815.94, 9361.99, 26447.38, 5675.32, 714.5, 5543.04, 11029.13, 2194.33, 3780.32, 208.79, 6935.59, 2377.83, 975.18, 1056.15, 3225.9],
2010: [3388.38, 4840.23, 10707.68, 5234, 6367.69, 9976.82, 4506.31, 5025.15, 7218.32, 21753.93, 14297.93, 6436.62, 7522.83, 5122.88, 21238.49, 13226.38, 7767.24, 7343.19, 23014.53, 4511.68, 571, 4359.12, 8672.18, 1800.06, 3223.49, 163.92, 5446.1, 1984.97, 744.63, 827.91, 2592.15],
2009: [2855.55, 3987.84, 8959.83, 3993.8, 5114, 7906.34, 3541.92, 4060.72, 6001.78, 18566.37, 11908.49, 4905.22, 6005.3, 3919.45, 18901.83, 11010.5, 6038.08, 5687.19, 19419.7, 3381.54, 443.43, 3448.77, 6711.87, 1476.62, 2582.53, 136.63, 4236.42, 1527.24, 575.33, 662.32, 1929.59],
2008: [2626.41, 3709.78, 8701.34, 4242.36, 4376.19, 7158.84, 3097.12, 4319.75, 6085.84, 16993.34, 11567.42, 4198.93, 5318.44, 3554.81, 17571.98, 10259.99, 5082.07, 5028.93, 18502.2, 3037.74, 423.55, 3057.78, 5823.39, 1370.03, 2452.75, 115.56, 3861.12, 1470.34, 557.12, 609.98, 2070.76],
2007: [2509.4, 2892.53, 7201.88, 3454.49, 3193.67, 5544.14, 2475.45, 3695.58, 5571.06, 14471.26, 10154.25, 3370.96, 4476.42, 2975.53, 14647.53, 8282.83, 4143.06, 3977.72, 16004.61, 2425.29, 364.26, 2368.53, 4648.79, 1124.79, 2038.39, 98.48, 2986.46, 1279.32, 419.03, 455.04, 1647.55],
2006: [2191.43, 2457.08, 6110.43, 2755.66, 2374.96, 4566.83, 1915.29, 3365.31, 4969.95, 12282.89, 8511.51, 2711.18, 3695.04, 2419.74, 12574.03, 6724.61, 3365.08, 3187.05, 13469.77, 1878.56, 308.62, 1871.65, 3775.14, 967.54, 1705.83, 80.1, 2452.44, 1043.19, 331.91, 351.58, 1459.3],
2005: [2026.51, 2135.07, 5271.57, 2357.04, 1773.21, 3869.4, 1580.83, 2971.68, 4381.2, 10524.96, 7164.75, 2245.9, 3175.92, 1917.47, 10478.62, 5514.14, 2852.12, 2612.57, 11356.6, 1510.68, 240.83, 1564, 3067.23, 821.16, 1426.42, 63.52, 1951.36, 838.56, 264.61, 281.05, 1164.79],
2004: [1853.58, 1685.93, 4301.73, 1919.4, 1248.27, 3061.62, 1329.68, 2487.04, 3892.12, 8437.99, 6250.38, 1844.9, 2770.49, 1566.4, 8478.69, 4182.1, 2320.6, 2190.54, 9280.73, 1253.7, 205.6, 1376.91, 2489.4, 681.5, 1281.63, 52.74, 1553.1, 713.3, 211.7, 244.05, 914.47],
2003: [1487.15, 1337.31, 3417.56, 1463.38, 967.49, 2898.89, 1098.37, 2084.7, 3209.02, 6787.11, 5096.38, 1535.29, 2340.82, 1204.33, 6485.05, 3310.14, 1956.02, 1777.74, 7592.78, 984.08, 175.82, 1135.31, 2014.8, 569.37, 1047.66, 47.64, 1221.17, 572.02, 171.92, 194.27, 719.54],
2002: [1249.99, 1069.08, 2911.69, 1134.31, 754.78, 2609.85, 943.49, 1843.6, 2622.45, 5604.49, 4090.48, 1337.04, 2036.97, 941.77, 5184.98, 2768.75, 1709.89, 1523.5, 6143.4, 846.89, 148.88, 958.87, 1733.38, 481.96, 934.88, 32.72, 1007.56, 501.69, 144.51, 153.06, 603.15]
})
dataMap.dataTI = dataFormatter({
2011: [12363.18, 5219.24, 8483.17, 3960.87, 5015.89, 8158.98, 3679.91, 4918.09, 11142.86, 20842.21, 14180.23, 4975.96, 6878.74, 3921.2, 17370.89, 7991.72, 7247.02, 7539.54, 24097.7, 3998.33, 1148.93, 3623.81, 7014.04, 2781.29, 3701.79, 322.57, 4355.81, 1963.79, 540.18, 861.92, 2245.12],
2010: [10600.84, 4238.65, 7123.77, 3412.38, 4209.03, 6849.37, 3111.12, 4040.55, 9833.51, 17131.45, 12063.82, 4193.69, 5850.62, 3121.4, 14343.14, 6607.89, 6053.37, 6369.27, 20711.55, 3383.11, 953.67, 2881.08, 6030.41, 2177.07, 2892.31, 274.82, 3688.93, 1536.5, 470.88, 702.45, 1766.69],
2009: [9179.19, 3405.16, 6068.31, 2886.92, 3696.65, 5891.25, 2756.26, 3371.95, 8930.85, 13629.07, 9918.78, 3662.15, 5048.49, 2637.07, 11768.18, 5700.91, 5127.12, 5402.81, 18052.59, 2919.13, 748.59, 2474.44, 5198.8, 1885.79, 2519.62, 240.85, 3143.74, 1363.27, 398.54, 563.74, 1587.72],
2008: [8375.76, 2886.65, 5276.04, 2759.46, 3212.06, 5207.72, 2412.26, 2905.68, 7872.23, 11888.53, 8799.31, 3234.64, 4346.4, 2355.86, 10358.64, 5099.76, 4466.85, 4633.67, 16321.46, 2529.51, 643.47, 2160.48, 4561.69, 1652.34, 2218.81, 218.67, 2699.74, 1234.21, 355.93, 475, 1421.38],
2007: [7236.15, 2250.04, 4600.72, 2257.99, 2467.41, 4486.74, 2025.44, 2493.04, 6821.11, 9730.91, 7613.46, 2789.78, 3770, 1918.95, 8620.24, 4511.97, 3812.34, 3835.4, 14076.83, 2156.76, 528.84, 1825.21, 3881.6, 1312.94, 1896.78, 188.06, 2178.2, 1037.11, 294.91, 366.18, 1246.89],
2006: [5837.55, 1902.31, 3895.36, 1846.18, 1934.35, 3798.26, 1687.07, 2096.35, 5508.48, 7914.11, 6281.86, 2390.29, 3022.83, 1614.65, 7187.26, 3721.44, 3111.98, 3229.42, 11585.82, 1835.12, 433.57, 1649.2, 3319.62, 989.38, 1557.91, 159.76, 1806.36, 900.16, 249.04, 294.78, 1058.16],
2005: [4854.33, 1658.19, 3340.54, 1611.07, 1542.26, 3295.45, 1413.83, 1857.42, 4776.2, 6612.22, 5360.1, 2137.77, 2551.41, 1411.92, 5924.74, 3181.27, 2655.94, 2882.88, 9772.5, 1560.92, 377.17, 1440.32, 2836.73, 815.32, 1374.62, 137.24, 1546.59, 787.36, 213.37, 259.49, 929.41],
2004: [4092.27, 1319.76, 2805.47, 1375.67, 1270, 2811.95, 1223.64, 1657.77, 4097.26, 5198.03, 4584.22, 1963.9, 2206.02, 1225.8, 4764.7, 2722.4, 2292.55, 2428.95, 8335.3, 1361.92, 335.3, 1229.62, 2510.3, 661.8, 1192.53, 123.3, 1234.6, 688.41, 193.7, 227.73, 833.36],
2003: [3435.95, 1150.81, 2439.68, 1176.65, 1000.79, 2487.85, 1075.48, 1467.9, 3404.19, 4493.31, 3890.79, 1638.42, 1949.91, 1043.08, 4112.43, 2358.86, 2003.08, 1995.78, 7178.94, 1178.25, 293.85, 1081.35, 2189.68, 558.28, 1013.76, 96.76, 1063.89, 589.91, 169.81, 195.46, 753.91],
2002: [2982.57, 997.47, 2149.75, 992.69, 811.47, 2258.17, 958.88, 1319.4, 3038.9, 3891.92, 3227.99, 1399.02, 1765.8, 972.73, 3700.52, 1978.37, 1795.93, 1780.79, 6343.94, 1074.85, 270.96, 956.12, 1943.68, 480.37, 914.5, 89.56, 963.62, 514.83, 148.83, 171.14, 704.5]
})
dataMap.dataEstate = dataFormatter({
2011: [1074.93, 411.46, 918.02, 224.91, 384.76, 876.12, 238.61, 492.1, 1019.68, 2747.89, 1677.13, 634.92, 911.16, 402.51, 1838.14, 987, 634.67, 518.04, 3321.31, 465.68, 208.71, 396.28, 620.62, 160.3, 222.31, 17.44, 398.03, 134.25, 29.05, 79.01, 176.22],
2010: [1006.52, 377.59, 697.79, 192, 309.25, 733.37, 212.32, 391.89, 1002.5, 2600.95, 1618.17, 532.17, 679.03, 340.56, 1622.15, 773.23, 564.41, 464.21, 2813.95, 405.79, 188.33, 266.38, 558.56, 139.64, 223.45, 14.54, 315.95, 110.02, 25.41, 60.53, 143.44],
2009: [1062.47, 308.73, 612.4, 173.31, 286.65, 605.27, 200.14, 301.18, 1237.56, 2025.39, 1316.84, 497.94, 656.61, 305.9, 1329.59, 622.98, 546.11, 400.11, 2470.63, 348.98, 121.76, 229.09, 548.14, 136.15, 205.14, 13.28, 239.92, 101.37, 23.05, 47.56, 115.23],
2008: [844.59, 227.88, 513.81, 166.04, 273.3, 500.81, 182.7, 244.47, 939.34, 1626.13, 1052.03, 431.27, 506.98, 281.96, 1104.95, 512.42, 526.88, 340.07, 2057.45, 282.96, 95.6, 191.21, 453.63, 104.81, 195.48, 15.08, 193.27, 93.8, 19.96, 38.85, 89.79],
2007: [821.5, 183.44, 467.97, 134.12, 191.01, 410.43, 153.03, 225.81, 958.06, 1365.71, 981.42, 366.57, 511.5, 225.96, 953.69, 447.44, 409.65, 301.8, 2029.77, 239.45, 67.19, 196.06, 376.84, 93.19, 193.59, 13.24, 153.98, 83.52, 16.98, 29.49, 91.28],
2006: [658.3, 156.64, 397.14, 117.01, 136.5, 318.54, 131.01, 194.7, 773.61, 1017.91, 794.41, 281.98, 435.22, 184.67, 786.51, 348.7, 294.73, 254.81, 1722.07, 192.2, 44.45, 158.2, 336.2, 80.24, 165.92, 11.92, 125.2, 73.21, 15.17, 25.53, 68.9],
2005: [493.73, 122.67, 330.87, 106, 98.75, 256.77, 112.29, 163.34, 715.97, 799.73, 688.86, 231.66, 331.8, 171.88, 664.9, 298.19, 217.17, 215.63, 1430.37, 165.05, 38.2, 143.88, 286.23, 76.38, 148.69, 10.02, 108.62, 63.78, 14.1, 22.97, 55.79],
2004: [436.11, 106.14, 231.08, 95.1, 73.81, 203.1, 97.93, 137.74, 666.3, 534.17, 587.83, 188.28, 248.44, 167.2, 473.27, 236.44, 204.8, 191.5, 1103.75, 122.52, 30.64, 129.12, 264.3, 68.3, 116.54, 5.8, 95.9, 56.84, 13, 20.78, 53.55],
2003: [341.88, 92.31, 185.19, 78.73, 61.05, 188.49, 91.99, 127.2, 487.82, 447.47, 473.16, 162.63, 215.84, 138.02, 418.21, 217.58, 176.8, 186.49, 955.66, 100.93, 25.14, 113.69, 231.72, 59.86, 103.79, 4.35, 83.9, 48.09, 11.41, 16.85, 47.84],
2002: [298.02, 73.04, 140.89, 65.83, 51.48, 130.94, 76.11, 118.7, 384.86, 371.09, 360.63, 139.18, 188.09, 125.27, 371.13, 199.31, 145.17, 165.29, 808.16, 82.83, 21.45, 90.48, 210.82, 53.49, 95.68, 3.42, 77.68, 41.52, 9.74, 13.46, 43.04]
})
dataMap.dataFinancial = dataFormatter({
2011: [2215.41, 756.5, 746.01, 519.32, 447.46, 755.57, 207.65, 370.78, 2277.4, 2600.11, 2730.29, 503.85, 862.41, 357.44, 1640.41, 868.2, 674.57, 501.09, 2916.13, 445.37, 105.24, 704.66, 868.15, 297.27, 456.23, 31.7, 432.11, 145.05, 62.56, 134.18, 288.77],
2010: [1863.61, 572.99, 615.42, 448.3, 346.44, 639.27, 190.12, 304.59, 1950.96, 2105.92, 2326.58, 396.17, 767.58, 241.49, 1361.45, 697.68, 561.27, 463.16, 2658.76, 384.53, 78.12, 496.56, 654.7, 231.51, 375.08, 27.08, 384.75, 100.54, 54.53, 97.87, 225.2],
2009: [1603.63, 461.2, 525.67, 361.64, 291.1, 560.2, 180.83, 227.54, 1804.28, 1596.98, 1899.33, 359.6, 612.2, 165.1, 1044.9, 499.92, 479.11, 402.57, 2283.29, 336.82, 65.73, 389.97, 524.63, 194.44, 351.74, 23.17, 336.21, 88.27, 45.63, 75.54, 198.87],
2008: [1519.19, 368.1, 420.74, 290.91, 219.09, 455.07, 147.24, 177.43, 1414.21, 1298.48, 1653.45, 313.81, 497.65, 130.57, 880.28, 413.83, 393.05, 334.32, 1972.4, 249.01, 47.33, 303.01, 411.14, 151.55, 277.66, 22.42, 287.16, 72.49, 36.54, 64.8, 171.97],
2007: [1302.77, 288.17, 347.65, 218.73, 148.3, 386.34, 126.03, 155.48, 1209.08, 1054.25, 1251.43, 223.85, 385.84, 101.34, 734.9, 302.31, 337.27, 260.14, 1705.08, 190.73, 34.43, 247.46, 359.11, 122.25, 168.55, 11.51, 231.03, 61.6, 27.67, 51.05, 149.22],
2006: [982.37, 186.87, 284.04, 169.63, 108.21, 303.41, 100.75, 74.17, 825.2, 653.25, 906.37, 166.01, 243.9, 79.75, 524.94, 219.72, 174.99, 204.72, 899.91, 129.14, 16.37, 213.7, 299.5, 89.43, 143.62, 6.44, 152.25, 50.51, 23.69, 36.99, 99.25],
2005: [840.2, 147.4, 213.47, 135.07, 72.52, 232.85, 83.63, 35.03, 675.12, 492.4, 686.32, 127.05, 186.12, 69.55, 448.36, 181.74, 127.32, 162.37, 661.81, 91.93, 13.16, 185.18, 262.26, 73.67, 130.5, 7.57, 127.58, 44.73, 20.36, 32.25, 80.34],
2004: [713.79, 136.97, 209.1, 110.29, 55.89, 188.04, 77.17, 32.2, 612.45, 440.5, 523.49, 94.1, 171, 65.1, 343.37, 170.82, 118.85, 118.64, 602.68, 74, 11.56, 162.38, 236.5, 60.3, 118.4, 5.4, 90.1, 42.99, 19, 27.92, 70.3],
2003: [635.56, 112.79, 199.87, 118.48, 55.89, 145.38, 73.15, 32.2, 517.97, 392.11, 451.54, 87.45, 150.09, 64.31, 329.71, 165.11, 107.31, 99.35, 534.28, 61.59, 10.68, 147.04, 206.24, 48.01, 105.48, 4.74, 77.87, 42.31, 17.98, 24.8, 64.92],
2002: [561.91, 76.86, 179.6, 124.1, 48.39, 137.18, 75.45, 31.6, 485.25, 368.86, 347.53, 81.85, 138.28, 76.51, 310.07, 158.77, 96.95, 92.43, 454.65, 35.86, 10.08, 134.52, 183.13, 41.45, 102.39, 2.81, 67.3, 42.08, 16.75, 21.45, 52.18]
})
this.chart.setOption({
baseOption: {
timeline: {
axisType: 'category',
autoPlay: true,
playInterval: 1000,
data: [
'2002-01-01', '2003-01-01', '2004-01-01',
{
value: '2005-01-01',
tooltip: {
formatter: '{b} GDP达到一个高度'
},
symbol: 'diamond',
symbolSize: 16
},
'2006-01-01', '2007-01-01', '2008-01-01', '2009-01-01', '2010-01-01',
{
value: '2011-01-01',
tooltip: {
formatter: function(params) {
return params.name + 'GDP达到又一个高度'
}
},
symbol: 'diamond',
symbolSize: 18
}
],
label: {
formatter: function(s) {
return (new Date(s)).getFullYear()
}
}
},
title: {
subtext: '数据来自国家统计局'
},
tooltip: {},
legend: {
x: 'right',
data: ['第一产业', '第二产业', '第三产业', 'GDP', '金融', '房地产'],
selected: {
'GDP': false, '金融': false, '房地产': false
}
},
calculable: true,
grid: {
top: 80,
bottom: 100,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true,
formatter: function(params) {
return params.value.replace('\n', '')
}
}
}
}
},
xAxis: [
{
'type': 'category',
'axisLabel': {
'interval': 0,
rotate: 45
},
'data': [
'北京', '\n天津', '河北', '\n山西', '内蒙古', '\n辽宁', '吉林', '\n黑龙江',
'上海', '\n江苏', '浙江', '\n安徽', '福建', '\n江西', '山东', '\n河南',
'湖北', '\n湖南', '广东', '\n广西', '海南', '\n重庆', '四川', '\n贵州',
'云南', '\n西藏', '陕西', '\n甘肃', '青海', '\n宁夏', '新疆'
],
splitLine: { show: false }
}
],
yAxis: [
{
type: 'value',
name: 'GDP亿元'
}
],
series: [
{ name: 'GDP', type: 'bar' },
{ name: '金融', type: 'bar' },
{ name: '房地产', type: 'bar' },
{ name: '第一产业', type: 'bar' },
{ name: '第二产业', type: 'bar' },
{ name: '第三产业', type: 'bar' },
{
name: 'GDP占比',
type: 'pie',
center: ['75%', '35%'],
radius: '28%',
z: 100
}
]
},
options: [
{
title: { text: '2002全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2002'] },
{ data: dataMap.dataFinancial['2002'] },
{ data: dataMap.dataEstate['2002'] },
{ data: dataMap.dataPI['2002'] },
{ data: dataMap.dataSI['2002'] },
{ data: dataMap.dataTI['2002'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2002sum'] },
{ name: '第二产业', value: dataMap.dataSI['2002sum'] },
{ name: '第三产业', value: dataMap.dataTI['2002sum'] }
]
}
]
},
{
title: { text: '2003全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2003'] },
{ data: dataMap.dataFinancial['2003'] },
{ data: dataMap.dataEstate['2003'] },
{ data: dataMap.dataPI['2003'] },
{ data: dataMap.dataSI['2003'] },
{ data: dataMap.dataTI['2003'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2003sum'] },
{ name: '第二产业', value: dataMap.dataSI['2003sum'] },
{ name: '第三产业', value: dataMap.dataTI['2003sum'] }
]
}
]
},
{
title: { text: '2004全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2004'] },
{ data: dataMap.dataFinancial['2004'] },
{ data: dataMap.dataEstate['2004'] },
{ data: dataMap.dataPI['2004'] },
{ data: dataMap.dataSI['2004'] },
{ data: dataMap.dataTI['2004'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2004sum'] },
{ name: '第二产业', value: dataMap.dataSI['2004sum'] },
{ name: '第三产业', value: dataMap.dataTI['2004sum'] }
]
}
]
},
{
title: { text: '2005全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2005'] },
{ data: dataMap.dataFinancial['2005'] },
{ data: dataMap.dataEstate['2005'] },
{ data: dataMap.dataPI['2005'] },
{ data: dataMap.dataSI['2005'] },
{ data: dataMap.dataTI['2005'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2005sum'] },
{ name: '第二产业', value: dataMap.dataSI['2005sum'] },
{ name: '第三产业', value: dataMap.dataTI['2005sum'] }
]
}
]
},
{
title: { text: '2006全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2006'] },
{ data: dataMap.dataFinancial['2006'] },
{ data: dataMap.dataEstate['2006'] },
{ data: dataMap.dataPI['2006'] },
{ data: dataMap.dataSI['2006'] },
{ data: dataMap.dataTI['2006'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2006sum'] },
{ name: '第二产业', value: dataMap.dataSI['2006sum'] },
{ name: '第三产业', value: dataMap.dataTI['2006sum'] }
]
}
]
},
{
title: { text: '2007全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2007'] },
{ data: dataMap.dataFinancial['2007'] },
{ data: dataMap.dataEstate['2007'] },
{ data: dataMap.dataPI['2007'] },
{ data: dataMap.dataSI['2007'] },
{ data: dataMap.dataTI['2007'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2007sum'] },
{ name: '第二产业', value: dataMap.dataSI['2007sum'] },
{ name: '第三产业', value: dataMap.dataTI['2007sum'] }
]
}
]
},
{
title: { text: '2008全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2008'] },
{ data: dataMap.dataFinancial['2008'] },
{ data: dataMap.dataEstate['2008'] },
{ data: dataMap.dataPI['2008'] },
{ data: dataMap.dataSI['2008'] },
{ data: dataMap.dataTI['2008'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2008sum'] },
{ name: '第二产业', value: dataMap.dataSI['2008sum'] },
{ name: '第三产业', value: dataMap.dataTI['2008sum'] }
]
}
]
},
{
title: { text: '2009全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2009'] },
{ data: dataMap.dataFinancial['2009'] },
{ data: dataMap.dataEstate['2009'] },
{ data: dataMap.dataPI['2009'] },
{ data: dataMap.dataSI['2009'] },
{ data: dataMap.dataTI['2009'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2009sum'] },
{ name: '第二产业', value: dataMap.dataSI['2009sum'] },
{ name: '第三产业', value: dataMap.dataTI['2009sum'] }
]
}
]
},
{
title: { text: '2010全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2010'] },
{ data: dataMap.dataFinancial['2010'] },
{ data: dataMap.dataEstate['2010'] },
{ data: dataMap.dataPI['2010'] },
{ data: dataMap.dataSI['2010'] },
{ data: dataMap.dataTI['2010'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2010sum'] },
{ name: '第二产业', value: dataMap.dataSI['2010sum'] },
{ name: '第三产业', value: dataMap.dataTI['2010sum'] }
]
}
]
},
{
title: { text: '2011全国宏观经济指标' },
series: [
{ data: dataMap.dataGDP['2011'] },
{ data: dataMap.dataFinancial['2011'] },
{ data: dataMap.dataEstate['2011'] },
{ data: dataMap.dataPI['2011'] },
{ data: dataMap.dataSI['2011'] },
{ data: dataMap.dataTI['2011'] },
{
data: [
{ name: '第一产业', value: dataMap.dataPI['2011sum'] },
{ name: '第二产业', value: dataMap.dataSI['2011sum'] },
{ name: '第三产业', value: dataMap.dataTI['2011sum'] }
]
}
]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,120 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
title: {
text: '漏斗图',
subtext: '纯属虚构'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
dataView: { readOnly: false },
restore: {},
saveAsImage: {}
}
},
legend: {
data: ['展现', '点击', '访问', '咨询', '订单']
},
calculable: true,
series: [
{
name: '漏斗图',
type: 'funnel',
left: '10%',
top: 60,
bottom: 60,
width: '80%',
// height: {totalHeight} - y - y2,
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending',
gap: 2,
label: {
show: true,
position: 'inside'
},
labelLine: {
length: 10,
lineStyle: {
width: 1,
type: 'solid'
}
},
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
label: {
fontSize: 20
}
},
data: [
{ value: 60, name: '访问' },
{ value: 40, name: '咨询' },
{ value: 20, name: '订单' },
{ value: 80, name: '点击' },
{ value: 100, name: '展现' }
]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,74 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
formatter: '{a} <br/>{b} : {c}%'
},
toolbox: {
feature: {
restore: {},
saveAsImage: {}
}
},
series: [
{
name: '业务指标',
type: 'gauge',
detail: { formatter: '{value}%' },
data: [{ value: 50, name: '完成率' }]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,101 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const axisData = ['周一', '周二', '周三', '很长很长的周四', '周五', '周六', '周日']
const data = axisData.map(function(item, i) {
return Math.round(Math.random() * 1000 * (i + 1))
})
const links = data.map(function(item, i) {
return {
source: i,
target: i + 1
}
})
links.pop()
this.chart.setOption({
title: {
text: '笛卡尔坐标系上的 Graph'
},
tooltip: {},
xAxis: {
type: 'category',
boundaryGap: false,
data: axisData
},
yAxis: {
type: 'value'
},
series: [
{
type: 'graph',
layout: 'none',
coordinateSystem: 'cartesian2d',
symbolSize: 40,
label: {
normal: {
show: true
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
data: data,
links: links,
lineStyle: {
normal: {
color: '#2f4554'
}
}
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,105 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
position: 'top'
},
animation: false,
grid: {
height: '50%',
y: '10%'
},
xAxis: {
type: 'category',
data: ['12a', '1a', '2a', '3a', '4a', '5a', '6a', '7a', '8a', '9a', '10a', '11a', '12p', '1p', '2p', '3p', '4p', '5p', '6p', '7p', '8p', '9p', '10p', '11p'],
splitArea: {
show: true
}
},
yAxis: {
type: 'category',
data: ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday'],
splitArea: {
show: true
}
},
visualMap: {
min: 0,
max: 10,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '15%'
},
series: [{
name: 'Punch Card',
type: 'heatmap',
data: [[0, 0, 5], [0, 1, 1], [0, 2, 0], [0, 3, 0], [0, 4, 0], [0, 5, 0], [0, 6, 0], [0, 7, 0], [0, 8, 0], [0, 9, 0], [0, 10, 0], [0, 11, 2], [0, 12, 4], [0, 13, 1], [0, 14, 1], [0, 15, 3], [0, 16, 4], [0, 17, 6], [0, 18, 4], [0, 19, 4], [0, 20, 3], [0, 21, 3], [0, 22, 2], [0, 23, 5], [1, 0, 7], [1, 1, 0], [1, 2, 0], [1, 3, 0], [1, 4, 0], [1, 5, 0], [1, 6, 0], [1, 7, 0], [1, 8, 0], [1, 9, 0], [1, 10, 5], [1, 11, 2], [1, 12, 2], [1, 13, 6], [1, 14, 9], [1, 15, 11], [1, 16, 6], [1, 17, 7], [1, 18, 8], [1, 19, 12], [1, 20, 5], [1, 21, 5], [1, 22, 7], [1, 23, 2], [2, 0, 1], [2, 1, 1], [2, 2, 0], [2, 3, 0], [2, 4, 0], [2, 5, 0], [2, 6, 0], [2, 7, 0], [2, 8, 0], [2, 9, 0], [2, 10, 3], [2, 11, 2], [2, 12, 1], [2, 13, 9], [2, 14, 8], [2, 15, 10], [2, 16, 6], [2, 17, 5], [2, 18, 5], [2, 19, 5], [2, 20, 7], [2, 21, 4], [2, 22, 2], [2, 23, 4], [3, 0, 7], [3, 1, 3], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [3, 6, 0], [3, 7, 0], [3, 8, 1], [3, 9, 0], [3, 10, 5], [3, 11, 4], [3, 12, 7], [3, 13, 14], [3, 14, 13], [3, 15, 12], [3, 16, 9], [3, 17, 5], [3, 18, 5], [3, 19, 10], [3, 20, 6], [3, 21, 4], [3, 22, 4], [3, 23, 1], [4, 0, 1], [4, 1, 3], [4, 2, 0], [4, 3, 0], [4, 4, 0], [4, 5, 1], [4, 6, 0], [4, 7, 0], [4, 8, 0], [4, 9, 2], [4, 10, 4], [4, 11, 4], [4, 12, 2], [4, 13, 4], [4, 14, 4], [4, 15, 14], [4, 16, 12], [4, 17, 1], [4, 18, 8], [4, 19, 5], [4, 20, 3], [4, 21, 7], [4, 22, 3], [4, 23, 0], [5, 0, 2], [5, 1, 1], [5, 2, 0], [5, 3, 3], [5, 4, 0], [5, 5, 0], [5, 6, 0], [5, 7, 0], [5, 8, 2], [5, 9, 0], [5, 10, 4], [5, 11, 1], [5, 12, 5], [5, 13, 10], [5, 14, 5], [5, 15, 7], [5, 16, 11], [5, 17, 6], [5, 18, 0], [5, 19, 5], [5, 20, 3], [5, 21, 4], [5, 22, 2], [5, 23, 0], [6, 0, 1], [6, 1, 0], [6, 2, 0], [6, 3, 0], [6, 4, 0], [6, 5, 0], [6, 6, 0], [6, 7, 0], [6, 8, 0], [6, 9, 0], [6, 10, 1], [6, 11, 0], [6, 12, 2], [6, 13, 1], [6, 14, 3], [6, 15, 4], [6, 16, 0], [6, 17, 0], [6, 18, 0], [6, 19, 0], [6, 20, 1], [6, 21, 2], [6, 22, 2], [6, 23, 6]].map(function(item) {
return [item[1], item[0], item[2] || '-']
}),
label: {
normal: {
show: true
}
},
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,84 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
left: 'center',
bottom: '10',
data: ['Industries', 'Technology', 'Forex', 'Gold', 'Forecasts']
},
calculable: true,
series: [
{
name: 'WEEKLY WRITE ARTICLES',
type: 'pie',
roseType: 'radius',
radius: [15, 95],
center: ['50%', '38%'],
data: [
{ value: 320, name: 'Industries' },
{ value: 240, name: 'Technology' },
{ value: 149, name: 'Forex' },
{ value: 100, name: 'Gold' },
{ value: 59, name: 'Forecasts' }
],
animationEasing: 'cubicInOut',
animationDuration: 2600
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,149 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
title: {
text: 'Graph 简单示例'
},
tooltip: {},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
series: [
{
type: 'graph',
layout: 'none',
symbolSize: 50,
roam: true,
label: {
normal: {
show: true
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
edgeLabel: {
normal: {
textStyle: {
fontSize: 20
}
}
},
data: [{
name: '节点1',
x: 100,
y: 300
}, {
name: '节点2',
x: 1000,
y: 300
}, {
name: '节点3',
x: 550,
y: 100
}, {
name: '节点4',
x: 550,
y: 500
}],
// links: [],
links: [{
source: 0,
target: 1,
symbolSize: [5, 20],
label: {
normal: {
show: true
}
},
lineStyle: {
normal: {
width: 5,
curveness: 0.2
}
}
}, {
source: '节点2',
target: '节点1',
label: {
normal: {
show: true
}
},
lineStyle: {
normal: { curveness: 0.2 }
}
}, {
source: '节点1',
target: '节点3'
}, {
source: '节点2',
target: '节点3'
}, {
source: '节点2',
target: '节点4'
}, {
source: '节点1',
target: '节点4'
}],
lineStyle: {
normal: {
opacity: 0.9,
width: 2,
curveness: 0
}
}
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,120 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
const animationDuration = 3000
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
radar: {
radius: '66%',
center: ['50%', '42%'],
splitNumber: 8,
splitArea: {
areaStyle: {
color: 'rgba(127,95,132,.3)',
opacity: 1,
shadowBlur: 45,
shadowColor: 'rgba(0,0,0,.5)',
shadowOffsetX: 0,
shadowOffsetY: 15
}
},
indicator: [
{ name: 'Sales', max: 10000 },
{ name: 'Administration', max: 20000 },
{ name: 'Information Techology', max: 20000 },
{ name: 'Customer Support', max: 20000 },
{ name: 'Development', max: 20000 },
{ name: 'Marketing', max: 20000 }
]
},
legend: {
left: 'center',
bottom: '10',
data: ['Allocated Budget', 'Expected Spending', 'Actual Spending']
},
series: [{
type: 'radar',
symbolSize: 0,
areaStyle: {
normal: {
shadowBlur: 13,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1
}
},
data: [
{
value: [5000, 7000, 12000, 11000, 15000, 14000],
name: 'Allocated Budget'
},
{
value: [4000, 9000, 15000, 15000, 13000, 11000],
name: 'Expected Spending'
},
{
value: [5500, 11000, 12000, 15000, 12000, 12000],
name: 'Actual Spending'
}
],
animationDuration: animationDuration
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,149 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '500px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
x: 'left',
data: ['直达', '营销广告', '搜索引擎', '邮件营销', '联盟广告', '视频广告', '百度', '谷歌', '必应', '其他']
},
series: [
{
name: '访问来源',
type: 'pie',
selectedMode: 'single',
radius: [0, '30%'],
label: {
normal: {
position: 'inner'
}
},
labelLine: {
normal: {
show: false
}
},
data: [
{ value: 335, name: '直达', selected: true },
{ value: 679, name: '营销广告' },
{ value: 1548, name: '搜索引擎' }
]
},
{
name: '访问来源',
type: 'pie',
radius: ['40%', '55%'],
label: {
normal: {
formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}}{c} {per|{d}%} ',
backgroundColor: '#eee',
borderColor: '#aaa',
borderWidth: 1,
borderRadius: 4,
shadowBlur: 3,
shadowOffsetX: 2,
shadowOffsetY: 2,
shadowColor: '#999',
padding: [0, 7],
rich: {
a: {
color: '#999',
lineHeight: 22,
align: 'center'
},
abg: {
backgroundColor: '#333',
width: '100%',
align: 'right',
height: 22,
borderRadius: [4, 4, 0, 0]
},
hr: {
borderColor: '#aaa',
width: '100%',
borderWidth: 0.5,
height: 0
},
b: {
fontSize: 16,
lineHeight: 33
},
per: {
color: '#eee',
backgroundColor: '#334455',
padding: [2, 4],
borderRadius: 2
}
}
}
},
data: [
{ value: 335, name: '直达' },
{ value: 310, name: '邮件营销' },
{ value: 234, name: '联盟广告' },
{ value: 135, name: '视频广告' },
{ value: 1048, name: '百度' },
{ value: 251, name: '谷歌' },
{ value: 147, name: '必应' },
{ value: 102, name: '其他' }
]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,100 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
series: {
type: 'sankey',
layout: 'none',
focusNodeAdjacency: 'allEdges',
data: [{
name: 'a'
}, {
name: 'b'
}, {
name: 'a1'
}, {
name: 'a2'
}, {
name: 'b1'
}, {
name: 'c'
}],
links: [{
source: 'a',
target: 'a1',
value: 5
}, {
source: 'a',
target: 'a2',
value: 3
}, {
source: 'b',
target: 'b1',
value: 8
}, {
source: 'a',
target: 'b1',
value: 3
}, {
source: 'b1',
target: 'a1',
value: 1
}, {
source: 'b1',
target: 'c',
value: 2
}]
}
})
}
}
}
</script>

View File

@@ -0,0 +1,143 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const data = [
[[28604, 77, 17096869, 'Australia', 1990], [31163, 77.4, 27662440, 'Canada', 1990], [1516, 68, 1154605773, 'China', 1990], [13670, 74.7, 10582082, 'Cuba', 1990], [28599, 75, 4986705, 'Finland', 1990], [29476, 77.1, 56943299, 'France', 1990], [31476, 75.4, 78958237, 'Germany', 1990], [28666, 78.1, 254830, 'Iceland', 1990], [1777, 57.7, 870601776, 'India', 1990], [29550, 79.1, 122249285, 'Japan', 1990], [2076, 67.9, 20194354, 'North Korea', 1990], [12087, 72, 42972254, 'South Korea', 1990], [24021, 75.4, 3397534, 'New Zealand', 1990], [43296, 76.8, 4240375, 'Norway', 1990], [10088, 70.8, 38195258, 'Poland', 1990], [19349, 69.6, 147568552, 'Russia', 1990], [10670, 67.3, 53994605, 'Turkey', 1990], [26424, 75.7, 57110117, 'United Kingdom', 1990], [37062, 75.4, 252847810, 'United States', 1990]],
[[44056, 81.8, 23968973, 'Australia', 2015], [43294, 81.7, 35939927, 'Canada', 2015], [13334, 76.9, 1376048943, 'China', 2015], [21291, 78.5, 11389562, 'Cuba', 2015], [38923, 80.8, 5503457, 'Finland', 2015], [37599, 81.9, 64395345, 'France', 2015], [44053, 81.1, 80688545, 'Germany', 2015], [42182, 82.8, 329425, 'Iceland', 2015], [5903, 66.8, 1311050527, 'India', 2015], [36162, 83.5, 126573481, 'Japan', 2015], [1390, 71.4, 25155317, 'North Korea', 2015], [34644, 80.7, 50293439, 'South Korea', 2015], [34186, 80.6, 4528526, 'New Zealand', 2015], [64304, 81.6, 5210967, 'Norway', 2015], [24787, 77.3, 38611794, 'Poland', 2015], [23038, 73.13, 143456918, 'Russia', 2015], [19360, 76.5, 78665830, 'Turkey', 2015], [38225, 81.4, 64715810, 'United Kingdom', 2015], [53354, 79.1, 321773631, 'United States', 2015]]
]
this.chart.setOption({
title: {
text: '1990 与 2015 年各国家人均寿命与 GDP'
},
legend: {
right: 10,
data: ['1990', '2015']
},
xAxis: {
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
yAxis: {
splitLine: {
lineStyle: {
type: 'dashed'
}
},
scale: true
},
series: [{
name: '1990',
data: data[0],
type: 'scatter',
symbolSize: function(data) {
return Math.sqrt(data[2]) / 5e2
},
label: {
emphasis: {
show: true,
formatter: function(param) {
return param.data[3]
},
position: 'top'
}
},
itemStyle: {
normal: {
shadowBlur: 10,
shadowColor: 'rgba(120, 36, 50, 0.5)',
shadowOffsetY: 5,
color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [{
offset: 0,
color: 'rgb(251, 118, 123)'
}, {
offset: 1,
color: 'rgb(204, 46, 72)'
}])
}
}
}, {
name: '2015',
data: data[1],
type: 'scatter',
symbolSize: function(data) {
return Math.sqrt(data[2]) / 5e2
},
label: {
emphasis: {
show: true,
formatter: function(param) {
return param.data[3]
},
position: 'top'
}
},
itemStyle: {
normal: {
shadowBlur: 10,
shadowColor: 'rgba(25, 100, 150, 0.5)',
shadowOffsetY: 5,
color: new echarts.graphic.RadialGradient(0.4, 0.3, 1, [{
offset: 0,
color: 'rgb(129, 227, 238)'
}, {
offset: 1,
color: 'rgb(25, 183, 207)'
}])
}
}
}]
})
}
}
}
</script>

View File

@@ -0,0 +1,107 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '300px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
const data = [{
name: 'Grandpa',
children: [{
name: 'Uncle Leo',
value: 15,
children: [{
name: 'Cousin Jack',
value: 2
}, {
name: 'Cousin Mary',
value: 5,
children: [{
name: 'Jackson',
value: 2
}]
}, {
name: 'Cousin Ben',
value: 4
}]
}, {
name: 'Father',
value: 10,
children: [{
name: 'Me',
value: 5
}, {
name: 'Brother Peter',
value: 1
}]
}]
}, {
name: 'Nancy',
children: [{
name: 'Uncle Nike',
children: [{
name: 'Cousin Betty',
value: 1
}, {
name: 'Cousin Jenny',
value: 2
}]
}]
}]
this.chart.setOption({
series: {
type: 'sunburst',
data: data,
radius: [0, '90%'],
label: {
rotate: 'radial'
}
}
})
}
}
}
</script>

View File

@@ -0,0 +1,148 @@
<template>
<div :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { debounce } from '@/utils'
export default {
props: {
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '500px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
this.__resizeHandler = debounce(() => {
if (this.chart) {
this.chart.resize()
}
}, 100)
window.addEventListener('resize', this.__resizeHandler)
},
beforeDestroy() {
if (!this.chart) {
return
}
window.removeEventListener('resize', this.__resizeHandler)
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$el, 'macarons')
this.chart.setOption({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'line',
lineStyle: {
color: 'rgba(0,0,0,0.2)',
width: 1,
type: 'solid'
}
}
},
legend: {
data: ['DQ', 'TY', 'SS', 'QG', 'SY', 'DD']
},
singleAxis: {
top: 50,
bottom: 50,
axisTick: {},
axisLabel: {},
type: 'time',
axisPointer: {
animation: true,
label: {
show: true
}
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
opacity: 0.2
}
}
},
series: [
{
type: 'themeRiver',
itemStyle: {
emphasis: {
shadowBlur: 20,
shadowColor: 'rgba(0, 0, 0, 0.8)'
}
},
data: [['2015/11/08', 10, 'DQ'], ['2015/11/09', 15, 'DQ'], ['2015/11/10', 35, 'DQ'],
['2015/11/11', 38, 'DQ'], ['2015/11/12', 22, 'DQ'], ['2015/11/13', 16, 'DQ'],
['2015/11/14', 7, 'DQ'], ['2015/11/15', 2, 'DQ'], ['2015/11/16', 17, 'DQ'],
['2015/11/17', 33, 'DQ'], ['2015/11/18', 40, 'DQ'], ['2015/11/19', 32, 'DQ'],
['2015/11/20', 26, 'DQ'], ['2015/11/21', 35, 'DQ'], ['2015/11/22', 40, 'DQ'],
['2015/11/23', 32, 'DQ'], ['2015/11/24', 26, 'DQ'], ['2015/11/25', 22, 'DQ'],
['2015/11/26', 16, 'DQ'], ['2015/11/27', 22, 'DQ'], ['2015/11/28', 10, 'DQ'],
['2015/11/08', 35, 'TY'], ['2015/11/09', 36, 'TY'], ['2015/11/10', 37, 'TY'],
['2015/11/11', 22, 'TY'], ['2015/11/12', 24, 'TY'], ['2015/11/13', 26, 'TY'],
['2015/11/14', 34, 'TY'], ['2015/11/15', 21, 'TY'], ['2015/11/16', 18, 'TY'],
['2015/11/17', 45, 'TY'], ['2015/11/18', 32, 'TY'], ['2015/11/19', 35, 'TY'],
['2015/11/20', 30, 'TY'], ['2015/11/21', 28, 'TY'], ['2015/11/22', 27, 'TY'],
['2015/11/23', 26, 'TY'], ['2015/11/24', 15, 'TY'], ['2015/11/25', 30, 'TY'],
['2015/11/26', 35, 'TY'], ['2015/11/27', 42, 'TY'], ['2015/11/28', 42, 'TY'],
['2015/11/08', 21, 'SS'], ['2015/11/09', 25, 'SS'], ['2015/11/10', 27, 'SS'],
['2015/11/11', 23, 'SS'], ['2015/11/12', 24, 'SS'], ['2015/11/13', 21, 'SS'],
['2015/11/14', 35, 'SS'], ['2015/11/15', 39, 'SS'], ['2015/11/16', 40, 'SS'],
['2015/11/17', 36, 'SS'], ['2015/11/18', 33, 'SS'], ['2015/11/19', 43, 'SS'],
['2015/11/20', 40, 'SS'], ['2015/11/21', 34, 'SS'], ['2015/11/22', 28, 'SS'],
['2015/11/23', 26, 'SS'], ['2015/11/24', 37, 'SS'], ['2015/11/25', 41, 'SS'],
['2015/11/26', 46, 'SS'], ['2015/11/27', 47, 'SS'], ['2015/11/28', 41, 'SS'],
['2015/11/08', 10, 'QG'], ['2015/11/09', 15, 'QG'], ['2015/11/10', 35, 'QG'],
['2015/11/11', 38, 'QG'], ['2015/11/12', 22, 'QG'], ['2015/11/13', 16, 'QG'],
['2015/11/14', 7, 'QG'], ['2015/11/15', 2, 'QG'], ['2015/11/16', 17, 'QG'],
['2015/11/17', 33, 'QG'], ['2015/11/18', 40, 'QG'], ['2015/11/19', 32, 'QG'],
['2015/11/20', 26, 'QG'], ['2015/11/21', 35, 'QG'], ['2015/11/22', 40, 'QG'],
['2015/11/23', 32, 'QG'], ['2015/11/24', 26, 'QG'], ['2015/11/25', 22, 'QG'],
['2015/11/26', 16, 'QG'], ['2015/11/27', 22, 'QG'], ['2015/11/28', 10, 'QG'],
['2015/11/08', 10, 'SY'], ['2015/11/09', 15, 'SY'], ['2015/11/10', 35, 'SY'],
['2015/11/11', 38, 'SY'], ['2015/11/12', 22, 'SY'], ['2015/11/13', 16, 'SY'],
['2015/11/14', 7, 'SY'], ['2015/11/15', 2, 'SY'], ['2015/11/16', 17, 'SY'],
['2015/11/17', 33, 'SY'], ['2015/11/18', 40, 'SY'], ['2015/11/19', 32, 'SY'],
['2015/11/20', 26, 'SY'], ['2015/11/21', 35, 'SY'], ['2015/11/22', 4, 'SY'],
['2015/11/23', 32, 'SY'], ['2015/11/24', 26, 'SY'], ['2015/11/25', 22, 'SY'],
['2015/11/26', 16, 'SY'], ['2015/11/27', 22, 'SY'], ['2015/11/28', 10, 'SY'],
['2015/11/08', 10, 'DD'], ['2015/11/09', 15, 'DD'], ['2015/11/10', 35, 'DD'],
['2015/11/11', 38, 'DD'], ['2015/11/12', 22, 'DD'], ['2015/11/13', 16, 'DD'],
['2015/11/14', 7, 'DD'], ['2015/11/15', 2, 'DD'], ['2015/11/16', 17, 'DD'],
['2015/11/17', 33, 'DD'], ['2015/11/18', 4, 'DD'], ['2015/11/19', 32, 'DD'],
['2015/11/20', 26, 'DD'], ['2015/11/21', 35, 'DD'], ['2015/11/22', 40, 'DD'],
['2015/11/23', 32, 'DD'], ['2015/11/24', 26, 'DD'], ['2015/11/25', 22, 'DD'],
['2015/11/26', 16, 'DD'], ['2015/11/27', 22, 'DD'], ['2015/11/28', 10, 'DD']]
}
]
})
}
}
}
</script>

View File

@@ -0,0 +1,78 @@
<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>

View File

@@ -0,0 +1,54 @@
<template>
<a href="https://github.com/alldatacenter/alldata" target="_blank" class="github-corner" aria-label="View source on Github">
<svg
width="80"
height="80"
viewBox="0 0 250 250"
style="fill:#40c9c6; color:#fff;"
aria-hidden="true"
>
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" />
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor"
style="transform-origin: 130px 106px;"
class="octo-arm"
/>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor"
class="octo-body"
/>
</svg>
</a>
</template>
<style scoped>
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0)
}
20%,
60% {
transform: rotate(-25deg)
}
40%,
80% {
transform: rotate(10deg)
}
}
@media (max-width:500px) {
.github-corner:hover .octo-arm {
animation: none
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
}
</style>

View File

@@ -0,0 +1,44 @@
<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>

View File

@@ -0,0 +1,188 @@
<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.state.permission.routers
}
},
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) {
if (this.ishttp(val.path)) {
// http(s):// 路径新窗口打开
window.open(val.path, '_blank')
} else {
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: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : 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 = []
}
},
ishttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
}
}
}
</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>

View File

@@ -0,0 +1,68 @@
<!-- @author zhengjie -->
<template>
<div class="icon-body">
<el-input v-model="name" style="position: relative;" clearable placeholder="请输入图标名称" @clear="filterIcons" @input.native="filterIcons">
<i slot="suffix" class="el-icon-search el-input__icon" />
</el-input>
<div class="icon-list">
<div v-for="(item, index) in iconList" :key="index" @click="selectedIcon(item)">
<svg-icon :icon-class="item" style="height: 30px;width: 16px;" />
<span>{{ item }}</span>
</div>
</div>
</div>
</template>
<script>
import icons from './requireIcons'
export default {
name: 'IconSelect',
data() {
return {
name: '',
iconList: icons
}
},
methods: {
filterIcons() {
this.iconList = icons
if (this.name) {
this.iconList = this.iconList.filter(item => item.includes(this.name))
}
},
selectedIcon(name) {
this.$emit('selected', name)
document.body.click()
},
reset() {
this.name = ''
this.iconList = icons
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.icon-body {
width: 100%;
padding: 10px;
.icon-list {
height: 200px;
overflow-y: scroll;
div {
height: 30px;
line-height: 30px;
margin-bottom: -5px;
cursor: pointer;
width: 33%;
float: left;
}
span {
display: inline-block;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
}
}
</style>

View File

@@ -0,0 +1,11 @@
const req = require.context('../../assets/icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map(i => {
return i.match(re)[1]
})
export default icons

View File

@@ -0,0 +1,30 @@
<template>
<div v-loading="loading" :style="'height:'+ height">
<iframe :src="src" frameborder="no" style="width: 100%;height: 100%" scrolling="auto" />
</div>
</template>
<script>
export default {
props: {
src: {
type: String,
required: true
}
},
data() {
return {
height: document.documentElement.clientHeight - 94.5 + 'px;',
loading: true
}
},
mounted: function() {
setTimeout(() => {
this.loading = false
}, 230)
const that = this
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + 'px;'
}
}
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
/**
* database64文件格式转换为2进制
*
* @param {[String]} data dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
* @param {[String]} mime [description]
* @return {[blob]} [description]
*/
export default function(data, mime) {
data = data.split(',')[1]
data = window.atob(data)
var ia = new Uint8Array(data.length)
for (var i = 0; i < data.length; i++) {
ia[i] = data.charCodeAt(i)
}
// canvas.toDataURL 返回的默认格式就是 image/png
return new Blob([ia], {
type: mime
})
}

View File

@@ -0,0 +1,39 @@
/**
* 点击波纹效果
*
* @param {[event]} e [description]
* @param {[Object]} arg_opts [description]
* @return {[bollean]} [description]
*/
export default function(e, arg_opts) {
var opts = Object.assign({
ele: e.target, // 波纹作用元素
type: 'hit', // hit点击位置扩散center中心点扩展
bgc: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
}, arg_opts)
var target = opts.ele
if (target) {
var rect = target.getBoundingClientRect()
var ripple = target.querySelector('.e-ripple')
if (!ripple) {
ripple = document.createElement('span')
ripple.className = 'e-ripple'
ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
target.appendChild(ripple)
} else {
ripple.className = 'e-ripple'
}
switch (opts.type) {
case 'center':
ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
break
default:
ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
}
ripple.style.backgroundColor = opts.bgc
ripple.className = 'e-ripple z-active'
return false
}
}

View File

@@ -0,0 +1,232 @@
export default {
zh: {
hint: '点击或拖动图片至此处',
loading: '正在上传',
noSupported: '浏览器不支持该功能请使用IE10以上或其他现在浏览器',
success: '上传成功',
fail: '图片上传失败',
preview: '头像预览',
btn: {
off: '取消',
close: '关闭',
back: '上一步',
save: '保存'
},
error: {
onlyImg: '仅限图片格式',
outOfSize: '单文件大小不能超过 ',
lowestPx: '图片最低像素为*'
}
},
'zh-tw': {
hint: '點擊或拖動圖片至此處',
loading: '正在上傳',
noSupported: '瀏覽器不支持該功能請使用IE10以上或其他現代瀏覽器',
success: '上傳成功',
fail: '圖片上傳失敗',
preview: '頭像預覽',
btn: {
off: '取消',
close: '關閉',
back: '上一步',
save: '保存'
},
error: {
onlyImg: '僅限圖片格式',
outOfSize: '單文件大小不能超過 ',
lowestPx: '圖片最低像素為*'
}
},
en: {
hint: 'Click or drag the file here to upload',
loading: 'Uploading',
noSupported: 'Browser is not supported, please use IE10+ or other browsers',
success: 'Upload success',
fail: 'Upload failed',
preview: 'Preview',
btn: {
off: 'Cancel',
close: 'Close',
back: 'Back',
save: 'Save'
},
error: {
onlyImg: 'Image only',
outOfSize: 'Image exceeds size limit: ',
lowestPx: 'Image\'s size is too low. Expected at least: '
}
},
ro: {
hint: 'Atinge sau trage fișierul aici',
loading: 'Se încarcă',
noSupported: 'Browser-ul tău nu suportă acest feature. Te rugăm încearcă cu alt browser.',
success: 'S-a încărcat cu succes',
fail: 'A apărut o problemă la încărcare',
preview: 'Previzualizează',
btn: {
off: 'Anulează',
close: 'Închide',
back: 'Înapoi',
save: 'Salvează'
},
error: {
onlyImg: 'Doar imagini',
outOfSize: 'Imaginea depășește limita de: ',
loewstPx: 'Imaginea este prea mică; Minim: '
}
},
ru: {
hint: 'Нажмите, или перетащите файл в это окно',
loading: 'Загружаю',
noSupported: 'Ваш браузер не поддерживается, пожалуйста, используйте IE10 + или другие браузеры',
success: 'Загрузка выполнена успешно',
fail: 'Ошибка загрузки',
preview: 'Предпросмотр',
btn: {
off: 'Отменить',
close: 'Закрыть',
back: 'Назад',
save: 'Сохранить'
},
error: {
onlyImg: 'Только изображения',
outOfSize: 'Изображение превышает предельный размер: ',
lowestPx: 'Минимальный размер изображения: '
}
},
'pt-br': {
hint: 'Clique ou arraste o arquivo aqui para carregar',
loading: 'Carregando',
noSupported: 'Browser não suportado, use o IE10+ ou outro browser',
success: 'Sucesso ao carregar imagem',
fail: 'Falha ao carregar imagem',
preview: 'Pré-visualizar',
btn: {
off: 'Cancelar',
close: 'Fechar',
back: 'Voltar',
save: 'Salvar'
},
error: {
onlyImg: 'Apenas imagens',
outOfSize: 'A imagem excede o limite de tamanho: ',
lowestPx: 'O tamanho da imagem é muito pequeno. Tamanho mínimo: '
}
},
fr: {
hint: 'Cliquez ou glissez le fichier ici.',
loading: 'Téléchargement',
noSupported: 'Votre navigateur n\'est pas supporté. Utilisez IE10 + ou un autre navigateur s\'il vous plaît.',
success: 'Téléchargement réussit',
fail: 'Téléchargement echoué',
preview: 'Aperçu',
btn: {
off: 'Annuler',
close: 'Fermer',
back: 'Retour',
save: 'Enregistrer'
},
error: {
onlyImg: 'Image uniquement',
outOfSize: 'L\'image sélectionnée dépasse la taille maximum: ',
lowestPx: 'L\'image sélectionnée est trop petite. Dimensions attendues: '
}
},
nl: {
hint: 'Klik hier of sleep een afbeelding in dit vlak',
loading: 'Uploaden',
noSupported: 'Je browser wordt helaas niet ondersteund. Gebruik IE10+ of een andere browser.',
success: 'Upload succesvol',
fail: 'Upload mislukt',
preview: 'Voorbeeld',
btn: {
off: 'Annuleren',
close: 'Sluiten',
back: 'Terug',
save: 'Opslaan'
},
error: {
onlyImg: 'Alleen afbeeldingen',
outOfSize: 'De afbeelding is groter dan: ',
lowestPx: 'De afbeelding is te klein! Minimale afmetingen: '
}
},
tr: {
hint: 'Tıkla veya yüklemek istediğini buraya sürükle',
loading: 'Yükleniyor',
noSupported: 'Tarayıcı desteklenmiyor, lütfen IE10+ veya farklı tarayıcı kullanın',
success: 'Yükleme başarılı',
fail: 'Yüklemede hata oluştu',
preview: 'Önizle',
btn: {
off: 'İptal',
close: 'Kapat',
back: 'Geri',
save: 'Kaydet'
},
error: {
onlyImg: 'Sadece resim',
outOfSize: 'Resim yükleme limitini ıyor: ',
lowestPx: 'Resmin boyutu çok küçük. En az olması gereken: '
}
},
'es-MX': {
hint: 'Selecciona o arrastra una imagen',
loading: 'Subiendo...',
noSupported: 'Tu navegador no es soportado, porfavor usa IE10+ u otros navegadores mas recientes',
success: 'Subido exitosamente',
fail: 'Sucedió un error',
preview: 'Vista previa',
btn: {
off: 'Cancelar',
close: 'Cerrar',
back: 'Atras',
save: 'Guardar'
},
error: {
onlyImg: 'Unicamente imagenes',
outOfSize: 'La imagen excede el tamaño maximo:',
lowestPx: 'La imagen es demasiado pequeño. Se espera por lo menos:'
}
},
de: {
hint: 'Klick hier oder zieh eine Datei hier rein zum Hochladen',
loading: 'Hochladen',
noSupported: 'Browser wird nicht unterstützt, bitte verwende IE10+ oder andere Browser',
success: 'Upload erfolgreich',
fail: 'Upload fehlgeschlagen',
preview: 'Vorschau',
btn: {
off: 'Abbrechen',
close: 'Schließen',
back: 'Zurück',
save: 'Speichern'
},
error: {
onlyImg: 'Nur Bilder',
outOfSize: 'Das Bild ist zu groß: ',
lowestPx: 'Das Bild ist zu klein. Mindestens: '
}
},
ja: {
hint: 'クリックドラッグしてファイルをアップロード',
loading: 'アップロード中...',
noSupported: 'このブラウザは対応されていませんIE10+かその他の主要ブラウザをお使いください',
success: 'アップロード成功',
fail: 'アップロード失敗',
preview: 'プレビュー',
btn: {
off: 'キャンセル',
close: '閉じる',
back: '戻る',
save: '保存'
},
error: {
onlyImg: '画像のみ',
outOfSize: '画像サイズが上限を超えています上限: ',
lowestPx: '画像が小さすぎます最小サイズ: '
}
}
}

View File

@@ -0,0 +1,7 @@
export default {
'jpg': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'svg': 'image/svg+xml',
'psd': 'image/photoshop'
}

View File

@@ -0,0 +1,77 @@
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// 替换主题这里需修改名称
import 'codemirror/theme/idea.css'
import 'codemirror/mode/clike/clike'
export default {
props: {
value: {
type: String,
required: true
},
height: {
type: String,
required: true
}
},
data() {
return {
editor: false
}
},
watch: {
value(value) {
const editorValue = this.editor.getValue()
if (value !== editorValue) {
this.editor.setValue(this.value)
}
},
height(value) {
this.editor.setSize('auto', this.height)
}
},
mounted() {
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
mode: 'text/x-java',
lineNumbers: true,
lint: true,
lineWrapping: true,
tabSize: 2,
cursorHeight: 0.9,
// 替换主题这里需修改名称
theme: 'idea',
readOnly: true
})
this.editor.setSize('auto', this.height)
this.editor.setValue(this.value)
},
methods: {
getValue() {
return this.editor.getValue()
}
}
}
</script>
<style scoped>
.json-editor{
height: 100%;
margin-bottom: 10px;
}
.json-editor >>> .CodeMirror {
font-size: 14px;
font-weight:normal
}
.json-editor >>> .CodeMirror-scroll{
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>

View File

@@ -0,0 +1,72 @@
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/rubyblue.css'
require('script-loader!jsonlint')
import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
import 'codemirror/addon/lint/json-lint'
export default {
name: 'JsonEditor',
/* eslint-disable vue/require-prop-types */
props: ['value'],
data() {
return {
jsonEditor: false
}
},
watch: {
value(value) {
const editorValue = this.jsonEditor.getValue()
if (value !== editorValue) {
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
}
}
},
mounted() {
this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
lineNumbers: true,
mode: 'application/json',
gutters: ['CodeMirror-lint-markers'],
theme: 'rubyblue',
lint: true
})
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
this.jsonEditor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.jsonEditor.getValue()
}
}
}
</script>
<style scoped>
.json-editor{
height: 100%;
position: relative;
}
.json-editor >>> .CodeMirror {
height: auto;
min-height: 300px;
}
.json-editor >>> .CodeMirror-scroll{
min-height: 300px;
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<div class="board-column">
<div class="board-column-header">
{{ headerText }}
</div>
<draggable
:list="list"
v-bind="$attrs"
class="board-column-content"
:set-data="setData"
>
<div v-for="element in list" :key="element.id" class="board-item">
{{ element.name }} {{ element.id }}
</div>
</draggable>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'DragKanbanDemo',
components: {
draggable
},
props: {
headerText: {
type: String,
default: 'Header'
},
options: {
type: Object,
default() {
return {}
}
},
list: {
type: Array,
default() {
return []
}
}
},
methods: {
setData(dataTransfer) {
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
}
}
}
</script>
<style lang="scss" scoped>
.board-column {
min-width: 300px;
min-height: 100px;
height: auto;
overflow: hidden;
background: #f0f0f0;
border-radius: 3px;
.board-column-header {
height: 50px;
line-height: 50px;
overflow: hidden;
padding: 0 20px;
text-align: center;
background: #333;
color: #fff;
border-radius: 3px 3px 0 0;
}
.board-column-content {
height: auto;
overflow: hidden;
border: 10px solid transparent;
min-height: 60px;
display: flex;
justify-content: flex-start;
flex-direction: column;
align-items: center;
.board-item {
cursor: pointer;
width: 100%;
height: 64px;
margin: 5px 0;
background-color: #fff;
text-align: left;
line-height: 54px;
padding: 5px 10px;
box-sizing: border-box;
box-shadow: 0px 1px 3px 0 rgba(0, 0, 0, 0.2);
}
}
}
</style>

View File

@@ -0,0 +1,360 @@
<template>
<div :class="computedClasses" class="material-input__component">
<div :class="{iconClass:icon}">
<i v-if="icon" :class="['el-icon-' + icon]" class="el-input__icon material-input__icon" />
<input
v-if="type === 'email'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="email"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'url'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="url"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'number'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:step="step"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="number"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'password'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:max="max"
:min="min"
:required="required"
type="password"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'tel'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:required="required"
type="tel"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<input
v-if="type === 'text'"
v-model="currentValue"
:name="name"
:placeholder="fillPlaceHolder"
:readonly="readonly"
:disabled="disabled"
:autocomplete="autoComplete"
:minlength="minlength"
:maxlength="maxlength"
:required="required"
type="text"
class="material-input"
@focus="handleMdFocus"
@blur="handleMdBlur"
@input="handleModelInput"
>
<span class="material-input-bar" />
<label class="material-label">
<slot />
</label>
</div>
</div>
</template>
<script>
// source:https://github.com/wemake-services/vue-material-input/blob/master/src/components/MaterialInput.vue
export default {
name: 'MdInput',
props: {
/* eslint-disable */
icon: String,
name: String,
type: {
type: String,
default: 'text'
},
value: [String, Number],
placeholder: String,
readonly: Boolean,
disabled: Boolean,
min: String,
max: String,
step: String,
minlength: Number,
maxlength: Number,
required: {
type: Boolean,
default: true
},
autoComplete: {
type: String,
default: 'off'
},
validateEvent: {
type: Boolean,
default: true
}
},
data() {
return {
currentValue: this.value,
focus: false,
fillPlaceHolder: null
}
},
computed: {
computedClasses() {
return {
'material--active': this.focus,
'material--disabled': this.disabled,
'material--raised': Boolean(this.focus || this.currentValue) // has value
}
}
},
watch: {
value(newValue) {
this.currentValue = newValue
}
},
methods: {
handleModelInput(event) {
const value = event.target.value
this.$emit('input', value)
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.change', [value])
}
}
this.$emit('change', value)
},
handleMdFocus(event) {
this.focus = true
this.$emit('focus', event)
if (this.placeholder && this.placeholder !== '') {
this.fillPlaceHolder = this.placeholder
}
},
handleMdBlur(event) {
this.focus = false
this.$emit('blur', event)
this.fillPlaceHolder = null
if (this.$parent.$options.componentName === 'ElFormItem') {
if (this.validateEvent) {
this.$parent.$emit('el.form.blur', [this.currentValue])
}
}
}
}
}
</script>
<style lang="scss" scoped>
// Fonts:
$font-size-base: 16px;
$font-size-small: 18px;
$font-size-smallest: 12px;
$font-weight-normal: normal;
$font-weight-bold: bold;
$apixel: 1px;
// Utils
$spacer: 12px;
$transition: 0.2s ease all;
$index: 0px;
$index-has-icon: 30px;
// Theme:
$color-white: white;
$color-grey: #9E9E9E;
$color-grey-light: #E0E0E0;
$color-blue: #2196F3;
$color-red: #F44336;
$color-black: black;
// Base clases:
%base-bar-pseudo {
content: '';
height: 1px;
width: 0;
bottom: 0;
position: absolute;
transition: $transition;
}
// Mixins:
@mixin slided-top() {
top: - ($font-size-base + $spacer);
left: 0;
font-size: $font-size-base;
font-weight: $font-weight-bold;
}
// Component:
.material-input__component {
margin-top: 36px;
position: relative;
* {
box-sizing: border-box;
}
.iconClass {
.material-input__icon {
position: absolute;
left: 0;
line-height: $font-size-base;
color: $color-blue;
top: $spacer;
width: $index-has-icon;
height: $font-size-base;
font-size: $font-size-base;
font-weight: $font-weight-normal;
pointer-events: none;
}
.material-label {
left: $index-has-icon;
}
.material-input {
text-indent: $index-has-icon;
}
}
.material-input {
font-size: $font-size-base;
padding: $spacer $spacer $spacer - $apixel * 10 $spacer / 2;
display: block;
width: 100%;
border: none;
line-height: 1;
border-radius: 0;
&:focus {
outline: none;
border: none;
border-bottom: 1px solid transparent; // fixes the height issue
}
}
.material-label {
font-weight: $font-weight-normal;
position: absolute;
pointer-events: none;
left: $index;
top: 0;
transition: $transition;
font-size: $font-size-small;
}
.material-input-bar {
position: relative;
display: block;
width: 100%;
&:before {
@extend %base-bar-pseudo;
left: 50%;
}
&:after {
@extend %base-bar-pseudo;
right: 50%;
}
}
// Disabled state:
&.material--disabled {
.material-input {
border-bottom-style: dashed;
}
}
// Raised state:
&.material--raised {
.material-label {
@include slided-top();
}
}
// Active state:
&.material--active {
.material-input-bar {
&:before,
&:after {
width: 50%;
}
}
}
}
.material-input__component {
background: $color-white;
.material-input {
background: none;
color: $color-black;
text-indent: $index;
border-bottom: 1px solid $color-grey-light;
}
.material-label {
color: $color-grey;
}
.material-input-bar {
&:before,
&:after {
background: $color-blue;
}
}
// Active state:
&.material--active {
.material-label {
color: $color-blue;
}
}
// Errors:
&.material--has-errors {
&.material--active .material-label {
color: $color-red;
}
.material-input-bar {
&:before,
&:after {
background: transparent;
}
}
}
}
</style>

View File

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

View File

@@ -0,0 +1,118 @@
<template>
<div :id="id" />
</template>
<script>
// deps for editor
import 'codemirror/lib/codemirror.css' // codemirror
import 'tui-editor/dist/tui-editor.css' // editor ui
import 'tui-editor/dist/tui-editor-contents.css' // editor content
import Editor from 'tui-editor'
import defaultOptions from './default-options'
export default {
name: 'MarddownEditor',
props: {
value: {
type: String,
default: ''
},
id: {
type: String,
required: false,
default() {
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
options: {
type: Object,
default() {
return defaultOptions
}
},
mode: {
type: String,
default: 'markdown'
},
height: {
type: String,
required: false,
default: '300px'
},
language: {
type: String,
required: false,
default: 'en_US' // https://github.com/nhnent/tui.editor/tree/master/src/js/langs
}
},
data() {
return {
editor: null
}
},
computed: {
editorOptions() {
const options = Object.assign({}, defaultOptions, this.options)
options.initialEditType = this.mode
options.height = this.height
options.language = this.language
return options
}
},
watch: {
value(newValue, preValue) {
if (newValue !== preValue && newValue !== this.editor.getValue()) {
this.editor.setValue(newValue)
}
},
language(val) {
this.destroyEditor()
this.initEditor()
},
height(newValue) {
this.editor.height(newValue)
},
mode(newValue) {
this.editor.changeMode(newValue)
}
},
mounted() {
this.initEditor()
},
destroyed() {
this.destroyEditor()
},
methods: {
initEditor() {
this.editor = new Editor({
el: document.getElementById(this.id),
...this.editorOptions
})
if (this.value) {
this.editor.setValue(this.value)
}
this.editor.on('change', () => {
this.$emit('input', this.editor.getValue())
})
},
destroyEditor() {
if (!this.editor) return
this.editor.off('change')
this.editor.remove()
},
setValue(value) {
this.editor.setValue(value)
},
getValue() {
return this.editor.getValue()
},
setHtml(value) {
this.editor.setHtml(value)
},
getHtml() {
return this.editor.getHtml()
}
}
}
</script>

View File

@@ -0,0 +1,102 @@
<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 '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: false,
type: Number,
default: 0
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
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>

View File

@@ -0,0 +1,140 @@
<template>
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
<div class="pan-info">
<div class="pan-info-roles-container">
<slot />
</div>
</div>
<img :src="image" class="pan-thumb">
</div>
</template>
<script>
export default {
name: 'PanThumb',
props: {
image: {
type: String,
required: true
},
zIndex: {
type: Number,
default: 1
},
width: {
type: String,
default: '150px'
},
height: {
type: String,
default: '150px'
}
}
}
</script>
<style scoped>
.pan-item {
width: 200px;
height: 200px;
border-radius: 50%;
display: inline-block;
position: relative;
cursor: default;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.pan-info-roles-container {
padding: 20px;
text-align: center;
}
.pan-thumb {
width: 100%;
height: 100%;
background-size: 100%;
border-radius: 50%;
overflow: hidden;
position: absolute;
transform-origin: 95% 40%;
transition: all 0.3s ease-in-out;
}
.pan-thumb:after {
content: '';
width: 8px;
height: 8px;
position: absolute;
border-radius: 50%;
top: 40%;
left: 95%;
margin: -4px 0 0 -4px;
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
}
.pan-info {
position: absolute;
width: inherit;
height: inherit;
border-radius: 50%;
overflow: hidden;
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
}
.pan-info h3 {
color: #fff;
text-transform: uppercase;
position: relative;
letter-spacing: 2px;
font-size: 18px;
margin: 0 60px;
padding: 22px 0 0 0;
height: 85px;
font-family: 'Open Sans', Arial, sans-serif;
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
}
.pan-info p {
color: #fff;
padding: 10px 5px;
font-style: italic;
margin: 0 30px;
font-size: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
}
.pan-info p a {
display: block;
color: #333;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: #fff;
font-style: normal;
font-weight: 700;
text-transform: uppercase;
font-size: 9px;
letter-spacing: 1px;
padding-top: 24px;
margin: 7px auto 0;
font-family: 'Open Sans', Arial, sans-serif;
opacity: 0;
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
transform: translateX(60px) rotate(90deg);
}
.pan-info p a:hover {
background: rgba(255, 255, 255, 0.5);
}
.pan-item:hover .pan-thumb {
transform: rotate(-110deg);
}
.pan-item:hover .pan-info p a {
opacity: 1;
transform: translateX(0px) rotate(0deg);
}
</style>

View File

@@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@@ -0,0 +1,13 @@
import permission from './permission'
const install = function(Vue) {
Vue.directive('permission', permission)
}
if (window.Vue) {
window['permission'] = permission
Vue.use(install); // eslint-disable-line
}
permission.install = install
export default permission

View File

@@ -0,0 +1,21 @@
import store from '@/store'
export default {
inserted(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array) {
if (value.length > 0) {
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else {
throw new Error(`使用方式 v-permission="['admin','editor']"`)
}
}
}

View File

@@ -0,0 +1,544 @@
<template>
<div class="table-responsive">
<table>
<!-- Table header -->
<thead>
<tr v-for="(tr, index) in combineHeads" :key="index">
<th v-for="cell in tr" :key="cell.__index" :rowspan="cell.rowspan" :colspan="cell.colspan">
<div :class="{ 'col-corner-bg': cell.isCorner }" :style="{ 'min-height': _getMinHeightByRowCount(cell.rowspan) }">
{{ cell.isCorner ? (rowPaths.length + ' x ' + colPaths.length) : cell.value }}
</div>
</th>
</tr>
</thead>
<!-- Table body -->
<tbody>
<tr v-for="(tr, index) in combineValues" :key="index">
<!-- Row headers -->
<th v-for="cell in tr.head" v-if="!cell.isRowspan" :key="cell.__index" :rowspan="cell.rowspan" :colspan="cell.colspan">
<div :style="{ 'min-height': _getMinHeightByRowCount(cell.rowspan) }">
{{ cell.value }}
</div>
</th>
<!-- Values -->
<td v-for="cell in tr.data" :key="cell.__index" :rowspan="cell.rowspan" :colspan="cell.colspan">
<div :style="{ 'min-height': _getMinHeightByRowCount(cell.rowspan) }">
{{ cell.value }}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { mergeBaseInfo, convertPathToMap, getHeightByCount, SEPARATOR } from '@/utils/visual-chart'
export default {
name: 'PivotTable',
props: {
data: {
type: Array,
default: () => []
},
rows: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
},
values: {
type: Array,
default: () => []
}
},
data: () => ({
localRows: [],
localColumns: [],
localValues: [],
localData: [],
// 计算列的数据
calcData: [],
// Separator
Separator: SEPARATOR,
// 合并后的表头
combineHeads: [],
// 合并后的单元格
combineValues: []
}),
computed: {
watchAllProps() {
const { rows, columns, values, data } = this
return { rows, columns, values, data }
},
rowPaths() {
const _paths = this._combineRowPaths(
this.localData,
...this.localRows.map(({ key, values }) => { return { key, values } })
)
return _paths
},
colPaths() {
const keys = this.localColumns.map(({ values }) => values)
if (this.localValues.length) {
keys.push(this.localValues.map(({ key }) => key))
}
const _paths = this._combineColPaths(...keys)
return _paths
},
// 列的表头
colHeads() {
// 共有多少行
const _rows = this.localColumns.map(() => [])
// 有几个值
const valuesLen = this.localValues.length
if (valuesLen) {
_rows.push([])
}
// 计算合并单元格
const colSpans = {}
this.colPaths.forEach((path, pathIndex) => {
// 条件值
const pathValues = path.split(this.Separator)
// 存储路径
const currPath = []
_rows.forEach((row, rowIndex) => {
const cellData = {}
const currVal = pathValues[rowIndex] || ''
// 是否为 values 行
const isLastRow = rowIndex === _rows.length - 1
// 存储路径
currPath.push(currVal)
const baseX = rowIndex
const baseY = this.localRows.length + pathIndex
if (!isLastRow) {
// 计算合并行数
let compareVal = valuesLen
for (let i = rowIndex; i < this.localColumns.length - 1; i++) {
compareVal *= this.localColumns[rowIndex + 1].values.length
}
const currColSpan = colSpans[rowIndex] || {}
const currColSpanVal = (currColSpan[currPath.join(this.Separator)] || 0) + 1
currColSpan[currPath.join(this.Separator)] = currColSpanVal
colSpans[rowIndex] = currColSpan
// 合并单元格:起始单元格加入
if (currColSpanVal === 1) {
row.push(
Object.assign(
cellData,
mergeBaseInfo({
__index: `${baseX}-${baseY}`,
x: baseX,
y: baseY,
colspan: compareVal,
path: currPath.filter((item) => !!item),
value: currVal
})
)
)
}
} else {
row.push(
Object.assign(
cellData,
mergeBaseInfo({
__index: `${baseX}-${baseY}`,
x: baseX,
y: baseY,
path: currPath.filter((item) => !!item),
// 最后一行是 values替换显示文本
value: this.localValues.find(({ key }) => key === currVal).label
})
)
)
}
})
})
return _rows
},
// 行的表头
rowHeads() {
// 共有多少列
const _columns = []
// 左上角特殊处理, 有行维、列维时才有
const columnsLen = this.localColumns.length
const rowsLen = this.localRows.length
if (rowsLen && columnsLen) {
_columns.push(mergeBaseInfo({
__index: `0-0`,
colspan: this.localRows.length,
rowspan: this.localColumns.length,
// 左上角标记
isCorner: true
}))
}
this.localRows.forEach(({ label }, index) => {
_columns.push(mergeBaseInfo({
__index: `${this.localColumns.length}-${index}`,
value: label,
x: this.localColumns.length,
y: index
}))
})
return _columns
},
// 行对应的值
rowHeadValues() {
let _values = []
const rowsLen = this.localRows.length
if (rowsLen) {
// 计算合并单元格
const rowSpans = {}
_values = this.rowPaths.map((path, pathIndex) => {
const values = path.split(this.Separator)
const currPath = []
return this.localRows.map((item, rowIndex) => {
const currVal = values[rowIndex] || ''
const baseX = this.localColumns.length + +Boolean(this.localValues.length) + pathIndex
const baseY = rowIndex
currPath.push(currVal)
// 是否为最后列
const isLastCol = rowIndex === rowsLen - 1
if (!isLastCol) {
// 计算合并列数
// 过滤条件
const conditions = {}
for (let i = 0; i < rowIndex + 1; i++) {
conditions[i] = values[i] || ''
}
const filterData = this.rowPaths.filter((data) => {
let status = true
const splitValues = data.split(this.Separator)
for (const key in conditions) {
if (conditions[key] !== splitValues[key]) {
status = false
return
}
}
return status
}) || []
const mergeSpans = filterData.length
const currRowSpan = rowSpans[rowIndex] || {}
const currRowSpanVal = (currRowSpan[currPath.join(this.Separator)] || 0) + 1
currRowSpan[currPath.join(this.Separator)] = currRowSpanVal
rowSpans[rowIndex] = currRowSpan
if (currRowSpanVal === 1) {
return mergeBaseInfo({
__index: `${baseX}-${baseY}`,
value: currVal,
x: baseX,
y: baseY,
rowspan: mergeSpans,
path: currPath.filter((item) => !!item)
})
} else {
return mergeBaseInfo({
__index: `${baseX}-${baseY}`,
value: currVal,
x: baseX,
y: baseY,
path: currPath.filter((item) => !!item),
// 是否合并单元格,遍历时判断不显示
isRowspan: true
})
}
} else {
return mergeBaseInfo({
__index: `${baseX}-${baseY}`,
value: currVal,
x: baseX,
y: baseY,
path: currPath.filter((item) => !!item)
})
}
})
})
}
return _values
},
// 计算所有对应条件的值
dataValues() {
// 列对应的条件
const colConditions = convertPathToMap(
this.colPaths,
this.localColumns.map(({ key }) => key).concat(this.localValues.length ? ['value'] : [])
)
// 行对应的条件
const rowConditions = convertPathToMap(
this.rowPaths,
this.localRows.map(({ key }) => key)
)
// console.log('colConditions', colConditions)
// console.log('rowConditions', rowConditions)
// 针对没传入行或列的处理
!colConditions.length && colConditions.push({})
!rowConditions.length && rowConditions.push({})
// 过滤数据, 遍历行以及遍历行对应的列
return rowConditions.map((rowCondition, rowConditionIndex) => {
const _data = colConditions.map((colCondition, colConditionIndex) => {
// 存储当前单元对应的数据
const cellData = {}
// 当前单元对应的条件
const conditions = Object.assign({}, rowCondition, colCondition)
const _filterConditions = Object.fromEntries(
Object.entries(conditions).filter(
(item) => item[0] !== 'value'
)
)
// 通过当前单元对应的条件,过滤数据
const filterData = this._filterData(_filterConditions, this.localData)
// 对应表格的坐标位置
const baseX = this.localColumns.length + +Boolean(this.localValues.length) + rowConditionIndex
const baseY = this.localRows.length + colConditionIndex
Object.assign(
cellData,
mergeBaseInfo({
conditions,
x: baseX,
y: baseY,
__index: `${baseX}-${baseY}`
})
)
// 针对为指定值 props.values 的空处理(绘制空表格)
const isEmptyValues = this.localColumns.length && this.localRows.length && !this.localValues.length
if (isEmptyValues) {
Object.assign(cellData, { value: '' })
} else {
// 从 props.values 中找出对应的值
const _value = this.values.find(({ key }) => key === conditions.value)
Object.assign(cellData, { value: _value && _value.key ? this._reduceValue(filterData, _value.key) : '' })
}
return cellData
})
return {
__index: _data[0].x,
data: _data
}
})
}
},
watch: {
watchAllProps() {
this.init()
}
},
created() {
this.init()
},
methods: {
init() {
if (this.rows.length || this.columns.length || this.values.length) {
this.handleDataClone()
this.setValuesToColAndRow()
this.handleCalcData()
this.handleCombineHeads()
this.handleCombineValues()
} else {
console.warn('[Warn]: props.rows, props.columns, props.values at least one is not empty.')
}
},
// clone data
handleDataClone() {
this.localRows = JSON.parse(JSON.stringify(this.rows))
this.localColumns = JSON.parse(JSON.stringify(this.columns))
this.localValues = JSON.parse(JSON.stringify(this.values))
this.localData = Object.freeze(this.data)
},
// set the `values` attribute to rows and columns
setValuesToColAndRow() {
const rowKeys = this.localRows.map(({ key }) => key)
const columnKeys = this.localColumns.map(({ key }) => key)
const rowValues = this._findCategory(rowKeys, this.localData)
const columnValues = this._findCategory(columnKeys, this.localData)
this.localRows.forEach((row) => {
const { key, values } = row
this.$set(row, 'values', values || rowValues[key] || [])
})
this.localColumns.forEach((column) => {
const { key, values } = column
this.$set(column, 'values', values || columnValues[key] || [])
})
},
// 合并表头
handleCombineHeads() {
let combineColHeads = JSON.parse(JSON.stringify(this.colHeads))
combineColHeads[0] = combineColHeads[0] || []
combineColHeads[0].unshift(...this.rowHeads.filter((item) => item.isCorner))
combineColHeads[combineColHeads.length - 1].unshift(...this.rowHeads.filter((item) => !item.isCorner))
combineColHeads = combineColHeads.filter((item) => item.length)
this.combineHeads = combineColHeads
},
// 合并值
handleCombineValues() {
// values
const combineValues = []
const valueRowCount = this.dataValues.length || this.rowHeadValues.length
for (let i = 0; i < valueRowCount; i++) {
const _currRowHeadValue = this.rowHeadValues[i] || []
const _currValue = this.dataValues[i] || {}
const _row = [...(_currValue.data || [])]
combineValues.push(
Object.assign({}, { head: [..._currRowHeadValue] }, { data: _row })
)
}
this.combineValues = combineValues
},
// 初始计算值
handleCalcData() {
if (!this.localValues.length) return
const _rowPaths = this._combineRowPaths(
this.localData,
...this.localRows.map(({ key, values }) => { return { key, values } })
)
const _rowKeys = this.localRows.map(({ key }) => key)
const _colPaths = this._combineColPaths(
...this.localColumns.map(({ values }) => values)
)
const _colKeys = this.localColumns.map(({ key }) => key)
// conditions of col-head
const colConditions = convertPathToMap(_colPaths, _colKeys)
// conditions of row-head
const rowConditions = convertPathToMap(_rowPaths, _rowKeys)
// Note: if there are no props.rows or props.columns, push an empty object
!colConditions.length && colConditions.push({})
!rowConditions.length && rowConditions.push({})
// draw data
this.calcData = Object.freeze(
rowConditions
.map((rowCondition, rowConditionIndex) =>
colConditions
.map((colCondition, colConditionIndex) => {
// the condition of current cell
const conditions = Object.assign({}, rowCondition, colCondition)
// filter the data
const filterData = this._filterData(conditions, this.localData)
// empty cell
const isEmptyCell = this.localRows.length && this.localColumns.length && !this.localValues.length
const _values = {}
// 多个值,多条数据
this.values.forEach(({ key }) => {
_values[key] = isEmptyCell ? '' : this._reduceValue(filterData, key)
})
return Object.assign({}, conditions, _values)
})
.flat()
)
.filter((item) => item.length)
.flat()
)
},
_combineRowPaths(data, ...arrays) {
const len = arrays.length
let _result = []
if (len) {
const rowPaths = arrays.reduce((prev, curr) => {
const arr = []
prev.values.forEach(_prevEl => {
const prevKey = prev.key.split(SEPARATOR)
curr.values.forEach(_currEl => {
const currKey = curr.key
const conditions = {}
prevKey.forEach((key, i) => {
conditions[key] = _prevEl.split(SEPARATOR)[i]
})
conditions[currKey] = _currEl
// 判断数据里是否有该项
const filter = data.some((data) => {
let status = true
for (const key in conditions) {
if (conditions[key] !== data[key]) {
status = false
return
}
}
return status
})
if (filter) {
arr.push(_prevEl + SEPARATOR + _currEl)
}
})
})
return { key: prev.key + SEPARATOR + curr.key, values: arr }
}) || {}
_result = rowPaths.values || []
}
return _result
},
_combineColPaths(...arrays) {
return arrays.length ? arrays.reduce((prev, curr) => {
const arr = []
prev.forEach(_prevEl => {
curr.forEach(_currEl => {
arr.push(_prevEl + SEPARATOR + _currEl)
})
})
return arr
}) : arrays
},
_findCategory(keys = [], data = []) {
const _result = {}
data.forEach(item => {
keys.forEach(key => {
// Remove duplicates
_result[key] = _result[key] || []
_result[key].push(item[key])
_result[key] = [...new Set(_result[key])]
})
})
return _result
},
_reduceValue(data, key) {
if (!data.length) return ''
return data.reduce((sum, item) => { return sum + Number(item[key]) }, 0)
},
_filterData(conditions, data) {
return data.filter((data) => {
let status = true
for (const key in conditions) {
if (conditions[key] !== data[key]) {
status = false
return
}
}
return status
})
},
// get min height by rowspan
_getMinHeightByRowCount(count) {
return getHeightByCount(count)
}
}
}
</script>
<style lang="scss" scoped>
table {
border-collapse: collapse;
border-spacing: 0;
border: none;
td, th {
border: 1px solid #ccc;
padding: 0;
vertical-align: middle;
box-sizing: border-box;
> div {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 5px;
text-align: center;
white-space: nowrap;
width: 100%;
height: 100%;
min-height: 36px;
cursor: default;
&.col-corner-bg {
}
}
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="powershell-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/rubyblue.css'
import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
require('codemirror/mode/powershell/powershell.js')
export default {
name: 'PowershellEditor',
props: ['value'],
data() {
return {
powershellEditor: false
}
},
watch: {
value(value) {
const editorValue = this.powershellEditor.getValue()
if (value !== editorValue) {
this.powershellEditor.setValue(this.value)
}
}
},
mounted() {
this.powershellEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
lineNumbers: true,
mode: 'powershell',
gutters: ['CodeMirror-lint-markers'],
theme: 'rubyblue',
lint: true
})
this.powershellEditor.setValue(this.value ? this.value : '')
this.powershellEditor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.powershellEditor.getValue()
}
}
}
</script>
<style scoped>
.powershell-editor{
height: 100%;
position: relative;
}
.powershell-editor >>> .CodeMirror {
height: auto;
min-height: 300px;
}
.powershell-editor >>> .CodeMirror-scroll{
min-height: 300px;
}
.powershell-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="python-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/rubyblue.css'
import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
require('codemirror/mode/python/python.js')
export default {
name: 'PythonEditor',
props: ['value'],
data() {
return {
pythonEditor: false
}
},
watch: {
value(value) {
const editorValue = this.pythonEditor.getValue()
if (value !== editorValue) {
this.pythonEditor.setValue(this.value)
}
}
},
mounted() {
this.pythonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
lineNumbers: true,
mode: 'text/x-python',
gutters: ['CodeMirror-lint-markers'],
theme: 'rubyblue',
lint: true
})
this.pythonEditor.setValue(this.value ? this.value : '')
this.pythonEditor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.pythonEditor.getValue()
}
}
}
</script>
<style scoped>
.python-editor{
height: 100%;
position: relative;
}
.python-editor >>> .CodeMirror {
height: auto;
min-height: 300px;
}
.python-editor >>> .CodeMirror-scroll{
min-height: 300px;
}
.python-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>

View File

@@ -0,0 +1,149 @@
<template>
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script>
import { addClass, removeClass } from '@/utils'
export default {
name: 'RightPanel',
props: {
clickNotClose: {
default: false,
type: Boolean
},
buttonTop: {
default: 250,
type: Number
}
},
computed: {
show: {
get() {
return this.$store.state.settings.showSettings
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val
})
}
},
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()
this.addEventClick()
},
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>

View File

@@ -0,0 +1,60 @@
<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>

View File

@@ -0,0 +1,100 @@
<template>
<div :class="{active:isActive}" class="share-dropdown-menu">
<div class="share-dropdown-menu-wrapper">
<span class="share-dropdown-menu-title" @click.self="clickTitle">{{ title }}</span>
<div v-for="(item,index) of items" :key="index" class="share-dropdown-menu-item">
<a v-if="item.href" :href="item.href" target="_blank">{{ item.title }}</a>
<span v-else>{{ item.title }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
default: function() {
return []
}
},
title: {
type: String,
default: 'vue'
}
},
data() {
return {
isActive: false
}
},
methods: {
clickTitle() {
this.isActive = !this.isActive
}
}
}
</script>
<style lang="scss" >
$n: 9; //和items.length 相同
$t: .1s;
.share-dropdown-menu {
width: 250px;
position: relative;
z-index: 1;
&-title {
width: 100%;
display: block;
cursor: pointer;
background: black;
color: white;
height: 60px;
line-height: 60px;
font-size: 20px;
text-align: center;
z-index: 2;
transform: translate3d(0,0,0);
}
&-wrapper {
position: relative;
}
&-item {
text-align: center;
position: absolute;
width: 100%;
background: #e0e0e0;
line-height: 60px;
height: 60px;
cursor: pointer;
font-size: 20px;
opacity: 1;
transition: transform 0.28s ease;
&:hover {
background: black;
color: white;
}
@for $i from 1 through $n {
&:nth-of-type(#{$i}) {
z-index: -1;
transition-delay: $i*$t;
transform: translate3d(0, -60px, 0);
}
}
}
&.active {
.share-dropdown-menu-wrapper {
z-index: 1;
}
.share-dropdown-menu-item {
@for $i from 1 through $n {
&:nth-of-type(#{$i}) {
transition-delay: ($n - $i)*$t;
transform: translate3d(0, ($i - 1)*60px, 0);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="shell-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/rubyblue.css'
import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
require('codemirror/mode/shell/shell.js')
export default {
name: 'ShellEditor',
props: ['value'],
data() {
return {
shellEditor: false
}
},
watch: {
value(value) {
const editorValue = this.shellEditor.getValue()
if (value !== editorValue) {
this.shellEditor.setValue(this.value)
}
}
},
mounted() {
this.shellEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
lineNumbers: true,
mode: 'text/x-sh',
gutters: ['CodeMirror-lint-markers'],
theme: 'rubyblue',
lint: true
})
this.shellEditor.setValue(this.value ? this.value : '')
this.shellEditor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.shellEditor.getValue()
}
}
}
</script>
<style scoped>
.shell-editor{
height: 100%;
position: relative;
}
.shell-editor >>> .CodeMirror {
height: auto;
min-height: 300px;
}
.shell-editor >>> .CodeMirror-scroll{
min-height: 300px;
}
.shell-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<el-dropdown trigger="click" @command="handleSetSize">
<div>
<svg-icon class-name="size-icon" icon-class="size" />
</div>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
{{
item.label }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
data() {
return {
sizeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'Medium', value: 'medium' },
{ label: 'Small', value: 'small' },
{ label: 'Mini', value: 'mini' }
]
}
},
computed: {
size() {
return this.$store.getters.size
}
},
methods: {
handleSetSize(size) {
this.$ELEMENT.size = size
this.$store.dispatch('app/setSize', size)
this.refreshView()
this.$message({
message: '布局设置成功',
type: 'success'
})
},
refreshView() {
// In order to make the cached page re-rendered
this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
const { fullPath } = this.$route
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
}
}
}
</script>

View File

@@ -0,0 +1,110 @@
<template>
<div>
<textarea
ref="mycode"
v-model="value"
class="codesql"
/>
</div>
</template>
<script>
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/blackboard.css'
import 'codemirror/addon/hint/show-hint.css'
const CodeMirror = require('codemirror/lib/codemirror')
require('codemirror/addon/edit/matchbrackets')
require('codemirror/addon/selection/active-line')
require('codemirror/mode/sql/sql')
require('codemirror/addon/hint/show-hint')
require('codemirror/addon/hint/sql-hint')
export default {
name: 'SqlEditor',
props: {
value: {
type: String,
default: ''
},
sqlStyle: {
type: String,
default: 'default'
},
readOnly: {
type: [Boolean, String]
}
},
data() {
return {
editor: null
}
},
computed: {
newVal() {
if (this.editor) {
return this.editor.getValue()
}
}
},
watch: {
newVal(newV, oldV) {
if (this.editor) {
this.$emit('changeTextarea', this.editor.getValue())
}
}
},
mounted() {
const mime = 'text/x-mariadb'
const theme = 'blackboard'// 设置主题,不设置的会使用默认主题
this.editor = CodeMirror.fromTextArea(this.$refs.mycode, {
value: this.value,
mode: mime, // 选择对应代码编辑器的语言,我这边选的是数据库,根据个人情况自行设置即可
indentWithTabs: true,
smartIndent: true,
lineNumbers: true,
matchBrackets: true,
cursorHeight: 1,
lineWrapping: true,
readOnly: this.readOnly,
theme: theme,
autofocus: true,
extraKeys: { 'Ctrl': 'autocomplete' }, // 自定义快捷键
hintOptions: { // 自定义提示选项
// 当匹配只有一项的时候是否自动补全
completeSingle: false
// tables: {
// users: ['name', 'score', 'birthDate'],
// countries: ['name', 'population', 'size']
// }
}
})
// 代码自动提示功能记住使用cursorActivity事件不要使用change事件这是一个坑那样页面直接会卡死
this.editor.on('inputRead', () => {
this.editor.showHint()
})
},
methods: {
setVal() {
if (this.editor) {
if (this.value === '') {
this.editor.setValue('')
} else {
this.editor.setValue(this.value)
}
}
}
}
}
</script>
<style lang="scss" scoped>
.CodeMirror {
border: 1px solid black;
font-size: 13px;
}
// 这句为了解决匹配框显示有问题而加
.CodeMirror-hints{
z-index: 9999 !important;
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<div :style="{height:height+'px',zIndex:zIndex}">
<div
:class="className"
:style="{top:(isSticky ? stickyTop +'px' : ''),zIndex:zIndex,position:position,width:width,height:height+'px'}"
>
<slot>
<div>sticky</div>
</slot>
</div>
</div>
</template>
<script>
export default {
name: 'Sticky',
props: {
stickyTop: {
type: Number,
default: 0
},
zIndex: {
type: Number,
default: 1
},
className: {
type: String,
default: ''
}
},
data() {
return {
active: false,
position: '',
width: undefined,
height: undefined,
isSticky: false
}
},
mounted() {
this.height = this.$el.getBoundingClientRect().height
window.addEventListener('scroll', this.handleScroll)
window.addEventListener('resize', this.handleResize)
},
activated() {
this.handleScroll()
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
},
methods: {
sticky() {
if (this.active) {
return
}
this.position = 'fixed'
this.active = true
this.width = this.width + 'px'
this.isSticky = true
},
handleReset() {
if (!this.active) {
return
}
this.reset()
},
reset() {
this.position = ''
this.width = 'auto'
this.active = false
this.isSticky = false
},
handleScroll() {
const width = this.$el.getBoundingClientRect().width
this.width = width || 'auto'
const offsetTop = this.$el.getBoundingClientRect().top
if (offsetTop < this.stickyTop) {
this.sticky()
return
}
this.handleReset()
},
handleResize() {
if (this.isSticky) {
this.width = this.$el.getBoundingClientRect().width + 'px'
}
}
}
}
</script>

View File

@@ -0,0 +1,62 @@
<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 '@/utils/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>

View File

@@ -0,0 +1,113 @@
<template>
<a :class="className" class="link--mallki" href="#">
{{ text }}
<span :data-letters="text" />
<span :data-letters="text" />
</a>
</template>
<script>
export default {
props: {
className: {
type: String,
default: ''
},
text: {
type: String,
default: 'vue-element-admin'
}
}
}
</script>
<style>
/* Mallki */
.link--mallki {
font-weight: 800;
color: #4dd9d5;
font-family: 'Dosis', sans-serif;
-webkit-transition: color 0.5s 0.25s;
transition: color 0.5s 0.25s;
overflow: hidden;
position: relative;
display: inline-block;
line-height: 1;
outline: none;
text-decoration: none;
}
.link--mallki:hover {
-webkit-transition: none;
transition: none;
color: transparent;
}
.link--mallki::before {
content: '';
width: 100%;
height: 6px;
margin: -3px 0 0 0;
background: #3888fa;
position: absolute;
left: 0;
top: 50%;
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
-webkit-transition: -webkit-transform 0.4s;
transition: transform 0.4s;
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
}
.link--mallki:hover::before {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.link--mallki span {
position: absolute;
height: 50%;
width: 100%;
left: 0;
top: 0;
overflow: hidden;
}
.link--mallki span::before {
content: attr(data-letters);
color: red;
position: absolute;
left: 0;
width: 100%;
color: #3888fa;
-webkit-transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
.link--mallki span:nth-child(2) {
top: 50%;
}
.link--mallki span:first-child::before {
top: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
.link--mallki span:nth-child(2)::before {
bottom: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
.link--mallki:hover span::before {
-webkit-transition-delay: 0.3s;
transition-delay: 0.3s;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
}
</style>

View File

@@ -0,0 +1,165 @@
<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
import Cookies from 'js-cookie'
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) {
Cookies.set('theme', val, { expires: 365 })
const oldVal = this.chalk ? this.theme : Cookies.get('theme') ? Cookies.get('theme') : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
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)
}
},
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>

View File

@@ -0,0 +1,111 @@
<template>
<div class="upload-container">
<el-button :style="{background:color,borderColor:color}" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
upload
</el-button>
<el-dialog :visible.sync="dialogVisible">
<el-upload
:multiple="true"
:file-list="fileList"
:show-file-list="true"
:on-remove="handleRemove"
:on-success="handleSuccess"
:before-upload="beforeUpload"
class="editor-slide-upload"
action="https://httpbin.org/post"
list-type="picture-card"
>
<el-button size="small" type="primary">
Click upload
</el-button>
</el-upload>
<el-button @click="dialogVisible = false">
Cancel
</el-button>
<el-button type="primary" @click="handleSubmit">
Confirm
</el-button>
</el-dialog>
</div>
</template>
<script>
// import { getToken } from 'api/qiniu'
export default {
name: 'EditorSlideUpload',
props: {
color: {
type: String,
default: '#1890ff'
}
},
data() {
return {
dialogVisible: false,
listObj: {},
fileList: []
}
},
methods: {
checkAllSuccess() {
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
},
handleSubmit() {
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
if (!this.checkAllSuccess()) {
this.$message('Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!')
return
}
this.$emit('successCBK', arr)
this.listObj = {}
this.fileList = []
this.dialogVisible = false
},
handleSuccess(response, file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
this.listObj[objKeyArr[i]].url = response.files.file
this.listObj[objKeyArr[i]].hasSuccess = true
return
}
}
},
handleRemove(file) {
const uid = file.uid
const objKeyArr = Object.keys(this.listObj)
for (let i = 0, len = objKeyArr.length; i < len; i++) {
if (this.listObj[objKeyArr[i]].uid === uid) {
delete this.listObj[objKeyArr[i]]
return
}
}
},
beforeUpload(file) {
const _self = this
const _URL = window.URL || window.webkitURL
const fileName = file.uid
this.listObj[fileName] = {}
return new Promise((resolve, reject) => {
const img = new Image()
img.src = _URL.createObjectURL(file)
img.onload = function() {
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
}
resolve(true)
})
}
}
}
</script>
<style lang="scss" scoped>
.editor-slide-upload {
margin-bottom: 20px;
/deep/ .el-upload--picture-card {
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,59 @@
let callbacks = []
function loadedTinymce() {
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
// check is successfully downloaded script
return window.tinymce
}
const dynamicLoadScript = (src, callback) => {
const existingScript = document.getElementById(src)
const cb = callback || function() {}
if (!existingScript) {
const script = document.createElement('script')
script.src = src // src url for the third-party library being loaded.
script.id = src
document.body.appendChild(script)
callbacks.push(cb)
const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
onEnd(script)
}
if (existingScript && cb) {
if (loadedTinymce()) {
cb(null, existingScript)
} else {
callbacks.push(cb)
}
}
function stdOnEnd(script) {
script.onload = function() {
// this.onload = null here is necessary
// because even IE9 works not like others
this.onerror = this.onload = null
for (const cb of callbacks) {
cb(null, script)
}
callbacks = null
}
script.onerror = function() {
this.onerror = this.onload = null
cb(new Error('Failed to load ' + src), script)
}
}
function ieOnEnd(script) {
script.onreadystatechange = function() {
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
this.onreadystatechange = null
for (const cb of callbacks) {
cb(null, script) // there is no way to catch loading errors in IE8
}
callbacks = null
}
}
}
export default dynamicLoadScript

View File

@@ -0,0 +1,237 @@
<template>
<div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
<textarea :id="tinymceId" class="tinymce-textarea" />
<div class="editor-custom-btn-container">
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
</div>
</div>
</template>
<script>
/**
* docs:
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
*/
import editorImage from './components/EditorImage'
import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
export default {
name: 'Tinymce',
components: { editorImage },
props: {
id: {
type: String,
default: function() {
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
value: {
type: String,
default: ''
},
toolbar: {
type: Array,
required: false,
default() {
return []
}
},
menubar: {
type: String,
default: 'file edit insert view format table'
},
height: {
type: [Number, String],
required: false,
default: 360
},
width: {
type: [Number, String],
required: false,
default: 'auto'
}
},
data() {
return {
hasChange: false,
hasInit: false,
tinymceId: this.id,
fullscreen: false,
languageTypeList: {
'en': 'en',
'zh': 'zh_CN',
'es': 'es_MX',
'ja': 'ja'
}
}
},
computed: {
containerWidth() {
const width = this.width
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
return `${width}px`
}
return width
}
},
watch: {
value(val) {
if (!this.hasChange && this.hasInit) {
this.$nextTick(() =>
window.tinymce.get(this.tinymceId).setContent(val || ''))
}
}
},
mounted() {
this.init()
},
activated() {
if (window.tinymce) {
this.initTinymce()
}
},
deactivated() {
this.destroyTinymce()
},
destroyed() {
this.destroyTinymce()
},
methods: {
init() {
// dynamic load tinymce from cdn
load(tinymceCDN, (err) => {
if (err) {
this.$message.error(err.message)
return
}
this.initTinymce()
})
},
initTinymce() {
const _this = this
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: this.languageTypeList['en'],
height: this.height,
body_class: 'panel-body ',
object_resizing: false,
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
menubar: this.menubar,
plugins: plugins,
end_container_on_empty_block: true,
powerpaste_word_import: 'clean',
code_dialog_height: 450,
code_dialog_width: 1000,
advlist_bullet_styles: 'square',
advlist_number_styles: 'default',
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
default_link_target: '_blank',
link_title: false,
nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
init_instance_callback: editor => {
if (_this.value) {
editor.setContent(_this.value)
}
_this.hasInit = true
editor.on('NodeChange Change KeyUp SetContent', () => {
this.hasChange = true
this.$emit('input', editor.getContent())
})
},
setup(editor) {
editor.on('FullscreenStateChanged', (e) => {
_this.fullscreen = e.state
})
}
// 整合七牛上传
// images_dataimg_filter(img) {
// setTimeout(() => {
// const $image = $(img);
// $image.removeAttr('width');
// $image.removeAttr('height');
// if ($image[0].height && $image[0].width) {
// $image.attr('data-wscntype', 'image');
// $image.attr('data-wscnh', $image[0].height);
// $image.attr('data-wscnw', $image[0].width);
// $image.addClass('wscnph');
// }
// }, 0);
// return img
// },
// images_upload_handler(blobInfo, success, failure, progress) {
// progress(0);
// const token = _this.$store.getters.token;
// getToken(token).then(response => {
// const url = response.data.qiniu_url;
// const formData = new FormData();
// formData.append('token', response.data.qiniu_token);
// formData.append('key', response.data.qiniu_key);
// formData.append('file', blobInfo.blob(), url);
// upload(formData).then(() => {
// success(url);
// progress(100);
// })
// }).catch(err => {
// failure('出现未知问题,刷新页面,或者联系程序员')
// console.log(err);
// });
// },
})
},
destroyTinymce() {
const tinymce = window.tinymce.get(this.tinymceId)
if (this.fullscreen) {
tinymce.execCommand('mceFullScreen')
}
if (tinymce) {
tinymce.destroy()
}
},
setContent(value) {
window.tinymce.get(this.tinymceId).setContent(value)
},
getContent() {
window.tinymce.get(this.tinymceId).getContent()
},
imageSuccessCBK(arr) {
const _this = this
arr.forEach(v => {
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
})
}
}
}
</script>
<style scoped>
.tinymce-container {
position: relative;
line-height: normal;
}
.tinymce-container>>>.mce-fullscreen {
z-index: 10000;
}
.tinymce-textarea {
visibility: hidden;
z-index: -1;
}
.editor-custom-btn-container {
position: absolute;
right: 4px;
top: 4px;
/*z-index: 2005;*/
}
.fullscreen .editor-custom-btn-container {
z-index: 10000;
position: fixed;
}
.editor-upload-btn {
display: inline-block;
}
</style>

View File

@@ -0,0 +1,7 @@
// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
export default plugins

View File

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

View File

@@ -0,0 +1,138 @@
<template>
<div>
<input ref="excel-upload-input" class="excel-upload-input" type="file" accept=".xlsx, .xls" @change="handleClick">
<div class="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
拖拽excel文件到此处 或者
<el-button :loading="loading" style="margin-left:16px;" size="mini" type="primary" @click="handleUpload">
浏览
</el-button>
</div>
</div>
</template>
<script>
import XLSX from 'xlsx'
export default {
props: {
beforeUpload: Function, // eslint-disable-line
onSuccess: Function// eslint-disable-line
},
data() {
return {
loading: false,
excelData: {
header: null,
results: null
}
}
},
methods: {
generateData({ header, results }) {
this.excelData.header = header
this.excelData.results = results
this.onSuccess && this.onSuccess(this.excelData)
},
handleDrop(e) {
e.stopPropagation()
e.preventDefault()
if (this.loading) return
const files = e.dataTransfer.files
if (files.length !== 1) {
this.$message.error('只支持单个文件上传!')
return
}
const rawFile = files[0]
if (!this.isExcel(rawFile)) {
this.$message.error('只支持.xlsx, .xls, .csv 格式文件')
return false
}
this.upload(rawFile)
e.stopPropagation()
e.preventDefault()
},
handleDragover(e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleUpload() {
this.$refs['excel-upload-input'].click()
},
handleClick(e) {
const files = e.target.files
const rawFile = files[0] // only use files[0]
if (!rawFile) return
this.upload(rawFile)
},
upload(rawFile) {
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
if (!this.beforeUpload) {
this.readerData(rawFile)
return
}
const before = this.beforeUpload(rawFile)
if (before) {
this.readerData(rawFile)
}
},
readerData(rawFile) {
this.loading = true
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = e => {
const data = e.target.result
const workbook = XLSX.read(data, { type: 'array' })
const firstSheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[firstSheetName]
const header = this.getHeaderRow(worksheet)
const results = XLSX.utils.sheet_to_json(worksheet)
this.generateData({ header, results })
this.loading = false
resolve()
}
reader.readAsArrayBuffer(rawFile)
})
},
getHeaderRow(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r
/* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
/* find the cell in the first row */
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
},
isExcel(file) {
return /\.(xlsx|xls|csv)$/.test(file.name)
}
}
}
</script>
<style scoped>
.excel-upload-input{
display: none;
z-index: -9999;
}
.drop{
border: 2px dashed #bbb;
width: 600px;
height: 160px;
line-height: 160px;
margin: 0 auto;
font-size: 24px;
border-radius: 5px;
text-align: center;
color: #bbb;
position: relative;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
// 替换主题这里需修改名称
import 'codemirror/theme/idea.css'
import 'codemirror/mode/yaml/yaml'
export default {
props: {
value: {
type: String,
required: true
},
height: {
type: String,
required: true
}
},
data() {
return {
editor: false
}
},
watch: {
value(value) {
const editorValue = this.editor.getValue()
if (value !== editorValue) {
this.editor.setValue(this.value)
}
},
height(value) {
this.editor.setSize('auto', this.height)
}
},
mounted() {
this.editor = CodeMirror.fromTextArea(this.$refs.textarea, {
mode: 'text/x-yaml',
lineNumbers: true,
lint: true,
lineWrapping: true,
tabSize: 2,
cursorHeight: 0.9,
// 替换主题这里需修改名称
theme: 'idea'
})
this.editor.setSize('auto', this.height)
this.editor.setValue(this.value)
this.editor.on('change', cm => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.editor.getValue()
}
}
}
</script>
<style scoped>
.json-editor{
height: 100%;
margin-bottom: 10px;
}
.json-editor >>> .CodeMirror {
font-size: 13px;
overflow-y:auto;
font-weight:normal
}
.json-editor >>> .CodeMirror-scroll{
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #F08047;
}
</style>