init
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<tooltip v-if="tips !== ''">
|
||||
<template slot="title">{{ tips }}</template>
|
||||
<avatar :size="avatarSize" :src="src" />
|
||||
</tooltip>
|
||||
<avatar v-else :size="avatarSize" :src="src" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Avatar from 'ant-design-vue/es/avatar'
|
||||
import Tooltip from 'ant-design-vue/es/tooltip'
|
||||
|
||||
export default {
|
||||
name: 'AvatarItem',
|
||||
components: {
|
||||
Avatar,
|
||||
Tooltip
|
||||
},
|
||||
props: {
|
||||
tips: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
size: this.$parent.size
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatarSize () {
|
||||
return ((this.size !== 'mini') && this.size) || 20
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.size' (val) {
|
||||
this.size = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<!--
|
||||
<template>
|
||||
<div :class="[prefixCls]">
|
||||
<ul>
|
||||
<slot></slot>
|
||||
<template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
|
||||
|
||||
<template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
|
||||
<avatar-item :size="size">
|
||||
<avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
|
||||
</avatar-item>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
-->
|
||||
|
||||
<script>
|
||||
import Avatar from 'ant-design-vue/es/avatar'
|
||||
import AvatarItem from './Item'
|
||||
import { filterEmpty } from '@/components/_util/util'
|
||||
|
||||
export default {
|
||||
AvatarItem,
|
||||
name: 'AvatarList',
|
||||
components: {
|
||||
Avatar,
|
||||
AvatarItem
|
||||
},
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-avatar-list'
|
||||
},
|
||||
/**
|
||||
* 头像大小 类型: large、small 、mini, default
|
||||
* 默认值: default
|
||||
*/
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 'default'
|
||||
},
|
||||
/**
|
||||
* 要显示的最大项目
|
||||
*/
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 多余的项目风格
|
||||
*/
|
||||
excessItemsStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
color: '#f56a00',
|
||||
backgroundColor: '#fde3cf'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
getItems (items) {
|
||||
const classString = {
|
||||
[`${this.prefixCls}-item`]: true,
|
||||
[`${this.size}`]: true
|
||||
}
|
||||
|
||||
if (this.maxLength > 0) {
|
||||
items = items.slice(0, this.maxLength)
|
||||
items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
|
||||
}
|
||||
return items.map((item) => (
|
||||
<li class={ classString }>{ item }</li>
|
||||
))
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { prefixCls, size } = this.$props
|
||||
const classString = {
|
||||
[`${prefixCls}`]: true,
|
||||
[`${size}`]: true
|
||||
}
|
||||
const items = filterEmpty(this.$slots.default)
|
||||
const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{ this.getItems(items) }</ul> : null
|
||||
|
||||
return (
|
||||
<div class={ classString }>
|
||||
{ itemsDom }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
import AvatarList from './List'
|
||||
import './index.less'
|
||||
|
||||
export default AvatarList
|
||||
@@ -0,0 +1,60 @@
|
||||
@import "../index";
|
||||
|
||||
@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
|
||||
@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
|
||||
|
||||
.@{avatar-list-prefix-cls} {
|
||||
display: inline-block;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0 0 0 8px;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.@{avatar-list-item-prefix-cls} {
|
||||
display: inline-block;
|
||||
font-size: @font-size-base;
|
||||
margin-left: -8px;
|
||||
width: @avatar-size-base;
|
||||
height: @avatar-size-base;
|
||||
|
||||
:global {
|
||||
.ant-avatar {
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
width: @avatar-size-lg;
|
||||
height: @avatar-size-lg;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: @avatar-size-sm;
|
||||
height: @avatar-size-sm;
|
||||
}
|
||||
|
||||
&.mini {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
:global {
|
||||
.ant-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
.ant-avatar-string {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
|
||||
<div class="chart-card-header">
|
||||
<div class="meta">
|
||||
<span class="chart-card-title">{{ title }}</span>
|
||||
<span class="chart-card-action">
|
||||
<slot name="action"></slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="total"><span>{{ total }}</span></div>
|
||||
</div>
|
||||
<div class="chart-card-content">
|
||||
<div class="content-fix">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card-footer">
|
||||
<div class="field">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChartCard',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
total: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-card-header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.meta {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-action {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.chart-card-footer {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding-top: 9px;
|
||||
margin-top: 8px;
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.field {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-content {
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
|
||||
.content-fix {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<span>
|
||||
{{ lastTime | format }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
function fixedZero (val) {
|
||||
return val * 1 < 10 ? `0${val}` : val
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'CountDown',
|
||||
props: {
|
||||
format: {
|
||||
type: Function,
|
||||
default: undefined
|
||||
},
|
||||
target: {
|
||||
type: [Date, Number],
|
||||
required: true
|
||||
},
|
||||
onEnd: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dateTime: '0',
|
||||
originTargetTime: 0,
|
||||
lastTime: 0,
|
||||
timer: 0,
|
||||
interval: 1000
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
format (time) {
|
||||
const hours = 60 * 60 * 1000
|
||||
const minutes = 60 * 1000
|
||||
|
||||
const h = Math.floor(time / hours)
|
||||
const m = Math.floor((time - h * hours) / minutes)
|
||||
const s = Math.floor((time - h * hours - m * minutes) / 1000)
|
||||
return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initTime()
|
||||
this.tick()
|
||||
},
|
||||
methods: {
|
||||
initTime () {
|
||||
let lastTime = 0
|
||||
let targetTime = 0
|
||||
this.originTargetTime = this.target
|
||||
try {
|
||||
if (Object.prototype.toString.call(this.target) === '[object Date]') {
|
||||
targetTime = this.target
|
||||
} else {
|
||||
targetTime = new Date(this.target).getTime()
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('invalid target prop')
|
||||
}
|
||||
|
||||
lastTime = targetTime - new Date().getTime()
|
||||
|
||||
this.lastTime = lastTime < 0 ? 0 : lastTime
|
||||
},
|
||||
tick () {
|
||||
const { onEnd } = this
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
if (this.lastTime < this.interval) {
|
||||
clearTimeout(this.timer)
|
||||
this.lastTime = 0
|
||||
if (typeof onEnd === 'function') {
|
||||
onEnd()
|
||||
}
|
||||
} else {
|
||||
this.lastTime -= this.interval
|
||||
this.tick()
|
||||
}
|
||||
}, this.interval)
|
||||
}
|
||||
},
|
||||
beforeUpdate () {
|
||||
if (this.originTargetTime !== this.target) {
|
||||
this.initTime()
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import CountDown from './CountDown'
|
||||
|
||||
export default CountDown
|
||||
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/StringUtil'
|
||||
|
||||
export default {
|
||||
name: 'Ellipsis',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-ellipsis'
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
default: 25
|
||||
},
|
||||
lines: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
fullWidthRecognition: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
render () {
|
||||
const { tooltip, length } = this.$props
|
||||
let text = ''
|
||||
// 处理没有default插槽时的特殊情况
|
||||
if (this.$slots.default) {
|
||||
text = this.$slots.default.map(vNode => vNode.text).join('')
|
||||
}
|
||||
// 判断是否显示 tooltip
|
||||
if (tooltip && getStrFullLength(text) > length) {
|
||||
return (
|
||||
<a-tooltip>
|
||||
<template slot="title">{text}</template>
|
||||
<span>{cutStrByFullLength(text, this.length) + '…'}</span>
|
||||
</a-tooltip>
|
||||
)
|
||||
} else {
|
||||
return (<span>{text}</span>)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
import Ellipsis from './Ellipsis'
|
||||
|
||||
export default Ellipsis
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
:show-time="false"
|
||||
:open="open"
|
||||
dropdownClassName="j-multiple-date-picker"
|
||||
@openChange="handleOpenChange"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template slot="dateRender" slot-scope="current">
|
||||
<div class="ant-calendar-date" @click.stop="clickCalendarDate(current)" :class="{'blue': getCurrentStyle(current)}">
|
||||
{{current.date()}}
|
||||
</div>
|
||||
</template>
|
||||
<template slot="renderExtraFooter" slot-scope="">
|
||||
<a class="ant-calendar-ok-btn" @click="handleOk">确定</a>
|
||||
</template>
|
||||
</a-date-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JMultipleDatePicker',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.checkedValue = []
|
||||
document.getElementsByClassName('ant-calendar-picker-input')[0].value = ''
|
||||
this.$emit('change', '')
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
open: false,
|
||||
checkedValue: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleOpenChange (open) {
|
||||
console.log(open)
|
||||
if (open) {
|
||||
this.open = open
|
||||
setTimeout(() => {
|
||||
document.getElementsByClassName('ant-calendar-input')[0].value = this.checkedValue.join(',')
|
||||
}, 100)
|
||||
} else {
|
||||
this.open = open
|
||||
this.$nextTick(() => {
|
||||
document.getElementsByClassName('ant-calendar-picker-input')[0].value = this.checkedValue.join(',')
|
||||
})
|
||||
}
|
||||
},
|
||||
getCurrentStyle (current) {
|
||||
const currentStr = current.format('YYYY-MM-DD')
|
||||
if (this.checkedValue.includes(currentStr)) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
clickCalendarDate (current) {
|
||||
const currentStr = current.format('YYYY-MM-DD')
|
||||
if (!this.checkedValue.includes(currentStr)) {
|
||||
console.log('加日期')
|
||||
this.checkedValue.push(currentStr)
|
||||
// event.target.className += ' blue'
|
||||
} else {
|
||||
console.log('减日期')
|
||||
this.checkedValue = this.checkedValue.filter(item => item !== currentStr)
|
||||
// console.log(event.target.className)
|
||||
// // 选中日期不存在该日期,该日期有blue类时删掉该类
|
||||
// if (event.target.className.split(' ').includes('blue')) {
|
||||
// event.target.className = event.target.className.split(' ').filter(item => item !== 'blue').join(' ')
|
||||
// }
|
||||
}
|
||||
document.getElementsByClassName('ant-calendar-input')[0].value = this.checkedValue.join(',')
|
||||
},
|
||||
handleOk () {
|
||||
this.open = false
|
||||
this.$nextTick(() => {
|
||||
document.getElementsByClassName('ant-calendar-picker-input')[0].value = this.checkedValue.join(',')
|
||||
})
|
||||
this.$emit('change', this.checkedValue.join(','))
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
// 去掉日期面板中原本选中的样式
|
||||
.j-multiple-date-picker {
|
||||
.ant-calendar-today {
|
||||
.ant-calendar-date {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.ant-calendar-selected-day {
|
||||
.ant-calendar-date{
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置日期面板中选中日期的样式
|
||||
.blue {
|
||||
background: #bae7ff !important;
|
||||
}
|
||||
|
||||
// 设置输入框中的文字显示
|
||||
/deep/ .ant-calendar-picker-input {
|
||||
padding-right: 30px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 设置弹出层footer的布局
|
||||
:global(.ant-calendar-footer-btn) {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
flex-direction: row-reverse !important;
|
||||
}
|
||||
// 删除弹出层的今天按钮
|
||||
:global(.ant-calendar-today-btn) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile">
|
||||
|
||||
<template v-for="(file, fileKey) of [innerFile || {}]">
|
||||
<div :key="fileKey" style="position: relative;">
|
||||
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
|
||||
<a-icon type="loading"/>
|
||||
<span style="margin-left:5px">上传中…</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip v-else-if="file.status==='done'" :title="file.name">
|
||||
<a-icon type="paper-clip"/>
|
||||
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip v-else :title="file.message||'上传失败'">
|
||||
<a-icon type="exclamation-circle" style="color:red;"/>
|
||||
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
|
||||
</a-tooltip>
|
||||
|
||||
<template style="width: 30px">
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
|
||||
<a-tooltip title="操作">
|
||||
<a-icon
|
||||
v-if="file.status!=='uploading'"
|
||||
type="setting"
|
||||
style="cursor: pointer;"/>
|
||||
</a-tooltip>
|
||||
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
|
||||
<span><a-icon type="download"/> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><a-icon type="delete"/> 删除</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleMoreOperation(originColumn)">
|
||||
<span><a-icon type="bars"/> 更多</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
<a-upload
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{'isup': 1}"
|
||||
:multiple="false"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:showUploadList="false"
|
||||
v-bind="cellProps"
|
||||
@change="handleChangeUpload"
|
||||
>
|
||||
<a-button icon="upload">{{originColumn.btnText || '上传文件'}}</a-button>
|
||||
</a-upload>
|
||||
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileAccessHttpUrl } from '@api/manage'
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import JFilePop from '@/components/jero/minipop/JFilePop'
|
||||
|
||||
import JVxeUploadCell from '@/components/jero/JVxeTable/components/cells/JVxeUploadCell'
|
||||
|
||||
export default {
|
||||
name: 'JVxeFileCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: { JFilePop },
|
||||
props: {},
|
||||
data () {
|
||||
return {
|
||||
innerFile: null,
|
||||
number: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders () {
|
||||
const { originColumn: col } = this
|
||||
const headers = {}
|
||||
if (col.token === true) {
|
||||
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
|
||||
/** 上传请求地址 */
|
||||
uploadAction () {
|
||||
if (!this.originColumn.action) {
|
||||
return window._CONFIG.domianURL + '/sys/common/upload'
|
||||
} else {
|
||||
return this.originColumn.action
|
||||
}
|
||||
},
|
||||
|
||||
hasFile () {
|
||||
return this.innerFile != null
|
||||
},
|
||||
|
||||
ellipsisFileName () {
|
||||
const length = 5
|
||||
const file = this.innerFile
|
||||
if (!file || !file.name) {
|
||||
return ''
|
||||
}
|
||||
if (file.name.length > length) {
|
||||
return file.name.substr(0, length) + '…'
|
||||
}
|
||||
return file.name
|
||||
},
|
||||
|
||||
responseName () {
|
||||
if (this.originColumn.responseName) {
|
||||
return this.originColumn.responseName
|
||||
} else {
|
||||
return 'message'
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
if (this.innerValue) {
|
||||
this.innerFile = this.innerValue
|
||||
} else {
|
||||
this.innerFile = null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 点击更多按钮
|
||||
handleMoreOperation (originColumn) {
|
||||
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
|
||||
if (originColumn.number) {
|
||||
this.number = originColumn.number
|
||||
} else {
|
||||
this.number = 0
|
||||
}
|
||||
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
|
||||
if (originColumn && originColumn.fieldExtendJson) {
|
||||
const json = JSON.parse(originColumn.fieldExtendJson)
|
||||
this.number = json.uploadnum ? json.uploadnum : 0
|
||||
}
|
||||
let path = ''
|
||||
if (this.innerFile) {
|
||||
path = this.innerFile.path
|
||||
}
|
||||
this.$refs.filePop.show('', path)
|
||||
},
|
||||
|
||||
// 更多上传回调
|
||||
handleFileSuccess (file) {
|
||||
if (file) {
|
||||
this.innerFile.path = file.path
|
||||
this.handleChangeCommon(this.innerFile)
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeUpload (info) {
|
||||
// const { originColumn: col } = this
|
||||
const { file } = info
|
||||
const value = {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
status: file.status,
|
||||
percent: file.percent
|
||||
}
|
||||
if (file.response) {
|
||||
value.responseName = file.response[this.responseName]
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
if (typeof file.response.success === 'boolean') {
|
||||
if (file.response.success) {
|
||||
value.path = file.response[this.responseName]
|
||||
this.handleChangeCommon(value)
|
||||
} else {
|
||||
value.status = 'error'
|
||||
value.message = file.response.message || '未知错误'
|
||||
}
|
||||
} else {
|
||||
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
|
||||
value.path = file.response[this.responseName]
|
||||
this.handleChangeCommon(value)
|
||||
}
|
||||
} else if (file.status === 'error') {
|
||||
value.message = file.response.message || '未知错误'
|
||||
}
|
||||
this.innerFile = value
|
||||
},
|
||||
|
||||
handleClickDownloadFile () {
|
||||
let { url, path } = this.innerFile || {}
|
||||
if (!url || url.length === 0) {
|
||||
if (path && path.length > 0) {
|
||||
url = getFileAccessHttpUrl(path.split(',')[0])
|
||||
}
|
||||
}
|
||||
if (url) {
|
||||
window.open(url)
|
||||
}
|
||||
},
|
||||
|
||||
handleClickDeleteFile () {
|
||||
this.handleChangeCommon(null)
|
||||
}
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: { visible: true },
|
||||
getValue: value => JVxeUploadCell.enhanced.getValue(value),
|
||||
setValue: value => JVxeUploadCell.enhanced.setValue(value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
||||
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile">
|
||||
<template v-for="(file, fileKey) of [innerFile || {}]">
|
||||
<div :key="fileKey" style="position: relative;">
|
||||
<template v-if="!file || !(file['url'] || file['path'] || file['message'])">
|
||||
<a-tooltip :title="'请稍后: ' + JSON.stringify (file) + ((file['url'] || file['path'] || file['message']))">
|
||||
<a-icon type="loading"/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="file['path']">
|
||||
<img class="j-editable-image" :src="imgSrc" alt="无图片" @click="handleMoreOperation"/>
|
||||
</template>
|
||||
<a-tooltip v-else :title="file.message||'上传失败'" @click="handleClickShowImageError">
|
||||
<a-icon type="exclamation-circle" style="color:red;"/>
|
||||
</a-tooltip>
|
||||
|
||||
<template style="width: 30px">
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
|
||||
<a-tooltip title="操作">
|
||||
<a-icon
|
||||
v-if="file.status!=='uploading'"
|
||||
type="setting"
|
||||
style="cursor: pointer;"/>
|
||||
</a-tooltip>
|
||||
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
|
||||
<span><a-icon type="download"/> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><a-icon type="delete"/> 删除</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleMoreOperation(originColumn)">
|
||||
<span><a-icon type="bars"/> 更多</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<a-upload
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{'isup': 1}"
|
||||
:multiple="false"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:showUploadList="false"
|
||||
v-bind="cellProps"
|
||||
@change="handleChangeUpload"
|
||||
>
|
||||
<a-button icon="upload">{{originColumn.btnText || '上传图片'}}</a-button>
|
||||
</a-upload>
|
||||
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileAccessHttpUrl } from '@api/manage'
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import JFilePop from '@/components/jero/minipop/JFilePop'
|
||||
|
||||
import JVxeUploadCell from '@/components/jero/JVxeTable/components/cells/JVxeUploadCell'
|
||||
|
||||
export default {
|
||||
name: 'JVxeImageCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: { JFilePop },
|
||||
props: {},
|
||||
data () {
|
||||
return {
|
||||
innerFile: null,
|
||||
number: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders () {
|
||||
const { originColumn: col } = this
|
||||
const headers = {}
|
||||
if (col.token === true) {
|
||||
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
|
||||
/** 上传请求地址 */
|
||||
uploadAction () {
|
||||
if (!this.originColumn.action) {
|
||||
return window._CONFIG.domianURL + '/sys/common/upload'
|
||||
} else {
|
||||
return this.originColumn.action
|
||||
}
|
||||
},
|
||||
|
||||
hasFile () {
|
||||
return this.innerFile != null
|
||||
},
|
||||
|
||||
/** 预览图片地址 */
|
||||
imgSrc () {
|
||||
if (this.innerFile) {
|
||||
if (this.innerFile.url) {
|
||||
return this.innerFile.url
|
||||
} else if (this.innerFile.path) {
|
||||
const path = this.innerFile.path.split(',')[0]
|
||||
return getFileAccessHttpUrl(path)
|
||||
}
|
||||
}
|
||||
return ''
|
||||
},
|
||||
|
||||
responseName () {
|
||||
if (this.originColumn.responseName) {
|
||||
return this.originColumn.responseName
|
||||
} else {
|
||||
return 'message'
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
if (this.innerValue) {
|
||||
this.innerFile = this.innerValue
|
||||
} else {
|
||||
this.innerFile = null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 点击更多按钮
|
||||
handleMoreOperation (originColumn) {
|
||||
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
|
||||
if (originColumn.number) {
|
||||
this.number = originColumn.number
|
||||
} else {
|
||||
this.number = 0
|
||||
}
|
||||
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
|
||||
if (originColumn && originColumn.fieldExtendJson) {
|
||||
const json = JSON.parse(originColumn.fieldExtendJson)
|
||||
this.number = json.uploadnum ? json.uploadnum : 0
|
||||
}
|
||||
let path = ''
|
||||
if (this.innerFile) {
|
||||
path = this.innerFile.path
|
||||
}
|
||||
this.$refs.filePop.show('', path, 'img')
|
||||
},
|
||||
|
||||
// 更多上传回调
|
||||
handleFileSuccess (file) {
|
||||
if (file) {
|
||||
this.innerFile.path = file.path
|
||||
this.handleChangeCommon(this.innerFile)
|
||||
}
|
||||
},
|
||||
|
||||
// 弹出上传出错详细信息
|
||||
handleClickShowImageError () {
|
||||
const file = this.innerFile || null
|
||||
if (file && file.message) {
|
||||
this.$error({ title: '上传出错', content: '错误信息:' + file.message, maskClosable: true })
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeUpload (info) {
|
||||
// const { originColumn: col } = this
|
||||
const { file } = info
|
||||
const value = {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
status: file.status,
|
||||
percent: file.percent
|
||||
}
|
||||
if (file.response) {
|
||||
value.responseName = file.response[this.responseName]
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
if (typeof file.response.success === 'boolean') {
|
||||
if (file.response.success) {
|
||||
value.path = file.response[this.responseName]
|
||||
this.handleChangeCommon(value)
|
||||
} else {
|
||||
value.status = 'error'
|
||||
value.message = file.response.message || '未知错误'
|
||||
}
|
||||
} else {
|
||||
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
|
||||
value.path = file.response[this.responseName]
|
||||
this.handleChangeCommon(value)
|
||||
}
|
||||
} else if (file.status === 'error') {
|
||||
value.message = file.response.message || '未知错误'
|
||||
}
|
||||
this.innerFile = value
|
||||
},
|
||||
|
||||
handleClickDownloadFile () {
|
||||
if (this.imgSrc) {
|
||||
window.open(this.imgSrc)
|
||||
}
|
||||
},
|
||||
|
||||
handleClickDeleteFile () {
|
||||
this.handleChangeCommon(null)
|
||||
}
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: { visible: true },
|
||||
getValue: value => JVxeUploadCell.enhanced.getValue(value),
|
||||
setValue: value => JVxeUploadCell.enhanced.setValue(value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.j-editable-image {
|
||||
height: 32px;
|
||||
max-width: 100px !important;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<j-popup
|
||||
v-bind="popupProps"
|
||||
@input="handlePopupInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { dispatchEvent, vModel } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxePopupCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
popupProps () {
|
||||
const { innerValue, originColumn: col, caseId, cellProps } = this
|
||||
return {
|
||||
...cellProps,
|
||||
value: innerValue,
|
||||
field: col.field || col.key,
|
||||
code: col.popupCode,
|
||||
orgFields: col.orgFields,
|
||||
destFields: col.destFields,
|
||||
groupId: caseId,
|
||||
param: col.param,
|
||||
sorter: col.sorter
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** popup回调 */
|
||||
handlePopupInput (value, others) {
|
||||
const { row, originColumn: col } = this
|
||||
// 存储输入的值
|
||||
let popupValue = value
|
||||
if (others && Object.keys(others).length > 0) {
|
||||
Object.keys(others).forEach(key => {
|
||||
const currentValue = others[key]
|
||||
// 当前列直接赋值,其他列通过vModel赋值
|
||||
if (key === col.key) {
|
||||
popupValue = currentValue
|
||||
} else {
|
||||
vModel.call(this, currentValue, row, key)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.handleChangeCommon(popupValue)
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived (event) {
|
||||
dispatchEvent.call(this, event, 'ant-input')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<a-radio-group
|
||||
:class="clazz"
|
||||
:value="innerValue"
|
||||
v-bind="cellProps"
|
||||
@change="(e)=>handleChangeCommon(e.target.value)"
|
||||
>
|
||||
<a-radio
|
||||
v-for="item of originColumn.options"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
@click="$event=>handleRadioClick(item,$event)"
|
||||
>{{ item.text }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeRadioCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
scrolling () {
|
||||
return !!this.renderOptions.scrolling
|
||||
},
|
||||
clazz () {
|
||||
return {
|
||||
'j-vxe-radio': true,
|
||||
'no-animation': this.scrolling
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRadioClick (item) {
|
||||
if (this.originColumn.allowClear === true) {
|
||||
// 取消选择
|
||||
if (item.value === this.innerValue) {
|
||||
this.handleChangeCommon(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: { visible: true }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// 关闭动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-radio.no-animation {
|
||||
.ant-radio-inner,
|
||||
.ant-radio-inner::after {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,262 @@
|
||||
import debounce from 'lodash/debounce'
|
||||
import { getAction } from '@/api/manage'
|
||||
import { cloneObject } from '@/utils/util'
|
||||
import { filterDictText } from '@/components/dict/JDictSelectUtil'
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
/** 公共资源 */
|
||||
const common = {
|
||||
/** value - label map,防止重复查询(刷新清空缓存) */
|
||||
labelMap: new Map(),
|
||||
|
||||
/** 公共data */
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
innerSelectValue: null,
|
||||
innerOptions: []
|
||||
}
|
||||
},
|
||||
/** 公共计算属性 */
|
||||
computed: {
|
||||
dict () {
|
||||
return this.originColumn.dict
|
||||
},
|
||||
options () {
|
||||
if (this.isAsync) {
|
||||
return this.innerOptions
|
||||
} else {
|
||||
return this.originColumn.options || []
|
||||
}
|
||||
},
|
||||
// 是否是异步模式
|
||||
isAsync () {
|
||||
const isAsync = this.originColumn.async
|
||||
return (isAsync != null && isAsync !== '') ? !!isAsync : true
|
||||
}
|
||||
},
|
||||
/** 公共属性监听 */
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler (value) {
|
||||
if (value == null || value === '') {
|
||||
this.innerSelectValue = null
|
||||
} else {
|
||||
this.loadDataByValue(value)
|
||||
}
|
||||
}
|
||||
},
|
||||
dict () {
|
||||
this.loadDataByDict()
|
||||
}
|
||||
},
|
||||
/** 公共方法 */
|
||||
methods: {
|
||||
|
||||
// 根据 value 查询数据,用于回显
|
||||
async loadDataByValue (value) {
|
||||
if (this.isAsync) {
|
||||
if (this.innerSelectValue !== value) {
|
||||
if (common.labelMap.has(value)) {
|
||||
this.innerOptions = cloneObject(common.labelMap.get(value))
|
||||
} else {
|
||||
const { success, result } = await getAction(`/sys/dict/loadDictItem/${this.dict}`, { key: value })
|
||||
if (success && result && result.length > 0) {
|
||||
this.innerOptions = [{ value: value, text: result[0] }]
|
||||
common.labelMap.set(value, cloneObject(this.innerOptions))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.innerSelectValue = (value || '').toString()
|
||||
},
|
||||
|
||||
// 初始化字典
|
||||
async loadDataByDict () {
|
||||
if (!this.isAsync) {
|
||||
// 如果字典项集合有数据
|
||||
if (!this.originColumn.options || this.originColumn.options.length === 0) {
|
||||
// 根据字典Code, 初始化字典数组
|
||||
let dictStr = ''
|
||||
if (this.dict) {
|
||||
const arr = this.dict.split(',')
|
||||
if (arr[0].indexOf('where') > 0) {
|
||||
const tbInfo = arr[0].split('where')
|
||||
dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1])
|
||||
} else {
|
||||
dictStr = this.dict
|
||||
}
|
||||
if (this.dict.indexOf(',') === -1) {
|
||||
// 优先从缓存中读取字典配置
|
||||
const cache = getDictItemsFromCache(this.dict)
|
||||
if (cache) {
|
||||
this.innerOptions = cache
|
||||
return
|
||||
}
|
||||
}
|
||||
const { success, result } = await ajaxGetDictItems(dictStr, null)
|
||||
if (success) {
|
||||
this.innerOptions = result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 显示组件,自带翻译
|
||||
export const DictSearchSpanCell = {
|
||||
name: 'JVxeSelectSearchSpanCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data () {
|
||||
return {
|
||||
...common.data.apply(this)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...common.computed
|
||||
},
|
||||
watch: {
|
||||
...common.watch
|
||||
},
|
||||
methods: {
|
||||
...common.methods
|
||||
},
|
||||
render (h) {
|
||||
return h('span', {}, [
|
||||
filterDictText(this.innerOptions, this.innerSelectValue || this.innerValue)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// 请求id
|
||||
let requestId = 0
|
||||
|
||||
// 输入选择组件
|
||||
export const DictSearchInputCell = {
|
||||
name: 'JVxeSelectSearchInputCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data () {
|
||||
return {
|
||||
...common.data.apply(this),
|
||||
|
||||
hasRequest: false,
|
||||
scopedSlots: {
|
||||
notFoundContent: () => {
|
||||
if (this.loading) {
|
||||
return <a-spin size="small"/>
|
||||
} else if (this.hasRequest) {
|
||||
return <div>没有查询到任何数据</div>
|
||||
} else {
|
||||
return <div>{this.tipsContent}</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...common.computed,
|
||||
tipsContent () {
|
||||
return this.originColumn.tipsContent || '请输入搜索内容'
|
||||
},
|
||||
filterOption () {
|
||||
if (this.isAsync) {
|
||||
return null
|
||||
}
|
||||
return (input, option) => option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
...common.watch
|
||||
},
|
||||
created () {
|
||||
this.loadData = debounce(this.loadData, 300)// 消抖
|
||||
},
|
||||
methods: {
|
||||
...common.methods,
|
||||
|
||||
loadData (value) {
|
||||
const currentRequestId = ++requestId
|
||||
this.loading = true
|
||||
this.innerOptions = []
|
||||
if (value == null || value.trim() === '') {
|
||||
this.loading = false
|
||||
this.hasRequest = false
|
||||
return
|
||||
}
|
||||
// 字典code格式:table,text,code
|
||||
this.hasRequest = true
|
||||
getAction(`/sys/dict/loadDict/${this.dict}`, { keyword: value }).then(res => {
|
||||
if (currentRequestId !== requestId) {
|
||||
return
|
||||
}
|
||||
const { success, result, message } = res
|
||||
if (success) {
|
||||
this.innerOptions = result
|
||||
result.forEach((item) => {
|
||||
common.labelMap.set(item.value, [item])
|
||||
})
|
||||
} else {
|
||||
this.$message.warning(message)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleChange (selectedValue) {
|
||||
this.innerSelectValue = selectedValue
|
||||
this.handleChangeCommon(this.innerSelectValue)
|
||||
},
|
||||
handleSearch (value) {
|
||||
if (this.isAsync) {
|
||||
// 在输入时也应该开启加载,因为loadData加了消抖,所以会有800ms的用户主观上认为的卡顿时间
|
||||
this.loading = true
|
||||
if (this.innerOptions.length > 0) {
|
||||
this.innerOptions = []
|
||||
}
|
||||
this.loadData(value)
|
||||
}
|
||||
},
|
||||
|
||||
renderOptionItem () {
|
||||
const options = []
|
||||
this.options.forEach(({ value, text, label, title, disabled }) => {
|
||||
options.push(
|
||||
<a-select-option key={value} value={value} disabled={disabled}>{text || label || title}</a-select-option>
|
||||
)
|
||||
})
|
||||
return options
|
||||
}
|
||||
},
|
||||
render () {
|
||||
return (
|
||||
<a-select
|
||||
showSearch
|
||||
allowClear
|
||||
value={this.innerSelectValue}
|
||||
filterOption={this.filterOption}
|
||||
style="width: 100%"
|
||||
{...this.cellProps}
|
||||
onSearch={this.handleSearch}
|
||||
onChange={this.handleChange}
|
||||
scopedSlots={this.scopedSlots}
|
||||
>
|
||||
{this.renderOptionItem()}
|
||||
</a-select>
|
||||
)
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived (event) {
|
||||
dispatchEvent.call(this, event, 'ant-select')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { installCell, JVXETypes } from '@/components/jero/JVxeTable'
|
||||
import JVxePopupCell from './JVxePopupCell'
|
||||
import { DictSearchInputCell, DictSearchSpanCell } from './JVxeSelectDictSearchCell'
|
||||
import JVxeFileCell from './JVxeFileCell'
|
||||
import JVxeImageCell from './JVxeImageCell'
|
||||
import JVxeRadioCell from './JVxeRadioCell'
|
||||
import JVxeSelectCell from '@comp/jero/JVxeTable/components/cells/JVxeSelectCell'
|
||||
import JVxeTextareaCell from '@comp/jero/JVxeTable/components/cells/JVxeTextareaCell'
|
||||
|
||||
// 注册online组件
|
||||
JVXETypes.input_pop = 'input_pop'
|
||||
JVXETypes.list_multi = 'list_multi'
|
||||
JVXETypes.sel_search = 'sel_search'
|
||||
installCell(JVXETypes.input_pop, JVxeTextareaCell)
|
||||
installCell(JVXETypes.list_multi, JVxeSelectCell)
|
||||
installCell(JVXETypes.sel_search, JVxeSelectCell)
|
||||
|
||||
// 注册【popup】组件(普通封装方式)
|
||||
JVXETypes.popup = 'popup'
|
||||
installCell(JVXETypes.popup, JVxePopupCell)
|
||||
|
||||
// 注册【字典搜索下拉】组件(高级封装方式)
|
||||
JVXETypes.selectDictSearch = 'select-dict-search'
|
||||
installCell(JVXETypes.selectDictSearch, DictSearchInputCell, DictSearchSpanCell)
|
||||
|
||||
// 注册【文件上传】组件
|
||||
JVXETypes.file = 'file'
|
||||
installCell(JVXETypes.file, JVxeFileCell)
|
||||
|
||||
// 注册【图片上传】组件
|
||||
JVXETypes.image = 'image'
|
||||
installCell(JVXETypes.image, JVxeImageCell)
|
||||
|
||||
// 注册【单选框】组件
|
||||
JVXETypes.radio = 'radio'
|
||||
installCell(JVXETypes.radio, JVxeRadioCell)
|
||||
@@ -0,0 +1,41 @@
|
||||
####1._util包:存放自定义函数 详细见代码注释
|
||||
####2.AvatarList:显示头像群并支持tip,用法参考src\views\Home.vue(如下图)
|
||||

|
||||
####3.chart包:存放各种图表相关的组件,条形图柱形图折线图等等 具体用法参考首页
|
||||
####4.countDown包:一个倒计时组件,用法参考home页,简单描述,该组件有3个属性,
|
||||
target(时间/毫秒数)必填,
|
||||
format(function,该方法接收一个毫秒数的参数,用于格式化显示当前倒计时时间)非必填,
|
||||
onEnd倒计时结束触发函数
|
||||

|
||||
####5.dict包:数据字典专用,用法参考文件夹下readme文件
|
||||
####6.Ellipsis包:字符串截取组件,可以指定字符串的显示长度,并将全部内容显示到tip中,简单使用参考src\views\system\PermissionList.vue
|
||||
####7.jero包:该包下自定义了很多列表/表单中用到的组件 参考包下readme文件
|
||||
####8.jerobiz包:该包下定义了一些业务相关的组件,比如选择用户弹框,根据部门选择用户等等
|
||||
####9.layouts+page包:系统页面布局相关组件,比如登陆进去之后页面顶部显示什么,底部显示什么,菜单点击触发多个tab的布局等等 一般情况不需要修改
|
||||
####10.menun包:菜单组件,俩个,一个折叠菜单一个正常显示的菜单
|
||||
####12.online包:该包下封装了online表单的相关组件,用于展示表单各种控件,验证表单等等,相关用法参考readme
|
||||
####13.setting包:该包下封装了首页风格切换等功能如下图
|
||||

|
||||
####14.table包:一个二次封装的table组件,用于展示列表,参考readme
|
||||
####15.tools包:
|
||||
Breadcrumb.vue:面包屑二次封装,支持路由跳转
|
||||
DetailList.vue:详情展示用法参考src\views\profile\advanced\Advanced.vue(效果如下图)
|
||||

|
||||
````
|
||||
个人认为该页面代码有两点值得学习:
|
||||
1.vue provide/inject的使用
|
||||
2.该页面css定义方式,只定义一个顶层class,其余样式都定义在其下,这样只要顶层class不和别的页面冲突,整个页面的样式都是唯一生效的
|
||||
````
|
||||
FooterToolBar.vue:fixed定位的底部,通过是否定义内部控件的属性slot="extra"决定是左浮动或是右浮动
|
||||
HeaderNotice.vue:首页通知(如下图)
|
||||

|
||||
HeaderInfo.vue:上下文字布局(如下图)
|
||||

|
||||
Logo.vue:首页左上侧的log图
|
||||

|
||||
UserMenu.vue:首页右上侧的内容
|
||||

|
||||
####16.trend包 趋势显示组件(如下图)
|
||||

|
||||

|
||||

|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div :class="[prefixCls, reverseColor && 'reverse-color' ]">
|
||||
<span>
|
||||
<slot name="term"></slot>
|
||||
<span class="item-text">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</span>
|
||||
<span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Trend',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-trend'
|
||||
},
|
||||
/**
|
||||
* 上升下降标识:up|down
|
||||
*/
|
||||
flag: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 颜色反转
|
||||
*/
|
||||
reverseColor: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "index";
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import Trend from './Trend.vue'
|
||||
|
||||
export default Trend
|
||||
@@ -0,0 +1,42 @@
|
||||
@import "../index";
|
||||
|
||||
@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
|
||||
|
||||
.@{trend-prefix-cls} {
|
||||
display: inline-block;
|
||||
font-size: @font-size-base;
|
||||
line-height: 22px;
|
||||
|
||||
.up,
|
||||
.down {
|
||||
margin-left: 4px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
transform: scale(0.83);
|
||||
}
|
||||
}
|
||||
|
||||
.item-text {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
color: rgba(0,0,0,.85);
|
||||
}
|
||||
|
||||
.up {
|
||||
color: @red-6;
|
||||
}
|
||||
.down {
|
||||
color: @green-6;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
&.reverse-color .up {
|
||||
color: @green-6;
|
||||
}
|
||||
&.reverse-color .down {
|
||||
color: @red-6;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<j-modal
|
||||
:title="$t('uploadFile.file')"
|
||||
:width="width"
|
||||
:visible="visible"
|
||||
switchFullscreen
|
||||
:maskClosable="false"
|
||||
:confirmLoading="confirmLoading"
|
||||
:footer="null"
|
||||
@cancel="close">
|
||||
|
||||
<a-upload-dragger
|
||||
name="file"
|
||||
:multiple="multiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{'biz':bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="doBeforeUpload"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:returnUrl="returnUrl"
|
||||
:listType="complistType"
|
||||
@preview="handlePreview"
|
||||
@download="handleDownload"
|
||||
:showUploadList="{
|
||||
showDownloadIcon: isDownload
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:class="{'uploadty-disabled': disabled}"
|
||||
>
|
||||
<p class="upload-drag-icon">
|
||||
<a-icon type="cloud-upload" />
|
||||
</p>
|
||||
<p class="ant-upload-text">
|
||||
{{ $t('uploadFile.clickOrDragUpload') }}
|
||||
</p>
|
||||
</a-upload-dragger>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-empty v-if="disabled && (!fileList || fileList.length === 0)" />
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { downloadFile, getFileAccessHttpUrl } from '@/api/manage'
|
||||
import { getFileInfo } from '@/api/api'
|
||||
import { previewPdf } from '@/utils/previewPdf'
|
||||
import Vue from 'vue'
|
||||
|
||||
const FILE_TYPE_ALL = 'all'
|
||||
const FILE_TYPE_IMG = 'image'
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png', 'raw']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
// 本系统限制可以上传的文件格式
|
||||
const CAN_UPLOAD_FILE_TYPE = 'doc,docx,xls,xlsx,pdf,png,jpg'
|
||||
|
||||
const Base64 = require('js-base64').Base64
|
||||
|
||||
// 支持预览的文件类型
|
||||
const CAN_PREVIEW_FILE_TYPE = ['pdf', 'image', 'doc', 'docx']
|
||||
// 不支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'mp4', 'flv']
|
||||
|
||||
export default {
|
||||
name: 'UploadFile',
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '点击上传'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: CAN_UPLOAD_FILE_TYPE
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
// 此属性被废弃了
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url,
|
||||
* true:仅返回url
|
||||
* false:返回fileName filePath fileSize
|
||||
*/
|
||||
returnUrl: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
beforeUpload: {
|
||||
type: Function
|
||||
},
|
||||
isDownload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabledUpload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
width: 600,
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
headers: {},
|
||||
fileList: [],
|
||||
fileIds: null, // 文件id,可以是数组,也可以是字符串
|
||||
image: false,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
},
|
||||
complistType () {
|
||||
return this.fileType === FILE_TYPE_IMG ? 'picture-card' : 'text'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open (fileIds) {
|
||||
this.headers = { 'X-Access-Token': Vue.ls.get(ACCESS_TOKEN) }
|
||||
this.fileIds = fileIds
|
||||
this.initFileList()
|
||||
this.visible = true
|
||||
},
|
||||
initFileList () {
|
||||
if (!this.fileIds) {
|
||||
return
|
||||
}
|
||||
let fileIdArr = []
|
||||
if (Array.isArray(this.fileIds)) {
|
||||
fileIdArr = this.fileIds
|
||||
} else {
|
||||
fileIdArr = this.fileIds.split(',')
|
||||
}
|
||||
const fileList = []
|
||||
fileIdArr.forEach(async id => {
|
||||
const fileInfo = await this.getFileInfoByFileId(id)
|
||||
if (fileInfo) {
|
||||
fileList.push(fileInfo)
|
||||
}
|
||||
})
|
||||
this.fileList = fileList
|
||||
console.log(this.fileList)
|
||||
},
|
||||
getFileInfoByFileId (id) {
|
||||
return new Promise(resolve => {
|
||||
let file
|
||||
getFileInfo({ id: id }).then(res => {
|
||||
if (res.success) {
|
||||
const fileInfo = res.result || {}
|
||||
file = {
|
||||
uid: fileInfo.id,
|
||||
name: fileInfo.fileName,
|
||||
status: 'done',
|
||||
url: fileInfo.url,
|
||||
// response用于下载和预览
|
||||
response: {
|
||||
success: true,
|
||||
result: {
|
||||
id: fileInfo.id,
|
||||
fileName: fileInfo.fileName
|
||||
},
|
||||
status: 'history'
|
||||
}
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
resolve(file)
|
||||
})
|
||||
})
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
setTimeout(() => {
|
||||
this.fileList = []
|
||||
}, 500)
|
||||
},
|
||||
handleChange (info) {
|
||||
console.log(info, this.uploadGoOn)
|
||||
if (!info.file.status && this.uploadGoOn === false) {
|
||||
info.fileList.pop()
|
||||
}
|
||||
console.log(info.fileList)
|
||||
let fileList = info.fileList
|
||||
if (info.file.status === 'done') {
|
||||
console.log(this.number)
|
||||
if (this.number > 0) {
|
||||
console.log(fileList)
|
||||
fileList = fileList.slice(-this.number)
|
||||
console.log(fileList)
|
||||
}
|
||||
if (info.file.response.success) {
|
||||
fileList = fileList.map((file) => {
|
||||
console.log(file)
|
||||
if (file.response) {
|
||||
// const reUrl = `${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.response.result.fileName}`
|
||||
// TODO getFileAccessHttpUrl方法会追加token,在之后拼参数
|
||||
file.url = getFileAccessHttpUrl(file.response.result.id) + '&fullfilename=' + file.response.result.fileName
|
||||
}
|
||||
// 校验不通过的文件需要筛出去
|
||||
if (file.uploadGoOn === false) {
|
||||
return null
|
||||
}
|
||||
return file
|
||||
})
|
||||
} else {
|
||||
this.$message.error(info.file.response.message)
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.error(`${info.file.name} ${this.$t('uploadFile.uploadFailed')}.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
fileList = fileList.filter(tt => !!tt)
|
||||
console.log(fileList)
|
||||
// 二次过滤不符合要求的(uploadGoOn为false的),否则只上传多个不符合要求的只会有一个不出现在列表中
|
||||
fileList = fileList.filter(tt => tt.uploadGoOn === undefined || tt.uploadGoOn === null || tt.uploadGoOn === true)
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
// returnUrl为true时仅返回文件路径
|
||||
if (this.returnUrl) {
|
||||
this.handlePathChange()
|
||||
} else {
|
||||
// returnUrl为false时返回文件名称、文件路径及文件大小
|
||||
this.newFileList = []
|
||||
for (let a = 0; a < fileList.length; a++) {
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if (fileList[a].status === 'done') {
|
||||
const fileJson = {
|
||||
id: fileList[a].response.result.id,
|
||||
fileName: fileList[a].name,
|
||||
filePath: fileList[a].url,
|
||||
fileSize: fileList[a].size
|
||||
}
|
||||
this.newFileList.push(fileJson)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
}
|
||||
this.$emit('change', this.newFileList)
|
||||
}
|
||||
}
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
const fileType = file.type
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = file.name ? file.name.split('.')[file.name.split('.').length - 1] : ''
|
||||
const canPreview = fileType ? CAN_PREVIEW_FILE_TYPE.every(tt => fileType.indexOf(tt) === -1) : CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (canPreview) {
|
||||
this.$message.loading(this.$t('uploadFile.cannotPreview')).then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/download/${file.response.result.id}?token=${sessionStorage.getItem(ACCESS_TOKEN)}&fullfilename=${file.name}`
|
||||
console.log(fileSuffix)
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (!canPreview && FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.response.result.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (!canPreview && FILE_TYPE_PDF.includes(fileSuffix)) {
|
||||
const url = previewPdf(file.response.result.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
const url = `${window._CONFIG.onlinePreviewDomainURL}?url=${encodeURIComponent(Base64.encode(fileFullUrl))}`
|
||||
window.open(url)
|
||||
},
|
||||
handleDownload (file) {
|
||||
// 下载文件
|
||||
downloadFile(`/sys/common/download/${file.response.result.id}`, file.name)
|
||||
},
|
||||
doBeforeUpload (file) {
|
||||
this.uploadGoOn = true
|
||||
file.uploadGoOn = true
|
||||
const fileSize = file.size // 上传的文件大小
|
||||
if (fileSize === 0) {
|
||||
this.$message.error(this.$t('uploadFile.cannotUploadEmpty'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (fileSize > 1024 * 1024 * 500) {
|
||||
this.$message.error(this.$t('uploadFile.maxSize'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_ALL) {
|
||||
return true
|
||||
}
|
||||
const fileType = file.type
|
||||
if (this.fileType === CAN_UPLOAD_FILE_TYPE && fileType.indexOf('image') !== -1) {
|
||||
return true
|
||||
}
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = (file.name ? file.name.split('.')[file.name.split('.').length - 1] : '').toLowerCase()
|
||||
if (this.fileType === FILE_TYPE_IMG && fileType.indexOf('image') < 0) {
|
||||
this.$message.error(this.$t('uploadFile.onlyUploadPic'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && (fileSuffix === 'png' || fileSuffix === 'jpg')) {
|
||||
return true
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && fileSuffix !== 'png' && fileSuffix !== 'jpg') {
|
||||
this.$message.error(this.$t('uploadFile.pleaseUpload') + 'png、jpg' + this.$t('uploadFile.file'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType.indexOf(fileSuffix) === -1) {
|
||||
this.$message.error(this.$t('uploadFile.pleaseUpload') + `${this.fileType}` + this.$t('uploadFile.file'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
// 扩展 beforeUpload 验证
|
||||
if (typeof this.beforeUpload === 'function') {
|
||||
return this.beforeUpload(file)
|
||||
}
|
||||
return true
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
console.log(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.upload-drag-icon {
|
||||
font-size: 70px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/*禁用情况下,不显示上传操作区域*/
|
||||
/deep/ .ant-upload.ant-upload-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 索赔上传需要根据传入的值判断是否可以上传
|
||||
.upload-disabled {
|
||||
/deep/ .ant-upload{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 不可删除文件
|
||||
.delete-disabled {
|
||||
/deep/.ant-upload-list-item-card-actions {
|
||||
a:nth-child(2){
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
import Vue from 'vue'
|
||||
/**
|
||||
* 省市区
|
||||
*/
|
||||
export default class Area {
|
||||
/**
|
||||
* 构造器
|
||||
* @param pcaa
|
||||
*/
|
||||
constructor (pcaa) {
|
||||
if (!pcaa) {
|
||||
pcaa = Vue.prototype.$Jpcaa
|
||||
}
|
||||
const arr = []
|
||||
const province = pcaa['86']
|
||||
Object.keys(province).map(key => {
|
||||
arr.push({ id: key, text: province[key], pid: '86', index: 1 })
|
||||
const city = pcaa[key]
|
||||
Object.keys(city).map(key2 => {
|
||||
arr.push({ id: key2, text: city[key2], pid: key, index: 2 })
|
||||
const qu = pcaa[key2]
|
||||
if (qu) {
|
||||
Object.keys(qu).map(key3 => {
|
||||
arr.push({ id: key3, text: qu[key3], pid: key2, index: 3 })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
this.all = arr
|
||||
}
|
||||
|
||||
get pca () {
|
||||
return this.all
|
||||
}
|
||||
|
||||
getCode (text) {
|
||||
if (!text || text.length === 0) {
|
||||
return ''
|
||||
}
|
||||
for (const item of this.all) {
|
||||
if (item.text === text) {
|
||||
return item.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getText (code) {
|
||||
if (!code || code.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const arr = []
|
||||
this.getAreaBycode(code, arr, 3)
|
||||
return arr.join('/')
|
||||
}
|
||||
|
||||
getRealCode (code) {
|
||||
const arr = []
|
||||
this.getPcode(code, arr, 3)
|
||||
return arr
|
||||
}
|
||||
|
||||
getPcode (id, arr, index) {
|
||||
for (const item of this.all) {
|
||||
if (item.id === id && item.index === index) {
|
||||
arr.unshift(id)
|
||||
if (item.pid !== '86') {
|
||||
this.getPcode(item.pid, arr, --index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAreaBycode (code, arr, index) {
|
||||
for (const item of this.all) {
|
||||
if (item.id === code && item.index === index) {
|
||||
arr.unshift(item.text)
|
||||
if (item.pid !== '86') {
|
||||
this.getAreaBycode(item.pid, arr, --index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 获取字符串的长度ascii长度为1 中文长度为2
|
||||
* @param str
|
||||
* @returns {number}
|
||||
*/
|
||||
export const getStrFullLength = (str = '') =>
|
||||
str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
return pre + 1
|
||||
}
|
||||
return pre + 2
|
||||
}, 0)
|
||||
|
||||
/**
|
||||
* 给定一个字符串和一个长度,将此字符串按指定长度截取
|
||||
* @param str
|
||||
* @param maxLength
|
||||
* @returns {string}
|
||||
*/
|
||||
export const cutStrByFullLength = (str = '', maxLength) => {
|
||||
let showLength = 0
|
||||
return str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
showLength += 1
|
||||
} else {
|
||||
showLength += 2
|
||||
}
|
||||
if (showLength <= maxLength) {
|
||||
return pre + cur
|
||||
}
|
||||
return pre
|
||||
}, '')
|
||||
}
|
||||
|
||||
// 下划线转换驼峰
|
||||
export function underLinetoHump (name) {
|
||||
return name.replace(/_(\w)/g, function (all, letter) {
|
||||
return letter.toUpperCase()
|
||||
})
|
||||
}
|
||||
// 驼峰转换下划线
|
||||
export function humptoUnderLine (name) {
|
||||
return name.replace(/([A-Z])/g, '_$1').toLowerCase()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* components util
|
||||
*/
|
||||
|
||||
/**
|
||||
* 清理空值,对象
|
||||
* @param children
|
||||
* @returns {*[]}
|
||||
*/
|
||||
export function filterEmpty (children = []) {
|
||||
return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
|
||||
<v-chart ref="chart" :forceFit="true" :height="height" :data="dataSource" :scale="scale">
|
||||
<v-tooltip :shared="false"/>
|
||||
<v-axis/>
|
||||
<v-line position="x*y" :size="lineSize" :color="lineColor"/>
|
||||
<v-area position="x*y" :color="color"/>
|
||||
</v-chart>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'AreaChartTy',
|
||||
props: {
|
||||
// 图表数据
|
||||
dataSource: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
// 图表标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// x 轴别名
|
||||
x: {
|
||||
type: String,
|
||||
default: 'x'
|
||||
},
|
||||
// y 轴别名
|
||||
y: {
|
||||
type: String,
|
||||
default: 'y'
|
||||
},
|
||||
// Y轴最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// Y轴最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
// 图表高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
},
|
||||
// 线的粗细
|
||||
lineSize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 面积的颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 线的颜色
|
||||
lineColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scale () {
|
||||
return [
|
||||
{ dataKey: 'x', title: this.x, alias: this.x },
|
||||
{ dataKey: 'y', title: this.y, alias: this.y, min: this.min, max: this.max }
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "chart";
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart :forceFit="true" :height="height" :data="dataSource" :scale="scale" :padding="padding">
|
||||
<v-tooltip/>
|
||||
<v-axis/>
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'Bar',
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
yaxisText: {
|
||||
type: String,
|
||||
default: 'y'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return { padding: ['auto', 'auto', '40', '50'] }
|
||||
},
|
||||
computed: {
|
||||
scale () {
|
||||
return [{
|
||||
dataKey: 'y',
|
||||
alias: this.yaxisText
|
||||
}]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 50px 32px 0' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding" :onClick="handleClick">
|
||||
<v-tooltip/>
|
||||
<v-legend/>
|
||||
<v-axis/>
|
||||
<v-bar position="type*bar"/>
|
||||
<v-line position="type*line" color="#2fc25b" :size="3"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ChartEventMixins } from './mixins/ChartMixins'
|
||||
|
||||
export default {
|
||||
name: 'BarAndLine',
|
||||
mixins: [ChartEventMixins],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ type: '10:10', bar: 200, line: 1000 },
|
||||
{ type: '10:15', bar: 600, line: 1000 },
|
||||
{ type: '10:20', bar: 200, line: 1000 },
|
||||
{ type: '10:25', bar: 900, line: 1000 },
|
||||
{ type: '10:30', bar: 200, line: 1000 },
|
||||
{ type: '10:35', bar: 200, line: 1000 },
|
||||
{ type: '10:40', bar: 100, line: 1000 }
|
||||
]
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 400
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
padding: { top: 50, right: 50, bottom: 100, left: 50 },
|
||||
scale: [{
|
||||
dataKey: 'bar',
|
||||
min: 0
|
||||
}, {
|
||||
dataKey: 'line',
|
||||
min: 0
|
||||
}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
return this.dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart :data="data" :height="height" :force-fit="true" :scale="scale" :onClick="handleClick">
|
||||
<v-tooltip/>
|
||||
<v-axis/>
|
||||
<v-legend/>
|
||||
<v-bar position="x*y" color="type" :adjust="adjust"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DataSet } from '@antv/data-set'
|
||||
import { ChartEventMixins } from './mixins/ChartMixins'
|
||||
|
||||
export default {
|
||||
name: 'BarMultid',
|
||||
mixins: [ChartEventMixins],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ type: 'JeroOne', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, May: 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
|
||||
{ type: 'JeroTwo', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, May: 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 }
|
||||
]
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
|
||||
},
|
||||
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
|
||||
aliases: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
adjust: [{
|
||||
type: 'dodge',
|
||||
marginRatio: 1 / 32
|
||||
}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
const dv = new DataSet.View().source(this.dataSource)
|
||||
dv.transform({
|
||||
type: 'fold',
|
||||
fields: this.fields,
|
||||
key: 'x',
|
||||
value: 'y'
|
||||
})
|
||||
|
||||
// bar 使用不了 - 和 / 所以替换下
|
||||
const rows = dv.rows.map(row => {
|
||||
if (typeof row.x === 'string') {
|
||||
row.x = row.x.replace(/[-/]/g, '_')
|
||||
}
|
||||
return row
|
||||
})
|
||||
// 替换别名
|
||||
rows.forEach(row => {
|
||||
for (const item of this.aliases) {
|
||||
if (item.field === row.type) {
|
||||
row.type = item.alias
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
return rows
|
||||
},
|
||||
scale () {
|
||||
return [
|
||||
{
|
||||
type: 'cat',
|
||||
dataKey: 'x'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<v-chart :forceFit="true" :height="300" :data="chartData" :scale="scale">
|
||||
<v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
|
||||
<v-axis
|
||||
dataKey="value"
|
||||
:zIndex="2"
|
||||
:line="null"
|
||||
:label="axisLabel"
|
||||
:subTickCount="4"
|
||||
:subTickLine="axisSubTickLine"
|
||||
:tickLine="axisTickLine"
|
||||
:grid="null"
|
||||
></v-axis>
|
||||
<v-axis dataKey="1" :show="false"></v-axis>
|
||||
<v-series
|
||||
gemo="point"
|
||||
position="value*1"
|
||||
shape="pointer"
|
||||
color="#1890FF"
|
||||
:active="false"
|
||||
></v-series>
|
||||
<v-guide
|
||||
type="arc"
|
||||
:zIndex="0"
|
||||
:top="false"
|
||||
:start="arcGuide1Start"
|
||||
:end="arcGuide1End"
|
||||
:vStyle="arcGuide1Style"
|
||||
></v-guide>
|
||||
<v-guide
|
||||
type="arc"
|
||||
:zIndex="1"
|
||||
:start="arcGuide2Start"
|
||||
:end="getArcGuide2End"
|
||||
:vStyle="arcGuide2Style"
|
||||
></v-guide>
|
||||
<v-guide
|
||||
type="html"
|
||||
:position="htmlGuidePosition"
|
||||
:html="getHtmlGuideHtml()"
|
||||
></v-guide>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { registerShape } from 'viser-vue'
|
||||
|
||||
registerShape('point', 'pointer', {
|
||||
draw (cfg, container) {
|
||||
let point = cfg.points[0]
|
||||
point = this.parsePoint(point)
|
||||
const center = this.parsePoint({
|
||||
x: 0,
|
||||
y: 0
|
||||
})
|
||||
container.addShape('line', {
|
||||
attrs: {
|
||||
x1: center.x,
|
||||
y1: center.y,
|
||||
x2: point.x,
|
||||
y2: point.y + 15,
|
||||
stroke: cfg.color,
|
||||
lineWidth: 5,
|
||||
lineCap: 'round'
|
||||
}
|
||||
})
|
||||
return container.addShape('circle', {
|
||||
attrs: {
|
||||
x: center.x,
|
||||
y: center.y,
|
||||
r: 9.75,
|
||||
stroke: cfg.color,
|
||||
lineWidth: 4.5,
|
||||
fill: '#fff'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const scale = [{
|
||||
dataKey: 'value',
|
||||
min: 0,
|
||||
max: 9,
|
||||
tickInterval: 1,
|
||||
nice: false
|
||||
}]
|
||||
|
||||
const data = [
|
||||
{ value: 7.0 }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'DashChartDemo',
|
||||
props: {
|
||||
datasource: {
|
||||
type: Number,
|
||||
default: 7
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.datasource) {
|
||||
this.chartData = data
|
||||
} else {
|
||||
this.chartData = [
|
||||
{ value: this.datasource }
|
||||
]
|
||||
}
|
||||
this.getChartData()
|
||||
},
|
||||
watch: {
|
||||
datasource: function (val) {
|
||||
this.chartData = [
|
||||
{ value: val }
|
||||
]
|
||||
this.getChartData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getChartData () {
|
||||
if (this.chartData && this.chartData.length > 0) {
|
||||
this.abcd = this.chartData[0].value * 10
|
||||
} else {
|
||||
this.abcd = 70
|
||||
}
|
||||
},
|
||||
getHtmlGuideHtml () {
|
||||
return '<div style="width: 300px;text-align: center;">\n' +
|
||||
'<p style="font-size: 14px;color: #545454;margin: 0;">' + this.title + '</p>\n' +
|
||||
'<p style="font-size: 36px;color: #545454;margin: 0;">' + this.abcd + '%</p>\n' +
|
||||
'</div>'
|
||||
},
|
||||
getArcGuide2End () {
|
||||
return [this.chartData[0].value, 0.945]
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
chartData: [],
|
||||
height: 400,
|
||||
scale: scale,
|
||||
abcd: 70,
|
||||
axisLabel: {
|
||||
offset: -16,
|
||||
textStyle: {
|
||||
fontSize: 18,
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle'
|
||||
}
|
||||
},
|
||||
axisSubTickLine: {
|
||||
length: -8,
|
||||
stroke: '#fff',
|
||||
strokeOpacity: 1
|
||||
},
|
||||
axisTickLine: {
|
||||
length: -17,
|
||||
stroke: '#fff',
|
||||
strokeOpacity: 1
|
||||
},
|
||||
arcGuide1Start: [0, 0.945],
|
||||
arcGuide1End: [9, 0.945],
|
||||
arcGuide1Style: {
|
||||
stroke: '#CBCBCB',
|
||||
lineWidth: 18
|
||||
},
|
||||
arcGuide2Start: [0, 0.945],
|
||||
arcGuide2Style: {
|
||||
stroke: '#1890FF',
|
||||
lineWidth: 18
|
||||
},
|
||||
htmlGuidePosition: ['50%', '100%'],
|
||||
htmlGuideHtml: `
|
||||
<div style="width: 300px;text-align: center;">
|
||||
<p style="font-size: 14px;color: #545454;margin: 0;">${this.title}</p>
|
||||
<p style="font-size: 36px;color: #545454;margin: 0;">${this.abcd}%</p>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart
|
||||
height="254"
|
||||
:data="datasource"
|
||||
:forceFit="true"
|
||||
:padding="['auto', 'auto', '40', '50']">
|
||||
<v-tooltip />
|
||||
<v-axis />
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const data = []
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
data.push({
|
||||
x: `${i + 1}月`,
|
||||
y: Math.floor(Math.random() * 1000) + 200
|
||||
})
|
||||
}
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y
|
||||
})
|
||||
]
|
||||
const scale = [{
|
||||
dataKey: 'x',
|
||||
min: 2
|
||||
}, {
|
||||
dataKey: 'y',
|
||||
title: '时间',
|
||||
min: 1,
|
||||
max: 22
|
||||
}]
|
||||
|
||||
export default {
|
||||
name: 'Bar',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.datasource = data
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
datasource: [],
|
||||
scale,
|
||||
tooltip
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
|
||||
<v-tooltip/>
|
||||
<v-axis/>
|
||||
<v-legend/>
|
||||
<v-line position="type*y" color="x"/>
|
||||
<v-point position="type*y" color="x" :size="4" :v-style="style" :shape="'circle'"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DataSet } from '@antv/data-set'
|
||||
import { ChartEventMixins } from './mixins/ChartMixins'
|
||||
|
||||
export default {
|
||||
name: 'LineChartMultid',
|
||||
mixins: [ChartEventMixins],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ type: 'Jan', jeroOne: 7.0, jeroTwo: 3.9 },
|
||||
{ type: 'Feb', jeroOne: 6.9, jeroTwo: 4.2 },
|
||||
{ type: 'Mar', jeroOne: 9.5, jeroTwo: 5.7 },
|
||||
{ type: 'Apr', jeroOne: 14.5, jeroTwo: 8.5 },
|
||||
{ type: 'May', jeroOne: 18.4, jeroTwo: 11.9 },
|
||||
{ type: 'Jun', jeroOne: 21.5, jeroTwo: 15.2 },
|
||||
{ type: 'Jul', jeroOne: 25.2, jeroTwo: 17.0 },
|
||||
{ type: 'Aug', jeroOne: 26.5, jeroTwo: 16.6 },
|
||||
{ type: 'Sep', jeroOne: 23.3, jeroTwo: 14.2 },
|
||||
{ type: 'Oct', jeroOne: 18.3, jeroTwo: 10.3 },
|
||||
{ type: 'Nov', jeroOne: 13.9, jeroTwo: 6.6 },
|
||||
{ type: 'Dec', jeroOne: 9.6, jeroTwo: 4.8 }
|
||||
]
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default: () => ['jeroOne', 'jeroTwo']
|
||||
},
|
||||
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
|
||||
aliases: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
scale: [{
|
||||
type: 'cat',
|
||||
dataKey: 'x',
|
||||
min: 0,
|
||||
max: 1
|
||||
}],
|
||||
style: { stroke: '#fff', lineWidth: 1 }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
const dv = new DataSet.View().source(this.dataSource)
|
||||
dv.transform({
|
||||
type: 'fold',
|
||||
fields: this.fields,
|
||||
key: 'x',
|
||||
value: 'y'
|
||||
})
|
||||
const rows = dv.rows
|
||||
// 替换别名
|
||||
rows.forEach(row => {
|
||||
for (const item of this.aliases) {
|
||||
if (item.field === row.x) {
|
||||
row.x = item.alias
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
return rows
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-chart
|
||||
:forceFit="true"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:data="data"
|
||||
:scale="scale"
|
||||
:padding="0">
|
||||
<v-tooltip/>
|
||||
<v-interval
|
||||
:shape="['liquid-fill-gauge']"
|
||||
position="transfer*value"
|
||||
color=""
|
||||
:v-style="{
|
||||
lineWidth: 8,
|
||||
opacity: 0.75
|
||||
}"
|
||||
:tooltip="[
|
||||
'transfer*value',
|
||||
(transfer, value) => {
|
||||
return {
|
||||
name: transfer,
|
||||
value
|
||||
};
|
||||
},
|
||||
]"
|
||||
></v-interval>
|
||||
<v-guide
|
||||
v-for="(row, index) in data"
|
||||
:key="index"
|
||||
type="text"
|
||||
:top="true"
|
||||
:position="{
|
||||
gender: row.transfer,
|
||||
value: 45
|
||||
}"
|
||||
:content="row.value + '%'"
|
||||
:v-style="{
|
||||
fontSize: 100,
|
||||
textAlign: 'center',
|
||||
opacity: 0.75,
|
||||
}"
|
||||
/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const sourceDataConst = [
|
||||
{ transfer: '一月', value: 813 },
|
||||
{ transfer: '二月', value: 233 },
|
||||
{ transfer: '三月', value: 561 }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'Liquid',
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: sourceDataConst,
|
||||
scale: []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="antv-chart-mini">
|
||||
<div class="chart-wrapper" :style="{ height: 46 }">
|
||||
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :padding="[36, 0, 18, 0]">
|
||||
<v-tooltip/>
|
||||
<v-smooth-area position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'dayjs'
|
||||
|
||||
const sourceData = []
|
||||
const beginDay = new Date().getTime()
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
sourceData.push({
|
||||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
|
||||
y: Math.round(Math.random() * 10)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'MiniArea',
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// x 轴别名
|
||||
x: {
|
||||
type: String,
|
||||
default: 'x'
|
||||
},
|
||||
// y 轴别名
|
||||
y: {
|
||||
type: String,
|
||||
default: 'y'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: [],
|
||||
height: 100
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
scale () {
|
||||
return [
|
||||
{ dataKey: 'x', title: this.x, alias: this.x },
|
||||
{ dataKey: 'y', title: this.y, alias: this.y }
|
||||
]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.dataSource.length === 0) {
|
||||
this.data = sourceData
|
||||
} else {
|
||||
this.data = this.dataSource
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "chart";
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div :style="{'width':width===null?'auto':width+'px'}">
|
||||
<v-chart :forceFit="typeof width !== 'number'" :height="height" :data="data" padding="0">
|
||||
<v-tooltip/>
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'dayjs'
|
||||
|
||||
const sourceData = []
|
||||
const beginDay = new Date().getTime()
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
sourceData.push({
|
||||
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
|
||||
y: Math.round(Math.random() * 10)
|
||||
})
|
||||
}
|
||||
|
||||
const tooltip = [
|
||||
'x*y',
|
||||
(x, y) => ({
|
||||
name: x,
|
||||
value: y
|
||||
})
|
||||
]
|
||||
|
||||
const scale = [{
|
||||
dataKey: 'x',
|
||||
min: 2
|
||||
}, {
|
||||
dataKey: 'y',
|
||||
title: '时间',
|
||||
min: 1,
|
||||
max: 30
|
||||
}]
|
||||
|
||||
export default {
|
||||
name: 'MiniBar',
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 200
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.dataSource.length === 0) {
|
||||
this.data = sourceData
|
||||
} else {
|
||||
this.data = this.dataSource
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tooltip,
|
||||
data: [],
|
||||
scale
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "chart";
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="chart-mini-progress">
|
||||
<div class="target" :style="{ left: target + '%'}">
|
||||
<span :style="{ backgroundColor: color }"/>
|
||||
<span :style="{ backgroundColor: color }"/>
|
||||
</div>
|
||||
<div class="progress-wrapper">
|
||||
<div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height+'px' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MiniProgress',
|
||||
props: {
|
||||
target: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#13C2C2'
|
||||
},
|
||||
percentage: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-mini-progress {
|
||||
padding: 5px 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.target {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
span {
|
||||
border-radius: 100px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
width: 2px;
|
||||
|
||||
&:last-child {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.progress-wrapper {
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
|
||||
.progress {
|
||||
transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
|
||||
border-radius: 1px 0 0 1px;
|
||||
background-color: #1890ff;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
|
||||
<v-tooltip :showTitle="false" dataKey="item*percent"/>
|
||||
<v-axis/>
|
||||
<v-legend dataKey="item"/>
|
||||
<v-pie position="percent" color="item" :v-style="pieStyle" :label="labelConfig"/>
|
||||
<v-coord type="theta"/>
|
||||
</v-chart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ChartEventMixins } from './mixins/ChartMixins'
|
||||
const DataSet = require('@antv/data-set')
|
||||
|
||||
export default {
|
||||
name: 'Pie',
|
||||
mixins: [ChartEventMixins],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => [
|
||||
{ item: '示例一', count: 40 },
|
||||
{ item: '示例二', count: 21 },
|
||||
{ item: '示例三', count: 17 },
|
||||
{ item: '示例四', count: 13 },
|
||||
{ item: '示例五', count: 9 }
|
||||
]
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
scale: [{
|
||||
dataKey: 'percent',
|
||||
min: 0,
|
||||
formatter: '.0%'
|
||||
}],
|
||||
pieStyle: {
|
||||
stroke: '#fff',
|
||||
lineWidth: 1
|
||||
},
|
||||
labelConfig: ['percent', {
|
||||
formatter: (val, item) => {
|
||||
return item.point.item + ': ' + val
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
const dv = new DataSet.View().source(this.dataSource)
|
||||
// 计算数据百分比
|
||||
dv.transform({
|
||||
type: 'percent',
|
||||
field: 'count',
|
||||
dimension: 'item',
|
||||
as: 'percent'
|
||||
})
|
||||
return dv.rows
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,367 @@
|
||||
# 报表组件文档
|
||||
|
||||
## 柱状图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import Bar from '@/components/chart/Bar'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|------------|
|
||||
| title | string | | 报表标题 |
|
||||
| dataSource | array | ✔️ | 报表数据源 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"x": "1月",
|
||||
"y": 320
|
||||
},
|
||||
{
|
||||
"x": "2月",
|
||||
"y": 457
|
||||
},
|
||||
{
|
||||
"x": "3月",
|
||||
"y": 182
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
##### 代码示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<bar title="柱状图" :dataSource="dataSource" :height="420"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Bar from '@/components/chart/Bar'
|
||||
|
||||
export default {
|
||||
name: 'ChartDemo',
|
||||
components: {
|
||||
Bar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataSource: [
|
||||
{
|
||||
"x": "1月",
|
||||
"y": 320
|
||||
},
|
||||
{
|
||||
"x": "2月",
|
||||
"y": 457
|
||||
},
|
||||
{
|
||||
"x": "3月",
|
||||
"y": 182
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
```
|
||||
|
||||
## 多列柱状图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import BarMultid from '@/components/chart/BarMultid'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|------------|
|
||||
| title | string | | 报表标题 |
|
||||
| fields | array | | 主列字段列表 |
|
||||
| dataSource | array | | 报表数据源 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
|
||||
##### fields 示例
|
||||
|
||||
```json
|
||||
["Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug."]
|
||||
```
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "jero", // 列名
|
||||
"Jan.": 18.9,
|
||||
"Feb.": 28.8,
|
||||
"Mar.": 39.3,
|
||||
"Apr.": 81.4,
|
||||
"May": 47,
|
||||
"Jun.": 20.3,
|
||||
"Jul.": 24,
|
||||
"Aug.": 35.6
|
||||
},
|
||||
{
|
||||
"type": "jeroTwo",
|
||||
"Jan.": 12.4,
|
||||
"Feb.": 23.2,
|
||||
"Mar.": 34.5,
|
||||
"Apr.": 99.7,
|
||||
"May": 52.6,
|
||||
"Jun.": 35.5,
|
||||
"Jul.": 37.4,
|
||||
"Aug.": 42.4
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 迷你柱状图
|
||||
|
||||
不带标题和数据轴的柱状图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import MiniBar from '@/components/chart/MiniBar'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|---------------|
|
||||
| width | number | | 报表宽度度,默认自适应宽度 |
|
||||
| height | number | | 报表高度,默认200 |
|
||||
| dataSource | array | | 报表数据源 |
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"x": "1月",
|
||||
"y": 320
|
||||
},
|
||||
{
|
||||
"x": "2月",
|
||||
"y": 457
|
||||
},
|
||||
{
|
||||
"x": "3月",
|
||||
"y": 182
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 面积图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import AreaChartTy from '@/components/chart/AreaChartTy'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|------------|
|
||||
| title | string | | 报表标题 |
|
||||
| dataSource | array | ✔️ | 报表数据源 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
| lineSize | number | | 线的粗细,默认2 |
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"x": "1月",
|
||||
"y": 320
|
||||
},
|
||||
{
|
||||
"x": "2月",
|
||||
"y": 457
|
||||
},
|
||||
{
|
||||
"x": "3月",
|
||||
"y": 182
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 多行折线图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import LineChartMultid from '@/components/chart/LineChartMultid'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|------------|
|
||||
| title | string | | 报表标题 |
|
||||
| fields | array | | 主列字段列表 |
|
||||
| dataSource | array | | 报表数据源 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
|
||||
##### fields 示例
|
||||
|
||||
```json
|
||||
["jero", "jeroTwo"]
|
||||
```
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "Jan", // 列名
|
||||
"jero": 7,
|
||||
"jeroTwo": 3.9
|
||||
},
|
||||
{ "type": "Feb", "jero": 6.9, "jeroTwo": 4.2 },
|
||||
{ "type": "Mar", "jero": 9.5, "jeroTwo": 5.7 },
|
||||
{ "type": "Apr", "jero": 14.5, "jeroTwo": 8.5 },
|
||||
{ "type": "May", "jero": 18.4, "jeroTwo": 11.9 },
|
||||
{ "type": "Jun", "jero": 21.5, "jeroTwo": 15.2 },
|
||||
{ "type": "Jul", "jero": 25.2, "jeroTwo": 17 },
|
||||
{ "type": "Aug", "jero": 26.5, "jeroTwo": 16.6 },
|
||||
{ "type": "Sep", "jero": 23.3, "jeroTwo": 14.2 },
|
||||
{ "type": "Oct", "jero": 18.3, "jeroTwo": 10.3 },
|
||||
{ "type": "Nov", "jero": 13.9, "jeroTwo": 6.6 },
|
||||
{ "type": "Dec", "jero": 9.6, "jeroTwo": 4.8 }
|
||||
]
|
||||
```
|
||||
|
||||
## 饼状图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import Pie from '@/components/chart/Pie'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|------------|
|
||||
| dataSource | array | | 报表数据源 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
// 所有的 percent 相加等于 100
|
||||
{ "item": "一月", "percent": 40 },
|
||||
{ "item": "二月", "percent": 21 },
|
||||
{ "item": "三月", "percent": 17 },
|
||||
{ "item": "四月", "percent": 13 },
|
||||
{ "item": "五月", "percent": 9 }
|
||||
]
|
||||
```
|
||||
|
||||
## 雷达图
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import Radar from '@/components/chart/Radar'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|------------|
|
||||
| dataSource | array | | 报表数据源 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
// score 最小值为 0,最大值为 100
|
||||
{ "item": "一月", "score": 40 },
|
||||
{ "item": "二月", "score": 20 },
|
||||
{ "item": "三月", "score": 67 },
|
||||
{ "item": "四月", "score": 43 },
|
||||
{ "item": "五月", "score": 90 }
|
||||
]
|
||||
```
|
||||
|
||||
## 进度条
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import MiniProgress from '@/components/chart/MiniProgress'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|------------|--------|----|-------------------|
|
||||
| percentage | number | | 当前进度百分比,默认0,最高100 |
|
||||
| target | number | | 目标值,默认10 |
|
||||
| height | number | | 进度条高度,默认10 |
|
||||
| color | string | | 进度条颜色,默认 #13C2C2 |
|
||||
|
||||
## 仪表盘
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import DashChartDemo from '@/components/chart/DashChartDemo'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|--------|----|----------------|
|
||||
| title | string | | 报表标题 |
|
||||
| value | number | | 当前值,默认6.7,最大为9 |
|
||||
| height | number | | 报表高度,默认254 |
|
||||
|
||||
## 排名列表
|
||||
|
||||
##### 引用方式
|
||||
|
||||
```js
|
||||
import RankList from '@/components/chart/RankList'
|
||||
```
|
||||
|
||||
##### 参数列表
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|--------|----|--------------|
|
||||
| title | string | | 报表标题 |
|
||||
| list | array | | 排名列表数据 |
|
||||
| height | number | | 报表高度,默认自适应高度 |
|
||||
|
||||
##### list 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "北京朝阳 1 号店",
|
||||
"total": 1981
|
||||
},
|
||||
{ "name": "北京朝阳 2 号店", "total": 1359 },
|
||||
{ "name": "北京朝阳 3 号店", "total": 1354 },
|
||||
{ "name": "北京朝阳 4 号店", "total": 263 },
|
||||
{ "name": "北京朝阳 5 号店", "total": 446 },
|
||||
{ "name": "北京朝阳 6 号店", "total": 796 }
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<v-chart :forceFit="true" :height="height" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
|
||||
<v-tooltip></v-tooltip>
|
||||
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid"/>
|
||||
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid"/>
|
||||
<v-legend dataKey="user" marker="circle" :offset="30"/>
|
||||
<v-coord type="polar" radius="0.8"/>
|
||||
<v-line position="item*score" color="user" :size="2"/>
|
||||
<v-point position="item*score" color="user" :size="4" shape="circle"/>
|
||||
</v-chart>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const axis1Opts = {
|
||||
dataKey: 'item',
|
||||
line: null,
|
||||
tickLine: null,
|
||||
grid: {
|
||||
lineStyle: {
|
||||
lineDash: null
|
||||
},
|
||||
hideFirstLine: false
|
||||
}
|
||||
}
|
||||
const axis2Opts = {
|
||||
dataKey: 'score',
|
||||
line: null,
|
||||
tickLine: null,
|
||||
grid: {
|
||||
type: 'polygon',
|
||||
lineStyle: {
|
||||
lineDash: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const scale = [
|
||||
{
|
||||
dataKey: 'score',
|
||||
min: 0,
|
||||
max: 100
|
||||
}, {
|
||||
dataKey: 'user',
|
||||
alias: '类型'
|
||||
}
|
||||
]
|
||||
|
||||
const sourceData = [
|
||||
{ item: '示例一', score: 40 },
|
||||
{ item: '示例二', score: 20 },
|
||||
{ item: '示例三', score: 67 },
|
||||
{ item: '示例四', score: 43 },
|
||||
{ item: '示例五', score: 90 }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'Radar',
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
axis1Opts,
|
||||
axis2Opts,
|
||||
scale,
|
||||
data: sourceData
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataSource (newVal) {
|
||||
if (newVal.length === 0) {
|
||||
this.data = sourceData
|
||||
} else {
|
||||
this.data = newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="rank">
|
||||
<h4 class="title">{{ title }}</h4>
|
||||
<ul class="list" :style="{height:height?`${height}px`:'auto',overflow:'auto'}">
|
||||
<li :key="index" v-for="(item, index) in list">
|
||||
<span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ item.total }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RankList',
|
||||
// ['title', 'list']
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.rank {
|
||||
padding: 0 32px 32px 72px;
|
||||
|
||||
.list {
|
||||
margin: 25px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-top: 16px;
|
||||
|
||||
span {
|
||||
color: rgba(0, 0, 0, .65);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
&:first-child {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-right: 24px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
&.active {
|
||||
background-color: #314659;
|
||||
color: #fff;
|
||||
}
|
||||
&:last-child {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile .rank {
|
||||
padding: 0 32px 32px 32px;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-chart :forceFit="true" :height="height" :data="data">
|
||||
<v-coord type="rect" direction="LB" />
|
||||
<v-tooltip />
|
||||
<v-legend />
|
||||
<v-axis dataKey="State" :label="label" />
|
||||
<v-stack-bar position="State*流程数量" color="流程状态" />
|
||||
</v-chart>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const DataSet = require('@antv/data-set')
|
||||
|
||||
export default {
|
||||
name: 'StackBar',
|
||||
props: {
|
||||
dataSource: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => [
|
||||
{ State: '请假', 流转中: 25, 已归档: 18 },
|
||||
{ State: '出差', 流转中: 30, 已归档: 20 },
|
||||
{ State: '加班', 流转中: 38, 已归档: 42 },
|
||||
{ State: '用车', 流转中: 51, 已归档: 67 }
|
||||
]
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
label: { offset: 12 }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
data () {
|
||||
const dv = new DataSet.View().source(this.dataSource)
|
||||
dv.transform({
|
||||
type: 'fold',
|
||||
fields: ['流转中', '已归档'],
|
||||
key: '流程状态',
|
||||
value: '流程数量',
|
||||
retains: ['State']
|
||||
})
|
||||
return dv.rows
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart
|
||||
:height="height"
|
||||
:data="data"
|
||||
:scale="scale"
|
||||
:forceFit="true"
|
||||
:padding="['auto', 'auto', '40', '50']">
|
||||
<v-tooltip/>
|
||||
<v-axis/>
|
||||
<v-bar position="x*y"/>
|
||||
</v-chart>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Bar',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
x: {
|
||||
type: String,
|
||||
default: 'x'
|
||||
},
|
||||
y: {
|
||||
type: String,
|
||||
default: 'y'
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 254
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
scale () {
|
||||
return [
|
||||
{ dataKey: 'x', title: this.x, alias: this.x },
|
||||
{ dataKey: 'y', title: this.y, alias: this.y }
|
||||
]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// this.getMonthBar()
|
||||
},
|
||||
methods: {
|
||||
// getMonthBar() {
|
||||
// this.$http.get('/analysis/month-bar')
|
||||
// .then(res => {
|
||||
// this.data = res.result
|
||||
// })
|
||||
// }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="chart-trend">
|
||||
{{ term }}
|
||||
<span>{{ rate }}%</span>
|
||||
<span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Trend',
|
||||
props: {
|
||||
// 同title
|
||||
term: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
// 百分比
|
||||
percentage: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
type: {
|
||||
type: Boolean,
|
||||
default: null
|
||||
},
|
||||
target: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
fixed: {
|
||||
type: Number,
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
trend: (this.type && 'up') || 'down',
|
||||
rate: this.percentage
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const type = this.type === null ? this.value >= this.target : this.type
|
||||
this.trend = type ? 'up' : 'down'
|
||||
this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-trend {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
.trend-icon {
|
||||
font-size: 12px;
|
||||
|
||||
&.up, &.down {
|
||||
margin-left: 4px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
transform: scale(.83);
|
||||
}
|
||||
}
|
||||
|
||||
&.up {
|
||||
color: #f5222d;
|
||||
}
|
||||
&.down {
|
||||
color: #52c41a;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
.antv-chart-mini {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.chart-wrapper {
|
||||
position: absolute;
|
||||
bottom: -28px;
|
||||
width: 100%;
|
||||
|
||||
/* margin: 0 -5px;
|
||||
overflow: hidden;*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export const ChartEventMixins = {
|
||||
methods: {
|
||||
handleClick (event, chart) {
|
||||
this.handleEvent('click', event, chart)
|
||||
},
|
||||
handleEvent (eventName, event, chart) {
|
||||
this.$emit(eventName, event, chart)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
<template>
|
||||
<a-form-model :model="formInline" v-if="isFormInline" class="form-add" :rules="rules" ref="ruleForm">
|
||||
<div v-for="(val,index) in dataList" :key="index">
|
||||
<div v-for="(ol, index) in Object.keys(val)" :key="index">
|
||||
<div class="header-text">
|
||||
{{ol}}
|
||||
</div>
|
||||
<a-row :gutter="24">
|
||||
<div v-for="(item, index) in val[ol]" :key="index">
|
||||
<a-col :span="12" v-if="item.field_show_type === FieldType.TEXT_STRING.value || item.field_show_type === FieldType.TEXT_LINK.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<a-input class="box-input"
|
||||
:disabled="disabled"
|
||||
v-model="formInline[item.db_field_name]"
|
||||
:maxLength="50"
|
||||
:placeholder="$t('pleaseEnter')+item.db_field_txt"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.TEXT_NUMBER.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<a-input-number class="box-input"
|
||||
:placeholder="$t('pleaseEnter')+item.db_field_txt"
|
||||
:disabled="disabled"
|
||||
v-model="formInline[item.db_field_name]" :min="1" :max="99999999"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.PULL_SINGLE.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<j-dict-select-tag class="box-input" v-model="formInline[item.db_field_name]"
|
||||
:disabled="disabled"
|
||||
@input="handleInput(item.db_field_name)"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
:type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.PULL_MORE.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model-multi" :prop="item.db_field_name">
|
||||
<j-multi-select-tag class="box-input" v-model="formInline[item.db_field_name]"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
:type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.DATE_SINGLE.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<a-date-picker class="box-input"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
@change="dateChange(item)"
|
||||
:getCalendarContainer="(trigger) => trigger.parentNode"
|
||||
format="YYYY-MM-DD"
|
||||
v-model="formInline[item.db_field_name]"
|
||||
:disabled="disabled"
|
||||
style="width: 100%"/>
|
||||
<!-- :disabledDate="disabledDate"-->
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.DATE_MORE.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<a-range-picker class="box-input" v-model="formInline[item.db_field_name]"
|
||||
:disabled="disabled"
|
||||
:getCalendarContainer="(trigger) => trigger.parentNode"
|
||||
@change="onChange(item.db_field_name)"></a-range-picker>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.FILE.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<a-button type="primary" class="button-text"
|
||||
@click="clickButtonToUpload(item)">
|
||||
{{
|
||||
(formInline[item.db_field_name] === 'null' || formInline[item.db_field_name] === ''
|
||||
|| formInline[item.db_field_name] === null || formInline[item.db_field_name] === undefined)
|
||||
? $t('uploadFile.clickUpload')
|
||||
: $t('uploadFile.viewUploadedFiles')
|
||||
}}
|
||||
</a-button>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24" v-else-if="item.field_show_type === FieldType.TEXT.value || item.field_show_type === 'text'">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model-textarea" :prop="item.db_field_name">
|
||||
<a-textarea
|
||||
:placeholder="$t('pleaseEnter')+item.db_field_txt"
|
||||
:disabled="disabled"
|
||||
:maxLength="500"
|
||||
v-model="formInline[item.db_field_name]" :rows="4"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24" v-else-if="item.field_show_type === FieldType.PERSON.value || item.field_show_type == 'sel_user'">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<personnel-selection :query="item"
|
||||
v-if="isFormInline"
|
||||
:personneQuery="formInline"
|
||||
@change="personnelSelectionChange"
|
||||
:disabled="disabled"
|
||||
v-model="formInline[item.db_field_name]"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="24" v-else-if="item.field_show_type === FieldType.STANDARD.value">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<standard-selection :query="item"
|
||||
:disabled="disabled"
|
||||
:standard="formInline"
|
||||
@change="standardSelectionChange"
|
||||
v-model="formInline[item.db_field_name]"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === 'RADIO'">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<j-dict-select-tag v-model="formInline[item.db_field_name]"
|
||||
:disabled="disabled"
|
||||
@input="handleInput(item.db_field_name)"
|
||||
:placeholder="$t('selectStatus')"
|
||||
:type="'radio'" :triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === FieldType.TREE.value">
|
||||
<div class="box-title-text" :title="item.db_field_txt">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.db_field_name">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="formInline[item.db_field_name]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
style="width: 100%"
|
||||
:tree-data="item.tree"
|
||||
tree-checkable
|
||||
:placeholder="$t('PleaseSelect')+item.db_field_txt"
|
||||
/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="12" v-else-if="item.field_show_type === 'CHECKBOX'">
|
||||
<div class="box-title-text">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.field_must_input === 1">*</span>
|
||||
<span class="title-text-text" :title="item.db_field_txt">{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model-multi" :prop="item.db_field_name">
|
||||
<j-multi-select-tag class="box-input" v-model="formInline[item.db_field_name]"
|
||||
:disabled="disabled"
|
||||
:placeholder="$t('selectStatus')" :type="'checkbox'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</a-col>
|
||||
|
||||
</div>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<upload-file ref="uploadFile" @change="uploadFileChange" :return-url="false"></upload-file>
|
||||
</a-form-model>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
import JDictSelectTag from '../dict/JDictSelectTag'
|
||||
import JMultiSelectTag from '../dict/JMultiSelectTag'
|
||||
import UploadFile from '../UploadFile'
|
||||
// 选择标准选择器
|
||||
import StandardSelection from '../selection/StandardSelection'
|
||||
import { FieldType } from '../../enums/commonEnums'
|
||||
|
||||
export default {
|
||||
name: 'AddForm',
|
||||
components: { StandardSelection, JDictSelectTag, JMultiSelectTag, UploadFile },
|
||||
props: {
|
||||
url: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
flag: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
FieldType,
|
||||
loading: false,
|
||||
formInline: {},
|
||||
isFormInline: false,
|
||||
rules: {},
|
||||
confirmLoading: false,
|
||||
type: 'add',
|
||||
dataList: [],
|
||||
ruleList: [],
|
||||
uploadName: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add () {
|
||||
this.formInline = {}
|
||||
this.type = 'add'
|
||||
// this.getForm()
|
||||
this.dataList = [
|
||||
{
|
||||
基本信息: [
|
||||
{
|
||||
area: '基本信息',
|
||||
db_field_name: 'serial_number',
|
||||
db_field_txt: '编号',
|
||||
db_length: 200,
|
||||
dict_field: '',
|
||||
field_must_input: '1',
|
||||
field_show_type: '1',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
area: '基本信息',
|
||||
db_field_name: 'state',
|
||||
db_field_txt: '状态',
|
||||
db_length: 36,
|
||||
dict_field: 'state',
|
||||
field_must_input: '1',
|
||||
field_show_type: '3',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
area: '基本信息',
|
||||
db_field_name: 'standard_system',
|
||||
db_field_txt: '标准体系',
|
||||
db_length: 100,
|
||||
dict_field: 'standard_system',
|
||||
field_must_input: '0',
|
||||
field_show_type: '3',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
area: '基本信息',
|
||||
db_field_name: 'corresponding_standard',
|
||||
db_field_txt: '对应标准',
|
||||
db_length: 200,
|
||||
dict_field: '',
|
||||
field_must_input: '0',
|
||||
field_show_type: '10',
|
||||
tree: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
文本信息: [
|
||||
{
|
||||
area: '文本信息',
|
||||
db_field_name: 'release_draft',
|
||||
db_field_txt: '发言稿(必读)',
|
||||
db_length: 3000,
|
||||
dict_field: '',
|
||||
field_must_input: '0',
|
||||
field_show_type: '7',
|
||||
flag: 1,
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
area: '文本信息',
|
||||
db_field_name: 'release_draft',
|
||||
db_field_txt: '发言稿(必读)',
|
||||
db_length: 3000,
|
||||
dict_field: '',
|
||||
field_must_input: '0',
|
||||
field_show_type: '7',
|
||||
flag: 1,
|
||||
tree: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
this.ruleList = this.dataList
|
||||
this.integrateData()
|
||||
},
|
||||
edit (item) {
|
||||
this.type = 'edit'
|
||||
this.loading = true
|
||||
this.getForm(() => {
|
||||
getAction(this.url.getDocumentInfo, { id: item.id }).then((res) => {
|
||||
if (res.success) {
|
||||
this.formInline = res.result[0]
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取表单列表
|
||||
getForm (callBack) {
|
||||
this.confirmLoading = true
|
||||
const params = {
|
||||
flag: this.flag,
|
||||
type: this.type
|
||||
}
|
||||
getAction(this.url.getAddForm, params).then((res) => {
|
||||
if (res.success) {
|
||||
this.dataList = res.result
|
||||
this.ruleList = res.result
|
||||
this.integrateData()
|
||||
callBack && callBack()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 设置表单校验
|
||||
integrateData () {
|
||||
this.isFormInline = false
|
||||
const ruleList = []
|
||||
this.ruleList.forEach(res => {
|
||||
Object.keys(res).forEach(val => {
|
||||
res[val].forEach(ol => {
|
||||
ruleList.push(ol)
|
||||
})
|
||||
})
|
||||
})
|
||||
const rules = {}
|
||||
ruleList.forEach((res, index) => {
|
||||
const rule = []
|
||||
if (res.field_must_input === 1) {
|
||||
if (res.field_show_type === '1') {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.db_field_txt + this.$t('cannotEmpty'),
|
||||
trigger: 'blur'
|
||||
})
|
||||
} else if (res.field_show_type === '4' || res.field_show_type === '6' ||
|
||||
res.field_show_type === '5' || res.field_show_type === '7'
|
||||
) {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.db_field_txt + this.$t('cannotEmpty'),
|
||||
trigger: 'change'
|
||||
})
|
||||
} else if (res.field_show_type === '2') {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.db_field_txt + this.$t('cannotEmpty'),
|
||||
trigger: 'blur'
|
||||
})
|
||||
} else if (res.field_show_type === '8' || res.field_show_type === '9' || res.field_show_type === '10' || res.field_show_type === '11') {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.db_field_txt + this.$t('cannotEmpty'),
|
||||
trigger: 'blur'
|
||||
})
|
||||
} else if (res.field_show_type === '3') {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.db_field_txt + this.$t('cannotEmpty'),
|
||||
trigger: 'change'
|
||||
})
|
||||
}
|
||||
}
|
||||
if (res.field_show_type === '1' || res.field_show_type === '8' || res.field_show_type === '9' ||
|
||||
res.field_show_type === '10' || res.field_show_type === '11') {
|
||||
rule.push({
|
||||
max: res.db_length,
|
||||
message: res.db_field_txt + this.$t('cannotExceed') + res.db_length + this.$t('characters'),
|
||||
trigger: 'blur'
|
||||
})
|
||||
}
|
||||
if (rule.length > 0) {
|
||||
rules[res.db_field_name] = rule
|
||||
}
|
||||
})
|
||||
this.rules = rules
|
||||
this.isFormInline = true
|
||||
this.confirmLoading = false
|
||||
},
|
||||
// 点击点击上传按钮
|
||||
clickButtonToUpload (item) {
|
||||
this.$refs.uploadFile.open(this.formInline[item.db_field_name])
|
||||
this.uploadName = item.db_field_name
|
||||
},
|
||||
// 文件上传改变后的回调
|
||||
uploadFileChange (data) {
|
||||
const attIdList = []
|
||||
if (data && data.length > 0) {
|
||||
data.map(item => {
|
||||
attIdList.push(item.id || data.name)
|
||||
})
|
||||
}
|
||||
/** 赋值给当前对应的表单文件 */
|
||||
this.formInline[this.uploadName] = attIdList.join(',')
|
||||
this.formInline = { ...this.formInline }
|
||||
},
|
||||
// 标准选择变化
|
||||
standardSelectionChange (value, id) {
|
||||
this.formInline[value + '_id'] = id
|
||||
this.formInline = { ...this.formInline }
|
||||
},
|
||||
// 人员选择变化
|
||||
personnelSelectionChange (value, id) {
|
||||
this.formInline[value + '_id'] = id
|
||||
this.formInline = { ...this.formInline }
|
||||
},
|
||||
submit () {
|
||||
this.$refs.ruleForm.validate(valid => {
|
||||
if (valid) {
|
||||
const formInline = JSON.parse(JSON.stringify(this.formInline))
|
||||
Object.keys(formInline).forEach(res => {
|
||||
if (formInline[res] instanceof Array) {
|
||||
formInline[res] = formInline[res].join(',')
|
||||
}
|
||||
})
|
||||
this.$emit('submitFormData', formInline)
|
||||
} else {
|
||||
this.$emit('addFormWarning')
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.form-add{
|
||||
margin-bottom: 40px;
|
||||
/deep/ .ant-form-item-label {
|
||||
width: 130px;
|
||||
}
|
||||
/deep/ .ant-form-item-control-wrapper {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.item-model{
|
||||
width: calc(100% - 130px);
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
height: 40px;
|
||||
margin-bottom: 24px;
|
||||
/deep/ .ant-form-item-control-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.item-model-textarea {
|
||||
width: calc(100% - 130px);
|
||||
display: inline-block;
|
||||
margin-top: 2px;
|
||||
/deep/ .ant-form-item-control-wrapper {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
.box-input {
|
||||
/deep/ .ant-select-selection--single {
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-select-selection--multiple {
|
||||
height: 38px;
|
||||
.ant-select-selection__rendered > ul > li{
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
/deep/ .ant-select-selection__rendered {
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-calendar-picker {
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-calendar-picker-input {
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-input-number-input-wrap {
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.box-title-text {
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
/*align-items: center;*/
|
||||
.title-text {
|
||||
width: 114px;
|
||||
text-align: right;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
}
|
||||
}
|
||||
.box-input {
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: red;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.title-text-text {
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-left: 15px;
|
||||
/*border-bottom: 1px #d9d9d9 dashed;*/
|
||||
height: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.formAdd {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
height: 38px;
|
||||
width: calc(100% - 100px);
|
||||
line-height: 38px;
|
||||
background: #fff;
|
||||
border: 1px #1890ff solid;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<a-form layout="inline" @keyup.enter.native="searchQuery">
|
||||
<a-row :gutter="24">
|
||||
<a-col :md="6" :sm="8" v-for="(item,index) in searchList" :key="index" style="line-height: 48px">
|
||||
<template v-if="index < 3">
|
||||
<!-- 树形选择器 -->
|
||||
<div class="box-title-text" v-if="item.field_show_type === FieldType.TREE.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
class="box-input"
|
||||
v-model="queryParam[item.db_field_name]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
:tree-data="item.tree"
|
||||
tree-checkable
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
/>
|
||||
</div>
|
||||
<!-- 输入框 -->
|
||||
<div class="box-title-text"
|
||||
v-else-if="[FieldType.TEXT_STRING.value, FieldType.TEXT.value,FieldType.PERSON.value,
|
||||
FieldType.STANDARD.value, FieldType.TEXT_LINK.value].includes(item.field_show_type)">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-input class="box-input" :placeholder="$t('pleaseEnter')+item.db_field_txt"
|
||||
v-model="queryParam[item.db_field_name]"></a-input>
|
||||
</div>
|
||||
<!-- 数字输入框 -->
|
||||
<div class="box-title-text" v-else-if="item.field_show_type === FieldType.TEXT_NUMBER.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-input-number class="box-input" :placeholder="$t('pleaseEnter')+item.db_field_txt"
|
||||
v-model="queryParam[item.db_field_name]" :min="1" :max="99999999"/>
|
||||
</div>
|
||||
<!-- 字典多选框 -->
|
||||
<div class="box-title-text" v-else-if="item.field_show_type === FieldType.PULL_MORE.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<j-multi-select-tag class="box-input" v-model="queryParam[item.db_field_name]"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt" :type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</div>
|
||||
<!-- 日期区间 -->
|
||||
<div class="box-title-text" v-else-if="[FieldType.DATE_SINGLE.value, FieldType.DATE_MORE.value].includes(item.field_show_type)">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-range-picker class="box-input" v-model="queryParam[item.db_field_name]"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
format="YYYY-MM-DD" value-format="YYYY-MM-DD"
|
||||
@change="onChange(item.db_field_name)"></a-range-picker>
|
||||
</div>
|
||||
<!-- 字典单选框 -->
|
||||
<div class="box-title-text" v-else-if="item.field_show_type === FieldType.PULL_SINGLE.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<j-dict-select-tag class="box-input" v-model="queryParam[item.db_field_name]"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
:type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</div>
|
||||
<!-- 单选框 -->
|
||||
<!-- <div class="box-title-text" v-else-if="item.field_show_type === '12'">-->
|
||||
<!-- <div class="title-text" :title="item.db_field_txt">-->
|
||||
<!-- <span>{{item.db_field_txt}}</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <a-select :placeholder="$t('pleaseSelect')+item.db_field_txt"-->
|
||||
<!-- class="box-input"-->
|
||||
<!-- :getPopupContainer="triggerNode=> triggerNode.parentNode"-->
|
||||
<!-- allowClear-->
|
||||
<!-- v-model="queryParam[item.db_field_name]">-->
|
||||
<!-- <a-select-option v-for="(item, key) in item.option"-->
|
||||
<!-- :key="key"-->
|
||||
<!-- :value="item.value">-->
|
||||
<!-- <span style="display: inline-block;width: 100%" :title=" item.name">-->
|
||||
<!-- {{ item.name }}-->
|
||||
<!-- </span>-->
|
||||
<!-- </a-select-option>-->
|
||||
<!-- </a-select>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
|
||||
<template v-if="index >= 3 && toggleSearchStatus">
|
||||
<div class="box-title-text" v-if="item.field_show_type === FieldType.TREE.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
class="box-input"
|
||||
v-model="queryParam[item.db_field_name]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
:tree-data="item.tree"
|
||||
tree-checkable
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
/>
|
||||
</div>
|
||||
<div class="box-title-text" v-else-if="[FieldType.TEXT_STRING.value, FieldType.TEXT.value,FieldType.PERSON.value,
|
||||
FieldType.STANDARD.value, FieldType.TEXT_LINK.value].includes(item.field_show_type)">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-input class="box-input" :placeholder="$t('pleaseEnter')+item.db_field_txt"
|
||||
v-model="queryParam[item.db_field_name]"></a-input>
|
||||
</div>
|
||||
<div class="box-title-text" v-else-if="item.field_show_type === FieldType.TEXT_NUMBER.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-input-number class="box-input" :placeholder="$t('pleaseEnter')+item.db_field_txt"
|
||||
v-model="queryParam[item.db_field_name]" :min="1" :max="99999999"/>
|
||||
</div>
|
||||
<div class="box-title-text" v-else-if="item.field_show_type === FieldType.PULL_MORE.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<j-multi-select-tag class="box-input" v-model="queryParam[item.db_field_name]"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt" :type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</div>
|
||||
<div class="box-title-text" v-else-if="[FieldType.DATE_SINGLE.value, FieldType.DATE_MORE.value].includes(item.field_show_type)">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<a-range-picker class="box-input" v-model="queryParam[item.db_field_name]"
|
||||
format="YYYY-MM-DD" value-format="YYYY-MM-DD"
|
||||
@change="onChange(item.db_field_name)"></a-range-picker>
|
||||
</div>
|
||||
<div class="box-title-text" v-else-if="item.field_show_type === FieldType.PULL_SINGLE.value">
|
||||
<div class="title-text" :title="item.db_field_txt">
|
||||
<span>{{item.db_field_txt}}</span>
|
||||
</div>
|
||||
<j-dict-select-tag class="box-input" v-model="queryParam[item.db_field_name]"
|
||||
:placeholder="$t('pleaseSelect')+item.db_field_txt"
|
||||
:type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dict_field"/>
|
||||
</div>
|
||||
<!-- <div class="box-title-text" v-else-if="item.field_show_type === '12'">-->
|
||||
<!-- <div class="title-text" :title="item.db_field_txt">-->
|
||||
<!-- <span>{{item.db_field_txt}}</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <a-select :placeholder="$t('pleaseSelect')+item.db_field_txt"-->
|
||||
<!-- class="box-input"-->
|
||||
<!-- :getPopupContainer="triggerNode=> triggerNode.parentNode"-->
|
||||
<!-- allowClear-->
|
||||
<!-- v-model="queryParam[item.db_field_name]">-->
|
||||
<!-- <a-select-option v-for="(item, key) in item.option"-->
|
||||
<!-- :key="key"-->
|
||||
<!-- :value="item.value">-->
|
||||
<!-- <span style="display: inline-block;width: 100%" :title=" item.name">-->
|
||||
<!-- {{ item.name }}-->
|
||||
<!-- </span>-->
|
||||
<!-- </a-select-option>-->
|
||||
<!-- </a-select>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
|
||||
</a-col>
|
||||
<span style="float: right;overflow: hidden;margin-right: 11px" class="table-page-search-submitButtons">
|
||||
<a-col :md="6" :sm="24">
|
||||
<a @click="handleToggleSearch" v-if="searchList.length > 3">
|
||||
{{ !toggleSearchStatus ? $t('open') : $t('putAway') }}
|
||||
<a-icon :type="toggleSearchStatus ? 'up' : 'down'"/>
|
||||
</a>
|
||||
<a-button class="box-button" style="margin-left: 8px" @click="searchReset">{{$t('reset')}}</a-button>
|
||||
<a-button class="box-button" style="margin-left: 8px" type="primary" @click="searchQuery">{{$t('query')}}</a-button>
|
||||
</a-col>
|
||||
</span>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
import JDictSelectTag from '../dict/JDictSelectTag'
|
||||
import JMultiSelectTag from '../dict/JMultiSelectTag'
|
||||
import { FieldType } from '../../enums/commonEnums'
|
||||
|
||||
export default {
|
||||
name: 'Search',
|
||||
components: { JDictSelectTag, JMultiSelectTag },
|
||||
props: {
|
||||
url: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
flag: { // 暂时不清楚flag的用处,是要查searchList的时候传给后端的
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
searchQueryList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
FieldType,
|
||||
queryParam: {},
|
||||
toggleSearchStatus: false,
|
||||
searchList: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// this.getSearch()
|
||||
this.searchList = [
|
||||
{
|
||||
db_field_name: 'serial_number',
|
||||
db_field_txt: '编号',
|
||||
dict_field: '',
|
||||
field_show_type: '1',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
db_field_name: 'title',
|
||||
db_field_txt: '标题',
|
||||
dict_field: '',
|
||||
field_show_type: '1',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
db_field_name: 'state',
|
||||
db_field_txt: '状态',
|
||||
dict_field: 'state',
|
||||
field_show_type: '3',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
db_field_name: 'region',
|
||||
db_field_txt: '适用地区',
|
||||
dict_field: 'region',
|
||||
field_show_type: '4',
|
||||
tree: []
|
||||
},
|
||||
// {
|
||||
// db_field_name: 'technology_territory',
|
||||
// db_field_txt: '技术领域',
|
||||
// dict_field: 'technology_territory',
|
||||
// field_show_type: '0',
|
||||
// tree: []
|
||||
// },
|
||||
{
|
||||
db_field_name: 'issue_time',
|
||||
db_field_txt: '发布日期',
|
||||
dict_field: '',
|
||||
field_show_type: '5',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
db_field_name: 'xin1_che1_xing2_shi2_shi1_ri4_qi1',
|
||||
db_field_txt: '新车型实施日期',
|
||||
dict_field: '',
|
||||
field_show_type: '5',
|
||||
tree: []
|
||||
},
|
||||
{
|
||||
db_field_name: 'implement_time',
|
||||
db_field_txt: '在产车实施日期',
|
||||
dict_field: '',
|
||||
field_show_type: '5',
|
||||
tree: []
|
||||
}
|
||||
]
|
||||
},
|
||||
methods: {
|
||||
searchQuery () {
|
||||
this.$emit('searchQuery', JSON.parse(JSON.stringify(this.queryParam)))
|
||||
},
|
||||
searchReset () {
|
||||
this.queryParam = {}
|
||||
this.$emit('searchQuery', JSON.parse(JSON.stringify(this.queryParam)))
|
||||
},
|
||||
// 获取查询条件列表
|
||||
getSearch () {
|
||||
const params = {
|
||||
flag: this.flag
|
||||
}
|
||||
getAction(this.url.getSearchList, params).then((res) => {
|
||||
if (res.success) {
|
||||
this.searchList = res.result
|
||||
if (this.searchQueryList && this.searchQueryList.length > 0) {
|
||||
for (let i = 0; i < this.searchList.length; i++) {
|
||||
if (this.searchList[i].db_field_name === 'issue_time') {
|
||||
this.searchList.splice(i, 1)
|
||||
i--
|
||||
}
|
||||
// db_field_name 说明可以被代替位置??
|
||||
if (this.searchList[i].db_field_name === 'title') {
|
||||
const data = this.searchList[i + 1]
|
||||
this.searchList[i + 1] = this.searchQueryList[0]
|
||||
this.searchList.push(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onChange (item) {
|
||||
if (this.queryParam[item] && this.queryParam[item].length > 0) {
|
||||
const startDate = item + '_start'
|
||||
const endDate = item + '_end'
|
||||
this.queryParam[startDate] = this.queryParam[item][0]
|
||||
this.queryParam[endDate] = this.queryParam[item][1]
|
||||
} else {
|
||||
this.queryParam[item] = []
|
||||
}
|
||||
},
|
||||
handleToggleSearch () {
|
||||
this.toggleSearchStatus = !this.toggleSearchStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.box-title-text {
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
.title-text {
|
||||
width: 110px;
|
||||
color: #000F16;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-right: 16px;
|
||||
margin-top: 3px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.box-input {
|
||||
/*min-width: 200px;*/
|
||||
display: inline-block;
|
||||
width: calc(100% - 116px);
|
||||
height: 38px;
|
||||
margin-top: 2px;
|
||||
/deep/ .ant-select-selection--single {
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-select-selection--multiple {
|
||||
height: 38px;
|
||||
.ant-select-selection__rendered > ul > li {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
/deep/ .ant-select-selection__rendered {
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-calendar-picker {
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-calendar-picker-input {
|
||||
height: 38px;
|
||||
}
|
||||
/deep/ .ant-input-number-input-wrap {
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
.box-button {
|
||||
height: 38px;
|
||||
/*margin-top: 2px;*/
|
||||
}
|
||||
|
||||
/deep/ .ant-select-tree {
|
||||
width: auto;
|
||||
height: 300px;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
/*background: #fff;*/
|
||||
}
|
||||
|
||||
/deep/ .ant-select-tree-dropdown {
|
||||
min-height: 320px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<div class="box" v-if="isTrue">
|
||||
<a-table
|
||||
class="table"
|
||||
rowKey="id"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
:pagination="ipagination"
|
||||
:scroll="{x: '100%'}"
|
||||
:components="drag(columns,'columns')"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:rowClassName="rowClassName"
|
||||
:columns="columns"
|
||||
@change="tableOnChange"
|
||||
>
|
||||
<span slot="operation" slot-scope="text, record">
|
||||
<a v-for="(ol, index) in operationList" :key="index" @click="operationClick(ol,record)">
|
||||
<span v-if="ol.text === $t('cancelCollection')" v-has="ol.has" class="text">
|
||||
{{ !record.collectFlag || record.collectFlag === 0 ? $t('collection') :$t('cancelCollection') }}
|
||||
</span>
|
||||
<span v-else-if="ol.text === $t('cancelSubscribe')" v-has="ol.has" class="text">
|
||||
{{ !record.subscribeFlag || record.subscribeFlag === 0 ? $t('subscribe') :$t('cancelSubscribe') }}
|
||||
</span>
|
||||
<span v-else v-has="ol.has" class="text">
|
||||
{{ ol.text }}
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
<span slot="detailClick" slot-scope="text,record">
|
||||
<a class="textName" :title="text" @click="detailClick(record)">
|
||||
{{text}}
|
||||
</a>
|
||||
</span>
|
||||
<!-- <span slot="urlClick" slot-scope="text,record">-->
|
||||
<!-- <a class="text" :title="text" @click="urlClick(text)">-->
|
||||
<!-- {{text}}-->
|
||||
<!-- </a>-->
|
||||
<!-- </span>-->
|
||||
<!-- <span slot="detailText" slot-scope="text,record">-->
|
||||
<!-- <span class="text" :title="text">-->
|
||||
<!-- {{text}}-->
|
||||
<!-- </span>-->
|
||||
<!-- </span>-->
|
||||
</a-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ResizeColumnProvide, ResizeHeader } from '@/mixins/header'
|
||||
import { getAction, postAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'TableData',
|
||||
mixins: [ResizeColumnProvide, ResizeHeader],
|
||||
props: {
|
||||
// 接口
|
||||
url: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 操作按钮
|
||||
operationList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
flag: { // flag获取数据时传给后端的,具体作用还不知道??
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示操作;默认不显示
|
||||
showAction: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
/* 分页参数 */
|
||||
ipagination: {
|
||||
current: 1,
|
||||
pageSize: 50,
|
||||
pageSizeOptions: ['10', '50', '100', '150', '200'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' ' + this.$t('total') + ' ' + total + ' ' + this.$t('strip')
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 0
|
||||
},
|
||||
columns: [],
|
||||
selectedRowKeys: [],
|
||||
dataSource: [],
|
||||
isTrue: false,
|
||||
searchParams: {},
|
||||
loading: false,
|
||||
orderBy: '1',
|
||||
orderByField: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$on('searchQuery', search => {
|
||||
Object.keys(search).forEach(res => {
|
||||
if (search[res] instanceof Array) {
|
||||
search[res] = search[res].join(',')
|
||||
}
|
||||
})
|
||||
this.searchParams = search
|
||||
this.getData()
|
||||
this.getTableList()
|
||||
})
|
||||
this.$on('searchReset', target => {
|
||||
this.searchParams = {}
|
||||
this.selectedRowKeys = []
|
||||
this.getData()
|
||||
this.getTableList()
|
||||
})
|
||||
this.$on('searchGetData', target => {
|
||||
this.selectedRowKeys = []
|
||||
this.getData()
|
||||
this.getTableList()
|
||||
})
|
||||
// this.getData()
|
||||
// this.getTableList()
|
||||
this.columns = [
|
||||
{
|
||||
title: '编号',
|
||||
dataIndex: 'serial_number',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: 'true',
|
||||
width: 215,
|
||||
scopedSlots: {
|
||||
customRender: 'detailClick'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: 'true',
|
||||
width: 215,
|
||||
scopedSlots: {
|
||||
customRender: 'detailClick'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: 'true',
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: '适用地区',
|
||||
dataIndex: 'region',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: '技术领域',
|
||||
dataIndex: 'technologyTerritory',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: '发布日期',
|
||||
dataIndex: 'issue_time',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: this.$t('operation'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
scopedSlots: { customRender: 'operation' }
|
||||
}
|
||||
]
|
||||
this.dataSource = [
|
||||
{
|
||||
collectFlag: '0',
|
||||
id: 'fd045f5c5d734087a884a63664ee91f1',
|
||||
issue_time: '',
|
||||
region: '',
|
||||
serial_number: '0621001编号',
|
||||
state: '现行',
|
||||
subscribeFlag: '0',
|
||||
technology_territory: '',
|
||||
title: '0621001标题',
|
||||
title_en: '0621001Title'
|
||||
}
|
||||
]
|
||||
this.isTrue = true
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$off('searchQuery')
|
||||
this.$off('searchReset')
|
||||
this.$off('searchGetData')
|
||||
},
|
||||
methods: {
|
||||
rowClassName (record) {
|
||||
return record.state === '废止' ? 'rowClassNameBack' : ''
|
||||
},
|
||||
getTextWith (text, fontStyle) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
context.font = fontStyle || '14px' // 设置字体样式
|
||||
const dimension = context.measureText(text)
|
||||
return dimension.width + 40
|
||||
},
|
||||
getData () {
|
||||
const params = {
|
||||
flag: this.flag
|
||||
}
|
||||
getAction(this.url.tableHeader, params).then((res) => {
|
||||
if (res.success) {
|
||||
const jsonHead = res.result
|
||||
this.columns = []
|
||||
// res.db_field_txt
|
||||
jsonHead.forEach((res, index) => {
|
||||
this.columns.push({
|
||||
title: res.db_field_txt,
|
||||
dataIndex: res.db_field_name,
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: res.sort,
|
||||
width: 215
|
||||
})
|
||||
if (res.click) {
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'detailClick'
|
||||
}
|
||||
this.columns[index].ellipsis = false
|
||||
} else if (res.urlClick) {
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'urlClick'
|
||||
}
|
||||
} else {
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'detailText'
|
||||
}
|
||||
}
|
||||
})
|
||||
let width = 0
|
||||
if (this.operationList.length > 0) {
|
||||
for (let i = 0; i < this.operationList.length; i++) {
|
||||
width += this.getTextWith(this.operationList[i].text)
|
||||
}
|
||||
}
|
||||
if (this.showAction) {
|
||||
this.columns.push({
|
||||
title: this.$t('operation'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: width,
|
||||
scopedSlots: { customRender: 'operation' }
|
||||
})
|
||||
}
|
||||
this.isTrue = false
|
||||
this.$nextTick(() => {
|
||||
this.isTrue = true
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
tableOnChange (pagination, filters, sorter) {
|
||||
this.orderBy = sorter.order === 'ascend' ? '1' : '2'
|
||||
this.orderByField = sorter.columnKey
|
||||
this.ipagination = pagination
|
||||
this.getTableList()
|
||||
},
|
||||
getTableList () {
|
||||
const pageNo = this.ipagination.current
|
||||
const pageSize = this.ipagination.pageSize
|
||||
const params = {
|
||||
...this.searchParams,
|
||||
orderBy: this.orderBy,
|
||||
orderByField: this.orderByField,
|
||||
pageNo: pageNo,
|
||||
pageSize: pageSize
|
||||
}
|
||||
this.loading = true
|
||||
postAction(this.url.tableList, params).then((res) => {
|
||||
if (res.success) {
|
||||
if (res.result.current > 1 && res.result.records.length === 0) {
|
||||
this.ipagination.current = res.result.current - 1
|
||||
this.getTableList()
|
||||
return
|
||||
}
|
||||
this.dataSource = res.result.records
|
||||
this.ipagination.total = res.result.total
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
onSelectChange (value) {
|
||||
this.selectedRowKeys = value
|
||||
this.$emit('onSelectChange', value)
|
||||
},
|
||||
operationClick (ol, item) {
|
||||
this.$emit(ol.clickEvent, item)
|
||||
},
|
||||
detailClick (item, index) {
|
||||
this.$emit('detailClick', item)
|
||||
},
|
||||
urlClick (item) {
|
||||
window.open(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<a-radio-group v-if="tagType==='radio'" @change="handleInput" :value="getValueSting" :disabled="disabled">
|
||||
<a-radio v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio>
|
||||
</a-radio-group>
|
||||
|
||||
<a-radio-group v-else-if="tagType==='radioButton'" buttonStyle="solid" @change="handleInput" :value="getValueSting" :disabled="disabled">
|
||||
<a-radio-button v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
|
||||
<a-select v-else-if="tagType==='select'" :getPopupContainer = "getPopupContainer" :placeholder="placeholder" :disabled="disabled" :value="getValueSting" @change="handleInput">
|
||||
<a-select-option :value="undefined">请选择</a-select-option>
|
||||
<a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value">
|
||||
<span style="display: inline-block;width: 100%" :title=" item.text || item.label ">
|
||||
{{ item.text || item.label }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
|
||||
export default {
|
||||
name: 'JDictSelectTag',
|
||||
props: {
|
||||
dictCode: String,
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
value: [String, Number],
|
||||
type: String,
|
||||
getPopupContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dictOptions: [],
|
||||
tagType: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dictCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.initDictData()
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.type || this.type === 'list') {
|
||||
this.tagType = 'select'
|
||||
} else {
|
||||
this.tagType = this.type
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getValueSting () {
|
||||
// update-begin author:wangshuai date:20200601 for: 不显示placeholder的文字 ------
|
||||
// 当有null或“” placeholder不显示
|
||||
return this.value != null ? this.value.toString() : undefined
|
||||
// update-end author:wangshuai date:20200601 for: 不显示placeholder的文字 ------
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initDictData () {
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(this.dictCode)) {
|
||||
this.dictOptions = getDictItemsFromCache(this.dictCode)
|
||||
return
|
||||
}
|
||||
|
||||
// 根据字典Code, 初始化字典数组
|
||||
ajaxGetDictItems(this.dictCode, null).then((res) => {
|
||||
if (res.success) {
|
||||
this.dictOptions = res.result
|
||||
}
|
||||
})
|
||||
},
|
||||
handleInput (e = '') {
|
||||
let val
|
||||
if (Object.keys(e).includes('target')) {
|
||||
val = e.target.value
|
||||
} else {
|
||||
val = e
|
||||
}
|
||||
console.log(val)
|
||||
this.$emit('change', val)
|
||||
// 解决数据规则,选择自定义SQL 规则值无法输入空格
|
||||
this.$emit('input', val)
|
||||
},
|
||||
setCurrentDictOptions (dictOptions) {
|
||||
this.dictOptions = dictOptions
|
||||
},
|
||||
getCurrentDictOptions () {
|
||||
return this.dictOptions
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 字典 util
|
||||
* author: scott
|
||||
* date: 20190109
|
||||
*/
|
||||
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
// import { getAction } from '@/api/manage'
|
||||
|
||||
/**
|
||||
* 获取字典数组
|
||||
* @param dictCode 字典Code
|
||||
* @return List<Map>
|
||||
*/
|
||||
export async function initDictOptions (dictCode) {
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!'
|
||||
}
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(dictCode)) {
|
||||
const res = {}
|
||||
res.result = getDictItemsFromCache(dictCode)
|
||||
res.success = true
|
||||
return res
|
||||
}
|
||||
// 获取字典数组
|
||||
return (await ajaxGetDictItems(dictCode))
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法
|
||||
* @param dictOptions 字典数组
|
||||
* @param text 字典值
|
||||
* @return String
|
||||
*/
|
||||
export function filterDictText (dictOptions, text) {
|
||||
// --update-begin----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
|
||||
if (text != null && Array.isArray(dictOptions)) {
|
||||
const result = []
|
||||
// 允许多个逗号分隔,允许传数组对象
|
||||
let splitText
|
||||
if (Array.isArray(text)) {
|
||||
splitText = text
|
||||
} else {
|
||||
splitText = text.toString().trim().split(',')
|
||||
}
|
||||
for (const txt of splitText) {
|
||||
let dictText = txt
|
||||
for (const dictItem of dictOptions) {
|
||||
if (txt.toString() === dictItem.value.toString()) {
|
||||
dictText = (dictItem.text || dictItem.title || dictItem.label)
|
||||
break
|
||||
}
|
||||
}
|
||||
result.push(dictText)
|
||||
}
|
||||
return result.join(',')
|
||||
}
|
||||
return text
|
||||
// --update-end----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法(多选)
|
||||
* @param dictOptions 字典数组
|
||||
* @param text 字典值
|
||||
* @return String
|
||||
*/
|
||||
export function filterMultiDictText (dictOptions, text) {
|
||||
// js “!text” 认为0为空,所以做提前处理
|
||||
if (text === 0 || text === '0') {
|
||||
if (dictOptions) {
|
||||
for (const dictItem of dictOptions) {
|
||||
if ((text + '') === (dictItem.value + '')) {
|
||||
return dictItem.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text || text === 'null' || !dictOptions || dictOptions.length === 0) {
|
||||
return ''
|
||||
}
|
||||
let re = ''
|
||||
text = text.toString()
|
||||
const arr = text.split(',')
|
||||
dictOptions.forEach(function (option) {
|
||||
if (option) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i] === option.value) {
|
||||
re += option.text + ','
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (re === '') {
|
||||
return text
|
||||
}
|
||||
return re.substring(0, re.length - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译字段值对应的文本
|
||||
* @returns string
|
||||
* @param dictCode
|
||||
* @param key
|
||||
*/
|
||||
export function filterDictTextByCache (dictCode, key) {
|
||||
if (key === null || key === undefined || key.length === 0) {
|
||||
return ''
|
||||
}
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!'
|
||||
}
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(dictCode)) {
|
||||
const item = getDictItemsFromCache(dictCode).filter(t => t.value + '' === key + '')
|
||||
if (item && item.length > 0) {
|
||||
return item[0].text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 通过code获取字典数组 */
|
||||
export async function getDictItems (dictCode, params) {
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(dictCode)) {
|
||||
return getDictItemsFromCache(dictCode).map(item => ({ ...item, label: item.text }))
|
||||
}
|
||||
|
||||
// 缓存中没有,就请求后台
|
||||
return await ajaxGetDictItems(dictCode, params).then(({ success, result }) => {
|
||||
if (success) {
|
||||
const res = result.map(item => ({ ...item, label: item.text }))
|
||||
console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res)
|
||||
return Promise.resolve(res)
|
||||
} else {
|
||||
console.error('getDictItems error: : ', result)
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}).catch((res) => {
|
||||
console.error('getDictItems error: ', res)
|
||||
return Promise.resolve([])
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<a-checkbox-group v-if="tagType==='checkbox'" @change="onChange" :value="arrayValue" :disabled="disabled">
|
||||
<a-checkbox v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text || item.label }}</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
|
||||
<a-select
|
||||
v-else-if="tagType==='select'"
|
||||
:value="arrayValue"
|
||||
@change="onChange"
|
||||
:disabled="disabled"
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
:getPopupContainer="getParentContainer"
|
||||
optionFilterProp="children"
|
||||
:filterOption="filterOption"
|
||||
allowClear
|
||||
:options="dictOptions">
|
||||
</a-select>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
export default {
|
||||
name: 'JMultiSelectTag',
|
||||
props: {
|
||||
dictCode: String,
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
value: String,
|
||||
type: String,
|
||||
options: Array,
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dictOptions: [],
|
||||
tagType: '',
|
||||
arrayValue: !this.value ? [] : this.value.split(this.spliter)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.type || this.type === 'list_multi') {
|
||||
this.tagType = 'select'
|
||||
} else {
|
||||
this.tagType = this.type
|
||||
}
|
||||
// 获取字典数据
|
||||
// this.initDictData();
|
||||
},
|
||||
watch: {
|
||||
options (val) {
|
||||
val.forEach((item, index) => {
|
||||
this.$set(this.dictOptions, index, item)
|
||||
})
|
||||
},
|
||||
dictCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.initDictData()
|
||||
}
|
||||
},
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.arrayValue = []
|
||||
} else {
|
||||
this.arrayValue = this.value.split(this.spliter)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initDictData () {
|
||||
if (this.options && this.options.length > 0) {
|
||||
this.dictOptions = [...this.options]
|
||||
} else {
|
||||
// 优先从缓存中读取字典配置
|
||||
const cacheOption = getDictItemsFromCache(this.dictCode)
|
||||
if (cacheOption && cacheOption.length > 0) {
|
||||
this.dictOptions = cacheOption
|
||||
return
|
||||
}
|
||||
// 根据字典Code, 初始化字典数组
|
||||
ajaxGetDictItems(this.dictCode, null).then((res) => {
|
||||
if (res.success) {
|
||||
this.dictOptions = res.result
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onChange (selectedValue) {
|
||||
this.arrayValue = selectedValue
|
||||
this.$emit('change', selectedValue.join(this.spliter))
|
||||
},
|
||||
getParentContainer (node) {
|
||||
if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.querySelector(this.popContainer)
|
||||
}
|
||||
},
|
||||
// update--begin--autor:lvdandan-----date:20201120------for:LOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
|
||||
filterOption (input, option) {
|
||||
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
// update--end--autor:lvdandan-----date:20201120------for:LOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
|
||||
<a-select
|
||||
v-if="async"
|
||||
showSearch
|
||||
labelInValue
|
||||
:disabled="disabled"
|
||||
:getPopupContainer="getParentContainer"
|
||||
@search="loadData"
|
||||
:placeholder="placeholder"
|
||||
v-model="selectedAsyncValue"
|
||||
style="width: 100%"
|
||||
:filterOption="false"
|
||||
@change="handleAsyncChange"
|
||||
allowClear
|
||||
:notFoundContent="loading ? undefined : null"
|
||||
>
|
||||
<a-spin v-if="loading" slot="notFoundContent" size="small"/>
|
||||
<a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select
|
||||
v-else
|
||||
:getPopupContainer="getParentContainer"
|
||||
showSearch
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
optionFilterProp="children"
|
||||
style="width: 100%"
|
||||
@change="handleChange"
|
||||
:filterOption="filterOption"
|
||||
v-model="selectedValue"
|
||||
allowClear
|
||||
:notFoundContent="loading ? undefined : null">
|
||||
<a-spin v-if="loading" slot="notFoundContent" size="small"/>
|
||||
<a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
|
||||
</a-select>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { getAction } from '@api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JSearchSelectTag',
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
value: [String, Number],
|
||||
dict: String,
|
||||
dictOptions: Array,
|
||||
async: Boolean,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
required: false
|
||||
},
|
||||
getPopupContainer: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
this.loadData = debounce(this.loadData, 800)// 消抖
|
||||
this.lastLoad = 0
|
||||
return {
|
||||
loading: false,
|
||||
selectedValue: [],
|
||||
selectedAsyncValue: [],
|
||||
options: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initDictData()
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (!val) {
|
||||
if (val === 0) {
|
||||
this.initSelectValue()
|
||||
} else {
|
||||
this.selectedValue = []
|
||||
this.selectedAsyncValue = []
|
||||
}
|
||||
} else {
|
||||
this.initSelectValue()
|
||||
}
|
||||
}
|
||||
},
|
||||
dict: {
|
||||
handler () {
|
||||
this.initDictData()
|
||||
}
|
||||
},
|
||||
dictOptions: {
|
||||
deep: true,
|
||||
handler (val) {
|
||||
if (val && val.length > 0) {
|
||||
this.options = [...val]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initSelectValue () {
|
||||
if (this.async) {
|
||||
if (!this.selectedAsyncValue || !this.selectedAsyncValue.key || (this.selectedAsyncValue.key + '') !== (this.value + '')) {
|
||||
console.log('这才请求后台')
|
||||
getAction(`/sys/dict/loadDictItem/${this.dict}`, { key: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
const obj = {
|
||||
key: this.value,
|
||||
label: res.result
|
||||
}
|
||||
this.selectedAsyncValue = { ...obj }
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.selectedValue = this.value.toString()
|
||||
}
|
||||
},
|
||||
loadData (value) {
|
||||
console.log('数据加载', value)
|
||||
this.lastLoad += 1
|
||||
const currentLoad = this.lastLoad
|
||||
this.options = []
|
||||
this.loading = true
|
||||
// 字典code格式:table,text,code
|
||||
getAction(`/sys/dict/loadDict/${this.dict}`, { keyword: value, pageSize: this.pageSize }).then(res => {
|
||||
this.loading = false
|
||||
if (res.success) {
|
||||
if (currentLoad !== this.lastLoad) {
|
||||
return
|
||||
}
|
||||
this.options = res.result
|
||||
console.log('我是第一个', res)
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
initDictData () {
|
||||
if (!this.async) {
|
||||
// 如果字典项集合有数据
|
||||
if (this.dictOptions && this.dictOptions.length > 0) {
|
||||
this.options = [...this.dictOptions]
|
||||
} else {
|
||||
// 根据字典Code, 初始化字典数组
|
||||
let dictStr = ''
|
||||
if (this.dict) {
|
||||
const arr = this.dict.split(',')
|
||||
if (arr[0].indexOf('where') > 0) {
|
||||
const tbInfo = arr[0].split('where')
|
||||
dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1])
|
||||
} else {
|
||||
dictStr = this.dict
|
||||
}
|
||||
if (this.dict.indexOf(',') === -1) {
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(this.dictCode)) {
|
||||
this.options = getDictItemsFromCache(this.dictCode)
|
||||
return
|
||||
}
|
||||
}
|
||||
ajaxGetDictItems(dictStr, null).then((res) => {
|
||||
if (res.success) {
|
||||
this.options = res.result
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.dict) {
|
||||
console.error('搜索组件未配置字典项')
|
||||
} else {
|
||||
// 异步一开始也加载一点数据
|
||||
this.loading = true
|
||||
getAction(`/sys/dict/loadDict/${this.dict}`, { pageSize: this.pageSize, keyword: '' }).then(res => {
|
||||
this.loading = false
|
||||
if (res.success) {
|
||||
this.options = res.result
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
filterOption (input, option) {
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
handleChange (selectedValue) {
|
||||
console.log('selectedValue', selectedValue)
|
||||
this.selectedValue = selectedValue
|
||||
this.callback()
|
||||
},
|
||||
handleAsyncChange (selectedObj) {
|
||||
// update-begin-author:scott date:20201222 for:【搜索】搜索查询组件,删除条件,默认下拉还是上次的缓存数据,不好 JT-191
|
||||
if (selectedObj) {
|
||||
this.selectedAsyncValue = selectedObj
|
||||
this.selectedValue = selectedObj.key
|
||||
} else {
|
||||
this.selectedAsyncValue = null
|
||||
this.selectedValue = null
|
||||
this.options = null
|
||||
this.loadData('')
|
||||
}
|
||||
this.callback()
|
||||
// update-end-author:scott date:20201222 for:【搜索】搜索查询组件,删除条件,默认下拉还是上次的缓存数据,不好 JT-191
|
||||
},
|
||||
callback () {
|
||||
this.$emit('change', this.selectedValue)
|
||||
},
|
||||
setCurrentDictOptions (dictOptions) {
|
||||
this.options = dictOptions
|
||||
},
|
||||
getCurrentDictOptions () {
|
||||
return this.options
|
||||
},
|
||||
getParentContainer (node) {
|
||||
if (typeof this.getPopupContainer === 'function') {
|
||||
return this.getPopupContainer(node)
|
||||
} else if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.querySelector(this.popContainer)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,181 @@
|
||||
# JDictSelectTag 组件用法
|
||||
----
|
||||
- 从字典表获取数据,dictCode格式说明: 字典code
|
||||
```html
|
||||
<j-dict-select-tag v-model="queryParam.sex" placeholder="请输入用户性别"
|
||||
dictCode="sex"/>
|
||||
```
|
||||
|
||||
v-decorator用法:
|
||||
```html
|
||||
<j-dict-select-tag v-decorator="['sex', {}]" :triggerChange="true" placeholder="请输入用户性别"
|
||||
dictCode="sex"/>
|
||||
```
|
||||
|
||||
- 从数据库表获取字典数据,dictCode格式说明: 表名,文本字段,取值字段
|
||||
```html
|
||||
<j-dict-select-tag v-model="queryParam.username" placeholder="请选择用户名称"
|
||||
dictCode="sys_user,realname,id"/>
|
||||
```
|
||||
|
||||
|
||||
|
||||
# JDictSelectUtil.js 列表字典函数用法
|
||||
----
|
||||
|
||||
- 第一步: 引入依赖方法
|
||||
```html
|
||||
import {initDictOptions, filterDictText} from '@/components/dict/JDictSelectUtil'
|
||||
```
|
||||
|
||||
- 第二步: 在created()初始化方法执行字典配置方法
|
||||
```html
|
||||
//初始化字典配置
|
||||
this.initDictConfig();
|
||||
```
|
||||
|
||||
- 第三步: 实现initDictConfig方法,加载列表所需要的字典(列表上有多个字典项,就执行多次initDictOptions方法)
|
||||
|
||||
```html
|
||||
initDictConfig() {
|
||||
//初始化字典 - 性别
|
||||
initDictOptions('sex').then((res) => {
|
||||
if (res.success) {
|
||||
this.sexDictOptions = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
- 第四步: 实现字段的customRender方法
|
||||
```html
|
||||
customRender: (text, record, index) => {
|
||||
//字典值替换通用方法
|
||||
return filterDictText(this.sexDictOptions, text);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# JMultiSelectTag 多选组件
|
||||
下拉/checkbox
|
||||
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| disabled |Boolean | | 是否禁用 |
|
||||
| type |string | | 多选类型 select/checkbox 默认是select |
|
||||
| dictCode |string | | 数据字典编码或者表名,显示字段名,存储字段名拼接而成的字符串,如果提供了options参数 则此参数可不填|
|
||||
| options |Array | | 多选项,如果dictCode参数未提供,可以设置此参数加载多选项 |
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="下拉多选" style="width: 300px">
|
||||
<j-multi-select-tag
|
||||
v-model="selectValue"
|
||||
:options="dictOptions"
|
||||
placeholder="请做出你的选择">
|
||||
</j-multi-select-tag>
|
||||
{{ selectValue }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="checkbox">
|
||||
<j-multi-select-tag
|
||||
v-model="checkboxValue"
|
||||
:options="dictOptions"
|
||||
type="checkbox">
|
||||
</j-multi-select-tag>
|
||||
{{ checkboxValue }}
|
||||
</a-form-item>
|
||||
</a-form >
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
|
||||
export default {
|
||||
components: {JMultiSelectTag},
|
||||
data() {
|
||||
return {
|
||||
selectValue:"",
|
||||
checkboxValue:"",
|
||||
dictOptions:[{
|
||||
label:"选项一",
|
||||
value:"1"
|
||||
},{
|
||||
label:"选项二",
|
||||
value:"2"
|
||||
},{
|
||||
label:"选项三",
|
||||
value:"3"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# JSearchSelectTag 字典表的搜索组件
|
||||
下拉搜索组件,支持异步加载,异步加载用于大数据量的字典表
|
||||
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| disabled |Boolean | | 是否禁用 |
|
||||
| dict |string | | 表名,显示字段名,存储字段名拼接而成的字符串,如果提供了dictOptions参数 则此参数可不填|
|
||||
| dictOptions |Array | | 多选项,如果dict参数未提供,可以设置此参数加载多选项 |
|
||||
| async |Boolean | | 是否支持异步加载,设置成true,则通过输入的内容加载远程数据,否则在本地过滤数据,默认false|
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="下拉搜索" style="width: 300px">
|
||||
<j-search-select-tag
|
||||
placeholder="请做出你的选择"
|
||||
v-model="selectValue"
|
||||
:dictOptions="dictOptions">
|
||||
</j-search-select-tag>
|
||||
{{ selectValue }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="异步加载" style="width: 300px">
|
||||
<j-search-select-tag
|
||||
placeholder="请做出你的选择"
|
||||
v-model="asyncSelectValue"
|
||||
dict="sys_depart,depart_name,id"
|
||||
:async="true">
|
||||
</j-search-select-tag>
|
||||
{{ asyncSelectValue }}
|
||||
</a-form-item>
|
||||
</a-form >
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JSearchSelectTag from '@/components/dict/JSearchSelectTag'
|
||||
export default {
|
||||
components: {JSearchSelectTag},
|
||||
data() {
|
||||
return {
|
||||
selectValue:"",
|
||||
asyncSelectValue:"",
|
||||
dictOptions:[{
|
||||
text:"选项一",
|
||||
value:"1"
|
||||
},{
|
||||
text:"选项二",
|
||||
value:"2"
|
||||
},{
|
||||
text:"选项三",
|
||||
value:"3"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import JDictSelectTag from './JDictSelectTag.vue'
|
||||
import JMultiSelectTag from './JMultiSelectTag.vue'
|
||||
import JSearchSelectTag from './JSearchSelectTag.vue'
|
||||
import { filterMultiDictText, filterDictText, initDictOptions, filterDictTextByCache } from './JDictSelectUtil'
|
||||
|
||||
export default {
|
||||
install: function (Vue) {
|
||||
Vue.component('JDictSelectTag', JDictSelectTag)
|
||||
Vue.component('JMultiSelectTag', JMultiSelectTag)
|
||||
Vue.component('JSearchSelectTag', JSearchSelectTag)
|
||||
Vue.prototype.$initDictOptions = (dictCode) => initDictOptions(dictCode)
|
||||
Vue.prototype.$filterMultiDictText = (dictOptions, text) => filterMultiDictText(dictOptions, text)
|
||||
Vue.prototype.$filterDictText = (dictOptions, text) => filterDictText(dictOptions, text)
|
||||
Vue.prototype.$filterDictTextByCache = (...param) => filterDictTextByCache(...param)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "~ant-design-vue/lib/style/index";
|
||||
|
||||
// The prefix to use on all css classes from ant-pro.
|
||||
@ant-pro-prefix : ant-pro;
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="j-area-linkage">
|
||||
<div v-if="reloading">
|
||||
<span> Reloading... </span>
|
||||
</div>
|
||||
<area-cascader
|
||||
v-else-if="_type === enums.type[0]"
|
||||
:value="innerValue"
|
||||
:data="pcaa"
|
||||
:level="1"
|
||||
:style="{width}"
|
||||
v-bind="$attrs"
|
||||
v-on="_listeners"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<area-select
|
||||
v-else-if="_type === enums.type[1]"
|
||||
:value="innerValue"
|
||||
:data="pcaa"
|
||||
:level="2"
|
||||
v-bind="$attrs"
|
||||
v-on="_listeners"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<div v-else>
|
||||
<span style="color:red;"> Bad type value: {{ _type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Area from '@/components/_util/Area'
|
||||
|
||||
export default {
|
||||
name: 'JAreaLinkage',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
// 组件的类型,可选值:
|
||||
// select 下拉样式
|
||||
// cascader 级联样式(默认)
|
||||
type: {
|
||||
type: String,
|
||||
default: 'cascader'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pcaa: this.$Jpcaa,
|
||||
innerValue: [],
|
||||
usedListeners: ['change'],
|
||||
enums: {
|
||||
type: ['cascader', 'select']
|
||||
},
|
||||
reloading: false,
|
||||
areaData: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_listeners () {
|
||||
const listeners = { ...this.$listeners }
|
||||
// 去掉已使用的事件,防止冲突
|
||||
this.usedListeners.forEach(key => {
|
||||
delete listeners[key]
|
||||
})
|
||||
return listeners
|
||||
},
|
||||
_type () {
|
||||
if (this.enums.type.includes(this.type)) {
|
||||
return this.type
|
||||
} else {
|
||||
console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')})`)
|
||||
return this.enums.type[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.loadDataByValue(this.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initAreaData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 重新加载组件 */
|
||||
reload () {
|
||||
this.reloading = true
|
||||
this.$nextTick(() => {
|
||||
this.reloading = false
|
||||
})
|
||||
},
|
||||
|
||||
/** 通过 value 反推 options */
|
||||
loadDataByValue (value) {
|
||||
if (!value || value.length === 0) {
|
||||
this.innerValue = []
|
||||
} else {
|
||||
this.initAreaData()
|
||||
this.innerValue = this.areaData.getRealCode(value)
|
||||
}
|
||||
this.reload()
|
||||
},
|
||||
/** 通过地区code获取子级 */
|
||||
loadDataByCode (value) {
|
||||
const options = []
|
||||
const data = this.pcaa[value]
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
options.push({ value: key, label: data[key] })
|
||||
}
|
||||
}
|
||||
return options
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/** 判断是否有子节点 */
|
||||
hasChildren (options) {
|
||||
options.forEach(option => {
|
||||
const data = this.loadDataByCode(option.value)
|
||||
option.isLeaf = data.length === 0
|
||||
})
|
||||
},
|
||||
handleChange (values) {
|
||||
const value = values[values.length - 1]
|
||||
this.$emit('change', value)
|
||||
},
|
||||
initAreaData () {
|
||||
if (!this.areaData) {
|
||||
this.areaData = new Area(this.$Jpcaa)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
model: { prop: 'value', event: 'change' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.j-area-linkage {
|
||||
height: 40px;
|
||||
|
||||
/deep/ .area-cascader-wrap .area-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .area-select .area-selected-trigger {
|
||||
line-height: 1.15;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JCategorySelect',
|
||||
props: {
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadTriggleChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
pid: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
back: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: '',
|
||||
treeData: [],
|
||||
url: '/sys/category/loadTreeData',
|
||||
view: '/sys/category/loadDictItem/',
|
||||
tableName: '',
|
||||
text: '',
|
||||
code: ''
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
pCode () {
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.validateProp().then(() => {
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/** 加载一级节点 */
|
||||
loadRoot () {
|
||||
const param = {
|
||||
pid: this.pid,
|
||||
pcode: !this.pCode ? '0' : this.pCode,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success && res.result) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
console.log('树一级节点查询结果-else', res)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 数据回显 */
|
||||
loadItemByCode () {
|
||||
if (!this.value || this.value === '0') {
|
||||
this.treeValue = []
|
||||
} else {
|
||||
getAction(this.view, { ids: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
if (res.result && res.result.length > 0) {
|
||||
const values = this.value.split(',')
|
||||
this.treeValue = res.result.map((item, index) => ({
|
||||
key: values[index],
|
||||
value: values[index],
|
||||
label: item
|
||||
}))
|
||||
this.onLoadTriggleChange(res.result[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoadTriggleChange (text) {
|
||||
// 只有单选才会触发
|
||||
if (!this.multiple && this.loadTriggleChange) {
|
||||
this.backValue(this.value, text)
|
||||
}
|
||||
},
|
||||
backValue (value, label) {
|
||||
const obj = {}
|
||||
if (this.back) {
|
||||
obj[this.back] = label
|
||||
}
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', value, obj)
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') {
|
||||
if (!children || children.length === 0) {
|
||||
item.isLeaf = true
|
||||
} else {
|
||||
item.children = children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
this.treeValue = ''
|
||||
} else if (Array.isArray(value)) {
|
||||
const labels = []
|
||||
const values = value.map(item => {
|
||||
labels.push(item.label)
|
||||
return item.value
|
||||
})
|
||||
this.backValue(values.join(','), labels.join(','))
|
||||
this.treeValue = value
|
||||
} else {
|
||||
this.backValue(value.value, value.label)
|
||||
this.treeValue = value
|
||||
}
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
},
|
||||
validateProp () {
|
||||
const myCondition = this.condition
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!myCondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
const test = JSON.parse(myCondition)
|
||||
if (typeof test === 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-checkbox-group :options="options"
|
||||
:value="checkboxArray"
|
||||
v-bind="$attrs"
|
||||
@change="onChange" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JCheckbox',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
/* label value */
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checkboxArray: !this.value ? [] : this.value.split(',')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.checkboxArray = []
|
||||
} else {
|
||||
this.checkboxArray = this.value.split(',')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (checkedValues) {
|
||||
this.$emit('change', checkedValues.join(','))
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<div v-bind="fullScreenParentProps">
|
||||
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder" />
|
||||
|
||||
<div class="code-editor-cust full-screen-child">
|
||||
<textarea ref="textarea"></textarea>
|
||||
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
|
||||
<template v-if="languageChange">
|
||||
<a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
|
||||
<a-select-option
|
||||
v-for="mode in modes"
|
||||
:key="mode.value"
|
||||
:value="mode.value">
|
||||
{{ mode.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/ecmascript-6">
|
||||
// 引入全局实例
|
||||
import _CodeMirror from 'codemirror'
|
||||
|
||||
// 核心样式
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
// 引入主题后还需要在 options 中指定主题才会生效 darcula gruvbox-dark hopscotch monokai
|
||||
import 'codemirror/theme/panda-syntax.css'
|
||||
// 提示css
|
||||
import 'codemirror/addon/hint/show-hint.css'
|
||||
|
||||
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
||||
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
|
||||
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/css/css.js'
|
||||
import 'codemirror/mode/xml/xml.js'
|
||||
import 'codemirror/mode/clike/clike.js'
|
||||
import 'codemirror/mode/markdown/markdown.js'
|
||||
import 'codemirror/mode/python/python.js'
|
||||
import 'codemirror/mode/r/r.js'
|
||||
import 'codemirror/mode/shell/shell.js'
|
||||
import 'codemirror/mode/sql/sql.js'
|
||||
import 'codemirror/mode/swift/swift.js'
|
||||
import 'codemirror/mode/vue/vue.js'
|
||||
|
||||
import { isIE11, isIE } from '@/utils/browser'
|
||||
|
||||
// 尝试获取全局实例
|
||||
const CodeMirror = window.CodeMirror || _CodeMirror
|
||||
|
||||
export default {
|
||||
name: 'JCodeEditor',
|
||||
props: {
|
||||
// 外部传入的内容,用于实现双向绑定
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外部传入的语法类型
|
||||
language: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
languageChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 显示行号
|
||||
lineNumbers: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 全屏以后的z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
// 是否自适应高度,可以传String或Boolean
|
||||
// 传 String 类型只能写"!ie" ,
|
||||
// 填写这个字符串,代表其他浏览器自适应高度
|
||||
// 唯独IE下不自适应高度,因为IE下不支持min、max-height样式
|
||||
// 如果填写的不是"!ie"就视为true
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 不自适应高度的情况下生效的固定高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '240px'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部真实的内容
|
||||
code: '',
|
||||
iconType: 'fullscreen',
|
||||
hasCode: false,
|
||||
// 默认的语法类型
|
||||
mode: 'javascript',
|
||||
// 编辑器实例
|
||||
coder: null,
|
||||
// 默认配置
|
||||
options: {
|
||||
// 缩进格式
|
||||
tabSize: 2,
|
||||
// 主题,对应主题库 JS 需要提前引入
|
||||
theme: 'panda-syntax',
|
||||
line: true,
|
||||
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
|
||||
hintOptions: {
|
||||
tables: {
|
||||
users: ['name', 'score', 'birthDate'],
|
||||
countries: ['name', 'population', 'size']
|
||||
}
|
||||
}
|
||||
},
|
||||
// 支持切换的语法高亮类型,对应 JS 已经提前引入
|
||||
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
|
||||
modes: [{
|
||||
value: 'css',
|
||||
label: 'CSS'
|
||||
}, {
|
||||
value: 'javascript',
|
||||
label: 'Javascript'
|
||||
}, {
|
||||
value: 'html',
|
||||
label: 'XML/HTML'
|
||||
}, {
|
||||
value: 'x-java',
|
||||
label: 'Java'
|
||||
}, {
|
||||
value: 'x-objectivec',
|
||||
label: 'Objective-C'
|
||||
}, {
|
||||
value: 'x-python',
|
||||
label: 'Python'
|
||||
}, {
|
||||
value: 'x-rsrc',
|
||||
label: 'R'
|
||||
}, {
|
||||
value: 'x-sh',
|
||||
label: 'Shell'
|
||||
}, {
|
||||
value: 'x-sql',
|
||||
label: 'SQL'
|
||||
}, {
|
||||
value: 'x-swift',
|
||||
label: 'Swift'
|
||||
}, {
|
||||
value: 'x-vue',
|
||||
label: 'Vue'
|
||||
}, {
|
||||
value: 'markdown',
|
||||
label: 'Markdown'
|
||||
}],
|
||||
// code 编辑器 是否全屏
|
||||
fullCoder: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fullCoder: {
|
||||
handler (value) {
|
||||
if (value) {
|
||||
this.iconType = 'fullscreen-exit'
|
||||
} else {
|
||||
this.iconType = 'fullscreen'
|
||||
}
|
||||
}
|
||||
},
|
||||
// value: {
|
||||
// immediate: false,
|
||||
// handler(value) {
|
||||
// this._getCoder().then(() => {
|
||||
// this.coder.setValue(value)
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
language: {
|
||||
immediate: true,
|
||||
handler (language) {
|
||||
this._getCoder().then(() => {
|
||||
// 尝试从父容器获取语法类型
|
||||
if (language) {
|
||||
// 获取具体的语法类型对象
|
||||
const modeObj = this._getLanguage(language)
|
||||
|
||||
// 判断父容器传入的语法是否被支持
|
||||
if (modeObj) {
|
||||
this.mode = modeObj.label
|
||||
this.coder.setOption('mode', `text/${modeObj.value}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholderShow () {
|
||||
if (this.placeholder == null) {
|
||||
return `请在此输入${this.language}代码`
|
||||
} else {
|
||||
return this.placeholder
|
||||
}
|
||||
},
|
||||
nullTipStyle () {
|
||||
if (this.lineNumbers) {
|
||||
return { left: '36px' }
|
||||
} else {
|
||||
return { left: '12px' }
|
||||
}
|
||||
},
|
||||
// coder 配置
|
||||
coderOptions () {
|
||||
return {
|
||||
tabSize: this.options.tabSize,
|
||||
theme: this.options.theme,
|
||||
lineNumbers: this.lineNumbers,
|
||||
line: true,
|
||||
hintOptions: this.options.hintOptions
|
||||
}
|
||||
},
|
||||
isAutoHeight () {
|
||||
let { autoHeight } = this
|
||||
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
|
||||
autoHeight = !(isIE() || isIE11())
|
||||
} else {
|
||||
autoHeight = true
|
||||
}
|
||||
return autoHeight
|
||||
},
|
||||
fullScreenParentProps () {
|
||||
const props = {
|
||||
class: {
|
||||
'full-screen-parent': true,
|
||||
'full-screen': this.fullCoder,
|
||||
'auto-height': this.isAutoHeight
|
||||
},
|
||||
style: {}
|
||||
}
|
||||
if (isIE() || isIE11()) {
|
||||
props.style.height = '240px'
|
||||
}
|
||||
if (this.fullCoder) {
|
||||
props.style['z-index'] = this.zIndex
|
||||
}
|
||||
if (!this.isAutoHeight) {
|
||||
props.style.height = (typeof this.height === 'number' ? this.height + 'px' : this.height)
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 初始化
|
||||
this._initialize()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
_initialize () {
|
||||
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
|
||||
this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
|
||||
// 编辑器赋值
|
||||
if (this.value || this.code) {
|
||||
this.hasCode = true
|
||||
// this.coder.setValue(this.value || this.code)
|
||||
this.setCodeContent(this.value || this.code)
|
||||
} else {
|
||||
this.coder.setValue('')
|
||||
this.hasCode = false
|
||||
}
|
||||
// 支持双向绑定
|
||||
this.coder.on('change', (coder) => {
|
||||
this.code = coder.getValue()
|
||||
this.hasCode = !!this.code
|
||||
if (this.$emit) {
|
||||
this.$emit('input', this.code)
|
||||
}
|
||||
})
|
||||
this.coder.on('focus', () => {
|
||||
this.hasCode = true
|
||||
})
|
||||
this.coder.on('blur', () => {
|
||||
this.hasCode = !!this.code
|
||||
})
|
||||
|
||||
/* this.coder.on('cursorActivity',()=>{
|
||||
this.coder.showHint()
|
||||
}) */
|
||||
},
|
||||
getCodeContent () {
|
||||
return this.code
|
||||
},
|
||||
setCodeContent (val) {
|
||||
setTimeout(() => {
|
||||
if (!val) {
|
||||
this.coder.setValue('')
|
||||
} else {
|
||||
this.coder.setValue(val)
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
// 获取当前语法类型
|
||||
_getLanguage (language) {
|
||||
// 在支持的语法类型列表中寻找传入的语法类型
|
||||
return this.modes.find((mode) => {
|
||||
// 所有的值都忽略大小写,方便比较
|
||||
const currentLanguage = language.toLowerCase()
|
||||
const currentLabel = mode.label.toLowerCase()
|
||||
const currentValue = mode.value.toLowerCase()
|
||||
|
||||
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
||||
return currentLabel === currentLanguage || currentValue === currentLanguage
|
||||
})
|
||||
},
|
||||
_getCoder () {
|
||||
const _this = this
|
||||
return new Promise((resolve) => {
|
||||
(function get () {
|
||||
if (_this.coder) {
|
||||
resolve(_this.coder)
|
||||
} else {
|
||||
setTimeout(get, 10)
|
||||
}
|
||||
})()
|
||||
})
|
||||
},
|
||||
// 更改模式
|
||||
changeMode (val) {
|
||||
// 修改编辑器的语法配置
|
||||
this.coder.setOption('mode', `text/${val}`)
|
||||
|
||||
// 获取修改后的语法
|
||||
const label = this._getLanguage(val).label.toLowerCase()
|
||||
|
||||
// 允许父容器通过以下函数监听当前的语法值
|
||||
this.$emit('language-change', label)
|
||||
},
|
||||
nullTipClick () {
|
||||
this.coder.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.code-editor-cust {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.CodeMirror {
|
||||
flex-grow: 1;
|
||||
z-index: 1;
|
||||
|
||||
.CodeMirror-code {
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.code-mode-select {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.null-tip {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 36px;
|
||||
z-index: 10;
|
||||
color: #ffffffc9;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.null-tip-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**选中样式偶然出现高度不够的情况*/
|
||||
|
||||
.CodeMirror-selected {
|
||||
min-height: 19px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全屏样式 */
|
||||
.full-screen-parent {
|
||||
position: relative;
|
||||
|
||||
.full-screen-icon {
|
||||
opacity: 0;
|
||||
color: black;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.full-screen-icon {
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.full-screen-icon {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.auto-height {
|
||||
.full-screen-child {
|
||||
min-height: 120px;
|
||||
max-height: 320px;
|
||||
height: unset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.full-screen .full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
height: 18.4px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="components-input-demo-presuffix">
|
||||
<a-input @click="openModal" placeholder="cron表达式" v-model="cron" @change="(e)=>handleOK(e.target.value)">
|
||||
<a-icon slot="prefix" type="schedule" title="cron控件"/>
|
||||
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</a-input>
|
||||
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import JCronModal from './modal/JCronModal'
|
||||
export default {
|
||||
name: 'JCron',
|
||||
components: {
|
||||
JCronModal
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false,
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
cron: this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.cron = val
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
this.$refs.innerVueCron.show()
|
||||
},
|
||||
handleOK (val) {
|
||||
this.cron = val
|
||||
this.$emit('change', this.cron)
|
||||
// this.$emit("change", Object.assign({}, this.cron));
|
||||
},
|
||||
handleEmpty () {
|
||||
this.handleOK('')
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.components-input-demo-presuffix .anticon-close-circle {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
transition: color 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
.components-input-demo-presuffix .anticon-close-circle:hover {
|
||||
color: #f5222d;
|
||||
}
|
||||
.components-input-demo-presuffix .anticon-close-circle:active {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<span>
|
||||
<!--YYYY-MM-DD HH:mm:ss-->
|
||||
<a-date-picker
|
||||
v-if="showType === 'time'"
|
||||
dropdownClassName="j-date-picker"
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
@change="handleDateChange"
|
||||
:value="momVal"
|
||||
:showTime="true"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer">
|
||||
</a-date-picker>
|
||||
<!--YYYY-MM-DD-->
|
||||
<a-date-picker
|
||||
v-if="showType === 'day'"
|
||||
dropdownClassName="j-date-picker"
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
@change="handleDateChange"
|
||||
:value="momVal"
|
||||
:showTime="false"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer">
|
||||
</a-date-picker>
|
||||
<!--YYYY-MM-->
|
||||
<a-month-picker
|
||||
v-if="showType === 'month'"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:disabled="disabled || readOnly"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
@change="handleMonthChange">
|
||||
</a-month-picker>
|
||||
<!--YYYY-->
|
||||
<a-date-picker
|
||||
v-if="showType === 'year'"
|
||||
:placeholder="placeholder"
|
||||
mode="year"
|
||||
:format="dateFormat"
|
||||
:value="year"
|
||||
:disabled="disabled || readOnly"
|
||||
:open="yearShowOne"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
@openChange="openChangeOne"
|
||||
@panelChange="panelChangeOne">
|
||||
</a-date-picker>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'JDate',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD',
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 控制选择时分秒
|
||||
showType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'time'
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const dateStr = this.value
|
||||
return {
|
||||
yearShowOne: false, // 控制年份模态框的显示与否
|
||||
year: '', // mode==“year” 专用
|
||||
momVal: !dateStr ? null : moment(dateStr, this.dateFormat)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
const fm = this.dateFormat
|
||||
moment.prototype.toJSON = function () {
|
||||
return moment(this).format(fm)
|
||||
}
|
||||
if (!val) {
|
||||
this.momVal = null
|
||||
} else {
|
||||
this.momVal = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleDateChange (mom, dateStr) {
|
||||
this.$emit('change', dateStr)
|
||||
},
|
||||
handleMonthChange (mom, dateStr) {
|
||||
this.$emit('change', dateStr)
|
||||
},
|
||||
/**
|
||||
* 弹出日历和关闭日历的回调
|
||||
* status:打开或关闭的状态
|
||||
* */
|
||||
openChangeOne (status) {
|
||||
this.yearShowOne = status
|
||||
},
|
||||
// 得到年份选择器的值
|
||||
panelChangeOne (value) {
|
||||
this.yearShowOne = false
|
||||
const year = moment(value, this.dateFormat).year().toString() // 处理成字符串
|
||||
this.year = value // 组件显示的
|
||||
this.$emit('change', year)
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-calendar-picker {
|
||||
min-width: 195px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="j-easy-cron">
|
||||
<div class="content">
|
||||
<div>
|
||||
<a-tabs size="small" v-model="curtab">
|
||||
<a-tab-pane tab="秒" key="second" v-if="!hideSecond">
|
||||
<second-ui v-model="second" :disabled="disabled"></second-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="分" key="minute">
|
||||
<minute-ui v-model="minute" :disabled="disabled"></minute-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="时" key="hour">
|
||||
<hour-ui v-model="hour" :disabled="disabled"></hour-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="日" key="day">
|
||||
<day-ui v-model="day" :week="week" :disabled="disabled"></day-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="月" key="month">
|
||||
<month-ui v-model="month" :disabled="disabled"></month-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="周" key="week">
|
||||
<week-ui v-model="week" :day="day" :disabled="disabled"></week-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="年" key="year" v-if="!hideYear && !hideSecond">
|
||||
<year-ui v-model="year" :disabled="disabled"></year-ui>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-divider />
|
||||
<!-- 执行时间预览 -->
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="18" style="margin-top: 22px;">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="秒" v-model="inputValues.second" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="分" v-model="inputValues.minute" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="时" v-model="inputValues.hour" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="日" v-model="inputValues.day" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="月" v-model="inputValues.month" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="周" v-model="inputValues.week" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="年" v-model="inputValues.year" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="16" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="Cron" v-model="inputValues.cron" @blur="onInputCronBlur" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
|
||||
<div>近十次执行时间(不含年)</div>
|
||||
<a-textarea type="textarea" :value="preTimeList" :rows="5" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SecondUi from './tabs/second'
|
||||
import MinuteUi from './tabs/minute'
|
||||
import HourUi from './tabs/hour'
|
||||
import DayUi from './tabs/day'
|
||||
import WeekUi from './tabs/week'
|
||||
import MonthUi from './tabs/month'
|
||||
import YearUi from './tabs/year'
|
||||
import CronParser from 'cron-parser'
|
||||
import dateFormat from './format-date'
|
||||
import { simpleDebounce } from '@/utils/util'
|
||||
import ACol from 'ant-design-vue/es/grid/Col'
|
||||
|
||||
export default {
|
||||
name: 'easy-cron',
|
||||
components: {
|
||||
ACol,
|
||||
SecondUi,
|
||||
MinuteUi,
|
||||
HourUi,
|
||||
DayUi,
|
||||
WeekUi,
|
||||
MonthUi,
|
||||
YearUi
|
||||
},
|
||||
props: {
|
||||
cronValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideSecond: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideYear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
curtab: this.hideSecond ? 'minute' : 'second',
|
||||
second: '*',
|
||||
minute: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?',
|
||||
year: '*',
|
||||
inputValues: { second: '', minute: '', hour: '', day: '', month: '', week: '', year: '', cron: '' },
|
||||
preTimeList: '执行预览,会忽略年份参数'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cronValue_c () {
|
||||
const result = []
|
||||
if (!this.hideSecond) result.push(this.second ? this.second : '*')
|
||||
result.push(this.minute ? this.minute : '*')
|
||||
result.push(this.hour ? this.hour : '*')
|
||||
result.push(this.day ? this.day : '*')
|
||||
result.push(this.month ? this.month : '*')
|
||||
result.push(this.week ? this.week : '?')
|
||||
if (!this.hideYear && !this.hideSecond) result.push(this.year ? this.year : '*')
|
||||
return result.join(' ')
|
||||
},
|
||||
cronValue_c2 () {
|
||||
const v = this.cronValue_c
|
||||
if (this.hideYear || this.hideSecond) return v
|
||||
const vs = v.split(' ')
|
||||
if (vs.length >= 6) {
|
||||
// 将 Quartz 星期 的规则转换为 CronParser 的规则
|
||||
vs[5] = this.convertQuartzWeekToCParser(vs[5])
|
||||
}
|
||||
return vs.slice(0, vs.length - 1).join(' ')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cronValue (newVal) {
|
||||
if (newVal === this.cronValue_c) {
|
||||
// console.info('same cron value: ' + newVal)
|
||||
return
|
||||
}
|
||||
this.formatValue()
|
||||
},
|
||||
cronValue_c (newVal) {
|
||||
this.calTriggerList()
|
||||
this.$emit('change', newVal)
|
||||
this.assignInput()
|
||||
},
|
||||
minute () {
|
||||
if (this.second === '*') {
|
||||
this.second = '0'
|
||||
}
|
||||
},
|
||||
hour () {
|
||||
if (this.minute === '*') {
|
||||
this.minute = '0'
|
||||
}
|
||||
},
|
||||
day (day) {
|
||||
if (day !== '?' && this.hour === '*') {
|
||||
this.hour = '0'
|
||||
}
|
||||
},
|
||||
week (week) {
|
||||
if (week !== '?' && this.hour === '*') {
|
||||
this.hour = '0'
|
||||
}
|
||||
},
|
||||
month () {
|
||||
if (this.day === '?' && this.week === '*') {
|
||||
this.week = '1'
|
||||
} else if (this.week === '?' && this.day === '*') {
|
||||
this.day = '1'
|
||||
}
|
||||
},
|
||||
year () {
|
||||
if (this.month === '*') {
|
||||
this.month = '1'
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.formatValue()
|
||||
this.$nextTick(() => {
|
||||
this.calTriggerListInner()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
assignInput () {
|
||||
Object.assign(this.inputValues, {
|
||||
second: this.second,
|
||||
minute: this.minute,
|
||||
hour: this.hour,
|
||||
day: this.day,
|
||||
month: this.month,
|
||||
week: this.week,
|
||||
year: this.year,
|
||||
cron: this.cronValue_c
|
||||
})
|
||||
},
|
||||
formatValue () {
|
||||
if (!this.cronValue) return
|
||||
const values = this.cronValue.split(' ').filter(item => !!item)
|
||||
if (!values || values.length <= 0) return
|
||||
let i = 0
|
||||
if (!this.hideSecond) this.second = values[i++]
|
||||
if (values.length > i) this.minute = values[i++]
|
||||
if (values.length > i) this.hour = values[i++]
|
||||
if (values.length > i) this.day = values[i++]
|
||||
if (values.length > i) this.month = values[i++]
|
||||
if (values.length > i) this.week = values[i++]
|
||||
if (values.length > i) this.year = values[i]
|
||||
this.assignInput()
|
||||
},
|
||||
// 将 Quartz 星期 的规则转换为 CronParser 的规则:
|
||||
// Quartz 的规则:1 = 周日,2 = 周一,3 = 周二,4 = 周三,5 = 周四,6 = 周五,7 = 周六
|
||||
// CronParser 的规则: 0 = 周日,1 = 周一,2 = 周二,3 = 周三,4 = 周四,5 = 周五,6 = 周六,7 = 周日
|
||||
convertQuartzWeekToCParser (week) {
|
||||
const convert = (v) => {
|
||||
if (v === '0') {
|
||||
return '1'
|
||||
}
|
||||
if (v === '1') {
|
||||
return '0'
|
||||
}
|
||||
return (Number.parseInt(v) - 1).toString()
|
||||
}
|
||||
// 匹配示例 1-7 or 1/7
|
||||
const patten1 = /^([0-7])([-/])([0-7])$/
|
||||
// 匹配示例 1,4,7
|
||||
const patten2 = /^([0-7])(,[0-7])+$/
|
||||
if (/^[0-7]$/.test(week)) {
|
||||
return convert(week)
|
||||
} else if (patten1.test(week)) {
|
||||
return week.replace(patten1, ($0, before, separator, after) => {
|
||||
if (separator === '/') {
|
||||
return convert(before) + separator + after
|
||||
} else {
|
||||
return convert(before) + separator + convert(after)
|
||||
}
|
||||
})
|
||||
} else if (patten2.test(week)) {
|
||||
return week.split(',').map(v => convert(v)).join(',')
|
||||
}
|
||||
return week
|
||||
},
|
||||
calTriggerList: simpleDebounce(function () {
|
||||
this.calTriggerListInner()
|
||||
}, 500),
|
||||
calTriggerListInner () {
|
||||
// 设置了回调函数
|
||||
if (this.remote) {
|
||||
this.remote(this.cronValue_c, +new Date(), v => {
|
||||
this.preTimeList = v
|
||||
})
|
||||
return
|
||||
}
|
||||
const format = 'yyyy-MM-dd hh:mm:ss'
|
||||
const options = {
|
||||
currentDate: dateFormat(new Date(), format)
|
||||
}
|
||||
const iter = CronParser.parseExpression(this.cronValue_c2, options)
|
||||
const result = []
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
result.push(dateFormat(new Date(iter.next()), format))
|
||||
}
|
||||
this.preTimeList = result.length > 0 ? result.join('\n') : '无执行时间'
|
||||
},
|
||||
onInputBlur () {
|
||||
this.second = this.inputValues.second
|
||||
this.minute = this.inputValues.minute
|
||||
this.hour = this.inputValues.hour
|
||||
this.day = this.inputValues.day
|
||||
this.month = this.inputValues.month
|
||||
this.week = this.inputValues.week
|
||||
this.year = this.inputValues.year
|
||||
},
|
||||
onInputCronBlur (event) {
|
||||
this.$emit('change', event.target.value)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'cronValue',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.j-easy-cron {
|
||||
|
||||
/deep/ .content {
|
||||
.ant-checkbox-wrapper + .ant-checkbox-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="input-cron">
|
||||
<a-input :placeholder="placeholder" v-model="editCronValue" :disabled="disabled">
|
||||
<a slot="addonAfter" @click="showConfigDlg" class="config-btn" :disabled="disabled">
|
||||
<a-icon type="setting"></a-icon>
|
||||
选择
|
||||
</a>
|
||||
</a-input>
|
||||
<j-modal :visible.sync="show" title="Cron表达式" width="800px">
|
||||
<easy-cron
|
||||
v-model="editCronValue"
|
||||
:exeStartTime="exeStartTime"
|
||||
:hideYear="hideYear"
|
||||
:remote="remote"
|
||||
:hideSecond="hideSecond"
|
||||
style="width: 100%"
|
||||
></easy-cron>
|
||||
</j-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EasyCron from './EasyCron.vue'
|
||||
|
||||
export default {
|
||||
name: 'input-cron',
|
||||
components: { EasyCron },
|
||||
model: {
|
||||
prop: 'cronValue',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
cronValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '800px'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入cron表达式'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
exeStartTime: {
|
||||
type: [Number, String, Object],
|
||||
default: 0
|
||||
},
|
||||
hideSecond: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideYear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editCronValue: this.cronValue,
|
||||
show: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cronValue (newVal) {
|
||||
if (newVal === this.editCronValue) {
|
||||
return
|
||||
}
|
||||
this.editCronValue = newVal
|
||||
},
|
||||
editCronValue (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showConfigDlg () {
|
||||
if (!this.disabled) {
|
||||
this.show = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.config-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
const dateFormat = (date, block) => {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let format = block || 'yyyy-MM-dd'
|
||||
|
||||
date = new Date(date)
|
||||
|
||||
const map = {
|
||||
M: date.getMonth() + 1, // 月份
|
||||
d: date.getDate(), // 日
|
||||
h: date.getHours(), // 小时
|
||||
m: date.getMinutes(), // 分
|
||||
s: date.getSeconds(), // 秒
|
||||
q: Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds() // 毫秒
|
||||
}
|
||||
|
||||
format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
|
||||
let v = map[t]
|
||||
if (v !== undefined) {
|
||||
if (all.length > 1) {
|
||||
v = `0${v}`
|
||||
v = v.substr(v.length - 2)
|
||||
}
|
||||
return v
|
||||
} else if (t === 'y') {
|
||||
return (date.getFullYear().toString()).substr(4 - all.length)
|
||||
}
|
||||
return all
|
||||
})
|
||||
|
||||
return format
|
||||
}
|
||||
|
||||
export default dateFormat
|
||||
@@ -0,0 +1,6 @@
|
||||
// 原开源项目地址:https://gitee.com/toktok/easy-cron
|
||||
|
||||
import InputCron from './InputCron.vue'
|
||||
|
||||
InputCron.name = 'JEasyCron'
|
||||
export default InputCron
|
||||
@@ -0,0 +1,21 @@
|
||||
export const WEEK_MAP_EN = {
|
||||
SUN: '1',
|
||||
MON: '2',
|
||||
TUE: '3',
|
||||
WED: '4',
|
||||
THU: '5',
|
||||
FRI: '6',
|
||||
SAT: '7'
|
||||
}
|
||||
|
||||
export const replaceWeekName = (c) => {
|
||||
// console.info('after: ' + c)
|
||||
if (c) {
|
||||
Object.keys(WEEK_MAP_EN).forEach(k => {
|
||||
c = c.replace(new RegExp(k, 'g'), WEEK_MAP_EN[k])
|
||||
})
|
||||
// c = c.replace(new RegExp('7', 'g'), '0')
|
||||
}
|
||||
// console.info('after: ' + c)
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disableChoice">每日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
日
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
日开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_WORK" class="choice" :disabled="disableChoice">工作日</a-radio>
|
||||
本月
|
||||
<a-input-number :disabled="type!==TYPE_WORK || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueWork" />
|
||||
日,最近的工作日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LAST" class="choice" :disabled="disableChoice">最后一日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i of specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'day',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
week: {
|
||||
type: String,
|
||||
default: '?'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
disableChoice () {
|
||||
return (this.week && this.week !== '?') || this.disabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value_c () {
|
||||
// 数值变化
|
||||
this.updateValue()
|
||||
},
|
||||
week () {
|
||||
// console.info('new week: ' + newVal)
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue () {
|
||||
this.$emit('change', this.disableChoice ? '?' : this.value_c)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 1
|
||||
this.maxValue = 31
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 31
|
||||
this.valueLoop.start = 1
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每时</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
时
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
时
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
时开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
时
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'minute',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 23
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 23
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每分</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
分
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
分
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
分开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
分
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'minute',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 59
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 59
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
// 主要用于日和星期的互斥使用
|
||||
const TYPE_NOT_SET = 'TYPE_NOT_SET'
|
||||
const TYPE_EVERY = 'TYPE_EVERY'
|
||||
const TYPE_RANGE = 'TYPE_RANGE'
|
||||
const TYPE_LOOP = 'TYPE_LOOP'
|
||||
const TYPE_WORK = 'TYPE_WORK'
|
||||
const TYPE_LAST = 'TYPE_LAST'
|
||||
const TYPE_SPECIFY = 'TYPE_SPECIFY'
|
||||
|
||||
const DEFAULT_VALUE = '?'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: 'prop',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
prop: {
|
||||
type: String,
|
||||
default: DEFAULT_VALUE
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const type = TYPE_EVERY
|
||||
return {
|
||||
DEFAULT_VALUE,
|
||||
// 类型
|
||||
type,
|
||||
// 启用日或者星期互斥用
|
||||
TYPE_NOT_SET,
|
||||
TYPE_EVERY,
|
||||
TYPE_RANGE,
|
||||
TYPE_LOOP,
|
||||
TYPE_WORK,
|
||||
TYPE_LAST,
|
||||
TYPE_SPECIFY,
|
||||
// 对于不同的类型,所定义的值也有所不同
|
||||
valueRange: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
valueLoop: {
|
||||
start: 0,
|
||||
interval: 1
|
||||
},
|
||||
valueWeek: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
valueList: [],
|
||||
valueWork: 1,
|
||||
maxValue: 0,
|
||||
minValue: 0,
|
||||
valueLast: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
prop (newVal) {
|
||||
if (newVal === this.value_c) {
|
||||
// console.info('skip ' + newVal)
|
||||
return
|
||||
}
|
||||
this.parseProp(newVal)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value_c () {
|
||||
const result = []
|
||||
switch (this.type) {
|
||||
case TYPE_NOT_SET:
|
||||
result.push('?')
|
||||
break
|
||||
case TYPE_EVERY:
|
||||
result.push('*')
|
||||
break
|
||||
case TYPE_RANGE:
|
||||
result.push(`${this.valueRange.start}-${this.valueRange.end}`)
|
||||
break
|
||||
case TYPE_LOOP:
|
||||
result.push(`${this.valueLoop.start}/${this.valueLoop.interval}`)
|
||||
break
|
||||
case TYPE_WORK:
|
||||
result.push(`${this.valueWork}W`)
|
||||
break
|
||||
case TYPE_LAST:
|
||||
result.push('L')
|
||||
break
|
||||
case TYPE_SPECIFY:
|
||||
if (this.valueList.length === 0) {
|
||||
this.valueList.push(this.minValue)
|
||||
}
|
||||
result.push(this.valueList.join(','))
|
||||
break
|
||||
default:
|
||||
result.push(this.DEFAULT_VALUE)
|
||||
break
|
||||
}
|
||||
return result.length > 0 ? result.join('') : this.DEFAULT_VALUE
|
||||
},
|
||||
// 指定值范围区间,介于最小值和最大值之间
|
||||
specifyRange () {
|
||||
const range = []
|
||||
for (let i = this.minValue; i <= this.maxValue; i++) {
|
||||
range.push(i)
|
||||
}
|
||||
return range
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseProp (value) {
|
||||
if (value === this.value_c) {
|
||||
// console.info('same ' + value)
|
||||
return
|
||||
}
|
||||
if (typeof (this.preProcessProp) === 'function') {
|
||||
value = this.preProcessProp(value)
|
||||
}
|
||||
try {
|
||||
if (!value || value === this.DEFAULT_VALUE) {
|
||||
this.type = TYPE_EVERY
|
||||
} else if (value.indexOf('?') >= 0) {
|
||||
this.type = TYPE_NOT_SET
|
||||
} else if (value.indexOf('-') >= 0) {
|
||||
this.type = TYPE_RANGE
|
||||
const values = value.split('-')
|
||||
if (values.length >= 2) {
|
||||
this.valueRange.start = parseInt(values[0])
|
||||
this.valueRange.end = parseInt(values[1])
|
||||
}
|
||||
} else if (value.indexOf('/') >= 0) {
|
||||
this.type = TYPE_LOOP
|
||||
const values = value.split('/')
|
||||
if (values.length >= 2) {
|
||||
this.valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0])
|
||||
this.valueLoop.interval = parseInt(values[1])
|
||||
}
|
||||
} else if (value.indexOf('W') >= 0) {
|
||||
this.type = TYPE_WORK
|
||||
const values = value.split('W')
|
||||
if (!values[0] && !isNaN(values[0])) {
|
||||
this.valueWork = parseInt(values[0])
|
||||
}
|
||||
} else if (value.indexOf('L') >= 0) {
|
||||
this.type = TYPE_LAST
|
||||
const values = value.split('L')
|
||||
this.valueLast = parseInt(values[0])
|
||||
} else if (value.indexOf(',') >= 0 || !isNaN(value)) {
|
||||
this.type = TYPE_SPECIFY
|
||||
this.valueList = value.split(',').map(item => parseInt(item))
|
||||
} else {
|
||||
this.type = TYPE_EVERY
|
||||
}
|
||||
} catch (e) {
|
||||
// console.info(e)
|
||||
this.type = TYPE_EVERY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
.config-list {
|
||||
text-align: left;
|
||||
margin: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.choice {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
|
||||
.w60 {
|
||||
width: 60px;
|
||||
}
|
||||
.w80 {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.list-check-item {
|
||||
padding: 1px 3px;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.tip-info {
|
||||
color: #999
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每月</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
月
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
月
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
月开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
月
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i of specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'month',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 1
|
||||
this.maxValue = 12
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 12
|
||||
this.valueLoop.start = 1
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每秒</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
秒
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
秒
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
秒开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
秒
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'second',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 59
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 59
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
// console.info('created')
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
|
||||
从
|
||||
<a-select v-model="valueRange.start" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
至
|
||||
<a-select v-model="valueRange.end" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
|
||||
从
|
||||
<a-select v-model="valueLoop.start" class="w80" :disabled="type!==TYPE_LOOP || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
天
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
import { replaceWeekName, WEEK_MAP_EN } from './const.js'
|
||||
|
||||
const WEEK_MAP = {
|
||||
周一: 2,
|
||||
周二: 3,
|
||||
周三: 4,
|
||||
周四: 5,
|
||||
周五: 6,
|
||||
周六: 7,
|
||||
// 按照国人习惯,将周日放到每周的最后一天
|
||||
周日: 1
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'week',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
day: {
|
||||
type: String,
|
||||
default: '*'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
WEEK_MAP,
|
||||
WEEK_MAP_EN
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disableChoice () {
|
||||
return (this.day && this.day !== '?') || this.disabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value_c () {
|
||||
// 如果设置日,那么星期就直接不设置
|
||||
this.updateValue()
|
||||
},
|
||||
day () {
|
||||
// console.info('new day: ' + newVal)
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue () {
|
||||
this.$emit('change', this.disableChoice ? '?' : this.value_c)
|
||||
},
|
||||
preProcessProp (c) {
|
||||
return replaceWeekName(c)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
// 0,7表示周日 1表示周一
|
||||
this.minValue = 1
|
||||
this.maxValue = 7
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 7
|
||||
this.valueLoop.start = 2
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每年</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="0" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
年
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="1" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
年
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="0" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
年开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="1" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
年
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'year',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
// console.info('change:' + newVal)
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const nowYear = (new Date()).getFullYear()
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 0
|
||||
this.valueRange.start = nowYear
|
||||
this.valueRange.end = nowYear + 100
|
||||
this.valueLoop.start = nowYear
|
||||
this.valueLoop.interval = 1
|
||||
// console.info('created')
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
import CronParser from 'cron-parser'
|
||||
import { replaceWeekName } from './tabs/const'
|
||||
|
||||
export default (rule, value, callback) => {
|
||||
// 没填写就不校验
|
||||
if (!value) {
|
||||
callback()
|
||||
return true
|
||||
}
|
||||
const values = value.split(' ').filter(item => !!item)
|
||||
if (values.length > 7) {
|
||||
callback(new Error('Cron表达式最多7项!'))
|
||||
return false
|
||||
}
|
||||
// 检查第7项
|
||||
let e = value
|
||||
if (values.length === 7) {
|
||||
const year = replaceWeekName(values[6])
|
||||
if (year !== '*' && year !== '?') {
|
||||
let yearValues
|
||||
if (year.indexOf('-') >= 0) {
|
||||
yearValues = year.split('-')
|
||||
} else if (year.indexOf('/')) {
|
||||
yearValues = year.split('/')
|
||||
} else {
|
||||
yearValues = [year]
|
||||
}
|
||||
// console.info(yearValues)
|
||||
// 判断是否都是数字
|
||||
const checkYear = yearValues.some(item => isNaN(item))
|
||||
if (checkYear) {
|
||||
callback(new Error('Cron表达式参数[年]错误:' + year))
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 取其中的前六项
|
||||
e = values.slice(0, 6).join(' ')
|
||||
}
|
||||
// 6位 没有年
|
||||
// 5位没有秒、年
|
||||
let result = true
|
||||
try {
|
||||
const iter = CronParser.parseExpression(e)
|
||||
iter.next()
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error('Cron表达式错误:' + e))
|
||||
result = false
|
||||
}
|
||||
return result
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="tinymce-editor">
|
||||
<editor
|
||||
v-if="!reloading"
|
||||
v-model="myValue"
|
||||
:init="init"
|
||||
:disabled="disabled"
|
||||
@onClick="onClick">
|
||||
</editor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tinymce from 'tinymce/tinymce'
|
||||
import Editor from '@tinymce/tinymce-vue'
|
||||
import 'tinymce/themes/silver/theme'
|
||||
import 'tinymce/plugins/image'
|
||||
import 'tinymce/plugins/link'
|
||||
import 'tinymce/plugins/media'
|
||||
import 'tinymce/plugins/table'
|
||||
import 'tinymce/plugins/lists'
|
||||
import 'tinymce/plugins/contextmenu'
|
||||
import 'tinymce/plugins/wordcount'
|
||||
import 'tinymce/plugins/colorpicker'
|
||||
import 'tinymce/plugins/textcolor'
|
||||
import 'tinymce/plugins/fullscreen'
|
||||
import 'tinymce/icons/default'
|
||||
import { uploadAction, getFileAccessHttpUrl } from '@/api/manage'
|
||||
import { getVmParentByName } from '@/utils/util'
|
||||
export default {
|
||||
components: {
|
||||
Editor
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plugins: {
|
||||
type: [String, Array],
|
||||
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
|
||||
},
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
|
||||
branding: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 初始化配置
|
||||
init: {
|
||||
language_url: '/tinymce/langs/zh_CN.js',
|
||||
language: 'zh_CN',
|
||||
skin_url: '/tinymce/skins/lightgray',
|
||||
height: 300,
|
||||
plugins: this.plugins,
|
||||
toolbar: this.toolbar,
|
||||
branding: false,
|
||||
menubar: false,
|
||||
toolbar_drawer: false,
|
||||
images_upload_handler: (blobInfo, success) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', blobInfo.blob(), blobInfo.filename())
|
||||
formData.append('biz', 'jeditor')
|
||||
formData.append('jeditor', '1')
|
||||
uploadAction(window._CONFIG.domianURL + '/sys/common/upload', formData).then((res) => {
|
||||
if (res.success) {
|
||||
if (res.message === 'local') {
|
||||
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
|
||||
success(img)
|
||||
} else {
|
||||
const img = getFileAccessHttpUrl(res.message)
|
||||
success(img)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
myValue: this.value,
|
||||
reloading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initATabsChangeAutoReload()
|
||||
},
|
||||
methods: {
|
||||
|
||||
reload () {
|
||||
this.reloading = true
|
||||
this.$nextTick(() => {
|
||||
this.reloading = false
|
||||
})
|
||||
},
|
||||
|
||||
onClick (e) {
|
||||
this.$emit('onClick', e, tinymce)
|
||||
},
|
||||
// 可以添加一些自己的自定义事件,如清空内容
|
||||
clear () {
|
||||
this.myValue = ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 自动判断父级是否是 <a-tabs/> 组件,然后添加事件监听,自动触发reload()
|
||||
*
|
||||
* 由于 tabs 组件切换会导致 tinymce 无法输入,
|
||||
* 只有重新加载才能使用(无论是vue版的还是jQuery版tinymce都有这个通病)
|
||||
*/
|
||||
initATabsChangeAutoReload () {
|
||||
// 获取父级
|
||||
const tabs = getVmParentByName(this, 'ATabs')
|
||||
const tabPane = getVmParentByName(this, 'ATabPane')
|
||||
if (tabs && tabPane) {
|
||||
// 用户自定义的 key
|
||||
const currentKey = tabPane.$vnode.key
|
||||
// 添加事件监听
|
||||
tabs.$on('change', (key) => {
|
||||
// 切换到自己时执行reload
|
||||
if (currentKey === key) {
|
||||
this.reload()
|
||||
}
|
||||
})
|
||||
// update--begin--autor:liusq-----date:20210316------for:富文本编辑器tab父组件可能导致的赋值问题------
|
||||
this.reload()
|
||||
// update--end--autor:liusq-----date:20210316------for:富文本编辑器tab父组件可能导致的赋值问题------
|
||||
} else {
|
||||
// update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
|
||||
const tabLayout = getVmParentByName(this, 'TabLayout')
|
||||
// update--begin--autor:liusq-----date:20210713------for:处理特殊情况excuteCallback不能使用------
|
||||
try {
|
||||
tabLayout.excuteCallback(() => {
|
||||
this.reload()
|
||||
})
|
||||
} catch (error) {
|
||||
if (tabLayout) {
|
||||
this.reload()
|
||||
}
|
||||
}
|
||||
// update--end--autor:liusq-----date:20210713------for:处理特殊情况excuteCallback不能使用------
|
||||
// update--begin--autor:wangshuai-----date:20200724------for:文本编辑器切换tab无法修改------
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
value (newValue) {
|
||||
this.myValue = (newValue == null ? '' : newValue)
|
||||
},
|
||||
myValue (newValue) {
|
||||
if (this.triggerChange) {
|
||||
this.$emit('change', newValue)
|
||||
} else {
|
||||
this.$emit('input', newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
<template slot="title">
|
||||
<span>{{value}}</span>
|
||||
</template>
|
||||
{{ value | ellipsis(length) }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JEllipsis',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 25
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div :class="disabled?'jero-form-container-disabled':''">
|
||||
<fieldset :disabled="disabled">
|
||||
<slot name="detail"></slot>
|
||||
</fieldset>
|
||||
<slot name="edit"></slot>
|
||||
<fieldset disabled>
|
||||
<slot></slot>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 使用方法
|
||||
* 在form下直接写这个组件就行了,
|
||||
*<a-form layout="inline" :form="form" >
|
||||
* <j-form-container :disabled="true">
|
||||
* <!-- 表单内容省略..... -->
|
||||
* </j-form-container>
|
||||
*</a-form>
|
||||
*/
|
||||
export default {
|
||||
name: 'JFormContainer',
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
console.log('我是表单禁用专用组件,但是我并不支持表单中iframe的内容禁用')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.jero-form-container-disabled{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.jero-form-container-disabled fieldset[disabled] {
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.jero-form-container-disabled .ant-select{
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jero-form-container-disabled .ant-upload-select{display:none}
|
||||
.jero-form-container-disabled .ant-upload-list{cursor:grabbing}
|
||||
.jero-form-container-disabled fieldset[disabled] .ant-upload-list{
|
||||
-ms-pointer-events: auto !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/*.jero-form-container-disabled .ant-upload-list-item-actions .anticon-delete,*/
|
||||
.jero-form-container-disabled .ant-upload-list-item .anticon-delete,
|
||||
.jero-form-container-disabled .ant-upload-list-item .anticon-close{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="img">
|
||||
<a-upload
|
||||
name="file"
|
||||
listType="picture-card"
|
||||
:multiple="isMultiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{biz:bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="beforeUpload"
|
||||
:disabled="disabled"
|
||||
v-bind="$attrs"
|
||||
:accept="accept"
|
||||
v-on="childListeners"
|
||||
@change="handleChange"
|
||||
@preview="handlePreview"
|
||||
:class="[!isMultiple?'imgupload':'', (!isMultiple && picUrl)?'image-upload-single-over':'' ]">
|
||||
<div>
|
||||
<!--<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>-->
|
||||
<div class="iconp">
|
||||
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
|
||||
<div class="ant-upload-text">{{ text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <j-image-preview-modal ref="JImagePreviewModal"></j-image-preview-modal>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
// import JImagePreviewModal from './modal/JImagePreviewModal'
|
||||
// const Base64 = require('js-base64').Base64
|
||||
|
||||
const uidGenerator = () => {
|
||||
return '-' + parseInt(Math.random() * 10000 + 1 + '', 10)
|
||||
}
|
||||
export default {
|
||||
name: 'JImageUpload',
|
||||
// components: { JImagePreviewModal },
|
||||
data () {
|
||||
return {
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
uploadLoading: false,
|
||||
image: false,
|
||||
picUrl: false,
|
||||
headers: {},
|
||||
fileList: [],
|
||||
accept: 'image/png, image/jpeg',
|
||||
previewImage: '',
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '上传'
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isMultiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler (val) {
|
||||
if (val instanceof Array) {
|
||||
this.initFileList(val.join(','))
|
||||
} else {
|
||||
this.initFileList(val)
|
||||
}
|
||||
if (!val || val.length === 0) {
|
||||
this.picUrl = false
|
||||
}
|
||||
},
|
||||
// 立刻执行handler
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const token = Vue.ls.get(ACCESS_TOKEN)
|
||||
this.headers = { 'X-Access-Token': token }
|
||||
},
|
||||
methods: {
|
||||
initFileList (paths) {
|
||||
if (!paths || paths.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
this.picUrl = true
|
||||
const fileList = []
|
||||
const arr = paths.split(',')
|
||||
for (let a = 0; a < arr.length; a++) {
|
||||
const url = getFileAccessHttpUrl(arr[a])
|
||||
fileList.push({
|
||||
uid: uidGenerator(),
|
||||
name: arr[a],
|
||||
status: 'done',
|
||||
url: url,
|
||||
response: {
|
||||
status: 'history',
|
||||
message: arr[a]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.fileList = fileList
|
||||
},
|
||||
beforeUpload: function (file) {
|
||||
const fileType = file.type
|
||||
if (fileType.indexOf('image') < 0) {
|
||||
this.$message.warning('请上传图片')
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleChange (info) {
|
||||
this.picUrl = false
|
||||
let fileList = info.fileList
|
||||
// update-begin-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
|
||||
if (this.number > 0) {
|
||||
fileList = fileList.slice(-this.number)
|
||||
}
|
||||
// update-end-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
|
||||
if (info.file.status === 'done') {
|
||||
if (info.file.response.success) {
|
||||
this.picUrl = true
|
||||
fileList = fileList.map((file) => {
|
||||
if (file.response) {
|
||||
file.url = `${file.response.result.id}`
|
||||
}
|
||||
return file
|
||||
})
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.error(`${info.file.name} 上传失败.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
this.handlePathChange()
|
||||
}
|
||||
},
|
||||
// 预览
|
||||
handlePreview (file) {
|
||||
if (file && file.url) {
|
||||
console.log(file)
|
||||
// const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/${file.url}`
|
||||
// const url = `${window._CONFIG.onlinePreviewDomainURL}?url=${encodeURIComponent(Base64.encode(fileFullUrl))}`
|
||||
// window.open(url)
|
||||
// this.$refs.JImagePreviewModal.open(file)
|
||||
this.imageUrl = getFileAccessHttpUrl(file.name)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
}
|
||||
},
|
||||
getAvatarView () {
|
||||
if (this.fileList.length > 0) {
|
||||
const url = this.fileList[this.fileList.length - 1].url
|
||||
return getFileAccessHttpUrl(url)
|
||||
}
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
if (!this.isMultiple && uploadFiles && uploadFiles.length > 0) {
|
||||
arr.push(uploadFiles[uploadFiles.length - 1].url)
|
||||
} else {
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
this.previewVisible = false
|
||||
},
|
||||
close () {
|
||||
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .imgupload .ant-upload-select{display:block}
|
||||
/deep/ .imgupload .ant-upload.ant-upload-select-picture-card{ width:120px;height: 120px;}
|
||||
/deep/ .imgupload .iconp{padding:32px;}
|
||||
/* update--end--autor:lvdandan-----date:20201016------for:j-image-upload图片组件单张图片详情回显空白*/
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-modal
|
||||
title="导入EXCEL"
|
||||
:width="600"
|
||||
:visible="visible"
|
||||
:confirmLoading="uploading"
|
||||
@cancel="handleClose">
|
||||
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="true"
|
||||
accept=".xls,.xlsx"
|
||||
:fileList="fileList"
|
||||
:remove="handleRemove"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:beforeUpload="beforeUpload">
|
||||
<a-button>
|
||||
<a-icon type="upload" />
|
||||
选择导入文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
|
||||
<template slot="footer">
|
||||
<a-button @click="handleClose">关闭</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleImport"
|
||||
:disabled="fileList.length === 0"
|
||||
:loading="uploading">
|
||||
{{ uploading ? '上传中...' : '开始上传' }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { postAction } from '@/api/manage'
|
||||
export default {
|
||||
name: 'JImportModal',
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
biz: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
uploading: false,
|
||||
fileList: [],
|
||||
uploadAction: '',
|
||||
foreignKeys: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url (val) {
|
||||
if (val) {
|
||||
this.uploadAction = window._CONFIG.domianURL + val
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
console.log(this.$attrs)
|
||||
this.uploadAction = window._CONFIG.domianURL + this.url
|
||||
},
|
||||
methods: {
|
||||
handleClose () {
|
||||
this.visible = false
|
||||
},
|
||||
show (arg) {
|
||||
this.fileList = []
|
||||
this.uploading = false
|
||||
this.visible = true
|
||||
this.foreignKeys = arg
|
||||
},
|
||||
handleRemove (file) {
|
||||
const index = this.fileList.indexOf(file)
|
||||
const newFileList = this.fileList.slice()
|
||||
newFileList.splice(index, 1)
|
||||
this.fileList = newFileList
|
||||
},
|
||||
beforeUpload (file) {
|
||||
this.fileList = [...this.fileList, file]
|
||||
return false
|
||||
},
|
||||
handleImport () {
|
||||
const { fileList } = this
|
||||
const formData = new FormData()
|
||||
if (this.biz) {
|
||||
formData.append('isSingleTableImport', this.biz)
|
||||
}
|
||||
if (this.foreignKeys && this.foreignKeys.length > 0) {
|
||||
formData.append('foreignKeys', this.foreignKeys)
|
||||
}
|
||||
fileList.forEach((file) => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploading = true
|
||||
postAction(this.uploadAction, formData).then((res) => {
|
||||
this.uploading = false
|
||||
if (res.success) {
|
||||
this.$message.success(res.message)
|
||||
this.visible = false
|
||||
this.$emit('ok')
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-input :placeholder="placeholder"
|
||||
:value="inputVal"
|
||||
@input="backValue"
|
||||
v-bind="$props"
|
||||
v-on="childListeners"
|
||||
></a-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
const JINPUT_QUERY_LIKE = 'like'
|
||||
const JINPUT_QUERY_NE = 'ne'
|
||||
const JINPUT_QUERY_GE = 'ge' // 大于等于
|
||||
const JINPUT_QUERY_LE = 'le' // 小于等于
|
||||
|
||||
export default {
|
||||
name: 'JInput',
|
||||
props: {
|
||||
...Input.props, // 拿到全部外部的参数
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: JINPUT_QUERY_LIKE
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
trim: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function () {
|
||||
this.initVal()
|
||||
}
|
||||
},
|
||||
// 当 type 变化的时候重新计算值
|
||||
type () {
|
||||
this.backValue({ target: { value: this.inputVal } })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initVal () {
|
||||
if (!this.value) {
|
||||
this.inputVal = ''
|
||||
} else {
|
||||
let text = this.value
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
// 修复路由传参的值传送到jinput框被前后各截取了一位 #1336
|
||||
if (text.indexOf('*') !== -1) {
|
||||
text = text.substring(1, text.length - 1)
|
||||
}
|
||||
break
|
||||
case JINPUT_QUERY_NE:
|
||||
text = text.substring(1)
|
||||
break
|
||||
case JINPUT_QUERY_GE:
|
||||
text = text.substring(2)
|
||||
break
|
||||
case JINPUT_QUERY_LE:
|
||||
text = text.substring(2)
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.inputVal = text
|
||||
}
|
||||
},
|
||||
backValue (e) {
|
||||
let text = e.target.value
|
||||
if (text && this.trim === true) {
|
||||
text = text.trim()
|
||||
}
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
text = '*' + text + '*'
|
||||
break
|
||||
case JINPUT_QUERY_NE:
|
||||
text = '!' + text
|
||||
break
|
||||
case JINPUT_QUERY_GE:
|
||||
text = '>=' + text
|
||||
break
|
||||
case JINPUT_QUERY_LE:
|
||||
text = '<=' + text
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.$emit('change', text)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
minHeight: '200px',
|
||||
previewStyle: 'vertical',
|
||||
useCommandShortcut: true,
|
||||
useDefaultHTMLSanitizer: true,
|
||||
usageStatistics: false,
|
||||
hideModeSwitch: false,
|
||||
toolbarItems: [
|
||||
'heading',
|
||||
'bold',
|
||||
'italic',
|
||||
'strike',
|
||||
'divider',
|
||||
'hr',
|
||||
'quote',
|
||||
'divider',
|
||||
'ul',
|
||||
'ol',
|
||||
'task',
|
||||
'indent',
|
||||
'outdent',
|
||||
'divider',
|
||||
'table',
|
||||
'link',
|
||||
'divider',
|
||||
'code',
|
||||
'codeblock'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="j-markdown-editor" :id="id"/>
|
||||
<div v-if="isShow">
|
||||
<j-modal
|
||||
title="图片上传"
|
||||
:visible.sync="dialogVisible"
|
||||
width="30%"
|
||||
:before-close="handleClose"
|
||||
@ok="handleOk">
|
||||
<a-tabs default-active-key="1" @change="handleChange">
|
||||
<a-tab-pane tab="本地图片上传" key="1" :forceRender="true">
|
||||
<j-upload v-model="fileList" :number="1"></j-upload>
|
||||
<div style="margin-top: 20px">
|
||||
<a-input v-model="remark" placeholder="请填写备注"></a-input>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="网络图片地址" key="2" :forceRender="true">
|
||||
<a-input v-model="networkPic" placeholder="请填写网络图片地址"></a-input>
|
||||
<a-input style="margin-top: 20px" v-model="remark" placeholder="请填写备注"></a-input>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</j-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import '@toast-ui/editor/dist/toastui-editor.css'
|
||||
import '@toast-ui/editor/dist/i18n/zh-cn'
|
||||
|
||||
import Editor from '@toast-ui/editor'
|
||||
import defaultOptions from './default-options'
|
||||
import JUpload from '@/components/jero/JUpload'
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JMarkdownEditor',
|
||||
components: {
|
||||
JUpload
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default () {
|
||||
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default () {
|
||||
return defaultOptions
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'markdown'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '300px'
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'zh-CN'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
isShow: false,
|
||||
activeIndex: '1',
|
||||
dialogVisible: false,
|
||||
index: '1',
|
||||
fileList: [],
|
||||
remark: '',
|
||||
imageName: '',
|
||||
imageUrl: '',
|
||||
networkPic: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorOptions () {
|
||||
const options = Object.assign({}, defaultOptions, this.options)
|
||||
options.initialEditType = this.mode
|
||||
options.height = this.height
|
||||
options.language = this.language
|
||||
return options
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newValue, preValue) {
|
||||
if (newValue !== preValue && newValue !== this.editor.getMarkdown()) {
|
||||
this.editor.setMarkdown(newValue)
|
||||
}
|
||||
},
|
||||
language () {
|
||||
this.destroyEditor()
|
||||
this.initEditor()
|
||||
},
|
||||
height (newValue) {
|
||||
this.editor.height(newValue)
|
||||
},
|
||||
mode (newValue) {
|
||||
this.editor.changeMode(newValue)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initEditor()
|
||||
},
|
||||
destroyed () {
|
||||
this.destroyEditor()
|
||||
},
|
||||
methods: {
|
||||
initEditor () {
|
||||
this.editor = new Editor({
|
||||
el: document.getElementById(this.id),
|
||||
...this.editorOptions
|
||||
})
|
||||
if (this.value) {
|
||||
this.editor.setMarkdown(this.value)
|
||||
}
|
||||
this.editor.on('change', () => {
|
||||
this.$emit('change', this.editor.getMarkdown())
|
||||
})
|
||||
// --begin 添加自定义上传按钮
|
||||
/*
|
||||
* 添加自定义按钮
|
||||
*/
|
||||
// 获取编辑器上的功能条
|
||||
const toolbar = this.editor.getUI().getToolbar()
|
||||
// 添加图片点击事件
|
||||
this.editor.eventManager.addEventType('isShowClickEvent')
|
||||
this.editor.eventManager.listen('isShowClickEvent', () => {
|
||||
this.isShow = true
|
||||
this.dialogVisible = true
|
||||
})
|
||||
// addImageBlobHook图片上传、剪切、拖拽都会走此方法
|
||||
// 删除默认监听事件
|
||||
this.editor.eventManager.removeEventHandler('addImageBlobHook')
|
||||
// 添加自定义监听事件
|
||||
this.editor.eventManager.listen('addImageBlobHook', (blob, callback) => {
|
||||
this.upload(blob, url => {
|
||||
callback(url)
|
||||
})
|
||||
})
|
||||
// 添加自定义按钮 第二个参数代表位置,不传默认放在最后
|
||||
toolbar.insertItem(15, {
|
||||
type: 'button',
|
||||
options: {
|
||||
name: 'customize',
|
||||
className: 'tui-image tui-toolbar-icons',
|
||||
event: 'isShowClickEvent',
|
||||
tooltip: '上传图片'
|
||||
}
|
||||
//
|
||||
})
|
||||
// --end 添加自定义上传按钮
|
||||
},
|
||||
destroyEditor () {
|
||||
if (!this.editor) return
|
||||
this.editor.off('change')
|
||||
this.editor.remove()
|
||||
},
|
||||
setMarkdown (value) {
|
||||
this.editor.setMarkdown(value)
|
||||
},
|
||||
getMarkdown () {
|
||||
return this.editor.getMarkdown()
|
||||
},
|
||||
setHtml (value) {
|
||||
this.editor.setHtml(value)
|
||||
},
|
||||
getHtml () {
|
||||
return this.editor.getHtml()
|
||||
},
|
||||
handleOk () {
|
||||
if (this.index === '1') {
|
||||
this.imageUrl = getFileAccessHttpUrl(this.fileList)
|
||||
if (this.remark) {
|
||||
this.addImgToMd(this.imageUrl, this.remark)
|
||||
} else {
|
||||
this.addImgToMd(this.imageUrl, '')
|
||||
}
|
||||
} else {
|
||||
if (this.remark) {
|
||||
this.addImgToMd(this.networkPic, this.remark)
|
||||
} else {
|
||||
this.addImgToMd(this.networkPic, '')
|
||||
}
|
||||
}
|
||||
this.index = '1'
|
||||
this.fileList = []
|
||||
this.imageName = ''
|
||||
this.imageUrl = ''
|
||||
this.remark = ''
|
||||
this.networkPic = ''
|
||||
this.dialogVisible = false
|
||||
this.isShow = false
|
||||
},
|
||||
handleClose (done) {
|
||||
done()
|
||||
},
|
||||
handleChange (val) {
|
||||
this.fileList = []
|
||||
this.remark = ''
|
||||
this.imageName = ''
|
||||
this.imageUrl = ''
|
||||
this.networkPic = ''
|
||||
this.index = val
|
||||
},
|
||||
// 添加图片到markdown
|
||||
addImgToMd (data, name) {
|
||||
const editor = this.editor.getCodeMirror()
|
||||
const editorHtml = this.editor.getCurrentModeEditor()
|
||||
const isMarkdownMode = this.editor.isMarkdownMode()
|
||||
if (isMarkdownMode) {
|
||||
editor.replaceSelection(``)
|
||||
} else {
|
||||
const range = editorHtml.getRange()
|
||||
const img = document.createElement('img')
|
||||
img.src = `${data}`
|
||||
img.alt = name
|
||||
range.insertNode(img)
|
||||
}
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
.j-markdown-editor {
|
||||
/deep/ .tui-editor-defaultUI {
|
||||
.te-mode-switch,
|
||||
.tui-scrollsync
|
||||
{
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
destroyOnClose
|
||||
>
|
||||
|
||||
<slot></slot>
|
||||
<!--有设置标题-->
|
||||
<template v-if="!isNoTitle" slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col class="left">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</a-col>
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!--没有设置标题-->
|
||||
<template v-else slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 处理 scopedSlots -->
|
||||
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 处理 slots -->
|
||||
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { getClass, getStyle } from '@/utils/props-util'
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JModal',
|
||||
props: {
|
||||
title: String,
|
||||
// 可使用 .sync 修饰符
|
||||
visible: Boolean,
|
||||
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许切换全屏(允许后右上角会出现一个按钮)
|
||||
switchFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击确定按钮的时候是否关闭弹窗
|
||||
okClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部使用的 slots ,不再处理
|
||||
usedSlots: ['title'],
|
||||
// 实际控制是否全屏的参数
|
||||
innerFullscreen: this.fullscreen
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
|
||||
_attrs () {
|
||||
const attrs = { ...this.$attrs }
|
||||
// 如果全屏就将宽度设为 100%
|
||||
if (this.innerFullscreen) {
|
||||
attrs.width = '100%'
|
||||
}
|
||||
return attrs
|
||||
},
|
||||
modalClass () {
|
||||
return {
|
||||
'j-modal-box': true,
|
||||
fullscreen: this.innerFullscreen,
|
||||
'no-title': this.isNoTitle,
|
||||
'no-footer': this.isNoFooter
|
||||
}
|
||||
},
|
||||
modalStyle () {
|
||||
const style = {}
|
||||
// 如果全屏就将top设为 0
|
||||
if (this.innerFullscreen) {
|
||||
style.top = '0'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isNoTitle () {
|
||||
return !this.title && !this.allSlotsKeys.includes('title')
|
||||
},
|
||||
isNoFooter () {
|
||||
return this._attrs.footer === null
|
||||
},
|
||||
slotsKeys () {
|
||||
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
scopedSlotsKeys () {
|
||||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys () {
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon () {
|
||||
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible () {
|
||||
if (this.visible) {
|
||||
this.innerFullscreen = this.fullscreen
|
||||
}
|
||||
},
|
||||
innerFullscreen (val) {
|
||||
this.$emit('update:fullscreen', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getClass (clazz) {
|
||||
return { ...getClass(this), ...clazz }
|
||||
},
|
||||
getStyle (style) {
|
||||
return { ...getStyle(this), ...style }
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
if (this.okClose) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 切换全屏 */
|
||||
toggleFullscreen () {
|
||||
this.innerFullscreen = !this.innerFullscreen
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.j-modal-box {
|
||||
&.fullscreen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
||||
// 兼容1.6.2版本的antdv
|
||||
& .ant-modal {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
& .ant-modal-content {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
|
||||
& .ant-modal-body {
|
||||
/* title 和 footer 各占 55px */
|
||||
height: calc(100% - 55px - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-title, &.no-footer {
|
||||
.ant-modal-body {
|
||||
height: calc(100% - 55px);
|
||||
}
|
||||
}
|
||||
&.no-title.no-footer {
|
||||
.ant-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-modal-title-row {
|
||||
.left {
|
||||
width: calc(100% - 56px - 56px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 56px;
|
||||
position: inherit;
|
||||
|
||||
.ant-modal-close {
|
||||
right: 56px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.no-title{
|
||||
.ant-modal-header {
|
||||
padding: 0 24px;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<j-modal :visible="visible" :confirmLoading="loading" :after-close="afterClose" v-bind="modalProps" @ok="onOk" @cancel="onCancel">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-html="content"></div>
|
||||
<a-form-model ref="form" :model="model" :rules="rules">
|
||||
<a-form-model-item prop="input">
|
||||
<a-input ref="input" v-model="model.input" v-bind="inputProps" @pressEnter="onInputPressEnter"/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-spin>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pick from 'lodash.pick'
|
||||
|
||||
export default {
|
||||
name: 'JPrompt',
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
content: '',
|
||||
// 弹窗参数
|
||||
modalProps: {
|
||||
title: ''
|
||||
},
|
||||
inputProps: {
|
||||
placeholder: ''
|
||||
},
|
||||
// form model
|
||||
model: {
|
||||
input: ''
|
||||
},
|
||||
// 校验
|
||||
rule: [],
|
||||
// 回调函数
|
||||
callback: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rules () {
|
||||
return {
|
||||
input: this.rule
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show (options) {
|
||||
this.content = options.content
|
||||
if (Array.isArray(options.rule)) {
|
||||
this.rule = options.rule
|
||||
}
|
||||
if (options.defaultValue != null) {
|
||||
this.model.input = options.defaultValue
|
||||
}
|
||||
// 取出常用的弹窗参数
|
||||
const pickModalProps = pick(options, 'title', 'centered', 'cancelText', 'closable', 'mask', 'maskClosable', 'okText', 'okType', 'okButtonProps', 'cancelButtonProps', 'width', 'wrapClassName', 'zIndex', 'dialogStyle', 'dialogClass')
|
||||
this.modalProps = Object.assign({}, pickModalProps, options.modalProps)
|
||||
// 取出常用的input参数
|
||||
const pickInputProps = pick(options, 'placeholder', 'allowClear')
|
||||
this.inputProps = Object.assign({}, pickInputProps, options.inputProps)
|
||||
// 回调函数
|
||||
this.callback = pick(options, 'onOk', 'onOkAsync', 'onCancel')
|
||||
this.visible = true
|
||||
this.$nextTick(() => this.$refs.input.focus())
|
||||
},
|
||||
|
||||
onOk () {
|
||||
this.$refs.form.validate((ok) => {
|
||||
if (ok) {
|
||||
const event = { value: this.model.input, target: this }
|
||||
// 异步方法优先级高于同步方法
|
||||
if (typeof this.callback.onOkAsync === 'function') {
|
||||
this.callback.onOkAsync(event)
|
||||
} else if (typeof this.callback.onOk === 'function') {
|
||||
this.callback.onOk(event)
|
||||
this.close()
|
||||
} else {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
if (typeof this.callback.onCancel === 'function') {
|
||||
this.callback.onCancel(this.model.input)
|
||||
}
|
||||
this.close()
|
||||
},
|
||||
|
||||
onInputPressEnter () {
|
||||
this.onOk()
|
||||
},
|
||||
|
||||
close () {
|
||||
this.visible = this.loading ? this.visible : false
|
||||
},
|
||||
|
||||
forceClose () {
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
showLoading () {
|
||||
this.loading = true
|
||||
},
|
||||
hideLoading () {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
afterClose (e) {
|
||||
if (typeof this.modalProps.afterClose === 'function') {
|
||||
this.modalProps.afterClose(e)
|
||||
}
|
||||
this.$emit('after-close', e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
import JModal from './JModal'
|
||||
import JPrompt from './JPrompt'
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.component(JModal.name, JModal)
|
||||
|
||||
const JPromptExtend = Vue.extend(JPrompt)
|
||||
Vue.prototype.$JPrompt = function (options = {}) {
|
||||
// 创建prompt实例
|
||||
const vm = new JPromptExtend().$mount()
|
||||
vm.show(options)
|
||||
// 关闭后销毁
|
||||
vm.$on('after-close', () => vm.$destroy())
|
||||
return vm
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
destroyOnClose
|
||||
>
|
||||
|
||||
<slot></slot>
|
||||
<!--有设置标题-->
|
||||
<template v-if="!isNoTitle" slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col class="left">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</a-col>
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!--没有设置标题-->
|
||||
<template v-else slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 处理 scopedSlots -->
|
||||
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 处理 slots -->
|
||||
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { getClass, getStyle } from '@/utils/props-util'
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JModal',
|
||||
props: {
|
||||
title: String,
|
||||
// 可使用 .sync 修饰符
|
||||
visible: Boolean,
|
||||
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许切换全屏(允许后右上角会出现一个按钮)
|
||||
switchFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击确定按钮的时候是否关闭弹窗
|
||||
okClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部使用的 slots ,不再处理
|
||||
usedSlots: ['title'],
|
||||
// 实际控制是否全屏的参数
|
||||
innerFullscreen: this.fullscreen
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
|
||||
_attrs () {
|
||||
const attrs = { ...this.$attrs }
|
||||
// 如果全屏就将宽度设为 100%
|
||||
if (this.innerFullscreen) {
|
||||
attrs.width = '100%'
|
||||
}
|
||||
return attrs
|
||||
},
|
||||
modalClass () {
|
||||
return {
|
||||
'j-modal-box': true,
|
||||
fullscreen: this.innerFullscreen,
|
||||
'no-title': this.isNoTitle,
|
||||
'no-footer': this.isNoFooter
|
||||
}
|
||||
},
|
||||
modalStyle () {
|
||||
const style = {}
|
||||
// 如果全屏就将top设为 0
|
||||
if (this.innerFullscreen) {
|
||||
style.top = '0'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isNoTitle () {
|
||||
return !this.title && !this.allSlotsKeys.includes('title')
|
||||
},
|
||||
isNoFooter () {
|
||||
return this._attrs.footer === null
|
||||
},
|
||||
slotsKeys () {
|
||||
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
scopedSlotsKeys () {
|
||||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys () {
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon () {
|
||||
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible () {
|
||||
if (this.visible) {
|
||||
this.innerFullscreen = this.fullscreen
|
||||
}
|
||||
},
|
||||
innerFullscreen (val) {
|
||||
this.$emit('update:fullscreen', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getClass (clazz) {
|
||||
return { ...getClass(this), ...clazz }
|
||||
},
|
||||
getStyle (style) {
|
||||
return { ...getStyle(this), ...style }
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
if (this.okClose) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 切换全屏 */
|
||||
toggleFullscreen () {
|
||||
this.innerFullscreen = !this.innerFullscreen
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-modal-box {
|
||||
&.fullscreen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
||||
// 兼容1.6.2版本的antdv
|
||||
& .ant-modal {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
& .ant-modal-content {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
|
||||
& .ant-modal-body {
|
||||
/* title 和 footer 各占 55px */
|
||||
height: calc(100% - 55px - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-title, &.no-footer {
|
||||
.ant-modal-body {
|
||||
height: calc(100% - 55px);
|
||||
}
|
||||
}
|
||||
&.no-title.no-footer {
|
||||
.ant-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-modal-title-row {
|
||||
.left {
|
||||
width: calc(100% - 56px - 56px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 56px;
|
||||
position: inherit;
|
||||
|
||||
.ant-modal-close {
|
||||
right: 56px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.no-title{
|
||||
.ant-modal-header {
|
||||
padding: 0 24px;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="components-input-demo-presuffix" v-if="avalid">
|
||||
<!---->
|
||||
<a-input @click="openModal" :placeholder="placeholder" v-model="showText" readOnly :disabled="disabled">
|
||||
<a-icon slot="prefix" type="cluster" :title="title"/>
|
||||
<a-icon v-if="showText" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</a-input>
|
||||
|
||||
<j-popup-onl-report
|
||||
ref="jPopupOnlReport"
|
||||
:code="code"
|
||||
:multi="multi"
|
||||
:sorter="sorter"
|
||||
:groupId="uniqGroupId"
|
||||
:param="param"
|
||||
@ok="callBack"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JPopupOnlReport from './modal/JPopupOnlReport'
|
||||
|
||||
export default {
|
||||
name: 'JPopup',
|
||||
components: {
|
||||
JPopupOnlReport
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
orgFields: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
destFields: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
/** 排序列,指定要排序的列,使用方式:列名=desc|asc */
|
||||
sorter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 1200,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// popup动态参数 支持系统变量语法
|
||||
param: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {}
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
/** 分组ID,用于将多个popup的请求合并到一起,不传不分组 */
|
||||
groupId: String
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showText: '',
|
||||
title: '',
|
||||
avalid: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uniqGroupId () {
|
||||
if (this.groupId) {
|
||||
const { groupId, code, field, orgFields, destFields } = this
|
||||
return `${groupId}_${code}_${field}_${orgFields}_${destFields}`
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (!val) {
|
||||
this.showText = ''
|
||||
} else {
|
||||
this.showText = val.split(this.spliter).join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
mounted () {
|
||||
if (!this.orgFields || !this.destFields || !this.code) {
|
||||
this.$message.error('popup参数未正确配置!')
|
||||
this.avalid = false
|
||||
}
|
||||
if (this.destFields.split(',').length !== this.orgFields.split(',').length) {
|
||||
this.$message.error('popup参数未正确配置,原始值和目标值数量不一致!')
|
||||
this.avalid = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
if (this.disabled === false) {
|
||||
this.$refs.jPopupOnlReport.show()
|
||||
}
|
||||
},
|
||||
handleEmpty () {
|
||||
this.showText = ''
|
||||
const destFieldsArr = this.destFields.split(',')
|
||||
if (destFieldsArr.length === 0) {
|
||||
return
|
||||
}
|
||||
const res = {}
|
||||
for (let i = 0; i < destFieldsArr.length; i++) {
|
||||
res[destFieldsArr[i]] = ''
|
||||
}
|
||||
if (this.triggerChange) {
|
||||
this.$emit('callback', res)
|
||||
} else {
|
||||
this.$emit('input', '', res)
|
||||
}
|
||||
},
|
||||
callBack (rows) {
|
||||
// update--begin--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
|
||||
const orgFieldsArr = this.orgFields.split(',')
|
||||
const destFieldsArr = this.destFields.split(',')
|
||||
let resetText = false
|
||||
if (this.field && this.field.length > 0) {
|
||||
this.showText = ''
|
||||
resetText = true
|
||||
}
|
||||
const res = {}
|
||||
if (orgFieldsArr.length > 0) {
|
||||
for (let i = 0; i < orgFieldsArr.length; i++) {
|
||||
const tempDestArr = []
|
||||
for (const rw of rows) {
|
||||
let val = rw[orgFieldsArr[i]]
|
||||
// update--begin--autor:liusq-----date:20210713------for:处理val等于0的情况issues/I3ZL4T------
|
||||
if (typeof val === 'undefined' || val == null || val.toString() === '') {
|
||||
val = ''
|
||||
}
|
||||
// update--end--autor:liusq-----date:20210713------for:处理val等于0的情况issues/I3ZL4T------
|
||||
tempDestArr.push(val)
|
||||
}
|
||||
res[destFieldsArr[i]] = tempDestArr.join(',')
|
||||
}
|
||||
if (resetText === true) {
|
||||
const tempText = []
|
||||
for (const rw of rows) {
|
||||
let val = rw[orgFieldsArr[destFieldsArr.indexOf(this.field)]]
|
||||
if (!val) {
|
||||
val = ''
|
||||
}
|
||||
tempText.push(val)
|
||||
}
|
||||
this.showText = tempText.join(',')
|
||||
}
|
||||
// update--end--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
|
||||
}
|
||||
if (this.triggerChange) {
|
||||
// v-dec时即triggerChange为true时 将整个对象给form页面 让他自己setFieldsValue
|
||||
this.$emit('callback', res)
|
||||
} else {
|
||||
// v-model时 需要传一个参数field 表示当前这个字段 从而根据这个字段的顺序找到原始值
|
||||
// this.$emit("input",row[orgFieldsArr[destFieldsArr.indexOf(this.field)]])
|
||||
let str = ''
|
||||
if (this.showText) {
|
||||
str = this.showText.split(',').join(this.spliter)
|
||||
}
|
||||
this.$emit('input', str, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.components-input-demo-presuffix .anticon-close-circle {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
transition: color 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.components-input-demo-presuffix .anticon-close-circle:hover {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.components-input-demo-presuffix .anticon-close-circle:active {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
:value="arrayValue"
|
||||
@change="onChange"
|
||||
option-filter-prop="children"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item,index) in newOptions"
|
||||
:key="index"
|
||||
:getPopupContainer="getParentContainer"
|
||||
:value="item.value">
|
||||
{{ item.text || item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// option {label:,value:}
|
||||
export default {
|
||||
name: 'JSelectMultiple',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
newOptions: [],
|
||||
arrayValue: !this.value ? [] : this.value.split(this.spliter) // arrayValue是已选中的数据
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.arrayValue = []
|
||||
} else {
|
||||
this.arrayValue = this.value.split(this.spliter)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.newOptions = this.options
|
||||
},
|
||||
methods: {
|
||||
onChange (selectedValue) {
|
||||
this.newOptions = this.options
|
||||
if (this.triggerChange) {
|
||||
this.$emit('change', selectedValue.join(this.spliter))
|
||||
} else {
|
||||
this.$emit('input', selectedValue.join(this.spliter))
|
||||
}
|
||||
},
|
||||
getParentContainer (node) {
|
||||
if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.querySelector(this.popContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="drag" ref="dragDiv">
|
||||
<div class="drag_bg"></div>
|
||||
<div class="drag_text">{{confirmWords}}</div>
|
||||
<div ref="moveDiv" @mousedown="mousedownFn($event)" :class="{'handler_ok_bg':confirmSuccess}" class="handler handler_bg" style="border: 0.5px solid #fff;height: 34px;position: absolute;top: 0;left: 0;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JSlider',
|
||||
data () {
|
||||
return {
|
||||
beginClientX: 0, /* 距离屏幕左端距离 */
|
||||
mouseMoveStata: false, /* 触发拖动状态 判断 */
|
||||
maxwidth: '', /* 拖动最大宽度,依据滑块宽度算出来的 */
|
||||
confirmWords: '拖动滑块验证', /* 滑块文字 */
|
||||
confirmSuccess: false /* 验证成功判断 */
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isSuccess () {
|
||||
return this.confirmSuccess
|
||||
},
|
||||
mousedownFn: function (e) {
|
||||
if (!this.confirmSuccess) {
|
||||
e.preventDefault && e.preventDefault() // 阻止文字选中等 浏览器默认事件
|
||||
this.mouseMoveStata = true
|
||||
this.beginClientX = e.clientX
|
||||
}
|
||||
}, // mousedoen 事件
|
||||
successFunction () {
|
||||
this.confirmSuccess = true
|
||||
this.confirmWords = '验证通过'
|
||||
if (window.addEventListener) {
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mousemove', this.mouseMoveFn)
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mouseup', this.moseUpFn)
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mouseup', () => {})
|
||||
}
|
||||
document.getElementsByClassName('drag_text')[0].style.color = '#fff'
|
||||
document.getElementsByClassName('handler')[0].style.left = this.maxwidth + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = this.maxwidth + 'px'
|
||||
|
||||
this.$emit('onSuccess', true)
|
||||
}, // 验证成功函数
|
||||
mouseMoveFn (e) {
|
||||
if (this.mouseMoveStata) {
|
||||
const width = e.clientX - this.beginClientX
|
||||
if (width > 0 && width <= this.maxwidth) {
|
||||
document.getElementsByClassName('handler')[0].style.left = width + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = width + 'px'
|
||||
} else if (width > this.maxwidth) {
|
||||
this.successFunction()
|
||||
}
|
||||
}
|
||||
}, // mousemove事件
|
||||
moseUpFn (e) {
|
||||
this.mouseMoveStata = false
|
||||
var width = e.clientX - this.beginClientX
|
||||
if (width < this.maxwidth) {
|
||||
// ---- update-begin- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
|
||||
const handler = document.getElementsByClassName('handler')[0]
|
||||
if (handler) {
|
||||
handler.style.left = 0 + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = 0 + 'px'
|
||||
}
|
||||
// ---- update-end- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
|
||||
}
|
||||
} // mouseup事件
|
||||
},
|
||||
mounted () {
|
||||
this.maxwidth = this.$refs.dragDiv.clientWidth - this.$refs.moveDiv.clientWidth
|
||||
document.getElementsByTagName('html')[0].addEventListener('mousemove', this.mouseMoveFn)
|
||||
document.getElementsByTagName('html')[0].addEventListener('mouseup', this.moseUpFn)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drag{
|
||||
position: relative;
|
||||
background-color: #e8e8e8;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
.handler{
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
.handler_bg{
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
.handler_ok_bg{
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
.drag_bg{
|
||||
background-color: #7ac23c;
|
||||
height: 34px;
|
||||
width: 0;
|
||||
}
|
||||
.drag_text{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;text-align: center;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-o-user-select:none;
|
||||
-ms-user-select:none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,678 @@
|
||||
<template>
|
||||
<div class="j-super-query-box">
|
||||
|
||||
<slot name="button" :isActive="superQueryFlag" :isMobile="izMobile" :open="handleOpen" :reset="handleReset">
|
||||
<a-tooltip v-if="superQueryFlag" v-bind="tooltipProps" :mouseLeaveDelay="0.2">
|
||||
<!-- begin 不知道为什么不加上这段代码就无法生效 -->
|
||||
<span v-show="false">{{tooltipProps}}</span>
|
||||
<!-- end 不知道为什么不加上这段代码就无法生效 -->
|
||||
<template slot="title">
|
||||
<span>{{ $t('superQuery.advancedQueryEffective') }}</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleReset">{{ $t('empty') }}</a>
|
||||
</template>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="handleOpen">
|
||||
<a-icon type="appstore" theme="twoTone" spin/>
|
||||
<span>{{ $t('superQuery.advancedQuery') }}</span>
|
||||
</a-button>
|
||||
<a-button v-if="izMobile" type="primary" icon="delete" @click="handleReset"/>
|
||||
</a-button-group>
|
||||
</a-tooltip>
|
||||
<a-button v-else type="primary" icon="filter" @click="handleOpen">{{ $t('superQuery.advancedQuery') }}</a-button>
|
||||
</slot>
|
||||
|
||||
<j-modal
|
||||
:title="$t('superQuery.advancedQuery')+$t('superQuery.constructor')"
|
||||
:width="1000"
|
||||
:visible="visible"
|
||||
@cancel="handleCancel"
|
||||
:mask="false"
|
||||
:fullscreen="izMobile"
|
||||
class="j-super-query-modal"
|
||||
style="top:5%;max-height: 95%;"
|
||||
>
|
||||
|
||||
<template slot="footer">
|
||||
<div style="float: left">
|
||||
<a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button>
|
||||
<a-button :loading="loading" @click="handleSave">{{ $t('superQuery.saveQueryCriteria') }}</a-button>
|
||||
</div>
|
||||
<a-button :loading="loading" @click="handleCancel">{{ $t('close') }}</a-button>
|
||||
<a-button :loading="loading" type="primary" @click="handleOk">{{ $t('query') }}</a-button>
|
||||
</template>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="24-5">
|
||||
|
||||
<a-empty v-if="queryParamsModel.length === 0" style="margin-bottom: 12px;">
|
||||
<div slot="description">
|
||||
<span>{{ $t('no') }}+{{ $t('query') }}+{{ $t('condition') }}</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleAdd">{{ $t('click') }}+{{ $t('newlyAdded') }}</a>
|
||||
</div>
|
||||
</a-empty>
|
||||
|
||||
<a-form v-else layout="inline">
|
||||
|
||||
<a-row style="margin-bottom: 12px;">
|
||||
<a-col :md="12" :xs="24">
|
||||
<a-form-item :label="$t('superQuery.filterMatching')" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
|
||||
<a-select v-model="matchType" :getPopupContainer="node=>node.parentNode" style="width: 100%;">
|
||||
<a-select-option value="and">AND{{$t('superQuery.allMatching')}}</a-select-option>
|
||||
<a-select-option value="or">OR{{$t('superQuery.anyOneMatches')}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row type="flex" style="margin-bottom:10px" :gutter="16" v-for="(item, index) in queryParamsModel" :key="index">
|
||||
|
||||
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
|
||||
<a-tree-select
|
||||
:showSearch="true"
|
||||
v-model="item.field"
|
||||
:treeData="fieldTreeData"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="$t('superQuery.selectQueryField')"
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
:getPopupContainer="node=>node.parentNode"
|
||||
style="width: 100%"
|
||||
@select="(val,option)=>handleSelected(option,item)"
|
||||
>
|
||||
</a-tree-select>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="4" :xs="24" style="margin-bottom: 12px;">
|
||||
<a-select :placeholder="$t('superQuery.matchRules')" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
|
||||
<a-select-option value="eq">{{ $t('superQuery.beEqualTo') }}</a-select-option>
|
||||
<a-select-option value="like">{{ $t('superQuery.contain') }}</a-select-option>
|
||||
<a-select-option value="right_like">{{ $t('superQuery.withStart') }}</a-select-option>
|
||||
<a-select-option value="left_like">{{ $t('superQuery.withEnd') }}</a-select-option>
|
||||
<a-select-option value="in">{{ $t('superQuery.in') }}</a-select-option>
|
||||
<a-select-option value="ne">{{ $t('superQuery.notEqual') }}</a-select-option>
|
||||
<a-select-option value="gt">{{ $t('superQuery.granter') }}</a-select-option>
|
||||
<a-select-option value="ge">{{ $t('superQuery.greaterOrEqual') }}</a-select-option>
|
||||
<a-select-option value="lt">{{ $t('superQuery.less') }}</a-select-option>
|
||||
<a-select-option value="le">{{ $t('superQuery.lessOrEqual') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
|
||||
<!-- 下拉搜索 -->
|
||||
<j-search-select-tag v-if="item.type==='sel_search'" v-model="item.val" :dict="getDictInfo(item)" placeholder="请选择"/>
|
||||
<!-- 下拉多选 -->
|
||||
<template v-else-if="item.type==='list_multi'">
|
||||
<j-multi-select-tag v-if="item.options" v-model="item.val" :options="item.options" :placeholder="$t('pleaseSelect')"/>
|
||||
<j-multi-select-tag v-else v-model="item.val" :dictCode="getDictInfo(item)" :placeholder="$t('pleaseSelect')"/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.dictCode">
|
||||
<template v-if="item.type === 'table-dict'">
|
||||
<j-popup
|
||||
v-model="item.val"
|
||||
:code="item.dictTable"
|
||||
:field="item.dictCode"
|
||||
:orgFields="item.dictCode"
|
||||
:destFields="item.dictCode"
|
||||
:multi="true"
|
||||
></j-popup>
|
||||
</template>
|
||||
<template v-else>
|
||||
<j-multi-select-tag v-show="allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" :placeholder="$t('pleaseSelect')"/>
|
||||
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" :placeholder="$t('pleaseSelect')"/>
|
||||
</template>
|
||||
</template>
|
||||
<j-popup
|
||||
v-else-if="item.type === 'popup'"
|
||||
:value="item.val"
|
||||
v-bind="item.popup"
|
||||
group-id="superQuery"
|
||||
@input="(e,v)=>handleChangeJPopup(item,e,v)"
|
||||
:multi="true"/>
|
||||
<j-select-multi-user
|
||||
v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
|
||||
v-model="item.val"
|
||||
:buttons="false"
|
||||
:multiple="false"
|
||||
:placeholder="$t('pleaseSelect')+$t('superQuery.user')"
|
||||
:returnKeys="['id', item.customReturnField || 'username']"
|
||||
/>
|
||||
<j-select-depart
|
||||
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
|
||||
v-model="item.val"
|
||||
:multi="false"
|
||||
:placeholder="$t('pleaseSelect')+$t('department')"
|
||||
:customReturnField="item.customReturnField || 'id'"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="item.options instanceof Array"
|
||||
v-model="item.val"
|
||||
:options="item.options"
|
||||
allowClear
|
||||
:placeholder="$t('pleaseSelect')"
|
||||
:mode="allowMultiple(item)?'multiple':''"
|
||||
/>
|
||||
<j-area-linkage v-model="item.val" v-else-if="item.type==='area-linkage' || item.type==='pca'" style="width: 100%"/>
|
||||
<j-date v-else-if=" item.type + '' === 'date' " v-model="item.val" :placeholder="$t('pleaseSelect')+$t('date')" style="width: 100%"></j-date>
|
||||
<j-date v-else-if=" item.type + '' ==='datetime' " v-model="item.val" :placeholder="$t('pleaseSelect')+$t('time')" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"></j-date>
|
||||
<a-time-picker v-else-if="item.type + '' === 'time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
|
||||
<a-input-number v-else-if=" item.type + '' === 'int'||item.type + '' === 'number' " style="width: 100%" :placeholder="$t('pleaseSelect')+$t('superQuery.numericalValue')" v-model="item.val"/>
|
||||
<a-select v-else-if="item.type + '' === 'switch'" :placeholder="$t('pleaseSelect')" v-model="item.val">
|
||||
<a-select-option value="Y">{{ $t('yes') }}</a-select-option>
|
||||
<a-select-option value="N">{{ $t('not') }}</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-else v-model="item.val" :placeholder="$t('superQuery.enterValue')"/>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="4" :xs="0" style="margin-bottom: 12px;">
|
||||
<a-button @click="handleAdd" icon="plus"></a-button>
|
||||
<a-button @click="handleDel( index )" icon="minus"></a-button>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="0" :xs="24" style="margin-bottom: 12px;text-align: right;">
|
||||
<a-button @click="handleAdd" icon="plus"></a-button>
|
||||
<a-button @click="handleDel( index )" icon="minus"></a-button>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="5">
|
||||
<!-- 查询记录 -->
|
||||
|
||||
<a-card class="j-super-query-history-card" :bordered="true">
|
||||
<div slot="title">
|
||||
{{ $t('superQuery.savedQuery') }}
|
||||
</div>
|
||||
|
||||
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" :description="$t('superQuery.noQueriesSaved')"/>
|
||||
<a-tree
|
||||
v-else
|
||||
class="j-super-query-history-tree"
|
||||
:showIcon="true"
|
||||
:treeData="saveTreeData"
|
||||
:selectedKeys="[]"
|
||||
@select="handleTreeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
</a-card>
|
||||
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</a-spin>
|
||||
|
||||
<a-modal :title="$t('pleaseEnter')+$t('superQuery.savedName')" :visible="prompt.visible" @cancel="prompt.visible=false" @ok="handlePromptOk">
|
||||
<a-input v-model="prompt.value"></a-input>
|
||||
</a-modal>
|
||||
|
||||
</j-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import * as utils from '@/utils/util'
|
||||
import { mixinDevice } from '@/utils/mixin'
|
||||
import JDate from '@/components/jero/JDate.vue'
|
||||
import JSelectDepart from '@/components/jerobiz/JSelectDepart'
|
||||
import JSelectMultiUser from '@/components/jerobiz/JSelectMultiUser'
|
||||
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
|
||||
import JAreaLinkage from '@comp/jero/JAreaLinkage'
|
||||
|
||||
export default {
|
||||
name: 'JSuperQuery',
|
||||
mixins: [mixinDevice],
|
||||
components: { JAreaLinkage, JMultiSelectTag, JDate, JSelectDepart, JSelectMultiUser },
|
||||
props: {
|
||||
/*
|
||||
fieldList: [{
|
||||
value:'',
|
||||
text:'',
|
||||
type:'',
|
||||
dictCode:'' // 只要 dictCode 有值,无论 type 是什么,都显示为字典下拉框
|
||||
}]
|
||||
type:date datetime int number string
|
||||
* */
|
||||
fieldList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
/*
|
||||
* 这个回调函数接收一个数组参数 即查询条件
|
||||
* */
|
||||
callback: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'handleSuperQuery'
|
||||
},
|
||||
|
||||
// 当前是否在加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// 保存查询条件的唯一 code,通过该 code 区分
|
||||
// 默认为 null,代表以当前路由全路径为区分Code
|
||||
saveCode: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
moment,
|
||||
fieldTreeData: [],
|
||||
|
||||
prompt: {
|
||||
visible: false,
|
||||
value: ''
|
||||
},
|
||||
|
||||
visible: false,
|
||||
queryParamsModel: [],
|
||||
treeIcon: <a-icon type="file-text"/>,
|
||||
// 保存查询条件的treeData
|
||||
saveTreeData: [],
|
||||
// 保存查询条件的前缀名
|
||||
saveCodeBefore: 'JSuperQuerySaved_',
|
||||
// 查询类型,过滤条件匹配(and、or)
|
||||
matchType: 'and',
|
||||
superQueryFlag: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
izMobile () {
|
||||
return this.device === 'mobile'
|
||||
},
|
||||
tooltipProps () {
|
||||
return this.izMobile ? { visible: false } : {}
|
||||
},
|
||||
fullSaveCode () {
|
||||
let saveCode = this.saveCode
|
||||
if (saveCode == null || saveCode === '') {
|
||||
saveCode = this.$route.fullPath
|
||||
}
|
||||
return this.saveCodeBefore + saveCode
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当 saveCode 变化时,重新查询已保存的条件
|
||||
fullSaveCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
const list = this.$ls.get(this.fullSaveCode)
|
||||
if (list instanceof Array) {
|
||||
this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
|
||||
}
|
||||
}
|
||||
},
|
||||
fieldList: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
const mainData = []; const subData = []
|
||||
val.forEach(item => {
|
||||
const data = { ...item }
|
||||
data.label = data.label || data.text
|
||||
const hasChildren = (data.children instanceof Array)
|
||||
data.disabled = hasChildren
|
||||
data.selectable = !hasChildren
|
||||
if (hasChildren) {
|
||||
data.children = data.children.map(item2 => {
|
||||
const child = { ...item2 }
|
||||
child.label = child.label || child.text
|
||||
child.label = data.label + '-' + child.label
|
||||
child.value = data.value + ',' + child.value
|
||||
child.val = ''
|
||||
return child
|
||||
})
|
||||
data.val = ''
|
||||
subData.push(data)
|
||||
} else {
|
||||
mainData.push(data)
|
||||
}
|
||||
})
|
||||
this.fieldTreeData = mainData.concat(subData)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
show () {
|
||||
if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
|
||||
this.resetLine()
|
||||
}
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
getDictInfo (item) {
|
||||
let str = ''
|
||||
if (!item.dictTable) {
|
||||
str = item.dictCode
|
||||
} else {
|
||||
str = item.dictTable + ',' + item.dictText + ',' + item.dictCode
|
||||
}
|
||||
console.log('高级查询字典信息', str)
|
||||
return str
|
||||
},
|
||||
handleOk () {
|
||||
if (!this.isNullArray(this.queryParamsModel)) {
|
||||
const event = {
|
||||
matchType: this.matchType,
|
||||
params: this.removeEmptyObject(this.queryParamsModel)
|
||||
}
|
||||
// 移动端模式下关闭弹窗
|
||||
if (this.izMobile) {
|
||||
this.visible = false
|
||||
}
|
||||
this.emitCallback(event)
|
||||
} else {
|
||||
this.$message.warn(this.$t('superQuery.cannotQueryEmpty'))
|
||||
}
|
||||
},
|
||||
emitCallback (event = {}) {
|
||||
const { params = [], matchType = this.matchType } = event
|
||||
this.superQueryFlag = (params && params.length > 0)
|
||||
for (const param of params) {
|
||||
if (Array.isArray(param.val)) {
|
||||
param.val = param.val.join(',')
|
||||
}
|
||||
}
|
||||
console.debug('---高级查询参数--->', { params, matchType })
|
||||
this.$emit(this.callback, params, matchType)
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
close () {
|
||||
this.$emit('close')
|
||||
this.visible = false
|
||||
},
|
||||
handleAdd () {
|
||||
this.addNewLine()
|
||||
},
|
||||
addNewLine () {
|
||||
this.queryParamsModel.push({ rule: 'eq' })
|
||||
},
|
||||
resetLine () {
|
||||
this.superQueryFlag = false
|
||||
this.queryParamsModel = []
|
||||
this.addNewLine()
|
||||
},
|
||||
handleDel (index) {
|
||||
this.queryParamsModel.splice(index, 1)
|
||||
},
|
||||
handleSelected (node, item) {
|
||||
const { type, dbType, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
|
||||
item.type = type
|
||||
item.dbType = dbType
|
||||
item.options = options
|
||||
item.dictCode = dictCode
|
||||
item.dictTable = dictTable
|
||||
item.dictText = dictText
|
||||
item.customReturnField = customReturnField
|
||||
if (popup) {
|
||||
item.popup = popup
|
||||
}
|
||||
this.$set(item, 'val', undefined)
|
||||
},
|
||||
handleOpen () {
|
||||
this.show()
|
||||
},
|
||||
handleReset () {
|
||||
this.resetLine()
|
||||
this.emitCallback()
|
||||
},
|
||||
handleSave () {
|
||||
const queryParams = this.removeEmptyObject(this.queryParamsModel)
|
||||
if (this.isNullArray(queryParams)) {
|
||||
this.$message.warning(this.$t('superQuery.emptyCannotSaved'))
|
||||
} else {
|
||||
this.prompt.value = ''
|
||||
this.prompt.visible = true
|
||||
}
|
||||
},
|
||||
handlePromptOk () {
|
||||
const { value } = this.prompt
|
||||
if (!value) {
|
||||
this.$message.warning(this.$t('preservation') + this.$t('name') + this.$t('cannotEmpty'))
|
||||
return
|
||||
}
|
||||
// 取出查询条件
|
||||
const records = this.removeEmptyObject(this.queryParamsModel)
|
||||
// 判断有没有重名的
|
||||
const filterList = this.saveTreeData.filter(i => i.originTitle === value)
|
||||
if (filterList.length > 0) {
|
||||
this.$confirm({
|
||||
content: `${value} ` + this.$t('superQuery.alreadyExists'),
|
||||
onOk: () => {
|
||||
this.prompt.visible = false
|
||||
filterList[0].records = records
|
||||
this.saveToLocalStore()
|
||||
this.$message.success(this.$t('savedSuccessfully'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 没有重名的,直接添加
|
||||
this.prompt.visible = false
|
||||
// 添加到树列表中
|
||||
this.saveTreeData.push(this.renderSaveTreeData({
|
||||
title: value,
|
||||
matchType: this.matchType,
|
||||
records: records
|
||||
}))
|
||||
// 保存到 LocalStore
|
||||
this.saveToLocalStore()
|
||||
this.$message.success(this.$t('SavedSuccessfully'))
|
||||
}
|
||||
},
|
||||
handleTreeSelect (idx, event) {
|
||||
if (event.selectedNodes[0]) {
|
||||
const { matchType, records } = event.selectedNodes[0].data.props
|
||||
// 将保存的matchType取出,兼容旧数据,如果没有保存就还是使用原来的
|
||||
this.matchType = matchType || this.matchType
|
||||
this.queryParamsModel = utils.cloneObject(records)
|
||||
}
|
||||
},
|
||||
handleRemoveSaveTreeItem (event, vNode) {
|
||||
// 阻止事件冒泡
|
||||
event.stopPropagation()
|
||||
|
||||
this.$confirm({
|
||||
content: this.$t('superQuery.deleteQuery'),
|
||||
onOk: () => {
|
||||
const { eventKey } = vNode
|
||||
this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
|
||||
this.saveToLocalStore()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 将查询保存到 LocalStore 里
|
||||
saveToLocalStore () {
|
||||
const saveValue = this.saveTreeData.map(({ originTitle, matchType, records }) => ({ title: originTitle, matchType, records }))
|
||||
this.$ls.set(this.fullSaveCode, saveValue)
|
||||
},
|
||||
|
||||
isNullArray (array) {
|
||||
// 判断是不是空数组对象
|
||||
if (!array || array.length === 0) {
|
||||
return true
|
||||
}
|
||||
if (array.length === 1) {
|
||||
const obj = array[0]
|
||||
if (!obj.field || (obj.val == null || obj.val === '') || !obj.rule) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 去掉数组中的空对象
|
||||
removeEmptyObject (arr) {
|
||||
const array = utils.cloneObject(arr)
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const item = array[i]
|
||||
if (item == null || Object.keys(item).length <= 0) {
|
||||
array.splice(i--, 1)
|
||||
} else {
|
||||
if (Array.isArray(item.options)) {
|
||||
// 如果有字典属性,就不需要保存 options 了
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
delete item.options
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
},
|
||||
|
||||
/** 渲染保存查询条件的 title(加个删除按钮) */
|
||||
renderSaveTreeData (item) {
|
||||
item.icon = this.treeIcon
|
||||
item.originTitle = item.title
|
||||
item.title = (arg1, arg2) => {
|
||||
let vNode
|
||||
// 兼容旧版的Antdv
|
||||
if (arg1.dataRef) {
|
||||
vNode = arg1
|
||||
} else if (arg2.dataRef) {
|
||||
vNode = arg2
|
||||
} else {
|
||||
return <span style="color:red;">Antdv版本不支持</span>
|
||||
}
|
||||
const { originTitle } = vNode.dataRef
|
||||
return (
|
||||
<div class="j-history-tree-title">
|
||||
<span>{originTitle}</span>
|
||||
|
||||
<div class="j-history-tree-title-closer" onClick={e => this.handleRemoveSaveTreeItem(e, vNode)}>
|
||||
<a-icon type="close-circle"/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return item
|
||||
},
|
||||
|
||||
/** 判断是否允许多选 */
|
||||
allowMultiple (item) {
|
||||
return item.rule === 'in'
|
||||
},
|
||||
|
||||
handleRuleChange (item, newValue) {
|
||||
const oldValue = item.rule
|
||||
this.$set(item, 'rule', newValue)
|
||||
// 上一个规则是否是 in,且type是字典或下拉
|
||||
if (oldValue === 'in') {
|
||||
if (item.dictCode || item.options instanceof Array) {
|
||||
let value = item.val
|
||||
if (typeof item.val === 'string') {
|
||||
value = item.val.split(',')[0]
|
||||
} else if (Array.isArray(item.val)) {
|
||||
value = item.val[0]
|
||||
}
|
||||
this.$set(item, 'val', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeJPopup (item, e, values) {
|
||||
item.val = values[item.popup.destFields]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.j-super-query-box {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.j-super-query-modal {
|
||||
|
||||
.j-super-query-history-card {
|
||||
/deep/ .ant-card-body,
|
||||
/deep/ .ant-card-head-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/deep/ .ant-card-head {
|
||||
padding: 4px 8px;
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.j-super-query-history-empty {
|
||||
/deep/ .ant-empty-image {
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/deep/ img {
|
||||
width: 80px;
|
||||
height: 65px;
|
||||
}
|
||||
|
||||
/deep/ .ant-empty-description {
|
||||
color: #afafaf;
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.j-super-query-history-tree {
|
||||
|
||||
.j-history-tree-title {
|
||||
width: calc(100% - 24px);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&-closer {
|
||||
color: #999999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.j-history-tree-title-closer {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/deep/ .ant-tree-switcher {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/deep/ .ant-tree-node-content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-if="query"
|
||||
style="width: 100%"
|
||||
@change="handleSelectChange"
|
||||
>
|
||||
<a-select-option v-for="(item, index) in queryOption" :key="index" :value="item.value">
|
||||
{{ item.text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-switch
|
||||
v-else
|
||||
v-model="checkStatus"
|
||||
:disabled="disabled"
|
||||
@change="handleChange"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'JSwitch',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => ['Y', 'N']
|
||||
},
|
||||
query: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checkStatus: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
queryOption () {
|
||||
const arr = []
|
||||
arr.push({ value: this.options[0], text: '是' })
|
||||
arr.push({ value: this.options[1], text: '否' })
|
||||
return arr
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (!this.query) {
|
||||
if (!val) {
|
||||
this.checkStatus = false
|
||||
this.$emit('change', this.options[1])
|
||||
} else {
|
||||
this.checkStatus = this.options[0] + '' === val + ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (checked) {
|
||||
const flag = checked === false ? this.options[1] : this.options[0]
|
||||
this.$emit('change', flag)
|
||||
},
|
||||
handleSelectChange (value) {
|
||||
this.$emit('change', value)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,431 @@
|
||||
<template>
|
||||
<a-table
|
||||
class="j-table"
|
||||
ref="table"
|
||||
:bordered="bordered"
|
||||
:table-key="tableKey"
|
||||
:columns="resultColumns"
|
||||
:components="components"
|
||||
:scroll="scroll || selfScroll"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
|
||||
<!-- 如果插槽是作用域插槽,将作用域插槽的props包装成对象传递给slot -->
|
||||
<template v-for="(_, slotName) in $scopedSlots" :slot="slotName" slot-scope="text, record, index">
|
||||
<slot :name="slotName" v-bind="{text, record, index}" />
|
||||
</template>
|
||||
<!-- 如果是普通插槽,直接使用$slots渲染 -->
|
||||
<slot v-for="(_, slotName) in $slots" :name="slotName" :slot="slotName"/>
|
||||
|
||||
<div slot="settingDropdown">
|
||||
<a-card>
|
||||
<a-table
|
||||
rowKey="key"
|
||||
:style="settingStyle"
|
||||
tableLayout="fixed"
|
||||
size="small"
|
||||
bordered
|
||||
:pagination="false"
|
||||
:scroll="settingScroll"
|
||||
:data-source="settingDataSource"
|
||||
:columns="settingColumns">
|
||||
|
||||
<a-checkbox slot="hide" slot-scope="text, record"
|
||||
:checked="text"
|
||||
:disabled="record.disabledHide"
|
||||
@change="changeSetting(text, 'hide', record)"></a-checkbox>
|
||||
<a-checkbox slot="freeze" slot-scope="text, record"
|
||||
:checked="text"
|
||||
:disabled="record.disabledFreeze"
|
||||
@change="changeSetting(text, 'freeze', record)"></a-checkbox>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
<a-icon ref="settingBtn" slot="settingIcon" type="setting" :style="{ fontSize:'16px', color: '#108ee9' }" />
|
||||
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import VueDraggableResizable from 'vue-draggable-resizable'
|
||||
|
||||
Vue.component('vue-draggable-resizable', VueDraggableResizable)
|
||||
|
||||
const actionKey = 'action'
|
||||
// 保存localStorage中的后缀
|
||||
const saveSuffix = ':JTable'
|
||||
// 拿到column的key
|
||||
const getKey = col => col.key || col.dataIndex
|
||||
// 所有JTable的缓存key
|
||||
const J_TABLE_KEYS = 'J_TABLE_KEYS'
|
||||
export default {
|
||||
name: 'JTable',
|
||||
props: {
|
||||
// 用于持久化存储列配置,保证唯一
|
||||
tableKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
// 配置表的样式
|
||||
settingStyle: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ width: '300px' })
|
||||
},
|
||||
// 配置表的scroll
|
||||
settingScroll: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ y: 300 })
|
||||
},
|
||||
// 如果需要拖拽后增加总列宽而不是占用其他列宽度:需要父组件 :scroll.sync="scroll"
|
||||
scroll: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
// 列最小宽度
|
||||
columnMinWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// settingVisible: false,
|
||||
// 设置表格
|
||||
settingColumns: [
|
||||
{ title: '列名', dataIndex: 'title', align: 'center', ellipsis: true, width: 150 },
|
||||
{ title: '隐藏', dataIndex: 'hide', align: 'center', ellipsis: true, width: 60, scopedSlots: { customRender: 'hide' } },
|
||||
{ title: '冻结', dataIndex: 'freeze', align: 'center', ellipsis: true, width: 60, scopedSlots: { customRender: 'freeze' } }
|
||||
],
|
||||
settingDataSource: [],
|
||||
settingColumnsObj: { hide: [], freeze: [] }, // 本地存储的配置对象
|
||||
resultColumns: [],
|
||||
components: {
|
||||
header: {
|
||||
cell: this.initDrag(this.columns)
|
||||
}
|
||||
},
|
||||
selfScroll: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 配置列改变,将配置转换成配置表格数据
|
||||
settingColumnsObj: {
|
||||
deep: true, // 深度监听
|
||||
immediate: true, // 挂载先执行一次
|
||||
handler () {
|
||||
const dataSource = []
|
||||
this.columns.forEach(col => {
|
||||
const tempKey = getKey(col)
|
||||
// 操作栏不可配置
|
||||
if (tempKey === actionKey) {
|
||||
return
|
||||
}
|
||||
// hideSettingColumn: true 可以让不显示字段控制
|
||||
if (col.hideSettingColumn) {
|
||||
return
|
||||
}
|
||||
dataSource.push({
|
||||
title: col.title || col.columnTitle, // 用于显示的字段
|
||||
key: tempKey, // 用于存储的字段
|
||||
hide: this.settingColumnsObj.hide.includes(tempKey), // 是否隐藏
|
||||
freeze: this.settingColumnsObj.freeze.includes(tempKey), // 是否冻结
|
||||
disabledHide: col.disabledHide, // 禁用隐藏
|
||||
disabledFreeze: col.disabledFreeze // 禁用冻结
|
||||
})
|
||||
})
|
||||
this.settingDataSource = dataSource
|
||||
// 自定义列配置修改后,更新表格字段
|
||||
this.setResultColumns(this.columns)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 将父组件的columns进行处理:增加设置列按钮
|
||||
setResultColumns (columns) {
|
||||
// 深度克隆,防止直接修改父组件数据
|
||||
columns = cloneDeep(columns)
|
||||
// 设置按钮插槽配置
|
||||
const settingOption = {
|
||||
filterDropdown: 'settingDropdown',
|
||||
filterIcon: 'settingIcon'
|
||||
}
|
||||
let findActionIndex = columns.findIndex(col => col && (getKey(col) === actionKey))
|
||||
// 将设置按钮放到表格右上角,如果有操作栏就固定到右侧(操作栏固定右侧,没有的话就最后一列)
|
||||
if (findActionIndex === -1) {
|
||||
findActionIndex = columns.length - 1
|
||||
}
|
||||
if (columns[findActionIndex].scopedSlots instanceof Object) {
|
||||
Object.assign(columns[findActionIndex].scopedSlots, settingOption)
|
||||
} else {
|
||||
columns[findActionIndex].scopedSlots = settingOption
|
||||
}
|
||||
// 自定义设置弹框(尝试解决表格刷新丢失弹框的问题)此方法会出现两个弹框
|
||||
// columns[findActionIndex].filterDropdownVisible = this.settingVisible
|
||||
// columns[findActionIndex].onFilterDropdownVisibleChange = (visible) => {
|
||||
// this.settingVisible = visible
|
||||
// }
|
||||
|
||||
// 最后过滤掉需要隐藏的字段,按定位进行排序
|
||||
const startArr = [] // 冻结左侧的列
|
||||
const midArr = [] // 没有冻结的列
|
||||
const endArr = [] // 冻结右侧的列(只能是action,暂时不能自定义固定右侧)
|
||||
columns.forEach(col => {
|
||||
const key = getKey(col)
|
||||
const fixed = this.settingColumnsObj.freeze.includes(key)
|
||||
col.hide = this.settingColumnsObj.hide.includes(key)
|
||||
col.fixed = key === actionKey ? 'right' : fixed
|
||||
// 如果是字符串就转换成数字(因为实现拖拽,单位只能是px)
|
||||
col.width = col.width && parseInt(col.width + '')
|
||||
// 如果没有最小宽度就获取统一设置的最小宽度
|
||||
col.minWidth = (col.minWidth && parseInt(col.minWidth + '')) || this.columnMinWidth
|
||||
if (!col.hide) {
|
||||
// 如果没有宽度就取minWidth(不添加原数据的width属性,这里只对需要固定的minWidth生效)
|
||||
const width = col.width || col.minWidth
|
||||
switch (col.fixed) {
|
||||
case true:
|
||||
case 'left':
|
||||
startArr.push({ ...col, width })
|
||||
break
|
||||
case 'right':
|
||||
endArr.push({ ...col, width })
|
||||
break
|
||||
default:
|
||||
midArr.push(col)
|
||||
}
|
||||
}
|
||||
})
|
||||
// 更新父组件的数据,将排序后的进行渲染
|
||||
this.$emit('update:columns', columns)
|
||||
// 按冻结顺序排序
|
||||
this.resultColumns = [
|
||||
...startArr.sort((a, b) => {
|
||||
const freezeArr = this.settingColumnsObj.freeze
|
||||
return freezeArr.indexOf(getKey(a)) - freezeArr.indexOf(getKey(b))
|
||||
}),
|
||||
...midArr,
|
||||
...endArr
|
||||
]
|
||||
return this.resultColumns
|
||||
},
|
||||
// 初始化表格拖拽
|
||||
initDrag (columns) {
|
||||
// 没有边框就不能拖拽
|
||||
if (!this.bordered) {
|
||||
return
|
||||
}
|
||||
// 如果没有任何列开启拖拽就不需要拖拽
|
||||
if (!columns.some(col => col.resizable)) {
|
||||
return
|
||||
}
|
||||
// 第一步:列宽映射
|
||||
const draggingMap = {}
|
||||
columns.forEach((col) => {
|
||||
draggingMap[getKey(col)] = col.width
|
||||
})
|
||||
const draggingState = Vue.observable(draggingMap)
|
||||
// 第二步:表头渲染
|
||||
return (h, props, children) => {
|
||||
// 表头DOM
|
||||
let thDom = null
|
||||
// 获取列的key值和特性
|
||||
const { key, ...restProps } = props
|
||||
// 获取最新列配置
|
||||
const columns = cloneDeep(this.columns)
|
||||
let col
|
||||
if (key === 'selection-column') {
|
||||
col = {}
|
||||
} else {
|
||||
col = columns.find(col => getKey(col) === key)
|
||||
}
|
||||
// 没有开启拖拽 或 没有宽度 或 有定位,都不能拖拽(防止布局异常,至少有一列不设width并且不能有fixed)
|
||||
if (!col.resizable || !col.width || !!col.fixed) {
|
||||
return <th {...restProps}>{children}</th>
|
||||
}
|
||||
// 开始拖拽监听
|
||||
const onDragging = (x) => {
|
||||
const beforeWidth = col.width
|
||||
const scroll = cloneDeep(this.scroll) || {}
|
||||
draggingState[key] = 0
|
||||
col.width = Math.max(x, col.minWidth || this.columnMinWidth)
|
||||
// 如果有scroll.x就计算差值,否则获取所有列的和
|
||||
if (scroll.x) {
|
||||
scroll.x = scroll.x + (col.width - beforeWidth)
|
||||
} else {
|
||||
scroll.x = 0
|
||||
this.columns.forEach(col => (scroll.x += (col.width || col.minWidth || this.columnMinWidth)))
|
||||
}
|
||||
// 将计算好的宽度更新
|
||||
if (this.scroll) {
|
||||
this.$emit('update:scroll', scroll)
|
||||
} else {
|
||||
this.selfScroll = scroll
|
||||
}
|
||||
// 修改列宽后,更新表格字段
|
||||
this.setResultColumns(columns)
|
||||
}
|
||||
// 停止拖拽监听
|
||||
const onDragstop = () => {
|
||||
draggingState[key] = thDom.getBoundingClientRect().width
|
||||
}
|
||||
// 控制最小拖拽宽度
|
||||
const onDrag = (x) => {
|
||||
return x >= (col.minWidth || this.columnMinWidth)
|
||||
}
|
||||
return (
|
||||
<th
|
||||
{...restProps}
|
||||
v-ant-ref={(r) => (thDom = r)}
|
||||
width={col.width}
|
||||
class="resize-table-th"
|
||||
>
|
||||
{children}
|
||||
<vue-draggable-resizable
|
||||
key={getKey(col)}
|
||||
class="table-draggable-handle"
|
||||
minw={10}
|
||||
w={10}
|
||||
x={col.width || draggingState[key]}
|
||||
z={1}
|
||||
axis="x"
|
||||
draggable={true}
|
||||
resizable={false}
|
||||
props={{ onDrag }}
|
||||
onDragging={onDragging}
|
||||
onDragstop={onDragstop}
|
||||
></vue-draggable-resizable>
|
||||
</th>
|
||||
)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 配置表中复选框改变状态的事件
|
||||
* @param text 修改前的状态
|
||||
* @param type 隐藏还是冻结
|
||||
* @param record 当前行数据
|
||||
*/
|
||||
changeSetting (text, type, record) {
|
||||
const checked = !text // text是点击前的状态,取反就是要修改的状态
|
||||
// console.log('修改了配置', checked, type, record)
|
||||
// 修改当前行配置
|
||||
record[type] = !checked
|
||||
// 勾选就添加,取消就删除
|
||||
if (checked) {
|
||||
this.settingColumnsObj[type].push(record.key)
|
||||
} else {
|
||||
this.settingColumnsObj[type].splice(this.settingColumnsObj[type].findIndex(item => item === record.key), 1)
|
||||
}
|
||||
this.saveSetting()
|
||||
/**
|
||||
* 弹框消失问题 冻结列出现和消失会导致dom结构改变而从更新结构丢失弹框
|
||||
* 解决方案,刷新前获取dom,刷新后再次获取,如果获取不一致就触发点击事件点开弹框
|
||||
*/
|
||||
const beforeIsFixed = !!this.$refs.table.$el.querySelector('.ant-table-fixed-left')
|
||||
this.$nextTick(() => {
|
||||
const afterIsFixed = !!this.$refs.table.$el.querySelector('.ant-table-fixed-left')
|
||||
// console.log(beforeIsFixed !== afterIsFixed)
|
||||
if (beforeIsFixed !== afterIsFixed) {
|
||||
this.$refs.settingBtn.$el.click()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 初始化配置,从columns里获取
|
||||
initSetting (columns) {
|
||||
const hide = []
|
||||
const freeze = []
|
||||
columns.forEach(col => {
|
||||
const key = getKey(col)
|
||||
// hide属性控制隐藏
|
||||
if (col.hide) {
|
||||
hide.push(key)
|
||||
}
|
||||
// fixed冻结到左侧
|
||||
if (col.fixed === true || col.fixed === 'left') {
|
||||
freeze.push(key)
|
||||
}
|
||||
})
|
||||
this.settingColumnsObj = { hide, freeze }
|
||||
this.saveSetting()
|
||||
},
|
||||
// 配置保存到本地
|
||||
saveSetting () {
|
||||
// 获取所有的jtable缓存的key数组(用于清除缓存,没有就初始化数组)
|
||||
const jTableKeys = Vue.ls.get(J_TABLE_KEYS) || []
|
||||
const saveKey = this.tableKey + saveSuffix
|
||||
// 如果不在数组里面就追加进去
|
||||
if (!jTableKeys.includes(saveKey)) {
|
||||
jTableKeys.push(saveKey)
|
||||
}
|
||||
Vue.ls.set(J_TABLE_KEYS, jTableKeys, 7 * 24 * 60 * 60 * 10)
|
||||
Vue.ls.set(saveKey, this.settingColumnsObj, 7 * 24 * 60 * 60 * 10)
|
||||
},
|
||||
// 还原默认配置
|
||||
resteColumns () {
|
||||
this.initSetting(this.columnsBak)
|
||||
},
|
||||
// 清除本地的配置(当前的JTable)
|
||||
clearSetting () {
|
||||
this.settingColumnsObj = { hide: [], freeze: [] }
|
||||
Vue.ls.remove(this.tableKey + saveSuffix)
|
||||
this.resteColumns()
|
||||
this.$message.success('成功清除当前JTable的缓存!')
|
||||
},
|
||||
// 清除所有缓存
|
||||
clearAllCacheSetting () {
|
||||
// 获取到之后遍历删除,最后把keys数组删除
|
||||
const jTableKeys = Vue.ls.get(J_TABLE_KEYS)
|
||||
jTableKeys && jTableKeys.forEach(key => {
|
||||
Vue.ls.remove(key)
|
||||
})
|
||||
Vue.ls.remove(J_TABLE_KEYS)
|
||||
this.resteColumns() // 这里只能刷新当前的表
|
||||
this.$message.success('成功清除全局JTable的缓存!')
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 备份默认配置
|
||||
this.columnsBak = cloneDeep(this.columns)
|
||||
const settingColumnsObj = Vue.ls.get(this.tableKey + saveSuffix)
|
||||
// 第一次进页面或清空缓存进行初始化
|
||||
if (settingColumnsObj) {
|
||||
this.settingColumnsObj = settingColumnsObj
|
||||
} else {
|
||||
this.initSetting(this.columns)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-table {
|
||||
.resize-table-th {
|
||||
position: relative;
|
||||
|
||||
.table-draggable-handle {
|
||||
transform: none !important;
|
||||
position: absolute !important;
|
||||
height: 100% !important;
|
||||
bottom: 0;
|
||||
left: auto !important;
|
||||
right: -5px;
|
||||
//width: 10px !important;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<a-time-picker
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
@change="handleTimeChange"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'JTime',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const timeStr = this.value
|
||||
return {
|
||||
decorator: '',
|
||||
momVal: !timeStr ? null : moment(timeStr, this.dateFormat)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.momVal = null
|
||||
} else {
|
||||
this.momVal = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleTimeChange (mom, timeStr) {
|
||||
this.$emit('change', timeStr)
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
@change="onChange"
|
||||
@search="onSearch"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeDict',
|
||||
data () {
|
||||
return {
|
||||
treeData: [],
|
||||
treeValue: null,
|
||||
url_root: '/sys/category/loadTreeRoot',
|
||||
url_children: '/sys/category/loadTreeChildren',
|
||||
url_view: '/sys/category/loadOne'
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
parentCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
required: false
|
||||
},
|
||||
root: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {
|
||||
pid: '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
root: {
|
||||
handler (val) {
|
||||
console.log('root-change', val)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
parentCode: {
|
||||
handler () {
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
value: {
|
||||
handler () {
|
||||
this.loadViewInfo()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.loadRoot()
|
||||
this.loadViewInfo()
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
methods: {
|
||||
loadViewInfo () {
|
||||
if (!this.value || this.value + '' === '0') {
|
||||
this.treeValue = null
|
||||
} else {
|
||||
const param = {
|
||||
field: this.field,
|
||||
val: this.value
|
||||
}
|
||||
getAction(this.url_view, param).then(res => {
|
||||
if (res.success) {
|
||||
this.treeValue = {
|
||||
value: this.value,
|
||||
label: res.result.name
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadRoot () {
|
||||
const param = {
|
||||
async: this.async,
|
||||
pcode: this.parentCode
|
||||
}
|
||||
getAction(this.url_root, param).then(res => {
|
||||
if (res.success) {
|
||||
this.handleTreeNodeValue(res.result)
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.async) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid
|
||||
}
|
||||
getAction(this.url_children, param).then(res => {
|
||||
if (res.success) {
|
||||
this.handleTreeNodeValue(res.result)
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') {
|
||||
if (!children || children.length === 0) {
|
||||
item.leaf = true
|
||||
} else {
|
||||
item.children = children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handleTreeNodeValue (result) {
|
||||
const storeField = this.field + '' === 'code' ? 'code' : 'key'
|
||||
for (const i of result) {
|
||||
i.value = i[storeField]
|
||||
i.isLeaf = !(!i.leaf)
|
||||
if (i.children && i.children.length > 0) {
|
||||
this.handleTreeNodeValue(i.children)
|
||||
}
|
||||
}
|
||||
},
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
} else {
|
||||
this.$emit('change', value.value)
|
||||
}
|
||||
this.treeValue = value
|
||||
},
|
||||
onSearch (value) {
|
||||
console.log(value)
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-tree-select
|
||||
v-if="isAsync"
|
||||
allowClear
|
||||
tree-node-filter-prop="title"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
:show-search="!multiple"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
<a-tree-select
|
||||
v-if="!isAsync"
|
||||
allowClear
|
||||
tree-node-filter-prop="title"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
:show-search="!multiple"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
/*
|
||||
* 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
|
||||
* <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
|
||||
* */
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeSelect',
|
||||
props: {
|
||||
isAsync: { // 是否采用异步方式初始化树
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
dict: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pidField: {
|
||||
type: String,
|
||||
default: 'pid',
|
||||
required: false
|
||||
},
|
||||
pidValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
hasChildField: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadTriggleChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: null, // 选中的数据
|
||||
treeData: [], // 渲染树的数组
|
||||
url: '/sys/dict/loadTreeData',
|
||||
view: '/sys/dict/loadDictItem/',
|
||||
tableName: '',
|
||||
text: '',
|
||||
code: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
dict () {
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.validateProp().then(() => {
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
window.that = this
|
||||
},
|
||||
methods: {
|
||||
loadItemByCode () {
|
||||
if (!this.value || this.value + '' === '0') {
|
||||
this.treeValue = null
|
||||
} else {
|
||||
getAction(`${this.view}${this.dict}`, { key: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
this.treeValue = this.value.split(',') // v-model接收的是string || string[]
|
||||
// 将节点名称显示在选择框上
|
||||
this.treeValue = res.result[0]
|
||||
this.onLoadTriggleChange(res.result[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoadTriggleChange (text) {
|
||||
// 只有单选才会触发
|
||||
if (!this.multiple && this.loadTriggleChange) {
|
||||
this.$emit('change', this.value, text)
|
||||
}
|
||||
},
|
||||
initDictInfo () { // 取出父组件传过来的code,text,tableName
|
||||
const arr = this.dict.split(',')
|
||||
this.tableName = arr[0]
|
||||
this.text = arr[1]
|
||||
this.code = arr[2]
|
||||
},
|
||||
// 异步加载树节点
|
||||
asyncLoadTreeData (treeNode) {
|
||||
debugger
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid,
|
||||
tableName: this.tableName,
|
||||
text: this.text,
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
hasChildField: this.hasChildField,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') { // 找到当前元素所在的父节点
|
||||
if (!children || children.length === 0) {
|
||||
item.isLeaf = true
|
||||
} else {
|
||||
item.children = children // 搜索出来的children添加到原树子children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 初始化树
|
||||
* isAsync:true===异步获取 false===同步获取数据 默认异步 */
|
||||
loadRoot () {
|
||||
if (this.isAsync) {
|
||||
const param = {
|
||||
pid: this.pidValue,
|
||||
tableName: this.tableName,
|
||||
text: this.text,
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
hasChildField: this.hasChildField,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success && res.result) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
console.log('数根节点查询结果-else', res)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 同步
|
||||
const param = {
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
tableName: this.tableName,
|
||||
text: this.text
|
||||
}
|
||||
getAction('/sys/dict/queryAllTreeData', param).then((res) => {
|
||||
if (res.success) {
|
||||
this.treeData = deepFor(res.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
this.treeValue = null
|
||||
} else if (value instanceof Array) {
|
||||
// 多选
|
||||
this.$emit('change', value.join(',').toString()) // 修改第一次选中时,选中为空的情况
|
||||
this.treeValue = value
|
||||
} else {
|
||||
// 单选
|
||||
this.$emit('change', value)
|
||||
this.treeValue = value
|
||||
}
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
},
|
||||
validateProp () {
|
||||
const myCondition = this.condition
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!myCondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
const test = JSON.parse(myCondition)
|
||||
if (typeof test === 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
// 递归整个树,判断是否是叶子节点----同步获取数据时用到
|
||||
function deepFor (source) {
|
||||
for (const i of source) {
|
||||
i.value = i.key
|
||||
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
if (i.children && i.children.length > 0) {
|
||||
deepFor(i.children)
|
||||
}
|
||||
}
|
||||
return source
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<a-table
|
||||
:rowKey="rowKey"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:expandedRowKeys="expandedRowKeys"
|
||||
v-bind="tableAttrs"
|
||||
v-on="$listeners"
|
||||
@expand="handleExpand"
|
||||
@expandedRowsChange="expandedRowKeys=$event">
|
||||
|
||||
<template v-for="(slotItem) of slots" :slot="slotItem" slot-scope="text, record, index">
|
||||
<slot :name="slotItem" v-bind="{text,record,index}"></slot>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeTable',
|
||||
props: {
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
},
|
||||
// 根据什么查询,如果传递 id 就根据 id 查询
|
||||
queryKey: {
|
||||
type: String,
|
||||
default: 'parentId'
|
||||
},
|
||||
queryParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 查询顶级时的值,如果顶级为0,则传0
|
||||
topValue: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
childrenUrl: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
tableProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
/** 是否在创建组件的时候就查询数据 */
|
||||
immediateRequest: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dataSource: [],
|
||||
expandedRowKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getChildrenUrl () {
|
||||
if (this.childrenUrl) {
|
||||
return this.childrenUrl
|
||||
} else {
|
||||
return this.url
|
||||
}
|
||||
},
|
||||
slots () {
|
||||
const slots = []
|
||||
for (const column of this.columns) {
|
||||
if (column.scopedSlots && column.scopedSlots.customRender) {
|
||||
slots.push(column.scopedSlots.customRender)
|
||||
}
|
||||
}
|
||||
return slots
|
||||
},
|
||||
tableAttrs () {
|
||||
return Object.assign(this.$attrs, this.tableProps)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler () {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.immediateRequest) this.loadData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 加载数据 */
|
||||
loadData (id = this.topValue, first = true, url = this.url) {
|
||||
this.$emit('requestBefore', { first })
|
||||
|
||||
if (first) {
|
||||
this.expandedRowKeys = []
|
||||
}
|
||||
|
||||
const params = Object.assign({}, this.queryParams || {})
|
||||
params[this.queryKey] = id
|
||||
if (this.condition && this.condition.length > 0) {
|
||||
params.condition = this.condition
|
||||
}
|
||||
|
||||
return getAction(url, params).then(res => {
|
||||
let list = []
|
||||
if (res.result instanceof Array) {
|
||||
list = res.result
|
||||
} else if (res.result.records instanceof Array) {
|
||||
list = res.result.records
|
||||
} else {
|
||||
throw new Error('返回数据类型不识别')
|
||||
}
|
||||
const dataSource = list.map(item => {
|
||||
// 判断是否标记了带有子级
|
||||
if (item.hasChildren === true) {
|
||||
// 查找第一个带有dataIndex的值的列
|
||||
let firstColumn
|
||||
for (const column of this.columns) {
|
||||
firstColumn = column.dataIndex
|
||||
if (firstColumn) break
|
||||
}
|
||||
// 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
|
||||
const loadChild = { id: `${item.id}_loadChild`, [firstColumn]: 'loading...', isLoading: true }
|
||||
item.children = [loadChild]
|
||||
}
|
||||
return item
|
||||
})
|
||||
if (first) {
|
||||
this.dataSource = dataSource
|
||||
}
|
||||
this.$emit('requestSuccess', { first, dataSource, res })
|
||||
return Promise.resolve(dataSource)
|
||||
}).finally(() => this.$emit('requestFinally', { first }))
|
||||
},
|
||||
|
||||
/** 点击展开图标时触发 */
|
||||
handleExpand (expanded, record) {
|
||||
// 判断是否是展开状态
|
||||
if (expanded) {
|
||||
// 判断子级的首个项的标记是否是“正在加载中”,如果是就加载数据
|
||||
if (record.children[0].isLoading === true) {
|
||||
this.loadData(record.id, false, this.getChildrenUrl).then(dataSource => {
|
||||
// 处理好的数据可直接赋值给children
|
||||
if (dataSource.length === 0) {
|
||||
record.children = null
|
||||
} else {
|
||||
record.children = dataSource
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div :id="containerId" style="position: relative">
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="multiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{'biz':bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="doBeforeUpload"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:returnUrl="returnUrl"
|
||||
:listType="complistType"
|
||||
@preview="handlePreview"
|
||||
@download="handleDownload"
|
||||
:showUploadList="{
|
||||
showDownloadIcon: isDownload
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:class="{'uploadty-disabled':disabled}">
|
||||
<template>
|
||||
<div v-if="isImageComp">
|
||||
<a-icon type="plus" />
|
||||
<div class="ant-upload-text">{{ text }}</div>
|
||||
</div>
|
||||
<a-button v-else-if="buttonVisible">
|
||||
<a-icon type="upload" />
|
||||
{{ text }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<j-image-preview-modal ref="imagePreviewModal" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl, downloadFile } from '@/api/manage'
|
||||
import { previewPdf } from '@/utils/previewPdf'
|
||||
import JImagePreviewModal from '@comp/jero/modal/JImagePreviewModal.vue'
|
||||
|
||||
const FILE_TYPE_ALL = 'all'
|
||||
const FILE_TYPE_IMG = 'image'
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png', 'raw']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
// const uidGenerator = () => {
|
||||
// return '-' + parseInt(Math.random() * 10000 + 1 + '', 10)
|
||||
// }
|
||||
|
||||
const Base64 = require('js-base64').Base64
|
||||
|
||||
// 支持预览的文件类型
|
||||
const CAN_PREVIEW_FILE_TYPE = ['pdf', 'image']
|
||||
// 不支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'mp4', 'flv']
|
||||
|
||||
export default {
|
||||
name: 'JUpload',
|
||||
components: { JImagePreviewModal },
|
||||
data () {
|
||||
return {
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
headers: {},
|
||||
fileList: [],
|
||||
newFileList: [],
|
||||
image: false,
|
||||
uploadGoOn: true,
|
||||
containerId: null,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '点击上传'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: FILE_TYPE_ALL
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
// 此属性被废弃了
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url,
|
||||
* true:仅返回url
|
||||
* false:返回fileName filePath fileSize
|
||||
*/
|
||||
returnUrl: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
buttonVisible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
beforeUpload: {
|
||||
type: Function
|
||||
},
|
||||
isDownload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
const val = this.value
|
||||
this.initFileList(val)
|
||||
// if (val instanceof Array) {
|
||||
// if (this.returnUrl) {
|
||||
// this.initFileList(val.join(','))
|
||||
// } else {
|
||||
// this.initFileListArr(val)
|
||||
// }
|
||||
// } else {
|
||||
// this.initFileList(val)
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
},
|
||||
isImageComp () {
|
||||
return this.fileType === FILE_TYPE_IMG
|
||||
},
|
||||
complistType () {
|
||||
return this.fileType === FILE_TYPE_IMG ? 'picture-card' : 'text'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const token = Vue.ls.get(ACCESS_TOKEN)
|
||||
// ---------------------------- begin 图片左右换位置 -------------------------------------
|
||||
this.headers = { 'X-Access-Token': token }
|
||||
this.containerId = 'container-ty-' + new Date().getTime()
|
||||
// ---------------------------- end 图片左右换位置 -------------------------------------
|
||||
},
|
||||
methods: {
|
||||
// 将url的参数拆分成对象
|
||||
urlToParams (url) {
|
||||
const commonUrl = window._CONFIG.staticDomainURL
|
||||
// url截取参数的部分
|
||||
const paramsStr = url.slice(url.indexOf('?') + 1)
|
||||
const paramsObj = {
|
||||
url,
|
||||
id: url.slice(url.indexOf(commonUrl) + commonUrl.length + 1, url.indexOf('?'))
|
||||
}
|
||||
paramsStr.split('&').forEach(item => {
|
||||
const arr = item.split('=')
|
||||
paramsObj[arr[0]] = arr[1]
|
||||
})
|
||||
return paramsObj
|
||||
},
|
||||
initFileList (val) {
|
||||
if (!val || val.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
// 所有文件url的数组
|
||||
let arr = []
|
||||
// 用于临时存储文件的数组,最终会被赋值到this.fileList
|
||||
const fileList = []
|
||||
if (val instanceof Array) {
|
||||
// url数组直接返回,如果是对象数组,就将每个对象的url取出
|
||||
arr = this.returnUrl ? val : val.map(item => item.filePath)
|
||||
} else {
|
||||
// 将字符串拆分数组(props声明value只能是Array或String)
|
||||
arr = val.split(',')
|
||||
}
|
||||
arr.forEach(url => {
|
||||
if (url) {
|
||||
const params = this.urlToParams(url)
|
||||
fileList.push({
|
||||
uid: params.id,
|
||||
name: params.fullfilename,
|
||||
status: 'done',
|
||||
url,
|
||||
// response用于下载和预览
|
||||
response: {
|
||||
success: true,
|
||||
result: {
|
||||
id: params.id,
|
||||
fileName: params.fullfilename
|
||||
},
|
||||
status: 'history'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// 将处理好的数据回显
|
||||
this.fileList = fileList
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
doBeforeUpload (file) {
|
||||
this.uploadGoOn = true
|
||||
const fileType = file.type
|
||||
if (this.fileType === FILE_TYPE_IMG) {
|
||||
if (fileType.indexOf('image') < 0) {
|
||||
this.$message.warning('请上传图片')
|
||||
this.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 扩展 beforeUpload 验证
|
||||
if (typeof this.beforeUpload === 'function') {
|
||||
return this.beforeUpload(file)
|
||||
}
|
||||
return true
|
||||
},
|
||||
handleChange (info) {
|
||||
if (!info.file.status && this.uploadGoOn === false) {
|
||||
info.fileList.pop()
|
||||
}
|
||||
let fileList = info.fileList
|
||||
if (info.file.status === 'done') {
|
||||
if (this.number > 0) {
|
||||
fileList = fileList.slice(-this.number)
|
||||
}
|
||||
if (info.file.response.success) {
|
||||
fileList = fileList.map((file) => {
|
||||
if (file.response) {
|
||||
// const reUrl = `${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.response.result.fileName}`
|
||||
// TODO getFileAccessHttpUrl方法会追加token,在之后拼参数
|
||||
file.url = getFileAccessHttpUrl(file.response.result.id) + '&fullfilename=' + file.response.result.fileName
|
||||
}
|
||||
return file
|
||||
})
|
||||
} else {
|
||||
this.$message.error(info.file.response.message)
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.error(`${info.file.name} 上传失败.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
// returnUrl为true时仅返回文件路径
|
||||
if (this.returnUrl) {
|
||||
this.handlePathChange()
|
||||
} else {
|
||||
// returnUrl为false时返回文件名称、文件路径及文件大小
|
||||
this.newFileList = []
|
||||
for (let a = 0; a < fileList.length; a++) {
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if (fileList[a].status === 'done') {
|
||||
const fileJson = {
|
||||
fileName: fileList[a].name,
|
||||
filePath: fileList[a].url,
|
||||
fileSize: fileList[a].size
|
||||
}
|
||||
this.newFileList.push(fileJson)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
}
|
||||
this.$emit('change', this.newFileList)
|
||||
}
|
||||
}
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
console.log(file)
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
const fileType = file.type
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = file.name ? file.name.split('.')[file.name.split('.').length - 1] : ''
|
||||
const canPreview = fileType ? CAN_PREVIEW_FILE_TYPE.some(tt => fileType.indexOf(tt) !== -1) : CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (!canPreview) {
|
||||
this.$message.loading('该文件类型不支持预览,正在为您准备下载...').then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/download/${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.name}`
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (canPreview && FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.response.result.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (canPreview && FILE_TYPE_PDF.includes(fileSuffix)) {
|
||||
const url = previewPdf(file.response.result.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
const url = `${window._CONFIG.onlinePreviewDomainURL}?url=${encodeURIComponent(Base64.encode(fileFullUrl))}`
|
||||
window.open(url)
|
||||
},
|
||||
handleDownload (file) {
|
||||
// 下载文件
|
||||
downloadFile(`/sys/common/download/${file.response.result.id}`, file.name)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.uploadty-disabled {
|
||||
.ant-upload-list-item {
|
||||
.anticon-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.anticon-delete {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<j-modal
|
||||
title="详细信息"
|
||||
:width="1200"
|
||||
:visible="visible"
|
||||
@ok="handleOk"
|
||||
@cancel="close"
|
||||
switch-fullscreen
|
||||
:fullscreen.sync="fullscreen"
|
||||
>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="visible">
|
||||
<slot name="mainForm" :row="row" :column="column"/>
|
||||
<slot name="subForm" :row="row" :column="column"/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</j-modal>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { cloneObject } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDetailsModal',
|
||||
inject: ['superTrigger'],
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
fullscreen: false,
|
||||
row: null,
|
||||
column: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
methods: {
|
||||
|
||||
open (event) {
|
||||
const { row, column } = event
|
||||
this.row = cloneObject(row)
|
||||
this.column = column
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
close () {
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
this.superTrigger('detailsConfirm', {
|
||||
row: this.row,
|
||||
column: this.column,
|
||||
callback: (success) => {
|
||||
this.visible = !success
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user