Commit be0092176455ad2f3963ca24b0ec1b876304bb96

Authored by monkeyhouyi
1 parent 090611a7

处理,通知公告,任务中心

Showing 44 changed files with 3934 additions and 117 deletions
src/components/Charts/Keyboard.vue 0 → 100644
  1 +<template>
  2 + <div :id="id" :class="className" :style="{height:height,width:width}" />
  3 +</template>
  4 +
  5 +<script>
  6 +import echarts from 'echarts'
  7 +import resize from './mixins/resize'
  8 +
  9 +export default {
  10 + mixins: [resize],
  11 + props: {
  12 + className: {
  13 + type: String,
  14 + default: 'chart'
  15 + },
  16 + id: {
  17 + type: String,
  18 + default: 'chart'
  19 + },
  20 + width: {
  21 + type: String,
  22 + default: '200px'
  23 + },
  24 + height: {
  25 + type: String,
  26 + default: '200px'
  27 + }
  28 + },
  29 + data() {
  30 + return {
  31 + chart: null
  32 + }
  33 + },
  34 + mounted() {
  35 + this.initChart()
  36 + },
  37 + beforeDestroy() {
  38 + if (!this.chart) {
  39 + return
  40 + }
  41 + this.chart.dispose()
  42 + this.chart = null
  43 + },
  44 + methods: {
  45 + initChart() {
  46 + this.chart = echarts.init(document.getElementById(this.id))
  47 +
  48 + const xAxisData = []
  49 + const data = []
  50 + const data2 = []
  51 + for (let i = 0; i < 50; i++) {
  52 + xAxisData.push(i)
  53 + data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
  54 + data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
  55 + }
  56 + this.chart.setOption({
  57 + backgroundColor: '#08263a',
  58 + grid: {
  59 + left: '5%',
  60 + right: '5%'
  61 + },
  62 + xAxis: [{
  63 + show: false,
  64 + data: xAxisData
  65 + }, {
  66 + show: false,
  67 + data: xAxisData
  68 + }],
  69 + visualMap: {
  70 + show: false,
  71 + min: 0,
  72 + max: 50,
  73 + dimension: 0,
  74 + inRange: {
  75 + color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
  76 + }
  77 + },
  78 + yAxis: {
  79 + axisLine: {
  80 + show: false
  81 + },
  82 + axisLabel: {
  83 + textStyle: {
  84 + color: '#4a657a'
  85 + }
  86 + },
  87 + splitLine: {
  88 + show: true,
  89 + lineStyle: {
  90 + color: '#08263f'
  91 + }
  92 + },
  93 + axisTick: {
  94 + show: false
  95 + }
  96 + },
  97 + series: [{
  98 + name: 'back',
  99 + type: 'bar',
  100 + data: data2,
  101 + z: 1,
  102 + itemStyle: {
  103 + normal: {
  104 + opacity: 0.4,
  105 + barBorderRadius: 5,
  106 + shadowBlur: 3,
  107 + shadowColor: '#111'
  108 + }
  109 + }
  110 + }, {
  111 + name: 'Simulate Shadow',
  112 + type: 'line',
  113 + data,
  114 + z: 2,
  115 + showSymbol: false,
  116 + animationDelay: 0,
  117 + animationEasing: 'linear',
  118 + animationDuration: 1200,
  119 + lineStyle: {
  120 + normal: {
  121 + color: 'transparent'
  122 + }
  123 + },
  124 + areaStyle: {
  125 + normal: {
  126 + color: '#08263a',
  127 + shadowBlur: 50,
  128 + shadowColor: '#000'
  129 + }
  130 + }
  131 + }, {
  132 + name: 'front',
  133 + type: 'bar',
  134 + data,
  135 + xAxisIndex: 1,
  136 + z: 3,
  137 + itemStyle: {
  138 + normal: {
  139 + barBorderRadius: 5
  140 + }
  141 + }
  142 + }],
  143 + animationEasing: 'elasticOut',
  144 + animationEasingUpdate: 'elasticOut',
  145 + animationDelay(idx) {
  146 + return idx * 20
  147 + },
  148 + animationDelayUpdate(idx) {
  149 + return idx * 20
  150 + }
  151 + })
  152 + }
  153 + }
  154 +}
  155 +</script>
... ...
src/components/Charts/LineMarker.vue 0 → 100644
  1 +<template>
  2 + <div :id="id" :class="className" :style="{height:height,width:width}" />
  3 +</template>
  4 +
  5 +<script>
  6 +import echarts from 'echarts'
  7 +import resize from './mixins/resize'
  8 +
  9 +export default {
  10 + mixins: [resize],
  11 + props: {
  12 + className: {
  13 + type: String,
  14 + default: 'chart'
  15 + },
  16 + id: {
  17 + type: String,
  18 + default: 'chart'
  19 + },
  20 + width: {
  21 + type: String,
  22 + default: '200px'
  23 + },
  24 + height: {
  25 + type: String,
  26 + default: '200px'
  27 + }
  28 + },
  29 + data() {
  30 + return {
  31 + chart: null
  32 + }
  33 + },
  34 + mounted() {
  35 + this.initChart()
  36 + },
  37 + beforeDestroy() {
  38 + if (!this.chart) {
  39 + return
  40 + }
  41 + this.chart.dispose()
  42 + this.chart = null
  43 + },
  44 + methods: {
  45 + initChart() {
  46 + this.chart = echarts.init(document.getElementById(this.id))
  47 +
  48 + this.chart.setOption({
  49 + backgroundColor: '#394056',
  50 + title: {
  51 + top: 20,
  52 + text: 'Requests',
  53 + textStyle: {
  54 + fontWeight: 'normal',
  55 + fontSize: 16,
  56 + color: '#F1F1F3'
  57 + },
  58 + left: '1%'
  59 + },
  60 + tooltip: {
  61 + trigger: 'axis',
  62 + axisPointer: {
  63 + lineStyle: {
  64 + color: '#57617B'
  65 + }
  66 + }
  67 + },
  68 + legend: {
  69 + top: 20,
  70 + icon: 'rect',
  71 + itemWidth: 14,
  72 + itemHeight: 5,
  73 + itemGap: 13,
  74 + data: ['CMCC', 'CTCC', 'CUCC'],
  75 + right: '4%',
  76 + textStyle: {
  77 + fontSize: 12,
  78 + color: '#F1F1F3'
  79 + }
  80 + },
  81 + grid: {
  82 + top: 100,
  83 + left: '2%',
  84 + right: '2%',
  85 + bottom: '2%',
  86 + containLabel: true
  87 + },
  88 + xAxis: [{
  89 + type: 'category',
  90 + boundaryGap: false,
  91 + axisLine: {
  92 + lineStyle: {
  93 + color: '#57617B'
  94 + }
  95 + },
  96 + 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']
  97 + }],
  98 + yAxis: [{
  99 + type: 'value',
  100 + name: '(%)',
  101 + axisTick: {
  102 + show: false
  103 + },
  104 + axisLine: {
  105 + lineStyle: {
  106 + color: '#57617B'
  107 + }
  108 + },
  109 + axisLabel: {
  110 + margin: 10,
  111 + textStyle: {
  112 + fontSize: 14
  113 + }
  114 + },
  115 + splitLine: {
  116 + lineStyle: {
  117 + color: '#57617B'
  118 + }
  119 + }
  120 + }],
  121 + series: [{
  122 + name: 'CMCC',
  123 + type: 'line',
  124 + smooth: true,
  125 + symbol: 'circle',
  126 + symbolSize: 5,
  127 + showSymbol: false,
  128 + lineStyle: {
  129 + normal: {
  130 + width: 1
  131 + }
  132 + },
  133 + areaStyle: {
  134 + normal: {
  135 + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  136 + offset: 0,
  137 + color: 'rgba(137, 189, 27, 0.3)'
  138 + }, {
  139 + offset: 0.8,
  140 + color: 'rgba(137, 189, 27, 0)'
  141 + }], false),
  142 + shadowColor: 'rgba(0, 0, 0, 0.1)',
  143 + shadowBlur: 10
  144 + }
  145 + },
  146 + itemStyle: {
  147 + normal: {
  148 + color: 'rgb(137,189,27)',
  149 + borderColor: 'rgba(137,189,2,0.27)',
  150 + borderWidth: 12
  151 +
  152 + }
  153 + },
  154 + data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
  155 + }, {
  156 + name: 'CTCC',
  157 + type: 'line',
  158 + smooth: true,
  159 + symbol: 'circle',
  160 + symbolSize: 5,
  161 + showSymbol: false,
  162 + lineStyle: {
  163 + normal: {
  164 + width: 1
  165 + }
  166 + },
  167 + areaStyle: {
  168 + normal: {
  169 + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  170 + offset: 0,
  171 + color: 'rgba(0, 136, 212, 0.3)'
  172 + }, {
  173 + offset: 0.8,
  174 + color: 'rgba(0, 136, 212, 0)'
  175 + }], false),
  176 + shadowColor: 'rgba(0, 0, 0, 0.1)',
  177 + shadowBlur: 10
  178 + }
  179 + },
  180 + itemStyle: {
  181 + normal: {
  182 + color: 'rgb(0,136,212)',
  183 + borderColor: 'rgba(0,136,212,0.2)',
  184 + borderWidth: 12
  185 +
  186 + }
  187 + },
  188 + data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
  189 + }, {
  190 + name: 'CUCC',
  191 + type: 'line',
  192 + smooth: true,
  193 + symbol: 'circle',
  194 + symbolSize: 5,
  195 + showSymbol: false,
  196 + lineStyle: {
  197 + normal: {
  198 + width: 1
  199 + }
  200 + },
  201 + areaStyle: {
  202 + normal: {
  203 + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
  204 + offset: 0,
  205 + color: 'rgba(219, 50, 51, 0.3)'
  206 + }, {
  207 + offset: 0.8,
  208 + color: 'rgba(219, 50, 51, 0)'
  209 + }], false),
  210 + shadowColor: 'rgba(0, 0, 0, 0.1)',
  211 + shadowBlur: 10
  212 + }
  213 + },
  214 + itemStyle: {
  215 + normal: {
  216 + color: 'rgb(219,50,51)',
  217 + borderColor: 'rgba(219,50,51,0.2)',
  218 + borderWidth: 12
  219 + }
  220 + },
  221 + data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
  222 + }]
  223 + })
  224 + }
  225 + }
  226 +}
  227 +</script>
... ...
src/components/Charts/MixChart.vue 0 → 100644
  1 +<template>
  2 + <div :id="id" :class="className" :style="{height:height,width:width}" />
  3 +</template>
  4 +
  5 +<script>
  6 +import echarts from 'echarts'
  7 +import resize from './mixins/resize'
  8 +
  9 +export default {
  10 + mixins: [resize],
  11 + props: {
  12 + className: {
  13 + type: String,
  14 + default: 'chart'
  15 + },
  16 + id: {
  17 + type: String,
  18 + default: 'chart'
  19 + },
  20 + width: {
  21 + type: String,
  22 + default: '200px'
  23 + },
  24 + height: {
  25 + type: String,
  26 + default: '200px'
  27 + }
  28 + },
  29 + data() {
  30 + return {
  31 + chart: null
  32 + }
  33 + },
  34 + mounted() {
  35 + this.initChart()
  36 + },
  37 + beforeDestroy() {
  38 + if (!this.chart) {
  39 + return
  40 + }
  41 + this.chart.dispose()
  42 + this.chart = null
  43 + },
  44 + methods: {
  45 + initChart() {
  46 + this.chart = echarts.init(document.getElementById(this.id))
  47 + const xData = (function() {
  48 + const data = []
  49 + for (let i = 1; i < 13; i++) {
  50 + data.push(i + 'month')
  51 + }
  52 + return data
  53 + }())
  54 + this.chart.setOption({
  55 + backgroundColor: '#344b58',
  56 + title: {
  57 + text: 'statistics',
  58 + x: '20',
  59 + top: '20',
  60 + textStyle: {
  61 + color: '#fff',
  62 + fontSize: '22'
  63 + },
  64 + subtextStyle: {
  65 + color: '#90979c',
  66 + fontSize: '16'
  67 + }
  68 + },
  69 + tooltip: {
  70 + trigger: 'axis',
  71 + axisPointer: {
  72 + textStyle: {
  73 + color: '#fff'
  74 + }
  75 + }
  76 + },
  77 + grid: {
  78 + left: '5%',
  79 + right: '5%',
  80 + borderWidth: 0,
  81 + top: 150,
  82 + bottom: 95,
  83 + textStyle: {
  84 + color: '#fff'
  85 + }
  86 + },
  87 + legend: {
  88 + x: '5%',
  89 + top: '10%',
  90 + textStyle: {
  91 + color: '#90979c'
  92 + },
  93 + data: ['female', 'male', 'average']
  94 + },
  95 + calculable: true,
  96 + xAxis: [{
  97 + type: 'category',
  98 + axisLine: {
  99 + lineStyle: {
  100 + color: '#90979c'
  101 + }
  102 + },
  103 + splitLine: {
  104 + show: false
  105 + },
  106 + axisTick: {
  107 + show: false
  108 + },
  109 + splitArea: {
  110 + show: false
  111 + },
  112 + axisLabel: {
  113 + interval: 0
  114 +
  115 + },
  116 + data: xData
  117 + }],
  118 + yAxis: [{
  119 + type: 'value',
  120 + splitLine: {
  121 + show: false
  122 + },
  123 + axisLine: {
  124 + lineStyle: {
  125 + color: '#90979c'
  126 + }
  127 + },
  128 + axisTick: {
  129 + show: false
  130 + },
  131 + axisLabel: {
  132 + interval: 0
  133 + },
  134 + splitArea: {
  135 + show: false
  136 + }
  137 + }],
  138 + dataZoom: [{
  139 + show: true,
  140 + height: 30,
  141 + xAxisIndex: [
  142 + 0
  143 + ],
  144 + bottom: 30,
  145 + start: 10,
  146 + end: 80,
  147 + 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',
  148 + handleSize: '110%',
  149 + handleStyle: {
  150 + color: '#d3dee5'
  151 +
  152 + },
  153 + textStyle: {
  154 + color: '#fff' },
  155 + borderColor: '#90979c'
  156 +
  157 + }, {
  158 + type: 'inside',
  159 + show: true,
  160 + height: 15,
  161 + start: 1,
  162 + end: 35
  163 + }],
  164 + series: [{
  165 + name: 'female',
  166 + type: 'bar',
  167 + stack: 'total',
  168 + barMaxWidth: 35,
  169 + barGap: '10%',
  170 + itemStyle: {
  171 + normal: {
  172 + color: 'rgba(255,144,128,1)',
  173 + label: {
  174 + show: true,
  175 + textStyle: {
  176 + color: '#fff'
  177 + },
  178 + position: 'insideTop',
  179 + formatter(p) {
  180 + return p.value > 0 ? p.value : ''
  181 + }
  182 + }
  183 + }
  184 + },
  185 + data: [
  186 + 709,
  187 + 1917,
  188 + 2455,
  189 + 2610,
  190 + 1719,
  191 + 1433,
  192 + 1544,
  193 + 3285,
  194 + 5208,
  195 + 3372,
  196 + 2484,
  197 + 4078
  198 + ]
  199 + },
  200 +
  201 + {
  202 + name: 'male',
  203 + type: 'bar',
  204 + stack: 'total',
  205 + itemStyle: {
  206 + normal: {
  207 + color: 'rgba(0,191,183,1)',
  208 + barBorderRadius: 0,
  209 + label: {
  210 + show: true,
  211 + position: 'top',
  212 + formatter(p) {
  213 + return p.value > 0 ? p.value : ''
  214 + }
  215 + }
  216 + }
  217 + },
  218 + data: [
  219 + 327,
  220 + 1776,
  221 + 507,
  222 + 1200,
  223 + 800,
  224 + 482,
  225 + 204,
  226 + 1390,
  227 + 1001,
  228 + 951,
  229 + 381,
  230 + 220
  231 + ]
  232 + }, {
  233 + name: 'average',
  234 + type: 'line',
  235 + stack: 'total',
  236 + symbolSize: 10,
  237 + symbol: 'circle',
  238 + itemStyle: {
  239 + normal: {
  240 + color: 'rgba(252,230,48,1)',
  241 + barBorderRadius: 0,
  242 + label: {
  243 + show: true,
  244 + position: 'top',
  245 + formatter(p) {
  246 + return p.value > 0 ? p.value : ''
  247 + }
  248 + }
  249 + }
  250 + },
  251 + data: [
  252 + 1036,
  253 + 3693,
  254 + 2962,
  255 + 3810,
  256 + 2519,
  257 + 1915,
  258 + 1748,
  259 + 4675,
  260 + 6209,
  261 + 4323,
  262 + 2865,
  263 + 4298
  264 + ]
  265 + }
  266 + ]
  267 + })
  268 + }
  269 + }
  270 +}
  271 +</script>
... ...
src/components/Charts/Normal.vue 0 → 100644
  1 +<template>
  2 + <div :class="className" :style="{height: height,width: width}" ref="echarts"/>
  3 +</template>
  4 +
  5 +<script>
  6 + import echarts from 'echarts'
  7 + require('echarts/theme/macarons') // echarts theme
  8 + import resize from './mixins/resize'
  9 +
  10 + export default {
  11 + name: 'chart',
  12 + mixins: [resize],
  13 + props: {
  14 + className: {
  15 + type: String,
  16 + default: 'chart'
  17 + },
  18 + width: {
  19 + type: String,
  20 + default: '100%'
  21 + },
  22 + height: {
  23 + type: String,
  24 + default: '350px'
  25 + },
  26 + autoResize: {
  27 + type: Boolean,
  28 + default: true
  29 + },
  30 + options: {
  31 + type: Object,
  32 + default: {},
  33 + required: true
  34 + }
  35 + },
  36 + data() {
  37 + return {
  38 + chart: null
  39 + }
  40 + },
  41 + watch: {
  42 + options: {
  43 + deep: true,
  44 + handler(options) {
  45 + if (!this.chart && options) {
  46 + this.initChart()
  47 + } else {
  48 + this.chart.setOption(this.options, true)
  49 + }
  50 + }
  51 + }
  52 + },
  53 + mounted() {
  54 + this.$nextTick(() => {
  55 + this.initChart()
  56 + })
  57 + },
  58 + beforeDestroy() {
  59 + if (!this.chart) {
  60 + return
  61 + }
  62 + this.chart.dispose()
  63 + this.chart = null
  64 + },
  65 + methods: {
  66 + initChart() {
  67 + this.chart = echarts.init(this.$refs.echarts, 'macarons')
  68 + this.chart.setOption(this.options)
  69 + }
  70 + }
  71 + }
  72 +</script>
... ...
src/components/Charts/mixins/resize.js 0 → 100644
  1 +import { debounce } from '@/utils'
  2 +
  3 +export default {
  4 + data() {
  5 + return {
  6 + $_sidebarElm: null,
  7 + $_resizeHandler: null
  8 + }
  9 + },
  10 + mounted() {
  11 + this.initListener()
  12 + },
  13 + activated() {
  14 + if (!this.$_resizeHandler) {
  15 + // avoid duplication init
  16 + this.initListener()
  17 + }
  18 +
  19 + // when keep-alive chart activated, auto resize
  20 + this.resize()
  21 + },
  22 + beforeDestroy() {
  23 + this.destroyListener()
  24 + },
  25 + deactivated() {
  26 + this.destroyListener()
  27 + },
  28 + methods: {
  29 + // use $_ for mixins properties
  30 + // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
  31 + $_sidebarResizeHandler(e) {
  32 + if (e.propertyName === 'width') {
  33 + this.$_resizeHandler()
  34 + }
  35 + },
  36 + initListener() {
  37 + this.$_resizeHandler = debounce(() => {
  38 + this.resize()
  39 + }, 100)
  40 + window.addEventListener('resize', this.$_resizeHandler)
  41 +
  42 + this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
  43 + this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
  44 + },
  45 + destroyListener() {
  46 + window.removeEventListener('resize', this.$_resizeHandler)
  47 + this.$_resizeHandler = null
  48 +
  49 + this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
  50 + },
  51 + resize() {
  52 + const { chart } = this
  53 + chart && chart.resize()
  54 + }
  55 + }
  56 +}
... ...
src/components/ColumnSettings/index.vue 0 → 100644
  1 +<template>
  2 + <el-tooltip :content="$t('common.columnSettings')" placement="top">
  3 + <el-popover width="200" popper-class="columnSetting-popover">
  4 + <el-link icon="icon-ym icon-ym-options NCC-common-head-icon" :underline="false"
  5 + slot="reference" />
  6 + <NCC-table :data="data" class="columnTable" :hasNO="false" size="mini" ref="columnTable"
  7 + @selection-change="columnSelectionChange" @row-click="handleRowClick">
  8 + <el-table-column prop="label" label="列表字段" />
  9 + <el-table-column type="selection" width="50" align="center" />
  10 + </NCC-table>
  11 + </el-popover>
  12 + </el-tooltip>
  13 +</template>
  14 +
  15 +<script>
  16 +export default {
  17 + name: 'ColumnSettings',
  18 + model: {
  19 + prop: 'value',
  20 + event: 'change'
  21 + },
  22 + props: {
  23 + value: Array,
  24 + data: {
  25 + type: Array,
  26 + default: () => []
  27 + }
  28 + },
  29 + mounted() {
  30 + this.$refs.columnTable.$refs.NCCTable.toggleAllSelection()
  31 + },
  32 + methods: {
  33 + handleRowClick(row) {
  34 + this.$refs.columnTable.$refs.NCCTable.toggleRowSelection(row)
  35 + },
  36 + columnSelectionChange(val) {
  37 + this.$emit('change', val)
  38 + }
  39 + }
  40 +}
  41 +</script>
  42 +<style lang="scss">
  43 +.columnSetting-popover {
  44 + padding: 0 !important;
  45 +}
  46 +</style>
0 47 \ No newline at end of file
... ...
src/components/CompanyForm/index.vue
... ... @@ -66,7 +66,7 @@
66 66 </el-form>
67 67 <span slot="footer" class="dialog-footer">
68 68 <el-button @click="close">取消</el-button>
69   - <el-button type="primary" @click="confirm">确认</el-button>
  69 + <el-button type="primary" @click="confirm" :loading="btnLoading">确认</el-button>
70 70 </span>
71 71 </el-dialog>
72 72 </div>
... ... @@ -79,35 +79,36 @@ export default {
79 79 props: {},
80 80 data() {
81 81 return {
82   - visible: false,
83   - loading: false,
84   - dataForm: {
85   - id: '',
86   - id: undefined,
87   - companyName: undefined,
88   - socialCreditAgency: undefined,
89   - legalPerson: undefined,
90   - address: undefined,
91   - contactInformation: undefined,
92   - qualificationCertificate: [],
93   - otherInfo: undefined,
94   - },
95   - rules: {
96   - companyName: [
97   - {
98   - required: true,
99   - message: '请输入公司名称',
100   - trigger: 'blur'
101   - },
102   - ],
103   - contactInformation: [
104   - {
105   - pattern: /^1[3456789]\d{9}$|^0\d{2,3}-?\d{7,8}$/,
106   - message: '请输入正确的联系方式',
107   - trigger: 'blur'
108   - },
109   - ],
110   - },
  82 + visible: false,
  83 + loading: false,
  84 + dataForm: {
  85 + id: '',
  86 + id: undefined,
  87 + companyName: undefined,
  88 + socialCreditAgency: undefined,
  89 + legalPerson: undefined,
  90 + address: undefined,
  91 + contactInformation: undefined,
  92 + qualificationCertificate: [],
  93 + otherInfo: undefined,
  94 + },
  95 + rules: {
  96 + companyName: [
  97 + {
  98 + required: true,
  99 + message: '请输入公司名称',
  100 + trigger: 'blur'
  101 + },
  102 + ],
  103 + contactInformation: [
  104 + {
  105 + pattern: /^1[3456789]\d{9}$|^0\d{2,3}-?\d{7,8}$/,
  106 + message: '请输入正确的联系方式',
  107 + trigger: 'blur'
  108 + },
  109 + ],
  110 + },
  111 + btnLoading: false
111 112 };
112 113 },
113 114 watch: {},
... ... @@ -122,25 +123,27 @@ export default {
122 123 this.$refs['elForm'].resetFields();
123 124 },
124 125 async confirm() {
125   - this.$refs['elForm'].validate((valid) => {
126   - if (valid) {
127   - request({
128   - url: `/Extend/BaseComapnyInfo`,
129   - method: 'post',
130   - data: this.dataForm,
131   - }).then((res) => {
132   - this.$message({
133   - message: res.msg,
134   - type: 'success',
135   - duration: 1000,
136   - onClose: () => {
137   - this.visible = false,
138   - this.$emit('refresh', true)
139   - }
140   - })
141   - })
142   - }})
143   -
  126 + this.$refs['elForm'].validate((valid) => {
  127 + if (valid) {
  128 + this.btnLoading = true;
  129 + request({
  130 + url: `/Extend/BaseComapnyInfo`,
  131 + method: 'post',
  132 + data: this.dataForm,
  133 + }).then((res) => {
  134 + this.btnLoading = false;
  135 + this.$message({
  136 + message: res.msg,
  137 + type: 'success',
  138 + duration: 1000,
  139 + onClose: () => {
  140 + this.visible = false,
  141 + this.$emit('refresh', true)
  142 + }
  143 + })
  144 + }).catch(() => {this.btnLoading = false})
  145 + }
  146 + })
144 147 },
145 148 },
146 149 };
... ...
src/components/Hamburger/index.vue 0 → 100644
  1 +<template>
  2 + <div style="padding: 0 15px;" @click="toggleClick">
  3 + <i class="icon-ym icon-ym-header-collapse1" v-if='isActive'></i>
  4 + <i class="icon-ym icon-ym-header-expand" v-else></i>
  5 + </div>
  6 +</template>
  7 +
  8 +<script>
  9 +export default {
  10 + name: 'Hamburger',
  11 + props: {
  12 + isActive: {
  13 + type: Boolean,
  14 + default: false
  15 + }
  16 + },
  17 + methods: {
  18 + toggleClick() {
  19 + this.$emit('toggleClick')
  20 + }
  21 + }
  22 +}
  23 +</script>
  24 +
  25 +<style scoped>
  26 +.icon-ym {
  27 + font-size: 20px;
  28 + color: #666666;
  29 +}
  30 +</style>
0 31 \ No newline at end of file
... ...
src/components/HeaderSearch/index.vue 0 → 100644
  1 +<template>
  2 + <div :class="{'show':show}" class="header-search">
  3 + <i class="el-icon-search search-icon" @click.stop="click"></i>
  4 + <el-select ref="headerSearchSelect" v-model="search" :remote-method="querySearch" filterable
  5 + default-first-option remote placeholder="搜索:导航菜单" class="header-search-select"
  6 + @change="change">
  7 + <el-option v-for="item in options" :key="item.path" :value="item"
  8 + :label="item.title.join(' > ')" />
  9 + </el-select>
  10 + </div>
  11 +</template>
  12 +
  13 +<script>
  14 +// fuse is a lightweight fuzzy-search module
  15 +// make search results more in line with expectations
  16 +import Fuse from 'fuse.js'
  17 +import path from 'path'
  18 +import i18n from '@/lang'
  19 +
  20 +export default {
  21 + name: 'HeaderSearch',
  22 + data() {
  23 + return {
  24 + search: '',
  25 + options: [],
  26 + searchPool: [],
  27 + show: false,
  28 + fuse: undefined
  29 + }
  30 + },
  31 + computed: {
  32 + routes() {
  33 + return this.$store.getters.permission_routes
  34 + },
  35 + lang() {
  36 + return this.$store.getters.language
  37 + }
  38 + },
  39 + watch: {
  40 + lang() {
  41 + this.searchPool = this.generateRoutes(this.routes)
  42 + },
  43 + routes() {
  44 + this.searchPool = this.generateRoutes(this.routes)
  45 + },
  46 + searchPool(list) {
  47 + this.initFuse(list)
  48 + },
  49 + show(value) {
  50 + if (value) {
  51 + document.body.addEventListener('click', this.close)
  52 + } else {
  53 + document.body.removeEventListener('click', this.close)
  54 + }
  55 + }
  56 + },
  57 + mounted() {
  58 + this.searchPool = this.generateRoutes(this.routes)
  59 + },
  60 + methods: {
  61 + click() {
  62 + this.show = !this.show
  63 + if (this.show) {
  64 + this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
  65 + }
  66 + },
  67 + close() {
  68 + this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
  69 + this.options = []
  70 + this.show = false
  71 + },
  72 + change(val) {
  73 + this.$router.push(val.path)
  74 + this.search = ''
  75 + this.options = []
  76 + this.$nextTick(() => {
  77 + this.show = false
  78 + })
  79 + },
  80 + initFuse(list) {
  81 + this.fuse = new Fuse(list, {
  82 + shouldSort: true,
  83 + threshold: 0.4,
  84 + location: 0,
  85 + distance: 100,
  86 + maxPatternLength: 32,
  87 + minMatchCharLength: 1,
  88 + keys: [{
  89 + name: 'title',
  90 + weight: 0.7
  91 + }, {
  92 + name: 'path',
  93 + weight: 0.3
  94 + }]
  95 + })
  96 + },
  97 + // Filter out the routes that can be displayed in the sidebar
  98 + // And generate the internationalized title
  99 + generateRoutes(routes, basePath = '/', prefixTitle = []) {
  100 + let res = []
  101 +
  102 + for (const router of routes) {
  103 + // console.log(router);
  104 +
  105 + // skip hidden router
  106 + if (router.hidden) { continue }
  107 +
  108 + const data = {
  109 + path: path.resolve(basePath, router.path),
  110 + title: [...prefixTitle]
  111 + }
  112 +
  113 + if (router.meta && router.meta.title) {
  114 + // generate internationalized title
  115 + const i18ntitle = i18n.t(`route.${router.meta.title}`)
  116 +
  117 + let title = i18ntitle
  118 + if (i18ntitle.indexOf('route.') > -1) title = router.meta.zhTitle
  119 + data.title = [...data.title, title]
  120 +
  121 + if (router.redirect !== 'noRedirect') {
  122 + // only push the routes with title
  123 + // special case: need to exclude parent router without redirect
  124 + res.push(data)
  125 + }
  126 + }
  127 +
  128 + // recursive child routes
  129 + if (router.children) {
  130 + const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
  131 + if (tempRoutes.length >= 1) {
  132 + res = [...res, ...tempRoutes]
  133 + }
  134 + }
  135 + }
  136 + return res
  137 + },
  138 + querySearch(query) {
  139 + if (query !== '') {
  140 + this.options = this.fuse.search(query)
  141 + } else {
  142 + this.options = []
  143 + }
  144 + }
  145 + }
  146 +}
  147 +</script>
  148 +
  149 +<style lang="scss" scoped>
  150 +.header-search {
  151 + font-size: 0 !important;
  152 +
  153 + .search-icon {
  154 + cursor: pointer;
  155 + font-size: 16px;
  156 + vertical-align: middle;
  157 + }
  158 +
  159 + .header-search-select {
  160 + font-size: 20px;
  161 + transition: width 0.2s;
  162 + width: 0;
  163 + overflow: hidden;
  164 + background: transparent;
  165 + border-radius: 0;
  166 + display: inline-block;
  167 + vertical-align: middle;
  168 +
  169 + >>> .el-input__inner {
  170 + border-radius: 0;
  171 + border: 0;
  172 + // padding-left: 0;
  173 + // padding-right: 0;
  174 + box-shadow: none !important;
  175 + border-bottom: 1px solid #d9d9d9;
  176 + vertical-align: middle;
  177 + }
  178 + }
  179 +
  180 + &.show {
  181 + .header-search-select {
  182 + width: 210px;
  183 + margin-left: 10px;
  184 + }
  185 + }
  186 +}
  187 +</style>
... ...
src/components/InfoForm/index.vue
... ... @@ -220,7 +220,7 @@
220 220 </el-form>
221 221 <span slot="footer" class="dialog-footer">
222 222 <el-button @click="close">取消</el-button>
223   - <el-button type="primary" @click="confirm">确认</el-button>
  223 + <el-button type="primary" @click="confirm" :loading="btnLoading">确认</el-button>
224 224 </span>
225 225 </el-dialog>
226 226 </div>
... ... @@ -312,6 +312,7 @@ export default {
312 312 systemTypeOptions: [],
313 313 systemClassOptions: [],
314 314 areaOptions: [],
  315 + btnLoading: false,
315 316 };
316 317 },
317 318 watch: {},
... ... @@ -388,6 +389,7 @@ export default {
388 389 async confirm() {
389 390 this.$refs["infoForm"].validate((valid) => {
390 391 if (valid) {
  392 + this.btnLoading = true;
391 393 switch (this.type) {
392 394 case "add":
393 395 addSystem(this.infoForm).then((res) => {
... ... @@ -395,9 +397,10 @@ export default {
395 397 message: "恭喜你,新增成功",
396 398 type: "success",
397 399 });
  400 + this.btnLoading = false;
398 401 this.$emit('reInit');
399 402 this.close();
400   - });
  403 + }).catch(() => { this.btnLoading = false });
401 404 break;
402 405 case "edit":
403 406 this.infoForm.id = this.systemId;
... ... @@ -406,9 +409,10 @@ export default {
406 409 message: "恭喜你,修改成功",
407 410 type: "success",
408 411 });
  412 + this.btnLoading = false;
409 413 this.$emit('reInit');
410 414 this.close();
411   - });
  415 + }).catch(() => { this.btnLoading = false });;
412 416 break;
413 417  
414 418 default:
... ...
src/components/LangSelect/index.vue 0 → 100644
  1 +<template>
  2 + <el-dropdown class="international" @command="handleSetLanguage">
  3 + <div>
  4 + <i class="icon-ym icon-ym-header-language"></i>
  5 + </div>
  6 + <el-dropdown-menu slot="dropdown">
  7 + <el-dropdown-item :disabled="language==='zh'" command="zh">
  8 + 简体中文
  9 + </el-dropdown-item>
  10 + <el-dropdown-item :disabled="language==='zhtw'" command="zhtw">
  11 + 繁体中文
  12 + </el-dropdown-item>
  13 + <el-dropdown-item :disabled="language==='en'" command="en">
  14 + English
  15 + </el-dropdown-item>
  16 + </el-dropdown-menu>
  17 + </el-dropdown>
  18 +</template>
  19 +
  20 +<script>
  21 +import { UpdateLanguage } from '@/api/permission/userSetting'
  22 +import getPageTitle from '@/utils/get-page-title'
  23 +export default {
  24 + computed: {
  25 + language() {
  26 + return this.$store.getters.language
  27 + }
  28 + },
  29 + methods: {
  30 + handleSetLanguage(lang) {
  31 + UpdateLanguage({ language: lang }).then(res => { })
  32 + this.$i18n.locale = lang
  33 + this.$store.dispatch('app/setLanguage', lang)
  34 + let text = '切换成功'
  35 + if (lang === 'en') text = 'Switch Language Success'
  36 + if (lang === 'zhtw') text = '切換成功'
  37 + document.title = getPageTitle(this.$route.meta.title, this.$route.meta.zhTitle)
  38 + this.$message({
  39 + message: text,
  40 + type: 'success'
  41 + })
  42 + }
  43 + }
  44 +}
  45 +</script>
... ...
src/components/MarkdownEditor/default-options.js 0 → 100644
  1 +// doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
  2 +export default {
  3 + minHeight: '200px',
  4 + previewStyle: 'vertical',
  5 + useCommandShortcut: true,
  6 + useDefaultHTMLSanitizer: true,
  7 + usageStatistics: false,
  8 + hideModeSwitch: false,
  9 + toolbarItems: [
  10 + 'heading',
  11 + 'bold',
  12 + 'italic',
  13 + 'strike',
  14 + 'divider',
  15 + 'hr',
  16 + 'quote',
  17 + 'divider',
  18 + 'ul',
  19 + 'ol',
  20 + 'task',
  21 + 'indent',
  22 + 'outdent',
  23 + 'divider',
  24 + 'table',
  25 + 'image',
  26 + 'link',
  27 + 'divider',
  28 + 'code',
  29 + 'codeblock'
  30 + ]
  31 +}
... ...
src/components/MarkdownEditor/index.vue 0 → 100644
  1 +<template>
  2 + <div :id="id" />
  3 +</template>
  4 +
  5 +<script>
  6 +// deps for editor
  7 +import 'codemirror/lib/codemirror.css' // codemirror
  8 +import 'tui-editor/dist/tui-editor.css' // editor ui
  9 +import 'tui-editor/dist/tui-editor-contents.css' // editor content
  10 +
  11 +import Editor from 'tui-editor'
  12 +import defaultOptions from './default-options'
  13 +
  14 +export default {
  15 + name: 'MarkdownEditor',
  16 + props: {
  17 + value: {
  18 + type: String,
  19 + default: ''
  20 + },
  21 + id: {
  22 + type: String,
  23 + required: false,
  24 + default() {
  25 + return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
  26 + }
  27 + },
  28 + options: {
  29 + type: Object,
  30 + default() {
  31 + return defaultOptions
  32 + }
  33 + },
  34 + mode: {
  35 + type: String,
  36 + default: 'markdown'
  37 + },
  38 + height: {
  39 + type: String,
  40 + required: false,
  41 + default: '300px'
  42 + },
  43 + language: {
  44 + type: String,
  45 + required: false,
  46 + default: 'en_US' // https://github.com/nhnent/tui.editor/tree/master/src/js/langs
  47 + }
  48 + },
  49 + data() {
  50 + return {
  51 + editor: null
  52 + }
  53 + },
  54 + computed: {
  55 + editorOptions() {
  56 + const options = Object.assign({}, defaultOptions, this.options)
  57 + options.initialEditType = this.mode
  58 + options.height = this.height
  59 + options.language = this.language
  60 + return options
  61 + }
  62 + },
  63 + watch: {
  64 + value(newValue, preValue) {
  65 + if (newValue !== preValue && newValue !== this.editor.getValue()) {
  66 + this.editor.setValue(newValue)
  67 + }
  68 + },
  69 + language(val) {
  70 + this.destroyEditor()
  71 + this.initEditor()
  72 + },
  73 + height(newValue) {
  74 + this.editor.height(newValue)
  75 + },
  76 + mode(newValue) {
  77 + this.editor.changeMode(newValue)
  78 + }
  79 + },
  80 + mounted() {
  81 + this.initEditor()
  82 + },
  83 + destroyed() {
  84 + this.destroyEditor()
  85 + },
  86 + methods: {
  87 + initEditor() {
  88 + this.editor = new Editor({
  89 + el: document.getElementById(this.id),
  90 + ...this.editorOptions
  91 + })
  92 + if (this.value) {
  93 + this.editor.setValue(this.value)
  94 + }
  95 + this.editor.on('change', () => {
  96 + this.$emit('input', this.editor.getValue())
  97 + })
  98 + },
  99 + destroyEditor() {
  100 + if (!this.editor) return
  101 + this.editor.off('change')
  102 + this.editor.remove()
  103 + },
  104 + setValue(value) {
  105 + this.editor.setValue(value)
  106 + },
  107 + getValue() {
  108 + return this.editor.getValue()
  109 + },
  110 + setHtml(value) {
  111 + this.editor.setHtml(value)
  112 + },
  113 + getHtml() {
  114 + return this.editor.getHtml()
  115 + }
  116 + }
  117 +}
  118 +</script>
... ...
src/components/NCC-TreeTransfer/array.js 0 → 100644
  1 +/**
  2 + * auth: weilan
  3 + * github: https://github.com/hql7
  4 + * description: 一个数组操作函数库
  5 + */
  6 +
  7 +// 从树形数据中递归筛选目标值
  8 +function valInDeep(arr = [], val, id, childs) {
  9 + return arr.reduce((flat, item) => {
  10 + return flat.concat(
  11 + item[id] == val ? item : valInDeep(item[childs] || [], val, id, childs)
  12 + );
  13 + }, []);
  14 +}
  15 +
  16 +// 将树形数据向下递归为一维数组
  17 +function flattenDeep(arr = [], childs) {
  18 + return arr.reduce((flat, item) => {
  19 + return flat.concat(
  20 + item,
  21 + item[childs] ? flattenDeep(item[childs], childs) : []
  22 + );
  23 + }, []);
  24 +}
  25 +
  26 +// 将树形数据向上将此支线递归为一维数组
  27 +function flattenDeepParents(arr, parent) {
  28 + return arr.reduce((flat, item) => {
  29 + return flat.concat(
  30 + item[parent] || [],
  31 + item[parent] ? flattenDeepParents([item[parent]], parent) : []
  32 + );
  33 + }, []);
  34 +}
  35 +
  36 +// 根据条件递归祖先元素
  37 +function regDeepParents(row, parent, reg) {
  38 + if (row[parent]) {
  39 + reg && reg(row[parent]);
  40 + regDeepParents(row[parent], parent, reg);
  41 + }
  42 +}
  43 +
  44 +// 将数组转化成树结构
  45 +function arrayToTree(
  46 + array = [],
  47 + options = { id: "id", pid: "pid", children: "children" }
  48 +) {
  49 + let array_ = []; // 创建储存剔除叶子节点后的骨架节点数组
  50 + let unique = {}; // 创建盒子辅助本轮children合并去重
  51 + array.forEach(item => {
  52 + // 适应el-tree-transfer 将根节点pid重置为 0
  53 + let root = ["undefined", undefined, null].includes(item[options.pid]);
  54 + if (root) item[options.pid] = 0;
  55 + // 筛选可以插入当前节点的所有子节点
  56 + let children_array = array.filter(
  57 + it => it[options.pid] === item[options.id]
  58 + );
  59 + if (item.children && item.children instanceof Array) {
  60 + // 去重合并数组
  61 + item.children.map(i => (unique[i[options.id]] = 1));
  62 + item.children.push(
  63 + ...children_array.filter(i => unique[i[options.id]] !== 1)
  64 + );
  65 + } else {
  66 + item.children = children_array;
  67 + }
  68 + // 当children_array有数据时插入下一轮array_,当无数据时将最后留下来的根节点树形插入数组
  69 + let has_children = children_array.length > 0;
  70 + if (
  71 + has_children ||
  72 + (!has_children && [0, "0"].includes(item[options.pid]))
  73 + ) {
  74 + array_.push(item);
  75 + }
  76 + });
  77 + // 当数组内仅有根节点时退出,否组继续处理 最终递归深度次
  78 + if (!array_.every(item => [0, "0"].includes(item[options.pid]))) {
  79 + return arrayToTree(array_, options);
  80 + } else {
  81 + return array_;
  82 + }
  83 +}
  84 +
  85 +export {
  86 + valInDeep,
  87 + flattenDeep,
  88 + flattenDeepParents,
  89 + regDeepParents,
  90 + arrayToTree
  91 +};
... ...
src/components/NCC-TreeTransfer/index.vue 0 → 100644
  1 +<template>
  2 + <div class="transfer" :style="{ width, height }">
  3 + <template>
  4 + <!-- 左侧穿梭框 原料框 -->
  5 + <div class="transfer-left">
  6 + <h3 class="transfer-title">
  7 + <el-checkbox :indeterminate="from_is_indeterminate" v-model="from_check_all"
  8 + @change="fromAllBoxChange" />
  9 + <span>{{ fromTitle }}</span>
  10 + <slot name="title-left"></slot>
  11 + </h3>
  12 + <!-- 内容区 -->
  13 + <div class="transfer-main">
  14 + <slot name="from"></slot>
  15 + <el-input v-if="filter" :placeholder="placeholder" v-model="filterFrom"
  16 + class="filter-tree" clearable />
  17 + <el-tree ref="from-tree" show-checkbox :lazy="lazy" :node-key="node_key"
  18 + :load="leftloadNode" :props="defaultProps" :data="self_from_data"
  19 + :default-expand-all="openAll" :highlight-current="highLight"
  20 + :render-content="renderContentLeft" :filter-node-method="filterNodeFrom"
  21 + :default-checked-keys="defaultCheckedKeys" :default-expanded-keys="from_expanded_keys"
  22 + @check="fromTreeChecked">
  23 + <div slot-scope="{ node, data }">
  24 + <i :class="data.icon" />
  25 + <span style="padding-left: 4px;">{{ node.label }}</span>
  26 + </div>
  27 + </el-tree>
  28 + <slot name="left-footer"></slot>
  29 + </div>
  30 + </div>
  31 + <!-- 穿梭区 按钮框 -->
  32 + <div class="transfer-center">
  33 + <template v-if="button_text">
  34 + <p class="transfer-center-item">
  35 + <el-button type="primary" @click="addToAims(true)" :disabled="from_disabled">
  36 + {{ fromButton || "添加" }}
  37 + <i class="el-icon-arrow-right"></i>
  38 + </el-button>
  39 + </p>
  40 + <p class="transfer-center-item">
  41 + <el-button type="primary" @click="removeToSource" :disabled="to_disabled"
  42 + icon="el-icon-arrow-left">{{ toButton || "移除" }}</el-button>
  43 + </p>
  44 + </template>
  45 + <template v-else>
  46 + <p class="transfer-center-item">
  47 + <el-button type="primary" @click="addToAims(true)" icon="el-icon-arrow-right"
  48 + :disabled="from_disabled" />
  49 + </p>
  50 + <p class="transfer-center-item">
  51 + <el-button type="primary" @click="removeToSource" :disabled="to_disabled"
  52 + icon="el-icon-arrow-left" />
  53 + </p>
  54 + </template>
  55 + </div>
  56 + <!-- 右侧穿梭框 目标框 -->
  57 + <div class="transfer-right">
  58 + <h3 class="transfer-title">
  59 + <el-checkbox :indeterminate="to_is_indeterminate" v-model="to_check_all"
  60 + @change="toAllBoxChange" />
  61 + <span>{{ toTitle }}</span>
  62 + <slot name="title-right"></slot>
  63 + </h3>
  64 + <!-- 内容区 -->
  65 + <div class="transfer-main">
  66 + <slot name="to"></slot>
  67 + <el-input v-if="filter" :placeholder="placeholder" v-model="filterTo" size="small"
  68 + class="filter-tree" clearable />
  69 + <el-tree slot="to" ref="to-tree" show-checkbox :lazy="lazyRight" :data="self_to_data"
  70 + :node-key="node_key" :props="defaultProps" :load="rightloadNode"
  71 + :default-expand-all="openAll" :highlight-current="highLight"
  72 + :render-content="renderContentRight" :filter-node-method="filterNodeTo"
  73 + :default-expanded-keys="to_expanded_keys" @check="toTreeChecked">
  74 + <div slot-scope="{ node, data }">
  75 + <i :class="data.icon" />
  76 + <span style="padding-left: 4px;">{{ node.label }}</span>
  77 + </div>
  78 + </el-tree>
  79 + <slot name="right-footer"></slot>
  80 + </div>
  81 + </div>
  82 + </template>
  83 + </div>
  84 +</template>
  85 +
  86 +<script>
  87 +import { arrayToTree } from './array';
  88 +export default {
  89 + name: 'NCC-tree-transfer',
  90 + data() {
  91 + return {
  92 + from_is_indeterminate: false, // 源数据是否半选
  93 + from_check_all: false, // 源数据是否全选
  94 + to_is_indeterminate: false, // 目标数据是否半选
  95 + to_check_all: false, // 目标数据是否全选
  96 + from_expanded_keys: [], // 源数据展开节点
  97 + to_expanded_keys: [], // 目标数据展开节点
  98 + from_disabled: true, // 添加按钮是否禁用
  99 + to_disabled: true, // 移除按钮是否禁用
  100 + from_check_keys: [], // 源数据选中key数组 以此属性关联穿梭按钮,总全选、半选状态
  101 + to_check_keys: [], // 目标数据选中key数组 以此属性关联穿梭按钮,总全选、半选状态
  102 + filterFrom: "", // 源数据筛选
  103 + filterTo: "", // 目标数据筛选
  104 + }
  105 + },
  106 + props: {
  107 + // 宽度
  108 + width: {
  109 + type: String,
  110 + default: "100%"
  111 + },
  112 + // 高度
  113 + height: {
  114 + type: String,
  115 + default: "320px"
  116 + },
  117 + // 标题
  118 + title: {
  119 + type: Array,
  120 + default: () => ["源列表", "目标列表"]
  121 + },
  122 + // 穿梭按钮名字
  123 + button_text: Array,
  124 + // 源数据
  125 + from_data: {
  126 + type: Array,
  127 + default: () => []
  128 + },
  129 + // 选中数据
  130 + to_data: {
  131 + type: Array,
  132 + default: () => []
  133 + },
  134 + // el-tree 配置项
  135 + defaultProps: {
  136 + type: Object,
  137 + default: () => {
  138 + return { label: "fullName", children: "children" };
  139 + }
  140 + },
  141 + // el-tree node-key 必须唯一
  142 + node_key: {
  143 + type: String,
  144 + default: "id"
  145 + },
  146 + // 自定义 pid参数名
  147 + pid: {
  148 + type: String,
  149 + default: "parentId"
  150 + },
  151 + // 是否启用筛选
  152 + filter: {
  153 + type: Boolean,
  154 + default: false
  155 + },
  156 + // 是否展开所有节点
  157 + openAll: {
  158 + type: Boolean,
  159 + default: false
  160 + },
  161 + // 左侧自定义树节点
  162 + renderContentLeft: Function,
  163 + // 右侧自定义树节点
  164 + renderContentRight: Function,
  165 + // 是否展开节点
  166 + transferOpenNode: {
  167 + type: Boolean,
  168 + default: true
  169 + },
  170 + // 源数据 默认选中节点
  171 + defaultCheckedKeys: {
  172 + type: Array,
  173 + default: () => []
  174 + },
  175 + // 源数据 默认展开节点
  176 + defaultExpandedKeys: {
  177 + type: Array,
  178 + default: () => []
  179 + },
  180 + // 筛选placeholder
  181 + placeholder: {
  182 + type: String,
  183 + default: "输入关键词"
  184 + },
  185 + // 自定义筛选函数
  186 + filterNode: Function,
  187 + // 默认穿梭一次默认选中数据
  188 + defaultTransfer: {
  189 + type: Boolean,
  190 + default: false
  191 + },
  192 + // 是否开启arrayToTree
  193 + arrayToTree: {
  194 + type: Boolean,
  195 + default: false
  196 + },
  197 + // 是否启用懒加载
  198 + lazy: {
  199 + type: Boolean,
  200 + default: false
  201 + },
  202 + // 右侧是否启用懒加载
  203 + lazyRight: {
  204 + type: Boolean,
  205 + default: false
  206 + },
  207 + // 懒加载的回调函数
  208 + lazyFn: Function,
  209 + // 是否高亮当前选中节点,默认值是 false。
  210 + highLight: {
  211 + type: Boolean,
  212 + default: false
  213 + }
  214 + },
  215 + created() {
  216 + this.from_check_keys = this.defaultCheckedKeys;
  217 + this.from_expanded_keys = this.defaultExpandedKeys;
  218 + this.to_expanded_keys = this.defaultExpandedKeys;
  219 + if (this.defaultTransfer && this.defaultCheckedKeys.length > 0) {
  220 + this.$nextTick(() => {
  221 + this.addToAims(false);
  222 + });
  223 + }
  224 + },
  225 + methods: {
  226 + // -------------------------------提供输出函数---------------------
  227 + /**
  228 + * 清空选中节点
  229 + * type:string left左边 right右边 all全部 默认all
  230 + */
  231 + clearChecked(type = "all") {
  232 + if (type === "left") {
  233 + this.$refs["from-tree"].setCheckedKeys([]);
  234 + this.from_is_indeterminate = false;
  235 + this.from_check_all = false;
  236 + } else if (type === "right") {
  237 + this.$refs["to-tree"].setCheckedKeys([]);
  238 + this.to_is_indeterminate = false;
  239 + this.to_check_all = false;
  240 + } else {
  241 + this.$refs["from-tree"].setCheckedKeys([]);
  242 + this.$refs["to-tree"].setCheckedKeys([]);
  243 + this.from_is_indeterminate = false;
  244 + this.from_check_all = false;
  245 + this.to_is_indeterminate = false;
  246 + this.to_check_all = false;
  247 + }
  248 + },
  249 + // 添加按钮
  250 + addToAims(emit) {
  251 + // 获取选中通过穿梭框的keys - 仅用于传送纯净的id数组到父组件同后台通信
  252 + let keys = this.$refs["from-tree"].getCheckedKeys();
  253 + // 获取半选通过穿梭框的keys - 仅用于传送纯净的id数组到父组件同后台通信
  254 + let harfKeys = this.$refs["from-tree"].getHalfCheckedKeys();
  255 + // 选中节点数据
  256 + let arrayCheckedNodes = this.$refs["from-tree"].getCheckedNodes();
  257 + // 获取选中通过穿梭框的nodes - 仅用于传送选中节点数组到父组件同后台通信需求
  258 + let nodes = JSON.parse(JSON.stringify(arrayCheckedNodes));
  259 + // 半选中节点数据
  260 + let arrayHalfCheckedNodes = this.$refs["from-tree"].getHalfCheckedNodes();
  261 + // 获取半选通过穿梭框的nodes - 仅用于传送选中节点数组到父组件同后台通信需求
  262 + let halfNodes = JSON.parse(JSON.stringify(arrayHalfCheckedNodes));
  263 +
  264 + // 自定义参数读取设置
  265 + let children__ = this.defaultProps.children || "children";
  266 + let pid__ = this.pid || "parentId";
  267 + let id__ = this["node_key"] || "id";
  268 +
  269 + /*
  270 + * 先整合目标树没有父节点的叶子节点选中,需要整理出来此叶子节点的父节点直到根节点路径 - 此时所有骨架节点已有
  271 + * 再将所有末端叶子节点根据pid直接推入目标树即可
  272 + * 声明新盒子将所有半选节点的子节点清除 - 只保留骨架 因为排序是先父后子 因此不存在子元素处理好插入时父元素还没处理的情况
  273 + * 下面一二步是为了搭建出来目标树没有根节点躯干节点时的叶子选中,给此叶子搭建出根节点和躯干节点
  274 + */
  275 +
  276 + // let不存在状态提升 因此在函数调用之前赋值 并递归为以为数组!
  277 + let self_to_data = JSON.stringify(this.self_to_data);
  278 + // 第一步
  279 + let skeletonHalfCheckedNodes = JSON.parse(
  280 + JSON.stringify(arrayHalfCheckedNodes)
  281 + ); // 深拷贝数据 - 半选节点
  282 + // 筛选目标树不存在的骨架节点 - 半选内的节点
  283 + let newSkeletonHalfCheckedNodes = [];
  284 + skeletonHalfCheckedNodes.forEach(item => {
  285 + if (!inquireIsExist(item)) {
  286 + newSkeletonHalfCheckedNodes.push(item);
  287 + }
  288 + });
  289 + // 筛选到目标树不存在的骨架后在处理每个骨架节点-非末端叶子节点 - 半选节点
  290 + newSkeletonHalfCheckedNodes.forEach(item => {
  291 + item[children__] = [];
  292 + [0, "-1"].includes(item[pid__])
  293 + ? this.$refs["to-tree"].append(item)
  294 + : this.$refs["to-tree"].append(item, item[pid__]);
  295 + });
  296 +
  297 + // 第二步
  298 + // 筛选目标树不存在的骨架节点 - 全选内的节点
  299 + let newSkeletonCheckedNodes = [];
  300 + nodes.forEach(item => {
  301 + if (!inquireIsExist(item)) {
  302 + newSkeletonCheckedNodes.push(item);
  303 + }
  304 + });
  305 + // 筛选到目标树不存在的骨架后在处理每个骨架节点-非末端叶子节点 - 全选节点
  306 + newSkeletonCheckedNodes.forEach(item => {
  307 + if (item[children__] && item[children__].length > 0) {
  308 + item[children__] = [];
  309 + [0, "-1"].includes(item[pid__])
  310 + ? this.$refs["to-tree"].append(item)
  311 + : this.$refs["to-tree"].append(item, item[pid__]);
  312 + }
  313 + });
  314 +
  315 + // 第三步 处理末端叶子元素 - 声明新盒子筛选出所有末端叶子节点
  316 + let leafCheckedNodes = arrayCheckedNodes.filter(
  317 + item => !item[children__] || item[children__].length === 0
  318 + );
  319 + // 末端叶子插入目标树
  320 + leafCheckedNodes.forEach(item => {
  321 + if (!inquireIsExist(item)) {
  322 + this.$refs["to-tree"].append(item, item[pid__]);
  323 + }
  324 + });
  325 +
  326 + // 递归查询data内是否存在item函数
  327 + function inquireIsExist(item, strData = self_to_data) {
  328 + // 将树形数据格式化成一维字符串 然后通过匹配来判断是否已存在
  329 + let strItem =
  330 + typeof item[id__] == "number"
  331 + ? `"${id__}":${item[id__]},`
  332 + : `"${id__}":"${item[id__]}"`;
  333 + let reg = RegExp(strItem);
  334 + let existed = reg.test(strData);
  335 + return existed;
  336 + }
  337 +
  338 + // 左侧删掉选中数据
  339 + arrayCheckedNodes.map(item => this.$refs["from-tree"].remove(item));
  340 +
  341 + // 处理完毕按钮恢复禁用状态
  342 + this.from_check_keys = [];
  343 +
  344 + // 目标数据节点展开
  345 + if (this.transferOpenNode && !this.lazy) {
  346 + this.to_expanded_keys = keys;
  347 + }
  348 +
  349 + // 传递信息给父组件
  350 + emit &&
  351 + this.$emit("addBtn", this.self_from_data, this.self_to_data, {
  352 + keys,
  353 + nodes,
  354 + harfKeys,
  355 + halfNodes
  356 + });
  357 +
  358 + // 处理完毕取消选中
  359 + this.$refs["from-tree"].setCheckedKeys([]);
  360 + },
  361 + // 移除按钮
  362 + removeToSource() {
  363 + // 获取选中通过穿梭框的keys - 仅用于传送纯净的id数组到父组件同后台通信
  364 + let keys = this.$refs["to-tree"].getCheckedKeys();
  365 + // 获取半选通过穿梭框的keys - 仅用于传送纯净的id数组到父组件同后台通信
  366 + let harfKeys = this.$refs["to-tree"].getHalfCheckedKeys();
  367 + // 获取选中通过穿梭框的nodes 选中节点数据
  368 + let arrayCheckedNodes = this.$refs["to-tree"].getCheckedNodes();
  369 + // 获取选中通过穿梭框的nodes - 仅用于传送选中节点数组到父组件同后台通信需求
  370 + let nodes = JSON.parse(JSON.stringify(arrayCheckedNodes));
  371 + // 半选中节点数据
  372 + let arrayHalfCheckedNodes = this.$refs["to-tree"].getHalfCheckedNodes();
  373 + // 获取半选通过穿梭框的nodes - 仅用于传送选中节点数组到父组件同后台通信需求
  374 + let halfNodes = JSON.parse(JSON.stringify(arrayHalfCheckedNodes));
  375 +
  376 + // 自定义参数读取设置
  377 + let children__ = this.defaultProps.children || "children";
  378 + let pid__ = this.pid || "parentId";
  379 + let id__ = this["node_key"] || "id";
  380 +
  381 + /*
  382 + * 先整合目标树没有父节点的叶子节点选中,需要整理出来此叶子节点的父节点直到根节点路径 - 此时所有骨架节点已有
  383 + * 再将所有末端叶子节点根据pid直接推入目标树即可
  384 + * 声明新盒子将所有半选节点的子节点清除 - 只保留骨架 因为排序是先父后子 因此不存在子元素处理好插入时父元素还没处理的情况
  385 + * 下面一二步是为了搭建出来目标树没有根节点躯干节点时的叶子选中,给此叶子搭建出根节点和躯干节点
  386 + */
  387 +
  388 + // let不存在状态提升 因此在函数调用之前赋值 并递归为以为数组!
  389 + let self_from_data = JSON.stringify(this.self_from_data);
  390 + // 第一步
  391 + let skeletonHalfCheckedNodes = JSON.parse(
  392 + JSON.stringify(arrayHalfCheckedNodes)
  393 + ); // 深拷贝数据 - 半选节点
  394 + // 筛选目标树不存在的骨架节点 - 半选内的节点
  395 + let newSkeletonHalfCheckedNodes = [];
  396 + skeletonHalfCheckedNodes.forEach(item => {
  397 + if (!inquireIsExist(item)) {
  398 + newSkeletonHalfCheckedNodes.push(item);
  399 + }
  400 + });
  401 + // 筛选到目标树不存在的骨架后在处理每个骨架节点-非末端叶子节点 - 半选节点
  402 + newSkeletonHalfCheckedNodes.forEach(item => {
  403 + item[children__] = [];
  404 + [0, "-1"].includes(item[pid__])
  405 + ? this.$refs["from-tree"].append(item)
  406 + : this.$refs["from-tree"].append(item, item[pid__]);
  407 + });
  408 +
  409 + // 第二步
  410 + // 筛选目标树不存在的骨架节点 - 全选内的节点
  411 + let newSkeletonCheckedNodes = [];
  412 + nodes.forEach(item => {
  413 + if (!inquireIsExist(item)) {
  414 + newSkeletonCheckedNodes.push(item);
  415 + }
  416 + });
  417 + // 筛选到目标树不存在的骨架后在处理每个骨架节点-非末端叶子节点 - 全选节点
  418 + newSkeletonCheckedNodes.forEach(item => {
  419 + if (item[children__] && item[children__].length > 0) {
  420 + item[children__] = [];
  421 + [0, "-1"].includes(item[pid__])
  422 + ? this.$refs["from-tree"].append(item)
  423 + : this.$refs["from-tree"].append(item, item[pid__]);
  424 + }
  425 + });
  426 +
  427 + // 第三步 处理末端叶子元素 - 声明新盒子筛选出所有末端叶子节点
  428 + let leafCheckedNodes = arrayCheckedNodes.filter(
  429 + item => !item[children__] || item[children__].length == 0
  430 + );
  431 + // 末端叶子插入目标树
  432 + leafCheckedNodes.forEach(item => {
  433 + if (!inquireIsExist(item)) {
  434 + this.$refs["from-tree"].append(item, item[pid__]);
  435 + }
  436 + });
  437 +
  438 + // 递归查询data内是否存在item函数
  439 + function inquireIsExist(item, strData = self_from_data) {
  440 + // 将树形数据格式化成一维字符串 然后通过匹配来判断是否已存在
  441 + let strItem =
  442 + typeof item[id__] == "number"
  443 + ? `"${id__}":${item[id__]},`
  444 + : `"${id__}":"${item[id__]}"`;
  445 + let reg = RegExp(strItem);
  446 + let existed = reg.test(strData);
  447 + return existed;
  448 + }
  449 +
  450 + // 右侧删掉选中数据
  451 + arrayCheckedNodes.map(item => this.$refs["to-tree"].remove(item));
  452 +
  453 + // 处理完毕按钮恢复禁用状态
  454 + this.to_check_keys = [];
  455 +
  456 + // 目标数据节点展开
  457 + if (this.transferOpenNode && !this.lazy) {
  458 + this.from_expanded_keys = keys;
  459 + }
  460 +
  461 + // 传递信息给父组件
  462 + this.$emit("removeBtn", this.self_from_data, this.self_to_data, {
  463 + keys,
  464 + nodes,
  465 + harfKeys,
  466 + halfNodes
  467 + });
  468 + // 处理完毕取消选中
  469 + this.$refs["to-tree"].setCheckedKeys([]);
  470 + },
  471 + // 异步加载左侧
  472 + leftloadNode(node, resolve) {
  473 + if (node.level === 0) {
  474 + return resolve(this.self_from_data);
  475 + }
  476 + this.lazyFn && this.lazyFn(node, resolve, "left");
  477 + },
  478 + // 异步加载右侧
  479 + rightloadNode(node, resolve) {
  480 + if (node.level === 0) {
  481 + return resolve(this.self_to_data);
  482 + }
  483 +
  484 + this.lazyFn && this.lazyFn(node, resolve, "right");
  485 + },
  486 + // 源树选中事件 - 是否禁用穿梭按钮
  487 + fromTreeChecked(nodeObj, treeObj) {
  488 + this.from_check_keys = treeObj.checkedNodes
  489 + this.$nextTick(() => {
  490 +
  491 + this.$emit("left-check-change", nodeObj, treeObj, this.from_check_all)
  492 + })
  493 + },
  494 + // 目标树选中事件 - 是否禁用穿梭按钮
  495 + toTreeChecked(nodeObj, treeObj) {
  496 + this.to_check_keys = treeObj.checkedNodes;
  497 + this.$nextTick(() => {
  498 + this.$emit("right-check-change", nodeObj, treeObj, this.to_check_all);
  499 + });
  500 + },
  501 + // 源数据 总全选checkbox
  502 + fromAllBoxChange(val) {
  503 + if (this.self_from_data.length === 0) {
  504 + return;
  505 + }
  506 + if (val) {
  507 + this.from_check_keys = this.self_from_data;
  508 + this.$refs["from-tree"].setCheckedNodes(this.self_from_data);
  509 + } else {
  510 + this.$refs["from-tree"].setCheckedNodes([]);
  511 + this.from_check_keys = [];
  512 + }
  513 + this.$emit("left-check-change", null, null, this.from_check_all);
  514 + },
  515 + // 目标数据 总全选checkbox
  516 + toAllBoxChange(val) {
  517 + if (this.self_to_data.length === 0) {
  518 + return;
  519 + }
  520 + if (val) {
  521 + this.to_check_keys = this.self_to_data;
  522 + this.$refs["to-tree"].setCheckedNodes(this.self_to_data);
  523 + } else {
  524 + this.$refs["to-tree"].setCheckedNodes([]);
  525 + this.to_check_keys = [];
  526 + }
  527 + this.$emit("right-check-change", null, null, this.to_check_all);
  528 + },
  529 + // 源数据 筛选
  530 + filterNodeFrom(value, data) {
  531 + if (this.filterNode) {
  532 + return this.filterNode(value, data, "form");
  533 + }
  534 + if (!value) return true;
  535 + return data[this.defaultProps.label].indexOf(value) !== -1;
  536 + },
  537 + // 目标数据筛选
  538 + filterNodeTo(value, data) {
  539 + if (this.filterNode) {
  540 + return this.filterNode(value, data, "to");
  541 + }
  542 + if (!value) return true;
  543 + return data[this.defaultProps.label].indexOf(value) !== -1;
  544 + },
  545 + },
  546 + computed: {
  547 + // 左侧数据
  548 + self_from_data() {
  549 + let from_array = [...this.from_data];
  550 + if (!this.arrayToTree) {
  551 + from_array.forEach(item => {
  552 + item[this.pid] = 0;
  553 + });
  554 + return from_array;
  555 + } else {
  556 + return arrayToTree(from_array, {
  557 + id: this.node_key,
  558 + pid: this.pid,
  559 + children: this.defaultProps.children
  560 + });
  561 + }
  562 + },
  563 + // 右侧数据
  564 + self_to_data() {
  565 + let to_array = [...this.to_data];
  566 + if (!this.arrayToTree) {
  567 + to_array.forEach(item => {
  568 + item[this.pid] = 0;
  569 + });
  570 + return to_array;
  571 + } else {
  572 + return arrayToTree(to_array, {
  573 + id: this.node_key,
  574 + pid: this.pid,
  575 + children: this.defaultProps.children
  576 + });
  577 + }
  578 + },
  579 + // 左侧菜单名
  580 + fromTitle() {
  581 + let [text] = this.title;
  582 + return text;
  583 + },
  584 + // 右侧菜单名
  585 + toTitle() {
  586 + let [, text] = this.title;
  587 + return text;
  588 + },
  589 + // 右侧菜单名2
  590 + toTitleSecond() {
  591 + let [, , text] = this.title;
  592 + return text;
  593 + },
  594 + // 右侧菜单名3
  595 + toTitleThird() {
  596 + let [, , , text] = this.title;
  597 + return text;
  598 + },
  599 + // 上部按钮名
  600 + fromButton() {
  601 + if (this.button_text == undefined) {
  602 + return;
  603 + }
  604 +
  605 + let [text] = this.button_text;
  606 + return text;
  607 + },
  608 + // 下部按钮名
  609 + toButton() {
  610 + if (this.button_text == undefined) {
  611 + return;
  612 + }
  613 + let [, text] = this.button_text;
  614 + return text;
  615 + }
  616 + },
  617 + watch: {
  618 + // 左侧 状态监测
  619 + from_check_keys(val) {
  620 + if (val.length > 0) {
  621 + // 穿梭按钮是否禁用
  622 + this.from_disabled = false;
  623 + // 总半选是否开启
  624 + this.from_is_indeterminate = true;
  625 +
  626 + // 总全选是否开启 - 根据选中节点中为根节点的数量是否和源数据长度相等
  627 + let allCheck = val.filter(item => item[this.pid] === 0);
  628 + if (allCheck.length == this.self_from_data.length) {
  629 + // 关闭半选 开启全选
  630 + this.from_is_indeterminate = false;
  631 + this.from_check_all = true;
  632 + } else {
  633 + this.from_is_indeterminate = true;
  634 + this.from_check_all = false;
  635 + }
  636 + } else {
  637 + this.from_disabled = true;
  638 + this.from_is_indeterminate = false;
  639 + this.from_check_all = false;
  640 + }
  641 + },
  642 + // 右侧 状态监测
  643 + to_check_keys(val) {
  644 + if (val.length > 0) {
  645 + // 穿梭按钮是否禁用
  646 + this.to_disabled = false;
  647 + // 总半选是否开启
  648 + this.to_is_indeterminate = true;
  649 +
  650 + // 总全选是否开启 - 根据选中节点中为根节点的数量是否和源数据长度相等
  651 + let allCheck = val.filter(item => item[this.pid] === 0);
  652 + if (allCheck.length == this.self_to_data.length) {
  653 + // 关闭半选 开启全选
  654 + this.to_is_indeterminate = false;
  655 + this.to_check_all = true;
  656 + } else {
  657 + this.to_is_indeterminate = true;
  658 + this.to_check_all = false;
  659 + }
  660 + } else {
  661 + this.to_disabled = true;
  662 + this.to_is_indeterminate = false;
  663 + this.to_check_all = false;
  664 + }
  665 + },
  666 + // 左侧 数据筛选
  667 + filterFrom(val) {
  668 + this.$refs["from-tree"].filter(val);
  669 + },
  670 + // 右侧 数据筛选
  671 + filterTo(val) {
  672 + this.$refs["to-tree"].filter(val);
  673 + },
  674 + // 监视默认选中
  675 + defaultCheckedKeys(val) {
  676 + if (this.defaultTransfer && val.length > 0) {
  677 + this.$nextTick(() => {
  678 + this.addToAims(false);
  679 + });
  680 + }
  681 + },
  682 + // 监视默认展开
  683 + defaultExpandedKeys(val) {
  684 + let _form = new Set(this.from_expanded_keys.concat(val));
  685 + this.from_expanded_keys = [..._form];
  686 + let _to = new Set(this.to_expanded_keys.concat(val));
  687 + this.to_expanded_keys = [..._to];
  688 + }
  689 + }
  690 +};
  691 +</script>
  692 +
  693 +<style lang="scss" scoped>
  694 +.el-tree {
  695 + min-width: 100%;
  696 + display: inline-block !important;
  697 +}
  698 +
  699 +.transfer {
  700 + position: relative;
  701 + overflow: hidden;
  702 +}
  703 +
  704 +.transfer-left {
  705 + position: absolute;
  706 + top: 0;
  707 + left: 0;
  708 +}
  709 +
  710 +.transfer-right {
  711 + position: absolute;
  712 + top: 0;
  713 + right: 0;
  714 +}
  715 +
  716 +.transfer-right-item {
  717 + height: calc((100% - 41px) / 2);
  718 +}
  719 +
  720 +.transfer-right-small {
  721 + height: 41px;
  722 +}
  723 +
  724 +.transfer-right-only {
  725 + height: 100%;
  726 +}
  727 +
  728 +.transfer-main {
  729 + padding: 10px;
  730 + // height: calc(100% - 41px);
  731 + box-sizing: border-box;
  732 + overflow: hidden;
  733 + .el-tree {
  734 + height: calc(100vh - 590px);
  735 + overflow: auto;
  736 + }
  737 +}
  738 +
  739 +.transfer-left,
  740 +.transfer-right {
  741 + border: 1px solid #ebeef5;
  742 + width: 40%;
  743 + height: 100%;
  744 + box-sizing: border-box;
  745 + border-radius: 5px;
  746 + vertical-align: middle;
  747 +}
  748 +
  749 +.transfer-center {
  750 + position: absolute;
  751 + top: 50%;
  752 + left: 40%;
  753 + width: 20%;
  754 + transform: translateY(-50%);
  755 + text-align: center;
  756 +}
  757 +
  758 +.transfer-center-item {
  759 + padding: 10px;
  760 + overflow: hidden;
  761 +}
  762 +
  763 +.address-list-center {
  764 + height: 100%;
  765 +}
  766 +
  767 +.address-list-center > .transfer-center-item {
  768 + height: 50%;
  769 + padding: 70px 10px 0;
  770 + box-sizing: border-box;
  771 + overflow: hidden;
  772 +}
  773 +
  774 +.address-list-center > .address-only-item {
  775 + height: 100%;
  776 + position: relative;
  777 +}
  778 +
  779 +.address-only-item > .address-first-btn {
  780 + position: absolute;
  781 + top: 50%;
  782 + left: 50%;
  783 + transform: translate(-50%, -50%);
  784 +}
  785 +
  786 +.transfer-title {
  787 + border-bottom: 1px solid #ebeef5;
  788 + padding: 0 15px;
  789 + height: 40px;
  790 + line-height: 40px;
  791 + color: #333;
  792 + font-size: 16px;
  793 + background-color: #f5f7fa;
  794 + font-weight: normal;
  795 +}
  796 +
  797 +.transfer-title .el-checkbox {
  798 + margin-right: 10px;
  799 +}
  800 +
  801 +.filter-tree {
  802 + margin-bottom: 10px;
  803 +}
  804 +
  805 +.u-clear {
  806 + float: right;
  807 + color: #67c23a;
  808 + font-size: 14px;
  809 + cursor: pointer;
  810 +}
  811 +</style>
... ...
src/components/NCC-enlarge/index.vue 0 → 100644
  1 +<template>
  2 + <div class="enlarge-main">
  3 + <img :src="img" class="enlarge-img" @click="dialogVisible = true" title="点击查看放大" />
  4 + <el-dialog :visible.sync="dialogVisible" append-to-body width="600px"
  5 + class="NCC-dialog NCC-dialog_center enlarge-dialog">
  6 + <img width="100%" :src="img" alt />
  7 + </el-dialog>
  8 + </div>
  9 +</template>
  10 +
  11 +<script>
  12 +export default {
  13 + props: ["img"],
  14 + data() {
  15 + return {
  16 + dialogVisible: false
  17 + }
  18 + }
  19 +}
  20 +</script>
  21 +
  22 +<style lang="scss" scoped>
  23 +.enlarge-main {
  24 + cursor: pointer;
  25 + display: inline-block;
  26 + width: 120px;
  27 + height: 120px;
  28 + overflow: hidden;
  29 + margin: 0 8px 8px 0;
  30 + border: 1px solid #c0ccda;
  31 + border-radius: 6px;
  32 + -webkit-box-sizing: border-box;
  33 + box-sizing: border-box;
  34 + .enlarge-img {
  35 + width: 120px;
  36 + height: 120px;
  37 + object-fit: contain;
  38 + }
  39 +}
  40 +</style>
0 41 \ No newline at end of file
... ...
src/components/NCC-tableOperation/index.vue 0 → 100644
  1 +<template>
  2 + <div class="ncc-table-opts">
  3 + <!--左侧插槽-->
  4 + <slot name="left" />
  5 + <template v-if="isJudgePer">
  6 + <el-button size="mini" type="text" @click="edit()" v-if="hasEdit" :disabled="editDisabled"
  7 + v-has="editPerCode">{{ editText === '编辑' ? $t(`common.editBtn`) : editText }}</el-button>
  8 + </template>
  9 + <template v-else>
  10 + <el-button size="mini" type="text" @click="edit()" v-if="hasEdit" :disabled="editDisabled">
  11 + {{ editText === '编辑' ? $t(`common.editBtn`) : editText }}</el-button>
  12 + </template>
  13 + <!-- 中间插槽 -->
  14 + <slot name="center" />
  15 + <template v-if="isJudgePer">
  16 + <el-button size="mini" type="text" class="NCC-table-delBtn" @click="del()" v-if="hasDel"
  17 + :disabled="delDisabled" v-has="delPerCode">
  18 + {{ delText === '删除' ? $t(`common.delBtn`) : delText }}</el-button>
  19 + </template>
  20 + <template v-else>
  21 + <el-button size="mini" type="text" class="NCC-table-delBtn" @click="del()" v-if="hasDel"
  22 + :disabled="delDisabled">{{ delText === '删除' ? $t(`common.delBtn`) : delText }}</el-button>
  23 + </template>
  24 + <!-- 默认右侧插槽 -->
  25 + <slot />
  26 + </div>
  27 +</template>
  28 +<script>
  29 +export default {
  30 + props: {
  31 + delText: {
  32 + type: String,
  33 + default: '删除'
  34 + },
  35 + editText: {
  36 + type: String,
  37 + default: '编辑'
  38 + },
  39 + // 是否展示编辑按钮
  40 + hasEdit: {
  41 + type: Boolean,
  42 + default: true
  43 + },
  44 + // 是否展示删除按钮
  45 + hasDel: {
  46 + type: Boolean,
  47 + default: true
  48 + },
  49 + editDisabled: {
  50 + type: Boolean,
  51 + default: false
  52 + },
  53 + delDisabled: {
  54 + type: Boolean,
  55 + default: false
  56 + },
  57 + // 编辑按钮权限标识
  58 + editPerCode: {
  59 + type: String,
  60 + default: 'btn_edit'
  61 + },
  62 + // 删除按钮权限标识
  63 + delPerCode: {
  64 + type: String,
  65 + default: 'btn_remove'
  66 + },
  67 + // 是否开启权限判断
  68 + isJudgePer: {
  69 + type: Boolean,
  70 + default: false
  71 + },
  72 + },
  73 + data() {
  74 + return {}
  75 + },
  76 + methods: {
  77 + del() {
  78 + this.$emit('del')
  79 + },
  80 + edit() {
  81 + this.$emit('edit')
  82 + }
  83 + }
  84 +}
  85 +</script>
0 86 \ No newline at end of file
... ...
src/components/NCC-topOperation/index.vue 0 → 100644
  1 +<template>
  2 + <div class="ncc-opts">
  3 + <!--左侧插槽-->
  4 + <slot name="left" />
  5 + <template v-if="isJudgePer">
  6 + <el-button type="primary" @click="add" icon="el-icon-plus" v-has="addPerCode">
  7 + {{ addText === '新建' ? $t(`common.addBtn`) : addText }}
  8 + </el-button>
  9 + </template>
  10 + <template v-else>
  11 + <el-button type="primary" @click="add" icon="el-icon-plus">
  12 + {{ addText === '新建' ? $t(`common.addBtn`) : addText }}
  13 + </el-button>
  14 + </template>
  15 + <!-- 默认右侧插槽 -->
  16 + <slot />
  17 + </div>
  18 +</template>
  19 +<script>
  20 +export default {
  21 + props: {
  22 + refreshText: {
  23 + type: String,
  24 + default: '刷新'
  25 + },
  26 + addText: {
  27 + type: String,
  28 + default: '新建'
  29 + },
  30 + // 刷新加载状态
  31 + loading: {
  32 + type: Boolean,
  33 + default: false
  34 + },
  35 + // 是否展示刷新按钮
  36 + hasRefresh: {
  37 + type: Boolean,
  38 + default: true
  39 + },
  40 + // 新增按钮权限标识
  41 + addPerCode: {
  42 + type: String,
  43 + default: 'btn_add'
  44 + },
  45 + // 是否开启权限判断
  46 + isJudgePer: {
  47 + type: Boolean,
  48 + default: false
  49 + }
  50 + },
  51 + data() {
  52 + return {}
  53 + },
  54 + methods: {
  55 + refresh() {
  56 + this.$emit('refresh')
  57 + },
  58 + add() {
  59 + this.$emit('add')
  60 + }
  61 + }
  62 +}
  63 +</script>
  64 +<style lang="scss" scoped>
  65 +.ncc-opts {
  66 + display: inline-block;
  67 +}
  68 +</style>
0 69 \ No newline at end of file
... ...
src/components/NCC-uploadBtn/index.vue 0 → 100644
  1 +<template>
  2 + <el-upload :action="define.comUrl+url" :headers="{ Authorization: $store.getters.token}"
  3 + :on-success="handleSuccess" :before-upload="beforeUpload" :show-file-list="false"
  4 + class="upload-btn">
  5 + <el-button :type="buttonType" icon="el-icon-upload2" :loading="loading">{{buttonText}}
  6 + </el-button>
  7 + </el-upload>
  8 +</template>
  9 +
  10 +<script>
  11 +export default {
  12 + name: 'NCC-uploadBtn',
  13 + data() {
  14 + return {
  15 + loading: false
  16 + }
  17 + },
  18 + props: {
  19 + url: {
  20 + type: String,
  21 + default: ""
  22 + },
  23 + buttonText: {
  24 + type: String,
  25 + default: '导入'
  26 + },
  27 + buttonType: {
  28 + type: String,
  29 + default: 'text'
  30 + }
  31 + },
  32 + methods: {
  33 + beforeUpload() {
  34 + this.loading = true
  35 + },
  36 + handleSuccess(res) {
  37 + this.loading = false
  38 + if (res.code == 200) {
  39 + this.$message({
  40 + message: res.msg,
  41 + type: 'success',
  42 + duration: 1000
  43 + })
  44 + this.$emit('on-success')
  45 + } else {
  46 + this.$message({
  47 + message: res.msg,
  48 + type: 'error',
  49 + duration: 1000
  50 + })
  51 + }
  52 + }
  53 + }
  54 +};
  55 +</script>
  56 +<style lang="scss" scoped>
  57 +.upload-btn {
  58 + display: inline-block;
  59 + margin: 0 10px;
  60 +}
  61 +</style>
0 62 \ No newline at end of file
... ...
src/components/NCC-userBox/index.vue 0 → 100644
  1 +<template>
  2 + <el-dialog :title="'选择'+title" :close-on-click-modal="false" :visible.sync="visible"
  3 + class="NCC-dialog NCC-dialog_center NCC-dialog-tree" lock-scroll append-to-body
  4 + width='450px'>
  5 + <el-input placeholder="输入姓名或者编号进行过滤" v-model="keyword" clearable @keyup.enter.native="getList">
  6 + <el-button slot="append" icon="el-icon-search" @click="getList"></el-button>
  7 + </el-input>
  8 + <el-tree :data="treeData" :props="props" highlight-current :expand-on-click-node="false"
  9 + check-on-click-node @node-click="handleNodeClick" class="NCC-common-el-tree" node-key="id"
  10 + v-loading="loading" lazy :load="loadNode">
  11 + <span class="custom-tree-node" slot-scope="{ node, data }">
  12 + <i :class="data.icon"></i>
  13 + <span class="text">{{node.label}}</span>
  14 + </span>
  15 + </el-tree>
  16 + <span slot="footer" class="dialog-footer">
  17 + <el-button @click="visible = false">{{$t('common.cancelButton')}}</el-button>
  18 + <el-button type="primary" @click="dataFormSubmit()">{{$t('common.confirmButton')}}</el-button>
  19 + </span>
  20 + </el-dialog>
  21 +</template>
  22 +
  23 +<script>
  24 +import { getImUserSelector } from '@/api/permission/user'
  25 +export default {
  26 + name: 'UserBox',
  27 + props: {
  28 + title: {
  29 + type: String,
  30 + default: '审批人'
  31 + }
  32 + },
  33 + data() {
  34 + return {
  35 + visible: false,
  36 + id: '',
  37 + nodeId: '0',
  38 + props: {
  39 + children: 'children',
  40 + label: 'fullName',
  41 + value: 'id',
  42 + isLeaf: 'isLeaf'
  43 + },
  44 + treeData: [],
  45 + loading: false,
  46 + keyword: ''
  47 + }
  48 + },
  49 + methods: {
  50 + init() {
  51 + this.visible = true
  52 + this.keyword = ''
  53 + this.nodeId = '0'
  54 + this.getList()
  55 + },
  56 + getList() {
  57 + this.loading = true
  58 + if (this.keyword) this.nodeId = '0'
  59 + getImUserSelector(this.nodeId, this.keyword).then(res => {
  60 + this.treeData = res.data.list
  61 + this.loading = false
  62 + })
  63 + },
  64 + loadNode(node, resolve) {
  65 + if (node.level === 0) {
  66 + this.nodeId = '0'
  67 + return resolve(this.treeData)
  68 + }
  69 + this.nodeId = node.data.id
  70 + getImUserSelector(this.nodeId).then(res => {
  71 + resolve(res.data.list)
  72 + })
  73 + },
  74 + handleNodeClick(data) {
  75 + if (data.type !== 'user') return
  76 + this.id = data.id
  77 + },
  78 + dataFormSubmit() {
  79 + if (!this.id) {
  80 + this.$message({
  81 + message: `请选择${this.title}`,
  82 + type: 'error',
  83 + duration: 1000,
  84 + })
  85 + return
  86 + }
  87 + this.visible = false
  88 + this.$emit('submit', this.id)
  89 + },
  90 + }
  91 +}
  92 +</script>
0 93 \ No newline at end of file
... ...
src/components/NCC-userSelect/index.vue 0 → 100644
  1 +<template>
  2 + <div class="userSelect-container">
  3 + <div class="userSelect-input" @click="openDialog">
  4 + <el-input :placeholder="placeholder" v-model="innerValue" readonly :disabled="disabled">
  5 + <i slot="suffix" class="el-input__icon el-icon-circle-close" @click.stop="clear"
  6 + v-if="clearable"></i>
  7 + <i slot="suffix" class="el-input__icon el-icon-arrow-down"
  8 + :class="{'clearable':clearable}"></i>
  9 + </el-input>
  10 + </div>
  11 + <el-dialog title="选择用户" :close-on-click-modal="false" :visible.sync="visible"
  12 + class="NCC-dialog NCC-dialog_center transfer-dialog" lock-scroll append-to-body
  13 + width="800px" :modal-append-to-body="false">
  14 + <div class="transfer__body" :element-loading-text="$t('common.loadingText')">
  15 + <div class="transfer-pane">
  16 + <div class="transfer-pane__tools">
  17 + <el-input placeholder="输入关键词进行搜索" v-model="keyword" @keyup.enter.native="getList">
  18 + <el-button slot="append" icon="el-icon-search" @click="getList"></el-button>
  19 + </el-input>
  20 + </div>
  21 + <div class="transfer-pane__body">
  22 + <el-tree :data="treeData" :props="props" highlight-current :expand-on-click-node="false"
  23 + check-on-click-node @node-click="handleNodeClick" class="NCC-common-el-tree"
  24 + node-key="id" v-loading="loading" lazy :load="loadNode">
  25 + <span class="custom-tree-node" slot-scope="{ node, data }">
  26 + <i :class="data.icon"></i>
  27 + <span class="text">{{node.label}}</span>
  28 + </span>
  29 + </el-tree>
  30 + </div>
  31 + </div>
  32 + <div class="transfer-pane">
  33 + <div class="transfer-pane__tools">
  34 + <span>已选</span>
  35 + <el-button @click="removeAll" type="text">清空列表</el-button>
  36 + </div>
  37 + <div class="transfer-pane__body shadow right-pane">
  38 + <template>
  39 + <div v-for="(item, index) in selectedData" :key=" index" class="selected-item">
  40 + <span>{{ item.fullName}}</span>
  41 + <i class="el-icon-delete" @click="removeData(index)"></i>
  42 + </div>
  43 + </template>
  44 + </div>
  45 + </div>
  46 + </div>
  47 + <span slot="footer" class="dialog-footer">
  48 + <el-button @click="visible=false">{{$t('common.cancelButton')}}</el-button>
  49 + <el-button type="primary" @click="confirm">{{$t('common.confirmButton')}}</el-button>
  50 + </span>
  51 + </el-dialog>
  52 + </div>
  53 +</template>
  54 +
  55 +<script>
  56 +import { getImUserSelector, getUserInfoList } from '@/api/permission/user'
  57 +export default {
  58 + name: 'userSelect',
  59 + props: {
  60 + value: {
  61 + default: ''
  62 + },
  63 + interfaceId: {
  64 + type: String,
  65 + default: ''
  66 + },
  67 + placeholder: {
  68 + type: String,
  69 + default: '请选择'
  70 + },
  71 + disabled: {
  72 + type: Boolean,
  73 + default: false
  74 + },
  75 + multiple: {
  76 + type: Boolean,
  77 + default: false
  78 + },
  79 + clearable: {
  80 + type: Boolean,
  81 + default: true
  82 + }
  83 + },
  84 + data() {
  85 + return {
  86 + visible: false,
  87 + keyword: '',
  88 + nodeId: '',
  89 + innerValue: '',
  90 + loading: false,
  91 + props: {
  92 + children: 'children',
  93 + label: 'fullName',
  94 + isLeaf: 'isLeaf'
  95 + },
  96 + treeData: [],
  97 + selectedData: [],
  98 + }
  99 + },
  100 + watch: {
  101 + value(val) {
  102 + this.setDefault()
  103 + }
  104 + },
  105 + created() {
  106 + this.setDefault()
  107 + },
  108 + methods: {
  109 + clear() {
  110 + if (this.disabled) return
  111 + this.innerValue = ''
  112 + this.selectedData = []
  113 + this.$emit('input', '')
  114 + this.$emit('change', '', '')
  115 + },
  116 + openDialog() {
  117 + if (this.disabled) return
  118 + this.visible = true
  119 + this.keyword = ''
  120 + this.nodeId = '0'
  121 + this.getList()
  122 + },
  123 + confirm() {
  124 + let txt = '', ids = ''
  125 + for (let i = 0; i < this.selectedData.length; i++) {
  126 + txt += (i ? ',' : '') + this.selectedData[i].fullName
  127 + ids += (i ? ',' : '') + this.selectedData[i].id
  128 + }
  129 + this.innerValue = txt
  130 + this.$emit('input', ids)
  131 + this.$emit('change', ids, this.selectedData)
  132 + this.visible = false
  133 + },
  134 + setDefault() {
  135 + this.selectedData = []
  136 + if (!this.value) return this.innerValue = ''
  137 + const arr = this.value.split(',')
  138 + getUserInfoList(arr).then(res => {
  139 + const list = res.data.list
  140 + this.selectedData = list
  141 + let txt = ''
  142 + for (let i = 0; i < list.length; i++) {
  143 + txt += (i ? ',' : '') + list[i].fullName
  144 + }
  145 + this.innerValue = txt
  146 + })
  147 + },
  148 + getList() {
  149 + this.loading = true
  150 + if (this.keyword) this.nodeId = '0'
  151 + getImUserSelector(this.nodeId, this.keyword).then(res => {
  152 + this.treeData = res.data.list
  153 + this.loading = false
  154 + })
  155 + },
  156 + loadNode(node, resolve) {
  157 + if (node.level === 0) {
  158 + this.nodeId = '0'
  159 + return resolve(this.treeData)
  160 + }
  161 + this.nodeId = node.data.id
  162 + getImUserSelector(this.nodeId).then(res => {
  163 + resolve(res.data.list)
  164 + })
  165 + },
  166 + handleNodeClick(data) {
  167 + if (data.type !== 'user') return
  168 + const boo = this.selectedData.some(o => o.id === data.id)
  169 + if (boo) return
  170 + const item = {
  171 + id: data.id,
  172 + fullName: data.fullName
  173 + }
  174 + this.multiple ? this.selectedData.push(item) : this.selectedData = [item]
  175 + },
  176 + removeAll() {
  177 + this.selectedData = []
  178 + },
  179 + removeData(index) {
  180 + this.selectedData.splice(index, 1)
  181 + },
  182 + },
  183 +}
  184 +</script>
  185 +<style lang="scss" scoped>
  186 +.userSelect-container {
  187 + width: 100%;
  188 + .userSelect-input {
  189 + width: 100%;
  190 + cursor: pointer;
  191 + .is-disabled {
  192 + &:hover {
  193 + .el-icon-circle-close {
  194 + display: none;
  195 + }
  196 + .el-icon-arrow-down {
  197 + display: inline-block;
  198 + }
  199 + }
  200 + >>> input {
  201 + cursor: not-allowed;
  202 + }
  203 + }
  204 + >>> input {
  205 + cursor: pointer;
  206 + }
  207 + .el-icon-circle-close {
  208 + display: none;
  209 + }
  210 + &:hover {
  211 + .el-icon-circle-close {
  212 + display: block;
  213 + }
  214 + .el-icon-arrow-down.clearable {
  215 + display: none;
  216 + }
  217 + }
  218 + }
  219 +}
  220 +</style>
0 221 \ No newline at end of file
... ...
src/components/NCCEditor/quill.vue
1 1 <template>
2 2 <div :class="prefixCls">
3   - <quill-editor v-model="content" ref="myQuillEditor" :content="value" :options="editorOption"
  3 + <quill-editor v-model="content" ref="myQuillEditor" :content="value" :options="editorOption" :disabled="disabled"
4 4 @blur="onEditorBlur($event)" @focus="onEditorFocus($event)" @ready="onEditorReady($event)"
5 5 @change="onEditorChange($event)">
6 6 </quill-editor>
... ... @@ -29,6 +29,10 @@ export default {
29 29 placeholder: {
30 30 type: String,
31 31 default: '请输入内容...'
  32 + },
  33 + disabled: {
  34 + type: Boolean,
  35 + default: false
32 36 }
33 37 },
34 38 data() {
... ... @@ -47,11 +51,11 @@ export default {
47 51 [{ align: [] }],
48 52 [{ direction: "rtl" }],
49 53 ["clean"],
50   - ["link", "image"],
  54 + // ["link", "image"],
51 55 ]
52 56 },
53 57 theme: 'snow',
54   - placeholder: this.placeholder
  58 + placeholder: this.placeholder,
55 59 }
56 60 }
57 61 },
... ...
src/components/SizeSelect/index.vue 0 → 100644
  1 +<template>
  2 + <el-dropdown trigger="click" @command="handleSetSize">
  3 + <div>
  4 + <i class="ym-custom ym-custom-format-size" />
  5 + </div>
  6 + <el-dropdown-menu slot="dropdown">
  7 + <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value"
  8 + :command="item.value">
  9 + {{
  10 + item.label }}
  11 + </el-dropdown-item>
  12 + </el-dropdown-menu>
  13 + </el-dropdown>
  14 +</template>
  15 +
  16 +<script>
  17 +export default {
  18 + data() {
  19 + return {
  20 + sizeOptions: [
  21 + { label: 'Default', value: 'default' },
  22 + { label: 'Medium', value: 'medium' },
  23 + { label: 'Small', value: 'small' },
  24 + { label: 'Mini', value: 'mini' }
  25 + ]
  26 + }
  27 + },
  28 + computed: {
  29 + size() {
  30 + return this.$store.getters.size
  31 + }
  32 + },
  33 + methods: {
  34 + handleSetSize(size) {
  35 + this.$ELEMENT.size = size
  36 + this.$store.dispatch('app/setSize', size)
  37 + this.refreshView()
  38 + this.$message({
  39 + message: 'Switch Size Success',
  40 + type: 'success'
  41 + })
  42 + },
  43 + refreshView() {
  44 + // In order to make the cached page re-rendered
  45 + this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
  46 +
  47 + const { fullPath } = this.$route
  48 +
  49 + this.$nextTick(() => {
  50 + this.$router.replace({
  51 + path: '/redirect' + fullPath
  52 + })
  53 + })
  54 + }
  55 + }
  56 +
  57 +}
  58 +</script>
... ...
src/components/VisualPortal/CommonFunc/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="commonFunc-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="commonFunc-box-body">
  7 + <router-link class="item" :to="'/'+item.urlAddress" v-for="(item,i) in menuList" :key="i">
  8 + <i :class="item.icon" :style="{color:item.iconBackgroundColor||'#1890FF'}"></i>
  9 + <p class="name">{{item.fullName}}</p>
  10 + </router-link>
  11 + </div>
  12 + </el-card>
  13 +</template>
  14 +<script>
  15 +export default {
  16 + props: {
  17 + title: { type: String, default: '' },
  18 + list: { type: Array, default: () => [] }
  19 + },
  20 + data() {
  21 + return {
  22 + menuList: []
  23 + }
  24 + },
  25 + created() {
  26 + this.menuList = this.list.filter(o => o.id)
  27 + },
  28 + watch: {
  29 + list: {
  30 + handler(val) {
  31 + this.menuList = val.filter(o => o.id)
  32 + },
  33 + deep: true
  34 + }
  35 + }
  36 +}
  37 +</script>
  38 +<style lang="scss" scoped>
  39 +.commonFunc-box {
  40 + >>> .el-card__body {
  41 + width: 100%;
  42 + height: calc(100% - 55px);
  43 + }
  44 + .commonFunc-box-body {
  45 + padding: 0 30px;
  46 + height: 100%;
  47 + display: flex;
  48 + justify-content: space-between;
  49 + align-items: center;
  50 + .item {
  51 + display: block;
  52 + text-align: center;
  53 + i {
  54 + display: inline-block;
  55 + height: 40px;
  56 + font-size: 40px;
  57 + margin-bottom: 10px;
  58 + }
  59 + .name {
  60 + font-size: 14px;
  61 + line-height: 20px;
  62 + }
  63 + }
  64 + }
  65 +}
  66 +</style>
0 67 \ No newline at end of file
... ...
src/components/VisualPortal/DataBoard/index.vue 0 → 100644
  1 +<template>
  2 + <el-row :gutter="10" class="dataBoard">
  3 + <el-col :span="6" class="dataBoard-item" v-for="(item,i) in menuList" :key="i">
  4 + <el-card shadow="never">
  5 + <div class="dataBoard-body">
  6 + <i :class="item.icon+' dataBoard-body-item dataBoard-body-item'+(i+1)"></i>
  7 + <div class="text">
  8 + <p class="num">{{item.num}}</p>
  9 + <p class="name">{{item.fullName}}</p>
  10 + </div>
  11 + </div>
  12 + </el-card>
  13 + </el-col>
  14 + </el-row>
  15 +</template>
  16 +<script>
  17 +import { getCountData } from '@/api/home'
  18 +export default {
  19 + props: {
  20 + title: { type: String, default: '' },
  21 + // list: { type: Array, default: () => [] }
  22 + },
  23 + data() {
  24 + return {
  25 + menuList: []
  26 + }
  27 + },
  28 + created() {
  29 + this.getData()
  30 + },
  31 + methods: {
  32 + getData() {
  33 + getCountData().then(res => {
  34 + this.menuList = res.data.list
  35 + })
  36 + }
  37 + },
  38 + watch: {
  39 + list: {
  40 + handler(val) {
  41 + this.menuList = val
  42 + },
  43 + deep: true
  44 + }
  45 + }
  46 +}
  47 +</script>
  48 +<style lang="scss" scoped>
  49 +.dataBoard {
  50 + height: 100%;
  51 + overflow: hidden;
  52 + .dataBoard-item {
  53 + height: 100%;
  54 + }
  55 + >>> .el-card {
  56 + width: 100%;
  57 + height: 100%;
  58 + .el-card__body {
  59 + padding: 0;
  60 + height: 100%;
  61 + }
  62 + }
  63 + .dataBoard-body {
  64 + padding-left: 30px;
  65 + height: 100%;
  66 + display: flex;
  67 + align-items: center;
  68 + .dataBoard-body-item {
  69 + width: 66px;
  70 + height: 66px;
  71 + margin-right: 16px;
  72 + border-radius: 50%;
  73 + text-align: center;
  74 + line-height: 66px;
  75 + font-size: 36px;
  76 + flex-shrink: 0;
  77 + &.dataBoard-body-item1 {
  78 + background: #f2ebfb;
  79 + color: #7b1ae1;
  80 + }
  81 + &.dataBoard-body-item2 {
  82 + background: #edf8fe;
  83 + color: #4ab8ff;
  84 + }
  85 + &.dataBoard-body-item3 {
  86 + background: #fff7e4;
  87 + color: #ff8b58;
  88 + }
  89 + &.dataBoard-body-item4 {
  90 + background: #fff2f5;
  91 + color: #fc5b87;
  92 + }
  93 + }
  94 + .text {
  95 + display: inline-block;
  96 + height: 56px;
  97 + .num {
  98 + font-size: 20px;
  99 + line-height: 36px;
  100 + }
  101 + .name {
  102 + font-size: 14px;
  103 + color: #666;
  104 + }
  105 + }
  106 + }
  107 +}
  108 +</style>
0 109 \ No newline at end of file
... ...
src/components/VisualPortal/HAnnularChart/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-eChart-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="eChart-box-body">
  7 + <div ref="chart" id="chart" v-show="!isEmpty"></div>
  8 + <div class="portal-common-noData portal-common-noData-eChart" v-show="isEmpty">
  9 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  10 + <p class="noData-txt">暂无数据</p>
  11 + </div>
  12 + </div>
  13 + </el-card>
  14 +</template>
  15 +<script>
  16 +import echartMixin from '@/components/VisualPortal/mixins'
  17 +export default {
  18 + mixins: [echartMixin]
  19 +}
  20 +</script>
... ...
src/components/VisualPortal/HAreaChart/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-eChart-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="eChart-box-body">
  7 + <div ref="chart" id="chart" v-show="!isEmpty"></div>
  8 + <div class="portal-common-noData portal-common-noData-eChart" v-show="isEmpty">
  9 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  10 + <p class="noData-txt">暂无数据</p>
  11 + </div>
  12 + </div>
  13 + </el-card>
  14 +</template>
  15 +<script>
  16 +import echartMixin from '@/components/VisualPortal/mixins'
  17 +export default {
  18 + mixins: [echartMixin]
  19 +}
  20 +</script>
... ...
src/components/VisualPortal/HBarChart/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-eChart-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="eChart-box-body">
  7 + <div ref="chart" id="chart" v-show="!isEmpty"></div>
  8 + <div class="portal-common-noData portal-common-noData-eChart" v-show="isEmpty">
  9 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  10 + <p class="noData-txt">暂无数据</p>
  11 + </div>
  12 + </div>
  13 + </el-card>
  14 +</template>
  15 +<script>
  16 +import { getProjectMonthLine } from "@/api/home";
  17 +import echarts from 'echarts'
  18 +import resize from '@/components/Charts/mixins/resize'
  19 +export default {
  20 + mixins: [resize],
  21 + props: {
  22 + title: { type: String, default: '' },
  23 + option: { type: Object, default: () => { } }
  24 + },
  25 + data() {
  26 + return {
  27 + chart: null,
  28 + currOption: {},
  29 + isEmpty: false
  30 + }
  31 + },
  32 + created() {
  33 + this.getData();
  34 + },
  35 + methods: {
  36 + getData() {
  37 + getProjectMonthLine().then((res) => {
  38 + window.console.log("柱状图:" + res.data.chartdata);
  39 + this.currOption = res.data.chartdata;
  40 + this.initChart();
  41 + });
  42 + },
  43 + initChart() {
  44 + this.chart = echarts.init(this.$refs.chart);
  45 + this.chart.setOption(this.currOption);
  46 + setTimeout(() => {
  47 + this.$nextTick(() => {
  48 + this.chart.resize();
  49 + });
  50 + }, 50);
  51 + },
  52 + },
  53 + beforeDestroy() {
  54 + if (!this.chart) return;
  55 + this.chart.dispose();
  56 + this.chart = null;
  57 + },
  58 +}
  59 +</script>
0 60 \ No newline at end of file
... ...
src/components/VisualPortal/HEmail/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-todoList-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="portal-todoList-box-body">
  7 + <template v-if="list.length">
  8 + <router-link class="item" to="/extend/email" v-for="(item, i) in list" :key="i">
  9 + <span class="name">{{item.fullName}}</span>
  10 + <span class="time">{{item.creatorTime | toDateText()}}</span>
  11 + </router-link>
  12 + </template>
  13 + <div class="portal-common-noData" v-else>
  14 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  15 + <p class="noData-txt">暂无数据</p>
  16 + </div>
  17 + </div>
  18 + </el-card>
  19 +</template>
  20 +<script>
  21 +import { getEmail } from '@/api/home'
  22 +export default {
  23 + props: {
  24 + title: { type: String, default: '' }
  25 + },
  26 + data() {
  27 + return {
  28 + list: []
  29 + }
  30 + },
  31 + created() {
  32 + this.getData()
  33 + },
  34 + methods: {
  35 + getData() {
  36 + getEmail().then(res => {
  37 + this.list = res.data.list.slice(0, 7)
  38 + })
  39 + }
  40 + }
  41 +}
  42 +</script>
0 43 \ No newline at end of file
... ...
src/components/VisualPortal/HLineChart/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-eChart-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="eChart-box-body">
  7 + <div ref="chart" id="chart" v-show="!isEmpty"></div>
  8 + <div class="portal-common-noData portal-common-noData-eChart" v-show="isEmpty">
  9 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  10 + <p class="noData-txt">暂无数据</p>
  11 + </div>
  12 + </div>
  13 + </el-card>
  14 +</template>
  15 +<script>
  16 +import echartMixin from '@/components/VisualPortal/mixins'
  17 +export default {
  18 + mixins: [echartMixin]
  19 +}
  20 +</script>
0 21 \ No newline at end of file
... ...
src/components/VisualPortal/HNotice/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-todoList-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="portal-todoList-box-body">
  7 + <template v-if="list.length">
  8 + <router-link class="item" to="/messageRecord" v-for="(item, i) in list" :key="i">
  9 + <span class="name">{{item.fullName}}</span>
  10 + <span class="time">{{item.creatorTime | toDate('yyyy-MM-dd')}}</span>
  11 + </router-link>
  12 + </template>
  13 + <div class="portal-common-noData" v-else>
  14 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  15 + <p class="noData-txt">暂无数据</p>
  16 + </div>
  17 + </div>
  18 + </el-card>
  19 +</template>
  20 +<script>
  21 +import { getArticle } from '@/api/home'
  22 +export default {
  23 + props: {
  24 + title: { type: String, default: '' }
  25 + },
  26 + data() {
  27 + return {
  28 + list: []
  29 + }
  30 + },
  31 + created() {
  32 + this.getData()
  33 + },
  34 + methods: {
  35 + getData() {
  36 + getArticle().then(res => {
  37 + this.list = res.data.list.slice(0, 7)
  38 + })
  39 + }
  40 + }
  41 +}
  42 +</script>
0 43 \ No newline at end of file
... ...
src/components/VisualPortal/HPieChart/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-eChart-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="eChart-box-body">
  7 + <div ref="chart" id="chart" v-show="!isEmpty"></div>
  8 + <div class="portal-common-noData portal-common-noData-eChart" v-show="isEmpty">
  9 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  10 + <p class="noData-txt">暂无数据</p>
  11 + </div>
  12 + </div>
  13 + </el-card>
  14 +</template>
  15 +<script>
  16 +import echartMixin from '@/components/VisualPortal/mixins'
  17 +export default {
  18 + mixins: [echartMixin]
  19 +}
  20 +</script>
0 21 \ No newline at end of file
... ...
src/components/VisualPortal/HRadarChart/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-eChart-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="eChart-box-body">
  7 + <div ref="chart" id="chart" v-show="!isEmpty"></div>
  8 + <div class="portal-common-noData portal-common-noData-eChart" v-show="isEmpty">
  9 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  10 + <p class="noData-txt">暂无数据</p>
  11 + </div>
  12 + </div>
  13 + </el-card>
  14 +</template>
  15 +<script>
  16 +import echartMixin from '@/components/VisualPortal/mixins'
  17 +export default {
  18 + mixins: [echartMixin]
  19 +}
  20 +</script>
0 21 \ No newline at end of file
... ...
src/components/VisualPortal/Layout/index.vue 0 → 100644
  1 +<template>
  2 + <el-scrollbar class="layout-area">
  3 + <template v-if="layout.length">
  4 + <grid-layout :layout.sync="layout" :row-height="40" :is-draggable="false"
  5 + :is-resizable="false">
  6 + <grid-item v-for="item in layout" :x="item.x" :y="item.y" :w="item.w" :h="item.h"
  7 + :i="item.i" :key="item.i" static>
  8 + <Todo v-if="item.nccKey==='todo'" :title="item.title" />
  9 + <CommonFunc v-if="item.nccKey==='commonFunc'" :title="item.title" :list="item.list" />
  10 + <TodoList v-if="item.nccKey==='todoList'" :title="item.title" />
  11 + <HNotice v-if="item.nccKey==='notice'" :title="item.title" />
  12 + <HEmail v-if="item.nccKey==='email'" :title="item.title" />
  13 + <DataBoard v-if="item.nccKey==='dataBoard'" :title="item.title" :list="item.list" />
  14 + <HBarChart v-if="item.nccKey==='barChart'" :title="item.title" :option="item.option"
  15 + :dataType="item.dataType" :propsApi="item.propsApi" />
  16 + <HAnnularChart v-if="item.nccKey==='annularChart'" :title="item.title"
  17 + :option=" item.option" :dataType="item.dataType" :propsApi="item.propsApi" />
  18 + <HAreaChart v-if="item.nccKey==='areaChart'" :title="item.title" :option="item.option"
  19 + :dataType="item.dataType" :propsApi="item.propsApi" />
  20 + <HLineChart v-if="item.nccKey==='lineChart'" :title="item.title" :option="item.option"
  21 + :dataType="item.dataType" :propsApi="item.propsApi" />
  22 + <HPieChart v-if="item.nccKey==='pieChart'" :title="item.title" :option="item.option"
  23 + :dataType="item.dataType" :propsApi="item.propsApi" />
  24 + <HRadarChart v-if="item.nccKey==='radarChart'" :title="item.title" :option="item.option"
  25 + :dataType="item.dataType" :propsApi="item.propsApi" />
  26 + <div class="mask" v-if="mask"></div>
  27 + </grid-item>
  28 + </grid-layout>
  29 + </template>
  30 + <div class="portal-layout-nodata" v-else>
  31 + <img src="@/assets/images/dashboard-nodata.png" alt="" class="layout-nodata-img">
  32 + <p class="layout-nodata-txt">暂无数据</p>
  33 + </div>
  34 + </el-scrollbar>
  35 +</template>
  36 +
  37 +<script>
  38 +import {
  39 + Todo,
  40 + CommonFunc,
  41 + TodoList,
  42 + HNotice,
  43 + HEmail,
  44 + DataBoard,
  45 + HBarChart,
  46 + HAnnularChart,
  47 + HAreaChart,
  48 + HLineChart,
  49 + HPieChart,
  50 + HRadarChart
  51 +} from "@/components/VisualPortal"
  52 +import VueGridLayout from 'vue-grid-layout'
  53 +export default {
  54 + props: {
  55 + layout: { type: Array, default: () => [] },
  56 + mask: { type: Boolean, default: false },
  57 + },
  58 + components: {
  59 + GridLayout: VueGridLayout.GridLayout,
  60 + GridItem: VueGridLayout.GridItem,
  61 + Todo,
  62 + CommonFunc,
  63 + TodoList,
  64 + HNotice,
  65 + HEmail,
  66 + DataBoard,
  67 + HBarChart,
  68 + HAnnularChart,
  69 + HAreaChart,
  70 + HLineChart,
  71 + HPieChart,
  72 + HRadarChart
  73 + },
  74 +}
  75 +</script>
  76 +<style lang="scss" scoped>
  77 +.layout-area {
  78 + height: 100%;
  79 + overflow: hidden;
  80 + >>> .el-scrollbar__wrap {
  81 + margin-bottom: 0 !important;
  82 + overflow-x: auto;
  83 + }
  84 + >>> .el-scrollbar__bar.is-horizontal > div {
  85 + display: none;
  86 + }
  87 + >>> .el-card {
  88 + width: 100%;
  89 + height: 100%;
  90 + .el-card__body {
  91 + padding: 0;
  92 + }
  93 + }
  94 + .vue-grid-item {
  95 + position: relative;
  96 + .mask {
  97 + position: absolute;
  98 + top: 0;
  99 + left: 0;
  100 + width: 100%;
  101 + height: 100%;
  102 + z-index: 1;
  103 + }
  104 + }
  105 +}
  106 +</style>
0 107 \ No newline at end of file
... ...
src/components/VisualPortal/Todo/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="todo-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="todo-box-body">
  7 + <router-link class="item" to="/workFlow/flowTodo">
  8 + <i class="icon-ym icon-ym-flowTodo"></i>
  9 + <div class="text">
  10 + <p class="num">{{toBeReviewed}}</p>
  11 + <p class="name">待我审核</p>
  12 + </div>
  13 + </router-link>
  14 + <router-link class="item" to="/workFlow/entrust">
  15 + <i class="icon-ym icon-ym-flowEntrust"></i>
  16 + <div class="text">
  17 + <p class="num">{{entrust}}</p>
  18 + <p class="name">流程委托</p>
  19 + </div>
  20 + </router-link>
  21 + <router-link class="item" to="/workFlow/flowDone">
  22 + <i class="icon-ym icon-ym-flowDone"></i>
  23 + <div class="text">
  24 + <p class="num">{{flowDone}}</p>
  25 + <p class="name">已办事宜</p>
  26 + </div>
  27 + </router-link>
  28 + </div>
  29 + </el-card>
  30 +</template>
  31 +<script>
  32 +import { getFlowTodoCount } from '@/api/home'
  33 +export default {
  34 + props: {
  35 + title: { type: String, default: '' }
  36 + },
  37 + data() {
  38 + return {
  39 + entrust: 0,
  40 + flowDone: 0,
  41 + toBeReviewed: 0
  42 + }
  43 + },
  44 + created() {
  45 + this.getData()
  46 + },
  47 + methods: {
  48 + getData() {
  49 + getFlowTodoCount().then(res => {
  50 + this.entrust = res.data.entrust || 0
  51 + this.flowDone = res.data.flowDone || 0
  52 + this.toBeReviewed = res.data.toBeReviewed || 0
  53 + })
  54 + }
  55 + }
  56 +}
  57 +</script>
  58 +<style lang="scss" scoped>
  59 +.todo-box {
  60 + >>> .el-card__body {
  61 + width: 100%;
  62 + height: calc(100% - 55px);
  63 + }
  64 + .todo-box-body {
  65 + padding: 0 30px;
  66 + height: 100%;
  67 + display: flex;
  68 + justify-content: space-between;
  69 + align-items: center;
  70 + .item {
  71 + height: 56px;
  72 + display: block;
  73 + i {
  74 + width: 56px;
  75 + height: 56px;
  76 + margin-right: 14px;
  77 + border-radius: 50%;
  78 + color: #fff;
  79 + display: inline-block;
  80 + vertical-align: top;
  81 + text-align: center;
  82 + line-height: 56px;
  83 + font-size: 30px;
  84 + &.icon-ym-flowTodo {
  85 + background: #f68900;
  86 + }
  87 + &.icon-ym-flowEntrust {
  88 + background: #1890ff;
  89 + }
  90 + &.icon-ym-flowDone {
  91 + background: #7b1ae1;
  92 + }
  93 + }
  94 + .text {
  95 + display: inline-block;
  96 + height: 56px;
  97 + .num {
  98 + font-size: 20px;
  99 + line-height: 36px;
  100 + }
  101 + .name {
  102 + font-size: 14px;
  103 + color: #666;
  104 + }
  105 + }
  106 + }
  107 + }
  108 +}
  109 +</style>
0 110 \ No newline at end of file
... ...
src/components/VisualPortal/TodoList/index.vue 0 → 100644
  1 +<template>
  2 + <el-card shadow="never" class="portal-todoList-box">
  3 + <div slot="header" class="portal-common-title">
  4 + <span>{{title}}</span>
  5 + </div>
  6 + <div class="portal-todoList-box-body">
  7 + <template v-if="list.length">
  8 + <router-link class="item" to="/workFlow/flowTodo" v-for="(item, i) in list" :key="i">
  9 + <span class="name">{{item.fullName}}</span>
  10 + <span class="time">{{item.creatorTime | toDate('yyyy-MM-dd')}}</span>
  11 + </router-link>
  12 + </template>
  13 + <div class="portal-common-noData" v-else>
  14 + <img src="@/assets/images/portal-nodata.png" alt="" class="noData-img">
  15 + <p class="noData-txt">暂无数据</p>
  16 + </div>
  17 + </div>
  18 + </el-card>
  19 +</template>
  20 +<script>
  21 +import { getFlowTodo } from '@/api/home'
  22 +export default {
  23 + props: {
  24 + title: { type: String, default: '' }
  25 + },
  26 + data() {
  27 + return {
  28 + list: []
  29 + }
  30 + },
  31 + created() {
  32 + this.getData()
  33 + },
  34 + methods: {
  35 + getData() {
  36 + getFlowTodo().then(res => {
  37 + this.list = res.data.list.slice(0, 7)
  38 + })
  39 + }
  40 + }
  41 +}
  42 +</script>
0 43 \ No newline at end of file
... ...
src/components/VisualPortal/index.js 0 → 100644
  1 +// 门户设计专供
  2 +export { default as Todo } from '@/components/VisualPortal/Todo'
  3 +export { default as CommonFunc } from '@/components/VisualPortal/CommonFunc'
  4 +export { default as TodoList } from '@/components/VisualPortal/TodoList'
  5 +export { default as HNotice } from '@/components/VisualPortal/HNotice'
  6 +export { default as HEmail } from '@/components/VisualPortal/HEmail'
  7 +export { default as DataBoard } from '@/components/VisualPortal/DataBoard'
  8 +export { default as HBarChart } from '@/components/VisualPortal/HBarChart'
  9 +export { default as HAnnularChart } from '@/components/VisualPortal/HAnnularChart'
  10 +export { default as HAreaChart } from '@/components/VisualPortal/HAreaChart'
  11 +export { default as HLineChart } from '@/components/VisualPortal/HLineChart'
  12 +export { default as HPieChart } from '@/components/VisualPortal/HPieChart'
  13 +export { default as HRadarChart } from '@/components/VisualPortal/HRadarChart'
0 14 \ No newline at end of file
... ...
src/components/VisualPortal/mixins/index.js 0 → 100644
  1 +import echarts from 'echarts'
  2 +import resize from '@/components/Charts/mixins/resize'
  3 +import { previewDataInterface } from '@/api/systemData/dataInterface'
  4 +
  5 +export default {
  6 + mixins: [resize],
  7 + props: {
  8 + title: { type: String, default: '' },
  9 + dataType: { type: String, default: 'static' },
  10 + propsApi: { type: String, default: '' },
  11 + option: { type: Object, default: () => {} }
  12 + },
  13 + data() {
  14 + return {
  15 + chart: null,
  16 + currOption: {},
  17 + isEmpty: false
  18 + }
  19 + },
  20 + mounted() {
  21 + if (this.dataType === 'dynamic') {
  22 + if (!this.propsApi) return
  23 + previewDataInterface(this.propsApi).then(res => {
  24 + this.currOption = res.data
  25 + this.resetChart()
  26 + })
  27 + } else {
  28 + this.currOption = this.option
  29 + this.initChart()
  30 + }
  31 + },
  32 + watch: {
  33 + option: {
  34 + handler(val) {
  35 + this.currOption = val
  36 + this.resetChart()
  37 + },
  38 + deep: true
  39 + },
  40 + dataType(val) {
  41 + if (val !== 'dynamic') {
  42 + this.currOption = this.option
  43 + this.resetChart()
  44 + }
  45 + },
  46 + propsApi(val) {
  47 + if (this.dataType === 'static') return
  48 + if (!val) return
  49 + previewDataInterface(val).then(res => {
  50 + this.currOption = res.data
  51 + this.resetChart()
  52 + })
  53 + }
  54 + },
  55 + methods: {
  56 + initChart() {
  57 + this.chart = echarts.init(this.$refs.chart)
  58 + this.chart.setOption(this.currOption)
  59 + setTimeout(() => {
  60 + this.$nextTick(() => {
  61 + this.chart.resize();
  62 + })
  63 + }, 50);
  64 + },
  65 + resetChart() {
  66 + this.isEmpty = JSON.stringify(this.currOption) === "{}"
  67 + this.chart && this.chart.dispose()
  68 + this.chart = null
  69 + if (!this.isEmpty) this.initChart()
  70 + }
  71 + },
  72 + beforeDestroy() {
  73 + if (!this.chart) return
  74 + this.chart.dispose()
  75 + this.chart = null
  76 + }
  77 +}
0 78 \ No newline at end of file
... ...
src/components/index.js
... ... @@ -12,6 +12,28 @@ import Screenfull from &#39;@/components/Screenfull&#39;
12 12 import NCCTreeSelect from '@/components/NCC-treeSelect'
13 13 import NCCAddress from '@/components/Generator/components/Address'
14 14 import SelsctLoad from '@/components/SelsctLoad'
  15 +import topOperation from '@/components/NCC-topOperation/index'
  16 +import tableOperation from '@/components/NCC-tableOperation'
  17 +import UserBox from '@/components/NCC-userBox'
  18 +import ColumnSettings from '@/components/ColumnSettings'
  19 +import UserSelect from '@/components/NCC-userSelect'
  20 +import uploadBtn from '@/components/NCC-uploadBtn'
  21 +// 代码生成器专供
  22 +import NCCText from '@/components/Generator/components/NCCText'
  23 +import NCCUploadImg from '@/components/Generator/components/Upload/UploadImg'
  24 +import PopupSelect from '@/components/Generator/components/PopupSelect'
  25 +import NumRange from '@/components/Generator/components/NumRange'
  26 +import ComSelect from '@/components/Generator/components/ComSelect'
  27 +import PosSelect from '@/components/Generator/components/PosSelect'
  28 +import DicSelect from '@/components/Generator/components/DicSelect'
  29 +import BillRule from '@/components/Generator/components/BillRule'
  30 +import NCCInputTable from '@/components/Generator/components/InputTable'
  31 +import GroupTitle from '@/components/Generator/components/GroupTitle'
  32 +import RelationForm from '@/components/Generator/components/RelationForm'
  33 +import RelationFormAttr from '@/components/Generator/components/RelationFormAttr'
  34 +import RelationFlow from '@/components/Generator/components/RelationFlow'
  35 +import RelationFlowAttr from '@/components/Generator/components/RelationFlowAttr'
  36 +import Calculate from '@/components/Generator/components/Calculate'
15 37  
16 38 export default {
17 39 install(Vue, options) {
... ... @@ -22,13 +44,34 @@ export default {
22 44 Vue.component('InfoEditRecord', InfoEditRecord)
23 45 Vue.component('Pagination', Pagination)
24 46 Vue.component('NCCTable', NCCTable)
  47 + Vue.component('SelsctLoad', SelsctLoad)
  48 + Vue.component('NCCTreeSelect', NCCTreeSelect)
  49 + Vue.component('topOpts', topOperation)
  50 + Vue.component('tableOpts', tableOperation)
  51 + Vue.component('uploadBtn', uploadBtn)
  52 + Vue.component('UserBox', UserBox)
  53 + Vue.component('NCCText', NCCText)
25 54 Vue.component('NCCUploadFz', NCCUploadFz)
26   - Vue.component('NCCQuill', NCCQuill)
  55 + Vue.component('NCCUploadImg', NCCUploadImg)
  56 + Vue.component('PopupSelect', PopupSelect)
  57 + Vue.component('NumRange', NumRange)
  58 + Vue.component('ComSelect', ComSelect)
27 59 Vue.component('DepSelect', DepSelect)
28   - Vue.component('NCCTreeSelect', NCCTreeSelect)
29   - Vue.component('Screenfull', Screenfull)
  60 + Vue.component('PosSelect', PosSelect)
  61 + Vue.component('UserSelect', UserSelect)
  62 + Vue.component('DicSelect', DicSelect)
  63 + Vue.component('BillRule', BillRule)
  64 + Vue.component('NCCInputTable', NCCInputTable)
30 65 Vue.component('NCCAddress', NCCAddress)
31   - Vue.component('SelsctLoad', SelsctLoad)
  66 + Vue.component('GroupTitle', GroupTitle)
  67 + Vue.component('RelationForm', RelationForm)
  68 + Vue.component('RelationFormAttr', RelationFormAttr)
  69 + Vue.component('RelationFlow', RelationFlow)
  70 + Vue.component('RelationFlowAttr', RelationFlowAttr)
  71 + Vue.component('Calculate', Calculate)
  72 + Vue.component('NCCQuill', NCCQuill)
  73 + Vue.component('Screenfull', Screenfull)
  74 + Vue.component('ColumnSettings', ColumnSettings)
32 75  
33 76 }
34 77 }
35 78 \ No newline at end of file
... ...
src/views/baseSpecialAction/dynamicModel/list/Form.vue
... ... @@ -51,6 +51,7 @@ import Parser from &#39;@/components/Generator/parser/Parser&#39;
51 51 import ParserMixin from '@/components/Generator/parser/mixin'
52 52 import PrintBrowse from '@/components/PrintBrowse'
53 53 import { deepClone } from '@/utils'
  54 +import request from "@/utils/request";
54 55 export default {
55 56 components: { Parser, PrintBrowse },
56 57 mixins: [ParserMixin],
... ... @@ -69,6 +70,7 @@ export default {
69 70 useFormPermission: false,
70 71 printBrowseVisible: false,
71 72 formOperates: [],
  73 + taskId: '',
72 74 }
73 75 },
74 76 methods: {
... ... @@ -79,12 +81,13 @@ export default {
79 81 if (this.isPreview) return this.$message({ message: '功能预览不支持打印', type: 'warning' })
80 82 this.printBrowseVisible = true
81 83 },
82   - init(formConf, modelId, id, isPreview, useFormPermission) {
  84 + init(formConf, modelId, id, isPreview, useFormPermission, taskId) {
83 85 this.formConf = deepClone(JSON.parse(formConf))
84 86 this.modelId = modelId
85 87 this.isPreview = isPreview
86 88 this.useFormPermission = useFormPermission
87 89 this.dataForm.id = id || ''
  90 + this.taskId = taskId
88 91 this.getFormOperates()
89 92 this.loading = true
90 93 this.$nextTick(() => {
... ... @@ -150,20 +153,33 @@ export default {
150 153 const formMethod = this.dataForm.id ? updateModel : createModel
151 154 formMethod(this.modelId, this.dataForm).then(res => {
152 155 console.info(res);
153   - alert(res.data.Id);
  156 + let itemId = !this.dataForm.id ? res.data.Id : '';
154 157 this.$message({
155 158 message: res.msg,
156 159 type: 'success',
157 160 duration: 1500,
158   - onClose: () => {
  161 + onClose: async () => {
159 162 if (callback && typeof callback === "function") callback()
160 163 this.visible = false
161 164 this.btnLoading = false
  165 + !this.dataForm.id && this.toSaveItemid(itemId)
162 166 this.$emit('refreshDataList', true)
163 167 }
164 168 })
165 169 }).catch(() => { this.btnLoading = false })
166 170 },
  171 + async toSaveItemid(itemId) {
  172 + console.log(11);
  173 + let res = await request({
  174 + url: '/Extend/BaseSpecialActionInfo/UpdateState',
  175 + method: "PUT",
  176 + data: {
  177 + id: this.taskId,
  178 + itemId
  179 + },
  180 + })
  181 + return res
  182 + },
167 183 dataFormSubmit() {
168 184 if (this.isPreview) return this.$message({ message: '功能预览不支持数据保存', type: 'warning' })
169 185 this.$refs.dynamicForm && this.$refs.dynamicForm.submitForm()
... ...
src/views/baseSpecialAction/index.vue
... ... @@ -49,10 +49,10 @@
49 49 </el-table-column>
50 50 <el-table-column prop="deadline" label="截止日期" align="left" :formatter="ncc.tableDateFormat" show-overflow-tooltip/>
51 51 <el-table-column prop="creatorUserId" show-overflow-tooltip label="创建用户" align="left"/>
52   - <el-table-column label="操作" fixed="right" width="130">
  52 + <el-table-column label="操作" fixed="right" width="100">
53 53 <template slot-scope="scope">
54   - <el-button type="text" @click="addOrUpdateHandle(scope.row.id)">发布任务</el-button>
55   - <el-button type="text" @click="toDetail(scope.row)" v-if="scope.row.state == '已发布'">行动情况</el-button>
  54 + <el-button type="text" @click="addOrUpdateHandle(scope.row.id)" v-if="scope.row.state == '未发布'">发布任务</el-button>
  55 + <el-button type="text" @click="toDetail(scope.row)" v-else>处理情况</el-button>
56 56 </template>
57 57 </el-table-column>
58 58 </NCC-table>
... ...
src/views/baseSpecialhandle/index.vue
... ... @@ -39,7 +39,8 @@
39 39 show-overflow-tooltip />
40 40 <el-table-column label="操作" fixed="right" width="130">
41 41 <template slot-scope="scope">
42   - <el-button type="text" @click="toFillForm(scope.row)">处理</el-button>
  42 + <el-button type="text" @click="toFillForm(scope.row)">{{scope.row.state == '已填写' ? '修改' : '处理'}}</el-button>
  43 + <!-- <el-button type="text" @click="toFillForm(scope.row)" v-if="scope.row.state == '已填写'">详情</el-button> -->
43 44 </template>
44 45 </el-table-column>
45 46 </NCC-table>
... ... @@ -49,7 +50,7 @@
49 50 </div>
50 51 </div>
51 52 </div>
52   - <NCCForm v-if="dialogVisible" ref="NCCForm" @refresh="refresh" />
  53 + <NCCForm v-if="dialogVisible" ref="NCCForm" @refreshDataList="refresh" />
53 54 <!-- <el-dialog title="专项行动处理" :visible.sync="dialogVisible" fullscreen :modal="false">
54 55 <iframe
55 56 :src="nestedPageUrl"
... ... @@ -86,6 +87,7 @@ export default {
86 87 dialogVisible: false,
87 88 nestedPageUrl: '',
88 89 viewportHeight: 0,
  90 + taskId: '',
89 91 };
90 92 },
91 93 created() {
... ... @@ -135,9 +137,9 @@ export default {
135 137 },
136 138 toFillForm(row) {
137 139 this.dialogVisible = true;
138   - var Itemid= "588014226493146373";//数据id,没有的话就是新增 ,有的话就是修改
139   - Itemid="";
140   - var modelId=row.formId;//关联的表单id
  140 + var Itemid = row.itemId || '';//数据id,没有的话就是新增 ,有的话就是修改
  141 + var modelId = row.formId;//关联的表单id
  142 + var taskId = row.id; // 当前专项行动id
141 143 var isPreview = false;//固定死,值不变
142 144 var useFormPermission = false;//固定死,值不变
143 145 var formData = [];
... ... @@ -147,7 +149,8 @@ export default {
147 149 params:null
148 150 }).then(res => {
149 151 formData = res.data.formData;
150   - this.$refs.NCCForm.init(formData, modelId, Itemid, isPreview, useFormPermission)
  152 + console.log(res, formData, modelId, Itemid, isPreview, useFormPermission);
  153 + this.$refs.NCCForm.init(formData, modelId, Itemid, isPreview, useFormPermission, taskId)
151 154 });
152 155  
153 156 // this.nestedPageUrl = `http://8.130.38.56:8043/old/#/previewModel?isPreview=1&id=${row.originId}`;
... ... @@ -195,6 +198,7 @@ export default {
195 198 this.formVisible = false;
196 199 if (isrRefresh) this.reset();
197 200 },
  201 +
198 202 reset() {
199 203 for (let key in this.query) {
200 204 this.query[key] = undefined;
... ...
src/views/overView/Overview.vue
... ... @@ -3,10 +3,18 @@
3 3 <el-row :gutter="20">
4 4 <el-col :span="16">
5 5 <div class="item-box todo">
6   - <div class="item-title">事件中心</div>
  6 + <div class="item-title">
  7 + <div class="left">任务中心</div>
  8 + <div class="right">
  9 + <!-- <el-button type="success" size="mini" style="margin-right: 10px;" v-if="isSHILevel" @click="announceMsg">发布</el-button> -->
  10 + <el-tooltip effect="dark" content="刷新" placement="top">
  11 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="resetTask"/>
  12 + </el-tooltip>
  13 + </div>
  14 + </div>
7 15 <div class="item-body">
8 16 <template>
9   - <el-table :data="todoTableData" style="width: 100%" stripe>
  17 + <el-table :data="taskList" style="width: 100%" stripe v-loading="taskLoading">
10 18 <el-table-column type="index" width="50"> </el-table-column>
11 19 <el-table-column
12 20 prop="teskName"
... ... @@ -44,6 +52,7 @@
44 52 </template>
45 53 </el-table-column>
46 54 </el-table>
  55 + <pagination :total="taskTotal" :page.sync="taskListQuery.currentPage" :limit.sync="taskListQuery.pageSize" @pagination="getAllTaskList" />
47 56 </template>
48 57 </div>
49 58 </div>
... ... @@ -111,60 +120,74 @@
111 120 <div class="item-box msg">
112 121 <div class="item-title">
113 122 <div class="left">通知公告</div>
114   - <el-tooltip effect="dark" content="刷新" placement="top">
115   - <el-link
116   - icon="icon-ym icon-ym-Refresh NCC-common-head-icon"
117   - :underline="false"
118   - @click="resetMsg()"
119   - />
120   - </el-tooltip>
  123 + <div class="right">
  124 + <el-button type="success" size="mini" style="margin-right: 10px;" v-if="isSHILevel" @click="announceMsg">发布</el-button>
  125 + <el-tooltip effect="dark" content="刷新" placement="top">
  126 + <el-link icon="icon-ym icon-ym-Refresh NCC-common-head-icon" :underline="false" @click="resetMsg"/>
  127 + </el-tooltip>
  128 + </div>
121 129 </div>
122 130 <div class="item-body">
123 131 <template>
124   - <el-table :data="msgList" style="width: 100%" stripe>
125   - <el-table-column
126   - prop="teskName"
127   - label="内容"
128   - show-overflow-tooltip
129   - >
  132 + <el-table :data="msgList" style="width: 100%" stripe v-loading="msgLoading">
  133 + <el-table-column prop="title" label="标题" show-overflow-tooltip>
130 134 </el-table-column>
131   - <el-table-column
132   - prop="teskCode"
133   - label="事件"
134   - show-overflow-tooltip
135   - >
  135 + <el-table-column label="操作" fixed="right" width="80">
  136 + <template slot-scope="scope">
  137 + <el-button type="text" @click="checkDetail(scope.row)">详情</el-button>
  138 + </template>
136 139 </el-table-column>
137 140 </el-table>
  141 + <pagination :total="msgTotal" :page.sync="msgListQuery.currentPage" :limit.sync="msgListQuery.pageSize" @pagination="getAllMsgList" />
138 142 </template>
139 143 </div>
140 144 </div>
141 145 </el-col>
142 146 </el-row>
  147 + <MsgForm v-if="MsgFormVisible" ref="MsgForm" @refreshDataList="msgRefresh"/>
143 148 </div>
144 149 </template>
145 150  
146 151 <script>
147   -import {
148   - todoObj,
149   - tipObj,
150   - areaObj,
151   - chObj,
152   -} from "@/assets/mockdata/demodata.json";
153   -import { computed } from "vue";
  152 +import request from "@/utils/request";
  153 +import MsgForm from "./msgForm.vue"
154 154 export default {
155 155 name: "Overview",
  156 + components: { MsgForm },
156 157 data() {
157 158 return {
158 159 todoTableData: [],
159   - msgList: [],
160 160 tipTableData: [],
161 161 aimList: [],
  162 +
  163 + msgLoading: false,
  164 + msgListQuery: {
  165 + currentPage: 1,
  166 + pageSize: 20,
  167 + },
  168 + msgQuery: {
  169 + keyword: ''
  170 + },
  171 + msgList: [],
  172 + msgTotal: 0,
  173 + MsgFormVisible: false,
  174 +
  175 + taskLoading: false,
  176 + taskListQuery: {
  177 + currentPage: 1,
  178 + pageSize: 20,
  179 + },
  180 + taskQuery: {
  181 + keyword: ''
  182 + },
  183 + taskList: [],
  184 + taskTotal: 0,
  185 + TaskFormVisible: false
162 186 };
163 187 },
164 188 created() {
165   - this.getMsgList();
166   - this.getTipList();
167   - this.getAimList();
  189 + this.getAllMsgList();
  190 + this.getAllTaskList();
168 191 },
169 192 computed: {
170 193 isSHILevel() {
... ... @@ -173,29 +196,90 @@ export default {
173 196 },
174 197 },
175 198 methods: {
176   - getMsgList() {
177   - for (let index = 0; index < 10; index++) {
178   - this.msgList.push(todoObj);
179   - this.todoTableData.push(todoObj);
  199 + // 通知公告
  200 + getAllMsgList() {
  201 + this.msgLoading = true;
  202 + let _query = {
  203 + ...this.msgListQuery,
  204 + ...this.msgQuery,
  205 + };
  206 + let query = {};
  207 + for (let key in _query) {
  208 + if (Array.isArray(_query[key])) {
  209 + query[key] = _query[key].join();
  210 + } else {
  211 + query[key] = _query[key];
  212 + }
180 213 }
  214 + request({
  215 + url: '/SubDev/ZyOaArticle',
  216 + method: "GET",
  217 + params: query,
  218 + }).then((res) => {
  219 + this.msgList = res.data.list;
  220 + this.msgTotal = res.data.pagination.total;
  221 + this.msgLoading = false;
  222 + });
181 223 },
182 224 resetMsg() {
183 225 this.msgList = [];
184   - this.getMsgList();
  226 + this.msgListQuery = {
  227 + currentPage: 1,
  228 + pageSize: 20,
  229 + };
  230 + this.getAllMsgList();
185 231 },
186   - getTipList() {
187   - for (let index = 0; index < 10; index++) {
188   - this.tipTableData.push(tipObj);
189   - }
  232 + msgRefresh(val) {
  233 + this.MsgFormVisible = false;
  234 + val && this.getAllMsgList();
  235 + },
  236 + checkDetail(row) {
  237 + console.log(row);
  238 + this.MsgFormVisible = true;
  239 + this.$nextTick(() => {
  240 + this.$refs.MsgForm.init(row.id, true);
  241 + })
190 242 },
191   - getAimList() {
192   - for (let index = 0; index < 12; index++) {
193   - if (Math.random() > 0.5) {
194   - this.aimList.push(areaObj);
  243 + // 发布消息通知
  244 + announceMsg() {
  245 + this.MsgFormVisible = true;
  246 + this.$nextTick(() => {
  247 + this.$refs.MsgForm.init();
  248 + })
  249 + },
  250 +
  251 + // 任务中心
  252 + getAllTaskList() {
  253 + this.taskLoading = true;
  254 + let _query = {
  255 + ...this.taskListQuery,
  256 + ...this.taskQuery,
  257 + };
  258 + let query = {};
  259 + for (let key in _query) {
  260 + if (Array.isArray(_query[key])) {
  261 + query[key] = _query[key].join();
195 262 } else {
196   - this.aimList.push(chObj);
  263 + query[key] = _query[key];
197 264 }
198 265 }
  266 + request({
  267 + url: '/Extend/BaseTaskCenter/GetListByCurretUser',
  268 + method: "GET",
  269 + params: query,
  270 + }).then((res) => {
  271 + this.taskList = res.data.list;
  272 + this.taskTotal = res.data.pagination.total;
  273 + this.taskLoading = false;
  274 + });
  275 + },
  276 + resetTask() {
  277 + this.taskList = [];
  278 + this.taskListQuery = {
  279 + currentPage: 1,
  280 + pageSize: 20,
  281 + };
  282 + this.getAllTaskList();
199 283 },
200 284 },
201 285 };
... ... @@ -234,7 +318,7 @@ export default {
234 318 }
235 319 }
236 320 .item-body {
237   - height: calc(100% - 68px);
  321 + height: calc(100% - 95px);
238 322 margin: 0 13px;
239 323 // padding-bottom: 28px;
240 324 .el-table {
... ...
src/views/overView/msgForm.vue 0 → 100644
  1 +<template>
  2 + <el-dialog
  3 + :title="isDetail ? '通知详情' : '发布通知'"
  4 + :close-on-click-modal="false"
  5 + :visible.sync="visible"
  6 + class="NCC-dialog NCC-dialog_center msg-dialog"
  7 + lock-scroll
  8 + width="50%"
  9 + >
  10 + <el-row :gutter="15" style="padding-top: 10px">
  11 + <el-form
  12 + ref="elForm"
  13 + :model="dataForm"
  14 + size="small"
  15 + label-width="100px"
  16 + label-position="right"
  17 + :disabled="!!isDetail"
  18 + :rules="rules">
  19 + <el-col :span="24">
  20 + <el-form-item label="通知标题" prop="title">
  21 + <el-input v-model="dataForm.title"></el-input>
  22 + </el-form-item>
  23 + </el-col>
  24 + <el-col :span="24">
  25 + <el-form-item label="内容" prop="bodyContent">
  26 + <NCC-Quill v-model="dataForm.bodyContent" placeholder="请输入内容..." :disabled="!!isDetail" />
  27 + </el-form-item>
  28 + </el-col>
  29 + <el-col :span="24">
  30 + <el-form-item label="图片上传" prop="imgUrl">
  31 + <NCC-UploadFz v-model="dataForm.imgUrl" :fileSize="5" sizeUnit="MB" :limit="9" buttonText="点击上传" />
  32 + </el-form-item>
  33 + </el-col>
  34 + </el-form>
  35 + </el-row>
  36 + <span slot="footer" class="dialog-footer">
  37 + <el-button @click="visible = false">取 消</el-button>
  38 + <el-button type="primary" @click="dataFormSubmit()" v-if="!isDetail">确 定</el-button>
  39 + </span>
  40 + </el-dialog>
  41 + </template>
  42 + <script>
  43 + import request from "@/utils/request";
  44 + export default {
  45 + components: {},
  46 + props: [],
  47 + data() {
  48 + return {
  49 + loading: false,
  50 + visible: false,
  51 + isDetail: false,
  52 + dataForm: {
  53 + id: '',
  54 + title: '',
  55 + bodyContent: '',
  56 + imgUrl: [],
  57 + categoryId: 'cc225c68421644f79037aaf624ccccc0',
  58 + isType: '0',
  59 + },
  60 + rules: {
  61 + title: [
  62 + { required: true, message: '请输入标题', trigger: 'blur' },
  63 + ],
  64 + bodyContent: [
  65 + { required: true, message: '请输入内容', trigger: 'blur' },
  66 + ],
  67 + },
  68 + };
  69 + },
  70 + computed: {},
  71 + watch: {},
  72 + created() {
  73 +
  74 + },
  75 + mounted() {},
  76 + beforeDestroy() {
  77 + },
  78 + methods: {
  79 +
  80 + goBack() {
  81 + this.$emit("refresh");
  82 + },
  83 + init(id, isDetail) {
  84 + this.dataForm.id = id || 0;
  85 + this.visible = true;
  86 + this.isDetail = isDetail || false;
  87 + this.$nextTick(() => {
  88 + this.$refs["elForm"].resetFields();
  89 + if(this.dataForm.id) {
  90 + request({
  91 + url: `/SubDev/ZyOaArticle/${this.dataForm.id}`,
  92 + method: "GET",
  93 + }).then((res) => {
  94 + this.dataForm = res.data;
  95 + console.log(this.dataForm);
  96 + });
  97 + }
  98 + });
  99 + },
  100 + dataFormSubmit() {
  101 + this.$refs['elForm'].validate((valid) => {
  102 + if (valid) {
  103 + request({
  104 + url: `/SubDev/ZyOaArticle`,
  105 + method: 'post',
  106 + data: this.dataForm,
  107 + }).then((res) => {
  108 + this.$message({
  109 + message: res.msg,
  110 + type: 'success',
  111 + duration: 1000,
  112 + onClose: () => {
  113 + this.visible = false, this.$emit('refresh', true)
  114 + }
  115 + })
  116 + })
  117 + }
  118 + })
  119 + },
  120 + },
  121 + };
  122 + </script>
  123 + <style lang="scss" scoped>
  124 + .msg-dialog {
  125 + :deep(.el-dialog__body) {
  126 + max-height: 50vh;
  127 + overflow-y: scroll;
  128 + }
  129 + }
  130 + </style>
  131 +
0 132 \ No newline at end of file
... ...