【重命名】重命名最顶级文件
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,100 @@
|
||||
<!--
|
||||
<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>))
|
||||
}
|
||||
const itemList = items.map((item) => (
|
||||
<li class={ classString }>{ item }</li>
|
||||
))
|
||||
return itemList
|
||||
}
|
||||
},
|
||||
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,217 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" 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.name">
|
||||
<a-icon type="paper-clip" style="color:red;"/>
|
||||
<span style="color:red;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>
|
||||
|
||||
<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/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import JFilePop from '@/components/jeecg/minipop/JFilePop'
|
||||
|
||||
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
|
||||
|
||||
export default {
|
||||
name: 'JVxeFileCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {JFilePop},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerFile: null,
|
||||
number:0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders() {
|
||||
let {originColumn: col} = this
|
||||
let 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() {
|
||||
let length = 5
|
||||
let 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){
|
||||
let 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) {
|
||||
let {originColumn: col} = this
|
||||
let {file} = info
|
||||
let 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') {
|
||||
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,242 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" 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>
|
||||
<template v-else>
|
||||
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError"/>
|
||||
</template>
|
||||
<template slot="addonBefore" style="width: 30px">
|
||||
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
|
||||
<a-icon type="loading"/>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else-if="file.status==='done'" title="上传完成">
|
||||
<a-icon type="check-circle" style="color:#00DB00;"/>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else title="上传失败">
|
||||
<a-icon type="exclamation-circle" style="color:red;"/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
<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/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import JFilePop from '@/components/jeecg/minipop/JFilePop'
|
||||
|
||||
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
|
||||
|
||||
export default {
|
||||
name: 'JVxeImageCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {JFilePop},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerFile: null,
|
||||
number:0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders() {
|
||||
let {originColumn: col} = this
|
||||
let 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']) {
|
||||
let 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){
|
||||
let 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() {
|
||||
let file = this.innerFile || null
|
||||
if (file && file['message']) {
|
||||
this.$error({title: '上传出错', content: '错误信息:' + file['message'], maskClosable: true})
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeUpload(info) {
|
||||
let {originColumn: col} = this
|
||||
let {file} = info
|
||||
let 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') {
|
||||
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,59 @@
|
||||
<template>
|
||||
<j-popup
|
||||
v-bind="popupProps"
|
||||
@input="handlePopupInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { vModel, dispatchEvent } from '@/components/jeecg/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,
|
||||
}
|
||||
},
|
||||
},
|
||||
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 => {
|
||||
let 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(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/jeecg/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,260 @@
|
||||
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/jeecg/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() {
|
||||
let 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 {
|
||||
let {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) {
|
||||
let arr = this.dict.split(',')
|
||||
if (arr[0].indexOf('where') > 0) {
|
||||
let 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) {
|
||||
//优先从缓存中读取字典配置
|
||||
let cache = getDictItemsFromCache(this.dict)
|
||||
if (cache) {
|
||||
this.innerOptions = cache
|
||||
return
|
||||
}
|
||||
}
|
||||
let {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
|
||||
}
|
||||
let {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() {
|
||||
let 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(event, 'ant-select'),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { installCell, JVXETypes } from '@/components/jeecg/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/jeecg/JVxeTable/components/cells/JVxeSelectCell'
|
||||
import JVxeTextareaCell from '@comp/jeecg/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,54 @@
|
||||
<template>
|
||||
<div :class="[prefixCls]">
|
||||
<slot name="subtitle">
|
||||
<div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
|
||||
</slot>
|
||||
<div class="number-info-value">
|
||||
<span>{{ total }}</span>
|
||||
<span class="sub-total">
|
||||
{{ subTotal }}
|
||||
<icon :type="`caret-${status}`" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Icon from 'ant-design-vue/es/icon'
|
||||
|
||||
export default {
|
||||
name: 'NumberInfo',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-number-info'
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
subTotal: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
subTitle: {
|
||||
type: [String, Function],
|
||||
default: ''
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: 'up'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Icon
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "index";
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import NumberInfo from './NumberInfo'
|
||||
|
||||
export default NumberInfo
|
||||
@@ -0,0 +1,55 @@
|
||||
@import "../index";
|
||||
|
||||
@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
|
||||
|
||||
.@{numberInfo-prefix-cls} {
|
||||
|
||||
.ant-pro-number-info-subtitle {
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-base;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.number-info-value {
|
||||
margin-top: 4px;
|
||||
font-size: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
|
||||
& > span {
|
||||
color: @heading-color;
|
||||
display: inline-block;
|
||||
line-height: 32px;
|
||||
height: 32px;
|
||||
font-size: 24px;
|
||||
margin-right: 32px;
|
||||
}
|
||||
|
||||
.sub-total {
|
||||
color: @text-color-secondary;
|
||||
font-size: @font-size-lg;
|
||||
vertical-align: top;
|
||||
margin-right: 0;
|
||||
i {
|
||||
font-size: 12px;
|
||||
transform: scale(0.82);
|
||||
margin-left: 4px;
|
||||
}
|
||||
:global {
|
||||
.anticon-caret-up {
|
||||
color: @red-6;
|
||||
}
|
||||
.anticon-caret-down {
|
||||
color: @green-6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
####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.jeecg包:该包下自定义了很多列表/表单中用到的组件 参考包下readme文件
|
||||
####8.jeecgbiz包:该包下定义了一些业务相关的组件,比如选择用户弹框,根据部门选择用户等等
|
||||
####9.layouts+page包:系统页面布局相关组件,比如登陆进去之后页面顶部显示什么,底部显示什么,菜单点击触发多个tab的布局等等 一般情况不需要修改
|
||||
####10.menun包:菜单组件,俩个,一个折叠菜单一个正常显示的菜单
|
||||
####11.NumberInfo:数字信息显示组件 如下图
|
||||

|
||||
####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,79 @@
|
||||
import { pcaa } from 'area-data'
|
||||
|
||||
/**
|
||||
* 省市区
|
||||
*/
|
||||
export default class Area {
|
||||
/**
|
||||
* 构造器
|
||||
* @param express
|
||||
*/
|
||||
constructor() {
|
||||
let arr = []
|
||||
const province = pcaa['86']
|
||||
Object.keys(province).map(key=>{
|
||||
arr.push({id:key, text:province[key], pid:'86'});
|
||||
const city = pcaa[key];
|
||||
Object.keys(city).map(key2=>{
|
||||
arr.push({id:key2, text:city[key2], pid:key});
|
||||
const qu = pcaa[key2];
|
||||
Object.keys(qu).map(key3=>{
|
||||
arr.push({id:key3, text:qu[key3], pid:key2});
|
||||
})
|
||||
})
|
||||
})
|
||||
this.all = arr;
|
||||
}
|
||||
|
||||
get pca(){
|
||||
return this.all;
|
||||
}
|
||||
|
||||
getCode(text){
|
||||
if(!text || text.length==0){
|
||||
return ''
|
||||
}
|
||||
for(let item of this.all){
|
||||
if(item.text === text){
|
||||
return item.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getText(code){
|
||||
if(!code || code.length==0){
|
||||
return ''
|
||||
}
|
||||
let arr = []
|
||||
this.getAreaBycode(code,arr);
|
||||
return arr.join('/')
|
||||
}
|
||||
|
||||
getRealCode(code){
|
||||
let arr = []
|
||||
this.getPcode(code, arr)
|
||||
return arr;
|
||||
}
|
||||
|
||||
getPcode(id, arr){
|
||||
for(let item of this.all){
|
||||
if(item.id === id){
|
||||
arr.unshift(id)
|
||||
if(item.pid != '86'){
|
||||
this.getPcode(item.pid,arr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAreaBycode(code,arr){
|
||||
//console.log("this.all.length",this.all)
|
||||
for(let item of this.all){
|
||||
if(item.id === code){
|
||||
arr.unshift(item.text);
|
||||
this.getAreaBycode(item.pid,arr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 获取字符串的长度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
|
||||
}, '')
|
||||
}
|
||||
@@ -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: 'Jeecg', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, 'May': 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
|
||||
{ type: 'Jeebt', '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 使用不了 - 和 / 所以替换下
|
||||
let rows = dv.rows.map(row => {
|
||||
if (typeof row.x === 'string') {
|
||||
row.x = row.x.replace(/[-/]/g, '_')
|
||||
}
|
||||
return row
|
||||
})
|
||||
// 替换别名
|
||||
rows.forEach(row => {
|
||||
for (let 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', jeecg: 7.0, jeebt: 3.9 },
|
||||
{ type: 'Feb', jeecg: 6.9, jeebt: 4.2 },
|
||||
{ type: 'Mar', jeecg: 9.5, jeebt: 5.7 },
|
||||
{ type: 'Apr', jeecg: 14.5, jeebt: 8.5 },
|
||||
{ type: 'May', jeecg: 18.4, jeebt: 11.9 },
|
||||
{ type: 'Jun', jeecg: 21.5, jeebt: 15.2 },
|
||||
{ type: 'Jul', jeecg: 25.2, jeebt: 17.0 },
|
||||
{ type: 'Aug', jeecg: 26.5, jeebt: 16.6 },
|
||||
{ type: 'Sep', jeecg: 23.3, jeebt: 14.2 },
|
||||
{ type: 'Oct', jeecg: 18.3, jeebt: 10.3 },
|
||||
{ type: 'Nov', jeecg: 13.9, jeebt: 6.6 },
|
||||
{ type: 'Dec', jeecg: 9.6, jeebt: 4.8 }
|
||||
]
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default: () => ['jeecg', 'jeebt']
|
||||
},
|
||||
// 别名,需要的格式:[{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'
|
||||
})
|
||||
let rows = dv.rows
|
||||
// 替换别名
|
||||
rows.forEach(row => {
|
||||
for (let 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="width==null" :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>
|
||||
const DataSet = require('@antv/data-set')
|
||||
import { ChartEventMixins } from './mixins/ChartMixins'
|
||||
|
||||
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() {
|
||||
let 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": "Jeecg", // 列名
|
||||
"Jan.": 18.9,
|
||||
"Feb.": 28.8,
|
||||
"Mar.": 39.3,
|
||||
"Apr.": 81.4,
|
||||
"May": 47,
|
||||
"Jun.": 20.3,
|
||||
"Jul.": 24,
|
||||
"Aug.": 35.6
|
||||
},
|
||||
{
|
||||
"type": "Jeebt",
|
||||
"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
|
||||
["jeecg", "jeebt"]
|
||||
```
|
||||
|
||||
##### dataSource 示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "Jan", // 列名
|
||||
"jeecg": 7,
|
||||
"jeebt": 3.9
|
||||
},
|
||||
{ "type": "Feb", "jeecg": 6.9, "jeebt": 4.2 },
|
||||
{ "type": "Mar", "jeecg": 9.5, "jeebt": 5.7 },
|
||||
{ "type": "Apr", "jeecg": 14.5, "jeebt": 8.5 },
|
||||
{ "type": "May", "jeecg": 18.4, "jeebt": 11.9 },
|
||||
{ "type": "Jun", "jeecg": 21.5, "jeebt": 15.2 },
|
||||
{ "type": "Jul", "jeecg": 25.2, "jeebt": 17 },
|
||||
{ "type": "Aug", "jeecg": 26.5, "jeebt": 16.6 },
|
||||
{ "type": "Sep", "jeecg": 23.3, "jeebt": 14.2 },
|
||||
{ "type": "Oct", "jeecg": 18.3, "jeebt": 10.3 },
|
||||
{ "type": "Nov", "jeecg": 13.9, "jeebt": 6.6 },
|
||||
{ "type": "Dec", "jeecg": 9.6, "jeebt": 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 () {
|
||||
let 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,110 @@
|
||||
<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,
|
||||
triggerChange: Boolean,
|
||||
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() {
|
||||
// console.log(this.dictCode);
|
||||
if(!this.type || this.type==="list"){
|
||||
this.tagType = "select"
|
||||
}else{
|
||||
this.tagType = this.type
|
||||
}
|
||||
//获取字典数据
|
||||
// this.initDictData();
|
||||
},
|
||||
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) {
|
||||
// console.log(res.result);
|
||||
this.dictOptions = res.result;
|
||||
}
|
||||
})
|
||||
},
|
||||
handleInput(e) {
|
||||
let val;
|
||||
if(this.tagType=="radio"){
|
||||
val = e.target.value
|
||||
}else{
|
||||
val = e
|
||||
}
|
||||
console.log(val);
|
||||
if(this.triggerChange){
|
||||
this.$emit('change', val);
|
||||
}else{
|
||||
this.$emit('input', val);
|
||||
}
|
||||
},
|
||||
setCurrentDictOptions(dictOptions){
|
||||
this.dictOptions = dictOptions
|
||||
},
|
||||
getCurrentDictOptions(){
|
||||
return this.dictOptions
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 字典 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)){
|
||||
let res = {}
|
||||
res.result = getDictItemsFromCache(dictCode);
|
||||
res.success = true;
|
||||
return res;
|
||||
}
|
||||
//获取字典数组
|
||||
let res = await ajaxGetDictItems(dictCode);
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法
|
||||
* @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)) {
|
||||
let result = []
|
||||
// 允许多个逗号分隔,允许传数组对象
|
||||
let splitText
|
||||
if (Array.isArray(text)) {
|
||||
splitText = text
|
||||
} else {
|
||||
splitText = text.toString().trim().split(',')
|
||||
}
|
||||
for (let txt of splitText) {
|
||||
let dictText = txt
|
||||
for (let 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 (let dictItem of dictOptions) {
|
||||
if (text == dictItem.value) {
|
||||
return dictItem.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!text || text=='null' || !dictOptions || dictOptions.length==0){
|
||||
return ""
|
||||
}
|
||||
let re = "";
|
||||
text = text.toString()
|
||||
let 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译字段值对应的文本
|
||||
* @param children
|
||||
* @returns string
|
||||
*/
|
||||
export function filterDictTextByCache(dictCode, key) {
|
||||
if(key==null ||key.length==0){
|
||||
return;
|
||||
}
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!';
|
||||
}
|
||||
//优先从缓存中读取字典配置
|
||||
if(getDictItemsFromCache(dictCode)){
|
||||
let 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)){
|
||||
let desformDictItems = getDictItemsFromCache(dictCode).map(item => ({...item, label: item.text}))
|
||||
return desformDictItems;
|
||||
}
|
||||
|
||||
//缓存中没有,就请求后台
|
||||
return await ajaxGetDictItems(dictCode, params).then(({success, result}) => {
|
||||
if (success) {
|
||||
let res = result.map(item => ({...item, label: item.text}))
|
||||
console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res)
|
||||
return Promise.resolve(res)
|
||||
} else {
|
||||
console.error('getDictItems error: : ', res)
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}).catch((res) => {
|
||||
console.error('getDictItems error: ', res)
|
||||
return Promise.resolve([])
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<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>
|
||||
<a-select-option
|
||||
v-for="(item,index) in dictOptions"
|
||||
:key="index"
|
||||
: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: '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: function(val){
|
||||
this.setCurrentDictOptions(val);
|
||||
},
|
||||
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{
|
||||
//优先从缓存中读取字典配置
|
||||
let 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.$emit('change', selectedValue.join(this.spliter));
|
||||
},
|
||||
setCurrentDictOptions(dictOptions){
|
||||
this.dictOptions = dictOptions
|
||||
},
|
||||
getCurrentDictOptions(){
|
||||
return this.dictOptions
|
||||
},
|
||||
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,246 @@
|
||||
<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
|
||||
}
|
||||
},
|
||||
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){
|
||||
let 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){
|
||||
let arr = this.dict.split(',')
|
||||
if(arr[0].indexOf('where')>0){
|
||||
let 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{
|
||||
//异步一开始也加载一点数据
|
||||
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(!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,163 @@
|
||||
<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 { pcaa } from 'area-data'
|
||||
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,
|
||||
innerValue: [],
|
||||
usedListeners: ['change'],
|
||||
enums: {
|
||||
type: ['cascader', 'select']
|
||||
},
|
||||
reloading: false,
|
||||
areaData:''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_listeners() {
|
||||
let 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()
|
||||
let arr = this.areaData.getRealCode(value)
|
||||
this.innerValue = arr
|
||||
}
|
||||
this.reload()
|
||||
},
|
||||
/** 通过地区code获取子级 */
|
||||
loadDataByCode(value) {
|
||||
let options = []
|
||||
let data = pcaa[value]
|
||||
if (data) {
|
||||
for (let key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
options.push({ value: key, label: data[key], })
|
||||
}
|
||||
}
|
||||
return options
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/** 判断是否有子节点 */
|
||||
hasChildren(options) {
|
||||
options.forEach(option => {
|
||||
let data = this.loadDataByCode(option.value)
|
||||
option.isLeaf = data.length === 0
|
||||
})
|
||||
},
|
||||
handleChange(values) {
|
||||
let value = values[values.length - 1]
|
||||
this.$emit('change', value)
|
||||
},
|
||||
initAreaData(){
|
||||
if(!this.areaData){
|
||||
this.areaData = new Area();
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
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,243 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
: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:"",
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
pcode(){
|
||||
this.loadRoot();
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.validateProp().then(()=>{
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/**加载一级节点 */
|
||||
loadRoot(){
|
||||
let 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(let 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){
|
||||
let 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){
|
||||
let obj = {}
|
||||
if(this.back){
|
||||
obj[this.back] = label
|
||||
}
|
||||
this.$emit('change', value, obj)
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
let pid = treeNode.$vnode.key
|
||||
let param = {
|
||||
pid:pid,
|
||||
condition:this.condition
|
||||
}
|
||||
getAction(this.url,param).then(res=>{
|
||||
if(res.success){
|
||||
for(let 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(let 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){
|
||||
this.$emit('change', '');
|
||||
this.treeValue = ''
|
||||
} else if (Array.isArray(value)) {
|
||||
let labels = []
|
||||
let 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(){
|
||||
let mycondition = this.condition
|
||||
return new Promise((resolve,reject)=>{
|
||||
if(!mycondition){
|
||||
resolve();
|
||||
}else{
|
||||
try {
|
||||
let 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,43 @@
|
||||
<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,479 @@
|
||||
<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) {
|
||||
// 获取具体的语法类型对象
|
||||
let 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() {
|
||||
let 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()
|
||||
if(this.code){
|
||||
this.hasCode=true
|
||||
}else{
|
||||
this.hasCode=false
|
||||
}
|
||||
if (this.$emit) {
|
||||
this.$emit('input', this.code)
|
||||
}
|
||||
})
|
||||
this.coder.on('focus', () => {
|
||||
this.hasCode=true
|
||||
})
|
||||
this.coder.on('blur', () => {
|
||||
if(this.code){
|
||||
this.hasCode=true
|
||||
}else{
|
||||
this.hasCode=false
|
||||
}
|
||||
})
|
||||
|
||||
/* 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) => {
|
||||
// 所有的值都忽略大小写,方便比较
|
||||
let currentLanguage = language.toLowerCase()
|
||||
let currentLabel = mode.label.toLowerCase()
|
||||
let currentValue = mode.value.toLowerCase()
|
||||
|
||||
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
||||
return currentLabel === currentLanguage || currentValue === currentLanguage
|
||||
})
|
||||
},
|
||||
_getCoder() {
|
||||
let _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}`)
|
||||
|
||||
// 获取修改后的语法
|
||||
let 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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全屏样式 */
|
||||
.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="corn表达式" v-model="cron" @change="handleOK">
|
||||
<a-icon slot="prefix" type="schedule" title="corn控件"/>
|
||||
<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,86 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
dropdownClassName="j-date-picker"
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
@change="handleDateChange"
|
||||
:value="momVal"
|
||||
:showTime="showTime"
|
||||
:format="dateFormat"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
/>
|
||||
</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
|
||||
},
|
||||
//此属性可以被废弃了
|
||||
triggerChange:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
readOnly:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
showTime:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
let dateStr = this.value;
|
||||
return {
|
||||
decorator:"",
|
||||
momVal:!dateStr?null:moment(dateStr,this.dateFormat)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if(!val){
|
||||
this.momVal = null
|
||||
}else{
|
||||
this.momVal = moment(val,this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleDateChange(mom,dateStr){
|
||||
this.$emit('change', dateStr);
|
||||
}
|
||||
},
|
||||
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
<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) => {
|
||||
let 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{
|
||||
let 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() {
|
||||
// 获取父级
|
||||
let tabs = getVmParentByName(this, 'ATabs')
|
||||
let tabPane = getVmParentByName(this, 'ATabPane')
|
||||
if (tabs && tabPane) {
|
||||
// 用户自定义的 key
|
||||
let currentKey = tabPane.$vnode.key
|
||||
// 添加事件监听
|
||||
tabs.$on('change', (key) => {
|
||||
// 切换到自己时执行reload
|
||||
if (currentKey === key) {
|
||||
this.reload()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
//update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
|
||||
let tabLayout = getVmParentByName(this, 'TabLayout')
|
||||
tabLayout.excuteCallback(()=>{
|
||||
this.reload()
|
||||
})
|
||||
//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,29 @@
|
||||
<template>
|
||||
<a-tooltip placement="topLeft">
|
||||
<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,61 @@
|
||||
<template>
|
||||
<div :class="disabled?'jeecg-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>
|
||||
.jeecg-form-container-disabled{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.jeecg-form-container-disabled fieldset[disabled] {
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.jeecg-form-container-disabled .ant-select{
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jeecg-form-container-disabled .ant-upload-select{display:none}
|
||||
.jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
|
||||
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
|
||||
-ms-pointer-events: auto !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.jeecg-form-container-disabled .ant-upload-list-item-actions .anticon-delete,
|
||||
.jeecg-form-container-disabled .ant-upload-list-item .anticon-close{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<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"
|
||||
:isMultiple="isMultiple"
|
||||
:showUploadList="isMultiple"
|
||||
@change="handleChange"
|
||||
@preview="handlePreview"
|
||||
:class="!isMultiple?'imgupload':''">
|
||||
<div :style="{'width':(!isMultiple?'104px':'auto'),'height':(!isMultiple?'104px':'auto')}">
|
||||
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>
|
||||
<div v-else class="iconp">
|
||||
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
|
||||
<div class="ant-upload-text">{{ text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
|
||||
<img alt="example" style="width: 100%" :src="previewImage"/>
|
||||
</a-modal>
|
||||
</a-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from "@/store/mutation-types"
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
|
||||
const uidGenerator=()=>{
|
||||
return '-'+parseInt(Math.random()*10000+1,10);
|
||||
}
|
||||
const getFileName=(path)=>{
|
||||
if(path.lastIndexOf("\\")>=0){
|
||||
let reg=new RegExp("\\\\","g");
|
||||
path = path.replace(reg,"/");
|
||||
}
|
||||
return path.substring(path.lastIndexOf("/")+1);
|
||||
}
|
||||
export default {
|
||||
name: 'JImageUpload',
|
||||
data(){
|
||||
return {
|
||||
uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
|
||||
uploadLoading:false,
|
||||
picUrl:false,
|
||||
headers:{},
|
||||
fileList: [],
|
||||
previewImage:"",
|
||||
previewVisible: false,
|
||||
}
|
||||
},
|
||||
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属性,用于判断上传数量
|
||||
},
|
||||
watch:{
|
||||
value: {
|
||||
handler(val,oldValue) {
|
||||
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;
|
||||
let fileList = [];
|
||||
let arr = paths.split(",")
|
||||
for(var a=0;a<arr.length;a++){
|
||||
let url = getFileAccessHttpUrl(arr[a]);
|
||||
fileList.push({
|
||||
uid: uidGenerator(),
|
||||
name: getFileName(arr[a]),
|
||||
status: 'done',
|
||||
url: url,
|
||||
response:{
|
||||
status:"history",
|
||||
message:arr[a]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.fileList = fileList
|
||||
},
|
||||
beforeUpload: function(file){
|
||||
var 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 && this.isMultiple){
|
||||
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.message;
|
||||
}
|
||||
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) {
|
||||
this.previewImage = file.url || file.thumbUrl
|
||||
this.previewVisible = true
|
||||
},
|
||||
getAvatarView(){
|
||||
if(this.fileList.length>0){
|
||||
let url = this.fileList[0].url
|
||||
return getFileAccessHttpUrl(url)
|
||||
}
|
||||
},
|
||||
handlePathChange(){
|
||||
let uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if(!uploadFiles || uploadFiles.length==0){
|
||||
path = ''
|
||||
}
|
||||
let arr = [];
|
||||
if(!this.isMultiple){
|
||||
arr.push(uploadFiles[uploadFiles.length-1].response.message)
|
||||
}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].response.message)
|
||||
}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){
|
||||
//如有需要新增 删除逻辑
|
||||
console.log(file)
|
||||
},
|
||||
handleCancel() {
|
||||
this.close();
|
||||
this.previewVisible = false;
|
||||
},
|
||||
close () {
|
||||
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* update--begin--autor:lvdandan-----date:20201016------for:j-image-upload图片组件单张图片详情回显空白
|
||||
* https://github.com/zhangdaiscott/jeecg-boot/issues/1810
|
||||
* https://github.com/zhangdaiscott/jeecg-boot/issues/1779
|
||||
*/
|
||||
/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,123 @@
|
||||
<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"
|
||||
: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
|
||||
},
|
||||
biz:{
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
visible:false,
|
||||
uploading:false,
|
||||
fileList:[],
|
||||
uploadAction:'',
|
||||
foreignKeys:''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url (val) {
|
||||
if(val){
|
||||
this.uploadAction = window._CONFIG['domianURL']+val
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
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,111 @@
|
||||
<template>
|
||||
<a-input :placeholder="placeholder" :value="inputVal" @input="backValue"></a-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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:{
|
||||
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:false
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
value:{
|
||||
immediate:true,
|
||||
handler:function(){
|
||||
this.initVal();
|
||||
}
|
||||
},
|
||||
// update-begin author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
|
||||
type() {
|
||||
this.backValue({ target: { value: this.inputVal } })
|
||||
},
|
||||
// update-end author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
</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,255 @@
|
||||
<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/jeecg/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(val) {
|
||||
this.destroyEditor()
|
||||
this.initEditor()
|
||||
},
|
||||
height(newValue) {
|
||||
this.editor.height(newValue)
|
||||
},
|
||||
mode(newValue) {
|
||||
this.editor.changeMode(newValue)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initEditor()
|
||||
},
|
||||
destroyed() {
|
||||
this.destroyEditor()
|
||||
},
|
||||
methods: {
|
||||
initEditor() {
|
||||
this.editor = new Editor({
|
||||
el: document.getElementById(this.id),
|
||||
...this.editorOptions
|
||||
})
|
||||
if (this.value) {
|
||||
this.editor.setMarkdown(this.value)
|
||||
}
|
||||
this.editor.on('change', () => {
|
||||
this.$emit('change', this.editor.getMarkdown())
|
||||
})
|
||||
//--begin 添加自定义上传按钮
|
||||
/*
|
||||
* 添加自定义按钮
|
||||
*/
|
||||
//获取编辑器上的功能条
|
||||
let toolbar = this.editor.getUI().getToolbar();
|
||||
let fileDom = this.$refs.files;
|
||||
//添加图片点击事件
|
||||
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) {
|
||||
let editor = this.editor.getCodeMirror();
|
||||
let editorHtml = this.editor.getCurrentModeEditor();
|
||||
let isMarkdownMode = this.editor.isMarkdownMode();
|
||||
if (isMarkdownMode) {
|
||||
editor.replaceSelection(``);
|
||||
} else {
|
||||
let range = editorHtml.getRange();
|
||||
let 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,228 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 处理 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() {
|
||||
let 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() {
|
||||
let 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,221 @@
|
||||
<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"
|
||||
: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
|
||||
},
|
||||
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) {
|
||||
let { groupId, code, field, orgFields, destFields } = this
|
||||
return `${groupId}_${code}_${field}_${orgFields}_${destFields}`
|
||||
}
|
||||
}
|
||||
},
|
||||
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 = ''
|
||||
let destFieldsArr = this.destFields.split(',')
|
||||
if (destFieldsArr.length === 0) {
|
||||
return
|
||||
}
|
||||
let 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:多选时未带回多个值------
|
||||
let orgFieldsArr = this.orgFields.split(',')
|
||||
let destFieldsArr = this.destFields.split(',')
|
||||
let resetText = false
|
||||
if (this.field && this.field.length > 0) {
|
||||
this.showText = ''
|
||||
resetText = true
|
||||
}
|
||||
let res = {}
|
||||
if (orgFieldsArr.length > 0) {
|
||||
for (let i = 0; i < orgFieldsArr.length; i++) {
|
||||
let tempDestArr = []
|
||||
for(let rw of rows){
|
||||
let val = rw[orgFieldsArr[i]]
|
||||
if(!val){
|
||||
val = ""
|
||||
}
|
||||
tempDestArr.push(val)
|
||||
}
|
||||
res[destFieldsArr[i]] = tempDestArr.join(",")
|
||||
}
|
||||
if (resetText === true) {
|
||||
let tempText = []
|
||||
for(let 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,84 @@
|
||||
<template>
|
||||
<a-select :value="arrayValue" @change="onChange" mode="multiple" :placeholder="placeholder">
|
||||
<a-select-option
|
||||
v-for="(item,index) in options"
|
||||
: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 {
|
||||
arrayValue:!this.value?[]:this.value.split(this.spliter)
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
value (val) {
|
||||
if(!val){
|
||||
this.arrayValue = []
|
||||
}else{
|
||||
this.arrayValue = this.value.split(this.spliter)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
onChange (selectedValue) {
|
||||
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: 0px;left: 0px;"></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){
|
||||
let 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 的时候报错 ----
|
||||
let 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: 0px;
|
||||
}
|
||||
.drag_text{
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
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,679 @@
|
||||
<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>已有高级查询条件生效</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleReset">清空</a>
|
||||
</template>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="handleOpen">
|
||||
<a-icon type="appstore" theme="twoTone" spin/>
|
||||
<span>高级查询</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">高级查询</a-button>
|
||||
</slot>
|
||||
|
||||
<j-modal
|
||||
title="高级查询构造器"
|
||||
: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">重置</a-button>
|
||||
<a-button :loading="loading" @click="handleSave">保存查询条件</a-button>
|
||||
</div>
|
||||
<a-button :loading="loading" @click="handleCancel">关闭</a-button>
|
||||
<a-button :loading="loading" type="primary" @click="handleOk">查询</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>没有任何查询条件</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleAdd">点击新增</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="过滤条件匹配" :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(所有条件都要求匹配)</a-select-option>
|
||||
<a-select-option value="or">OR(条件中的任意一个匹配)</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
|
||||
v-model="item.field"
|
||||
:treeData="fieldTreeData"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
placeholder="选择查询字段"
|
||||
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="匹配规则" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
|
||||
<a-select-option value="eq">等于</a-select-option>
|
||||
<a-select-option value="like">包含</a-select-option>
|
||||
<a-select-option value="right_like">以..开始</a-select-option>
|
||||
<a-select-option value="left_like">以..结尾</a-select-option>
|
||||
<a-select-option value="in">在...中</a-select-option>
|
||||
<a-select-option value="ne">不等于</a-select-option>
|
||||
<a-select-option value="gt">大于</a-select-option>
|
||||
<a-select-option value="ge">大于等于</a-select-option>
|
||||
<a-select-option value="lt">小于</a-select-option>
|
||||
<a-select-option value="le">小于等于</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="请选择"/>
|
||||
<j-multi-select-tag v-else v-model="item.val" :dictCode="getDictInfo(item)" placeholder="请选择"/>
|
||||
</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="请选择"/>
|
||||
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
|
||||
</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="请选择用户"
|
||||
: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="请选择部门"
|
||||
:customReturnField="item.customReturnField || 'id'"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="item.options instanceof Array"
|
||||
v-model="item.val"
|
||||
:options="item.options"
|
||||
allowClear
|
||||
placeholder="请选择"
|
||||
: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="请选择日期" style="width: 100%"></j-date>
|
||||
<j-date v-else-if=" item.type=='datetime' " v-model="item.val" placeholder="请选择时间" :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="请输入数值" v-model="item.val"/>
|
||||
<a-select v-else-if="item.type=='switch'" placeholder="请选择" v-model="item.val">
|
||||
<a-select-option value="Y">是</a-select-option>
|
||||
<a-select-option value="N">否</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-else v-model="item.val" placeholder="请输入值"/>
|
||||
</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">
|
||||
保存的查询
|
||||
</div>
|
||||
|
||||
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" description="没有保存任何查询"/>
|
||||
<a-tree
|
||||
v-else
|
||||
class="j-super-query-history-tree"
|
||||
showIcon
|
||||
:treeData="saveTreeData"
|
||||
:selectedKeys="[]"
|
||||
@select="handleTreeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
</a-card>
|
||||
|
||||
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
|
||||
</a-spin>
|
||||
|
||||
<a-modal title="请输入保存的名称" :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/jeecg/JDate.vue'
|
||||
import JSelectDepart from '@/components/jeecgbiz/JSelectDepart'
|
||||
import JSelectMultiUser from '@/components/jeecgbiz/JSelectMultiUser'
|
||||
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
|
||||
import JAreaLinkage from '@comp/jeecg/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() {
|
||||
let 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) {
|
||||
let mainData = [], subData = []
|
||||
val.forEach(item => {
|
||||
let data = { ...item }
|
||||
data.label = data.label || data.text
|
||||
let hasChildren = (data.children instanceof Array)
|
||||
data.disabled = hasChildren
|
||||
data.selectable = !hasChildren
|
||||
if (hasChildren) {
|
||||
data.children = data.children.map(item2 => {
|
||||
let 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)) {
|
||||
let event = {
|
||||
matchType: this.matchType,
|
||||
params: this.removeEmptyObject(this.queryParamsModel)
|
||||
}
|
||||
// 移动端模式下关闭弹窗
|
||||
if (this.izMobile) {
|
||||
this.visible = false
|
||||
}
|
||||
this.emitCallback(event)
|
||||
} else {
|
||||
this.$message.warn("不能查询空条件")
|
||||
}
|
||||
},
|
||||
emitCallback(event = {}) {
|
||||
let { params = [], matchType = this.matchType } = event
|
||||
this.superQueryFlag = (params && params.length > 0)
|
||||
for (let 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) {
|
||||
let { type, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
|
||||
item['type'] = type
|
||||
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() {
|
||||
let queryParams = this.removeEmptyObject(this.queryParamsModel)
|
||||
if (this.isNullArray(queryParams)) {
|
||||
this.$message.warning('空条件不能保存')
|
||||
} else {
|
||||
this.prompt.value = ''
|
||||
this.prompt.visible = true
|
||||
}
|
||||
},
|
||||
handlePromptOk() {
|
||||
let { value } = this.prompt
|
||||
if(!value){
|
||||
this.$message.warning('保存名称不能为空')
|
||||
return
|
||||
}
|
||||
// 取出查询条件
|
||||
let records = this.removeEmptyObject(this.queryParamsModel)
|
||||
// 判断有没有重名的
|
||||
let filterList = this.saveTreeData.filter(i => i.originTitle === value)
|
||||
if (filterList.length > 0) {
|
||||
this.$confirm({
|
||||
content: `${value} 已存在,是否覆盖?`,
|
||||
onOk: () => {
|
||||
this.prompt.visible = false
|
||||
filterList[0].records = records
|
||||
this.saveToLocalStore()
|
||||
this.$message.success('保存成功')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 没有重名的,直接添加
|
||||
this.prompt.visible = false
|
||||
// 添加到树列表中
|
||||
this.saveTreeData.push(this.renderSaveTreeData({
|
||||
title: value,
|
||||
matchType: this.matchType,
|
||||
records: records
|
||||
}))
|
||||
// 保存到 LocalStore
|
||||
this.saveToLocalStore()
|
||||
this.$message.success('保存成功')
|
||||
}
|
||||
},
|
||||
handleTreeSelect(idx, event) {
|
||||
if (event.selectedNodes[0]) {
|
||||
let { 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: '是否删除当前查询?',
|
||||
onOk: () => {
|
||||
let { eventKey } = vNode
|
||||
this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
|
||||
this.saveToLocalStore()
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// 将查询保存到 LocalStore 里
|
||||
saveToLocalStore() {
|
||||
let 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) {
|
||||
let obj = array[0]
|
||||
if (!obj.field || (obj.val == null || obj.val === '') || !obj.rule) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 去掉数组中的空对象
|
||||
removeEmptyObject(arr) {
|
||||
let array = utils.cloneObject(arr)
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let 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>
|
||||
}
|
||||
let {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) {
|
||||
let 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,83 @@
|
||||
<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"/>
|
||||
</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
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value:{
|
||||
immediate: true,
|
||||
handler(val){
|
||||
if(!this.query){
|
||||
if(!val){
|
||||
this.checkStatus = false
|
||||
this.$emit('change', this.options[1]);
|
||||
}else{
|
||||
if(this.options[0]==val){
|
||||
this.checkStatus = true
|
||||
}else{
|
||||
this.checkStatus = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
queryOption(){
|
||||
let arr = []
|
||||
arr.push({value:this.options[0],text:'是'})
|
||||
arr.push({value:this.options[1],text:'否'})
|
||||
return arr;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(checked){
|
||||
let 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,77 @@
|
||||
<template>
|
||||
<a-time-picker
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:format="dateFormat"
|
||||
: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 () {
|
||||
let timeStr = this.value;
|
||||
return {
|
||||
decorator:"",
|
||||
momVal:!timeStr?null:moment(timeStr,this.dateFormat)
|
||||
}
|
||||
},
|
||||
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,198 @@
|
||||
<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">
|
||||
</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()
|
||||
}
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.loadRoot()
|
||||
this.loadViewInfo()
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
methods:{
|
||||
loadViewInfo(){
|
||||
if(!this.value || this.value=="0"){
|
||||
this.treeValue = null
|
||||
}else{
|
||||
let 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(){
|
||||
let 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
|
||||
}
|
||||
let pid = treeNode.$vnode.key
|
||||
let 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(let 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){
|
||||
let storeField = this.field=='code'?'code':'key'
|
||||
for(let i of result){
|
||||
i.value = i[storeField]
|
||||
i.isLeaf = (!i.leaf)?false:true
|
||||
if(i.children && i.children.length>0){
|
||||
this.handleTreeNodeValue(i.children)
|
||||
}
|
||||
}
|
||||
},
|
||||
onChange(value){
|
||||
console.log(value)
|
||||
if(!value){
|
||||
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,259 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
@change="onChange"
|
||||
@search="onSearch">
|
||||
</a-tree-select>
|
||||
</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: {
|
||||
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();
|
||||
}
|
||||
},
|
||||
created(){
|
||||
this.validateProp().then(()=>{
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
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){
|
||||
let 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.$emit('change', this.value,text)
|
||||
}
|
||||
},
|
||||
initDictInfo(){
|
||||
let arr = this.dict.split(",")
|
||||
this.tableName = arr[0]
|
||||
this.text = arr[1]
|
||||
this.code = arr[2]
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
let pid = treeNode.$vnode.key
|
||||
let 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(let 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(let 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
loadRoot(){
|
||||
let 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(let 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)
|
||||
}
|
||||
})
|
||||
},
|
||||
onChange(value){
|
||||
if(!value){
|
||||
this.$emit('change', '');
|
||||
this.treeValue = null
|
||||
} else if (value instanceof Array) {
|
||||
this.$emit('change', value.map(item => item.value).join(','))
|
||||
this.treeValue = value
|
||||
} else {
|
||||
this.$emit('change', value.value,value.label)
|
||||
this.treeValue = value
|
||||
}
|
||||
|
||||
},
|
||||
onSearch(value){
|
||||
console.log(value)
|
||||
},
|
||||
getCurrTreeData(){
|
||||
return this.treeData
|
||||
},
|
||||
validateProp(){
|
||||
let mycondition = this.condition
|
||||
return new Promise((resolve,reject)=>{
|
||||
if(!mycondition){
|
||||
resolve();
|
||||
}else{
|
||||
try {
|
||||
let 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,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() {
|
||||
let slots = []
|
||||
for (let 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 = []
|
||||
}
|
||||
|
||||
let 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 '返回数据类型不识别'
|
||||
}
|
||||
let dataSource = list.map(item => {
|
||||
// 判断是否标记了带有子级
|
||||
if (item.hasChildren === true) {
|
||||
// 查找第一个带有dataIndex的值的列
|
||||
let firstColumn
|
||||
for (let column of this.columns) {
|
||||
firstColumn = column.dataIndex
|
||||
if (firstColumn) break
|
||||
}
|
||||
// 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
|
||||
let 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,449 @@
|
||||
<template>
|
||||
<div :id="containerId" style="position: relative">
|
||||
|
||||
<!-- ---------------------------- begin 图片左右换位置 ------------------------------------- -->
|
||||
<div class="movety-container" :style="{top:top+'px',left:left+'px',display:moveDisplay}" style="padding:0 8px;position: absolute;z-index: 91;height: 32px;width: 104px;text-align: center;">
|
||||
<div :id="containerId+'-mover'" :class="showMoverTask?'uploadty-mover-mask':'movety-opt'" style="margin-top: 12px">
|
||||
<a @click="moveLast" style="margin: 0 5px;"><a-icon type="arrow-left" style="color: #fff;font-size: 16px"/></a>
|
||||
<a @click="moveNext" style="margin: 0 5px;"><a-icon type="arrow-right" style="color: #fff;font-size: 16px"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ---------------------------- end 图片左右换位置 ------------------------------------- -->
|
||||
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="true"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{'biz':bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="beforeUpload"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:returnUrl="returnUrl"
|
||||
:listType="complistType"
|
||||
@preview="handlePreview"
|
||||
: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>
|
||||
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
|
||||
<img alt="example" style="width: 100%" :src="previewImage" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from "@/store/mutation-types"
|
||||
import { getFileAccessHttpUrl } from '@/api/manage';
|
||||
|
||||
const FILE_TYPE_ALL = "all"
|
||||
const FILE_TYPE_IMG = "image"
|
||||
const FILE_TYPE_TXT = "file"
|
||||
const uidGenerator=()=>{
|
||||
return '-'+parseInt(Math.random()*10000+1,10);
|
||||
}
|
||||
const getFileName=(path)=>{
|
||||
if(path.lastIndexOf("\\")>=0){
|
||||
let reg=new RegExp("\\\\","g");
|
||||
path = path.replace(reg,"/");
|
||||
}
|
||||
return path.substring(path.lastIndexOf("/")+1);
|
||||
}
|
||||
export default {
|
||||
name: 'JUpload',
|
||||
data(){
|
||||
return {
|
||||
uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
|
||||
headers:{},
|
||||
fileList: [],
|
||||
newFileList: [],
|
||||
uploadGoOn:true,
|
||||
previewVisible: false,
|
||||
//---------------------------- begin 图片左右换位置 -------------------------------------
|
||||
previewImage: '',
|
||||
containerId:'',
|
||||
top:'',
|
||||
left:'',
|
||||
moveDisplay:'none',
|
||||
showMoverTask:false,
|
||||
moverHold:false,
|
||||
currentImg:''
|
||||
//---------------------------- end 图片左右换位置 -------------------------------------
|
||||
}
|
||||
},
|
||||
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
|
||||
},
|
||||
},
|
||||
watch:{
|
||||
value:{
|
||||
immediate: true,
|
||||
handler() {
|
||||
let val = this.value
|
||||
if (val instanceof Array) {
|
||||
if(this.returnUrl){
|
||||
this.initFileList(val.join(','))
|
||||
}else{
|
||||
this.initFileListArr(val);
|
||||
}
|
||||
} else {
|
||||
this.initFileList(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
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:{
|
||||
initFileListArr(val){
|
||||
if(!val || val.length==0){
|
||||
this.fileList = [];
|
||||
return;
|
||||
}
|
||||
let fileList = [];
|
||||
for(var a=0;a<val.length;a++){
|
||||
let url = getFileAccessHttpUrl(val[a].filePath);
|
||||
fileList.push({
|
||||
uid:uidGenerator(),
|
||||
name:val[a].fileName,
|
||||
status: 'done',
|
||||
url: url,
|
||||
response:{
|
||||
status:"history",
|
||||
message:val[a].filePath
|
||||
}
|
||||
})
|
||||
}
|
||||
this.fileList = fileList
|
||||
},
|
||||
initFileList(paths){
|
||||
if(!paths || paths.length==0){
|
||||
//return [];
|
||||
// update-begin- --- author:os_chengtgen ------ date:20190729 ---- for:issues:326,Jupload组件初始化bug
|
||||
this.fileList = [];
|
||||
return;
|
||||
// update-end- --- author:os_chengtgen ------ date:20190729 ---- for:issues:326,Jupload组件初始化bug
|
||||
}
|
||||
let fileList = [];
|
||||
let arr = paths.split(",")
|
||||
for(var a=0;a<arr.length;a++){
|
||||
let url = getFileAccessHttpUrl(arr[a]);
|
||||
fileList.push({
|
||||
uid:uidGenerator(),
|
||||
name:getFileName(arr[a]),
|
||||
status: 'done',
|
||||
url: url,
|
||||
response:{
|
||||
status:"history",
|
||||
message:arr[a]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.fileList = fileList
|
||||
},
|
||||
handlePathChange(){
|
||||
let uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if(!uploadFiles || uploadFiles.length==0){
|
||||
path = ''
|
||||
}
|
||||
let arr = [];
|
||||
|
||||
for(var a=0;a<uploadFiles.length;a++){
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if(uploadFiles[a].status === 'done' ) {
|
||||
arr.push(uploadFiles[a].response.message)
|
||||
}else{
|
||||
return;
|
||||
}
|
||||
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
}
|
||||
if(arr.length>0){
|
||||
path = arr.join(",")
|
||||
}
|
||||
this.$emit('change', path);
|
||||
},
|
||||
beforeUpload(file){
|
||||
this.uploadGoOn=true
|
||||
var fileType = file.type;
|
||||
if(this.fileType===FILE_TYPE_IMG){
|
||||
if(fileType.indexOf('image')<0){
|
||||
this.$message.warning('请上传图片');
|
||||
this.uploadGoOn=false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//TODO 扩展功能验证文件大小
|
||||
return true
|
||||
},
|
||||
handleChange(info) {
|
||||
console.log("--文件列表改变--")
|
||||
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) {
|
||||
let reUrl = file.response.message;
|
||||
file.url = getFileAccessHttpUrl(reUrl);
|
||||
}
|
||||
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'){
|
||||
//returnUrl为true时仅返回文件路径
|
||||
if(this.returnUrl){
|
||||
this.handlePathChange()
|
||||
}else{
|
||||
//returnUrl为false时返回文件名称、文件路径及文件大小
|
||||
this.newFileList = [];
|
||||
for(var a=0;a<fileList.length;a++){
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if(fileList[a].status === 'done' ) {
|
||||
var fileJson = {
|
||||
fileName:fileList[a].name,
|
||||
filePath:fileList[a].response.message,
|
||||
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(this.fileType === FILE_TYPE_IMG){
|
||||
this.previewImage = file.url || file.thumbUrl;
|
||||
this.previewVisible = true;
|
||||
}else{
|
||||
location.href=file.url
|
||||
}
|
||||
},
|
||||
handleCancel(){
|
||||
this.previewVisible = false;
|
||||
},
|
||||
//---------------------------- begin 图片左右换位置 -------------------------------------
|
||||
moveLast(){
|
||||
//console.log(ev)
|
||||
//console.log(this.fileList)
|
||||
//console.log(this.currentImg)
|
||||
let index = this.getIndexByUrl();
|
||||
if(index==0){
|
||||
this.$message.warn('未知的操作')
|
||||
}else{
|
||||
let curr = this.fileList[index].url;
|
||||
let last = this.fileList[index-1].url;
|
||||
let arr =[]
|
||||
for(let i=0;i<this.fileList.length;i++){
|
||||
if(i==index-1){
|
||||
arr.push(curr)
|
||||
}else if(i==index){
|
||||
arr.push(last)
|
||||
}else{
|
||||
arr.push(this.fileList[i].url)
|
||||
}
|
||||
}
|
||||
this.currentImg = last
|
||||
this.$emit('change',arr.join(','))
|
||||
}
|
||||
},
|
||||
moveNext(){
|
||||
let index = this.getIndexByUrl();
|
||||
if(index==this.fileList.length-1){
|
||||
this.$message.warn('已到最后~')
|
||||
}else{
|
||||
let curr = this.fileList[index].url;
|
||||
let next = this.fileList[index+1].url;
|
||||
let arr =[]
|
||||
for(let i=0;i<this.fileList.length;i++){
|
||||
if(i==index+1){
|
||||
arr.push(curr)
|
||||
}else if(i==index){
|
||||
arr.push(next)
|
||||
}else{
|
||||
arr.push(this.fileList[i].url)
|
||||
}
|
||||
}
|
||||
this.currentImg = next
|
||||
this.$emit('change',arr.join(','))
|
||||
}
|
||||
},
|
||||
getIndexByUrl(){
|
||||
for(let i=0;i<this.fileList.length;i++){
|
||||
if(this.fileList[i].url === this.currentImg || encodeURI(this.fileList[i].url) === this.currentImg){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
const moverObj = document.getElementById(this.containerId+'-mover');
|
||||
if(moverObj){
|
||||
moverObj.addEventListener('mouseover',()=>{
|
||||
this.moverHold = true
|
||||
this.moveDisplay = 'block';
|
||||
});
|
||||
moverObj.addEventListener('mouseout',()=>{
|
||||
this.moverHold = false
|
||||
this.moveDisplay = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
let picList = document.getElementById(this.containerId)?document.getElementById(this.containerId).getElementsByClassName('ant-upload-list-picture-card'):[];
|
||||
if(picList && picList.length>0){
|
||||
picList[0].addEventListener('mouseover',(ev)=>{
|
||||
ev = ev || window.event;
|
||||
let target = ev.target || ev.srcElement;
|
||||
if('ant-upload-list-item-info' == target.className){
|
||||
this.showMoverTask=false
|
||||
let item = target.parentElement
|
||||
this.left = item.offsetLeft
|
||||
this.top=item.offsetTop+item.offsetHeight-50;
|
||||
this.moveDisplay = 'block';
|
||||
this.currentImg = target.getElementsByTagName('img')[0].src
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
picList[0].addEventListener('mouseout',(ev)=>{
|
||||
ev = ev || window.event;
|
||||
let target = ev.target || ev.srcElement;
|
||||
//console.log('移除',target)
|
||||
if('ant-upload-list-item-info' == target.className){
|
||||
this.showMoverTask=true
|
||||
setTimeout(()=>{
|
||||
if(this.moverHold === false)
|
||||
this.moveDisplay = 'none';
|
||||
},100)
|
||||
}
|
||||
if('ant-upload-list-item ant-upload-list-item-done' == target.className || 'ant-upload-list ant-upload-list-picture-card'== target.className){
|
||||
this.moveDisplay = 'none';
|
||||
}
|
||||
})
|
||||
//---------------------------- end 图片左右换位置 -------------------------------------
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.uploadty-disabled{
|
||||
.ant-upload-list-item {
|
||||
.anticon-close{
|
||||
display: none;
|
||||
}
|
||||
.anticon-delete{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
//---------------------------- begin 图片左右换位置 -------------------------------------
|
||||
.uploadty-mover-mask{
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: .8;
|
||||
color: #fff;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
//---------------------------- end 图片左右换位置 -------------------------------------
|
||||
</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) {
|
||||
let {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>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div :class="boxClass">
|
||||
<a-pagination
|
||||
:disabled="disabled"
|
||||
v-bind="bindProps"
|
||||
@change="handleChange"
|
||||
@showSizeChange="handleShowSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PropTypes from 'ant-design-vue/es/_util/vue-types'
|
||||
|
||||
export default {
|
||||
name: 'JVxePagination',
|
||||
props: {
|
||||
size: String,
|
||||
disabled: PropTypes.bool,
|
||||
pagination: PropTypes.object.def({}),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultPagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: ['10', '20', '30'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' 共 ' + total + ' 条'
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
bindProps() {
|
||||
return {
|
||||
...this.defaultPagination,
|
||||
...this.pagination,
|
||||
size: this.size === 'tiny' ? 'small' : ''
|
||||
}
|
||||
},
|
||||
boxClass() {
|
||||
return {
|
||||
'j-vxe-pagination': true,
|
||||
'show-quick-jumper': !!this.bindProps.showQuickJumper
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange(current, pageSize) {
|
||||
this.$set(this.pagination, 'current', current)
|
||||
this.$emit('change', {current, pageSize})
|
||||
},
|
||||
handleShowSizeChange(current, pageSize) {
|
||||
this.$set(this.pagination, 'pageSize', pageSize)
|
||||
this.$emit('change', {current, pageSize})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<a-popover :visible="visible" :placement="placement" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
|
||||
<div class="j-vxe-popover-title" slot="title">
|
||||
<div>子表</div>
|
||||
<div class="j-vxe-popover-title-close" @click="close">
|
||||
<a-icon type="close"/>
|
||||
</div>
|
||||
</div>
|
||||
<template slot="content">
|
||||
<transition name="fade">
|
||||
<slot v-if="visible" name="subForm" :row="row" :column="column"/>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<div ref="div" class="j-vxe-popover-div"></div>
|
||||
|
||||
</a-popover>
|
||||
</template>
|
||||
<script>
|
||||
import domAlign from 'dom-align'
|
||||
import { getParentNodeByTagName } from '../utils/vxeUtils'
|
||||
import { cloneObject, triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JVxeSubPopover',
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
// 当前行
|
||||
row: null,
|
||||
column: null,
|
||||
|
||||
overlayStyle: {
|
||||
width: null,
|
||||
zIndex: 100
|
||||
},
|
||||
placement: 'bottom'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
|
||||
toggle(event) {
|
||||
|
||||
//update-begin-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
|
||||
if(document.body.clientHeight - event.$event.clientY > 350){
|
||||
this.placement = 'bottom'
|
||||
}else{
|
||||
this.placement = 'top'
|
||||
}
|
||||
//update-end-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
|
||||
if (this.row == null) {
|
||||
this.open(event)
|
||||
} else {
|
||||
this.row.id === event.row.id ? this.close() : this.reopen(event)
|
||||
}
|
||||
},
|
||||
|
||||
open(event, level = 0) {
|
||||
if (level > 3) {
|
||||
this.$message.error('打开子表失败')
|
||||
console.warn('【JVxeSubPopover】打开子表失败')
|
||||
return
|
||||
}
|
||||
|
||||
let {row, column, $table, $event: {target}} = event
|
||||
this.row = cloneObject(row)
|
||||
this.column = column
|
||||
|
||||
let className = target.className || ''
|
||||
className = typeof className === 'string' ? className : className.toString()
|
||||
|
||||
// 点击的是expand,不做处理
|
||||
if (className.includes('vxe-table--expand-btn')) {
|
||||
return
|
||||
}
|
||||
// 点击的是checkbox,不做处理
|
||||
if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) {
|
||||
return
|
||||
}
|
||||
// 点击的是radio,不做处理
|
||||
if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) {
|
||||
return
|
||||
}
|
||||
let table = $table.$el
|
||||
let tr = getParentNodeByTagName(target, 'tr')
|
||||
if (table && tr) {
|
||||
let clientWidth = table.clientWidth
|
||||
let clientHeight = tr.clientHeight
|
||||
this.$refs.div.style.width = clientWidth + 'px'
|
||||
this.$refs.div.style.height = clientHeight + 'px'
|
||||
this.overlayStyle.width = Number.parseInt((clientWidth - clientWidth * 0.04)) + 'px'
|
||||
this.overlayStyle.maxWidth = this.overlayStyle.width
|
||||
//update-begin-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
|
||||
//let realTable = getParentNodeByTagName(tr, 'table')
|
||||
//let left = realTable.parentNode.scrollLeft
|
||||
let h = event.$event.clientY
|
||||
if(h){
|
||||
h = h-140
|
||||
}
|
||||
let toolbar = this.$refs.div.nextSibling
|
||||
domAlign(this.$refs.div, toolbar, {
|
||||
points: ['tl', 'tl'],
|
||||
offset: [0, h],
|
||||
overflow: {
|
||||
alwaysByViewport: true
|
||||
},
|
||||
})
|
||||
//update-end-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
|
||||
this.$nextTick(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
triggerWindowResizeEvent()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
let num = ++level
|
||||
console.warn('【JVxeSubPopover】table or tr 获取失败,正在进行第 ' + num + '次重试', {event, table, tr})
|
||||
window.setTimeout(() => this.open(event, num), 100)
|
||||
}
|
||||
},
|
||||
close() {
|
||||
if (this.visible) {
|
||||
this.row = null
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
reopen(event) {
|
||||
this.close()
|
||||
this.open(event)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.j-vxe-popover-title {
|
||||
.j-vxe-popover-title-close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
text-align: center;
|
||||
line-height: 31px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
cursor: pointer;
|
||||
transition: color 300ms;
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-popover-div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 31px;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.j-vxe-popover-overlay.ant-popover {
|
||||
.ant-popover-title {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div :class="boxClass">
|
||||
<!-- 工具按钮 -->
|
||||
<div class="j-vxe-tool-button div" :size="btnSize">
|
||||
<slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize"/>
|
||||
|
||||
<a-button v-if="showAdd" icon="plus" @click="trigger('add')" :disabled="disabled" type="primary">新增</a-button>
|
||||
<a-button v-if="showSave" icon="save" @click="trigger('save')" :disabled="disabled">保存</a-button>
|
||||
<template v-if="selectedRowIds.length > 0">
|
||||
<a-popconfirm
|
||||
v-if="showRemove"
|
||||
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
|
||||
@confirm="trigger('remove')"
|
||||
>
|
||||
<a-button icon="minus" :disabled="disabled">删除</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-if="showClearSelection">
|
||||
<a-button icon="delete" @click="trigger('clearSelection')">清空选择</a-button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<slot v-if="showSuffix" name="toolbarSuffix" :size="btnSize"/>
|
||||
<a v-if="showCollapse" @click="toggleCollapse" style="margin-left: 4px">
|
||||
<span>{{ collapsed ? '展开' : '收起' }}</span>
|
||||
<a-icon :type="collapsed ? 'down' : 'up'"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JVxeToolbar',
|
||||
props: {
|
||||
toolbarConfig: Object,
|
||||
excludeCode: Array,
|
||||
size: String,
|
||||
disabled: Boolean,
|
||||
disabledRows: Object,
|
||||
selectedRowIds: Array,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 是否收起
|
||||
collapsed: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
boxClass() {
|
||||
return {
|
||||
'j-vxe-toolbar': true,
|
||||
'j-vxe-toolbar-collapsed': this.collapsed,
|
||||
}
|
||||
},
|
||||
|
||||
btns() {
|
||||
let arr = this.toolbarConfig.btn || ['add', 'remove', 'clearSelection']
|
||||
let exclude = [...this.excludeCode]
|
||||
// TODO 需要将remove替换batch_delete
|
||||
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
|
||||
if(exclude.indexOf('batch_delete')>=0){
|
||||
exclude.push('remove')
|
||||
}
|
||||
// 按钮权限 需要去掉不被授权的按钮
|
||||
return arr.filter(item=>{
|
||||
return exclude.indexOf(item)<0
|
||||
})
|
||||
},
|
||||
slots() {
|
||||
return this.toolbarConfig.slot || ['prefix', 'suffix']
|
||||
},
|
||||
showPrefix() {
|
||||
return this.slots.includes('prefix')
|
||||
},
|
||||
showSuffix() {
|
||||
return this.slots.includes('suffix')
|
||||
},
|
||||
showAdd() {
|
||||
return this.btns.includes('add')
|
||||
},
|
||||
showSave() {
|
||||
return this.btns.includes('save')
|
||||
},
|
||||
showRemove() {
|
||||
return this.btns.includes('remove')
|
||||
},
|
||||
showClearSelection() {
|
||||
if (this.btns.includes('clearSelection')) {
|
||||
// 有禁用行时才显示清空选择按钮
|
||||
// 因为禁用行会阻止选择行,导致无法取消全选
|
||||
let length = Object.keys(this.disabledRows).length
|
||||
return length > 0
|
||||
}
|
||||
return false
|
||||
},
|
||||
showCollapse() {
|
||||
return this.btns.includes('collapse')
|
||||
},
|
||||
|
||||
btnSize() {
|
||||
return this.size === 'tiny' ? 'small' : null
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 触发事件 */
|
||||
trigger(name) {
|
||||
this.$emit(name)
|
||||
},
|
||||
// 切换展开收起
|
||||
toggleCollapse() {
|
||||
this.collapsed = !this.collapsed
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-vxe-toolbar-collapsed {
|
||||
[data-collapse] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-tool-button.div .ant-btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div :class="clazz" :style="boxStyle">
|
||||
<a-checkbox
|
||||
ref="checkbox"
|
||||
:checked="innerValue"
|
||||
v-bind="cellProps"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { neverNull } from '@/utils/util'
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeCheckboxCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
computed: {
|
||||
bordered() {
|
||||
return !!this.renderOptions.bordered
|
||||
},
|
||||
scrolling() {
|
||||
return !!this.renderOptions.scrolling
|
||||
},
|
||||
clazz() {
|
||||
return {
|
||||
'j-vxe-checkbox': true,
|
||||
'no-animation': this.scrolling
|
||||
}
|
||||
},
|
||||
boxStyle() {
|
||||
const style = {}
|
||||
// 如果有边框且未设置align属性,就强制居中
|
||||
if (this.bordered && !this.originColumn.align) {
|
||||
style['text-align'] = 'center'
|
||||
}
|
||||
return style
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleChange(event) {
|
||||
this.handleChangeCommon(event.target.checked)
|
||||
},
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true,
|
||||
},
|
||||
getValue(value) {
|
||||
let {own: col} = this.column
|
||||
// 处理 customValue
|
||||
if (Array.isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col)
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? customValue[0] : customValue[1]
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setValue(value) {
|
||||
let {own: col} = this.column
|
||||
// 判断是否设定了customValue(自定义值)
|
||||
if (Array.isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col)
|
||||
return neverNull(value).toString() === customValue[0].toString()
|
||||
} else {
|
||||
return !!value
|
||||
}
|
||||
},
|
||||
createValue({column}) {
|
||||
let {own: col} = column
|
||||
if (Array.isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col)
|
||||
return col.defaultChecked ? customValue[0] : customValue[1]
|
||||
} else {
|
||||
return !!col.defaultChecked
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function getCustomValue(col) {
|
||||
let customTrue = neverNull(col.customValue[0], true)
|
||||
let customFalse = neverNull(col.customValue[1], false)
|
||||
return [customTrue, customFalse]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// 关闭动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-checkbox.no-animation {
|
||||
.ant-checkbox-inner,
|
||||
.ant-checkbox-inner::after {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
ref="datePicker"
|
||||
:value="innerDateValue"
|
||||
allowClear
|
||||
:format="dateFormat"
|
||||
:showTime="isDatetime"
|
||||
dropdownClassName="j-vxe-date-picker"
|
||||
style="min-width: 0;"
|
||||
v-bind="cellProps"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable/index'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDateCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerDateValue: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDatetime() {
|
||||
return this.$type === JVXETypes.datetime
|
||||
},
|
||||
dateFormat() {
|
||||
let format = this.originColumn.format
|
||||
return format ? format : (this.isDatetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val == null || val === '') {
|
||||
this.innerDateValue = null
|
||||
} else {
|
||||
this.innerDateValue = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(mom, dateStr) {
|
||||
this.handleChangeCommon(dateStr)
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived: event => dispatchEvent(event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event)),
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input
|
||||
v-show="!departIds"
|
||||
@click="openSelect"
|
||||
placeholder="请点击选择部门"
|
||||
v-model="departNames"
|
||||
readOnly
|
||||
:disabled="componentDisabled"
|
||||
class="jvxe-select-input">
|
||||
<a-icon slot="prefix" type="cluster" title="部门选择控件"/>
|
||||
</a-input>
|
||||
<j-select-depart-modal
|
||||
ref="innerDepartSelectModal"
|
||||
:modal-width="modalWidth"
|
||||
:multi="multi"
|
||||
:rootOpened="rootOpened"
|
||||
:depart-id="departIds"
|
||||
@ok="handleOK"
|
||||
@initComp="initComp"/>
|
||||
<span style="display: inline-block;height:100%;padding-left:14px" v-if="departIds" >
|
||||
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ departNames }}</span>
|
||||
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import JSelectDepartModal from '@/components/jeecgbiz/modal/JSelectDepartModal'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDepartSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components:{
|
||||
JSelectDepartModal
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
departNames: '',
|
||||
departIds: '',
|
||||
selectedOptions: [],
|
||||
customReturnField: 'id'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
custProps() {
|
||||
const {departIds, originColumn: col, caseId, cellProps} = this
|
||||
return {
|
||||
...cellProps,
|
||||
value: departIds,
|
||||
field: col.field || col.key,
|
||||
groupId: caseId,
|
||||
class: 'jvxe-select'
|
||||
}
|
||||
},
|
||||
componentDisabled(){
|
||||
if(this.cellProps.disabled==true){
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
modalWidth(){
|
||||
if(this.cellProps.modalWidth){
|
||||
return this.cellProps.modalWidth
|
||||
}else{
|
||||
return 500
|
||||
}
|
||||
},
|
||||
multi(){
|
||||
if(this.cellProps.multi==false){
|
||||
return false
|
||||
}else{
|
||||
return true
|
||||
}
|
||||
},
|
||||
rootOpened(){
|
||||
if(this.cellProps.open==false){
|
||||
return false
|
||||
}else{
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val == null || val === '') {
|
||||
this.departIds = ''
|
||||
} else {
|
||||
this.departIds = val
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSelect(){
|
||||
this.$refs.innerDepartSelectModal.show()
|
||||
},
|
||||
handleEmpty(){
|
||||
this.handleOK('')
|
||||
},
|
||||
handleOK(rows, idstr) {
|
||||
let value = ''
|
||||
if (!rows && rows.length <= 0) {
|
||||
this.departNames = ''
|
||||
this.departIds = ''
|
||||
} else {
|
||||
value = rows.map(row => row[this.customReturnField]).join(',')
|
||||
this.departNames = rows.map(row => row['departName']).join(',')
|
||||
this.departIds = idstr
|
||||
}
|
||||
this.handleChangeCommon(this.departIds)
|
||||
},
|
||||
initComp(departNames){
|
||||
this.departNames = departNames
|
||||
},
|
||||
handleChange(value) {
|
||||
this.handleChangeCommon(value)
|
||||
}
|
||||
},
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true
|
||||
},
|
||||
translate: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .jvxe-select-input .ant-input{
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<div class="j-vxe-ds-icons">
|
||||
<a-icon type="align-left"/>
|
||||
<a-icon type="align-right"/>
|
||||
</div>
|
||||
|
||||
<!-- <div class="j-vxe-ds-btns">-->
|
||||
<!-- <a-button icon="caret-up" size="small" :disabled="disabledMoveUp" @click="handleRowMoveUp"/>-->
|
||||
<!-- <a-button icon="caret-down" size="small" :disabled="disabledMoveDown" @click="handleRowMoveDown"/>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="0" :disabled="disabledMoveUp" @click="handleRowMoveUp">向上移</a-menu-item>
|
||||
<a-menu-item key="1" :disabled="disabledMoveDown" @click="handleRowMoveDown">向下移</a-menu-item>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="3" @click="handleRowInsertDown">插入一行</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDragSortCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
// 排序结果保存字段
|
||||
dragSortKey() {
|
||||
return this.renderOptions.dragSortKey || 'orderNum'
|
||||
},
|
||||
disabledMoveUp() {
|
||||
return this.rowIndex === 0
|
||||
},
|
||||
disabledMoveDown() {
|
||||
return this.rowIndex === (this.rows.length - 1)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 向上移 */
|
||||
handleRowMoveUp(event) {
|
||||
// event.target.blur()
|
||||
if (!this.disabledMoveUp) {
|
||||
this.trigger('rowMoveUp', this.rowIndex)
|
||||
}
|
||||
},
|
||||
/** 向下移 */
|
||||
handleRowMoveDown(event) {
|
||||
// event.target.blur()
|
||||
if (!this.disabledMoveDown) {
|
||||
this.trigger('rowMoveDown', this.rowIndex)
|
||||
}
|
||||
},
|
||||
/** 插入一行 */
|
||||
handleRowInsertDown() {
|
||||
this.trigger('rowInsertDown', this.rowIndex)
|
||||
},
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
// 【功能开关】
|
||||
switches: {
|
||||
editRender: false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-vxe-ds-icons {
|
||||
position: relative;
|
||||
/*cursor: move;*/
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
|
||||
.anticon-align-left,
|
||||
.anticon-align-right {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.anticon-align-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.anticon-align-right {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-ds-btns {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 24px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
|
||||
.ant-btn {
|
||||
border: none;
|
||||
|
||||
z-index: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
/*height: 30%;*/
|
||||
height: 40%;
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
|
||||
&:hover {
|
||||
z-index: 1;
|
||||
/* height: 40%;*/
|
||||
|
||||
/* & .anticon-caret-up,*/
|
||||
/* & .anticon-caret-down {*/
|
||||
/* top: 2px;*/
|
||||
/* }*/
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
& .anticon-caret-up,
|
||||
& .anticon-caret-down {
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
top: 0;
|
||||
transition: top 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<a-input
|
||||
ref="input"
|
||||
:value="innerValue"
|
||||
v-bind="cellProps"
|
||||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
const NumberRegExp = /^-?\d+\.?\d*$/
|
||||
export default {
|
||||
name: 'JVxeInputCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
methods: {
|
||||
|
||||
/** 处理change事件 */
|
||||
handleChange(event) {
|
||||
let {$type} = this
|
||||
let {target} = event
|
||||
let {value, selectionStart} = target
|
||||
let change = true
|
||||
if ($type === JVXETypes.inputNumber) {
|
||||
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
|
||||
if (!NumberRegExp.test(value) && (value !== '' && value !== '-')) {
|
||||
change = false
|
||||
value = this.innerValue
|
||||
target.value = value || ''
|
||||
if (typeof selectionStart === 'number') {
|
||||
target.selectionStart = selectionStart - 1
|
||||
target.selectionEnd = selectionStart - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
// 触发事件,存储输入的值
|
||||
if (change) {
|
||||
this.handleChangeCommon(value)
|
||||
}
|
||||
|
||||
if ($type === JVXETypes.inputNumber) {
|
||||
// this.recalcOneStatisticsColumn(col.key)
|
||||
}
|
||||
},
|
||||
|
||||
/** 处理blur失去焦点事件 */
|
||||
handleBlur(event) {
|
||||
let {$type} = this
|
||||
let {target} = event
|
||||
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
|
||||
if ($type === JVXETypes.inputNumber) {
|
||||
if (!NumberRegExp.test(target.value)) {
|
||||
target.value = ''
|
||||
} else {
|
||||
target.value = Number.parseFloat(target.value)
|
||||
}
|
||||
this.handleChangeCommon(target.value)
|
||||
}
|
||||
|
||||
this.handleBlurCommon(target.value)
|
||||
},
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
// 自动聚焦的 class 类名
|
||||
autofocus: '.ant-input',
|
||||
},
|
||||
getValue(value) {
|
||||
if (this.$type === JVXETypes.inputNumber && typeof value === 'string') {
|
||||
if (NumberRegExp.test(value)) {
|
||||
return Number.parseFloat(value)
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<reload-effect
|
||||
:vNode="innerValue"
|
||||
:effect="reloadEffect"
|
||||
@effect-end="handleEffectEnd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ReloadEffect from './ReloadEffect'
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeNormalCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {ReloadEffect},
|
||||
computed: {
|
||||
reloadEffectRowKeysMap() {
|
||||
return this.renderOptions.reloadEffectRowKeysMap
|
||||
},
|
||||
reloadEffect() {
|
||||
return (this.renderOptions.reloadEffect && this.reloadEffectRowKeysMap[this.row.id]) === true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 特效结束
|
||||
handleEffectEnd() {
|
||||
this.$delete(this.reloadEffectRowKeysMap, this.row.id)
|
||||
},
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<a-progress
|
||||
:class="clazz"
|
||||
:percent="innerValue"
|
||||
size="small"
|
||||
v-bind="cellProps"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
// JVxe 进度条组件
|
||||
export default {
|
||||
name: 'JVxeProgressCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
clazz() {
|
||||
return {
|
||||
'j-vxe-progress': true,
|
||||
'no-animation': this.scrolling
|
||||
}
|
||||
},
|
||||
scrolling() {
|
||||
return !!this.renderOptions.scrolling
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false,
|
||||
},
|
||||
setValue(value) {
|
||||
try {
|
||||
if (typeof value !== 'number') {
|
||||
return Number.parseFloat(value)
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
// 关闭进度条的动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-progress.no-animation {
|
||||
/deep/ .ant-progress-success-bg,
|
||||
/deep/ .ant-progress-bg {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<a-select
|
||||
ref="select"
|
||||
:value="innerValue"
|
||||
allowClear
|
||||
:filterOption="handleSelectFilterOption"
|
||||
v-bind="selectProps"
|
||||
style="width: 100%;"
|
||||
@blur="handleBlur"
|
||||
@change="handleChangeCommon"
|
||||
@search="handleSearchSelect"
|
||||
>
|
||||
|
||||
<template v-for="option of originColumn.options">
|
||||
<a-select-option :key="option.value" :value="option.value" :disabled="option.disabled">
|
||||
<span>{{option.text || option.label || option.title|| option.value}}</span>
|
||||
</a-select-option>
|
||||
</template>
|
||||
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { JVXETypes } from '@comp/jeecg/JVxeTable/index'
|
||||
|
||||
export default {
|
||||
name: 'JVxeSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
selectProps() {
|
||||
let props = {...this.cellProps}
|
||||
// 判断select是否允许输入
|
||||
let {allowSearch, allowInput} = this.originColumn
|
||||
if (allowInput === true || allowSearch === true) {
|
||||
props['showSearch'] = true
|
||||
}
|
||||
return props
|
||||
},
|
||||
},
|
||||
created() {
|
||||
let multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
|
||||
let search = [JVXETypes.selectSearch, JVXETypes.sel_search]
|
||||
if (multiple.includes(this.$type)) {
|
||||
// 处理多选
|
||||
let props = this.originColumn.props || {}
|
||||
props['mode'] = 'multiple'
|
||||
props['maxTagCount'] = 1
|
||||
this.$set(this.originColumn, 'props', props)
|
||||
} else if (search.includes(this.$type)) {
|
||||
// 处理搜索
|
||||
this.$set(this.originColumn, 'allowSearch', true)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 处理blur失去焦点事件 */
|
||||
handleBlur(value) {
|
||||
let {allowInput, options} = this.originColumn
|
||||
|
||||
if (allowInput === true) {
|
||||
// 删除无用的因搜索(用户输入)而创建的项
|
||||
if (typeof value === 'string') {
|
||||
let indexes = []
|
||||
options.forEach((option, index) => {
|
||||
if (option.value.toLocaleString() === value.toLocaleString()) {
|
||||
delete option.searchAdd
|
||||
} else if (option.searchAdd === true) {
|
||||
indexes.push(index)
|
||||
}
|
||||
})
|
||||
// 翻转删除数组中的项
|
||||
for (let index of indexes.reverse()) {
|
||||
options.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handleBlurCommon(value)
|
||||
},
|
||||
|
||||
/** 用于搜索下拉框中的内容 */
|
||||
handleSelectFilterOption(input, option) {
|
||||
let {allowSearch, allowInput} = this.originColumn
|
||||
if (allowSearch === true || allowInput === true) {
|
||||
//update-begin-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
|
||||
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
//update-end-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
/** select 搜索时的事件,用于动态添加options */
|
||||
handleSearchSelect(value) {
|
||||
let {allowSearch, allowInput, options} = this.originColumn
|
||||
|
||||
if (allowSearch !== true && allowInput === true) {
|
||||
// 是否找到了对应的项,找不到则添加这一项
|
||||
let flag = false
|
||||
for (let option of options) {
|
||||
if (option.value.toLocaleString() === value.toLocaleString()) {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// !!value :不添加空值
|
||||
if (!flag && !!value) {
|
||||
// searchAdd 是否是通过搜索添加的
|
||||
options.push({title: value, value: value, searchAdd: true})
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived: event => dispatchEvent(event, 'ant-select'),
|
||||
},
|
||||
translate: {enabled: true},
|
||||
getValue(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(',')
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setValue(value) {
|
||||
let {column: {own: col}, params: {$table}} = this
|
||||
// 判断是否是多选
|
||||
if ((col.props || {})['mode'] === 'multiple') {
|
||||
$table.$set(col.props, 'maxTagCount', 1)
|
||||
}
|
||||
if (value != null && value !== '') {
|
||||
if (typeof value === 'string') {
|
||||
return value === '' ? [] : value.split(',')
|
||||
}
|
||||
return value
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
// 插槽
|
||||
export default {
|
||||
name: 'JVxeSlotCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
slotProps() {
|
||||
return {
|
||||
value: this.innerValue,
|
||||
row: this.row,
|
||||
column: this.originColumn,
|
||||
|
||||
params: this.params,
|
||||
$table: this.params.$table,
|
||||
rowId: this.params.rowid,
|
||||
index: this.params.rowIndex,
|
||||
rowIndex: this.params.rowIndex,
|
||||
columnIndex: this.params.columnIndex,
|
||||
|
||||
target: this.renderOptions.target,
|
||||
caseId: this.renderOptions.target.caseId,
|
||||
scrolling: this.renderOptions.scrolling,
|
||||
reloadEffect: this.renderOptions.reloadEffect,
|
||||
|
||||
triggerChange: (v) => this.handleChangeCommon(v),
|
||||
}
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
let {slot} = this.renderOptions
|
||||
if (slot) {
|
||||
return h('div', {}, slot(this.slotProps))
|
||||
} else {
|
||||
return h('div')
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// :isNotPass="notPassedIds.includes(col.key+row.id)"
|
||||
@@ -0,0 +1,145 @@
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
// tags 组件的显示组件
|
||||
export const TagsSpanCell = {
|
||||
name: 'JVxeTagsCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data() {
|
||||
return {
|
||||
innerTags: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (value !== this.innerTags.join(';')) {
|
||||
let rv = replaceValue(value)
|
||||
this.innerTags = rv.split(';')
|
||||
this.handleChangeCommon(rv)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
renderTags(h) {
|
||||
let tags = []
|
||||
for (let tag of this.innerTags) {
|
||||
if (tag) {
|
||||
let tagProps = {}
|
||||
let tagStyle = {}
|
||||
let setTagColor = this.originColumn.setTagColor
|
||||
if (typeof setTagColor === 'function') {
|
||||
/**
|
||||
* 设置 tag 颜色
|
||||
*
|
||||
* @param event 包含的字段:
|
||||
* event.tagValue 当前tag的值
|
||||
* event.value 当前原始值
|
||||
* event.row 当前行的所有值
|
||||
* event.column 当前列的配置
|
||||
* event.column.own 当前列的原始配置
|
||||
* @return Array | String 可以返回一个数组,数据第一项是tag背景颜色,第二项是字体颜色。也可以返回一个字符串,即tag背景颜色
|
||||
*/
|
||||
let color = setTagColor({
|
||||
tagValue: tag,
|
||||
value: this.innerValue,
|
||||
row: this.row,
|
||||
column: this.column,
|
||||
})
|
||||
if (Array.isArray(color)) {
|
||||
tagProps.color = color[0]
|
||||
tagStyle.color = color[1]
|
||||
} else if (color && typeof color === 'string') {
|
||||
tagProps.color = color
|
||||
}
|
||||
}
|
||||
tags.push(h('a-tag', {
|
||||
props: tagProps,
|
||||
style: tagStyle,
|
||||
}, [tag]))
|
||||
}
|
||||
}
|
||||
return tags
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
return h('div', {}, [
|
||||
this.renderTags(h)
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
// tags 组件的输入框
|
||||
export const TagsInputCell = {
|
||||
name: 'JVxeTagsInputCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data() {
|
||||
return {
|
||||
innerTagValue: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler(value) {
|
||||
if (value !== this.innerTagValue) {
|
||||
this.handleInputChange(value)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleInputChange(value, event) {
|
||||
this.innerTagValue = replaceValue(value, event)
|
||||
this.handleChangeCommon(this.innerTagValue)
|
||||
return this.innerTagValue
|
||||
},
|
||||
|
||||
},
|
||||
render(h) {
|
||||
return h('a-input', {
|
||||
props: {
|
||||
value: this.innerValue,
|
||||
...this.cellProps
|
||||
},
|
||||
on: {
|
||||
change: (event) => {
|
||||
let {target, target: {value}} = event
|
||||
let newValue = this.handleInputChange(value, event)
|
||||
if (newValue !== value) {
|
||||
target.value = newValue
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// 将值每隔两位加上一个分号
|
||||
function replaceValue(value, event) {
|
||||
if (value) {
|
||||
// 首先去掉现有的分号
|
||||
value = value.replace(/;/g, '')
|
||||
// 然后再遍历添加分号
|
||||
let rv = ''
|
||||
let splitArr = value.split('')
|
||||
let count = 0
|
||||
splitArr.forEach((val, index) => {
|
||||
rv += val
|
||||
let position = index + 1
|
||||
if (position % 2 === 0 && position < splitArr.length) {
|
||||
count++
|
||||
rv += ';'
|
||||
}
|
||||
})
|
||||
if (event && count > 0) {
|
||||
let {target, target: {selectionStart}} = event
|
||||
target.selectionStart = selectionStart + count
|
||||
target.selectionEnd = selectionStart + count
|
||||
}
|
||||
return rv
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<j-input-pop
|
||||
:value="innerValue"
|
||||
:width="300"
|
||||
:height="210"
|
||||
v-bind="cellProps"
|
||||
style="width: 100%;"
|
||||
@change="handleChangeCommon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JInputPop from '@/components/jeecg/minipop/JInputPop'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeTextareaCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {JInputPop},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
autofocus: '.ant-input',
|
||||
},
|
||||
aopEvents: {
|
||||
editActived: event => dispatchEvent(event, 'anticon-fullscreen'),
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
|
||||
<a-input
|
||||
:key="fileKey"
|
||||
:readOnly="true"
|
||||
:value="file.name"
|
||||
>
|
||||
|
||||
<template slot="addonBefore" style="width: 30px">
|
||||
<a-tooltip v-if="file.status === 'uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
|
||||
<a-icon type="loading"/>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else-if="file.status === 'done'" title="上传完成">
|
||||
<a-icon type="check-circle" style="color:#00DB00;"/>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else title="上传失败">
|
||||
<a-icon type="exclamation-circle" style="color:red;"/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<span v-if="file.status === 'uploading'" slot="addonAfter">{{ Math.floor(file.percent) }}%</span>
|
||||
<template v-else-if="originColumn.allowDownload !== false || originColumn.allowRemove !== false" slot="addonAfter">
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
<a-tooltip title="操作">
|
||||
<a-icon
|
||||
type="setting"
|
||||
style="cursor: pointer;"/>
|
||||
</a-tooltip>
|
||||
|
||||
<a-menu slot="overlay">
|
||||
<!-- <a-menu-item @click="handleClickPreviewFile">-->
|
||||
<!-- <span><a-icon type="eye"/> 预览</span>-->
|
||||
<!-- </a-menu-item>-->
|
||||
<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>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
</a-input>
|
||||
</template>
|
||||
<a-upload
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{'isup': 1}"
|
||||
:multiple="false"
|
||||
:action="originColumn.action"
|
||||
:headers="uploadHeaders"
|
||||
:showUploadList="false"
|
||||
v-bind="cellProps"
|
||||
@change="handleChangeUpload"
|
||||
>
|
||||
<a-button icon="upload">{{originColumn.btnText || '点击上传'}}</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl } from '@api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JVxeUploadCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerFile: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders() {
|
||||
let {originColumn: col} = this
|
||||
let headers = {}
|
||||
if (col.token === true) {
|
||||
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
|
||||
hasFile() {
|
||||
return this.innerFile != null
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (this.innerValue) {
|
||||
this.innerFile = this.innerValue
|
||||
} else {
|
||||
this.innerFile = null
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleChangeUpload(info) {
|
||||
let {row, originColumn: col} = this
|
||||
let {file} = info
|
||||
let value = {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
status: file.status,
|
||||
percent: file.percent
|
||||
}
|
||||
if (col.responseName && file.response) {
|
||||
value['responseName'] = file.response[col.responseName]
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
value['path'] = file.response[col.responseName]
|
||||
this.handleChangeCommon(value)
|
||||
} else if (file.status === 'error') {
|
||||
value['message'] = file.response.message || '未知错误'
|
||||
}
|
||||
this.innerFile = value
|
||||
},
|
||||
|
||||
// handleClickPreviewFile(id) {
|
||||
// this.$message.info('尚未实现')
|
||||
// },
|
||||
|
||||
handleClickDownloadFile(id) {
|
||||
let {path} = this.value || {}
|
||||
if (path) {
|
||||
let url = getFileAccessHttpUrl(path)
|
||||
window.open(url)
|
||||
}
|
||||
},
|
||||
|
||||
handleClickDeleteFile() {
|
||||
this.handleChangeCommon(null)
|
||||
},
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {visible: true},
|
||||
getValue: value => fileGetValue(value),
|
||||
setValue: value => fileSetValue(value),
|
||||
}
|
||||
}
|
||||
|
||||
function fileGetValue(value) {
|
||||
if (value && value.path) {
|
||||
return value.path
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function fileSetValue(value) {
|
||||
if (value) {
|
||||
let first = value.split(',')[0]
|
||||
let name = first.substring(first.lastIndexOf('/') + 1)
|
||||
return {
|
||||
name: name,
|
||||
path: value,
|
||||
status: 'done',
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input
|
||||
v-show="!userIds"
|
||||
@click="openSelect"
|
||||
placeholder="请选择用户"
|
||||
v-model="userNames"
|
||||
readOnly
|
||||
class="jvxe-select-input"
|
||||
:disabled="componentDisabled">
|
||||
<a-icon slot="prefix" type="user" title="用户选择控件"/>
|
||||
</a-input>
|
||||
<j-select-user-by-dep-modal
|
||||
ref="selectModal"
|
||||
:modal-width="modalWidth"
|
||||
:multi="multi"
|
||||
:user-ids="userIds"
|
||||
@ok="selectOK"
|
||||
@initComp="initComp"/>
|
||||
<span style="display: inline-block;height:100%;padding-left:14px" v-if="userIds" >
|
||||
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ userNames }}</span>
|
||||
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- <j-select-user-by-dep
|
||||
v-bind="custProps"
|
||||
@change="handleChange"
|
||||
:trigger-change="true">
|
||||
</j-select-user-by-dep>-->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import JSelectUserByDepModal from '@/components/jeecgbiz/modal/JSelectUserByDepModal'
|
||||
|
||||
export default {
|
||||
name: 'JVxeUserSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: { JSelectUserByDepModal },
|
||||
data() {
|
||||
return {
|
||||
userIds:'',
|
||||
userNames:'',
|
||||
innerUserValue: '',
|
||||
selectedOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
custProps() {
|
||||
const {userIds, originColumn: col, caseId, cellProps} = this
|
||||
return {
|
||||
...cellProps,
|
||||
value: userIds,
|
||||
field: col.field || col.key,
|
||||
groupId: caseId,
|
||||
class: 'jvxe-select'
|
||||
}
|
||||
},
|
||||
componentDisabled(){
|
||||
console.log('333',this.cellProps)
|
||||
if(this.cellProps.disabled==true){
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
modalWidth(){
|
||||
if(this.cellProps.modalWidth){
|
||||
return this.cellProps.modalWidth
|
||||
}else{
|
||||
return 1250
|
||||
}
|
||||
},
|
||||
multi(){
|
||||
if(this.cellProps.multi==false){
|
||||
return false
|
||||
}else{
|
||||
return true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
if (val == null || val === '') {
|
||||
this.userIds = ''
|
||||
} else {
|
||||
this.userIds = val
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSelect() {
|
||||
this.$refs.selectModal.showModal()
|
||||
},
|
||||
selectOK(rows, idstr) {
|
||||
console.log("当前选中用户", rows)
|
||||
console.log("当前选中用户ID", idstr)
|
||||
if (!rows) {
|
||||
this.userNames = ''
|
||||
this.userIds = ''
|
||||
} else {
|
||||
let temp = ''
|
||||
for (let item of rows) {
|
||||
temp += ',' + item.realname
|
||||
}
|
||||
this.userNames = temp.substring(1)
|
||||
this.userIds = idstr
|
||||
}
|
||||
this.handleChangeCommon(this.userIds)
|
||||
},
|
||||
handleEmpty(){
|
||||
this.selectOK('')
|
||||
},
|
||||
initComp(userNames) {
|
||||
this.userNames = userNames
|
||||
},
|
||||
},
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true
|
||||
},
|
||||
translate: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .jvxe-select-input .ant-input {
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
import '../../less/reload-effect.less'
|
||||
import { randomString } from '@/utils/util'
|
||||
|
||||
// 修改数据特效
|
||||
export default {
|
||||
props: {
|
||||
vNode: null,
|
||||
// 是否启用特效
|
||||
effect: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// vNode: null,
|
||||
innerEffect: false,
|
||||
// 应付同时多个特效
|
||||
effectIdx: 0,
|
||||
effectList: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
vNode: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler(vNode, old) {
|
||||
this.innerEffect = this.effect
|
||||
if (this.innerEffect && old != null) {
|
||||
let topLayer = this.renderSpan(old, 'top')
|
||||
this.effectList.push(topLayer)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 条件渲染内容 span
|
||||
renderVNode() {
|
||||
if (this.vNode == null) {
|
||||
return null
|
||||
}
|
||||
let bottom = this.renderSpan(this.vNode, 'bottom')
|
||||
// 启用了特效,并且有旧数据,就渲染特效顶层
|
||||
if (this.innerEffect && this.effectList.length > 0) {
|
||||
this.$emit('effect-begin')
|
||||
// 1.4s 以后关闭特效
|
||||
window.setTimeout(() => {
|
||||
let item = this.effectList[this.effectIdx]
|
||||
if (item && item.elm) {
|
||||
// 特效结束后,展示先把 display 设为 none,而不是直接删掉该元素,
|
||||
// 目的是为了防止页面重新渲染,导致动画重置
|
||||
item.elm.style.display = 'none'
|
||||
}
|
||||
// 当所有的层级动画都结束时,再删掉所有元素
|
||||
if (++this.effectIdx === this.effectList.length) {
|
||||
this.innerEffect = false
|
||||
this.effectIdx = 0
|
||||
this.effectList = []
|
||||
this.$emit('effect-end')
|
||||
}
|
||||
}, 1400)
|
||||
return [this.effectList, bottom]
|
||||
} else {
|
||||
return bottom
|
||||
}
|
||||
},
|
||||
// 渲染内容 span
|
||||
renderSpan(vNode, layer) {
|
||||
let options = {
|
||||
key: layer + this.effectIdx + randomString(6),
|
||||
class: ['j-vxe-reload-effect-span', `layer-${layer}`],
|
||||
style: {},
|
||||
}
|
||||
if (layer === 'top') {
|
||||
// 最新渲染的在下面
|
||||
options.style['z-index'] = (9999 - this.effectIdx)
|
||||
}
|
||||
return this.$createElement('span', options, [vNode])
|
||||
},
|
||||
},
|
||||
render(h) {
|
||||
return h('div', {
|
||||
class: ['j-vxe-reload-effect-box'],
|
||||
}, [this.renderVNode()])
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as jvxeTypes from './jvxeTypes'
|
||||
import { installCell, mapCell } from './install'
|
||||
import JVxeTable from './components/JVxeTable'
|
||||
|
||||
import JVxeSlotCell from './components/cells/JVxeSlotCell'
|
||||
import JVxeNormalCell from './components/cells/JVxeNormalCell'
|
||||
import JVxeInputCell from './components/cells/JVxeInputCell'
|
||||
import JVxeDateCell from './components/cells/JVxeDateCell'
|
||||
import JVxeSelectCell from './components/cells/JVxeSelectCell'
|
||||
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell'
|
||||
import JVxeUploadCell from './components/cells/JVxeUploadCell'
|
||||
import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell'
|
||||
import JVxeProgressCell from './components/cells/JVxeProgressCell'
|
||||
import JVxeTextareaCell from './components/cells/JVxeTextareaCell'
|
||||
import JVxeDragSortCell from './components/cells/JVxeDragSortCell'
|
||||
import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell'
|
||||
import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell'
|
||||
|
||||
//update--begin--autor:lvdandan-----date:20201216------for:JVxeTable--JVXETypes 【online】代码结构调整,便于online打包
|
||||
// 组件类型
|
||||
export const JVXETypes = jvxeTypes.JVXETypes
|
||||
//update--end--autor:lvdandan-----date:20201216------for:JVxeTable--JVXETypes 【online】代码结构调整,便于online打包
|
||||
|
||||
// 注册自定义组件
|
||||
export const AllCells = {
|
||||
...mapCell(JVXETypes.normal, JVxeNormalCell),
|
||||
...mapCell(JVXETypes.input, JVxeInputCell),
|
||||
...mapCell(JVXETypes.inputNumber, JVxeInputCell),
|
||||
...mapCell(JVXETypes.checkbox, JVxeCheckboxCell),
|
||||
...mapCell(JVXETypes.select, JVxeSelectCell),
|
||||
...mapCell(JVXETypes.selectSearch, JVxeSelectCell), // 下拉搜索
|
||||
...mapCell(JVXETypes.selectMultiple, JVxeSelectCell), // 下拉多选
|
||||
...mapCell(JVXETypes.date, JVxeDateCell),
|
||||
...mapCell(JVXETypes.datetime, JVxeDateCell),
|
||||
...mapCell(JVXETypes.upload, JVxeUploadCell),
|
||||
...mapCell(JVXETypes.textarea, JVxeTextareaCell),
|
||||
|
||||
...mapCell(JVXETypes.tags, TagsInputCell, TagsSpanCell),
|
||||
...mapCell(JVXETypes.progress, JVxeProgressCell),
|
||||
|
||||
...mapCell(JVXETypes.rowDragSort, JVxeDragSortCell),
|
||||
...mapCell(JVXETypes.slot, JVxeSlotCell),
|
||||
...mapCell(JVXETypes.departSelect, JVxeDepartSelectCell),
|
||||
...mapCell(JVXETypes.userSelect, JVxeUserSelectCell)
|
||||
|
||||
/* hidden 是特殊的组件,不在这里注册 */
|
||||
}
|
||||
|
||||
export { installCell, mapCell }
|
||||
|
||||
export default JVxeTable
|
||||
@@ -0,0 +1,105 @@
|
||||
import Vue from 'vue'
|
||||
import { getEventPath } from '@/utils/util'
|
||||
import JVxeTable, { AllCells, JVXETypes } from './index'
|
||||
import './less/j-vxe-table.less'
|
||||
// 引入 vxe-table
|
||||
import 'xe-utils'
|
||||
import VXETable, { Grid } from 'vxe-table'
|
||||
import VXETablePluginAntd from 'vxe-table-plugin-antd'
|
||||
import 'vxe-table/lib/index.css'
|
||||
import 'vxe-table-plugin-antd/dist/style.css'
|
||||
import { getEnhancedMixins, installAllCell, installOneCell } from '@/components/jeecg/JVxeTable/utils/cellUtils'
|
||||
|
||||
// VxeGrid所有的方法映射
|
||||
const VxeGridMethodsMap = {}
|
||||
Object.keys(Grid.methods).forEach(key => {
|
||||
// 使用eval可以避免闭包(但是要注意不要写es6的代码)
|
||||
VxeGridMethodsMap[key] = eval(`(function(){return this.$refs.vxe.${key}.apply(this.$refs.vxe,arguments)})`)
|
||||
})
|
||||
// 将Grid所有的方法都映射(继承)到JVxeTable上
|
||||
JVxeTable.methods = Object.assign({}, VxeGridMethodsMap, JVxeTable.methods)
|
||||
|
||||
// VXETable 全局配置
|
||||
const VXETableSettings = {
|
||||
// z-index 起始值
|
||||
zIndex: 1000,
|
||||
table: {
|
||||
validConfig: {
|
||||
// 校验提示方式:强制使用tooltip
|
||||
message: 'tooltip'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行注册方法
|
||||
Vue.use(VXETable, VXETableSettings)
|
||||
VXETable.use(VXETablePluginAntd)
|
||||
Vue.component(JVxeTable.name, JVxeTable)
|
||||
|
||||
// 注册自定义组件
|
||||
installAllCell(VXETable)
|
||||
|
||||
// 添加事件拦截器 event.clearActived
|
||||
// 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
|
||||
VXETable.interceptor.add('event.clearActived', function (params, event, target) {
|
||||
// 获取组件增强
|
||||
let col = params.column.own
|
||||
const interceptor = getEnhancedMixins(col.$type, 'interceptor')
|
||||
// 执行增强
|
||||
let flag = interceptor['event.clearActived'].apply(this, arguments)
|
||||
if (flag === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
let path = getEventPath(event)
|
||||
for (let p of path) {
|
||||
let className = p.className || ''
|
||||
className = typeof className === 'string' ? className : className.toString()
|
||||
|
||||
/* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */
|
||||
|
||||
// 点击的标签是JInputPop
|
||||
if (className.includes('j-input-pop')) {
|
||||
return false
|
||||
}
|
||||
// 点击的标签是JPopup的弹出层、部门选择、用户选择
|
||||
if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) {
|
||||
return false
|
||||
}
|
||||
// 执行增强
|
||||
let flag = interceptor['event.clearActived.className'].apply(this, [className, ...arguments])
|
||||
if (flag === false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 注册map
|
||||
* @param type 类型
|
||||
* @param cell 输入组件
|
||||
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
|
||||
*/
|
||||
export function mapCell(type, cell, span) {
|
||||
let cells = {[type]: cell}
|
||||
if (span) {
|
||||
cells[type + ':span'] = span
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册自定义组件
|
||||
*
|
||||
* @param type 类型
|
||||
* @param cell 输入组件
|
||||
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
|
||||
*/
|
||||
export function installCell(type, cell, span) {
|
||||
let exclude = [JVXETypes.rowNumber, JVXETypes.rowCheckbox, JVXETypes.rowRadio, JVXETypes.rowExpand, JVXETypes.rowDragSort]
|
||||
if (exclude.includes(type)) {
|
||||
throw new Error(`【installCell】不能使用"${type}"作为组件的type,因为这是关键字。`)
|
||||
}
|
||||
Object.assign(AllCells, mapCell(type, cell, span))
|
||||
installOneCell(VXETable, type)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// 组件类型
|
||||
export default JVXETypes
|
||||
export const JVXETypes = {
|
||||
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
|
||||
// 前缀是自动加的,代码中直接用就行(JVXETypes.input)
|
||||
_prefix: 'j-',
|
||||
|
||||
// 行号列
|
||||
rowNumber: 'row-number',
|
||||
// 选择列
|
||||
rowCheckbox: 'row-checkbox',
|
||||
// 单选列
|
||||
rowRadio: 'row-radio',
|
||||
// 展开列
|
||||
rowExpand: 'row-expand',
|
||||
// 上下排序
|
||||
rowDragSort: 'row-drag-sort',
|
||||
|
||||
input: 'input',
|
||||
inputNumber: 'inputNumber',
|
||||
textarea: 'textarea',
|
||||
select: 'select',
|
||||
date: 'date',
|
||||
datetime: 'datetime',
|
||||
checkbox: 'checkbox',
|
||||
upload: 'upload',
|
||||
// 下拉搜索
|
||||
selectSearch: 'select-search',
|
||||
// 下拉多选
|
||||
selectMultiple: 'select-multiple',
|
||||
// 进度条
|
||||
progress: 'progress',
|
||||
//部门选择
|
||||
departSelect: 'sel_depart',
|
||||
//用户选择
|
||||
userSelect: 'sel_user',
|
||||
|
||||
// 拖轮Tags(暂无用)
|
||||
tags: 'tags',
|
||||
|
||||
slot: 'slot',
|
||||
normal: 'normal',
|
||||
hidden: 'hidden',
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user