Initial commit
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<tooltip v-if="tips !== ''">
|
||||
<template slot="title">{{ tips }}</template>
|
||||
<avatar :size="avatarSize" :src="src" />
|
||||
</tooltip>
|
||||
<avatar v-else :size="avatarSize" :src="src" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Avatar from 'ant-design-vue/es/avatar'
|
||||
import Tooltip from 'ant-design-vue/es/tooltip'
|
||||
|
||||
export default {
|
||||
name: 'AvatarItem',
|
||||
components: {
|
||||
Avatar,
|
||||
Tooltip
|
||||
},
|
||||
props: {
|
||||
tips: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
size: this.$parent.size
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatarSize () {
|
||||
return ((this.size !== 'mini') && this.size) || 20
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$parent.size' (val) {
|
||||
this.size = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,98 @@
|
||||
<!--
|
||||
<template>
|
||||
<div :class="[prefixCls]">
|
||||
<ul>
|
||||
<slot></slot>
|
||||
<template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
|
||||
|
||||
<template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
|
||||
<avatar-item :size="size">
|
||||
<avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
|
||||
</avatar-item>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
-->
|
||||
|
||||
<script>
|
||||
import Avatar from 'ant-design-vue/es/avatar'
|
||||
import AvatarItem from './Item'
|
||||
import { filterEmpty } from '@/components/_util/util'
|
||||
|
||||
export default {
|
||||
AvatarItem,
|
||||
name: 'AvatarList',
|
||||
components: {
|
||||
Avatar,
|
||||
AvatarItem
|
||||
},
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-avatar-list'
|
||||
},
|
||||
/**
|
||||
* 头像大小 类型: large、small 、mini, default
|
||||
* 默认值: default
|
||||
*/
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: 'default'
|
||||
},
|
||||
/**
|
||||
* 要显示的最大项目
|
||||
*/
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 多余的项目风格
|
||||
*/
|
||||
excessItemsStyle: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
color: '#f56a00',
|
||||
backgroundColor: '#fde3cf'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
getItems (items) {
|
||||
const classString = {
|
||||
[`${this.prefixCls}-item`]: true,
|
||||
[`${this.size}`]: true
|
||||
}
|
||||
|
||||
if (this.maxLength > 0) {
|
||||
items = items.slice(0, this.maxLength)
|
||||
items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
|
||||
}
|
||||
return items.map((item) => (
|
||||
<li class={ classString }>{ item }</li>
|
||||
))
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { prefixCls, size } = this.$props
|
||||
const classString = {
|
||||
[`${prefixCls}`]: true,
|
||||
[`${size}`]: true
|
||||
}
|
||||
const items = filterEmpty(this.$slots.default)
|
||||
const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{ this.getItems(items) }</ul> : null
|
||||
|
||||
return (
|
||||
<div class={ classString }>
|
||||
{ itemsDom }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,4 @@
|
||||
import AvatarList from './List'
|
||||
import './index.less'
|
||||
|
||||
export default AvatarList
|
||||
@@ -0,0 +1,60 @@
|
||||
@import "../index";
|
||||
|
||||
@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
|
||||
@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
|
||||
|
||||
.@{avatar-list-prefix-cls} {
|
||||
display: inline-block;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 0 0 0 8px;
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.@{avatar-list-item-prefix-cls} {
|
||||
display: inline-block;
|
||||
font-size: @font-size-base;
|
||||
margin-left: -8px;
|
||||
width: @avatar-size-base;
|
||||
height: @avatar-size-base;
|
||||
|
||||
:global {
|
||||
.ant-avatar {
|
||||
border: 1px solid #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
width: @avatar-size-lg;
|
||||
height: @avatar-size-lg;
|
||||
}
|
||||
|
||||
&.small {
|
||||
width: @avatar-size-sm;
|
||||
height: @avatar-size-sm;
|
||||
}
|
||||
|
||||
&.mini {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
:global {
|
||||
.ant-avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
|
||||
.ant-avatar-string {
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<a-card :loading="loading" :body-style="{ padding: '20px 24px 8px' }" :bordered="false">
|
||||
<div class="chart-card-header">
|
||||
<div class="meta">
|
||||
<span class="chart-card-title">{{ title }}</span>
|
||||
<span class="chart-card-action">
|
||||
<slot name="action"></slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="total"><span>{{ total }}</span></div>
|
||||
</div>
|
||||
<div class="chart-card-content">
|
||||
<div class="content-fix">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-card-footer">
|
||||
<div class="field">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ChartCard',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
total: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-card-header {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
.meta {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: rgba(0, 0, 0, .45);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-action {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.chart-card-footer {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding-top: 9px;
|
||||
margin-top: 8px;
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.field {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-card-content {
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
height: 46px;
|
||||
width: 100%;
|
||||
|
||||
.content-fix {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 0;
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<span>
|
||||
{{ lastTime | format }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
function fixedZero (val) {
|
||||
return val * 1 < 10 ? `0${val}` : val
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'CountDown',
|
||||
props: {
|
||||
format: {
|
||||
type: Function,
|
||||
default: undefined
|
||||
},
|
||||
target: {
|
||||
type: [Date, Number],
|
||||
required: true
|
||||
},
|
||||
onEnd: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dateTime: '0',
|
||||
originTargetTime: 0,
|
||||
lastTime: 0,
|
||||
timer: 0,
|
||||
interval: 1000
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
format (time) {
|
||||
const hours = 60 * 60 * 1000
|
||||
const minutes = 60 * 1000
|
||||
|
||||
const h = Math.floor(time / hours)
|
||||
const m = Math.floor((time - h * hours) / minutes)
|
||||
const s = Math.floor((time - h * hours - m * minutes) / 1000)
|
||||
return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initTime()
|
||||
this.tick()
|
||||
},
|
||||
methods: {
|
||||
initTime () {
|
||||
let lastTime = 0
|
||||
let targetTime = 0
|
||||
this.originTargetTime = this.target
|
||||
try {
|
||||
if (Object.prototype.toString.call(this.target) === '[object Date]') {
|
||||
targetTime = this.target
|
||||
} else {
|
||||
targetTime = new Date(this.target).getTime()
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('invalid target prop')
|
||||
}
|
||||
|
||||
lastTime = targetTime - new Date().getTime()
|
||||
|
||||
this.lastTime = lastTime < 0 ? 0 : lastTime
|
||||
},
|
||||
tick () {
|
||||
const { onEnd } = this
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
if (this.lastTime < this.interval) {
|
||||
clearTimeout(this.timer)
|
||||
this.lastTime = 0
|
||||
if (typeof onEnd === 'function') {
|
||||
onEnd()
|
||||
}
|
||||
} else {
|
||||
this.lastTime -= this.interval
|
||||
this.tick()
|
||||
}
|
||||
}, this.interval)
|
||||
}
|
||||
},
|
||||
beforeUpdate () {
|
||||
if (this.originTargetTime !== this.target) {
|
||||
this.initTime()
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import CountDown from './CountDown'
|
||||
|
||||
export default CountDown
|
||||
@@ -0,0 +1,49 @@
|
||||
<script>
|
||||
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/StringUtil'
|
||||
|
||||
export default {
|
||||
name: 'Ellipsis',
|
||||
props: {
|
||||
prefixCls: {
|
||||
type: String,
|
||||
default: 'ant-pro-ellipsis'
|
||||
},
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
default: 25
|
||||
},
|
||||
lines: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
fullWidthRecognition: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
render () {
|
||||
const { tooltip, length } = this.$props
|
||||
let text = ''
|
||||
// 处理没有default插槽时的特殊情况
|
||||
if (this.$slots.default) {
|
||||
text = this.$slots.default.map(vNode => vNode.text).join('')
|
||||
}
|
||||
// 判断是否显示 tooltip
|
||||
if (tooltip && getStrFullLength(text) > length) {
|
||||
return (
|
||||
<a-tooltip>
|
||||
<template slot="title">{text}</template>
|
||||
<span>{cutStrByFullLength(text, this.length) + '…'}</span>
|
||||
</a-tooltip>
|
||||
)
|
||||
} else {
|
||||
return (<span>{text}</span>)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
import Ellipsis from './Ellipsis'
|
||||
|
||||
export default Ellipsis
|
||||
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="fileList && fileList.length > 0">
|
||||
<div class="file" :class="{'operate-right': operateFixedRight}" v-for="file in fileList" :key="file.id">
|
||||
<div class="file-info" :class="{'can-download-file-info': canDownload}">
|
||||
<a-icon type="link" class="file-info-icon" />
|
||||
<div class="file-name" @click="handlePreview(file)">{{ file.fileName }}</div>
|
||||
</div>
|
||||
<div class="file-operate">
|
||||
<!-- 下载-->
|
||||
<a-button type="link" icon="download" @click="handleDownload(file)" v-if="canDownload">{{ $t('download') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileInfo } from '../api/api.js'
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '../store/mutation-types.js'
|
||||
import { downloadFile, getFileAccessHttpUrl } from '../api/manage.js'
|
||||
import { previewPdf } from '../utils/previewPdf.js'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { kkFilePreview } from '../utils/kkFilePreview'
|
||||
|
||||
// 支持预览的文件类型
|
||||
const CAN_PREVIEW_FILE_TYPE = ['pdf', 'image']
|
||||
// 支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png', 'raw']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
export default {
|
||||
name: 'FileEcho',
|
||||
props: {
|
||||
// 文件id
|
||||
fileIds: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 操作按钮固定在右侧
|
||||
operateFixedRight: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
// 是否可以下载
|
||||
canDownload: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fileIds: {
|
||||
handler (value) {
|
||||
if (value) {
|
||||
this.getFileList()
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
fileList: [],
|
||||
image: false,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFileList () {
|
||||
const fileIdsList = this.fileIds.split(',')
|
||||
for (let i = 0; i < fileIdsList.length; i++) {
|
||||
const id = fileIdsList[i]
|
||||
getFileInfo({ id }).then(res => {
|
||||
if (res.success) {
|
||||
this.fileList.push(res.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = file.fileName ? file.fileName.split('.')[file.fileName.split('.').length - 1] : ''
|
||||
const canPreview = CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix.toLowerCase() === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (!canPreview) {
|
||||
this.$message.loading('该文件类型不支持预览,正在为您准备下载...').then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/view/${file.id}?at=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.fileName}`
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (canPreview && FILE_TYPE_IMGS.includes(fileSuffix.toLowerCase())) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (canPreview && FILE_TYPE_PDF.includes(fileSuffix.toLowerCase())) {
|
||||
const url = previewPdf(file.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
kkFilePreview(fileFullUrl)
|
||||
},
|
||||
/**
|
||||
* 单个文件的下载功能
|
||||
* @param file
|
||||
*/
|
||||
handleDownload (file) {
|
||||
// 下载文件
|
||||
downloadFile(`/sys/common/download/${file.id}`, file.fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.operate-right {
|
||||
justify-content: space-between;
|
||||
|
||||
.file-info {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
|
||||
&-icon {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.file-name {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: @primary-color;
|
||||
cursor: pointer;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.file-info > .anticon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.can-download-file-info {
|
||||
max-width: calc(100% - 97px);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.file-operate {
|
||||
.ant-btn > .anticon + span, .ant-btn > .iconfont + span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.ant-btn:first-child {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,360 @@
|
||||
<template>
|
||||
<div :id="domId" class="chart-dom"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import insertCss from 'insert-css'
|
||||
import G6 from '@antv/g6'
|
||||
import DeleteIcon from '../assets/image/iconDelete.svg'
|
||||
|
||||
// 流程图用到的属性
|
||||
const ChartAttrs = {
|
||||
nodeStrokeColor: '#739FC8', // 节点边框颜色
|
||||
nodeFillColor: '#EEF4F8', // 节点背景颜色
|
||||
nodeTextColor: '#227CCF', // 节点文字颜色
|
||||
nodeWidth: 210, // 节点宽度
|
||||
nodeHeight: 40, // 节点高度
|
||||
ellipseLength: 12, // 文本显示最大长度
|
||||
returnLineSpacing: 30, // 文本左右绕的加宽
|
||||
edgeColor: '#86909c', // 线的颜色
|
||||
edgeWidth: 1,
|
||||
btnColor: '#1f1f1f', // 按钮颜色
|
||||
btnSize: 20, // 按钮的宽高
|
||||
btnPadding: 2 // 按钮边框和内部图标的距离
|
||||
}
|
||||
|
||||
// 添加图标
|
||||
const AddIcon = function EXPAND_ICON (x, y, r) {
|
||||
return [['M', x + 2, y], ['L', x + 2 * r - 2, y], ['M', x + r, y - r + 2], ['L', x + r, y + r - 2]]
|
||||
}
|
||||
|
||||
// 处理图的tooltip样式
|
||||
insertCss(`
|
||||
.g6-tooltip {
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
padding: 2px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
`)
|
||||
|
||||
export default {
|
||||
name: 'FlowChart',
|
||||
props: {
|
||||
// 元素的id
|
||||
domId: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
// 流程图用到的数据
|
||||
chartData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否仅查看
|
||||
isViewOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isLoadEnd: false, // 页面是否加载完成
|
||||
graph: null,
|
||||
observer: null // 图形容器大小变化监听器
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chartData: {
|
||||
handler () {
|
||||
if (this.isLoadEnd) {
|
||||
if (this.graph) {
|
||||
this.graph.destroy()
|
||||
this.graph = null
|
||||
this.initChart()
|
||||
} else {
|
||||
this.initChart()
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
isLoadEnd () {
|
||||
if (this.isLoadEnd && Object.keys(this.chartData).length > 0) {
|
||||
this.initChart()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.isLoadEnd = true
|
||||
const dom = document.getElementById(this.domId)
|
||||
this.observer = new ResizeObserver(this.handleResize)
|
||||
this.observer.observe(dom, { box: 'border-box' })
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
initChart () {
|
||||
const canOperate = !this.isViewOnly
|
||||
// 自定义节点
|
||||
G6.registerNode('default-node', {
|
||||
drawShape (cfg, group) {
|
||||
const rect = group.addShape('rect', {
|
||||
attrs: {
|
||||
x: -(ChartAttrs.nodeWidth / 2),
|
||||
y: -(ChartAttrs.nodeHeight / 2),
|
||||
width: ChartAttrs.nodeWidth,
|
||||
height: ChartAttrs.nodeHeight,
|
||||
radius: ChartAttrs.nodeHeight / 2,
|
||||
fill: ChartAttrs.nodeFillColor,
|
||||
stroke: ChartAttrs.nodeStrokeColor
|
||||
},
|
||||
name: 'default-node-box'
|
||||
})
|
||||
// 处理文本显示及文本超长
|
||||
if (cfg.name) {
|
||||
const text = cfg.name.length > ChartAttrs.ellipseLength ? cfg.name.substring(0, ChartAttrs.ellipseLength) + '...' : cfg.name
|
||||
group.addShape('text', {
|
||||
attrs: {
|
||||
text,
|
||||
fill: ChartAttrs.nodeTextColor,
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
name: 'default-node-text'
|
||||
})
|
||||
}
|
||||
// 可以添加,添加虽然在线上,但代码要写在自定义点里,不然获取不到节点id
|
||||
if (cfg.isApprovalNode === 1 && canOperate) {
|
||||
group.addShape('rect', {
|
||||
attrs: {
|
||||
x: -(ChartAttrs.btnSize / 2),
|
||||
y: 1.5 * ChartAttrs.btnSize,
|
||||
width: ChartAttrs.btnSize,
|
||||
height: ChartAttrs.btnSize,
|
||||
radius: 4,
|
||||
symbol: AddIcon,
|
||||
fill: 'white',
|
||||
cursor: 'pointer',
|
||||
stroke: ChartAttrs.btnColor,
|
||||
lineWidth: 1
|
||||
},
|
||||
name: 'add-btn'
|
||||
})
|
||||
group.addShape('marker', {
|
||||
attrs: {
|
||||
x: -(ChartAttrs.btnSize / 2) + ChartAttrs.btnPadding,
|
||||
y: 2 * ChartAttrs.btnSize,
|
||||
r: (ChartAttrs.btnSize / 2) - ChartAttrs.btnPadding,
|
||||
symbol: AddIcon,
|
||||
stroke: ChartAttrs.btnColor,
|
||||
fill: 'white',
|
||||
cursor: 'pointer',
|
||||
lineWidth: 1
|
||||
},
|
||||
name: 'add-btn-icon'
|
||||
})
|
||||
}
|
||||
// 可以删除
|
||||
if (cfg.isCanDeleted === 1 && canOperate) {
|
||||
group.addShape('rect', {
|
||||
attrs: {
|
||||
x: ChartAttrs.nodeWidth / 2 + ChartAttrs.btnSize / 2,
|
||||
y: -ChartAttrs.btnSize / 2,
|
||||
width: ChartAttrs.btnSize,
|
||||
height: ChartAttrs.btnSize,
|
||||
radius: 4,
|
||||
symbol: AddIcon,
|
||||
fill: 'white',
|
||||
cursor: 'pointer',
|
||||
stroke: ChartAttrs.btnColor
|
||||
},
|
||||
name: 'delete-btn'
|
||||
})
|
||||
group.addShape('image', {
|
||||
attrs: {
|
||||
x: ChartAttrs.nodeWidth / 2 + ChartAttrs.btnSize / 2 + ChartAttrs.btnPadding,
|
||||
y: -ChartAttrs.btnSize / 2 + ChartAttrs.btnPadding,
|
||||
img: DeleteIcon,
|
||||
width: ChartAttrs.btnSize - ChartAttrs.btnPadding * 2,
|
||||
height: ChartAttrs.btnSize - ChartAttrs.btnPadding * 2
|
||||
},
|
||||
name: 'delete-btn-icon'
|
||||
})
|
||||
}
|
||||
return rect
|
||||
},
|
||||
// 设置锚点,上下中心
|
||||
getAnchorPoints () {
|
||||
return [
|
||||
[0.5, 0],
|
||||
[0.5, 1]
|
||||
]
|
||||
}
|
||||
}, 'single-node')
|
||||
|
||||
// 自定义边
|
||||
G6.registerEdge('default-edge', {
|
||||
// 处理线条
|
||||
draw (cfg, group) {
|
||||
const startPoint = cfg.startPoint
|
||||
const endPoint = cfg.endPoint
|
||||
let path = []
|
||||
// 如果起始点在终点的右下方
|
||||
if (startPoint.y > endPoint.y && startPoint.x >= endPoint.x) {
|
||||
path = [
|
||||
['M', startPoint.x, startPoint.y],
|
||||
['L', startPoint.x, startPoint.y + ChartAttrs.nodeHeight], // 从下面出来
|
||||
['L', startPoint.x + ChartAttrs.nodeWidth / 2 + ChartAttrs.returnLineSpacing, startPoint.y + ChartAttrs.nodeHeight], // 右侧绕一下
|
||||
['L', startPoint.x + ChartAttrs.nodeWidth / 2 + ChartAttrs.returnLineSpacing, endPoint.y - ChartAttrs.nodeHeight], // 右侧绕一下
|
||||
['L', endPoint.x, endPoint.y - ChartAttrs.nodeHeight], // 到入点的上方
|
||||
['L', endPoint.x, endPoint.y]
|
||||
]
|
||||
} else if (startPoint.y > endPoint.y && startPoint.x < endPoint.x) { // 起始点在终点的左下方
|
||||
path = [
|
||||
['M', startPoint.x, startPoint.y],
|
||||
['L', startPoint.x, startPoint.y + ChartAttrs.nodeHeight], // 从下面出来
|
||||
['L', startPoint.x - ChartAttrs.nodeWidth - ChartAttrs.returnLineSpacing, startPoint.y + ChartAttrs.nodeHeight], // 左侧绕一下
|
||||
['L', startPoint.x - ChartAttrs.nodeWidth - ChartAttrs.returnLineSpacing, endPoint.y - ChartAttrs.nodeHeight], // 右侧绕一下
|
||||
['L', endPoint.x, endPoint.y - ChartAttrs.nodeHeight], // 到入点的上方
|
||||
['L', endPoint.x, endPoint.y]
|
||||
]
|
||||
} else {
|
||||
path = [
|
||||
['M', startPoint.x, startPoint.y],
|
||||
['L', startPoint.x, (endPoint.y - startPoint.y) / 2 + startPoint.y],
|
||||
['L', endPoint.x, (endPoint.y - startPoint.y) / 2 + startPoint.y],
|
||||
['L', endPoint.x, endPoint.y]
|
||||
]
|
||||
}
|
||||
const shape = group.addShape('path', {
|
||||
attrs: {
|
||||
path,
|
||||
stroke: ChartAttrs.edgeColor,
|
||||
lineWidth: ChartAttrs.edgeWidth,
|
||||
endArrow: true
|
||||
},
|
||||
className: 'edge-shape',
|
||||
// must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
|
||||
name: 'edge-shape'
|
||||
})
|
||||
// 处理label
|
||||
if (cfg.label && cfg.label.length > 0) {
|
||||
console.log(cfg.label)
|
||||
group.addShape('text', {
|
||||
attrs: {
|
||||
text: cfg.label,
|
||||
textAlign: 'center',
|
||||
x: endPoint.x,
|
||||
y: (endPoint.y - startPoint.y) / 2 + startPoint.y,
|
||||
fill: 'black'
|
||||
},
|
||||
name: 'edge-label'
|
||||
})
|
||||
}
|
||||
return shape
|
||||
}
|
||||
})
|
||||
|
||||
this.graph = new G6.Graph({
|
||||
container: this.domId,
|
||||
width: document.getElementById(this.domId).clientWidth,
|
||||
height: document.getElementById(this.domId).clientHeight,
|
||||
fitView: true,
|
||||
layout: {
|
||||
type: 'dagre',
|
||||
nodesep: 70,
|
||||
ranksep: 30,
|
||||
controlPoints: true,
|
||||
preventOverlap: true // 防止节点重叠
|
||||
},
|
||||
defaultNode: {
|
||||
type: 'default-node',
|
||||
sortByCombo: true,
|
||||
size: [210, 40]
|
||||
},
|
||||
defaultEdge: {
|
||||
type: 'default-edge' // 在数据中已经指定 type,这里无需再次指定
|
||||
},
|
||||
modes: {
|
||||
default: [
|
||||
'drag-canvas',
|
||||
'zoom-canvas',
|
||||
{
|
||||
type: 'tooltip',
|
||||
formatText (model) {
|
||||
return model.name
|
||||
},
|
||||
offset: 10
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
this.graph.data(this.chartData)
|
||||
this.graph.render()
|
||||
console.log(document.getElementById(this.domId).clientWidth,
|
||||
document.getElementById(this.domId).clientHeight)
|
||||
this.graph.on('node:click', event => {
|
||||
this.handleNodeClick(event)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 节点点击事件
|
||||
* @param target
|
||||
* @param item
|
||||
*/
|
||||
handleNodeClick ({ target, item }) {
|
||||
// 处理新增
|
||||
if (target.cfg.name === 'add-btn' || target.cfg.name === 'add-btn-icon') {
|
||||
this.handleAdd(item)
|
||||
return
|
||||
}
|
||||
// 处理删除
|
||||
if (target.cfg.name === 'delete-btn' || target.cfg.name === 'delete-btn-icon') {
|
||||
this.handleDelete(item)
|
||||
}
|
||||
},
|
||||
handleAdd (item) {
|
||||
this.$emit('add', item)
|
||||
},
|
||||
handleDelete (item) {
|
||||
this.$emit('delete', item)
|
||||
},
|
||||
/**
|
||||
* 处理元素大小变化
|
||||
*/
|
||||
handleResize () {
|
||||
if (!this.graph || this.graph.get('destroyed')) {
|
||||
return
|
||||
}
|
||||
this.graph.changeSize(document.getElementById(this.domId).clientWidth, document.getElementById(this.domId).clientHeight)
|
||||
this.graph.fitCenter()
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.observer.disconnect()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart-dom {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/deep/ .g6-tooltip {
|
||||
max-width: 200px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<j-modal
|
||||
:title="$t('workCenter.processDetail.flowChart')"
|
||||
:width="width"
|
||||
:visible="visible"
|
||||
switchFullscreen
|
||||
:maskClosable="false"
|
||||
:confirmLoading="confirmLoading"
|
||||
:footer="null"
|
||||
@cancel="close">
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<flow-chart dom-id="flowChart" :chart-data="chartData" is-view-only />
|
||||
</a-spin>
|
||||
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FlowChart from './FlowChart.vue'
|
||||
import { getFlowNodeList } from '../api/workCenter.js'
|
||||
|
||||
export default {
|
||||
name: 'FlowChartModal',
|
||||
components: { FlowChart },
|
||||
data () {
|
||||
return {
|
||||
width: 1000,
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
chartData: {},
|
||||
processKey: null // 流程key
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open () {
|
||||
this.visible = true
|
||||
this.initChartData()
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
},
|
||||
initChartData () {
|
||||
this.confirmLoading = true
|
||||
getFlowNodeList({ key: this.processKey }).then(res => {
|
||||
if (res.success) {
|
||||
this.chartData = Object.assign({}, res.result || {})
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.confirmLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
#flowChart {
|
||||
width: 100%;
|
||||
height: calc(100vh - 200px - 55px - 48px);
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
/deep/ #flowChart {
|
||||
height: calc(100vh - 55px - 48px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="$t('import')"
|
||||
:maskClosable="false"
|
||||
:width="600"
|
||||
:closable="true"
|
||||
:confirm-loading="confirmLoading"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:visible="visible">
|
||||
<a-form layout="inline">
|
||||
<a-form-item :label="$t('importWithTemplate.chooseFile')">
|
||||
<div class="form-content">
|
||||
<a-input v-model="fileName" />
|
||||
<j-upload type="primary" :number="1" @change="handleFileChange" :show-upload-list="false" :return-url="false" v-bind="$attrs">
|
||||
<template v-slot:customButton>
|
||||
<a-button type="primary">{{ $t('importWithTemplate.chooseFile') }}</a-button>
|
||||
</template>
|
||||
</j-upload>
|
||||
</div>
|
||||
<a-button type="link" class="down-template-btn" @click="downTemplate">{{ $t('importWithTemplate.downTemplate') }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@assets/less/common.less'
|
||||
import { urlToParams } from '../utils/util'
|
||||
import { downloadFile } from '../api/manage'
|
||||
|
||||
export default {
|
||||
name: 'ImportWithTemplate',
|
||||
props: {
|
||||
// 下载模板的地址
|
||||
downTemplateUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 导入模板文件名
|
||||
templateName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
fileId: null,
|
||||
fileName: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open () {
|
||||
this.visible = true
|
||||
},
|
||||
handleOk () {
|
||||
if (this.fileId) {
|
||||
this.$emit('ok', this.fileId)
|
||||
this.close()
|
||||
} else {
|
||||
this.$message.warn(this.$t('importWithTemplate.noFileMsg'))
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
this.fileId = null
|
||||
this.fileName = null
|
||||
},
|
||||
handleFileChange (fileList) {
|
||||
this.fileName = fileList[0].fileName
|
||||
const file = urlToParams(fileList[0].filePath)
|
||||
this.fileId = file.id
|
||||
},
|
||||
// 下载导入模板
|
||||
downTemplate () {
|
||||
if (!this.downTemplateUrl) {
|
||||
this.$message.warn('请设置downTemplateUrl属性')
|
||||
return
|
||||
}
|
||||
const fileName = this.templateName || '导入模板'
|
||||
downloadFile(this.downTemplateUrl, fileName + '.xlsx')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.form-content {
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
|
||||
.ant-input {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-inline .ant-form-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
/deep/ .ant-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-form-item-control {
|
||||
line-height: 32px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="detail-container">
|
||||
<div class="page-title">
|
||||
<slot name="customIcon" v-if="customIcon" />
|
||||
<i class="iconfont icon-chexiao" v-else @click="handleBack" />
|
||||
<div class="page-title-text">
|
||||
{{ title }}
|
||||
<a-tooltip v-if="subTitleTooltip">
|
||||
<template slot="title">{{ subTitle }}</template>
|
||||
<div class="page-title-sub-title">{{ subTitle }}</div>
|
||||
</a-tooltip>
|
||||
<div class="page-title-sub-title" v-else>{{ subTitle }}</div>
|
||||
</div>
|
||||
<div class="custom-operate">
|
||||
<slot name="titleRightCustom"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="detail-content" :style="{padding: contentPadding + 'px'}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'InternalDetailPage',
|
||||
props: {
|
||||
// 页面标题
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 是否需要自定义返回方法
|
||||
needCustomBackFunc: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 内容的内边距,默认是24
|
||||
contentPadding: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 24
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 自定义图标
|
||||
customIcon: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 副标题
|
||||
subTitle: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 副标题是否需要tooltip
|
||||
subTitleTooltip: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleBack () {
|
||||
if (this.needCustomBackFunc) {
|
||||
this.$emit('back')
|
||||
return
|
||||
}
|
||||
this.$router.go(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import "~@/assets/less/common";
|
||||
|
||||
.detail-container {
|
||||
height: @page-content-h;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
height: 56px;
|
||||
border-bottom: 1px solid #E5E6EB;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.iconfont {
|
||||
font-size: 24px;
|
||||
color: @primary-color;
|
||||
cursor: pointer;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Medium, PingFang SC, sans-serif;
|
||||
font-weight: bold;
|
||||
color: #1D2129;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-sub-title {
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
margin-left: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
height: calc(100% - 56px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<a-table
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:row-key="rowKey"
|
||||
:bordered="bordered"
|
||||
:scroll="{x: '100%'}"
|
||||
:row-class-name="lineCanExpand"
|
||||
:columns="columns"
|
||||
:expandedRowKeys.sync="expandedRowKeys"
|
||||
:pagination="ipagination"
|
||||
:data-source="dataSource">
|
||||
<!-- 如果插槽是作用域插槽,将作用域插槽的props包装成对象传递给slot -->
|
||||
<template v-for="(_, slotName) in $scopedSlots" :slot="slotName" slot-scope="text, record, index">
|
||||
<slot :name="slotName" v-bind="{text, record, index}" />
|
||||
</template>
|
||||
<!-- 如果是普通插槽,直接使用$slots渲染 -->
|
||||
<slot v-for="(_, slotName) in $slots" :name="slotName" :slot="slotName" />
|
||||
<!-- 文本悬浮插槽 -->
|
||||
<template slot="text" slot-scope="text">
|
||||
<a-tooltip overlay-class-name="tooltip-style">
|
||||
<template slot="title">{{ text || text === 0 ? text : global.emptyLine }}</template>
|
||||
<div class="table-text">{{ text || text === 0 ? text : global.emptyLine }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<!-- 表格扩展插槽 -->
|
||||
<div slot="expandedRowRender" slot-scope="record, index" style="margin: 0">
|
||||
<!-- 如果表单会依赖某个字段变化就需要加判断,循环判断extraForm中的表单 -->
|
||||
<template v-if="formIsDepend">
|
||||
<template v-for="(value, key) in extraForm">
|
||||
<template v-if="record[formIsDepend] === key">
|
||||
<a-form-model :key="key" :model="record" :rules="value.rules" :ref="`ruleForm${record[rowKey]}`">
|
||||
<a-form-model-item v-for="form in value.formArr"
|
||||
:key="form.dbFieldName"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
:label="form.dbFieldTxt"
|
||||
:prop="form.dbFieldName">
|
||||
<template v-if="form.fieldShowType === 1">
|
||||
<a-input
|
||||
type="textarea"
|
||||
:maxLength="500"
|
||||
:placeholder="$t('pleaseEnter')+form.dbFieldTxt"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
v-model="record[form.dbFieldName]" />
|
||||
</template>
|
||||
<template v-else-if="form.fieldShowType === 2">
|
||||
<j-upload
|
||||
return-id
|
||||
multiple
|
||||
v-model="record[form.dbFieldName]"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
:file-type="form.fileType || canUploadType">
|
||||
</j-upload>
|
||||
</template>
|
||||
<template v-else-if="form.fieldShowType === 3">
|
||||
<a-date-picker
|
||||
value-format="YYYY-MM-DD"
|
||||
format="YYYY-MM-DD"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
:disabled-date="form.disabledDate || undefined"
|
||||
v-model="record[form.dbFieldName]">
|
||||
</a-date-picker>
|
||||
</template>
|
||||
<template v-else-if="form.fieldShowType === 4">
|
||||
<a-input
|
||||
:maxLength="50"
|
||||
:placeholder="$t('pleaseEnter')+form.dbFieldTxt"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
v-model="record[form.dbFieldName]" />
|
||||
</template>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 没有依赖的字段,就直接把传入的extraForm数组渲染成表单 -->
|
||||
<template v-else>
|
||||
<a-form-model :model="record" :rules="extraForm.rules" :ref="`ruleForm${index}`">
|
||||
<a-form-model-item v-for="form in extraForm.formArr"
|
||||
:key="form.dbFieldName"
|
||||
:labelCol="labelCol"
|
||||
:wrapperCol="wrapperCol"
|
||||
:label="form.dbFieldTxt"
|
||||
:prop="form.dbFieldName">
|
||||
<template v-if="form.fieldShowType === 1">
|
||||
<a-input
|
||||
type="textarea"
|
||||
:maxLength="500"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
:placeholder="$t('pleaseEnter')+form.dbFieldTxt"
|
||||
v-model="record[form.dbFieldName]" />
|
||||
</template>
|
||||
<template v-else-if="form.fieldShowType === 2">
|
||||
<j-upload
|
||||
return-id
|
||||
multiple
|
||||
v-model="record[form.dbFieldName]"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
:file-type="form.fileType || canUploadType">
|
||||
</j-upload>
|
||||
</template>
|
||||
<template v-else-if="form.fieldShowType === 3">
|
||||
<a-date-picker
|
||||
value-format="YYYY-MM-DD"
|
||||
format="YYYY-MM-DD"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
:disabled-date="form.disabledDate || undefined"
|
||||
v-model="record[form.dbFieldName]">
|
||||
</a-date-picker>
|
||||
</template>
|
||||
<template v-else-if="form.fieldShowType === 4">
|
||||
<a-input
|
||||
:maxLength="50"
|
||||
:placeholder="$t('pleaseEnter')+form.dbFieldTxt"
|
||||
:disabled="allDisabled || form.isDisabled || (form.dynamicIsDisabled && form.dynamicIsDisabled(record)) || false"
|
||||
v-model="record[form.dbFieldName]" />
|
||||
</template>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</template>
|
||||
</div>
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
使用方法:
|
||||
html:
|
||||
<j-expand-table :formIsDepend="formIsDepend" :extraForm="extraForm"></j-expand-table>
|
||||
1、如果扩展中的表单会根据某个字段变化,就传formIsDepend=随之变化的字段名,extraForm就传
|
||||
extraForm: {
|
||||
1: { // 1表示formIsDepend对应的字段值是1
|
||||
rules: { // formIsDepend对应的字段值是1时的表单验证
|
||||
desc: { required: true, message: '请输入依据描述', trigger: 'blur' },
|
||||
...
|
||||
},
|
||||
formArr: [// formIsDepend对应的字段值是1时的表单数据
|
||||
{
|
||||
fieldShowType: 1,
|
||||
dbFieldTxt: this.$t(''), // 标签文本
|
||||
dbFieldName: 'desc', // dataSource中对应的字段名
|
||||
isDisabled: true, // false的话可以不传
|
||||
dynamicIsDisabled: (record) => {return true} // 可以通过行数据动态设置是否可以编辑
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
},
|
||||
2、如果扩展中的表单是固定的,extraForm就传
|
||||
extraForm: {
|
||||
rules: {
|
||||
desc: { required: false, message: '请输入依据描述', trigger: 'blur' },
|
||||
...
|
||||
},
|
||||
formArr: [
|
||||
{
|
||||
fieldShowType: 1,
|
||||
dbFieldTxt: this.$t(''), // 标签文本
|
||||
dbFieldName: 'desc', // dataSource中对应的字段名
|
||||
isDisabled: true // false的话可以不传
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
3、提交时调用该组件中的submit方法获取返回值:
|
||||
返回值为false说明没有通过表单校验,为true说明通过了校验,直接通过绑定的dataSource获取数据即可,示例如下:
|
||||
// 获取表格扩展部分的校验结果
|
||||
const result = this.$refs.jExpandTable.submit()
|
||||
if (result) {
|
||||
// 每个表单校验都通过了
|
||||
console.log('通过了', this.dataSource)
|
||||
} else {
|
||||
// 有表单没有通过
|
||||
this.$message.warning('请输入必填项')
|
||||
}
|
||||
|
||||
-->
|
||||
<script>
|
||||
import JUpload from './jero/JUpload'
|
||||
|
||||
export default {
|
||||
name: 'JExpandTable',
|
||||
components: { JUpload },
|
||||
props: {
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
dataSource: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 扩展区表单, 表单需要依赖某个字段判断的就用对象,key是字段对应值,value是字段值对应下的表单数组
|
||||
* 表单数组格式如下:
|
||||
* [
|
||||
* {
|
||||
* fieldShowType: // 字段类型: 1,多行文本;2, 上传文件; 3, 日期选择; 4, 单行文本,
|
||||
* dbFieldTxt: // 标签文本,例如:依据描述,
|
||||
* dbFieldName: // 字段名
|
||||
* isDisabled: // 是否可编辑
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
extraForm: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 表单是否有依赖,传false或者依赖字段名
|
||||
*/
|
||||
formIsDepend: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
// 全部禁用
|
||||
allDisabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 控制该行是否可展开的字段
|
||||
lineCanExpandField: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 控制该行是否可以展开的字段值
|
||||
lineCanExpandValue: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 表格行数据的主键
|
||||
rowKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'id'
|
||||
},
|
||||
// 分页参数
|
||||
pagination: {
|
||||
type: [Boolean, Object],
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pagination: {
|
||||
handler (val) {
|
||||
this.ipagination = Object.assign({}, val)
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
labelCol: {
|
||||
sm: 4
|
||||
},
|
||||
wrapperCol: {
|
||||
sm: 16
|
||||
},
|
||||
// 本系统限制可以上传的文件格式
|
||||
canUploadType: 'pdf,docx,doc,xlsx,xls,ppt,pptx,rar,zip,jpg,jpeg,png,avi,wmv,mov,rm,mp4,cad',
|
||||
ipagination: {},
|
||||
expandedRowKeys: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit () {
|
||||
console.log(this.rowKey)
|
||||
const arr = []
|
||||
for (const i in this.dataSource) {
|
||||
if (this.$refs[`ruleForm${i}`] && this.$refs[`ruleForm${i}`][0]) {
|
||||
this.$refs[`ruleForm${i}`][0].validate(valid => {
|
||||
arr[i] = { valid, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
})
|
||||
} else {
|
||||
// 表单都没有渲染,说明没有展开过,也就是未填状态
|
||||
if (this.formIsDepend) {
|
||||
if (this.dataSource[i][this.formIsDepend]) {
|
||||
// 需要依赖某个字段,获取当前行该字段时,扩展表单是否有必填项
|
||||
const currRowExtraFormFormArr = this.extraForm[this.dataSource[i][this.formIsDepend]].formArr
|
||||
const currRowExtraFormRules = this.extraForm[this.dataSource[i][this.formIsDepend]].rules
|
||||
let isHaveRequired = false
|
||||
for (const item of currRowExtraFormFormArr) {
|
||||
if (currRowExtraFormRules[item.dbFieldName].required === true && !this.dataSource[i][item.dbFieldName]) {
|
||||
// 有必填但没有填内容
|
||||
isHaveRequired = true
|
||||
}
|
||||
}
|
||||
if (isHaveRequired) {
|
||||
// 任意一个字段有必填,她又没展开过,校验不通过
|
||||
arr[i] = { valid: false, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
} else {
|
||||
arr[i] = { valid: true, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
}
|
||||
} else if (this.lineCanExpandField && this.dataSource[i][this.lineCanExpandField] !== this.lineCanExpandValue) {
|
||||
// 如果这行数据本身不需要填写
|
||||
arr[i] = { valid: true, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
} else {
|
||||
// 依赖的字段都没有填
|
||||
arr[i] = { valid: false, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
}
|
||||
} else {
|
||||
// 没有依赖的字段
|
||||
let isHaveRequired = false
|
||||
for (const item of this.extraForm.formArr) {
|
||||
if (this.extraForm.rules[item.dbFieldName].required === true && !this.dataSource[i][item.dbFieldName]) {
|
||||
// 有字段有必填,但没有内容
|
||||
isHaveRequired = true
|
||||
}
|
||||
}
|
||||
if (isHaveRequired) {
|
||||
// 任意一个字段有必填,她又没展开过,校验不通过
|
||||
arr[i] = { valid: false, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
} else {
|
||||
arr[i] = { valid: true, rowKey: this.dataSource[i][this.rowKey], index: i }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arr.some(tt => !tt.valid)) {
|
||||
const errorRows = arr.filter(tt => !tt.valid)
|
||||
const errorIndex = errorRows.map(tt => tt.index)
|
||||
const firstIndex = errorRows[0].index
|
||||
const firstPage = parseInt((Number(firstIndex) + 1) / this.ipagination.pageSize) + 1
|
||||
this.ipagination.current = firstPage
|
||||
this.$nextTick(() => {
|
||||
this.expandedRowKeys = [...this.expandedRowKeys, ...errorRows.map(tt => tt.rowKey)]
|
||||
let timer = setTimeout(() => {
|
||||
this.$nextTick(() => {
|
||||
errorIndex.forEach(index => {
|
||||
if (this.$refs[`ruleForm${index}`] && this.$refs[`ruleForm${index}`][0]) {
|
||||
this.$refs[`ruleForm${index}`][0].validate()
|
||||
}
|
||||
})
|
||||
})
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}, 100)
|
||||
})
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
lineCanExpand (record) {
|
||||
console.log(this.lineCanExpandField, typeof this.lineCanExpandValue, record[this.lineCanExpandField] === this.lineCanExpandValue)
|
||||
if (!this.lineCanExpandField || (!this.lineCanExpandValue && this.lineCanExpandValue !== 0 && this.lineCanExpandValue !== false)) {
|
||||
return 'can-expend'
|
||||
}
|
||||
if (record[this.lineCanExpandField] === this.lineCanExpandValue) {
|
||||
return 'can-expend'
|
||||
}
|
||||
return 'cannot-expend'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/deep/ .cannot-expend {
|
||||
.ant-table-row-expand-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
:id="fieldName"
|
||||
:show-time="false"
|
||||
:open="open"
|
||||
:ref="fieldName"
|
||||
:dropdownClassName="'j-multiple-date-picker ' + fieldName + '-drop'"
|
||||
@openChange="handleOpenChange"
|
||||
@change="handleChange"
|
||||
style="width: 100%"
|
||||
:value="firstValue"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template slot="dateRender" slot-scope="current">
|
||||
<div class="ant-calendar-date" @click.stop="clickCalendarDate(current)" :class="{'blue': getCurrentStyle(current)}">
|
||||
{{current.date()}}
|
||||
</div>
|
||||
</template>
|
||||
<!-- <template slot="renderExtraFooter" slot-scope="">-->
|
||||
<!-- <a class="ant-calendar-ok-btn" @click="handleOk">确定</a>-->
|
||||
<!-- </template>-->
|
||||
</a-date-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JMultipleDatePicker',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fieldName: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
console.log(val, '----watch--value-------')
|
||||
if (!val) {
|
||||
this.checkedValue = []
|
||||
this.firstValue = null
|
||||
document.getElementById(this.fieldName).children[0].children[0].value = ''
|
||||
this.$emit('change', '')
|
||||
} else {
|
||||
this.checkedValue = val.split(',')
|
||||
this.firstValue = this.checkedValue[0]
|
||||
this.$nextTick(() => {
|
||||
document.getElementById(this.fieldName).children[0].children[0].value = val
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
open: false,
|
||||
checkedValue: [],
|
||||
firstValue: null // 多选中第一个日期,用于触发显示删除按钮
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 面板头部切换日期的按钮部分点击事件,切换时会刷新dom,这里每次把刷新后的dom给改回多日期显示
|
||||
calendarHeaderFn () {
|
||||
this.firstValue = this.checkedValue[0]
|
||||
this.$nextTick(() => {
|
||||
document.getElementsByClassName(this.fieldName + '-drop')[0].children[0].children[0].children[0].children[0].children[0].value = this.checkedValue.join(',')
|
||||
})
|
||||
},
|
||||
handleOpenChange (open) {
|
||||
console.log(open)
|
||||
if (open) {
|
||||
this.open = open
|
||||
this.$nextTick(() => {
|
||||
// 为了固定弹出层,把弹出层dom元素移动
|
||||
const drop = document.getElementsByClassName(this.fieldName + '-drop')[0]
|
||||
const picker = document.getElementById(this.fieldName)
|
||||
// 创建第二层外层元素
|
||||
console.log(drop)
|
||||
if (drop) {
|
||||
const outerWrapperTwo = document.createElement('div')
|
||||
outerWrapperTwo.appendChild(drop)
|
||||
// 创建新的外层元素
|
||||
const outerWrapper = document.createElement('div')
|
||||
outerWrapper.style.position = 'absolute'
|
||||
outerWrapper.style.top = '0px'
|
||||
outerWrapper.style.left = '0px'
|
||||
outerWrapper.style.width = '100%'
|
||||
outerWrapper.appendChild(outerWrapperTwo)
|
||||
picker.appendChild(outerWrapper)
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 日期面板头部点击事件(头部切换日期会重新渲染dom,这里绑定事件,头部切换的时候就把值再改回多日期)
|
||||
console.log(`#${this.fieldName} .ant-calendar-header`, document.querySelector(`#${this.fieldName} .ant-calendar-header`))
|
||||
document.querySelector(`#${this.fieldName} .ant-calendar-header`).addEventListener('click', this.calendarHeaderFn)
|
||||
document.getElementsByClassName(this.fieldName + '-drop')[0].children[0].children[0].children[0].children[0].children[0].value = this.checkedValue.join(',')
|
||||
}, 100)
|
||||
})
|
||||
} else {
|
||||
const newVal = document.getElementsByClassName(this.fieldName + '-drop')[0].children[0].children[0].children[0].children[0].children[0].value
|
||||
const oldVal = this.checkedValue.join(',')
|
||||
if (newVal !== oldVal) {
|
||||
if (newVal) {
|
||||
this.checkedValue = newVal.split(',')
|
||||
} else {
|
||||
this.checkedValue = []
|
||||
}
|
||||
this.$emit('change', this.checkedValue.join(','))
|
||||
}
|
||||
this.$refs[this.fieldName].blur()
|
||||
this.open = open
|
||||
this.$nextTick(() => {
|
||||
// console.log('------this.checkedValue.join(\',\')----------', this.checkedValue.join(','))
|
||||
// this.$emit('change', lastCheckedValue.join(','))
|
||||
document.getElementById(this.fieldName).children[0].children[0].value = this.checkedValue.join(',')
|
||||
})
|
||||
}
|
||||
},
|
||||
handleChange (val) {
|
||||
console.log('-handleChange-----', val)
|
||||
if (!val) {
|
||||
this.firstValue = null
|
||||
this.checkedValue = []
|
||||
this.$emit('change', this.checkedValue.join(','))
|
||||
}
|
||||
},
|
||||
getCurrentStyle (current) {
|
||||
const currentStr = current.format('YYYY-MM-DD')
|
||||
if (this.checkedValue.includes(currentStr)) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
// 日期面板中input绑定事件
|
||||
focusInput (e) {
|
||||
e.target.value = this.checkedValue.join(',')
|
||||
},
|
||||
// 点击日期,加日期或者减日期
|
||||
clickCalendarDate (current) {
|
||||
const currentStr = current.format('YYYY-MM-DD')
|
||||
if (!this.checkedValue.includes(currentStr)) {
|
||||
console.log('加日期')
|
||||
const checkedValue = this.checkedValue
|
||||
checkedValue.push(currentStr)
|
||||
// 对日期进行排序
|
||||
this.checkedValue = checkedValue.sort((date1, date2) => {
|
||||
return new Date(date1) - new Date(date2)
|
||||
})
|
||||
} else {
|
||||
console.log('减日期')
|
||||
this.checkedValue = this.checkedValue.filter(item => item !== currentStr)
|
||||
}
|
||||
this.firstValue = this.checkedValue[0]
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
const inputDom = document.getElementsByClassName(this.fieldName + '-drop')[0].children[0].children[0].children[0].children[0].children[0]
|
||||
inputDom.value = this.checkedValue.join(',')
|
||||
inputDom.addEventListener('focus', this.focusInput)
|
||||
})
|
||||
})
|
||||
this.$emit('change', this.checkedValue.join(','))
|
||||
}
|
||||
// 点击日期弹出框中的确定按钮
|
||||
// handleOk () {
|
||||
// this.open = false
|
||||
// this.$nextTick(() => {
|
||||
// // 改变外侧日期显示框的数据
|
||||
// document.getElementById(this.fieldName).children[0].children[0].value = this.checkedValue.join(',')
|
||||
// // document.getElementsByClassName(this.fieldName)[0].children[0].children[0].value = this.checkedValue.join(',')
|
||||
// })
|
||||
// this.$emit('change', this.checkedValue.join(','))
|
||||
// }
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
// 去掉日期面板中原本选中的样式
|
||||
.j-multiple-date-picker {
|
||||
.ant-calendar-today {
|
||||
.ant-calendar-date {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.ant-calendar-selected-day {
|
||||
.ant-calendar-date{
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置日期面板中选中日期的样式
|
||||
.blue {
|
||||
background: #ffd5d3 !important;
|
||||
}
|
||||
|
||||
// 设置输入框中的文字显示
|
||||
/deep/ .ant-calendar-picker-input {
|
||||
padding-right: 30px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 设置弹出层footer的布局
|
||||
:global(.ant-calendar-footer-btn) {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
flex-direction: row-reverse !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
// 删除弹出层的今天按钮
|
||||
:global(.ant-calendar-today-btn) {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<div :id="domId" class="mind-map"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import G6 from '@antv/g6'
|
||||
import insertCss from 'insert-css'
|
||||
|
||||
// 收起图标
|
||||
const COLLAPSE_ICON = function COLLAPSE_ICON (x, y, r) {
|
||||
return [['M', x, y], ['a', r, r, 0, 1, 0, r * 2, 0], ['a', r, r, 0, 1, 0, -r * 2, 0], ['M', x + 2, y], ['L', x + 2 * r - 2, y]]
|
||||
}
|
||||
// 展开图标
|
||||
const EXPAND_ICON = function EXPAND_ICON (x, y, r) {
|
||||
return [['M', x, y], ['a', r, r, 0, 1, 0, r * 2, 0], ['a', r, r, 0, 1, 0, -r * 2, 0], ['M', x + 2, y], ['L', x + 2 * r - 2, y], ['M', x + r, y - r + 2], ['L', x + r, y + r - 2]]
|
||||
}
|
||||
// 节点的颜色
|
||||
const NODE_COLOR = [
|
||||
{
|
||||
bg: '#E9ECF0',
|
||||
stroke: '#3D557B',
|
||||
color: '#2F466B'
|
||||
},
|
||||
{
|
||||
bg: '#EEF4F8',
|
||||
stroke: '#739FC8',
|
||||
color: '#227CCF'
|
||||
},
|
||||
{
|
||||
bg: '#FEF1EA',
|
||||
stroke: '#F9964F',
|
||||
color: '#DF7426'
|
||||
},
|
||||
{
|
||||
bg: '#E5F2F4',
|
||||
stroke: '#2596A1',
|
||||
color: '#2596A1'
|
||||
}
|
||||
]
|
||||
// 节点和收起展开之间的间距
|
||||
const NODE_ICON_MARGIN = 8
|
||||
// 最长文本显示长度
|
||||
const ELLIPSE_LENGTH = 30
|
||||
// 处理图的tooltip样式
|
||||
insertCss(`
|
||||
.g6-tooltip {
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
padding: 2px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
`)
|
||||
|
||||
export default {
|
||||
name: 'MindMap',
|
||||
props: {
|
||||
// 页面元素的id
|
||||
domId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'mountNode'
|
||||
},
|
||||
// 图表的数据
|
||||
mapData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 图数据的key字段
|
||||
mapDataKeyField: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'id'
|
||||
},
|
||||
// 回显文本取的字段
|
||||
labelField: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'name'
|
||||
},
|
||||
// 节点的宽度
|
||||
nodeWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 400
|
||||
},
|
||||
// 节点高度
|
||||
nodeHeight: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 46
|
||||
},
|
||||
// 收起展开节点图标的半径
|
||||
expendCollapseIconR: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 8
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: {},
|
||||
graph: null,
|
||||
loadEnd: false,
|
||||
selectedNodeId: null,
|
||||
lastQueryId: null // 记录上次查询的节点id
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mapData: {
|
||||
handler () {
|
||||
this.data = JSON.parse(JSON.stringify(this.mapData))
|
||||
if (this.loadEnd && this.data.id) {
|
||||
this.initMindMap()
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
loadEnd () {
|
||||
if (this.loadEnd && this.data.id) {
|
||||
this.initMindMap()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.loadEnd = true
|
||||
window.onresize = () => {
|
||||
const width = document.getElementById(this.domId).clientWidth
|
||||
const height = document.getElementById(this.domId).clientHeight
|
||||
this.graph.changeSize(width, height)
|
||||
this.graph.fitCenter()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
initMindMap () {
|
||||
if (this.graph) {
|
||||
this.graph.destroy()
|
||||
this.graph = null
|
||||
}
|
||||
G6.registerNode('tree-node', {
|
||||
drawShape: (cfg, group) => {
|
||||
const nodeColorIndex = cfg.depth % NODE_COLOR.length
|
||||
const nodeStyle = NODE_COLOR[nodeColorIndex]
|
||||
// 节点整体的内容
|
||||
const nodeContainer = group.addShape('rect', {
|
||||
attrs: {
|
||||
x: this.nodeHeight / 2,
|
||||
y: this.nodeHeight / 2,
|
||||
width: this.nodeWidth + NODE_ICON_MARGIN + this.expendCollapseIconR,
|
||||
height: this.nodeHeight
|
||||
},
|
||||
name: 'rect-shape'
|
||||
})
|
||||
|
||||
// 有边框和背景色的box
|
||||
group.addShape('rect', {
|
||||
attrs: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.nodeWidth,
|
||||
height: this.nodeHeight,
|
||||
radius: this.nodeHeight / 2,
|
||||
fill: nodeStyle.bg,
|
||||
stroke: nodeStyle.stroke
|
||||
},
|
||||
name: 'rect-content'
|
||||
})
|
||||
|
||||
// 文本的部分
|
||||
let textContent = (cfg.num || '') + ' ' + (cfg.name || '')
|
||||
const text = textContent.length > ELLIPSE_LENGTH ? textContent.substring(0, ELLIPSE_LENGTH) + '...' : textContent
|
||||
group.addShape('text', {
|
||||
attrs: {
|
||||
text: text,
|
||||
x: this.nodeWidth / 2,
|
||||
y: this.nodeHeight / 2,
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
fill: nodeStyle.color
|
||||
},
|
||||
name: 'rect-text'
|
||||
})
|
||||
|
||||
// 处理展开收起图标
|
||||
const hasChildren = cfg.children && cfg.children.length > 0
|
||||
if (hasChildren) {
|
||||
group.addShape('marker', {
|
||||
attrs: {
|
||||
x: this.nodeWidth + NODE_ICON_MARGIN,
|
||||
y: this.nodeHeight / 2,
|
||||
r: this.expendCollapseIconR,
|
||||
symbol: COLLAPSE_ICON,
|
||||
stroke: '#666',
|
||||
lineWidth: 2
|
||||
},
|
||||
name: 'expend-collapse',
|
||||
className: 'collapse-icon'
|
||||
})
|
||||
}
|
||||
|
||||
nodeContainer.attr({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: this.nodeWidth + this.expendCollapseIconR + NODE_ICON_MARGIN + this.expendCollapseIconR,
|
||||
height: this.nodeHeight
|
||||
})
|
||||
return nodeContainer
|
||||
},
|
||||
setState (name, value, item) {
|
||||
const group = item.getContainer()
|
||||
const nodeDom = group.get('children')
|
||||
const nodeBox = nodeDom[1]
|
||||
const nodeText = nodeDom[2]
|
||||
if (name === 'selected') {
|
||||
if (value) {
|
||||
nodeBox.attr('fill', '#D52C26')
|
||||
nodeBox.attr('stroke', '#D52C26')
|
||||
nodeText.attr('fill', '#FFFFFF')
|
||||
} else {
|
||||
const nodeColorIndex = item._cfg.model.depth % NODE_COLOR.length
|
||||
const nodeStyle = NODE_COLOR[nodeColorIndex]
|
||||
nodeBox.attr('fill', nodeStyle.bg)
|
||||
nodeBox.attr('stroke', nodeStyle.stroke)
|
||||
nodeText.attr('fill', nodeStyle.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'single-shape')
|
||||
this.graph = new G6.TreeGraph({
|
||||
container: this.domId,
|
||||
width: document.getElementById(this.domId).clientWidth,
|
||||
height: document.getElementById(this.domId).clientHeight,
|
||||
modes: {
|
||||
default: [
|
||||
'drag-canvas',
|
||||
'zoom-canvas',
|
||||
{
|
||||
type: 'tooltip',
|
||||
formatText (model) {
|
||||
return (model.num || '') + ' ' + (model.name || '')
|
||||
},
|
||||
offset: 10
|
||||
}
|
||||
]
|
||||
},
|
||||
defaultNode: {
|
||||
type: 'tree-node',
|
||||
anchorPoints: [[0, 0.5], [1, 0.5]]
|
||||
},
|
||||
defaultEdge: {
|
||||
type: 'cubic-horizontal',
|
||||
style: {
|
||||
stroke: '#A3B1BF',
|
||||
endArrow: {
|
||||
path: G6.Arrow.circle(2),
|
||||
d: 0,
|
||||
fill: '#A3B1BF'
|
||||
}
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: 'compactBox',
|
||||
direction: 'LR',
|
||||
getId: (d) => {
|
||||
return d.id
|
||||
},
|
||||
getVGap: () => {
|
||||
return 20
|
||||
},
|
||||
getHGap: () => {
|
||||
return 300
|
||||
}
|
||||
}
|
||||
})
|
||||
// G6.Util.traverseTree(this.data, (item) => {
|
||||
// item.id = item[this.mapDataKeyField]
|
||||
// })
|
||||
this.graph.data(this.data)
|
||||
this.graph.render()
|
||||
this.graph.fitView()
|
||||
// 展开收起事件
|
||||
this.graph.on('expend-collapse:click', (event) => {
|
||||
const { item } = event
|
||||
item.getModel().collapsed = !item.getModel().collapsed
|
||||
// this.graph.setItemState(item, 'collapsed', item.getModel().collapsed)
|
||||
this.graph.refreshItem(item)
|
||||
this.graph.layout()
|
||||
})
|
||||
// 点击选中
|
||||
this.graph.on('rect-content:click', (event) => {
|
||||
this.handleSelectedNode(event)
|
||||
})
|
||||
this.graph.on('rect-text:click', (event) => {
|
||||
this.handleSelectedNode(event)
|
||||
})
|
||||
},
|
||||
handleSelectedNode (event) {
|
||||
const { item } = event
|
||||
if (this.selectedNodeId && this.selectedNodeId !== item._cfg.id) {
|
||||
this.graph.findById(this.selectedNodeId).getModel().selected = false
|
||||
this.graph.setItemState(this.selectedNodeId, 'selected', false)
|
||||
}
|
||||
item.getModel().selected = !item.getModel().selected
|
||||
this.graph.setItemState(item._cfg.id, 'selected', item.getModel().selected)
|
||||
this.selectedNodeId = item._cfg.id
|
||||
this.graph.refreshItem(item._cfg.id)
|
||||
this.graph.layout()
|
||||
// 选中抛出节点信息,取消选中抛出一个空对象
|
||||
if (item.getModel().selected) {
|
||||
this.$emit('select', item._cfg.model)
|
||||
} else {
|
||||
this.$emit('select', {})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 查询,定位高亮
|
||||
* @param nodeId
|
||||
*/
|
||||
searchNode (nodeId) {
|
||||
if (this.lastQueryId) {
|
||||
this.graph.setItemState(this.lastQueryId, 'selected', false)
|
||||
}
|
||||
if (this.selectedNodeId) {
|
||||
this.graph.setItemState(this.selectedNodeId, 'selected', false)
|
||||
}
|
||||
if (!nodeId) {
|
||||
return
|
||||
}
|
||||
const item = this.graph.findById(nodeId)
|
||||
this.graph.setItemState(nodeId, 'selected', true)
|
||||
const node = this.graph.findById(nodeId)
|
||||
this.selectedNodeId = nodeId
|
||||
this.$emit('select', node._cfg.model)
|
||||
this.lastQueryId = nodeId
|
||||
const matrix = item.get('group').getMatrix()
|
||||
const point = {
|
||||
x: matrix[6],
|
||||
y: matrix[7]
|
||||
}
|
||||
const width = this.graph.get('width')
|
||||
const height = this.graph.get('height')
|
||||
// 找到视口中心
|
||||
const viewCenter = {
|
||||
x: width / 2,
|
||||
y: height / 2
|
||||
}
|
||||
const modelCenter = this.graph.getPointByCanvas(viewCenter.x, viewCenter.y)
|
||||
const viewportMatrix = this.graph.get('group').getMatrix()
|
||||
// 画布平移的目标位置,最终目标是graph.translate(dx, dy);
|
||||
const dx = (modelCenter.x - point.x) * viewportMatrix[0]
|
||||
const dy = (modelCenter.y - point.y) * viewportMatrix[4]
|
||||
let lastX = 0
|
||||
let lastY = 0
|
||||
let newX = void 0
|
||||
let newY = void 0
|
||||
// 动画每次平移一点,直到目标位置
|
||||
this.graph.get('canvas').animate({
|
||||
onFrame: (ratio) => {
|
||||
newX = dx * ratio
|
||||
newY = dy * ratio
|
||||
this.graph.translate(newX - lastX, newY - lastY)
|
||||
lastX = newX
|
||||
lastY = newY
|
||||
}
|
||||
}, 300, 'easeCubic')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.mind-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/deep/ .g6-tooltip {
|
||||
max-width: 200px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<div class="person-comp-box">
|
||||
<a-steps direction="vertical">
|
||||
<!-- 没有currentNode说明流程已经结束了,图标和线都显示已经走过的样式 -->
|
||||
<a-step v-for="(step, index) in stepsData" :key="step.id" :status="step.nodeName === currentNode ? 'process' : 'wait'"
|
||||
:class="{'red-tail': currentNode ? index < currentNodeIndex : true}">
|
||||
<template #icon>
|
||||
<div class="step-cus-icon"
|
||||
:class="{'step-cus-icon-done': currentNode ? index < currentNodeIndex : true, 'step-cus-icon-now': index === currentNodeIndex}">
|
||||
<i class="iconfont icon-user" />
|
||||
</div>
|
||||
</template>
|
||||
<!-- 流程节点标题-->
|
||||
<!--<template #title>-->
|
||||
<!-- <a-tooltip>-->
|
||||
<!-- <template #title>{{ step.nodeName }}</template>-->
|
||||
<!-- <div class="step-title">{{ step.nodeName }}</div>-->
|
||||
<!-- </a-tooltip>-->
|
||||
<!--</template>-->
|
||||
<!-- 流程节点选人内容-->
|
||||
<template #description>
|
||||
<div class="personnel-container">
|
||||
<div class="personnel-container-title" :class="{'personnel-container-select-title': index === 0}">
|
||||
<a-tooltip>
|
||||
<template #title>{{ step.nodeName }}</template>
|
||||
<div class="personnel-container-title-text">{{ step.nodeName }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="personnel-container-content">
|
||||
<div class="personnel-container-content-text">
|
||||
<template v-if="step.canChoose && index >= currentNodeIndex">
|
||||
<user-select-by-work-group v-if="step.chooseSource === 'workGroup'"
|
||||
v-model="step.linkUserIds"
|
||||
:type="step.chooseType"
|
||||
:name-str="step.linkUserIds_dictText"
|
||||
:filedName="step.variableName"
|
||||
@nameChange="nameChange"
|
||||
input-type="textarea" />
|
||||
<user-select-by-contact v-else-if="step.chooseSource === 'contactManage'"
|
||||
v-model="step.linkUserIds"
|
||||
:type="step.chooseType"
|
||||
:name-str="step.linkUserIds_dictText"
|
||||
:filedName="step.variableName"
|
||||
@nameChange="nameChange"
|
||||
input-type="textarea" />
|
||||
<user-selection v-else-if="step.chooseSource === 'user'"
|
||||
:type="step.chooseType"
|
||||
v-model="step.linkUserIds"
|
||||
:name-str="step.linkUserIds_dictText"
|
||||
:filedName="step.variableName"
|
||||
@nameChange="nameChange"
|
||||
input-type="textarea" />
|
||||
</template>
|
||||
<div class="personnel-container-content-echo-text" v-else>{{ step.userList }}</div>
|
||||
</div>
|
||||
<a-icon type="clock-circle" theme="filled" v-if="index === currentNodeIndex" class="personnel-container-content-icon" />
|
||||
<a-icon type="check"
|
||||
v-if="index < currentNodeIndex"
|
||||
class="personnel-container-content-icon personnel-container-content-icon-now" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-step>
|
||||
</a-steps>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@assets/less/common.less'
|
||||
import UserSelection from './selection/UserSelection.vue'
|
||||
import UserSelectByWorkGroup from './selection/UserSelectByWorkGroup'
|
||||
import UserSelectByContact from './selection/UserSelectByContact'
|
||||
|
||||
export default {
|
||||
name: 'PersonnelInfo',
|
||||
components: { UserSelectByContact, UserSelectByWorkGroup, UserSelection },
|
||||
props: {
|
||||
// 数据
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 当前节点
|
||||
currentNode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 特殊流程需要用indexOf判断的前缀
|
||||
specialProcessNodePrefix: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 不需要做必填校验的字段,用于应对第一个节点选没选人决定节点跳不跳过的问题
|
||||
notPermissField: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
stepsData: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 当前节点的位置
|
||||
currentNodeIndex () {
|
||||
if (this.specialProcessNodePrefix) {
|
||||
return this.stepsData.findIndex(tt => tt.xmlNodeId.indexOf(this.specialProcessNodePrefix) === 0)
|
||||
}
|
||||
return this.stepsData.findIndex(tt => tt.xmlNodeId === this.currentNode)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
handler () {
|
||||
this.stepsData = JSON.parse(JSON.stringify(this.data))
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取人员对象,无校验
|
||||
getDataObj () {
|
||||
const personnelObj = {}
|
||||
// for (let i = 0; i < this.stepsData.length; i++) {
|
||||
// // 该属性还没有人就给他赋值
|
||||
// if ((this.stepsData[i].canChoose || i === 0) && !personnelObj[this.stepsData[i].variableName]) {
|
||||
// personnelObj[this.stepsData[i].variableName] = this.stepsData[i].linkUserIds
|
||||
// personnelObj[this.stepsData[i].variableName + '_dictText'] = this.stepsData[i].linkUserIds_dictText || this.stepsData[i].userList
|
||||
// }
|
||||
// }
|
||||
for (const item of this.stepsData) {
|
||||
console.log(item)
|
||||
// 该属性还没有人就给他赋值
|
||||
if (!personnelObj[item.variableName]) {
|
||||
personnelObj[item.variableName] = item.linkUserIds
|
||||
personnelObj[item.variableName + '_dictText'] = item.linkUserIds_dictText || item.userList
|
||||
}
|
||||
// if ((item.canChoose || item.sort === 1) && !personnelObj[item.variableName]) {
|
||||
// personnelObj[item.variableName] = item.linkUserIds
|
||||
// personnelObj[item.variableName + '_dictText'] = item.linkUserIds_dictText || item.userList
|
||||
// }
|
||||
}
|
||||
return personnelObj
|
||||
},
|
||||
// 有校验的获取人员对象
|
||||
getDataObjVerify () {
|
||||
let personnelObj = {}
|
||||
console.log(this.stepsData)
|
||||
for (let i = 0; i < this.stepsData.length; i++) {
|
||||
// 该属性还没有人就给他赋值,并且如果minLength有值的话,需要判断选的人数过了这个限制
|
||||
if ((this.stepsData[i].canChoose || i === 0) && !personnelObj[this.stepsData[i].variableName]) {
|
||||
if (this.stepsData[i].minLength) {
|
||||
// 有设置至少选几人
|
||||
if (this.stepsData[i].linkUserIds && this.stepsData[i].linkUserIds.split(',').length >= this.stepsData[i].minLength) {
|
||||
// 说明人数通过了限制
|
||||
personnelObj[this.stepsData[i].variableName] = this.stepsData[i].linkUserIds
|
||||
personnelObj[this.stepsData[i].variableName + '_dictText'] = this.stepsData[i].linkUserIds_dictText || this.stepsData[i].userList
|
||||
} else {
|
||||
// 提示XX节点至少选择XX人
|
||||
this.$message.warning(this.stepsData[i].nodeName + this.$t('node') + this.$t('atLeastSelect') + this.stepsData[i].minLength + this.$t('personnal'))
|
||||
personnelObj[this.stepsData[i].variableName] = ''
|
||||
return
|
||||
}
|
||||
} else {
|
||||
personnelObj[this.stepsData[i].variableName] = this.stepsData[i].linkUserIds
|
||||
personnelObj[this.stepsData[i].variableName + '_dictText'] = this.stepsData[i].linkUserIds_dictText || this.stepsData[i].userList
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(personnelObj)
|
||||
for (const item in personnelObj) {
|
||||
if (!this.notPermissField.includes(item) && !personnelObj[item]) {
|
||||
// 不在不需要做必填校验的字段里,说明是必填并且没有数据
|
||||
this.$message.warning(this.$t('pleaseEnterPersonnelInfo'))
|
||||
personnelObj = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return personnelObj
|
||||
},
|
||||
nameChange (value, fieldName) {
|
||||
this.stepsData.find(item => item.variableName === fieldName).linkUserIds_dictText = value
|
||||
this.stepsData.find(item => item.variableName === fieldName).userList = value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.person-comp-box {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ant-steps-vertical .ant-steps-item-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/deep/ .ant-steps-item-title {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ant-steps-item-content {
|
||||
padding: 0 12px 30px 12px;
|
||||
|
||||
}
|
||||
|
||||
.step-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
color: #1D2129;
|
||||
font-weight: 550;
|
||||
}
|
||||
|
||||
.personnel-container {
|
||||
&-title {
|
||||
height: 36px;
|
||||
background: #6A7484;
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 0 16px;
|
||||
|
||||
&-text {
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
color: #FFFFFF;
|
||||
line-height: 36px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-select-title {
|
||||
background: #9F5258;
|
||||
}
|
||||
|
||||
&-content {
|
||||
background: #FFFFFF;
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 16px;
|
||||
min-height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-text {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-left: 16px;
|
||||
color: @primary-color;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&-icon-now {
|
||||
font-size: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #FAE6E5;
|
||||
border-radius: 10px 10px 10px 10px;
|
||||
opacity: 1;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&-echo-text {
|
||||
font-size: 14px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
font-weight: 400;
|
||||
color: #4E5969;
|
||||
line-height: 22px;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .user-organ-wrap .button-box {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/deep/ .ant-steps-vertical > .ant-steps-item > .ant-steps-item-container > .ant-steps-item-tail {
|
||||
left: 14px;
|
||||
padding: 31px 0 4px;
|
||||
}
|
||||
|
||||
.step-cus-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: #F2F3F5;
|
||||
border-radius: 32px 32px 32px 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&-done {
|
||||
background: rgba(213, 44, 38, 0.08);
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&-now {
|
||||
color: white;
|
||||
background: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .red-tail > .ant-steps-item-container > .ant-steps-item-tail::after {
|
||||
background-color: @primary-color;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,420 @@
|
||||
<template>
|
||||
<div class="page-box">
|
||||
<div class="personnel-info">
|
||||
<slot name="personnelInfo"></slot>
|
||||
</div>
|
||||
<div class="approval-list">
|
||||
<!--查询区域-->
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline" @keyup.enter.native="searchQuery">
|
||||
<a-row :gutter="24">
|
||||
<a-col :md="8" :sm="8">
|
||||
<!--状态-->
|
||||
<a-form-item :label="$t('status')" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-dict-select-tag
|
||||
:placeholder="$t('pleaseEnter') + $t('status')"
|
||||
dict-code="prc_node_status"
|
||||
v-model="queryParam.finishFlag"></j-dict-select-tag>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<div style="float: right;overflow: hidden;margin-right: 11px" class="table-page-search-submitButtons">
|
||||
<a-button class="box-button" type="primary" icon="search" @click="searchQuery">{{ $t('query') }}</a-button>
|
||||
<a-button class="box-button" icon="reload" style="margin-left: 8px" @click="searchReset">{{ $t('reset') }}</a-button>
|
||||
</div>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<div class="table-page-oper-submitButtons">
|
||||
<!--催办-->
|
||||
<a-button type="primary" @click="handleUrge" icon="bell" v-if="showUrgeButton">
|
||||
{{ $t('workCenter.processDetail.urgeHandle') }}
|
||||
</a-button>
|
||||
<!--流程图-->
|
||||
<!--<a-button type="primary" @click="viewFlowChart" icon="apartment" ghost>{{ $t('workCenter.processDetail.flowChart') }}</a-button>-->
|
||||
</div>
|
||||
<div>
|
||||
<!-- rowKey加了id是因为强制撤回的记录没有taskId -->
|
||||
<j-table
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:can-drag="true"
|
||||
:loading="loading"
|
||||
:rowKey="record => {return record.id + ',' + (record.taskId || '')}"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
:pagination="ipagination"
|
||||
:scroll="{x: '100%', y: yScrollHeight}"
|
||||
@change="handleTableChange">
|
||||
|
||||
<!-- 耗时 -->
|
||||
<!--<template v-slot:timeConsuming="{text, record}">-->
|
||||
<!-- <a-tooltip overlay-class-name="tooltip-style">-->
|
||||
<!-- <template slot="title">{{ record.endTime && record.createTime ? computedTimeConsuming(record) : global.emptyLine }}</template>-->
|
||||
<!-- <div class="table-text">-->
|
||||
<!-- {{ record.endTime && record.createTime ? computedTimeConsuming(record) : global.emptyLine }}-->
|
||||
<!-- </div>-->
|
||||
<!-- </a-tooltip>-->
|
||||
<!--</template>-->
|
||||
|
||||
<!--审批意见-->
|
||||
<template v-slot:approvalOpinion="{text, record}">
|
||||
<a-tooltip overlay-class-name="tooltip-style">
|
||||
<template slot="title">{{ text || text === 0 ? record.commitFlag_dictText : global.emptyLine }}</template>
|
||||
<div class="table-text" :class="{'agree-color': record.commitFlag + '' === '1', 'reject-color': record.commitFlag + '' === '2'}">
|
||||
{{ text || text === 0 ? record.commitFlag_dictText : global.emptyLine }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!--附件-->
|
||||
<template v-slot:file="{text, record}">
|
||||
<a v-if="text" @click="fileLook(record)">{{ $t('view') }}</a>
|
||||
<div v-else class="table-text">{{ global.emptyLine }}</div>
|
||||
<!--<a-tooltip overlay-class-name="tooltip-style">-->
|
||||
<!-- <template slot="title">{{ text || text === 0 ? text : global.emptyLine }}</template>-->
|
||||
<!-- <div class="table-text can-click-table-text" v-if="text || text === 0" @click="handlePreview(record)">{{ text }}</div>-->
|
||||
<!-- <div v-else class="table-text">{{ global.emptyLine }}</div>-->
|
||||
<!--</a-tooltip>-->
|
||||
</template>
|
||||
</j-table>
|
||||
</div>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<flow-chart-modal ref="flowChartModal" />
|
||||
<upload-file ref="uploadFile" disabled></upload-file>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { JeroListMixin } from '../mixins/JeroListMixin.js'
|
||||
import JTable from './jero/JTable.vue'
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '../store/mutation-types.js'
|
||||
import { downloadFile, getFileAccessHttpUrl, getAction } from '../api/manage.js'
|
||||
import { previewPdf } from '../utils/previewPdf.js'
|
||||
import FlowChartModal from './FlowChartModal.vue'
|
||||
import UploadFile from './UploadFile'
|
||||
import { filterObj } from '../utils/util'
|
||||
import { kkFilePreview } from '../utils/kkFilePreview'
|
||||
import { urgeProcessNode } from '../api/workCenter'
|
||||
|
||||
// 支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png', 'raw']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
export default {
|
||||
name: 'ProcessDetailList',
|
||||
components: { UploadFile, JTable, FlowChartModal },
|
||||
mixins: [JeroListMixin],
|
||||
props: {
|
||||
// 需要有list属性,用于混入里的接口获取
|
||||
url: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {
|
||||
list: '/workCenter/getApprovalRecordList'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 催办按钮的权限
|
||||
urgePermissionFlag: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 流程图按钮的权限
|
||||
flowChartPermissionFlag: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 流程key
|
||||
processKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 是否展示催办按钮
|
||||
showUrgeButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
yScrollHeight () {
|
||||
/**
|
||||
* 前几个使用变量是为了配合iframe嵌套,算出页面显示范围的高度
|
||||
* 48px:分页器的高度
|
||||
* 48px:a-card的padding(上24+下24)
|
||||
* 54px:表头行的高度
|
||||
*/
|
||||
return `calc(100vh - ${this.defaultHeight['user-info-height']} - ${this.defaultHeight['breadcrumb-height']} - ${this.defaultHeight.oneLineSearchHeight} - ${this.defaultHeight.oneLineOperationHeight} - 48px - 48px - 54px - 56px)`
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
labelCol: {
|
||||
span: 1
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 14
|
||||
},
|
||||
columns: [
|
||||
// 任务节点
|
||||
{
|
||||
dataIndex: 'taskName',
|
||||
title: this.$t('workCenter.processDetail.taskNode'),
|
||||
width: 150
|
||||
},
|
||||
// 任务受理人
|
||||
{
|
||||
dataIndex: 'taskAcceptor',
|
||||
title: this.$t('workCenter.processDetail.taskHandler'),
|
||||
width: 120
|
||||
},
|
||||
// 创建时间
|
||||
{
|
||||
dataIndex: 'createTime',
|
||||
title: this.$t('createTime'),
|
||||
width: 150
|
||||
},
|
||||
// 完成时间
|
||||
{
|
||||
dataIndex: 'endTime',
|
||||
title: this.$t('workCenter.processDetail.completionTime'),
|
||||
width: 150
|
||||
},
|
||||
// 耗时
|
||||
{
|
||||
title: this.$t('workCenter.processDetail.timeConsuming'),
|
||||
width: 150,
|
||||
dataIndex: 'overTime',
|
||||
scopedSlots: { customRender: 'text' }
|
||||
},
|
||||
// 审批意见
|
||||
{
|
||||
dataIndex: 'commitFlag',
|
||||
title: this.$t('workCenter.processDetail.approvalOpinion'),
|
||||
width: 100,
|
||||
scopedSlots: { customRender: 'approvalOpinion' }
|
||||
},
|
||||
// 意见内容
|
||||
{
|
||||
dataIndex: 'commitText',
|
||||
title: this.$t('workCenter.processDetail.opinionContent'),
|
||||
width: 120
|
||||
},
|
||||
// 附件
|
||||
{
|
||||
dataIndex: 'commitFile',
|
||||
title: this.$t('businessSupport.questionAnswer.attach'),
|
||||
width: 160,
|
||||
scopedSlots: { customRender: 'file' }
|
||||
},
|
||||
// 状态
|
||||
{
|
||||
dataIndex: 'finishFlag_dictText',
|
||||
title: this.$t('status'),
|
||||
width: 100
|
||||
}
|
||||
],
|
||||
image: false,
|
||||
imageUrl: null,
|
||||
/* 排序参数 */
|
||||
// 后端需要去掉
|
||||
isorter: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadData (arg) {
|
||||
if (!this.url.list) {
|
||||
this.$message.warning('请设置url.list属性!')
|
||||
return
|
||||
}
|
||||
if (arg === 1) {
|
||||
this.ipagination.current = 1
|
||||
}
|
||||
const params = this.getQueryParams()// 查询条件
|
||||
// 流程监控进详情,并且是已撤回的流程不查状态为进行中的任务
|
||||
if (this.$route.query.source === 'monitorList' && this.$route.query.prcStatus === '3') {
|
||||
params.finishFlagSearch = '2,3,4'
|
||||
}
|
||||
if (this.$route.query.processInstanceId) {
|
||||
params.processInstanceId = this.$route.query.processInstanceId
|
||||
} else {
|
||||
// 没有流程实例id,那就是新增,不用查
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
getAction(this.url.list, params).then((res) => {
|
||||
if (res.success) {
|
||||
// update-begin---author:zhangyafei Date:20201118 for:适配不分页的数据列表------------
|
||||
this.dataSource = res.result.records || res.result
|
||||
if (res.result.total) {
|
||||
this.ipagination.total = res.result.total
|
||||
} else {
|
||||
this.ipagination.total = 0
|
||||
}
|
||||
// update-end---author:zhangyafei Date:20201118 for:适配不分页的数据列表------------
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
getQueryParams () {
|
||||
// 获取查询条件
|
||||
const sqp = {}
|
||||
if (this.superQueryParams) {
|
||||
sqp.superQueryParams = encodeURI(this.superQueryParams)
|
||||
sqp.superQueryMatchType = this.superQueryMatchType
|
||||
}
|
||||
const param = Object.assign(sqp, this.queryParam, this.isorter, this.filters)
|
||||
param.field = this.getQueryField()
|
||||
param.pageNo = this.ipagination.current
|
||||
param.pageSize = this.ipagination.pageSize
|
||||
return filterObj(param)
|
||||
},
|
||||
// 计算耗时
|
||||
computedTimeConsuming (record) {
|
||||
// 结束时间和创建时间
|
||||
const endTime = new Date(record.endTime)
|
||||
const createTime = new Date(record.createTime)
|
||||
// 计算时间差 转化为秒数
|
||||
const time = parseInt((endTime.getTime() - createTime.getTime()) / 1000)
|
||||
// 总秒数转化为对应的天数
|
||||
const days = parseInt(time / (24 * 60 * 60))
|
||||
// 计算小时
|
||||
const hours = parseInt((time % (24 * 60 * 60)) / (60 * 60))
|
||||
// 计算分钟
|
||||
const minutes = parseInt((time % (60 * 60)) / 60)
|
||||
// 计算秒
|
||||
const seconds = time % 60
|
||||
return (days ? days + '天 ' : '') + (hours ? hours + '小时 ' : '') +
|
||||
(minutes ? minutes + '分钟 ' : '') + (seconds ? seconds + '秒' : '')
|
||||
},
|
||||
// 附件列的查看点击
|
||||
fileLook (record) {
|
||||
this.$refs.uploadFile.open(record.commitFile)
|
||||
},
|
||||
/**
|
||||
* 催办
|
||||
*/
|
||||
handleUrge () {
|
||||
if (!this.selectedRowKeys || this.selectedRowKeys.length === 0) {
|
||||
this.$message.warning(this.$t('selectAtLeastOne'))
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
const taskIds = this.selectedRowKeys.map(item => {
|
||||
return item.split(',')[1]
|
||||
})
|
||||
urgeProcessNode({ taskId: taskIds.join(',') }).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success(res.message)
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 跳转流程图页面
|
||||
*/
|
||||
viewFlowChart () {
|
||||
if (!this.processKey) {
|
||||
return
|
||||
}
|
||||
this.$refs.flowChartModal.processKey = this.processKey
|
||||
this.$refs.flowChartModal.open()
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = file.fileName ? file.fileName.split('.')[file.fileName.split('.').length - 1] : ''
|
||||
const canPreview = CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix.toLowerCase() === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (!canPreview) {
|
||||
this.$message.loading('该文件类型不支持预览,正在为您准备下载...').then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/view/${file.id}?at=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.fileName}`
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (canPreview && FILE_TYPE_IMGS.includes(fileSuffix.toLowerCase())) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (canPreview && FILE_TYPE_PDF.includes(fileSuffix.toLowerCase())) {
|
||||
const url = previewPdf(file.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
kkFilePreview(fileFullUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page-box {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.personnel-info {
|
||||
height: 100%;
|
||||
width: 400px;
|
||||
padding: 32px;
|
||||
overflow: auto;
|
||||
border-right: 1px solid #E5E6EB;
|
||||
}
|
||||
|
||||
.approval-list {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
overflow: auto;
|
||||
padding: 32px 32px 0;
|
||||
}
|
||||
|
||||
.agree-color {
|
||||
color: #229B1F;
|
||||
}
|
||||
|
||||
// 这个地方的颜色不用primary-color是因为拒绝永远是红色的
|
||||
.reject-color {
|
||||
color: #D52C26;
|
||||
}
|
||||
|
||||
.table-page-oper-submitButtons {
|
||||
.ant-btn {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-form-item-label {
|
||||
min-width: 90px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
####1._util包:存放自定义函数 详细见代码注释
|
||||
####2.AvatarList:显示头像群并支持tip,用法参考src\views\Home.vue(如下图)
|
||||

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

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

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

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

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

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

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

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

|
||||

|
||||

|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:value="treeValue"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:tree-checkable="treeCheckable"
|
||||
:show-search="canSearch"
|
||||
:search-value.sync="searchValue"
|
||||
:filterTreeNode="filterTreeOption"
|
||||
@change="handleChange"
|
||||
@search="handleSearch">
|
||||
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'STreeSelect',
|
||||
props: {
|
||||
value: {
|
||||
required: false
|
||||
},
|
||||
// 是否可以搜索
|
||||
canSearch: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
// 是否多选
|
||||
treeCheckable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler () {
|
||||
if (typeof this.value !== 'string' && this.value) {
|
||||
this.treeValue = JSON.parse(JSON.stringify(this.value))
|
||||
} else {
|
||||
this.treeValue = this.value
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: null, // 选中的数据
|
||||
searchValue: null // 查询的字符串
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.treeCheckable) {
|
||||
this.$nextTick(() => {
|
||||
document.addEventListener('click', this.clearSearchValue)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterTreeOption (input, option) {
|
||||
const text = option.componentOptions.propsData.title || option.componentOptions.propsData.label
|
||||
return text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
handleChange (value) {
|
||||
this.treeValue = value
|
||||
this.$emit('change', value)
|
||||
},
|
||||
handleSearch (value) {
|
||||
this.searchValue = value
|
||||
},
|
||||
clearSearchValue (event) {
|
||||
event.stopPropagation()
|
||||
this.searchValue = null
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
if (this.treeCheckable) {
|
||||
document.removeEventListener('click', this.clearSearchValue)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/deep/ .ant-select-tree-dropdown {
|
||||
max-height: 50vh !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
:title="title"
|
||||
:maskClosable="false"
|
||||
:width="600"
|
||||
placement="right"
|
||||
:closable="true"
|
||||
@close="handleCancel"
|
||||
:visible="visible"
|
||||
style="height: 100%;overflow: auto;padding-bottom: 53px;">
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-input-search style="margin-bottom: 8px" v-model="searchModel"
|
||||
@search="onSearch"
|
||||
allowClear
|
||||
:placeholder="$t('NodeQuickLookup')" />
|
||||
<div class="drawer-content">
|
||||
<a-tree
|
||||
v-if="visibleTree"
|
||||
style="margin-bottom: 60px;height: 600px"
|
||||
checkable
|
||||
:autoExpandParent="false"
|
||||
:tree-data="gData"
|
||||
@select="onSelect"
|
||||
@check="checkChange"
|
||||
@expand="onExpand"
|
||||
:defaultExpandedKeys="defaultExpandedKeys"
|
||||
>
|
||||
</a-tree>
|
||||
</div>
|
||||
</a-spin>
|
||||
<div class="drawer-bootom-button">
|
||||
<a-button @click="handleCancel" style="margin-right: 16px">{{ $t('cancel') }}</a-button>
|
||||
<a-button @click="handleSubmit" type="primary" :loading="submitLoading">{{ $t('submit') }}</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction, postAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'index',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isSingleChoice: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
flag: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
gData: [],
|
||||
searchModel: '',
|
||||
autoExpandParent: true,
|
||||
expandedKeys: [],
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
selectedKey: [],
|
||||
tableKey: [],
|
||||
userIds: [],
|
||||
departId: '',
|
||||
defaultExpandedKeys: [],
|
||||
submitLoading: false,
|
||||
visibleTree: false,
|
||||
userName: []
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
||||
},
|
||||
methods: {
|
||||
getPush () {
|
||||
this.searchModel = ''
|
||||
this.visible = true
|
||||
this.visibleTree = false
|
||||
this.$nextTick(() => {
|
||||
this.departId = ''
|
||||
this.queryDepartUserTreeList()
|
||||
})
|
||||
},
|
||||
onSelect (selectedKeys) {
|
||||
this.selectedKey = selectedKeys
|
||||
},
|
||||
onExpand (selectedKeys, val) {
|
||||
if (this.searchModel === '' || !this.searchModel) {
|
||||
if (val.expanded) {
|
||||
if (this.departId === val.node.value) {
|
||||
} else {
|
||||
this.departId = val.node.value
|
||||
this.queryDepartUserTreeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.visible = false
|
||||
this.visibleTree = false
|
||||
},
|
||||
handleSubmit () {
|
||||
if (this.isSingleChoice) {
|
||||
if (this.userIds.length > 1) {
|
||||
this.$message.warning(this.$t('onlyOnePersonCanBeSelected'))
|
||||
return
|
||||
}
|
||||
}
|
||||
this.confirmLoading = true
|
||||
if (this.userIds && this.userIds.length > 0) {
|
||||
this.submitLoading = true
|
||||
this.$emit('SelectedByForm', this.userIds.join(','), this.userName.join(','))
|
||||
} else {
|
||||
this.$message.warning(this.$t('selectLeastOne'))
|
||||
}
|
||||
},
|
||||
onSearch (e) {
|
||||
this.gData = []
|
||||
this.departId = ''
|
||||
this.visibleTree = false
|
||||
if (e) {
|
||||
this.getUserAndDepart()
|
||||
} else {
|
||||
this.queryDepartUserTreeList()
|
||||
}
|
||||
},
|
||||
getUserAndDepart () {
|
||||
getAction('sys/user/getUserAndDepart', { name: this.searchModel }).then((res) => {
|
||||
if (res) {
|
||||
this.gData = res.filter(ele => ele.flag === 'DEPART')
|
||||
} else {
|
||||
this.gData = []
|
||||
}
|
||||
this.visibleTree = true
|
||||
})
|
||||
},
|
||||
queryDepartUserTreeList () {
|
||||
let gData = JSON.parse(JSON.stringify(this.gData))
|
||||
if (gData && gData.length > 0) {
|
||||
gData = JSON.stringify(gData)
|
||||
} else {
|
||||
gData = ''
|
||||
}
|
||||
let query = {}
|
||||
if (this.flag === 1) {
|
||||
query = {
|
||||
departId: this.departId,
|
||||
json: gData
|
||||
}
|
||||
} else {
|
||||
query = {
|
||||
departId: this.departId,
|
||||
json: gData,
|
||||
flag: '1'
|
||||
}
|
||||
}
|
||||
this.confirmLoading = true
|
||||
postAction('sys/user/queryDepartUserTreeList', query).then((res) => {
|
||||
this.confirmLoading = false
|
||||
if (res.success) {
|
||||
this.gData = res.result
|
||||
this.gData = [...this.gData]
|
||||
this.defaultExpandedKeys = [this.departId]
|
||||
this.visibleTree = true
|
||||
} else {
|
||||
this.gData = []
|
||||
}
|
||||
})
|
||||
},
|
||||
checkChange (e, name) {
|
||||
this.userName = []
|
||||
this.userIds = e
|
||||
if (name.checkedNodes && name.checkedNodes.length > 0) {
|
||||
name.checkedNodes.forEach(res => {
|
||||
this.userName.push(res.data.props.dataRef.title)
|
||||
})
|
||||
}
|
||||
if (this.isSingleChoice) {
|
||||
if (this.userIds.length > 1) {
|
||||
this.userIds.shift()
|
||||
this.userName.shift()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.box-title-text {
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.box-input {
|
||||
display: inline-block;
|
||||
height: 38px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-box {
|
||||
margin-left: 10px;
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
|
||||
}
|
||||
|
||||
.drawer-bootom-button {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding: 10px 16px;
|
||||
text-align: right;
|
||||
left: 0;
|
||||
background: #fff;
|
||||
border-radius: 0 0 2px 2px;
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
height: calc(100% - 60px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,611 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<!-- 拖拽中不能选中内容 -->
|
||||
<div class="sheet-box" :class="{ disabledSelect: isDragging }" ref="sheetBoxRef">
|
||||
<!--标准目录-->
|
||||
<div class="standard-catalogue part-common" ref="standardCatalogueRef" :style="`width: ${standardCatalogueRefX}px`"
|
||||
:class="isCatalogueExpand ? 'expand-catalogue' : 'no-expand-catalogue'">
|
||||
<div class="control-standard-catalogue-expand" @click="expandCatalogue">
|
||||
<a-icon :type="isCatalogueExpand ? 'left' : 'right'" />
|
||||
</div>
|
||||
<div class="part-title" v-show="isCatalogueExpand">{{ $t('standardRegulationLibrary.standardCatalogue') }}</div>
|
||||
<div class="has-title-part-content" v-show="isCatalogueExpand">
|
||||
<a-tree :tree-data="treeData"
|
||||
:selected-keys="selectedTreeKeys"
|
||||
@select="handleTreeSelected"
|
||||
:replaceFields="{children: 'children', key: 'id'}">
|
||||
<a-icon slot="switcherIcon" type="down" :style="{ fontSize: '12px', color: '#103770' }" />
|
||||
<template slot="title" slot-scope="{name, itemName}">
|
||||
<span :style="name && name.indexOf('.')===-1?'font-weight: 600;':''">{{ `${name} ${itemName || ''}` }}</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
<vue-draggable-resizable v-show="isCatalogueExpand" :draggable="true" :resizable="false" :w="6" axis="x" :x="standardCatalogueRefX" :z="2" class="draggable-border"
|
||||
:onDrag="(...args) => onDragCallback(100, 500, ...args)" class-name-dragging="dragging"
|
||||
@dragging="onDragging"
|
||||
@dragstop="(...args) => onDragstop('standardCatalogueRef', ...args)"></vue-draggable-resizable>
|
||||
</div>
|
||||
<!--标准内容-->
|
||||
<div class="sheet-container part-ml" ref="sheetContainerRef" :style="`width: ${sheetContainerRefX}px;`">
|
||||
<!--标准名称-->
|
||||
<div class="standard-name">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ spiltInfo.serialNumber }}
|
||||
</template>
|
||||
<span> {{ spiltInfo.serialNumber }}《{{ spiltInfo.fileName }}》</span>
|
||||
</a-tooltip>
|
||||
<a-button type="link" :icon="isContainerExpand ? 'fullscreen-exit' : 'fullscreen'" @click="handleContainerExpand">
|
||||
{{ isContainerExpand ? $t('putAway') : $t('open') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<!--条文内容-->
|
||||
<div class="standard-content">
|
||||
<div class="clause-content-item"
|
||||
v-for="clause in clauseContentList"
|
||||
:key="clause.id"
|
||||
@click="handleClickClauseContentItem(clause)">
|
||||
<div class="clause-content-item-title">{{ clause.item_num }} {{ clause.item_title }}</div>
|
||||
<div class="clause-content-item-content"
|
||||
:class="{'clause-content-item-content-hight': clause.menu_id === hightMenuId}"
|
||||
v-html="clause.item_content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<vue-draggable-resizable :draggable="true" :resizable="false" :w="6" axis="x" :x="sheetContainerRefX" :z="2" class="draggable-border"
|
||||
:onDrag="(...args) => onDragCallback(100, null, ...args)" class-name-dragging="dragging"
|
||||
@dragging="onDragging"
|
||||
@dragstop="(...args) => onDragstop('sheetContainerRef', ...args)"></vue-draggable-resizable>
|
||||
</div>
|
||||
<!--条文标签-->
|
||||
<div class="clause-label part-ml part-common" ref="clauseLabelRef" :style="`width: ${clauseLabelRefX}px`" :class="{'hide-clause-label': !showClauseLabel || isContainerExpand}">
|
||||
<div class="part-title">{{ $t('standardRegulationLibrary.clauseLabel') }}</div>
|
||||
<div class="has-title-part-content">
|
||||
<template v-for="clause in clauseField">
|
||||
<div class="clause-item" v-if="!clauseLabelHideField.includes(clause.dbFieldName)" :key="clause.id">
|
||||
<label>{{ clause.dbFieldTxt }}</label>
|
||||
<div class="clause-item-content"
|
||||
v-html="clauseLabelInfo[needDictField.includes(clause.fieldShowType) ? clause.dbFieldName + '_dictText' : clause.dbFieldName]"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<vue-draggable-resizable :draggable="true" :resizable="false" :w="6" axis="x" :x="clauseLabelRefX" :z="2" class="draggable-border"
|
||||
:onDrag="(...args) => onDragCallback(100, 500, ...args)" class-name-dragging="dragging"
|
||||
@dragging="onDragging"
|
||||
@dragstop="(...args) => onDragstop('clauseLabelRef', ...args)"></vue-draggable-resizable>
|
||||
</div>
|
||||
<!--条文、条文标签切换-->
|
||||
<div class="clause-label-change part-ml part-common" ref="clauseLabelChangeRef" :class="{'no-expand-clause-label': !isClauseLabelExpand}">
|
||||
<div class="control-clause-label-expand" @click="expandClauseLabel">
|
||||
<a-icon :type="isClauseLabelExpand ? 'right' : 'left'" />
|
||||
</div>
|
||||
<!--条文-->
|
||||
<a-tooltip placement="left">
|
||||
<template #title>{{ $t('docTool.split.article') }}</template>
|
||||
<div class="clause-label-change-item"
|
||||
:class="{'clause-label-change-item-selected': !showClauseLabel}"
|
||||
@click="handleClauseChange(false)">
|
||||
<i class="iconfont icon-ios-list-box" />
|
||||
<span v-show="isClauseLabelExpand">{{ $t('docTool.split.article') }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<!--条文标签-->
|
||||
<a-tooltip placement="left">
|
||||
<template #title>{{ $t('docTool.split.clauseLabel') }}</template>
|
||||
<div class="clause-label-change-item"
|
||||
:class="{'clause-label-change-item-selected': showClauseLabel}"
|
||||
@click="handleClauseChange(true)">
|
||||
<i class="iconfont icon-biaoqian-mianxing" />
|
||||
<span v-show="isClauseLabelExpand">{{ $t('docTool.split.clauseLabel') }}</span>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getDocSplitEditForm, previewDocSplit } from '@api/documentToolApi'
|
||||
import { FieldType } from '@/enums/commonEnums'
|
||||
import VueDraggableResizable from 'vue-draggable-resizable'
|
||||
|
||||
export default {
|
||||
name: 'StandardResolutionSheet',
|
||||
components: {
|
||||
VueDraggableResizable
|
||||
},
|
||||
props: {
|
||||
// 是否文档拆分处查看
|
||||
horizontalOverstep: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showClauseLabel: true, // 是否展示条文标签
|
||||
// 条文标签字段
|
||||
clauseField: [],
|
||||
// 目录数据
|
||||
treeData: [],
|
||||
selectedTreeKeys: [],
|
||||
hightMenuId: null, // 高亮的menuId
|
||||
isClauseLabelExpand: true, // 条文/条文标签切换部分是否展开,控制是否只显示图标
|
||||
isCatalogueExpand: true, // 标准目录收起
|
||||
isContainerExpand: false, // 条文是否展开
|
||||
loading: false,
|
||||
spiltInfo: {}, // 拆分的整体信息
|
||||
clauseContentList: [], // 条文内容的数据
|
||||
clauseLabelInfo: {}, // 条文标签数据
|
||||
clauseLabelHideField: ['item_content'],
|
||||
needDictField: [FieldType.USER_SINGLE.value, FieldType.ORGAN_SINGLE.value, FieldType.STANDARD_MORE.value, FieldType.ORGAN_MORE.value, FieldType.OPTION_SINGLE.value, FieldType.OPTION_MORE.value, FieldType.TREE.value],
|
||||
queryParams: {},
|
||||
isDragging: false, // 是否拖拽中
|
||||
standardCatalogueRefX: 280, // 标准目录默认宽度
|
||||
sheetContainerRefX: 0, // 标准内容默认宽度
|
||||
clauseLabelRefX: 400 // 条文标签默认宽度
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 容器实际剩余宽度 = 右侧内容宽度 - 内容中的间距
|
||||
const boxWidth = this.$refs.sheetBoxRef.getBoundingClientRect().width - this.$refs.clauseLabelChangeRef.getBoundingClientRect().width - (16 * 3)
|
||||
this.sheetContainerRefX = boxWidth - this.standardCatalogueRefX - this.clauseLabelRefX
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 拖拽回调
|
||||
* @param min - 拖拽最小宽度
|
||||
* @param max - 拖拽最大宽度
|
||||
* @param x - 当前宽度
|
||||
* @return {boolean}
|
||||
*/
|
||||
onDragCallback (min, max, x) {
|
||||
if ((min !== null) && (x < min)) {
|
||||
return false
|
||||
}
|
||||
if ((max !== null) && (x > max)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
/**
|
||||
* 拖拽中
|
||||
*/
|
||||
onDragging () {
|
||||
this.isDragging = true
|
||||
},
|
||||
/**
|
||||
* 拖拽结束
|
||||
* @param refName - ref名字用于获取dom改变宽度
|
||||
* @param x - 当前宽度
|
||||
*/
|
||||
onDragstop (refName, x) {
|
||||
this.isDragging = false
|
||||
// console.log(this.$refs[refName])
|
||||
this[refName + 'X'] = x
|
||||
// this.$refs[refName].style.width = x + 'px'
|
||||
},
|
||||
/**
|
||||
* 获取条文标签字段
|
||||
*/
|
||||
initClauseFieldList () {
|
||||
getDocSplitEditForm({ type: 1 }).then(res => {
|
||||
if (res.success) {
|
||||
this.clauseField = res.result || []
|
||||
}
|
||||
})
|
||||
},
|
||||
initDetailInfo () {
|
||||
this.loading = true
|
||||
const params = {
|
||||
...this.queryParams
|
||||
}
|
||||
if (this.horizontalOverstep) {
|
||||
params.horizontalOverstep = true
|
||||
}
|
||||
previewDocSplit(params).then(res => {
|
||||
if (res.success) {
|
||||
const data = res.result || {}
|
||||
this.treeData = data.sarFileSplitMenuEO || []
|
||||
this.spiltInfo = data.documentSplitInfo || {}
|
||||
this.clauseContentList = data.documentSplitDetail || []
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 树节点选中
|
||||
* @param selectedKeys
|
||||
*/
|
||||
handleTreeSelected (selectedKeys) {
|
||||
this.selectedTreeKeys = selectedKeys
|
||||
this.hightMenuId = this.selectedTreeKeys[0]
|
||||
if (this.hightMenuId) {
|
||||
this.handleJump()
|
||||
} else {
|
||||
this.clauseLabelInfo = {}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 内容跳转,处理条文标签数据
|
||||
*/
|
||||
handleJump () {
|
||||
const contentItemDomList = document.getElementsByClassName('clause-content-item')
|
||||
const contentDom = document.getElementsByClassName('standard-content')[0]
|
||||
const contentItemIndex = this.clauseContentList.findIndex(tt => tt.menu_id === this.hightMenuId)
|
||||
this.handleProcessClauseLabelInfo(contentItemIndex)
|
||||
contentDom.scrollTop = contentItemDomList[contentItemIndex].offsetTop - 150
|
||||
},
|
||||
/**
|
||||
* 处理条文标签的数据
|
||||
* @param contentItemIndex
|
||||
*/
|
||||
handleProcessClauseLabelInfo (contentItemIndex) {
|
||||
this.clauseLabelInfo = Object.assign({}, this.clauseContentList[contentItemIndex])
|
||||
},
|
||||
/**
|
||||
* 点击条文内容
|
||||
*/
|
||||
handleClickClauseContentItem ({ menu_id: menuId }) {
|
||||
this.selectedTreeKeys = [menuId]
|
||||
this.hightMenuId = menuId
|
||||
const contentItemIndex = this.clauseContentList.findIndex(tt => tt.menu_id === this.hightMenuId)
|
||||
this.handleProcessClauseLabelInfo(contentItemIndex)
|
||||
},
|
||||
/**
|
||||
* 标准内容展开
|
||||
*/
|
||||
handleContainerExpand () {
|
||||
this.isContainerExpand = !this.isContainerExpand
|
||||
if (this.isContainerExpand) {
|
||||
// 展开主要内容前:主要内容宽 = 当前宽度 + 条文标签宽度(未隐藏时) + 标准目录宽度(未隐藏时)
|
||||
this.sheetContainerRefX = this.sheetContainerRefX + (this.showClauseLabel ? this.clauseLabelRefX : 0) + (this.isCatalogueExpand ? this.standardCatalogueRefX : 0)
|
||||
this.isCatalogueExpand = false
|
||||
} else {
|
||||
// 收起主要内容前:主要内容宽 = 当前宽度 - 条文标签宽度(隐藏时) - 标准目录宽度(隐藏时)
|
||||
this.sheetContainerRefX = this.sheetContainerRefX - (this.showClauseLabel ? this.clauseLabelRefX : 0) - (!this.isCatalogueExpand ? this.standardCatalogueRefX : 0)
|
||||
this.isCatalogueExpand = true
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 条文标签切换
|
||||
* @param flag
|
||||
*/
|
||||
handleClauseChange (flag) {
|
||||
const beforeFlag = this.showClauseLabel
|
||||
this.showClauseLabel = flag
|
||||
// 如果值改变了,就进行宽度调整
|
||||
if (beforeFlag !== flag) {
|
||||
// 主要内容宽 = 当前宽度 +(收起)|-(展开) 条文标签宽度
|
||||
this.sheetContainerRefX = this.sheetContainerRefX + (this.clauseLabelRefX * (this.showClauseLabel ? -1 : 1))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 展开或收起条文/条文标签面板
|
||||
*/
|
||||
expandClauseLabel () {
|
||||
this.isClauseLabelExpand = !this.isClauseLabelExpand
|
||||
// TODO 右侧切换条文和条文标签折叠宽度有改的话,需要修改这里的差值
|
||||
// 根据展开或收起,给主要内容减少或增加宽度
|
||||
const diff = Math.abs(130 - 45)
|
||||
this.sheetContainerRefX = this.sheetContainerRefX + (diff * (this.isClauseLabelExpand ? -1 : 1))
|
||||
},
|
||||
/**
|
||||
* 展开或收起标准目录
|
||||
*/
|
||||
expandCatalogue () {
|
||||
this.isCatalogueExpand = !this.isCatalogueExpand
|
||||
// 主要内容宽 = 当前宽度 +(收起)|-(展开) 标准目录宽度
|
||||
this.sheetContainerRefX = this.sheetContainerRefX + (this.standardCatalogueRefX * (this.isCatalogueExpand ? -1 : 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import '~@assets/less/common.less';
|
||||
|
||||
.sheet-box {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
// 禁止flex自动扩充或收缩,拖拽必须要绝对单位
|
||||
& > div {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.part-ml {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.part-common {
|
||||
border-radius: 8px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.standard-catalogue {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
transition: width .2s linear;
|
||||
|
||||
/deep/ .ant-tree li .ant-tree-node-content-wrapper {
|
||||
max-width: calc(100% - 24px);
|
||||
|
||||
&:hover {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-tree li .ant-tree-node-content-wrapper.ant-tree-node-selected {
|
||||
background-color: transparent !important;
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.control-standard-catalogue-expand {
|
||||
width: 12px;
|
||||
height: 24px;
|
||||
border-radius: 0 12px 12px 0;
|
||||
background-color: @primary-color;
|
||||
color: white;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
left: 100%;
|
||||
top: calc(50% - 52px);
|
||||
|
||||
.anticon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -2px;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-catalogue {
|
||||
width: 280px;
|
||||
}
|
||||
// 拖拽会设置行内样式,这里要覆盖行内样式
|
||||
.no-expand-catalogue {
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
.sheet-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
//flex: 1;
|
||||
//width: 0;
|
||||
transition: width .2s linear;
|
||||
}
|
||||
|
||||
.clause-label {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
//overflow: hidden;
|
||||
transition: width .2s linear;
|
||||
|
||||
.clause-item {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
|
||||
label {
|
||||
width: 7.5em;
|
||||
font-size: 14px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
color: #86909C;
|
||||
margin-right: 20px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-content {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
font-size: 14px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
color: #1D2129;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hide-clause-label {
|
||||
width: 0 !important;
|
||||
overflow: hidden;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.clause-label-change {
|
||||
width: 130px;
|
||||
height: 100%;
|
||||
padding-top: 40px;
|
||||
position: relative;
|
||||
transition: width .2s linear;
|
||||
|
||||
&-item {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Semibold, PingFang SC, sans-serif;
|
||||
font-weight: 600;
|
||||
color: #1D2129;
|
||||
cursor: pointer;
|
||||
|
||||
.iconfont {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-item-selected {
|
||||
background: #FAE6E5;
|
||||
}
|
||||
}
|
||||
|
||||
.no-expand-clause-label {
|
||||
width: 45px;
|
||||
min-width: 45px;
|
||||
}
|
||||
|
||||
.part-title {
|
||||
height: 40px;
|
||||
background: rgba(213, 44, 38, 0.12);
|
||||
border-radius: 8px 8px 0 0;
|
||||
line-height: 40px;
|
||||
padding: 0 24px;
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Semibold, PingFang SC, sans-serif;
|
||||
font-weight: 600;
|
||||
color: #1D2129;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.has-title-part-content {
|
||||
height: calc(100% - 40px);
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.standard-name {
|
||||
height: 40px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.standard-name > span {
|
||||
font-size: 16px;
|
||||
font-family: PingFang SC-Semibold, PingFang SC, sans-serif;
|
||||
font-weight: 600;
|
||||
color: #1D2129;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.standard-content {
|
||||
height: calc(100% - 45px);
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
overflow: auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.clause-content-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&-title {
|
||||
font-size: 18px;
|
||||
font-family: PingFang SC-Medium, PingFang SC, sans-serif;
|
||||
font-weight: 600;
|
||||
color: #1D2129;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
font-size: 14px;
|
||||
font-family: PingFang SC-Regular, PingFang SC, sans-serif;
|
||||
font-weight: 500;
|
||||
color: #1D2129;
|
||||
line-height: 24px;
|
||||
|
||||
/deep/ table {
|
||||
width: 100%;
|
||||
border: 1px solid;
|
||||
|
||||
td {
|
||||
border: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ img.wordImg {
|
||||
width: auto !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-content-hight {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.control-clause-label-expand {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 24px;
|
||||
background-color: @primary-color;
|
||||
border-radius: 12px 0 0 12px;
|
||||
left: -12px;
|
||||
top: calc(50% - 52px);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
|
||||
.anticon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .ant-tree li span.ant-tree-switcher {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ant-spin-nested-loading {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ant-spin-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// 拖拽相关样式
|
||||
.draggable-border {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -3px;
|
||||
height: 100% !important;
|
||||
cursor: col-resize;
|
||||
}
|
||||
// 拖拽的线
|
||||
.draggable-border.dragging::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: @primary-color;
|
||||
}
|
||||
// 拖拽过程中禁止选中内容,防止拖拽过程中选中内容
|
||||
.disabledSelect {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@@ -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,457 @@
|
||||
<template>
|
||||
<j-modal
|
||||
:title="$t('uploadFile.file')"
|
||||
:width="width"
|
||||
:visible="visible"
|
||||
switchFullscreen
|
||||
:maskClosable="false"
|
||||
:confirmLoading="confirmLoading"
|
||||
:footer="null"
|
||||
@cancel="close">
|
||||
|
||||
<a-upload-dragger
|
||||
name="file"
|
||||
:multiple="multiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{'biz':bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="doBeforeUpload"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:returnUrl="returnUrl"
|
||||
:listType="complistType"
|
||||
@preview="handlePreview"
|
||||
@download="handleDownload"
|
||||
:showUploadList="{
|
||||
showDownloadIcon: isDownload
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:class="{'uploadty-disabled': disabled}"
|
||||
>
|
||||
<p class="upload-drag-icon">
|
||||
<a-icon type="cloud-upload" />
|
||||
</p>
|
||||
<p class="ant-upload-text">
|
||||
{{ $t('uploadFile.clickOrDragUpload') }}
|
||||
</p>
|
||||
</a-upload-dragger>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-empty v-if="disabled && (!fileList || fileList.length === 0)" />
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { downloadFile, getFileAccessHttpUrl } from '@/api/manage'
|
||||
import { getFileInfo } from '@/api/api'
|
||||
import { previewPdf } from '@/utils/previewPdf'
|
||||
import Vue from 'vue'
|
||||
import { kkFilePreview } from '../utils/kkFilePreview'
|
||||
|
||||
const FILE_TYPE_ALL = 'all'
|
||||
const FILE_TYPE_IMG = 'image'
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
// 本系统限制可以上传的文件格式
|
||||
const CAN_UPLOAD_FILE_TYPE = 'pdf,docx,doc,xlsx,xls,ppt,pptx,rar,zip,jpg,jpeg,png,avi,wmv,mov,rm,mp4,cad'
|
||||
|
||||
const Base64 = require('js-base64').Base64
|
||||
|
||||
// 支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']
|
||||
|
||||
export default {
|
||||
name: 'UploadFile',
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '点击上传'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: CAN_UPLOAD_FILE_TYPE
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
// 此属性被废弃了
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url,
|
||||
* true:仅返回url
|
||||
* false:返回fileName filePath fileSize
|
||||
*/
|
||||
returnUrl: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
beforeUpload: {
|
||||
type: Function
|
||||
},
|
||||
isDownload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabledUpload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fileMaxSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 500
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
width: 600,
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
headers: {},
|
||||
fileList: [],
|
||||
fileIds: null, // 文件id,可以是数组,也可以是字符串
|
||||
image: false,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
},
|
||||
complistType () {
|
||||
return this.fileType === FILE_TYPE_IMG ? 'picture-card' : 'text'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open (fileIds) {
|
||||
this.headers = { 'X-Access-Token': Vue.ls.get(ACCESS_TOKEN) }
|
||||
this.fileIds = fileIds
|
||||
this.initFileList()
|
||||
this.visible = true
|
||||
},
|
||||
async initFileList () {
|
||||
if (!this.fileIds) {
|
||||
return
|
||||
}
|
||||
let fileIdArr = []
|
||||
if (Array.isArray(this.fileIds)) {
|
||||
fileIdArr = this.fileIds
|
||||
} else {
|
||||
fileIdArr = this.fileIds.split(',')
|
||||
}
|
||||
const fileList = []
|
||||
for (const id of fileIdArr) {
|
||||
const fileInfo = await this.getFileInfoByFileId(id)
|
||||
if (fileInfo) {
|
||||
fileList.push(fileInfo)
|
||||
}
|
||||
}
|
||||
this.fileList = fileList
|
||||
console.log(this.fileList)
|
||||
},
|
||||
getFileInfoByFileId (id) {
|
||||
return new Promise(resolve => {
|
||||
let file
|
||||
getFileInfo({ id: id }).then(res => {
|
||||
if (res.success) {
|
||||
const fileInfo = res.result || {}
|
||||
file = {
|
||||
uid: fileInfo.id,
|
||||
name: fileInfo.fileName,
|
||||
status: 'done',
|
||||
url: fileInfo.url,
|
||||
pdfId: fileInfo.pdfId,
|
||||
// response用于下载和预览
|
||||
response: {
|
||||
success: true,
|
||||
result: {
|
||||
id: fileInfo.id,
|
||||
fileName: fileInfo.fileName
|
||||
},
|
||||
status: 'history'
|
||||
}
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
resolve(file)
|
||||
})
|
||||
})
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
setTimeout(() => {
|
||||
this.fileList = []
|
||||
}, 500)
|
||||
},
|
||||
handleChange (info) {
|
||||
console.log(info, this.uploadGoOn)
|
||||
if (!info.file.status && this.uploadGoOn === false) {
|
||||
info.fileList.pop()
|
||||
}
|
||||
console.log(info.fileList)
|
||||
let fileList = info.fileList
|
||||
if (info.file.status === 'done') {
|
||||
console.log(this.number)
|
||||
if (this.number > 0) {
|
||||
console.log(fileList)
|
||||
fileList = fileList.slice(-this.number)
|
||||
console.log(fileList)
|
||||
}
|
||||
if (info.file.response.success) {
|
||||
fileList = fileList.map((file) => {
|
||||
console.log(file)
|
||||
if (file.response) {
|
||||
// const reUrl = `${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.response.result.fileName}`
|
||||
// TODO getFileAccessHttpUrl方法会追加token,在之后拼参数
|
||||
file.url = getFileAccessHttpUrl(file.response.result.id) + '&fullfilename=' + file.response.result.fileName
|
||||
}
|
||||
// 校验不通过的文件需要筛出去
|
||||
if (file.uploadGoOn === false) {
|
||||
return null
|
||||
}
|
||||
return file
|
||||
})
|
||||
} else {
|
||||
this.$message.warning(info.file.response.message)
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.warning(`${info.file.name} ${this.$t('uploadFile.uploadFailed')}.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
fileList = fileList.filter(tt => !!tt)
|
||||
console.log(fileList)
|
||||
// 二次过滤不符合要求的(uploadGoOn为false的),否则只上传多个不符合要求的只会有一个不出现在列表中
|
||||
fileList = fileList.filter(tt => tt.uploadGoOn === undefined || tt.uploadGoOn === null || tt.uploadGoOn === true)
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
// returnUrl为true时仅返回文件路径
|
||||
if (this.returnUrl) {
|
||||
this.handlePathChange()
|
||||
} else {
|
||||
// returnUrl为false时返回文件名称、文件路径及文件大小
|
||||
this.newFileList = []
|
||||
for (let a = 0; a < fileList.length; a++) {
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if (fileList[a].status === 'done') {
|
||||
const fileJson = {
|
||||
id: fileList[a].response.result.id,
|
||||
fileName: fileList[a].name,
|
||||
filePath: fileList[a].url,
|
||||
fileSize: fileList[a].size
|
||||
}
|
||||
this.newFileList.push(fileJson)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
}
|
||||
this.$emit('change', this.newFileList)
|
||||
}
|
||||
}
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
console.log(file)
|
||||
if (file.pdfId) {
|
||||
// 说明是onlyOffice生成的
|
||||
const url = previewPdf(file.pdfId)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = (file.name ? file.name.split('.')[file.name.split('.').length - 1] : '').toLowerCase()
|
||||
const canPreview = CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (!canPreview) {
|
||||
this.$message.loading(this.$t('uploadFile.cannotPreview')).then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/view/${file.response.result.id}?at=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.name}`
|
||||
console.log(fileSuffix)
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.response.result.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (FILE_TYPE_PDF.includes(fileSuffix)) {
|
||||
const url = previewPdf(file.response.result.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
kkFilePreview(fileFullUrl)
|
||||
},
|
||||
handleDownload (file) {
|
||||
// 下载文件
|
||||
downloadFile(`/sys/common/download/${file.response.result.id}`, file.name)
|
||||
},
|
||||
doBeforeUpload (file) {
|
||||
this.uploadGoOn = true
|
||||
file.uploadGoOn = true
|
||||
const fileSize = file.size // 上传的文件大小
|
||||
if (fileSize === 0) {
|
||||
this.$message.warning(this.$t('uploadFile.cannotUploadEmpty'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileMaxSize && fileSize > 1024 * 1024 * this.fileMaxSize) {
|
||||
this.$message.warning(this.$t('uploadFile.pleaseUpload') + this.fileMaxSize + this.$t('uploadFile.theFollowingDocuments'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (fileSize > 1024 * 1024 * 500) {
|
||||
this.$message.warning(this.$t('uploadFile.maxSize'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_ALL) {
|
||||
return true
|
||||
}
|
||||
const fileType = file.type
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = (file.name ? file.name.split('.')[file.name.split('.').length - 1] : '').toLowerCase()
|
||||
if (this.fileType === CAN_UPLOAD_FILE_TYPE && CAN_UPLOAD_FILE_TYPE.split(',').includes(fileSuffix)) {
|
||||
return true
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && fileType.indexOf('image') < 0) {
|
||||
this.$message.warning(this.$t('uploadFile.onlyUploadPic'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
return true
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && !FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.$message.warning(this.$t('uploadFile.pleaseUpload') + FILE_TYPE_IMGS.join('、') + this.$t('uploadFile.file'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType.indexOf(fileSuffix) === -1) {
|
||||
this.$message.warning(this.$t('uploadFile.pleaseUpload') + `${this.fileType}` + this.$t('uploadFile.file'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
// 扩展 beforeUpload 验证
|
||||
if (typeof this.beforeUpload === 'function') {
|
||||
return this.beforeUpload(file)
|
||||
}
|
||||
return true
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
console.log(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.upload-drag-icon {
|
||||
font-size: 70px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/*禁用情况下,不显示上传操作区域*/
|
||||
/deep/ .ant-upload.ant-upload-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 索赔上传需要根据传入的值判断是否可以上传
|
||||
.upload-disabled {
|
||||
/deep/ .ant-upload {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 不可删除文件
|
||||
.delete-disabled {
|
||||
/deep/ .ant-upload-list-item-card-actions {
|
||||
a:nth-child(2) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
import Vue from 'vue'
|
||||
/**
|
||||
* 省市区
|
||||
*/
|
||||
export default class Area {
|
||||
/**
|
||||
* 构造器
|
||||
* @param pcaa
|
||||
*/
|
||||
constructor (pcaa) {
|
||||
if (!pcaa) {
|
||||
pcaa = Vue.prototype.$Jpcaa
|
||||
}
|
||||
const arr = []
|
||||
const province = pcaa['86']
|
||||
Object.keys(province).map(key => {
|
||||
arr.push({ id: key, text: province[key], pid: '86', index: 1 })
|
||||
const city = pcaa[key]
|
||||
Object.keys(city).map(key2 => {
|
||||
arr.push({ id: key2, text: city[key2], pid: key, index: 2 })
|
||||
const qu = pcaa[key2]
|
||||
if (qu) {
|
||||
Object.keys(qu).map(key3 => {
|
||||
arr.push({ id: key3, text: qu[key3], pid: key2, index: 3 })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
this.all = arr
|
||||
}
|
||||
|
||||
get pca () {
|
||||
return this.all
|
||||
}
|
||||
|
||||
getCode (text) {
|
||||
if (!text || text.length === 0) {
|
||||
return ''
|
||||
}
|
||||
for (const item of this.all) {
|
||||
if (item.text === text) {
|
||||
return item.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getText (code) {
|
||||
if (!code || code.length === 0) {
|
||||
return ''
|
||||
}
|
||||
const arr = []
|
||||
this.getAreaBycode(code, arr, 3)
|
||||
return arr.join('/')
|
||||
}
|
||||
|
||||
getRealCode (code) {
|
||||
const arr = []
|
||||
this.getPcode(code, arr, 3)
|
||||
return arr
|
||||
}
|
||||
|
||||
getPcode (id, arr, index) {
|
||||
for (const item of this.all) {
|
||||
if (item.id === id && item.index === index) {
|
||||
arr.unshift(id)
|
||||
if (item.pid !== '86') {
|
||||
this.getPcode(item.pid, arr, --index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAreaBycode (code, arr, index) {
|
||||
for (const item of this.all) {
|
||||
if (item.id === code && item.index === index) {
|
||||
arr.unshift(item.text)
|
||||
if (item.pid !== '86') {
|
||||
this.getAreaBycode(item.pid, arr, --index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 获取字符串的长度ascii长度为1 中文长度为2
|
||||
* @param str
|
||||
* @returns {number}
|
||||
*/
|
||||
export const getStrFullLength = (str = '') =>
|
||||
str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
return pre + 1
|
||||
}
|
||||
return pre + 2
|
||||
}, 0)
|
||||
|
||||
/**
|
||||
* 给定一个字符串和一个长度,将此字符串按指定长度截取
|
||||
* @param str
|
||||
* @param maxLength
|
||||
* @returns {string}
|
||||
*/
|
||||
export const cutStrByFullLength = (str = '', maxLength) => {
|
||||
let showLength = 0
|
||||
return str.split('').reduce((pre, cur) => {
|
||||
const charCode = cur.charCodeAt(0)
|
||||
if (charCode >= 0 && charCode <= 128) {
|
||||
showLength += 1
|
||||
} else {
|
||||
showLength += 2
|
||||
}
|
||||
if (showLength <= maxLength) {
|
||||
return pre + cur
|
||||
}
|
||||
return pre
|
||||
}, '')
|
||||
}
|
||||
|
||||
// 下划线转换驼峰
|
||||
export function underLinetoHump (name) {
|
||||
return name.replace(/_(\w)/g, function (all, letter) {
|
||||
return letter.toUpperCase()
|
||||
})
|
||||
}
|
||||
// 驼峰转换下划线
|
||||
export function humptoUnderLine (name) {
|
||||
return name.replace(/([A-Z])/g, '_$1').toLowerCase()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* components util
|
||||
*/
|
||||
|
||||
/**
|
||||
* 清理空值,对象
|
||||
* @param children
|
||||
* @returns {*[]}
|
||||
*/
|
||||
export function filterEmpty (children = []) {
|
||||
return children.filter(c => c.tag || (c.text && c.text.trim() !== ''))
|
||||
}
|
||||
@@ -0,0 +1,890 @@
|
||||
<template>
|
||||
<a-spin :spinning="loading">
|
||||
<a-form-model :model="formInline" v-if="isFormInline" class="form-add" :rules="rules" ref="ruleForm">
|
||||
<div v-for="(val,index) in dataList" :key="index">
|
||||
<div v-if="val && val.length > 0" class="header-text">{{ index }}</div>
|
||||
<div v-if="val && val.length > 0" class="form-flex-box">
|
||||
<template v-for="(item, index) in val">
|
||||
<div :key="index"
|
||||
class="box-title-text form-flex-item-half"
|
||||
:class="{'form-flex-item-line': item.fieldShowType === FieldType.STANDARD_MORE.value}"
|
||||
v-if="!hiddenFieldList.includes(item.dbFieldName)">
|
||||
<div class="title-text">
|
||||
<span class="required" v-if="item.fieldMustInput === '1' || rules[item.dbFieldName][0].required">*</span>
|
||||
<span class="title-text-text" :title="item.dbFieldTxt">{{ item.dbFieldTxt }}</span>
|
||||
</div>
|
||||
<a-form-model-item class="item-model" :prop="item.dbFieldName">
|
||||
<!-- 1, 日期单选 -->
|
||||
<a-date-picker v-if="item.fieldShowType === FieldType.DATE_SINGLE.value"
|
||||
class="box-input"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:getCalendarContainer="(trigger) => trigger.parentNode"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
style="width: 100%" />
|
||||
<!-- 2, 单选用户 -->
|
||||
<user-selection v-else-if="item.fieldShowType === FieldType.USER_SINGLE.value"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:filedName="item.dbFieldName"
|
||||
@nameChange="userSelectNameChange"
|
||||
:nameStr="formInline[item.dbFieldName + '_dictText']"
|
||||
type="radio"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)" />
|
||||
<!-- 3, 单选组织机构 -->
|
||||
<j-select-depart v-else-if="item.fieldShowType === FieldType.ORGAN_SINGLE.value"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:backDepart="true" />
|
||||
<!-- 4, 日期多选 -->
|
||||
<j-multiple-date-picker v-else-if="item.fieldShowType === FieldType.DATE_MORE.value"
|
||||
class="box-input"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:fieldName="item.dbFieldName"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)" />
|
||||
<!-- 5, 多行文本 -->
|
||||
<a-textarea v-else-if="item.fieldShowType === FieldType.TEXTAREA.value"
|
||||
:placeholder="$t('pleaseEnter')+item.dbFieldTxt"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
:maxLength="500"
|
||||
v-model="formInline[item.dbFieldName]" :rows="4" />
|
||||
<!-- 6, 标准多选 -->
|
||||
<standard-selection v-else-if="item.fieldShowType === FieldType.STANDARD_MORE.value"
|
||||
:placeholder="specialPlaceholder[item.dbFieldName] || $t('pleaseSelectStandard')"
|
||||
:source="getFlag(item.dbFieldName)"
|
||||
:disabledSourceSearch="!selectStandardCanUpdateTypeField.includes(item.dbFieldName)"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
:exclude-standard-numbers="excludeStandardFields.includes(item.dbFieldName) ? excludeStandardNumbers : null"
|
||||
v-model="formInline[item.dbFieldName]" />
|
||||
<!-- 7, 多选组织机构 -->
|
||||
<j-select-depart v-else-if="item.fieldShowType === FieldType.ORGAN_MORE.value"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName) || innerDisabledField.includes(item.dbFieldName)"
|
||||
:selectRange="item.dbFieldName === 'auth_dept' ? departSelectRange : []"
|
||||
:multi="true"
|
||||
:backDepart="true"
|
||||
:treeOpera="true" />
|
||||
<!-- 8, 上传文件 -->
|
||||
<a-button v-else-if="item.fieldShowType === FieldType.FILE_UP.value" type="primary" class="button-text"
|
||||
@click="clickButtonToUpload(item)">
|
||||
{{
|
||||
(formInline[item.dbFieldName] === 'null' || formInline[item.dbFieldName] === ''
|
||||
|| formInline[item.dbFieldName] === null || formInline[item.dbFieldName] === undefined)
|
||||
? $t('uploadFile.clickUpload')
|
||||
: $t('uploadFile.viewUploadedFiles')
|
||||
}}
|
||||
</a-button>
|
||||
<!-- 9, 文本框 -->
|
||||
<a-input v-else-if="item.fieldShowType === FieldType.TEXT_BOX.value"
|
||||
class="box-input"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:maxLength="item.dbLength"
|
||||
:placeholder="specialPlaceholder[item.dbFieldName] || $t('pleaseEnter')+item.dbFieldTxt" />
|
||||
<!-- 10, 下拉单选(数据字典) -->
|
||||
<j-dict-select-tag v-else-if="item.fieldShowType === FieldType.OPTION_SINGLE.value"
|
||||
class="box-input"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName) || innerDisabledField.includes(item.dbFieldName)"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:type="'select'"
|
||||
:triggerChange="false"
|
||||
:options="optionsData[item.dbFieldName]"
|
||||
:dictCode="item.fieldHref || dictNotCodes.includes(item.dbFieldName) ? null : item.dictField" />
|
||||
<!-- 11, 下拉多选(数据字典) -->
|
||||
<j-multi-select-tag v-else-if="item.fieldShowType === FieldType.OPTION_MORE.value"
|
||||
class="box-input"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:type="'select'"
|
||||
:triggerChange="false"
|
||||
:options="optionsData[item.dbFieldName]"
|
||||
:openSelectedAll="openSelectedAllFieldList.includes(item.dbFieldName)"
|
||||
:dictCode="item.fieldHref ? null : item.dictField" />
|
||||
<!-- 12, 树选择 -->
|
||||
<s-tree-select
|
||||
v-else-if="item.fieldShowType === FieldType.TREE.value"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
:tree-data="optionsData[item.dbFieldName]"
|
||||
tree-checkable
|
||||
treeCheckStrictly
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt" />
|
||||
<!-- 14, 年份选择 -->
|
||||
<j-date v-else-if="item.fieldShowType === FieldType.YEAR_PICKER.value"
|
||||
show-type="year"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:default-value="defaultValue[item.dbFieldName]"
|
||||
date-format="YYYY"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt" />
|
||||
<!-- 15, 树选择(单选) -->
|
||||
<s-tree-select v-else-if="item.fieldShowType === FieldType.TREE_SINGLE.value"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)"
|
||||
:tree-data="optionsData[item.dbFieldName]"
|
||||
:label-in-value="false"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt" />
|
||||
<!--16、树形弹框选择-->
|
||||
<tree-selection v-else-if="item.fieldShowType === FieldType.TREE_MODAL_SELECT.value"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
v-model="formInline[item.dbFieldName]"
|
||||
@nameChange="treeSelectNameChange"
|
||||
:filedName="item.dbFieldName"
|
||||
type="checkbox"
|
||||
:options-href="item.fieldHref"
|
||||
:nameStr="formInline[item.dbFieldName + '_dictText']"
|
||||
:dict-id="item.dictId"
|
||||
:disabled="disabled || disabledFieldList.includes(item.dbFieldName)" />
|
||||
</a-form-model-item>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<upload-file ref="uploadFile" @change="uploadFileChange" :return-url="false"></upload-file>
|
||||
</a-form-model>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@assets/less/common.less'
|
||||
import JDictSelectTag from '../dict/JDictSelectTag'
|
||||
import JMultiSelectTag from '../dict/JMultiSelectTag'
|
||||
import UploadFile from '../UploadFile'
|
||||
// 选择标准选择器
|
||||
import StandardSelection from '../selection/StandardSelection'
|
||||
import {
|
||||
FieldType,
|
||||
StandardGradeClassify,
|
||||
StandardProperty,
|
||||
IsTrial,
|
||||
EnterpriseStandardStatus,
|
||||
StandardSource,
|
||||
StandardStatus, EnterpriseStandardCode, EnterpriseNameCode
|
||||
} from '../../enums/commonEnums'
|
||||
import JMultipleDatePicker from '../JMultipleDatePicker'
|
||||
import UserSelection from '../selection/UserSelection'
|
||||
import { getAction } from '../../api/manage.js'
|
||||
import { ajaxGetDictItems, getDictByCode, getFormDictTreeList } from '../../api/api.js'
|
||||
import TreeSelection from '../selection/TreeSelection'
|
||||
import { getStandardGradeClassification } from '../../api/enterpriseStandardLibraryApi'
|
||||
import STreeSelect from '../STreeSelect'
|
||||
|
||||
export default {
|
||||
name: 'AddForm',
|
||||
components: {
|
||||
UserSelection,
|
||||
JMultipleDatePicker,
|
||||
StandardSelection,
|
||||
JDictSelectTag,
|
||||
JMultiSelectTag,
|
||||
UploadFile,
|
||||
TreeSelection,
|
||||
STreeSelect
|
||||
},
|
||||
props: {
|
||||
url: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 获取表单数据的方法
|
||||
getFormFunc: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: () => {
|
||||
return function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取详细信息的方法
|
||||
getDocumentInfoFunc: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: () => {
|
||||
return function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
// 标准模块的区分:1,国内; 2,国外;3,企标
|
||||
flag: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
extraParams: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 特殊的placeholder,目前只处理了输入框,有需要再处理别的
|
||||
specialPlaceholder: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 默认值
|
||||
defaultValue: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 不可修改值
|
||||
disabledFieldList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 数据字典多选,需要全选的字段列表
|
||||
openSelectedAllFieldList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 需要隐藏的字段
|
||||
hiddenFieldList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 不包含的标准从哪个字段取值
|
||||
excludeStandardValFormFields: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'standard_number'
|
||||
},
|
||||
// 需要处理不包含标准的字段
|
||||
excludeStandardFields: {
|
||||
type: [Array],
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否需要进行国内标准的特殊校验
|
||||
domesticSpecialCheck: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 选择标准字段和选择来源的对应关系
|
||||
flagByField: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 选择标准组件可以修改来源的字段(暂时只有企标的引用标准用到)
|
||||
selectStandardCanUpdateTypeField: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 如果是企标就获取企标级别映射数据
|
||||
if (this.flag === StandardSource.ENTERPRISE.value) {
|
||||
getStandardGradeClassification().then(res => {
|
||||
if (res.success) {
|
||||
this.standardGradeClassification = res.result
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
FieldType,
|
||||
StandardSource,
|
||||
loading: false,
|
||||
formInline: {},
|
||||
isFormInline: false,
|
||||
rules: {},
|
||||
confirmLoading: false,
|
||||
type: 2,
|
||||
dataList: [],
|
||||
ruleList: [],
|
||||
uploadName: '',
|
||||
optionsData: {}, // 用接口获取到的下拉数据
|
||||
excludeStandardNumbers: '', // 不包含的标准号
|
||||
standardGradeClassification: [], // 企标级别映射
|
||||
innerDisabledField: [], // 内部不可编辑字段
|
||||
departSelectRange: [], // 选择部门的选择范围,传了就只能从这些里面选,不传就都能选
|
||||
isEditSetData: false, // 正在编辑回显数据
|
||||
oldMainDraftingUnit: '', // 上一次的主起草单位
|
||||
dictNotCodes: ['name_code'] // 用到字典的字段不传dictCode的字段
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 国内的零部件分类、适用认证、适用车型和标准性质关联,准入标准必填,非准入标准不必填
|
||||
'formInline.standard_property' () {
|
||||
this.handleDomesticSpecialCheck()
|
||||
this.handleStatePropertyCheck()
|
||||
},
|
||||
// 标准等级分类改变后授权部门需要自动带出
|
||||
'formInline.grade_classification' (val) {
|
||||
if (val && this.flag === StandardSource.ENTERPRISE.value && !this.isEditSetData) {
|
||||
if (val === StandardGradeClassify.ZERO_LEVEL.value) {
|
||||
// 选的是0级,授权部门带出不可编辑
|
||||
this.formInline.auth_dept = this.standardGradeClassification.find(item => item.level + '' === '0').deptIds
|
||||
if (!this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField.push('auth_dept')
|
||||
}
|
||||
} else if (val === StandardGradeClassify.ONE_LEVEL.value) {
|
||||
// 选的是1级
|
||||
// // 带出3级的部门:起草部门
|
||||
// this.formInline.auth_dept = this.standardGradeClassification.find(item => item.level + '' === '3').deptIds
|
||||
// 带出主起草单位
|
||||
this.formInline.auth_dept = this.formInline.main_drafting_unit
|
||||
// 设置可选择项
|
||||
this.departSelectRange = this.standardGradeClassification.find(item => item.level + '' === '1').deptIds.split(',')
|
||||
this.formInline = JSON.parse(JSON.stringify(this.formInline))
|
||||
// 设置授权部门可编辑
|
||||
if (this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'auth_dept')
|
||||
}
|
||||
} else if (val === StandardGradeClassify.TWO_LEVEL.value) {
|
||||
// 选的是2级
|
||||
// // 带出3级的部门:起草部门
|
||||
// this.formInline.auth_dept = this.standardGradeClassification.find(item => item.level + '' === '3').deptIds
|
||||
// 带出主起草单位
|
||||
this.formInline.auth_dept = this.formInline.main_drafting_unit
|
||||
// 设置可选择项
|
||||
this.departSelectRange = this.standardGradeClassification.find(item => item.level + '' === '2').deptIds.split(',')
|
||||
this.formInline = JSON.parse(JSON.stringify(this.formInline))
|
||||
// 设置授权部门可编辑
|
||||
if (this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'auth_dept')
|
||||
}
|
||||
} else if (val === StandardGradeClassify.THREE_LEVEL.value) {
|
||||
// // 选的是3级,授权部门带出不可编辑
|
||||
// this.formInline.auth_dept = this.standardGradeClassification.find(item => item.level + '' === '3').deptIds
|
||||
// 选的是3级,带出主起草单位
|
||||
this.formInline.auth_dept = this.formInline.main_drafting_unit
|
||||
if (!this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField.push('auth_dept')
|
||||
}
|
||||
}
|
||||
} else if (val && this.flag === StandardSource.ENTERPRISE.value && this.isEditSetData) {
|
||||
if (val === StandardGradeClassify.ZERO_LEVEL.value) {
|
||||
// 选的是0级,授权部门带出不可编辑
|
||||
if (!this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField.push('auth_dept')
|
||||
}
|
||||
} else if (val === StandardGradeClassify.ONE_LEVEL.value) {
|
||||
// 选的是1级
|
||||
// 设置可选择项
|
||||
this.departSelectRange = this.standardGradeClassification.find(item => item.level + '' === '1').deptIds.split(',')
|
||||
// 设置授权部门可编辑
|
||||
if (this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'auth_dept')
|
||||
}
|
||||
} else if (val === StandardGradeClassify.TWO_LEVEL.value) {
|
||||
// 选的是2级
|
||||
// 设置可选择项
|
||||
this.departSelectRange = this.standardGradeClassification.find(item => item.level + '' === '2').deptIds.split(',')
|
||||
// 设置授权部门可编辑
|
||||
if (this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'auth_dept')
|
||||
}
|
||||
} else if (val === StandardGradeClassify.THREE_LEVEL.value) {
|
||||
// 选的是3级,授权部门带出不可编辑
|
||||
if (!this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField.push('auth_dept')
|
||||
}
|
||||
}
|
||||
} else if (!val && this.flag === StandardSource.ENTERPRISE.value && !this.isEditSetData) {
|
||||
// 清空授权部门的可选项
|
||||
this.departSelectRange = []
|
||||
// 设置授权部门可编辑
|
||||
if (this.innerDisabledField.includes('auth_dept')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'auth_dept')
|
||||
}
|
||||
// 清空授权部门
|
||||
this.formInline.auth_dept = ''
|
||||
}
|
||||
},
|
||||
// 主起草单位改变后,需要将选中的主起草单位增加到授权部门
|
||||
'formInline.main_drafting_unit' (val) {
|
||||
if (val) {
|
||||
if (this.formInline.grade_classification === StandardGradeClassify.ONE_LEVEL.value ||
|
||||
this.formInline.grade_classification === StandardGradeClassify.TWO_LEVEL.value) {
|
||||
// 标准等级分类是1级或者2级需要,把主起草单位追加到授权部门
|
||||
let authDeptArr = (this.formInline.auth_dept || '').split(',')
|
||||
// 把旧的主起草部门刨出去,否则每换一次主起草单位都会累计到授权部门
|
||||
authDeptArr = authDeptArr.filter(item => item !== this.oldMainDraftingUnit)
|
||||
authDeptArr.push(val)
|
||||
this.formInline.auth_dept = authDeptArr.join(',')
|
||||
} else if (this.formInline.grade_classification === StandardGradeClassify.THREE_LEVEL.value) {
|
||||
// 标准等级分类是3级,把授权部门替换成主起草单位
|
||||
this.formInline.auth_dept = val
|
||||
}
|
||||
this.oldMainDraftingUnit = val
|
||||
}
|
||||
},
|
||||
// 企标是否试行改变,标准状态需要跟着变,试行周期必填校验也跟着变
|
||||
'formInline.try_flag' (val) {
|
||||
if (val && this.flag === StandardSource.ENTERPRISE.value && !this.isEditSetData) {
|
||||
if (val === IsTrial.YES.value) {
|
||||
// 试行,标准状态改为试运行,
|
||||
this.formInline.standard_state = EnterpriseStandardStatus.TRIAL_OPERATION.value
|
||||
// 试行周期给默认值24
|
||||
this.formInline.trial_period = '24'
|
||||
this.formInline = JSON.parse(JSON.stringify(this.formInline))
|
||||
// 试行,标准状态不可修改
|
||||
if (!this.innerDisabledField.includes('standard_state')) {
|
||||
this.innerDisabledField.push('standard_state')
|
||||
}
|
||||
// 试行,试行周期必填
|
||||
this.rules.trial_period[0].required = true
|
||||
} else {
|
||||
// 试行周期置空
|
||||
this.formInline.trial_period = undefined
|
||||
// 不试行,标准状态可修改
|
||||
if (this.innerDisabledField.includes('standard_state')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'standard_state')
|
||||
}
|
||||
// 不试行,试行周期非必填
|
||||
this.rules.trial_period[0].required = false
|
||||
}
|
||||
} else if (val && this.flag === StandardSource.ENTERPRISE.value && this.isEditSetData) {
|
||||
if (val === IsTrial.YES.value) {
|
||||
// 编辑回显得时候改变,设置标准状态不可编辑,试行周期必填
|
||||
// 试行,试行周期必填
|
||||
this.rules.trial_period[0].required = true
|
||||
} else {
|
||||
// 不试行,试行周期非必填
|
||||
this.rules.trial_period[0].required = false
|
||||
}
|
||||
}
|
||||
},
|
||||
// 编辑回显标准状态是废止,标准状态不可更改
|
||||
'formInline.standard_state' (val) {
|
||||
if (val && this.isEditSetData && (
|
||||
(this.flag === StandardSource.ENTERPRISE.value && val === EnterpriseStandardStatus.ABOLISH.value) ||
|
||||
(this.flag === StandardSource.DOMESTIC.value && val === StandardStatus.ABOLISH.value) ||
|
||||
(this.flag === StandardSource.OVERSEAS.value && val === StandardStatus.ABOLISH.value))) {
|
||||
// 状态是废止
|
||||
if (!this.innerDisabledField.includes('standard_state')) {
|
||||
this.innerDisabledField.push('standard_state')
|
||||
}
|
||||
} else if (this.formInline.try_flag === IsTrial.YES.value && this.flag === StandardSource.ENTERPRISE.value && this.isEditSetData) {
|
||||
// 企标,是否试行选的是,需要标准状态不可编辑
|
||||
if (!this.innerDisabledField.includes('standard_state')) {
|
||||
this.innerDisabledField.push('standard_state')
|
||||
}
|
||||
} else {
|
||||
// 状态不是废止
|
||||
if (this.innerDisabledField.includes('standard_state')) {
|
||||
this.innerDisabledField = this.innerDisabledField.filter(item => item !== 'standard_state')
|
||||
}
|
||||
}
|
||||
// 起草、征求依据、报批、废止状态时发布日期、实施日期不必填
|
||||
this.handleRequiredByState(val)
|
||||
this.handleStatePropertyCheck()
|
||||
},
|
||||
// 企标:企业标准代号选Q,企业名称代号只能选ZZ\ZR;标准代号选TS,企业名称代号只能选ZZ
|
||||
'formInline.standard_code' (val) {
|
||||
if (this.flag === StandardSource.ENTERPRISE.value && !this.isEditSetData) {
|
||||
if (val === EnterpriseStandardCode.Q.value) {
|
||||
// 选的Q,企业名称代号只能选ZZ\ZR
|
||||
this.optionsData.name_code.map(item => {
|
||||
if (![EnterpriseNameCode.ZZ.value, EnterpriseNameCode.ZR.value].includes(item.value)) {
|
||||
item.disabled = true
|
||||
} else {
|
||||
item.disabled = false
|
||||
}
|
||||
})
|
||||
} else if (val === EnterpriseStandardCode.TS.value) {
|
||||
// 选的TS,企业名称代号只能选ZZ
|
||||
this.optionsData.name_code.map(item => {
|
||||
if (![EnterpriseNameCode.ZZ.value].includes(item.value)) {
|
||||
item.disabled = true
|
||||
} else {
|
||||
item.disabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this.formInline.name_code !== EnterpriseNameCode.ZZ.value) {
|
||||
this.formInline.name_code = undefined
|
||||
this.formInline = Object.assign({}, this.formInline)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add () {
|
||||
this.formInline = Object.assign({}, this.defaultValue)
|
||||
this.$forceUpdate()
|
||||
this.getForm()
|
||||
},
|
||||
edit (item, callback) {
|
||||
this.loading = true
|
||||
this.getForm(() => {
|
||||
if (!this.getDocumentInfoFunc || typeof this.getDocumentInfoFunc !== 'function') {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
// type标识是新增表单还是详情页
|
||||
this.getDocumentInfoFunc({ id: item.id, type: this.type }).then((res) => {
|
||||
if (res.success) {
|
||||
// 设置正在编辑回显值
|
||||
this.isEditSetData = true
|
||||
// 处理树结构多选回显
|
||||
const formList = Object.values(this.dataList).reduce((acc, cur) => acc.concat(cur), [])
|
||||
const treeMultipleField = formList.filter(tt => tt.fieldShowType === FieldType.TREE.value)
|
||||
this.formInline = res.result
|
||||
callback && callback(this.formInline)
|
||||
treeMultipleField.forEach(field => {
|
||||
const valueArr = []
|
||||
if (this.formInline[field.dbFieldName]) {
|
||||
const values = this.formInline[field.dbFieldName].split(',')
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
valueArr[i] = {
|
||||
value: values[i]
|
||||
}
|
||||
}
|
||||
this.formInline[field.dbFieldName] = valueArr
|
||||
console.log(this.formInline[field.dbFieldName])
|
||||
} else {
|
||||
this.formInline[field.dbFieldName] = undefined
|
||||
}
|
||||
})
|
||||
const dictField = formList.filter(tt => tt.fieldShowType === FieldType.OPTION_SINGLE.value)
|
||||
dictField.forEach(field => {
|
||||
this.formInline[field.dbFieldName] = this.formInline[field.dbFieldName] || null
|
||||
})
|
||||
|
||||
// 处理标准选择不能选自己
|
||||
this.excludeStandardNumbers = this.formInline[this.excludeStandardValFormFields]
|
||||
this.handleDomesticSpecialCheck()
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
}).finally(() => {
|
||||
// 设置编辑回显值完成
|
||||
this.isEditSetData = false
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取表单列表
|
||||
getForm (callBack) {
|
||||
if (!this.getFormFunc || typeof this.getFormFunc !== 'function') {
|
||||
return
|
||||
}
|
||||
this.confirmLoading = true
|
||||
const params = {
|
||||
module: this.flag,
|
||||
type: this.type
|
||||
}
|
||||
this.getFormFunc(params).then((res) => {
|
||||
if (res.success) {
|
||||
this.dataList = res.result
|
||||
this.ruleList = res.result
|
||||
this.integrateData()
|
||||
this.getOptionsData() // 获取下拉数据
|
||||
callBack && callBack()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 设置表单校验
|
||||
integrateData () {
|
||||
this.isFormInline = false
|
||||
const ruleList = []
|
||||
for (const item in this.ruleList) {
|
||||
this.ruleList[item].forEach(ol => {
|
||||
ruleList.push(ol)
|
||||
})
|
||||
}
|
||||
const rules = {}
|
||||
ruleList.forEach((res, index) => {
|
||||
const rule = []
|
||||
if (res.fieldMustInput === '1') {
|
||||
if ([FieldType.TEXT_BOX.value, FieldType.TEXTAREA.value].includes(res.fieldShowType)) {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.dbFieldTxt + this.$t('cannotEmpty'),
|
||||
trigger: 'blur'
|
||||
})
|
||||
} else {
|
||||
rule.push({
|
||||
required: true,
|
||||
message: res.dbFieldTxt + this.$t('cannotEmpty'),
|
||||
trigger: 'change'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if ([FieldType.TEXT_BOX.value, FieldType.TEXTAREA.value].includes(res.fieldShowType)) {
|
||||
rule.push({
|
||||
required: false,
|
||||
message: res.dbFieldTxt + this.$t('cannotEmpty'),
|
||||
trigger: 'blur'
|
||||
})
|
||||
} else {
|
||||
rule.push({
|
||||
required: false,
|
||||
message: res.dbFieldTxt + this.$t('cannotEmpty'),
|
||||
trigger: 'change'
|
||||
})
|
||||
}
|
||||
}
|
||||
if (rule.length > 0) {
|
||||
rules[res.dbFieldName] = rule
|
||||
}
|
||||
})
|
||||
this.rules = rules
|
||||
this.isFormInline = true
|
||||
this.confirmLoading = false
|
||||
},
|
||||
/**
|
||||
* 处理需要通过接口获取的下拉数据
|
||||
*/
|
||||
getOptionsData () {
|
||||
const formList = Object.values(this.dataList).reduce((acc, cur) => acc.concat(cur), [])
|
||||
formList.forEach(item => {
|
||||
// 如果接口这个字段有值
|
||||
if (item.fieldHref) {
|
||||
getAction(item.fieldHref).then(res => {
|
||||
if (res.success) {
|
||||
this.$set(this.optionsData, item.dbFieldName, res.result)
|
||||
}
|
||||
})
|
||||
} else if ((item.fieldShowType === FieldType.TREE.value || item.fieldShowType === FieldType.TREE_SINGLE.value) && item.dictId) {
|
||||
getFormDictTreeList({ dictId: item.dictId }).then(res => {
|
||||
if (res.success) {
|
||||
this.$set(this.optionsData, item.dbFieldName, res.result)
|
||||
}
|
||||
})
|
||||
} else if (item.fieldShowType === FieldType.OPTION_SINGLE.value && this.dictNotCodes.includes(item.dbFieldName)) {
|
||||
ajaxGetDictItems(item.dictField).then(res => {
|
||||
if (res.success) {
|
||||
const result = JSON.parse(JSON.stringify(res.result))
|
||||
if (item.dbFieldName === 'name_code') {
|
||||
for (const dictItem of result) {
|
||||
if ([EnterpriseNameCode.WQ.value, EnterpriseNameCode.JC.value, EnterpriseNameCode.JHK.value].includes(dictItem.value)) {
|
||||
dictItem.disabled = true
|
||||
} else {
|
||||
dictItem.disabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('获取企业名称代号下拉框数据成功了', result)
|
||||
this.$set(this.optionsData, item.dbFieldName, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 点击点击上传按钮
|
||||
clickButtonToUpload (item) {
|
||||
this.$refs.uploadFile.open(this.formInline[item.dbFieldName])
|
||||
this.uploadName = item.dbFieldName
|
||||
},
|
||||
// 文件上传改变后的回调
|
||||
uploadFileChange (data) {
|
||||
const attIdList = []
|
||||
const attNameList = []
|
||||
if (data && data.length > 0) {
|
||||
data.map(item => {
|
||||
attIdList.push(item.id)
|
||||
})
|
||||
data.map(item => {
|
||||
attNameList.push(item.fileName)
|
||||
})
|
||||
this.$refs.ruleForm.clearValidate(this.uploadName)
|
||||
}
|
||||
console.log(data)
|
||||
/** 赋值给当前对应的表单文件 */
|
||||
this.formInline[this.uploadName] = attIdList.join(',')
|
||||
this.formInline[this.uploadName + '_dictText'] = attNameList.join(',')
|
||||
this.formInline = { ...this.formInline }
|
||||
},
|
||||
// 标准选择变化
|
||||
standardSelectionChange (value, id) {
|
||||
// this.formInline[value + '_by'] = id
|
||||
this.formInline = { ...this.formInline }
|
||||
},
|
||||
// 选择用户得到用户名
|
||||
userSelectNameChange (value, fieldName) {
|
||||
this.formInline[fieldName + '_dictText'] = value
|
||||
},
|
||||
treeSelectNameChange (value, fieldName) {
|
||||
this.formInline[fieldName + '_dictText'] = value
|
||||
},
|
||||
submit () {
|
||||
this.$refs.ruleForm.validate(valid => {
|
||||
if (valid) {
|
||||
const formInline = { ...this.formInline }
|
||||
Object.keys(formInline).forEach(res => {
|
||||
if (formInline[res] instanceof Array) {
|
||||
if (formInline[res][0] instanceof Object) {
|
||||
// 说明是树选择
|
||||
formInline[res] = formInline[res].map(item => item.value).join(',')
|
||||
} else {
|
||||
formInline[res] = formInline[res].join(',')
|
||||
}
|
||||
} else if (typeof formInline[res] === 'undefined') {
|
||||
// 说明是undefined,改成空字符串,为了解决下拉框改成undefined时,不会传给后端该字段,改不了下拉框值的问题
|
||||
formInline[res] = ''
|
||||
}
|
||||
})
|
||||
this.$emit('submitFormData', formInline)
|
||||
} else {
|
||||
this.$emit('addFormWarning')
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
clearValidate (fieldNameList) {
|
||||
this.$refs.ruleForm.clearValidate(fieldNameList)
|
||||
},
|
||||
/**
|
||||
* 标准性质是准入,状态为发布、即将实施和现行有效的时候 新车型实施日期和在产车的实施日期 都改成必填
|
||||
*/
|
||||
handleStatePropertyCheck () {
|
||||
const mustFillFields = ['new_model_implementation_date', 'on_the_production_date']
|
||||
const rules = JSON.parse(JSON.stringify(this.rules))
|
||||
if (this.formInline.standard_property === StandardProperty.admittance.value && (
|
||||
this.formInline.standard_state === StandardStatus.RELEASE.value ||
|
||||
this.formInline.standard_state === StandardStatus.BE_IMPLEMENTED_SOON.value ||
|
||||
this.formInline.standard_state === StandardStatus.CURRENTLY_EFFECTIVE.value)) {
|
||||
mustFillFields.forEach(key => {
|
||||
if (rules[key] && rules[key].length > 0) {
|
||||
rules[key][0].required = true
|
||||
const dataPart = Object.keys(this.dataList).find(tt => this.dataList[tt].some(item => item.dbFieldName === key))
|
||||
const index = this.dataList[dataPart].findIndex(tt => tt.dbFieldName === key)
|
||||
this.dataList[dataPart][index].fieldMustInput = '1'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
mustFillFields.forEach(key => {
|
||||
if (rules[key] && rules[key].length > 0) {
|
||||
rules[key][0].required = false
|
||||
const dataPart = Object.keys(this.dataList).find(tt => this.dataList[tt].some(item => item.dbFieldName === key))
|
||||
const index = this.dataList[dataPart].findIndex(tt => tt.dbFieldName === key)
|
||||
this.dataList[dataPart][index].fieldMustInput = '0'
|
||||
}
|
||||
})
|
||||
}
|
||||
this.rules = Object.assign({}, rules)
|
||||
this.clearValidate(mustFillFields)
|
||||
},
|
||||
/**
|
||||
* 国内海外的特殊处理
|
||||
*/
|
||||
handleDomesticSpecialCheck () {
|
||||
if (this.domesticSpecialCheck) {
|
||||
const fieldNameList = ['part_name', 'applicable_certification', 'applicable_vehicle', 'responsible_department']
|
||||
const rules = JSON.parse(JSON.stringify(this.rules))
|
||||
if (this.formInline.standard_property === StandardProperty.admittance.value) {
|
||||
fieldNameList.forEach(key => {
|
||||
if (rules[key] && rules[key].length > 0) {
|
||||
rules[key][0].required = true
|
||||
const dataPart = Object.keys(this.dataList).find(tt => this.dataList[tt].some(item => item.dbFieldName === key))
|
||||
const index = this.dataList[dataPart].findIndex(tt => tt.dbFieldName === key)
|
||||
this.dataList[dataPart][index].fieldMustInput = '1'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
fieldNameList.forEach(key => {
|
||||
if (rules[key] && rules[key].length > 0) {
|
||||
rules[key][0].required = false
|
||||
const dataPart = Object.keys(this.dataList).find(tt => this.dataList[tt].some(item => item.dbFieldName === key))
|
||||
const index = this.dataList[dataPart].findIndex(tt => tt.dbFieldName === key)
|
||||
this.dataList[dataPart][index].fieldMustInput = '0'
|
||||
}
|
||||
})
|
||||
}
|
||||
this.rules = Object.assign({}, rules)
|
||||
this.clearValidate(fieldNameList)
|
||||
}
|
||||
},
|
||||
getFlag (fieldName) {
|
||||
return this.flagByField[fieldName] || this.flag
|
||||
},
|
||||
filterTreeOption (input, option) {
|
||||
const text = option.componentOptions.propsData.title || option.componentOptions.propsData.label
|
||||
return text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
/**
|
||||
* 标准状态为起草、征求意见、报批等状态时发布日期、实施日期不应是必填项
|
||||
* @param val
|
||||
*/
|
||||
handleRequiredByState (val) {
|
||||
let notRequiredStatus = [], fields = []
|
||||
// 国内和海外的标准状态是同一个数据字典
|
||||
if (this.flag === StandardSource.DOMESTIC.value || this.flag === StandardSource.OVERSEAS.value) {
|
||||
notRequiredStatus = [StandardStatus.DRAFT.value, StandardStatus.SEEK_FOR_OPTIONS.value, StandardStatus.EXAMINE.value, StandardStatus.APPROVAL.value, StandardStatus.ABOLISH.value]
|
||||
fields = ['release_date', 'implementation_date']
|
||||
} else { // 处理企标
|
||||
notRequiredStatus = [EnterpriseStandardStatus.DRAFT.value, EnterpriseStandardStatus.SEEK_FOR_OPTIONS.value, EnterpriseStandardStatus.EXAMINE.value, EnterpriseStandardStatus.APPROVAL.value]
|
||||
fields = ['release_date', 'implementation_date']
|
||||
}
|
||||
const rules = JSON.parse(JSON.stringify(this.rules))
|
||||
if (notRequiredStatus.includes(val)) {
|
||||
fields.forEach(key => {
|
||||
if (rules[key] && rules[key].length > 0) {
|
||||
rules[key][0].required = false
|
||||
const dataPart = Object.keys(this.dataList).find(tt => this.dataList[tt].some(item => item.dbFieldName === key))
|
||||
const index = this.dataList[dataPart].findIndex(tt => tt.dbFieldName === key)
|
||||
this.dataList[dataPart][index].fieldMustInput = '0'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
fields.forEach(key => {
|
||||
if (rules[key] && rules[key].length > 0) {
|
||||
rules[key][0].required = true
|
||||
const dataPart = Object.keys(this.dataList).find(tt => this.dataList[tt].some(item => item.dbFieldName === key))
|
||||
const index = this.dataList[dataPart].findIndex(tt => tt.dbFieldName === key)
|
||||
this.dataList[dataPart][index].fieldMustInput = '1'
|
||||
}
|
||||
})
|
||||
}
|
||||
this.rules = Object.assign({}, rules)
|
||||
this.clearValidate(fields)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/deep/ .ant-form-item-children {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-flex-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.form-flex-item-half {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.form-flex-item-line {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,447 @@
|
||||
<template>
|
||||
<a-form layout="inline" @keyup.enter.native="searchQuery">
|
||||
<a-row :gutter="24">
|
||||
<a-col :md="(24 / (minLength + 1))"
|
||||
:sm="(24 / minLength)"
|
||||
v-for="(item) in searchList.slice(0, minLength)"
|
||||
:key="item.id"
|
||||
style="line-height: 48px">
|
||||
<!-- 树形选择器 -->
|
||||
<template v-if="item.fieldShowType === FieldType.TREE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
:tree-data="optionsData[item.dbFieldName]"
|
||||
tree-checkable
|
||||
show-search
|
||||
allowClear
|
||||
:filterTreeNode="filterTreeOption"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 树形选择器(单选) -->
|
||||
<template v-if="item.fieldShowType === FieldType.TREE_SINGLE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
:tree-data="optionsData[item.dbFieldName]"
|
||||
show-search
|
||||
allowClear
|
||||
:filterTreeNode="filterTreeOption"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 输入框 -->
|
||||
<template
|
||||
v-if="[FieldType.TEXT_BOX.value, FieldType.TEXTAREA.value,FieldType.USER_SINGLE.value,
|
||||
FieldType.STANDARD_MORE.value,FieldType.TREE_MODAL_SELECT.value]
|
||||
.includes(item.fieldShowType)">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-input :placeholder="$t('pleaseEnter')+item.dbFieldTxt"
|
||||
v-model="queryParam[item.dbFieldName]"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 数字输入框 -->
|
||||
<!-- <template v-else-if="item.fieldShowType === FieldType.TEXT_NUMBER.value">-->
|
||||
<!-- <a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">-->
|
||||
<!-- <a-input-number :placeholder="$t('pleaseEnter')+item.dbFieldTxt"-->
|
||||
<!-- v-model="queryParam[item.dbFieldName]" :min="1" :max="99999999"/>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- </template>-->
|
||||
<!-- 日期区间 -->
|
||||
<template v-else-if="[FieldType.DATE_SINGLE.value, FieldType.DATE_MORE.value].includes(item.fieldShowType)">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-range-picker v-model="queryParam[item.dbFieldName]"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
format="YYYY-MM-DD" value-format="YYYY-MM-DD"
|
||||
@change="onChange(item.dbFieldName)"></a-range-picker>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 字典单选框 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.OPTION_SINGLE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-dict-select-tag v-model="queryParam[item.dbFieldName]"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dictField" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 字典多选框 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.OPTION_MORE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-multi-select-tag v-model="queryParam[item.dbFieldName]"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:type="'select'" :maxTagCount="1"
|
||||
:triggerChange="false" :dictCode="item.dictField" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 组织机构选择 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.ORGAN_SINGLE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
allowClear
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
class="box-input"
|
||||
style="width: 100%"
|
||||
:tree-data="categoryTreeList"
|
||||
show-search
|
||||
:filterTreeNode="filterTreeOption"
|
||||
:placeholder="$t('pleaseSelect') + item.dbFieldTxt"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 组织机构多选 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.ORGAN_MORE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
allowClear
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
class="box-input"
|
||||
style="width: 100%"
|
||||
:tree-data="categoryTreeList"
|
||||
:placeholder="$t('pleaseSelect') + item.dbFieldTxt"
|
||||
treeCheckable
|
||||
treeCheckStrictly
|
||||
show-search
|
||||
:filterTreeNode="filterTreeOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 年份选择 -->
|
||||
<template
|
||||
v-if="item.fieldShowType === FieldType.YEAR_PICKER.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-date show-type="year"
|
||||
:allowClear="false"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
date-format="YYYY"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-col>
|
||||
<template v-if="searchList.length > minLength && toggleSearchStatus">
|
||||
<a-col :md="(24 / (minLength + 1))"
|
||||
:sm="(24 / minLength)"
|
||||
v-for="(item) in searchList.slice(minLength)"
|
||||
:key="item.id"
|
||||
style="line-height: 48px">
|
||||
<!-- 树形选择器 -->
|
||||
<template v-if="item.fieldShowType === FieldType.TREE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
:tree-data="optionsData[item.dbFieldName]"
|
||||
tree-checkable
|
||||
treeCheckStrictly
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
show-search
|
||||
allowClear
|
||||
:filterTreeNode="filterTreeOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 树形选择器(单选) -->
|
||||
<template v-if="item.fieldShowType === FieldType.TREE_SINGLE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
:tree-data="optionsData[item.dbFieldName]"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
show-search
|
||||
allowClear
|
||||
:filterTreeNode="filterTreeOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 输入框 -->
|
||||
<template v-if="[FieldType.TEXT_BOX.value, FieldType.TEXTAREA.value,FieldType.USER_SINGLE.value,
|
||||
FieldType.STANDARD_MORE.value,FieldType.TREE_MODAL_SELECT.value]
|
||||
.includes(item.fieldShowType)">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-input :placeholder="$t('pleaseEnter')+item.dbFieldTxt"
|
||||
v-model="queryParam[item.dbFieldName]"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 组织机构单选 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.ORGAN_SINGLE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
class="box-input"
|
||||
style="width: 100%"
|
||||
:tree-data="categoryTreeList"
|
||||
:placeholder="$t('pleaseSelect') + item.dbFieldTxt"
|
||||
show-search
|
||||
allowClear
|
||||
:filterTreeNode="filterTreeOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 组织机构多选 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.ORGAN_MORE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-tree-select
|
||||
tree-node-filter-prop="title"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
:maxTagCount="1"
|
||||
:show-search="true"
|
||||
allowClear
|
||||
:getPopupContainer="triggerNode=> triggerNode.parentNode"
|
||||
class="box-input"
|
||||
style="width: 100%"
|
||||
:tree-data="categoryTreeList"
|
||||
:placeholder="$t('pleaseSelect') + item.dbFieldTxt"
|
||||
treeCheckable
|
||||
treeCheckStrictly
|
||||
:filterTreeNode="filterTreeOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 日期区间选择器 -->
|
||||
<template v-else-if="[FieldType.DATE_SINGLE.value, FieldType.DATE_MORE.value].includes(item.fieldShowType)">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<a-range-picker v-model="queryParam[item.dbFieldName]"
|
||||
format="YYYY-MM-DD" value-format="YYYY-MM-DD"
|
||||
@change="onChange(item.dbFieldName)"></a-range-picker>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 字典单选 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.OPTION_SINGLE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-dict-select-tag v-model="queryParam[item.dbFieldName]"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:type="'select'"
|
||||
:triggerChange="false" :dictCode="item.dictField" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 字典多选框 -->
|
||||
<template v-else-if="item.fieldShowType === FieldType.OPTION_MORE.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-multi-select-tag v-model="queryParam[item.dbFieldName]"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt"
|
||||
:type="'select'" :maxTagCount="1"
|
||||
:triggerChange="false" :dictCode="item.dictField" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- 年份选择 -->
|
||||
<template
|
||||
v-if="item.fieldShowType === FieldType.YEAR_PICKER.value">
|
||||
<a-form-item :label="item.dbFieldTxt" :labelCol="labelCol" :wrapperCol="wrapperCol">
|
||||
<j-date show-type="year"
|
||||
:allowClear="false"
|
||||
v-model="queryParam[item.dbFieldName]"
|
||||
date-format="YYYY"
|
||||
:placeholder="$t('pleaseSelect')+item.dbFieldTxt" />
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-col>
|
||||
</template>
|
||||
<div style="float: right;overflow: hidden;margin-right: 11px" class="table-page-search-submitButtons">
|
||||
<a @click="handleToggleSearch" v-if="searchList.length > minLength">
|
||||
{{ !toggleSearchStatus ? $t('open') : $t('putAway') }}
|
||||
<a-icon :type="toggleSearchStatus ? 'up' : 'down'" />
|
||||
</a>
|
||||
<a-button icon="search" type="primary" @click="searchQuery" style="margin-left: 8px" v-has="searchHas">{{
|
||||
$t('query')
|
||||
}}
|
||||
</a-button>
|
||||
<a-button icon="reload" type="primary" ghost style="margin-left: 8px" @click="searchReset">{{ $t('reset') }}</a-button>
|
||||
</div>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JDictSelectTag from '../dict/JDictSelectTag'
|
||||
import { FieldType } from '../../enums/commonEnums'
|
||||
import eventBus from '@/common/event'
|
||||
import { getFormDictTreeList, queryDepartTreeList } from '../../api/api'
|
||||
import { getAction } from '../../api/manage'
|
||||
|
||||
export default {
|
||||
name: 'Search',
|
||||
components: { JDictSelectTag },
|
||||
props: {
|
||||
// 获取查询条件的方法
|
||||
getQueryConditionFunc: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: () => {
|
||||
return function () {
|
||||
}
|
||||
}
|
||||
},
|
||||
flag: { // 暂时不清楚flag的用处,是要查searchList的时候传给后端的
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
minLength: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 查询权限
|
||||
searchHas: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null
|
||||
},
|
||||
// 默认的查询条件
|
||||
defaultSearchParams: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
// searchQueryList: {
|
||||
// type: Array,
|
||||
// default: () => {
|
||||
// return []
|
||||
// }
|
||||
// }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
FieldType,
|
||||
categoryTreeList: [], // 组织机构下拉框数据
|
||||
queryParam: {},
|
||||
toggleSearchStatus: false,
|
||||
searchList: [],
|
||||
labelCol: {
|
||||
span: 6
|
||||
},
|
||||
wrapperCol: {
|
||||
span: 14
|
||||
},
|
||||
optionsData: {} // 用接口获取到的下拉数据
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.queryParam = Object.assign({}, this.defaultSearchParams)
|
||||
},
|
||||
mounted () {
|
||||
this.getSysCategoryTree()
|
||||
this.getSearch()
|
||||
},
|
||||
methods: {
|
||||
searchQuery () {
|
||||
const formInline = JSON.parse(JSON.stringify(this.queryParam))
|
||||
Object.keys(formInline).forEach(res => {
|
||||
if (formInline[res] instanceof Array) {
|
||||
if (formInline[res][0] instanceof Object) {
|
||||
// 说明是树选择
|
||||
formInline[res] = formInline[res].map(item => item.value).join(',')
|
||||
} else {
|
||||
formInline[res] = formInline[res].join(',')
|
||||
}
|
||||
}
|
||||
})
|
||||
this.$emit('search', JSON.parse(JSON.stringify(formInline)))
|
||||
eventBus.$emit('searchQuery', JSON.parse(JSON.stringify(this.queryParam)))
|
||||
},
|
||||
searchReset () {
|
||||
this.queryParam = Object.assign({}, this.defaultSearchParams)
|
||||
console.log(this.defaultSearchParams, this.queryParam)
|
||||
this.$emit('reset', JSON.parse(JSON.stringify(this.queryParam)))
|
||||
eventBus.$emit('searchQuery', JSON.parse(JSON.stringify(this.queryParam)))
|
||||
},
|
||||
// 获取组织机构树
|
||||
getSysCategoryTree () {
|
||||
queryDepartTreeList().then((res) => {
|
||||
if (res.success) {
|
||||
this.categoryTreeList = res.result
|
||||
} else {
|
||||
this.categoryTreeList = []
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取查询条件列表
|
||||
getSearch () {
|
||||
console.log(this.getQueryConditionFunc)
|
||||
if (!this.getQueryConditionFunc || typeof this.getQueryConditionFunc !== 'function') {
|
||||
return
|
||||
}
|
||||
const params = {
|
||||
module: this.flag
|
||||
}
|
||||
this.getQueryConditionFunc(params).then((res) => {
|
||||
if (res.success) {
|
||||
this.searchList = res.result
|
||||
// 标准状态特殊处理成多选
|
||||
const standardStateIndex = this.searchList.findIndex(item => item.dbFieldName === 'standard_state')
|
||||
this.searchList[standardStateIndex].fieldShowType = FieldType.OPTION_MORE.value
|
||||
this.getOptionsData() // 获取下拉数据
|
||||
console.log(this.searchList)
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 处理需要通过接口获取的下拉数据
|
||||
*/
|
||||
getOptionsData () {
|
||||
const formList = Object.values(this.searchList).reduce((acc, cur) => acc.concat(cur), [])
|
||||
formList.forEach(item => {
|
||||
if (item.fieldShowType === FieldType.TREE.value || item.fieldShowType === FieldType.TREE_SINGLE.value) {
|
||||
// 展示类型是树选择,且有接口的
|
||||
if (item.fieldHref) {
|
||||
getAction(item.fieldHref).then(res => {
|
||||
if (res.success) {
|
||||
this.$set(this.optionsData, item.dbFieldName, res.result)
|
||||
}
|
||||
})
|
||||
} else if (item.dictId) {
|
||||
getFormDictTreeList({ dictId: item.dictId }).then(res => {
|
||||
if (res.success) {
|
||||
this.$set(this.optionsData, item.dbFieldName, res.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onChange (item) {
|
||||
if (this.queryParam[item] && this.queryParam[item].length > 0) {
|
||||
const startDate = item + '_start'
|
||||
const endDate = item + '_end'
|
||||
this.queryParam[startDate] = this.queryParam[item][0]
|
||||
this.queryParam[endDate] = this.queryParam[item][1]
|
||||
} else {
|
||||
this.queryParam[item] = []
|
||||
}
|
||||
},
|
||||
handleToggleSearch () {
|
||||
this.toggleSearchStatus = !this.toggleSearchStatus
|
||||
},
|
||||
filterTreeOption (input, option) {
|
||||
const text = option.componentOptions.propsData.title || option.componentOptions.propsData.label
|
||||
return text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
||||
@@ -0,0 +1,389 @@
|
||||
<template>
|
||||
<div class="box" v-if="isTrue">
|
||||
<a-table
|
||||
class="table"
|
||||
rowKey="id"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange, type: selectionType }"
|
||||
:pagination="ipagination"
|
||||
:scroll="{x: '100%'}"
|
||||
:components="drag(columns,'columns')"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:rowClassName="rowClassName"
|
||||
:columns="columns"
|
||||
@change="tableOnChange"
|
||||
>
|
||||
<tempalte slot="operation" slot-scope="text, record">
|
||||
<a v-for="(ol, index) in operationList" :key="index" @click="operationClick(ol,record)">
|
||||
<span v-if="ol.text === $t('cancelCollection')" v-has="ol.has" class="text">
|
||||
{{ !record.collectFlag || record.collectFlag === 0 ? $t('collection') :$t('cancelCollection') }}
|
||||
</span>
|
||||
<span v-else-if="ol.text === $t('cancelSubscribe')" v-has="ol.has" class="text">
|
||||
{{ !record.subscribeFlag || record.subscribeFlag === 0 ? $t('subscribe') :$t('cancelSubscribe') }}
|
||||
</span>
|
||||
<!-- v-has="ol.has"-->
|
||||
<span v-else class="text">
|
||||
{{ ol.text }}
|
||||
</span>
|
||||
</a>
|
||||
</tempalte>
|
||||
<span slot="detailClick" slot-scope="text, record">
|
||||
<a class="text-name" :title="text" @click="detailClick(record)">
|
||||
{{text}}
|
||||
</a>
|
||||
</span>
|
||||
<span slot="detail" slot-scope="text">
|
||||
<span class="text-name" :title="text">
|
||||
{{text}}
|
||||
</span>
|
||||
</span>
|
||||
<span slot="urlClick" slot-scope="text">
|
||||
<a class="text" :title="text" @click="urlClick(text)">
|
||||
{{text}}
|
||||
</a>
|
||||
</span>
|
||||
<span slot="detailText" slot-scope="text">
|
||||
<span class="text" :title="text">
|
||||
{{text}}
|
||||
</span>
|
||||
</span>
|
||||
<span slot="showContent" slot-scope="text, record" class="item-solt">
|
||||
<span class="content-show" href="javascript:;" @click="showContent(text,record)">{{record.iterms_conditions&&record.iterms_conditions!=='null'?record.iterms_conditions.replace(/<.*?>/ig, ' ') : ''}}</span>
|
||||
</span>
|
||||
</a-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ResizeColumnProvide, ResizeHeader } from '@/mixins/header'
|
||||
import { getAction, postAction } from '@/api/manage'
|
||||
import eventBus from '@/common/event'
|
||||
|
||||
export default {
|
||||
name: 'TableData',
|
||||
mixins: [ResizeColumnProvide, ResizeHeader],
|
||||
props: {
|
||||
// 接口
|
||||
url: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 操作按钮
|
||||
operationList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
flag: { // flag获取数据时传给后端的,具体作用还不知道??
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 表格选择是单选还是多选
|
||||
selectionType: {
|
||||
type: String,
|
||||
default: 'checkbox'
|
||||
},
|
||||
// 是否显示操作;默认不显示
|
||||
showAction: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
/* 分页参数 */
|
||||
ipagination: {
|
||||
current: 1,
|
||||
pageSize: 50,
|
||||
pageSizeOptions: ['10', '30', '50', '100', '150', '200'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' ' + this.$t('total') + ' ' + total + ' ' + this.$t('strip')
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 0
|
||||
},
|
||||
columns: [],
|
||||
selectedRowKeys: [],
|
||||
dataSource: [],
|
||||
isTrue: false,
|
||||
searchParams: {},
|
||||
loading: false,
|
||||
orderBy: '1',
|
||||
orderByField: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
eventBus.$on('searchQuery', search => {
|
||||
Object.keys(search).forEach(res => {
|
||||
if (search[res] instanceof Array) {
|
||||
search[res] = search[res].join(',')
|
||||
}
|
||||
})
|
||||
this.searchParams = search
|
||||
this.getData()
|
||||
this.getTableList()
|
||||
})
|
||||
eventBus.$on('searchReset', target => {
|
||||
this.searchParams = {}
|
||||
this.selectedRowKeys = []
|
||||
this.getData()
|
||||
this.getTableList()
|
||||
})
|
||||
this.$on('searchGetData', target => {
|
||||
this.selectedRowKeys = []
|
||||
this.getData()
|
||||
this.getTableList()
|
||||
})
|
||||
// this.getData()
|
||||
// this.getTableList()
|
||||
this.columns = [
|
||||
{
|
||||
title: '编号',
|
||||
dataIndex: 'serial_number',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: 'true',
|
||||
width: 215,
|
||||
scopedSlots: {
|
||||
customRender: 'detailClick'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: 'true',
|
||||
width: 215,
|
||||
scopedSlots: {
|
||||
customRender: 'detailClick'
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'state',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: 'true',
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: '适用地区',
|
||||
dataIndex: 'region',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: '技术领域',
|
||||
dataIndex: 'technologyTerritory',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
width: 215
|
||||
},
|
||||
{
|
||||
title: '发布日期',
|
||||
dataIndex: 'issue_time',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
width: 215
|
||||
}
|
||||
// {
|
||||
// title: this.$t('operation'),
|
||||
// align: 'center',
|
||||
// fixed: 'right',
|
||||
// width: 200,
|
||||
// scopedSlots: { customRender: 'operation' }
|
||||
// }
|
||||
]
|
||||
let width = 0
|
||||
if (this.operationList.length > 0) {
|
||||
for (let i = 0; i < this.operationList.length; i++) {
|
||||
width += this.getTextWith(this.operationList[i].text)
|
||||
}
|
||||
}
|
||||
if (this.showAction) {
|
||||
this.columns.push({
|
||||
title: this.$t('operation'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: width,
|
||||
scopedSlots: { customRender: 'operation' }
|
||||
})
|
||||
}
|
||||
this.dataSource = [
|
||||
{
|
||||
collectFlag: '0',
|
||||
id: 'fd045f5c5d734087a884a63664ee91f1',
|
||||
issue_time: '',
|
||||
region: '',
|
||||
serial_number: '0621001编号',
|
||||
state: '现行',
|
||||
subscribeFlag: '0',
|
||||
technology_territory: '',
|
||||
title: '0621001标题',
|
||||
title_en: '0621001Title'
|
||||
}
|
||||
]
|
||||
this.isTrue = true
|
||||
},
|
||||
beforeDestroy () {
|
||||
eventBus.$off('searchQuery')
|
||||
eventBus.$off('searchReset')
|
||||
this.$off('searchGetData')
|
||||
},
|
||||
methods: {
|
||||
rowClassName (record) {
|
||||
return record.state === '废止' ? 'rowClassNameBack' : ''
|
||||
},
|
||||
getTextWith (text, fontStyle) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
context.font = fontStyle || '14px' // 设置字体样式
|
||||
const dimension = context.measureText(text)
|
||||
return dimension.width + 40
|
||||
},
|
||||
getData () {
|
||||
const params = {
|
||||
flag: this.flag
|
||||
}
|
||||
getAction(this.url.tableHeader, params).then((res) => {
|
||||
if (res.success) {
|
||||
const jsonHead = res.result
|
||||
this.columns = []
|
||||
// res.db_field_txt
|
||||
jsonHead.forEach((res, index) => {
|
||||
this.columns.push({
|
||||
title: res.db_field_txt,
|
||||
dataIndex: res.db_field_name,
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
sorter: res.sort,
|
||||
width: 215
|
||||
})
|
||||
if (res.click) {
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'detailClick'
|
||||
}
|
||||
this.columns[index].ellipsis = false
|
||||
} else if (res.urlClick) {
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'urlClick'
|
||||
}
|
||||
} else if (res.db_field_name === 'iterms_conditions') {
|
||||
// 拆分文档详情页的条款内容点击可预览
|
||||
this.columns[index].width = 500
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'showContent'
|
||||
}
|
||||
} else {
|
||||
this.columns[index].scopedSlots = {
|
||||
customRender: 'detailText'
|
||||
}
|
||||
}
|
||||
})
|
||||
let width = 0
|
||||
if (this.operationList.length > 0) {
|
||||
for (let i = 0; i < this.operationList.length; i++) {
|
||||
width += this.getTextWith(this.operationList[i].text)
|
||||
}
|
||||
}
|
||||
if (this.showAction) {
|
||||
this.columns.push({
|
||||
title: this.$t('operation'),
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: width,
|
||||
scopedSlots: { customRender: 'operation' }
|
||||
})
|
||||
}
|
||||
this.isTrue = false
|
||||
this.$nextTick(() => {
|
||||
this.isTrue = true
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
tableOnChange (pagination, filters, sorter) {
|
||||
this.orderBy = sorter.order === 'ascend' ? '1' : '2'
|
||||
this.orderByField = sorter.columnKey
|
||||
this.ipagination = pagination
|
||||
this.getTableList()
|
||||
},
|
||||
getTableList () {
|
||||
const pageNo = this.ipagination.current
|
||||
const pageSize = this.ipagination.pageSize
|
||||
const params = {
|
||||
...this.searchParams,
|
||||
orderBy: this.orderBy,
|
||||
orderByField: this.orderByField,
|
||||
type: this.type,
|
||||
pageNo: pageNo,
|
||||
pageSize: pageSize
|
||||
}
|
||||
this.loading = true
|
||||
postAction(this.url.tableList, params).then((res) => {
|
||||
if (res.success) {
|
||||
if (res.result.current > 1 && res.result.records.length === 0) {
|
||||
this.ipagination.current = res.result.current - 1
|
||||
this.getTableList()
|
||||
return
|
||||
}
|
||||
this.dataSource = res.result.records
|
||||
this.ipagination.total = res.result.total
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
onSelectChange (selectedRowKeys, selectedRows) {
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
this.$emit('onSelectChange', selectedRowKeys, selectedRows)
|
||||
},
|
||||
operationClick (ol, item) {
|
||||
this.$emit(ol.clickEvent, item)
|
||||
},
|
||||
detailClick (item, index) {
|
||||
this.$emit('detailClick', item)
|
||||
},
|
||||
urlClick (item) {
|
||||
window.open(item)
|
||||
},
|
||||
showContent (item) {
|
||||
this.$emit('showContent', item)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.text {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.text-name {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
text-overflow: ellipsis;
|
||||
/*! autoprefixer: off */
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
/*! autoprefixer: on;*/
|
||||
text-justify: inter-ideograph;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.item-solt {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,145 @@
|
||||
<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"
|
||||
v-bind="$attrs"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
@change="handleInput"
|
||||
>
|
||||
<a-select-option :value="undefined">请选择</a-select-option>
|
||||
<a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value" :disabled="item.disabled || false">
|
||||
<span style="display: inline-block;width: 100%" :title=" item.text || item.label ">
|
||||
{{ item.text || item.label }}
|
||||
</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
|
||||
export default {
|
||||
name: 'JDictSelectTag',
|
||||
props: {
|
||||
dictCode: String,
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
value: [String, Number],
|
||||
type: String,
|
||||
getPopupContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dictOptions: [],
|
||||
tagType: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dictCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
if (this.dictCode) {
|
||||
this.initDictData()
|
||||
} else {
|
||||
this.dictOptions = this.options
|
||||
}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
handler () {
|
||||
if (!this.dictCode) {
|
||||
this.dictOptions = this.options
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.type || this.type === 'list') {
|
||||
this.tagType = 'select'
|
||||
} else {
|
||||
this.tagType = this.type
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getValueSting () {
|
||||
// update-begin author:wangshuai date:20200601 for: 不显示placeholder的文字 ------
|
||||
// 当有null或“” placeholder不显示
|
||||
return this.value != null ? this.value.toString() : undefined
|
||||
// update-end author:wangshuai date:20200601 for: 不显示placeholder的文字 ------
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterOption (input, option) {
|
||||
const text = option.componentOptions.children[0].text || option.componentOptions.children[0].data.attrs.title
|
||||
return text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
initDictData () {
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(this.dictCode)) {
|
||||
this.dictOptions = getDictItemsFromCache(this.dictCode)
|
||||
return
|
||||
}
|
||||
|
||||
// 根据字典Code, 初始化字典数组
|
||||
ajaxGetDictItems(this.dictCode, null).then((res) => {
|
||||
if (res.success) {
|
||||
this.dictOptions = res.result
|
||||
}
|
||||
})
|
||||
},
|
||||
handleInput (e) {
|
||||
let val
|
||||
if (e !== undefined && Object.keys(e).includes('target')) {
|
||||
val = e.target.value
|
||||
} else {
|
||||
val = e
|
||||
}
|
||||
console.log(val)
|
||||
this.$emit('change', val)
|
||||
// 解决数据规则,选择自定义SQL 规则值无法输入空格
|
||||
this.$emit('input', val)
|
||||
},
|
||||
setCurrentDictOptions (dictOptions) {
|
||||
this.dictOptions = dictOptions
|
||||
},
|
||||
getCurrentDictOptions () {
|
||||
return this.dictOptions
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 字典 util
|
||||
* author: scott
|
||||
* date: 20190109
|
||||
*/
|
||||
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
// import { getAction } from '@/api/manage'
|
||||
|
||||
/**
|
||||
* 获取字典数组
|
||||
* @param dictCode 字典Code
|
||||
* @return List<Map>
|
||||
*/
|
||||
export async function initDictOptions (dictCode) {
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!'
|
||||
}
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(dictCode)) {
|
||||
const res = {}
|
||||
res.result = getDictItemsFromCache(dictCode)
|
||||
res.success = true
|
||||
return res
|
||||
}
|
||||
// 获取字典数组
|
||||
return (await ajaxGetDictItems(dictCode))
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法
|
||||
* @param dictOptions 字典数组
|
||||
* @param text 字典值
|
||||
* @return String
|
||||
*/
|
||||
export function filterDictText (dictOptions, text) {
|
||||
// --update-begin----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
|
||||
if (text != null && Array.isArray(dictOptions)) {
|
||||
const result = []
|
||||
// 允许多个逗号分隔,允许传数组对象
|
||||
let splitText
|
||||
if (Array.isArray(text)) {
|
||||
splitText = text
|
||||
} else {
|
||||
splitText = text.toString().trim().split(',')
|
||||
}
|
||||
for (const txt of splitText) {
|
||||
let dictText = txt
|
||||
for (const dictItem of dictOptions) {
|
||||
if (txt.toString() === dictItem.value.toString()) {
|
||||
dictText = (dictItem.text || dictItem.title || dictItem.label)
|
||||
break
|
||||
}
|
||||
}
|
||||
result.push(dictText)
|
||||
}
|
||||
return result.join(',')
|
||||
}
|
||||
return text
|
||||
// --update-end----author:sunjianlei---date:20200323------for: 字典翻译 text 允许逗号分隔 ---
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典值替换文本通用方法(多选)
|
||||
* @param dictOptions 字典数组
|
||||
* @param text 字典值
|
||||
* @return String
|
||||
*/
|
||||
export function filterMultiDictText (dictOptions, text) {
|
||||
// js “!text” 认为0为空,所以做提前处理
|
||||
if (text === 0 || text === '0') {
|
||||
if (dictOptions) {
|
||||
for (const dictItem of dictOptions) {
|
||||
if ((text + '') === (dictItem.value + '')) {
|
||||
return dictItem.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text || text === 'null' || !dictOptions || dictOptions.length === 0) {
|
||||
return ''
|
||||
}
|
||||
let re = ''
|
||||
text = text.toString()
|
||||
const arr = text.split(',')
|
||||
dictOptions.forEach(function (option) {
|
||||
if (option) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i] === option.value) {
|
||||
re += option.text + ','
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (re === '') {
|
||||
return text
|
||||
}
|
||||
return re.substring(0, re.length - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻译字段值对应的文本
|
||||
* @returns string
|
||||
* @param dictCode
|
||||
* @param key
|
||||
*/
|
||||
export function filterDictTextByCache (dictCode, key) {
|
||||
if (key === null || key === undefined || key.length === 0) {
|
||||
return ''
|
||||
}
|
||||
if (!dictCode) {
|
||||
return '字典Code不能为空!'
|
||||
}
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(dictCode)) {
|
||||
const item = getDictItemsFromCache(dictCode).filter(t => t.value + '' === key + '')
|
||||
if (item && item.length > 0) {
|
||||
return item[0].text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 通过code获取字典数组 */
|
||||
export async function getDictItems (dictCode, params) {
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(dictCode)) {
|
||||
return getDictItemsFromCache(dictCode).map(item => ({ ...item, label: item.text }))
|
||||
}
|
||||
|
||||
// 缓存中没有,就请求后台
|
||||
return await ajaxGetDictItems(dictCode, params).then(({ success, result }) => {
|
||||
if (success) {
|
||||
const res = result.map(item => ({ ...item, label: item.text }))
|
||||
console.log('------- 从DB中获取到了字典-------dictCode : ', dictCode, res)
|
||||
return Promise.resolve(res)
|
||||
} else {
|
||||
console.error('getDictItems error: : ', result)
|
||||
return Promise.resolve([])
|
||||
}
|
||||
}).catch((res) => {
|
||||
console.error('getDictItems error: ', res)
|
||||
return Promise.resolve([])
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<a-checkbox-group v-if="tagType==='checkbox'" @change="onChange" :value="arrayValue" :disabled="disabled">
|
||||
<a-checkbox :key="-1" :value="selectedAllValue" v-if="openSelectedAll && dictOptions.length > 0">{{ $t('checkedAll') }}</a-checkbox>
|
||||
<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"
|
||||
@select="onSelect"
|
||||
@deselect="onDeselect"
|
||||
:disabled="disabled"
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
:getPopupContainer="getParentContainer"
|
||||
optionFilterProp="children"
|
||||
:filterOption="filterOption"
|
||||
allowClear
|
||||
:class="(openSelectedAll && arrayValue[0] === selectedAllValue) ? 'multi-select-tag-check-all' : ''"
|
||||
v-bind="$attrs">
|
||||
<a-select-option :key="-1" :value="selectedAllValue" v-if="openSelectedAll && dictOptions.length > 0">{{ $t('checkedAll') }}</a-select-option>
|
||||
<a-select-option v-for="(item, key) in dictOptions" :key="key" :value="item.value">{{ item.text || item.label }}</a-select-option>
|
||||
</a-select>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
|
||||
// 全选的value
|
||||
const selectedAllValue = '__all'
|
||||
|
||||
export default {
|
||||
name: 'JMultiSelectTag',
|
||||
props: {
|
||||
dictCode: String,
|
||||
placeholder: String,
|
||||
disabled: Boolean,
|
||||
value: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'select'
|
||||
},
|
||||
options: Array,
|
||||
// 是否需要全选
|
||||
openSelectedAll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 全选的value
|
||||
selectedAllValue,
|
||||
dictOptions: [],
|
||||
tagType: '',
|
||||
arrayValue: !this.value ? [] : this.value.split(this.spliter)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.type || this.type === 'list_multi') {
|
||||
this.tagType = 'select'
|
||||
} else {
|
||||
this.tagType = this.type
|
||||
}
|
||||
// 获取字典数据
|
||||
// this.initDictData();
|
||||
},
|
||||
watch: {
|
||||
options (val) {
|
||||
val.forEach((item, index) => {
|
||||
this.$set(this.dictOptions, index, item)
|
||||
})
|
||||
},
|
||||
dictCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
if (this.dictCode) {
|
||||
this.initDictData()
|
||||
}
|
||||
}
|
||||
},
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (!val) {
|
||||
this.arrayValue = []
|
||||
} else {
|
||||
const list = this.value.split(this.spliter)
|
||||
// 开启全选后如果所有项都被勾选要回显全选
|
||||
if (this.openSelectedAll && (list.length === this.dictOptions.length)) {
|
||||
list.unshift(selectedAllValue)
|
||||
}
|
||||
this.arrayValue = list
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initDictData () {
|
||||
if (this.options && this.options.length > 0) {
|
||||
this.dictOptions = [...this.options]
|
||||
} else {
|
||||
// 优先从缓存中读取字典配置
|
||||
const cacheOption = getDictItemsFromCache(this.dictCode)
|
||||
if (cacheOption && cacheOption.length > 0) {
|
||||
this.dictOptions = cacheOption
|
||||
return
|
||||
}
|
||||
// 根据字典Code, 初始化字典数组
|
||||
ajaxGetDictItems(this.dictCode, null).then((res) => {
|
||||
if (res.success) {
|
||||
this.dictOptions = res.result
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onChange (selectedValue) {
|
||||
// 如果开启全选,并且是复选框时,判断是全选还是取消勾选
|
||||
if (this.openSelectedAll && this.tagType === 'checkbox') {
|
||||
const findAllBefore = this.arrayValue.includes(selectedAllValue)
|
||||
const findAllAfter = selectedValue.includes(selectedAllValue)
|
||||
// 之前选了,之后没选就是取消所有勾选
|
||||
if (findAllBefore && !findAllAfter) {
|
||||
selectedValue = []
|
||||
} else if (!findAllBefore && findAllAfter) {
|
||||
// 之前没选,之后选了就是全部勾选
|
||||
selectedValue = this.dictOptions.map(item => item.value)
|
||||
}
|
||||
}
|
||||
|
||||
this.arrayValue = selectedValue
|
||||
// 开启全选时,不把全选的value进行返回
|
||||
if (this.openSelectedAll) {
|
||||
this.$emit('change', selectedValue.filter(item => item !== selectedAllValue).join(this.spliter))
|
||||
} else {
|
||||
this.$emit('change', selectedValue.join(this.spliter))
|
||||
}
|
||||
},
|
||||
// 选中的回调
|
||||
onSelect (value) {
|
||||
// 没有开启全选就直接退出
|
||||
if (!this.openSelectedAll) {
|
||||
return
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// 勾选全选就把所有项选中
|
||||
if (value === selectedAllValue) {
|
||||
this.onChange([selectedAllValue, ...this.dictOptions.map(item => item.value)])
|
||||
} else if (this.arrayValue.length === this.dictOptions.length) {
|
||||
// 勾选其他就判断是否全部勾选,如果已经全部勾选,把全选勾上
|
||||
this.arrayValue.unshift(selectedAllValue)
|
||||
this.onChange(this.arrayValue)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 取消选中的回调
|
||||
onDeselect (value) {
|
||||
// 没有开启全选就直接退出
|
||||
if (!this.openSelectedAll) {
|
||||
return
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// 如果取消全选就把所有项取消勾选
|
||||
if (value === selectedAllValue) {
|
||||
this.onChange([])
|
||||
} else {
|
||||
// 如果取消了任意一个,就把全选给取消勾选
|
||||
this.onChange(this.arrayValue.filter(item => item !== selectedAllValue))
|
||||
}
|
||||
})
|
||||
},
|
||||
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].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
// update--end--autor:lvdandan-----date:20201120------for:LOWCOD-1086 下拉多选框,搜索时只字典code进行搜索不能通过字典text搜索
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 全选是第一个,全选这个样式把第一个选项隐藏
|
||||
.multi-select-tag-check-all {
|
||||
/deep/ .ant-select-selection__rendered ul li:first-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
|
||||
<a-select
|
||||
v-if="async"
|
||||
showSearch
|
||||
labelInValue
|
||||
:disabled="disabled"
|
||||
:getPopupContainer="getParentContainer"
|
||||
@search="loadData"
|
||||
:placeholder="placeholder"
|
||||
v-model="selectedAsyncValue"
|
||||
style="width: 100%"
|
||||
:filterOption="false"
|
||||
@change="handleAsyncChange"
|
||||
allowClear
|
||||
:notFoundContent="loading ? undefined : null"
|
||||
>
|
||||
<a-spin v-if="loading" slot="notFoundContent" size="small"/>
|
||||
<a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<a-select
|
||||
v-else
|
||||
:getPopupContainer="getParentContainer"
|
||||
showSearch
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
optionFilterProp="children"
|
||||
style="width: 100%"
|
||||
@change="handleChange"
|
||||
:filterOption="filterOption"
|
||||
v-model="selectedValue"
|
||||
allowClear
|
||||
:notFoundContent="loading ? undefined : null">
|
||||
<a-spin v-if="loading" slot="notFoundContent" size="small"/>
|
||||
<a-select-option v-for="d in options" :key="d.value" :value="d.value">{{ d.text }}</a-select-option>
|
||||
</a-select>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { getAction } from '@api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JSearchSelectTag',
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
value: [String, Number],
|
||||
dict: String,
|
||||
dictOptions: Array,
|
||||
async: Boolean,
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
required: false
|
||||
},
|
||||
getPopupContainer: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
this.loadData = debounce(this.loadData, 800)// 消抖
|
||||
this.lastLoad = 0
|
||||
return {
|
||||
loading: false,
|
||||
selectedValue: [],
|
||||
selectedAsyncValue: [],
|
||||
options: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initDictData()
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (!val) {
|
||||
if (val === 0) {
|
||||
this.initSelectValue()
|
||||
} else {
|
||||
this.selectedValue = []
|
||||
this.selectedAsyncValue = []
|
||||
}
|
||||
} else {
|
||||
this.initSelectValue()
|
||||
}
|
||||
}
|
||||
},
|
||||
dict: {
|
||||
handler () {
|
||||
this.initDictData()
|
||||
}
|
||||
},
|
||||
dictOptions: {
|
||||
deep: true,
|
||||
handler (val) {
|
||||
if (val && val.length > 0) {
|
||||
this.options = [...val]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initSelectValue () {
|
||||
if (this.async) {
|
||||
if (!this.selectedAsyncValue || !this.selectedAsyncValue.key || (this.selectedAsyncValue.key + '') !== (this.value + '')) {
|
||||
console.log('这才请求后台')
|
||||
getAction(`/sys/dict/loadDictItem/${this.dict}`, { key: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
const obj = {
|
||||
key: this.value,
|
||||
label: res.result
|
||||
}
|
||||
this.selectedAsyncValue = { ...obj }
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.selectedValue = this.value.toString()
|
||||
}
|
||||
},
|
||||
loadData (value) {
|
||||
console.log('数据加载', value)
|
||||
this.lastLoad += 1
|
||||
const currentLoad = this.lastLoad
|
||||
this.options = []
|
||||
this.loading = true
|
||||
// 字典code格式:table,text,code
|
||||
getAction(`/sys/dict/loadDict/${this.dict}`, { keyword: value, pageSize: this.pageSize }).then(res => {
|
||||
this.loading = false
|
||||
if (res.success) {
|
||||
if (currentLoad !== this.lastLoad) {
|
||||
return
|
||||
}
|
||||
this.options = res.result
|
||||
console.log('我是第一个', res)
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
initDictData () {
|
||||
if (!this.async) {
|
||||
// 如果字典项集合有数据
|
||||
if (this.dictOptions && this.dictOptions.length > 0) {
|
||||
this.options = [...this.dictOptions]
|
||||
} else {
|
||||
// 根据字典Code, 初始化字典数组
|
||||
let dictStr = ''
|
||||
if (this.dict) {
|
||||
const arr = this.dict.split(',')
|
||||
if (arr[0].indexOf('where') > 0) {
|
||||
const tbInfo = arr[0].split('where')
|
||||
dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1])
|
||||
} else {
|
||||
dictStr = this.dict
|
||||
}
|
||||
if (this.dict.indexOf(',') === -1) {
|
||||
// 优先从缓存中读取字典配置
|
||||
if (getDictItemsFromCache(this.dictCode)) {
|
||||
this.options = getDictItemsFromCache(this.dictCode)
|
||||
return
|
||||
}
|
||||
}
|
||||
ajaxGetDictItems(dictStr, null).then((res) => {
|
||||
if (res.success) {
|
||||
this.options = res.result
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this.dict) {
|
||||
console.error('搜索组件未配置字典项')
|
||||
} else {
|
||||
// 异步一开始也加载一点数据
|
||||
this.loading = true
|
||||
getAction(`/sys/dict/loadDict/${this.dict}`, { pageSize: this.pageSize, keyword: '' }).then(res => {
|
||||
this.loading = false
|
||||
if (res.success) {
|
||||
this.options = res.result
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
filterOption (input, option) {
|
||||
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
},
|
||||
handleChange (selectedValue) {
|
||||
console.log('selectedValue', selectedValue)
|
||||
this.selectedValue = selectedValue
|
||||
this.callback()
|
||||
},
|
||||
handleAsyncChange (selectedObj) {
|
||||
// update-begin-author:scott date:20201222 for:【搜索】搜索查询组件,删除条件,默认下拉还是上次的缓存数据,不好 JT-191
|
||||
if (selectedObj) {
|
||||
this.selectedAsyncValue = selectedObj
|
||||
this.selectedValue = selectedObj.key
|
||||
} else {
|
||||
this.selectedAsyncValue = null
|
||||
this.selectedValue = null
|
||||
this.options = null
|
||||
this.loadData('')
|
||||
}
|
||||
this.callback()
|
||||
// update-end-author:scott date:20201222 for:【搜索】搜索查询组件,删除条件,默认下拉还是上次的缓存数据,不好 JT-191
|
||||
},
|
||||
callback () {
|
||||
this.$emit('change', this.selectedValue)
|
||||
},
|
||||
setCurrentDictOptions (dictOptions) {
|
||||
this.options = dictOptions
|
||||
},
|
||||
getCurrentDictOptions () {
|
||||
return this.options
|
||||
},
|
||||
getParentContainer (node) {
|
||||
if (typeof this.getPopupContainer === 'function') {
|
||||
return this.getPopupContainer(node)
|
||||
} else if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.querySelector(this.popContainer)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,181 @@
|
||||
# JDictSelectTag 组件用法
|
||||
----
|
||||
- 从字典表获取数据,dictCode格式说明: 字典code
|
||||
```html
|
||||
<j-dict-select-tag v-model="queryParam.sex" placeholder="请输入用户性别"
|
||||
dictCode="sex"/>
|
||||
```
|
||||
|
||||
v-decorator用法:
|
||||
```html
|
||||
<j-dict-select-tag v-decorator="['sex', {}]" :triggerChange="true" placeholder="请输入用户性别"
|
||||
dictCode="sex"/>
|
||||
```
|
||||
|
||||
- 从数据库表获取字典数据,dictCode格式说明: 表名,文本字段,取值字段
|
||||
```html
|
||||
<j-dict-select-tag v-model="queryParam.username" placeholder="请选择用户名称"
|
||||
dictCode="sys_user,realname,id"/>
|
||||
```
|
||||
|
||||
|
||||
|
||||
# JDictSelectUtil.js 列表字典函数用法
|
||||
----
|
||||
|
||||
- 第一步: 引入依赖方法
|
||||
```html
|
||||
import {initDictOptions, filterDictText} from '@/components/dict/JDictSelectUtil'
|
||||
```
|
||||
|
||||
- 第二步: 在created()初始化方法执行字典配置方法
|
||||
```html
|
||||
//初始化字典配置
|
||||
this.initDictConfig();
|
||||
```
|
||||
|
||||
- 第三步: 实现initDictConfig方法,加载列表所需要的字典(列表上有多个字典项,就执行多次initDictOptions方法)
|
||||
|
||||
```html
|
||||
initDictConfig() {
|
||||
//初始化字典 - 性别
|
||||
initDictOptions('sex').then((res) => {
|
||||
if (res.success) {
|
||||
this.sexDictOptions = res.result;
|
||||
}
|
||||
});
|
||||
},
|
||||
```
|
||||
|
||||
- 第四步: 实现字段的customRender方法
|
||||
```html
|
||||
customRender: (text, record, index) => {
|
||||
//字典值替换通用方法
|
||||
return filterDictText(this.sexDictOptions, text);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# JMultiSelectTag 多选组件
|
||||
下拉/checkbox
|
||||
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| disabled |Boolean | | 是否禁用 |
|
||||
| type |string | | 多选类型 select/checkbox 默认是select |
|
||||
| dictCode |string | | 数据字典编码或者表名,显示字段名,存储字段名拼接而成的字符串,如果提供了options参数 则此参数可不填|
|
||||
| options |Array | | 多选项,如果dictCode参数未提供,可以设置此参数加载多选项 |
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="下拉多选" style="width: 300px">
|
||||
<j-multi-select-tag
|
||||
v-model="selectValue"
|
||||
:options="dictOptions"
|
||||
placeholder="请做出你的选择">
|
||||
</j-multi-select-tag>
|
||||
{{ selectValue }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="checkbox">
|
||||
<j-multi-select-tag
|
||||
v-model="checkboxValue"
|
||||
:options="dictOptions"
|
||||
type="checkbox">
|
||||
</j-multi-select-tag>
|
||||
{{ checkboxValue }}
|
||||
</a-form-item>
|
||||
</a-form >
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
|
||||
export default {
|
||||
components: {JMultiSelectTag},
|
||||
data() {
|
||||
return {
|
||||
selectValue:"",
|
||||
checkboxValue:"",
|
||||
dictOptions:[{
|
||||
label:"选项一",
|
||||
value:"1"
|
||||
},{
|
||||
label:"选项二",
|
||||
value:"2"
|
||||
},{
|
||||
label:"选项三",
|
||||
value:"3"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# JSearchSelectTag 字典表的搜索组件
|
||||
下拉搜索组件,支持异步加载,异步加载用于大数据量的字典表
|
||||
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| disabled |Boolean | | 是否禁用 |
|
||||
| dict |string | | 表名,显示字段名,存储字段名拼接而成的字符串,如果提供了dictOptions参数 则此参数可不填|
|
||||
| dictOptions |Array | | 多选项,如果dict参数未提供,可以设置此参数加载多选项 |
|
||||
| async |Boolean | | 是否支持异步加载,设置成true,则通过输入的内容加载远程数据,否则在本地过滤数据,默认false|
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="下拉搜索" style="width: 300px">
|
||||
<j-search-select-tag
|
||||
placeholder="请做出你的选择"
|
||||
v-model="selectValue"
|
||||
:dictOptions="dictOptions">
|
||||
</j-search-select-tag>
|
||||
{{ selectValue }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="异步加载" style="width: 300px">
|
||||
<j-search-select-tag
|
||||
placeholder="请做出你的选择"
|
||||
v-model="asyncSelectValue"
|
||||
dict="sys_depart,depart_name,id"
|
||||
:async="true">
|
||||
</j-search-select-tag>
|
||||
{{ asyncSelectValue }}
|
||||
</a-form-item>
|
||||
</a-form >
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JSearchSelectTag from '@/components/dict/JSearchSelectTag'
|
||||
export default {
|
||||
components: {JSearchSelectTag},
|
||||
data() {
|
||||
return {
|
||||
selectValue:"",
|
||||
asyncSelectValue:"",
|
||||
dictOptions:[{
|
||||
text:"选项一",
|
||||
value:"1"
|
||||
},{
|
||||
text:"选项二",
|
||||
value:"2"
|
||||
},{
|
||||
text:"选项三",
|
||||
value:"3"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import JDictSelectTag from './JDictSelectTag.vue'
|
||||
import JMultiSelectTag from './JMultiSelectTag.vue'
|
||||
import JSearchSelectTag from './JSearchSelectTag.vue'
|
||||
import { filterMultiDictText, filterDictText, initDictOptions, filterDictTextByCache } from './JDictSelectUtil'
|
||||
|
||||
export default {
|
||||
install: function (Vue) {
|
||||
Vue.component('JDictSelectTag', JDictSelectTag)
|
||||
Vue.component('JMultiSelectTag', JMultiSelectTag)
|
||||
Vue.component('JSearchSelectTag', JSearchSelectTag)
|
||||
Vue.prototype.$initDictOptions = (dictCode) => initDictOptions(dictCode)
|
||||
Vue.prototype.$filterMultiDictText = (dictOptions, text) => filterMultiDictText(dictOptions, text)
|
||||
Vue.prototype.$filterDictText = (dictOptions, text) => filterDictText(dictOptions, text)
|
||||
Vue.prototype.$filterDictTextByCache = (...param) => filterDictTextByCache(...param)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "~ant-design-vue/lib/style/index";
|
||||
|
||||
// The prefix to use on all css classes from ant-pro.
|
||||
@ant-pro-prefix : ant-pro;
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="j-area-linkage">
|
||||
<div v-if="reloading">
|
||||
<span> Reloading... </span>
|
||||
</div>
|
||||
<area-cascader
|
||||
v-else-if="_type === enums.type[0]"
|
||||
:value="innerValue"
|
||||
:data="pcaa"
|
||||
:level="1"
|
||||
:style="{width}"
|
||||
v-bind="$attrs"
|
||||
v-on="_listeners"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<area-select
|
||||
v-else-if="_type === enums.type[1]"
|
||||
:value="innerValue"
|
||||
:data="pcaa"
|
||||
:level="2"
|
||||
v-bind="$attrs"
|
||||
v-on="_listeners"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<div v-else>
|
||||
<span style="color:red;"> Bad type value: {{ _type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Area from '@/components/_util/Area'
|
||||
|
||||
export default {
|
||||
name: 'JAreaLinkage',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
// 组件的类型,可选值:
|
||||
// select 下拉样式
|
||||
// cascader 级联样式(默认)
|
||||
type: {
|
||||
type: String,
|
||||
default: 'cascader'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pcaa: this.$Jpcaa,
|
||||
innerValue: [],
|
||||
usedListeners: ['change'],
|
||||
enums: {
|
||||
type: ['cascader', 'select']
|
||||
},
|
||||
reloading: false,
|
||||
areaData: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_listeners () {
|
||||
const listeners = { ...this.$listeners }
|
||||
// 去掉已使用的事件,防止冲突
|
||||
this.usedListeners.forEach(key => {
|
||||
delete listeners[key]
|
||||
})
|
||||
return listeners
|
||||
},
|
||||
_type () {
|
||||
if (this.enums.type.includes(this.type)) {
|
||||
return this.type
|
||||
} else {
|
||||
console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')})`)
|
||||
return this.enums.type[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.loadDataByValue(this.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initAreaData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 重新加载组件 */
|
||||
reload () {
|
||||
this.reloading = true
|
||||
this.$nextTick(() => {
|
||||
this.reloading = false
|
||||
})
|
||||
},
|
||||
|
||||
/** 通过 value 反推 options */
|
||||
loadDataByValue (value) {
|
||||
if (!value || value.length === 0) {
|
||||
this.innerValue = []
|
||||
} else {
|
||||
this.initAreaData()
|
||||
this.innerValue = this.areaData.getRealCode(value)
|
||||
}
|
||||
this.reload()
|
||||
},
|
||||
/** 通过地区code获取子级 */
|
||||
loadDataByCode (value) {
|
||||
const options = []
|
||||
const data = this.pcaa[value]
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
options.push({ value: key, label: data[key] })
|
||||
}
|
||||
}
|
||||
return options
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/** 判断是否有子节点 */
|
||||
hasChildren (options) {
|
||||
options.forEach(option => {
|
||||
const data = this.loadDataByCode(option.value)
|
||||
option.isLeaf = data.length === 0
|
||||
})
|
||||
},
|
||||
handleChange (values) {
|
||||
const value = values[values.length - 1]
|
||||
this.$emit('change', value)
|
||||
},
|
||||
initAreaData () {
|
||||
if (!this.areaData) {
|
||||
this.areaData = new Area(this.$Jpcaa)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
model: { prop: 'value', event: 'change' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.j-area-linkage {
|
||||
height: 40px;
|
||||
|
||||
/deep/ .area-cascader-wrap .area-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .area-select .area-selected-trigger {
|
||||
line-height: 1.15;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JCategorySelect',
|
||||
props: {
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadTriggleChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
pid: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
back: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: '',
|
||||
treeData: [],
|
||||
url: '/sys/category/loadTreeData',
|
||||
view: '/sys/category/loadDictItem/',
|
||||
tableName: '',
|
||||
text: '',
|
||||
code: ''
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
pCode () {
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.validateProp().then(() => {
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/** 加载一级节点 */
|
||||
loadRoot () {
|
||||
const param = {
|
||||
pid: this.pid,
|
||||
pcode: !this.pCode ? '0' : this.pCode,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success && res.result) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
console.log('树一级节点查询结果-else', res)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 数据回显 */
|
||||
loadItemByCode () {
|
||||
if (!this.value || this.value === '0') {
|
||||
this.treeValue = []
|
||||
} else {
|
||||
getAction(this.view, { ids: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
if (res.result && res.result.length > 0) {
|
||||
const values = this.value.split(',')
|
||||
this.treeValue = res.result.map((item, index) => ({
|
||||
key: values[index],
|
||||
value: values[index],
|
||||
label: item
|
||||
}))
|
||||
this.onLoadTriggleChange(res.result[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoadTriggleChange (text) {
|
||||
// 只有单选才会触发
|
||||
if (!this.multiple && this.loadTriggleChange) {
|
||||
this.backValue(this.value, text)
|
||||
}
|
||||
},
|
||||
backValue (value, label) {
|
||||
const obj = {}
|
||||
if (this.back) {
|
||||
obj[this.back] = label
|
||||
}
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', value, obj)
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') {
|
||||
if (!children || children.length === 0) {
|
||||
item.isLeaf = true
|
||||
} else {
|
||||
item.children = children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
this.treeValue = ''
|
||||
} else if (Array.isArray(value)) {
|
||||
const labels = []
|
||||
const values = value.map(item => {
|
||||
labels.push(item.label)
|
||||
return item.value
|
||||
})
|
||||
this.backValue(values.join(','), labels.join(','))
|
||||
this.treeValue = value
|
||||
} else {
|
||||
this.backValue(value.value, value.label)
|
||||
this.treeValue = value
|
||||
}
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
},
|
||||
validateProp () {
|
||||
const myCondition = this.condition
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!myCondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
const test = JSON.parse(myCondition)
|
||||
if (typeof test === 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.warning('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.warning('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-checkbox-group :options="options"
|
||||
:value="checkboxArray"
|
||||
v-bind="$attrs"
|
||||
@change="onChange" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JCheckbox',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
/* label value */
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checkboxArray: !this.value ? [] : this.value.split(',')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.checkboxArray = []
|
||||
} else {
|
||||
this.checkboxArray = this.value.split(',')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (checkedValues) {
|
||||
this.$emit('change', checkedValues.join(','))
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<div v-bind="fullScreenParentProps">
|
||||
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder" />
|
||||
|
||||
<div class="code-editor-cust full-screen-child">
|
||||
<textarea ref="textarea"></textarea>
|
||||
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
|
||||
<template v-if="languageChange">
|
||||
<a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
|
||||
<a-select-option
|
||||
v-for="mode in modes"
|
||||
:key="mode.value"
|
||||
:value="mode.value">
|
||||
{{ mode.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/ecmascript-6">
|
||||
// 引入全局实例
|
||||
import _CodeMirror from 'codemirror'
|
||||
|
||||
// 核心样式
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
// 引入主题后还需要在 options 中指定主题才会生效 darcula gruvbox-dark hopscotch monokai
|
||||
import 'codemirror/theme/panda-syntax.css'
|
||||
// 提示css
|
||||
import 'codemirror/addon/hint/show-hint.css'
|
||||
|
||||
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
||||
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
|
||||
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/css/css.js'
|
||||
import 'codemirror/mode/xml/xml.js'
|
||||
import 'codemirror/mode/clike/clike.js'
|
||||
import 'codemirror/mode/markdown/markdown.js'
|
||||
import 'codemirror/mode/python/python.js'
|
||||
import 'codemirror/mode/r/r.js'
|
||||
import 'codemirror/mode/shell/shell.js'
|
||||
import 'codemirror/mode/sql/sql.js'
|
||||
import 'codemirror/mode/swift/swift.js'
|
||||
import 'codemirror/mode/vue/vue.js'
|
||||
|
||||
import { isIE11, isIE } from '@/utils/browser'
|
||||
|
||||
// 尝试获取全局实例
|
||||
const CodeMirror = window.CodeMirror || _CodeMirror
|
||||
|
||||
export default {
|
||||
name: 'JCodeEditor',
|
||||
props: {
|
||||
// 外部传入的内容,用于实现双向绑定
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外部传入的语法类型
|
||||
language: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
languageChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 显示行号
|
||||
lineNumbers: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 全屏以后的z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
// 是否自适应高度,可以传String或Boolean
|
||||
// 传 String 类型只能写"!ie" ,
|
||||
// 填写这个字符串,代表其他浏览器自适应高度
|
||||
// 唯独IE下不自适应高度,因为IE下不支持min、max-height样式
|
||||
// 如果填写的不是"!ie"就视为true
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 不自适应高度的情况下生效的固定高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '240px'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部真实的内容
|
||||
code: '',
|
||||
iconType: 'fullscreen',
|
||||
hasCode: false,
|
||||
// 默认的语法类型
|
||||
mode: 'javascript',
|
||||
// 编辑器实例
|
||||
coder: null,
|
||||
// 默认配置
|
||||
options: {
|
||||
// 缩进格式
|
||||
tabSize: 2,
|
||||
// 主题,对应主题库 JS 需要提前引入
|
||||
theme: 'panda-syntax',
|
||||
line: true,
|
||||
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
|
||||
hintOptions: {
|
||||
tables: {
|
||||
users: ['name', 'score', 'birthDate'],
|
||||
countries: ['name', 'population', 'size']
|
||||
}
|
||||
}
|
||||
},
|
||||
// 支持切换的语法高亮类型,对应 JS 已经提前引入
|
||||
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
|
||||
modes: [{
|
||||
value: 'css',
|
||||
label: 'CSS'
|
||||
}, {
|
||||
value: 'javascript',
|
||||
label: 'Javascript'
|
||||
}, {
|
||||
value: 'html',
|
||||
label: 'XML/HTML'
|
||||
}, {
|
||||
value: 'x-java',
|
||||
label: 'Java'
|
||||
}, {
|
||||
value: 'x-objectivec',
|
||||
label: 'Objective-C'
|
||||
}, {
|
||||
value: 'x-python',
|
||||
label: 'Python'
|
||||
}, {
|
||||
value: 'x-rsrc',
|
||||
label: 'R'
|
||||
}, {
|
||||
value: 'x-sh',
|
||||
label: 'Shell'
|
||||
}, {
|
||||
value: 'x-sql',
|
||||
label: 'SQL'
|
||||
}, {
|
||||
value: 'x-swift',
|
||||
label: 'Swift'
|
||||
}, {
|
||||
value: 'x-vue',
|
||||
label: 'Vue'
|
||||
}, {
|
||||
value: 'markdown',
|
||||
label: 'Markdown'
|
||||
}],
|
||||
// code 编辑器 是否全屏
|
||||
fullCoder: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fullCoder: {
|
||||
handler (value) {
|
||||
if (value) {
|
||||
this.iconType = 'fullscreen-exit'
|
||||
} else {
|
||||
this.iconType = 'fullscreen'
|
||||
}
|
||||
}
|
||||
},
|
||||
// value: {
|
||||
// immediate: false,
|
||||
// handler(value) {
|
||||
// this._getCoder().then(() => {
|
||||
// this.coder.setValue(value)
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
language: {
|
||||
immediate: true,
|
||||
handler (language) {
|
||||
this._getCoder().then(() => {
|
||||
// 尝试从父容器获取语法类型
|
||||
if (language) {
|
||||
// 获取具体的语法类型对象
|
||||
const modeObj = this._getLanguage(language)
|
||||
|
||||
// 判断父容器传入的语法是否被支持
|
||||
if (modeObj) {
|
||||
this.mode = modeObj.label
|
||||
this.coder.setOption('mode', `text/${modeObj.value}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholderShow () {
|
||||
if (this.placeholder == null) {
|
||||
return `请在此输入${this.language}代码`
|
||||
} else {
|
||||
return this.placeholder
|
||||
}
|
||||
},
|
||||
nullTipStyle () {
|
||||
if (this.lineNumbers) {
|
||||
return { left: '36px' }
|
||||
} else {
|
||||
return { left: '12px' }
|
||||
}
|
||||
},
|
||||
// coder 配置
|
||||
coderOptions () {
|
||||
return {
|
||||
tabSize: this.options.tabSize,
|
||||
theme: this.options.theme,
|
||||
lineNumbers: this.lineNumbers,
|
||||
line: true,
|
||||
hintOptions: this.options.hintOptions
|
||||
}
|
||||
},
|
||||
isAutoHeight () {
|
||||
let { autoHeight } = this
|
||||
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
|
||||
autoHeight = !(isIE() || isIE11())
|
||||
} else {
|
||||
autoHeight = true
|
||||
}
|
||||
return autoHeight
|
||||
},
|
||||
fullScreenParentProps () {
|
||||
const props = {
|
||||
class: {
|
||||
'full-screen-parent': true,
|
||||
'full-screen': this.fullCoder,
|
||||
'auto-height': this.isAutoHeight
|
||||
},
|
||||
style: {}
|
||||
}
|
||||
if (isIE() || isIE11()) {
|
||||
props.style.height = '240px'
|
||||
}
|
||||
if (this.fullCoder) {
|
||||
props.style['z-index'] = this.zIndex
|
||||
}
|
||||
if (!this.isAutoHeight) {
|
||||
props.style.height = (typeof this.height === 'number' ? this.height + 'px' : this.height)
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 初始化
|
||||
this._initialize()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
_initialize () {
|
||||
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
|
||||
this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
|
||||
// 编辑器赋值
|
||||
if (this.value || this.code) {
|
||||
this.hasCode = true
|
||||
// this.coder.setValue(this.value || this.code)
|
||||
this.setCodeContent(this.value || this.code)
|
||||
} else {
|
||||
this.coder.setValue('')
|
||||
this.hasCode = false
|
||||
}
|
||||
// 支持双向绑定
|
||||
this.coder.on('change', (coder) => {
|
||||
this.code = coder.getValue()
|
||||
this.hasCode = !!this.code
|
||||
if (this.$emit) {
|
||||
this.$emit('input', this.code)
|
||||
}
|
||||
})
|
||||
this.coder.on('focus', () => {
|
||||
this.hasCode = true
|
||||
})
|
||||
this.coder.on('blur', () => {
|
||||
this.hasCode = !!this.code
|
||||
})
|
||||
|
||||
/* this.coder.on('cursorActivity',()=>{
|
||||
this.coder.showHint()
|
||||
}) */
|
||||
},
|
||||
getCodeContent () {
|
||||
return this.code
|
||||
},
|
||||
setCodeContent (val) {
|
||||
setTimeout(() => {
|
||||
if (!val) {
|
||||
this.coder.setValue('')
|
||||
} else {
|
||||
this.coder.setValue(val)
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
// 获取当前语法类型
|
||||
_getLanguage (language) {
|
||||
// 在支持的语法类型列表中寻找传入的语法类型
|
||||
return this.modes.find((mode) => {
|
||||
// 所有的值都忽略大小写,方便比较
|
||||
const currentLanguage = language.toLowerCase()
|
||||
const currentLabel = mode.label.toLowerCase()
|
||||
const currentValue = mode.value.toLowerCase()
|
||||
|
||||
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
||||
return currentLabel === currentLanguage || currentValue === currentLanguage
|
||||
})
|
||||
},
|
||||
_getCoder () {
|
||||
const _this = this
|
||||
return new Promise((resolve) => {
|
||||
(function get () {
|
||||
if (_this.coder) {
|
||||
resolve(_this.coder)
|
||||
} else {
|
||||
setTimeout(get, 10)
|
||||
}
|
||||
})()
|
||||
})
|
||||
},
|
||||
// 更改模式
|
||||
changeMode (val) {
|
||||
// 修改编辑器的语法配置
|
||||
this.coder.setOption('mode', `text/${val}`)
|
||||
|
||||
// 获取修改后的语法
|
||||
const label = this._getLanguage(val).label.toLowerCase()
|
||||
|
||||
// 允许父容器通过以下函数监听当前的语法值
|
||||
this.$emit('language-change', label)
|
||||
},
|
||||
nullTipClick () {
|
||||
this.coder.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.code-editor-cust {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.CodeMirror {
|
||||
flex-grow: 1;
|
||||
z-index: 1;
|
||||
|
||||
.CodeMirror-code {
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.code-mode-select {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.null-tip {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 36px;
|
||||
z-index: 10;
|
||||
color: #ffffffc9;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.null-tip-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**选中样式偶然出现高度不够的情况*/
|
||||
|
||||
.CodeMirror-selected {
|
||||
min-height: 19px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全屏样式 */
|
||||
.full-screen-parent {
|
||||
position: relative;
|
||||
|
||||
.full-screen-icon {
|
||||
opacity: 0;
|
||||
color: black;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.full-screen-icon {
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.full-screen-icon {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.auto-height {
|
||||
.full-screen-child {
|
||||
min-height: 120px;
|
||||
max-height: 320px;
|
||||
height: unset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.full-screen .full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
height: 18.4px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="components-input-demo-presuffix">
|
||||
<a-input @click="openModal" placeholder="cron表达式" v-model="cron" @change="(e)=>handleOK(e.target.value)">
|
||||
<a-icon slot="prefix" type="schedule" title="cron控件"/>
|
||||
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</a-input>
|
||||
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import JCronModal from './modal/JCronModal'
|
||||
export default {
|
||||
name: 'JCron',
|
||||
components: {
|
||||
JCronModal
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false,
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
cron: this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.cron = val
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
this.$refs.innerVueCron.show()
|
||||
},
|
||||
handleOK (val) {
|
||||
this.cron = val
|
||||
this.$emit('change', this.cron)
|
||||
// this.$emit("change", Object.assign({}, this.cron));
|
||||
},
|
||||
handleEmpty () {
|
||||
this.handleOK('')
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.components-input-demo-presuffix .anticon-close-circle {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
transition: color 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
.components-input-demo-presuffix .anticon-close-circle:hover {
|
||||
color: #f5222d;
|
||||
}
|
||||
.components-input-demo-presuffix .anticon-close-circle:active {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
:disabledDate="disabledDate"
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
@change="handleDateChange"
|
||||
@panelChange="panelChange"
|
||||
@openChange="handOpenChange"
|
||||
:open="isOpen"
|
||||
:value="momVal"
|
||||
:showTime="showTime"
|
||||
:format="dateFormat"
|
||||
:mode="mode"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
style="width: 100%" />
|
||||
</template>
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
|
||||
export default {
|
||||
name: 'JDate',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
disabledDate: {
|
||||
type: Function
|
||||
},
|
||||
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
|
||||
},
|
||||
showType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'time'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const dateStr = this.value
|
||||
return {
|
||||
decorator: '',
|
||||
momVal: !dateStr ? null : moment(dateStr, this.dateFormat),
|
||||
isOpen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mode () {
|
||||
if (this.showType === 'time') {
|
||||
return 'time'
|
||||
}
|
||||
if (this.showType === 'day') {
|
||||
return 'date'
|
||||
}
|
||||
if (this.showType === 'month') {
|
||||
return 'month'
|
||||
}
|
||||
if (this.showType === 'year') {
|
||||
return 'year'
|
||||
}
|
||||
return 'date'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.momVal = null
|
||||
} else {
|
||||
this.momVal = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleDateChange (mom, dateStr) {
|
||||
this.$emit('change', dateStr)
|
||||
},
|
||||
panelChange (value) {
|
||||
const dateStr = moment(value).format(this.dateFormat)
|
||||
this.isOpen = false
|
||||
this.$emit('change', dateStr)
|
||||
},
|
||||
handOpenChange (open) {
|
||||
this.isOpen = !!open
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
/deep/ .ant-input {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="j-easy-cron">
|
||||
<div class="content">
|
||||
<div>
|
||||
<a-tabs size="small" v-model="curtab">
|
||||
<a-tab-pane tab="秒" key="second" v-if="!hideSecond">
|
||||
<second-ui v-model="second" :disabled="disabled"></second-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="分" key="minute">
|
||||
<minute-ui v-model="minute" :disabled="disabled"></minute-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="时" key="hour">
|
||||
<hour-ui v-model="hour" :disabled="disabled"></hour-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="日" key="day">
|
||||
<day-ui v-model="day" :week="week" :disabled="disabled"></day-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="月" key="month">
|
||||
<month-ui v-model="month" :disabled="disabled"></month-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="周" key="week">
|
||||
<week-ui v-model="week" :day="day" :disabled="disabled"></week-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="年" key="year" v-if="!hideYear && !hideSecond">
|
||||
<year-ui v-model="year" :disabled="disabled"></year-ui>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-divider />
|
||||
<!-- 执行时间预览 -->
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="18" style="margin-top: 22px;">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="秒" v-model="inputValues.second" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="分" v-model="inputValues.minute" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="时" v-model="inputValues.hour" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="日" v-model="inputValues.day" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="月" v-model="inputValues.month" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="周" v-model="inputValues.week" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="年" v-model="inputValues.year" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="16" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="Cron" v-model="inputValues.cron" @blur="onInputCronBlur" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
|
||||
<div>近十次执行时间(不含年)</div>
|
||||
<a-textarea type="textarea" :value="preTimeList" :rows="5" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SecondUi from './tabs/second'
|
||||
import MinuteUi from './tabs/minute'
|
||||
import HourUi from './tabs/hour'
|
||||
import DayUi from './tabs/day'
|
||||
import WeekUi from './tabs/week'
|
||||
import MonthUi from './tabs/month'
|
||||
import YearUi from './tabs/year'
|
||||
import CronParser from 'cron-parser'
|
||||
import dateFormat from './format-date'
|
||||
import { simpleDebounce } from '@/utils/util'
|
||||
import ACol from 'ant-design-vue/es/grid/Col'
|
||||
|
||||
export default {
|
||||
name: 'easy-cron',
|
||||
components: {
|
||||
ACol,
|
||||
SecondUi,
|
||||
MinuteUi,
|
||||
HourUi,
|
||||
DayUi,
|
||||
WeekUi,
|
||||
MonthUi,
|
||||
YearUi
|
||||
},
|
||||
props: {
|
||||
cronValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideSecond: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideYear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
curtab: this.hideSecond ? 'minute' : 'second',
|
||||
second: '*',
|
||||
minute: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?',
|
||||
year: '*',
|
||||
inputValues: { second: '', minute: '', hour: '', day: '', month: '', week: '', year: '', cron: '' },
|
||||
preTimeList: '执行预览,会忽略年份参数'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cronValue_c () {
|
||||
const result = []
|
||||
if (!this.hideSecond) result.push(this.second ? this.second : '*')
|
||||
result.push(this.minute ? this.minute : '*')
|
||||
result.push(this.hour ? this.hour : '*')
|
||||
result.push(this.day ? this.day : '*')
|
||||
result.push(this.month ? this.month : '*')
|
||||
result.push(this.week ? this.week : '?')
|
||||
if (!this.hideYear && !this.hideSecond) result.push(this.year ? this.year : '*')
|
||||
return result.join(' ')
|
||||
},
|
||||
cronValue_c2 () {
|
||||
const v = this.cronValue_c
|
||||
if (this.hideYear || this.hideSecond) return v
|
||||
const vs = v.split(' ')
|
||||
if (vs.length >= 6) {
|
||||
// 将 Quartz 星期 的规则转换为 CronParser 的规则
|
||||
vs[5] = this.convertQuartzWeekToCParser(vs[5])
|
||||
}
|
||||
return vs.slice(0, vs.length - 1).join(' ')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cronValue (newVal) {
|
||||
if (newVal === this.cronValue_c) {
|
||||
// console.info('same cron value: ' + newVal)
|
||||
return
|
||||
}
|
||||
this.formatValue()
|
||||
},
|
||||
cronValue_c (newVal) {
|
||||
this.calTriggerList()
|
||||
this.$emit('change', newVal)
|
||||
this.assignInput()
|
||||
},
|
||||
minute () {
|
||||
if (this.second === '*') {
|
||||
this.second = '0'
|
||||
}
|
||||
},
|
||||
hour () {
|
||||
if (this.minute === '*') {
|
||||
this.minute = '0'
|
||||
}
|
||||
},
|
||||
day (day) {
|
||||
if (day !== '?' && this.hour === '*') {
|
||||
this.hour = '0'
|
||||
}
|
||||
},
|
||||
week (week) {
|
||||
if (week !== '?' && this.hour === '*') {
|
||||
this.hour = '0'
|
||||
}
|
||||
},
|
||||
month () {
|
||||
if (this.day === '?' && this.week === '*') {
|
||||
this.week = '1'
|
||||
} else if (this.week === '?' && this.day === '*') {
|
||||
this.day = '1'
|
||||
}
|
||||
},
|
||||
year () {
|
||||
if (this.month === '*') {
|
||||
this.month = '1'
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.formatValue()
|
||||
this.$nextTick(() => {
|
||||
this.calTriggerListInner()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
assignInput () {
|
||||
Object.assign(this.inputValues, {
|
||||
second: this.second,
|
||||
minute: this.minute,
|
||||
hour: this.hour,
|
||||
day: this.day,
|
||||
month: this.month,
|
||||
week: this.week,
|
||||
year: this.year,
|
||||
cron: this.cronValue_c
|
||||
})
|
||||
},
|
||||
formatValue () {
|
||||
if (!this.cronValue) return
|
||||
const values = this.cronValue.split(' ').filter(item => !!item)
|
||||
if (!values || values.length <= 0) return
|
||||
let i = 0
|
||||
if (!this.hideSecond) this.second = values[i++]
|
||||
if (values.length > i) this.minute = values[i++]
|
||||
if (values.length > i) this.hour = values[i++]
|
||||
if (values.length > i) this.day = values[i++]
|
||||
if (values.length > i) this.month = values[i++]
|
||||
if (values.length > i) this.week = values[i++]
|
||||
if (values.length > i) this.year = values[i]
|
||||
this.assignInput()
|
||||
},
|
||||
// 将 Quartz 星期 的规则转换为 CronParser 的规则:
|
||||
// Quartz 的规则:1 = 周日,2 = 周一,3 = 周二,4 = 周三,5 = 周四,6 = 周五,7 = 周六
|
||||
// CronParser 的规则: 0 = 周日,1 = 周一,2 = 周二,3 = 周三,4 = 周四,5 = 周五,6 = 周六,7 = 周日
|
||||
convertQuartzWeekToCParser (week) {
|
||||
const convert = (v) => {
|
||||
if (v === '0') {
|
||||
return '1'
|
||||
}
|
||||
if (v === '1') {
|
||||
return '0'
|
||||
}
|
||||
return (Number.parseInt(v) - 1).toString()
|
||||
}
|
||||
// 匹配示例 1-7 or 1/7
|
||||
const patten1 = /^([0-7])([-/])([0-7])$/
|
||||
// 匹配示例 1,4,7
|
||||
const patten2 = /^([0-7])(,[0-7])+$/
|
||||
if (/^[0-7]$/.test(week)) {
|
||||
return convert(week)
|
||||
} else if (patten1.test(week)) {
|
||||
return week.replace(patten1, ($0, before, separator, after) => {
|
||||
if (separator === '/') {
|
||||
return convert(before) + separator + after
|
||||
} else {
|
||||
return convert(before) + separator + convert(after)
|
||||
}
|
||||
})
|
||||
} else if (patten2.test(week)) {
|
||||
return week.split(',').map(v => convert(v)).join(',')
|
||||
}
|
||||
return week
|
||||
},
|
||||
calTriggerList: simpleDebounce(function () {
|
||||
this.calTriggerListInner()
|
||||
}, 500),
|
||||
calTriggerListInner () {
|
||||
// 设置了回调函数
|
||||
if (this.remote) {
|
||||
this.remote(this.cronValue_c, +new Date(), v => {
|
||||
this.preTimeList = v
|
||||
})
|
||||
return
|
||||
}
|
||||
const format = 'yyyy-MM-dd hh:mm:ss'
|
||||
const options = {
|
||||
currentDate: dateFormat(new Date(), format)
|
||||
}
|
||||
const iter = CronParser.parseExpression(this.cronValue_c2, options)
|
||||
const result = []
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
result.push(dateFormat(new Date(iter.next()), format))
|
||||
}
|
||||
this.preTimeList = result.length > 0 ? result.join('\n') : '无执行时间'
|
||||
},
|
||||
onInputBlur () {
|
||||
this.second = this.inputValues.second
|
||||
this.minute = this.inputValues.minute
|
||||
this.hour = this.inputValues.hour
|
||||
this.day = this.inputValues.day
|
||||
this.month = this.inputValues.month
|
||||
this.week = this.inputValues.week
|
||||
this.year = this.inputValues.year
|
||||
},
|
||||
onInputCronBlur (event) {
|
||||
this.$emit('change', event.target.value)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'cronValue',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.j-easy-cron {
|
||||
|
||||
/deep/ .content {
|
||||
.ant-checkbox-wrapper + .ant-checkbox-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="input-cron">
|
||||
<a-input :placeholder="placeholder" v-model="editCronValue" :disabled="disabled">
|
||||
<a slot="addonAfter" @click="showConfigDlg" class="config-btn" :disabled="disabled">
|
||||
<a-icon type="setting"></a-icon>
|
||||
选择
|
||||
</a>
|
||||
</a-input>
|
||||
<j-modal :visible.sync="show" title="Cron表达式" width="800px">
|
||||
<easy-cron
|
||||
v-model="editCronValue"
|
||||
:exeStartTime="exeStartTime"
|
||||
:hideYear="hideYear"
|
||||
:remote="remote"
|
||||
:hideSecond="hideSecond"
|
||||
style="width: 100%"
|
||||
></easy-cron>
|
||||
</j-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EasyCron from './EasyCron.vue'
|
||||
|
||||
export default {
|
||||
name: 'input-cron',
|
||||
components: { EasyCron },
|
||||
model: {
|
||||
prop: 'cronValue',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
cronValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '800px'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入cron表达式'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
exeStartTime: {
|
||||
type: [Number, String, Object],
|
||||
default: 0
|
||||
},
|
||||
hideSecond: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideYear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editCronValue: this.cronValue,
|
||||
show: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cronValue (newVal) {
|
||||
if (newVal === this.editCronValue) {
|
||||
return
|
||||
}
|
||||
this.editCronValue = newVal
|
||||
},
|
||||
editCronValue (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showConfigDlg () {
|
||||
if (!this.disabled) {
|
||||
this.show = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.config-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
const dateFormat = (date, block) => {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let format = block || 'yyyy-MM-dd'
|
||||
|
||||
date = new Date(date)
|
||||
|
||||
const map = {
|
||||
M: date.getMonth() + 1, // 月份
|
||||
d: date.getDate(), // 日
|
||||
h: date.getHours(), // 小时
|
||||
m: date.getMinutes(), // 分
|
||||
s: date.getSeconds(), // 秒
|
||||
q: Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds() // 毫秒
|
||||
}
|
||||
|
||||
format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
|
||||
let v = map[t]
|
||||
if (v !== undefined) {
|
||||
if (all.length > 1) {
|
||||
v = `0${v}`
|
||||
v = v.substr(v.length - 2)
|
||||
}
|
||||
return v
|
||||
} else if (t === 'y') {
|
||||
return (date.getFullYear().toString()).substr(4 - all.length)
|
||||
}
|
||||
return all
|
||||
})
|
||||
|
||||
return format
|
||||
}
|
||||
|
||||
export default dateFormat
|
||||
@@ -0,0 +1,6 @@
|
||||
// 原开源项目地址:https://gitee.com/toktok/easy-cron
|
||||
|
||||
import InputCron from './InputCron.vue'
|
||||
|
||||
InputCron.name = 'JEasyCron'
|
||||
export default InputCron
|
||||
@@ -0,0 +1,21 @@
|
||||
export const WEEK_MAP_EN = {
|
||||
SUN: '1',
|
||||
MON: '2',
|
||||
TUE: '3',
|
||||
WED: '4',
|
||||
THU: '5',
|
||||
FRI: '6',
|
||||
SAT: '7'
|
||||
}
|
||||
|
||||
export const replaceWeekName = (c) => {
|
||||
// console.info('after: ' + c)
|
||||
if (c) {
|
||||
Object.keys(WEEK_MAP_EN).forEach(k => {
|
||||
c = c.replace(new RegExp(k, 'g'), WEEK_MAP_EN[k])
|
||||
})
|
||||
// c = c.replace(new RegExp('7', 'g'), '0')
|
||||
}
|
||||
// console.info('after: ' + c)
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disableChoice">每日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
日
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
日开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_WORK" class="choice" :disabled="disableChoice">工作日</a-radio>
|
||||
本月
|
||||
<a-input-number :disabled="type!==TYPE_WORK || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueWork" />
|
||||
日,最近的工作日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LAST" class="choice" :disabled="disableChoice">最后一日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i of specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'day',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
week: {
|
||||
type: String,
|
||||
default: '?'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
disableChoice () {
|
||||
return (this.week && this.week !== '?') || this.disabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value_c () {
|
||||
// 数值变化
|
||||
this.updateValue()
|
||||
},
|
||||
week () {
|
||||
// console.info('new week: ' + newVal)
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue () {
|
||||
this.$emit('change', this.disableChoice ? '?' : this.value_c)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 1
|
||||
this.maxValue = 31
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 31
|
||||
this.valueLoop.start = 1
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每时</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
时
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
时
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
时开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
时
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'minute',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 23
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 23
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每分</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
分
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
分
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
分开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
分
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'minute',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 59
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 59
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
// 主要用于日和星期的互斥使用
|
||||
const TYPE_NOT_SET = 'TYPE_NOT_SET'
|
||||
const TYPE_EVERY = 'TYPE_EVERY'
|
||||
const TYPE_RANGE = 'TYPE_RANGE'
|
||||
const TYPE_LOOP = 'TYPE_LOOP'
|
||||
const TYPE_WORK = 'TYPE_WORK'
|
||||
const TYPE_LAST = 'TYPE_LAST'
|
||||
const TYPE_SPECIFY = 'TYPE_SPECIFY'
|
||||
|
||||
const DEFAULT_VALUE = '?'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: 'prop',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
prop: {
|
||||
type: String,
|
||||
default: DEFAULT_VALUE
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const type = TYPE_EVERY
|
||||
return {
|
||||
DEFAULT_VALUE,
|
||||
// 类型
|
||||
type,
|
||||
// 启用日或者星期互斥用
|
||||
TYPE_NOT_SET,
|
||||
TYPE_EVERY,
|
||||
TYPE_RANGE,
|
||||
TYPE_LOOP,
|
||||
TYPE_WORK,
|
||||
TYPE_LAST,
|
||||
TYPE_SPECIFY,
|
||||
// 对于不同的类型,所定义的值也有所不同
|
||||
valueRange: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
valueLoop: {
|
||||
start: 0,
|
||||
interval: 1
|
||||
},
|
||||
valueWeek: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
valueList: [],
|
||||
valueWork: 1,
|
||||
maxValue: 0,
|
||||
minValue: 0,
|
||||
valueLast: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
prop (newVal) {
|
||||
if (newVal === this.value_c) {
|
||||
// console.info('skip ' + newVal)
|
||||
return
|
||||
}
|
||||
this.parseProp(newVal)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value_c () {
|
||||
const result = []
|
||||
switch (this.type) {
|
||||
case TYPE_NOT_SET:
|
||||
result.push('?')
|
||||
break
|
||||
case TYPE_EVERY:
|
||||
result.push('*')
|
||||
break
|
||||
case TYPE_RANGE:
|
||||
result.push(`${this.valueRange.start}-${this.valueRange.end}`)
|
||||
break
|
||||
case TYPE_LOOP:
|
||||
result.push(`${this.valueLoop.start}/${this.valueLoop.interval}`)
|
||||
break
|
||||
case TYPE_WORK:
|
||||
result.push(`${this.valueWork}W`)
|
||||
break
|
||||
case TYPE_LAST:
|
||||
result.push('L')
|
||||
break
|
||||
case TYPE_SPECIFY:
|
||||
if (this.valueList.length === 0) {
|
||||
this.valueList.push(this.minValue)
|
||||
}
|
||||
result.push(this.valueList.join(','))
|
||||
break
|
||||
default:
|
||||
result.push(this.DEFAULT_VALUE)
|
||||
break
|
||||
}
|
||||
return result.length > 0 ? result.join('') : this.DEFAULT_VALUE
|
||||
},
|
||||
// 指定值范围区间,介于最小值和最大值之间
|
||||
specifyRange () {
|
||||
const range = []
|
||||
for (let i = this.minValue; i <= this.maxValue; i++) {
|
||||
range.push(i)
|
||||
}
|
||||
return range
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseProp (value) {
|
||||
if (value === this.value_c) {
|
||||
// console.info('same ' + value)
|
||||
return
|
||||
}
|
||||
if (typeof (this.preProcessProp) === 'function') {
|
||||
value = this.preProcessProp(value)
|
||||
}
|
||||
try {
|
||||
if (!value || value === this.DEFAULT_VALUE) {
|
||||
this.type = TYPE_EVERY
|
||||
} else if (value.indexOf('?') >= 0) {
|
||||
this.type = TYPE_NOT_SET
|
||||
} else if (value.indexOf('-') >= 0) {
|
||||
this.type = TYPE_RANGE
|
||||
const values = value.split('-')
|
||||
if (values.length >= 2) {
|
||||
this.valueRange.start = parseInt(values[0])
|
||||
this.valueRange.end = parseInt(values[1])
|
||||
}
|
||||
} else if (value.indexOf('/') >= 0) {
|
||||
this.type = TYPE_LOOP
|
||||
const values = value.split('/')
|
||||
if (values.length >= 2) {
|
||||
this.valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0])
|
||||
this.valueLoop.interval = parseInt(values[1])
|
||||
}
|
||||
} else if (value.indexOf('W') >= 0) {
|
||||
this.type = TYPE_WORK
|
||||
const values = value.split('W')
|
||||
if (!values[0] && !isNaN(values[0])) {
|
||||
this.valueWork = parseInt(values[0])
|
||||
}
|
||||
} else if (value.indexOf('L') >= 0) {
|
||||
this.type = TYPE_LAST
|
||||
const values = value.split('L')
|
||||
this.valueLast = parseInt(values[0])
|
||||
} else if (value.indexOf(',') >= 0 || !isNaN(value)) {
|
||||
this.type = TYPE_SPECIFY
|
||||
this.valueList = value.split(',').map(item => parseInt(item))
|
||||
} else {
|
||||
this.type = TYPE_EVERY
|
||||
}
|
||||
} catch (e) {
|
||||
// console.info(e)
|
||||
this.type = TYPE_EVERY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
.config-list {
|
||||
text-align: left;
|
||||
margin: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.choice {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
|
||||
.w60 {
|
||||
width: 60px;
|
||||
}
|
||||
.w80 {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.list-check-item {
|
||||
padding: 1px 3px;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.tip-info {
|
||||
color: #999
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每月</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
月
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
月
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
月开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
月
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i of specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'month',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 1
|
||||
this.maxValue = 12
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 12
|
||||
this.valueLoop.start = 1
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每秒</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
秒
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
秒
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
秒开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
秒
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'second',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 59
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 59
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
// console.info('created')
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
|
||||
从
|
||||
<a-select v-model="valueRange.start" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
至
|
||||
<a-select v-model="valueRange.end" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
|
||||
从
|
||||
<a-select v-model="valueLoop.start" class="w80" :disabled="type!==TYPE_LOOP || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
天
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
import { replaceWeekName, WEEK_MAP_EN } from './const.js'
|
||||
|
||||
const WEEK_MAP = {
|
||||
周一: 2,
|
||||
周二: 3,
|
||||
周三: 4,
|
||||
周四: 5,
|
||||
周五: 6,
|
||||
周六: 7,
|
||||
// 按照国人习惯,将周日放到每周的最后一天
|
||||
周日: 1
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'week',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
day: {
|
||||
type: String,
|
||||
default: '*'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
WEEK_MAP,
|
||||
WEEK_MAP_EN
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disableChoice () {
|
||||
return (this.day && this.day !== '?') || this.disabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value_c () {
|
||||
// 如果设置日,那么星期就直接不设置
|
||||
this.updateValue()
|
||||
},
|
||||
day () {
|
||||
// console.info('new day: ' + newVal)
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue () {
|
||||
this.$emit('change', this.disableChoice ? '?' : this.value_c)
|
||||
},
|
||||
preProcessProp (c) {
|
||||
return replaceWeekName(c)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
// 0,7表示周日 1表示周一
|
||||
this.minValue = 1
|
||||
this.maxValue = 7
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 7
|
||||
this.valueLoop.start = 2
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每年</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="0" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
年
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="1" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
年
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="0" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
年开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="1" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
年
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'year',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
// console.info('change:' + newVal)
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const nowYear = (new Date()).getFullYear()
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 0
|
||||
this.valueRange.start = nowYear
|
||||
this.valueRange.end = nowYear + 100
|
||||
this.valueLoop.start = nowYear
|
||||
this.valueLoop.interval = 1
|
||||
// console.info('created')
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
import CronParser from 'cron-parser'
|
||||
import { replaceWeekName } from './tabs/const'
|
||||
|
||||
export default (rule, value, callback) => {
|
||||
// 没填写就不校验
|
||||
if (!value) {
|
||||
callback()
|
||||
return true
|
||||
}
|
||||
const values = value.split(' ').filter(item => !!item)
|
||||
if (values.length > 7) {
|
||||
callback(new Error('Cron表达式最多7项!'))
|
||||
return false
|
||||
}
|
||||
// 检查第7项
|
||||
let e = value
|
||||
if (values.length === 7) {
|
||||
const year = replaceWeekName(values[6])
|
||||
if (year !== '*' && year !== '?') {
|
||||
let yearValues
|
||||
if (year.indexOf('-') >= 0) {
|
||||
yearValues = year.split('-')
|
||||
} else if (year.indexOf('/')) {
|
||||
yearValues = year.split('/')
|
||||
} else {
|
||||
yearValues = [year]
|
||||
}
|
||||
// console.info(yearValues)
|
||||
// 判断是否都是数字
|
||||
const checkYear = yearValues.some(item => isNaN(item))
|
||||
if (checkYear) {
|
||||
callback(new Error('Cron表达式参数[年]错误:' + year))
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 取其中的前六项
|
||||
e = values.slice(0, 6).join(' ')
|
||||
}
|
||||
// 6位 没有年
|
||||
// 5位没有秒、年
|
||||
let result = true
|
||||
try {
|
||||
const iter = CronParser.parseExpression(e)
|
||||
iter.next()
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error('Cron表达式错误:' + e))
|
||||
result = false
|
||||
}
|
||||
return result
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="tinymce-editor">
|
||||
<editor
|
||||
v-if="!reloading"
|
||||
v-model="myValue"
|
||||
:init="init"
|
||||
:disabled="disabled"
|
||||
@onClick="onClick">
|
||||
</editor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tinymce from 'tinymce/tinymce'
|
||||
import Editor from '@tinymce/tinymce-vue'
|
||||
import 'tinymce/themes/silver/theme'
|
||||
import 'tinymce/plugins/image'
|
||||
import 'tinymce/plugins/link'
|
||||
import 'tinymce/plugins/media'
|
||||
import 'tinymce/plugins/table'
|
||||
import 'tinymce/plugins/lists'
|
||||
import 'tinymce/plugins/contextmenu'
|
||||
import 'tinymce/plugins/wordcount'
|
||||
import 'tinymce/plugins/colorpicker'
|
||||
import 'tinymce/plugins/textcolor'
|
||||
import 'tinymce/plugins/fullscreen'
|
||||
import 'tinymce/icons/default'
|
||||
import { uploadAction, getFileAccessHttpUrl } from '@/api/manage'
|
||||
import { getVmParentByName } from '@/utils/util'
|
||||
export default {
|
||||
components: {
|
||||
Editor
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plugins: {
|
||||
type: [String, Array],
|
||||
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
|
||||
},
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
|
||||
branding: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 初始化配置
|
||||
init: {
|
||||
language_url: '/tinymce/langs/zh_CN.js',
|
||||
language: 'zh_CN',
|
||||
skin_url: '/tinymce/skins/lightgray',
|
||||
height: 300,
|
||||
plugins: this.plugins,
|
||||
toolbar: this.toolbar,
|
||||
branding: false,
|
||||
menubar: false,
|
||||
toolbar_drawer: false,
|
||||
images_upload_handler: (blobInfo, success) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', blobInfo.blob(), blobInfo.filename())
|
||||
formData.append('biz', 'jeditor')
|
||||
formData.append('jeditor', '1')
|
||||
uploadAction(window._CONFIG.domianURL + '/sys/common/upload', formData).then((res) => {
|
||||
if (res.success) {
|
||||
if (res.message === 'local') {
|
||||
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
|
||||
success(img)
|
||||
} else {
|
||||
const img = getFileAccessHttpUrl(res.message)
|
||||
success(img)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
myValue: this.value,
|
||||
reloading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initATabsChangeAutoReload()
|
||||
},
|
||||
methods: {
|
||||
|
||||
reload () {
|
||||
this.reloading = true
|
||||
this.$nextTick(() => {
|
||||
this.reloading = false
|
||||
})
|
||||
},
|
||||
|
||||
onClick (e) {
|
||||
this.$emit('onClick', e, tinymce)
|
||||
},
|
||||
// 可以添加一些自己的自定义事件,如清空内容
|
||||
clear () {
|
||||
this.myValue = ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 自动判断父级是否是 <a-tabs/> 组件,然后添加事件监听,自动触发reload()
|
||||
*
|
||||
* 由于 tabs 组件切换会导致 tinymce 无法输入,
|
||||
* 只有重新加载才能使用(无论是vue版的还是jQuery版tinymce都有这个通病)
|
||||
*/
|
||||
initATabsChangeAutoReload () {
|
||||
// 获取父级
|
||||
const tabs = getVmParentByName(this, 'ATabs')
|
||||
const tabPane = getVmParentByName(this, 'ATabPane')
|
||||
if (tabs && tabPane) {
|
||||
// 用户自定义的 key
|
||||
const currentKey = tabPane.$vnode.key
|
||||
// 添加事件监听
|
||||
tabs.$on('change', (key) => {
|
||||
// 切换到自己时执行reload
|
||||
if (currentKey === key) {
|
||||
this.reload()
|
||||
}
|
||||
})
|
||||
// update--begin--autor:liusq-----date:20210316------for:富文本编辑器tab父组件可能导致的赋值问题------
|
||||
this.reload()
|
||||
// update--end--autor:liusq-----date:20210316------for:富文本编辑器tab父组件可能导致的赋值问题------
|
||||
} else {
|
||||
// update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
|
||||
const tabLayout = getVmParentByName(this, 'TabLayout')
|
||||
// update--begin--autor:liusq-----date:20210713------for:处理特殊情况excuteCallback不能使用------
|
||||
try {
|
||||
tabLayout.excuteCallback(() => {
|
||||
this.reload()
|
||||
})
|
||||
} catch (error) {
|
||||
if (tabLayout) {
|
||||
this.reload()
|
||||
}
|
||||
}
|
||||
// update--end--autor:liusq-----date:20210713------for:处理特殊情况excuteCallback不能使用------
|
||||
// update--begin--autor:wangshuai-----date:20200724------for:文本编辑器切换tab无法修改------
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
value (newValue) {
|
||||
this.myValue = (newValue == null ? '' : newValue)
|
||||
},
|
||||
myValue (newValue) {
|
||||
if (this.triggerChange) {
|
||||
this.$emit('change', newValue)
|
||||
} else {
|
||||
this.$emit('input', newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
<template slot="title">
|
||||
<span>{{value}}</span>
|
||||
</template>
|
||||
{{ value | ellipsis(length) }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JEllipsis',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 25
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div :class="disabled?'jero-form-container-disabled':''">
|
||||
<fieldset :disabled="disabled">
|
||||
<slot name="detail"></slot>
|
||||
</fieldset>
|
||||
<slot name="edit"></slot>
|
||||
<fieldset disabled>
|
||||
<slot></slot>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 使用方法
|
||||
* 在form下直接写这个组件就行了,
|
||||
*<a-form layout="inline" :form="form" >
|
||||
* <j-form-container :disabled="true">
|
||||
* <!-- 表单内容省略..... -->
|
||||
* </j-form-container>
|
||||
*</a-form>
|
||||
*/
|
||||
export default {
|
||||
name: 'JFormContainer',
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
console.log('我是表单禁用专用组件,但是我并不支持表单中iframe的内容禁用')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.jero-form-container-disabled{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.jero-form-container-disabled fieldset[disabled] {
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.jero-form-container-disabled .ant-select{
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jero-form-container-disabled .ant-upload-select{display:none}
|
||||
.jero-form-container-disabled .ant-upload-list{cursor:grabbing}
|
||||
.jero-form-container-disabled fieldset[disabled] .ant-upload-list{
|
||||
-ms-pointer-events: auto !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/*.jero-form-container-disabled .ant-upload-list-item-actions .anticon-delete,*/
|
||||
.jero-form-container-disabled .ant-upload-list-item .anticon-delete,
|
||||
.jero-form-container-disabled .ant-upload-list-item .anticon-close{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="img">
|
||||
<a-upload
|
||||
name="file"
|
||||
listType="picture-card"
|
||||
:multiple="isMultiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{biz:bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="beforeUpload"
|
||||
:disabled="disabled"
|
||||
v-bind="$attrs"
|
||||
:accept="accept"
|
||||
v-on="childListeners"
|
||||
@change="handleChange"
|
||||
@preview="handlePreview"
|
||||
:class="[!isMultiple?'imgupload':'', (!isMultiple && picUrl)?'image-upload-single-over':'' ]">
|
||||
<div>
|
||||
<!--<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>-->
|
||||
<div class="iconp">
|
||||
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
|
||||
<div class="ant-upload-text">{{ text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <j-image-preview-modal ref="JImagePreviewModal"></j-image-preview-modal>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
// import JImagePreviewModal from './modal/JImagePreviewModal'
|
||||
// const Base64 = require('js-base64').Base64
|
||||
|
||||
const uidGenerator = () => {
|
||||
return '-' + parseInt(Math.random() * 10000 + 1 + '', 10)
|
||||
}
|
||||
export default {
|
||||
name: 'JImageUpload',
|
||||
// components: { JImagePreviewModal },
|
||||
data () {
|
||||
return {
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
uploadLoading: false,
|
||||
image: false,
|
||||
picUrl: false,
|
||||
headers: {},
|
||||
fileList: [],
|
||||
accept: 'image/png, image/jpeg',
|
||||
previewImage: '',
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '上传'
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isMultiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler (val) {
|
||||
if (val instanceof Array) {
|
||||
this.initFileList(val.join(','))
|
||||
} else {
|
||||
this.initFileList(val)
|
||||
}
|
||||
if (!val || val.length === 0) {
|
||||
this.picUrl = false
|
||||
}
|
||||
},
|
||||
// 立刻执行handler
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const token = Vue.ls.get(ACCESS_TOKEN)
|
||||
this.headers = { 'X-Access-Token': token }
|
||||
},
|
||||
methods: {
|
||||
initFileList (paths) {
|
||||
if (!paths || paths.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
this.picUrl = true
|
||||
const fileList = []
|
||||
const arr = paths.split(',')
|
||||
for (let a = 0; a < arr.length; a++) {
|
||||
const url = getFileAccessHttpUrl(arr[a])
|
||||
fileList.push({
|
||||
uid: uidGenerator(),
|
||||
name: arr[a],
|
||||
status: 'done',
|
||||
url: url,
|
||||
response: {
|
||||
status: 'history',
|
||||
message: arr[a]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.fileList = fileList
|
||||
},
|
||||
beforeUpload: function (file) {
|
||||
const fileType = file.type
|
||||
if (fileType.indexOf('image') < 0) {
|
||||
this.$message.warning('请上传图片')
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleChange (info) {
|
||||
this.picUrl = false
|
||||
let fileList = info.fileList
|
||||
// update-begin-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
|
||||
if (this.number > 0) {
|
||||
fileList = fileList.slice(-this.number)
|
||||
}
|
||||
// update-end-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
|
||||
if (info.file.status === 'done') {
|
||||
if (info.file.response.success) {
|
||||
this.picUrl = true
|
||||
fileList = fileList.map((file) => {
|
||||
if (file.response) {
|
||||
file.url = `${file.response.result.id}`
|
||||
}
|
||||
return file
|
||||
})
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.warning(`${info.file.name} 上传失败.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
this.handlePathChange()
|
||||
}
|
||||
},
|
||||
// 预览
|
||||
handlePreview (file) {
|
||||
if (file && file.url) {
|
||||
console.log(file)
|
||||
// const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/${file.url}`
|
||||
// const url = `${window._CONFIG.onlinePreviewDomainURL}?url=${encodeURIComponent(Base64.encode(fileFullUrl))}`
|
||||
// window.open(url)
|
||||
// this.$refs.JImagePreviewModal.open(file)
|
||||
this.imageUrl = getFileAccessHttpUrl(file.name)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
}
|
||||
},
|
||||
getAvatarView () {
|
||||
if (this.fileList.length > 0) {
|
||||
const url = this.fileList[this.fileList.length - 1].url
|
||||
return getFileAccessHttpUrl(url)
|
||||
}
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
if (!this.isMultiple && uploadFiles && uploadFiles.length > 0) {
|
||||
arr.push(uploadFiles[uploadFiles.length - 1].url)
|
||||
} else {
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
this.previewVisible = false
|
||||
},
|
||||
close () {
|
||||
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .imgupload .ant-upload-select{display:block}
|
||||
/deep/ .imgupload .ant-upload.ant-upload-select-picture-card{ width:120px;height: 120px;}
|
||||
/deep/ .imgupload .iconp{padding:32px;}
|
||||
/* update--end--autor:lvdandan-----date:20201016------for:j-image-upload图片组件单张图片详情回显空白*/
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-modal
|
||||
title="导入EXCEL"
|
||||
:width="600"
|
||||
:visible="visible"
|
||||
:confirmLoading="uploading"
|
||||
@cancel="handleClose">
|
||||
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="true"
|
||||
accept=".xls,.xlsx"
|
||||
:fileList="fileList"
|
||||
:remove="handleRemove"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:beforeUpload="beforeUpload">
|
||||
<a-button>
|
||||
<a-icon type="upload" />
|
||||
选择导入文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
|
||||
<template slot="footer">
|
||||
<a-button @click="handleClose">关闭</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleImport"
|
||||
:disabled="fileList.length === 0"
|
||||
:loading="uploading">
|
||||
{{ uploading ? '上传中...' : '开始上传' }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { postAction } from '@/api/manage'
|
||||
export default {
|
||||
name: 'JImportModal',
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
biz: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
uploading: false,
|
||||
fileList: [],
|
||||
uploadAction: '',
|
||||
foreignKeys: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url (val) {
|
||||
if (val) {
|
||||
this.uploadAction = window._CONFIG.domianURL + val
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
console.log(this.$attrs)
|
||||
this.uploadAction = window._CONFIG.domianURL + this.url
|
||||
},
|
||||
methods: {
|
||||
handleClose () {
|
||||
this.visible = false
|
||||
},
|
||||
show (arg) {
|
||||
this.fileList = []
|
||||
this.uploading = false
|
||||
this.visible = true
|
||||
this.foreignKeys = arg
|
||||
},
|
||||
handleRemove (file) {
|
||||
const index = this.fileList.indexOf(file)
|
||||
const newFileList = this.fileList.slice()
|
||||
newFileList.splice(index, 1)
|
||||
this.fileList = newFileList
|
||||
},
|
||||
beforeUpload (file) {
|
||||
this.fileList = [...this.fileList, file]
|
||||
return false
|
||||
},
|
||||
handleImport () {
|
||||
const { fileList } = this
|
||||
const formData = new FormData()
|
||||
if (this.biz) {
|
||||
formData.append('isSingleTableImport', this.biz)
|
||||
}
|
||||
if (this.foreignKeys && this.foreignKeys.length > 0) {
|
||||
formData.append('foreignKeys', this.foreignKeys)
|
||||
}
|
||||
fileList.forEach((file) => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploading = true
|
||||
postAction(this.uploadAction, formData).then((res) => {
|
||||
this.uploading = false
|
||||
if (res.success) {
|
||||
this.$message.success(res.message)
|
||||
this.visible = false
|
||||
this.$emit('ok')
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-input :placeholder="placeholder"
|
||||
:value="inputVal"
|
||||
@input="backValue"
|
||||
v-bind="$props"
|
||||
v-on="childListeners"
|
||||
></a-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
const JINPUT_QUERY_LIKE = 'like'
|
||||
const JINPUT_QUERY_NE = 'ne'
|
||||
const JINPUT_QUERY_GE = 'ge' // 大于等于
|
||||
const JINPUT_QUERY_LE = 'le' // 小于等于
|
||||
|
||||
export default {
|
||||
name: 'JInput',
|
||||
props: {
|
||||
...Input.props, // 拿到全部外部的参数
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: JINPUT_QUERY_LIKE
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
trim: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function () {
|
||||
this.initVal()
|
||||
}
|
||||
},
|
||||
// 当 type 变化的时候重新计算值
|
||||
type () {
|
||||
this.backValue({ target: { value: this.inputVal } })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initVal () {
|
||||
if (!this.value) {
|
||||
this.inputVal = ''
|
||||
} else {
|
||||
let text = this.value
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
// 修复路由传参的值传送到jinput框被前后各截取了一位 #1336
|
||||
if (text.indexOf('*') !== -1) {
|
||||
text = text.substring(1, text.length - 1)
|
||||
}
|
||||
break
|
||||
case JINPUT_QUERY_NE:
|
||||
text = text.substring(1)
|
||||
break
|
||||
case JINPUT_QUERY_GE:
|
||||
text = text.substring(2)
|
||||
break
|
||||
case JINPUT_QUERY_LE:
|
||||
text = text.substring(2)
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.inputVal = text
|
||||
}
|
||||
},
|
||||
backValue (e) {
|
||||
let text = e.target.value
|
||||
if (text && this.trim === true) {
|
||||
text = text.trim()
|
||||
}
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
text = '*' + text + '*'
|
||||
break
|
||||
case JINPUT_QUERY_NE:
|
||||
text = '!' + text
|
||||
break
|
||||
case JINPUT_QUERY_GE:
|
||||
text = '>=' + text
|
||||
break
|
||||
case JINPUT_QUERY_LE:
|
||||
text = '<=' + text
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.$emit('change', text)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="loading-box" :style="style" v-if="loading">
|
||||
<div class="loading-content">
|
||||
<a-icon class="loading" type="loading" />
|
||||
<div class="loading-tips">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JLoading',
|
||||
props: {
|
||||
// 是否显示隐藏
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否固定
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
style: {}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.fixed) {
|
||||
const el = $(this.el)
|
||||
const top = `${el.offset().top}px`
|
||||
const left = `${el.offset().left}px`
|
||||
const width = `${el.outerWidth()}px`
|
||||
const height = `${el.outerHeight()}px`
|
||||
this.style = {
|
||||
position: 'fixed',
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height,
|
||||
'z-index': 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.loading-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1009;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
|
||||
.loading-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
color: #21c9cc;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
|
||||
.loading {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.spin-loading {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
.loading-tips {
|
||||
margin-top: 5px;
|
||||
font-size: 16px;
|
||||
color: #21c9cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
minHeight: '200px',
|
||||
previewStyle: 'vertical',
|
||||
useCommandShortcut: true,
|
||||
useDefaultHTMLSanitizer: true,
|
||||
usageStatistics: false,
|
||||
hideModeSwitch: false,
|
||||
toolbarItems: [
|
||||
'heading',
|
||||
'bold',
|
||||
'italic',
|
||||
'strike',
|
||||
'divider',
|
||||
'hr',
|
||||
'quote',
|
||||
'divider',
|
||||
'ul',
|
||||
'ol',
|
||||
'task',
|
||||
'indent',
|
||||
'outdent',
|
||||
'divider',
|
||||
'table',
|
||||
'link',
|
||||
'divider',
|
||||
'code',
|
||||
'codeblock'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="j-markdown-editor" :id="id"/>
|
||||
<div v-if="isShow">
|
||||
<j-modal
|
||||
title="图片上传"
|
||||
:visible.sync="dialogVisible"
|
||||
width="30%"
|
||||
:before-close="handleClose"
|
||||
@ok="handleOk">
|
||||
<a-tabs default-active-key="1" @change="handleChange">
|
||||
<a-tab-pane tab="本地图片上传" key="1" :forceRender="true">
|
||||
<j-upload v-model="fileList" :number="1"></j-upload>
|
||||
<div style="margin-top: 20px">
|
||||
<a-input v-model="remark" placeholder="请填写备注"></a-input>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="网络图片地址" key="2" :forceRender="true">
|
||||
<a-input v-model="networkPic" placeholder="请填写网络图片地址"></a-input>
|
||||
<a-input style="margin-top: 20px" v-model="remark" placeholder="请填写备注"></a-input>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</j-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import '@toast-ui/editor/dist/toastui-editor.css'
|
||||
import '@toast-ui/editor/dist/i18n/zh-cn'
|
||||
|
||||
import Editor from '@toast-ui/editor'
|
||||
import defaultOptions from './default-options'
|
||||
import JUpload from '@/components/jero/JUpload'
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JMarkdownEditor',
|
||||
components: {
|
||||
JUpload
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default () {
|
||||
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default () {
|
||||
return defaultOptions
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'markdown'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '300px'
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'zh-CN'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
isShow: false,
|
||||
activeIndex: '1',
|
||||
dialogVisible: false,
|
||||
index: '1',
|
||||
fileList: [],
|
||||
remark: '',
|
||||
imageName: '',
|
||||
imageUrl: '',
|
||||
networkPic: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorOptions () {
|
||||
const options = Object.assign({}, defaultOptions, this.options)
|
||||
options.initialEditType = this.mode
|
||||
options.height = this.height
|
||||
options.language = this.language
|
||||
return options
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newValue, preValue) {
|
||||
if (newValue !== preValue && newValue !== this.editor.getMarkdown()) {
|
||||
this.editor.setMarkdown(newValue)
|
||||
}
|
||||
},
|
||||
language () {
|
||||
this.destroyEditor()
|
||||
this.initEditor()
|
||||
},
|
||||
height (newValue) {
|
||||
this.editor.height(newValue)
|
||||
},
|
||||
mode (newValue) {
|
||||
this.editor.changeMode(newValue)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initEditor()
|
||||
},
|
||||
destroyed () {
|
||||
this.destroyEditor()
|
||||
},
|
||||
methods: {
|
||||
initEditor () {
|
||||
this.editor = new Editor({
|
||||
el: document.getElementById(this.id),
|
||||
...this.editorOptions
|
||||
})
|
||||
if (this.value) {
|
||||
this.editor.setMarkdown(this.value)
|
||||
}
|
||||
this.editor.on('change', () => {
|
||||
this.$emit('change', this.editor.getMarkdown())
|
||||
})
|
||||
// --begin 添加自定义上传按钮
|
||||
/*
|
||||
* 添加自定义按钮
|
||||
*/
|
||||
// 获取编辑器上的功能条
|
||||
const toolbar = this.editor.getUI().getToolbar()
|
||||
// 添加图片点击事件
|
||||
this.editor.eventManager.addEventType('isShowClickEvent')
|
||||
this.editor.eventManager.listen('isShowClickEvent', () => {
|
||||
this.isShow = true
|
||||
this.dialogVisible = true
|
||||
})
|
||||
// addImageBlobHook图片上传、剪切、拖拽都会走此方法
|
||||
// 删除默认监听事件
|
||||
this.editor.eventManager.removeEventHandler('addImageBlobHook')
|
||||
// 添加自定义监听事件
|
||||
this.editor.eventManager.listen('addImageBlobHook', (blob, callback) => {
|
||||
this.upload(blob, url => {
|
||||
callback(url)
|
||||
})
|
||||
})
|
||||
// 添加自定义按钮 第二个参数代表位置,不传默认放在最后
|
||||
toolbar.insertItem(15, {
|
||||
type: 'button',
|
||||
options: {
|
||||
name: 'customize',
|
||||
className: 'tui-image tui-toolbar-icons',
|
||||
event: 'isShowClickEvent',
|
||||
tooltip: '上传图片'
|
||||
}
|
||||
//
|
||||
})
|
||||
// --end 添加自定义上传按钮
|
||||
},
|
||||
destroyEditor () {
|
||||
if (!this.editor) return
|
||||
this.editor.off('change')
|
||||
this.editor.remove()
|
||||
},
|
||||
setMarkdown (value) {
|
||||
this.editor.setMarkdown(value)
|
||||
},
|
||||
getMarkdown () {
|
||||
return this.editor.getMarkdown()
|
||||
},
|
||||
setHtml (value) {
|
||||
this.editor.setHtml(value)
|
||||
},
|
||||
getHtml () {
|
||||
return this.editor.getHtml()
|
||||
},
|
||||
handleOk () {
|
||||
if (this.index === '1') {
|
||||
this.imageUrl = getFileAccessHttpUrl(this.fileList)
|
||||
if (this.remark) {
|
||||
this.addImgToMd(this.imageUrl, this.remark)
|
||||
} else {
|
||||
this.addImgToMd(this.imageUrl, '')
|
||||
}
|
||||
} else {
|
||||
if (this.remark) {
|
||||
this.addImgToMd(this.networkPic, this.remark)
|
||||
} else {
|
||||
this.addImgToMd(this.networkPic, '')
|
||||
}
|
||||
}
|
||||
this.index = '1'
|
||||
this.fileList = []
|
||||
this.imageName = ''
|
||||
this.imageUrl = ''
|
||||
this.remark = ''
|
||||
this.networkPic = ''
|
||||
this.dialogVisible = false
|
||||
this.isShow = false
|
||||
},
|
||||
handleClose (done) {
|
||||
done()
|
||||
},
|
||||
handleChange (val) {
|
||||
this.fileList = []
|
||||
this.remark = ''
|
||||
this.imageName = ''
|
||||
this.imageUrl = ''
|
||||
this.networkPic = ''
|
||||
this.index = val
|
||||
},
|
||||
// 添加图片到markdown
|
||||
addImgToMd (data, name) {
|
||||
const editor = this.editor.getCodeMirror()
|
||||
const editorHtml = this.editor.getCurrentModeEditor()
|
||||
const isMarkdownMode = this.editor.isMarkdownMode()
|
||||
if (isMarkdownMode) {
|
||||
editor.replaceSelection(``)
|
||||
} else {
|
||||
const range = editorHtml.getRange()
|
||||
const img = document.createElement('img')
|
||||
img.src = `${data}`
|
||||
img.alt = name
|
||||
range.insertNode(img)
|
||||
}
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
.j-markdown-editor {
|
||||
/deep/ .tui-editor-defaultUI {
|
||||
.te-mode-switch,
|
||||
.tui-scrollsync
|
||||
{
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
destroyOnClose
|
||||
>
|
||||
|
||||
<slot></slot>
|
||||
<!--有设置标题-->
|
||||
<template v-if="!isNoTitle" slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col class="left">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</a-col>
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!--没有设置标题-->
|
||||
<template v-else slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 处理 scopedSlots -->
|
||||
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 处理 slots -->
|
||||
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { getClass, getStyle } from '@/utils/props-util'
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JModal',
|
||||
props: {
|
||||
title: String,
|
||||
// 可使用 .sync 修饰符
|
||||
visible: Boolean,
|
||||
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许切换全屏(允许后右上角会出现一个按钮)
|
||||
switchFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击确定按钮的时候是否关闭弹窗
|
||||
okClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部使用的 slots ,不再处理
|
||||
usedSlots: ['title'],
|
||||
// 实际控制是否全屏的参数
|
||||
innerFullscreen: this.fullscreen
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
|
||||
_attrs () {
|
||||
const attrs = { ...this.$attrs }
|
||||
// 如果全屏就将宽度设为 100%
|
||||
if (this.innerFullscreen) {
|
||||
attrs.width = '100%'
|
||||
}
|
||||
return attrs
|
||||
},
|
||||
modalClass () {
|
||||
return {
|
||||
'j-modal-box': true,
|
||||
fullscreen: this.innerFullscreen,
|
||||
'no-title': this.isNoTitle,
|
||||
'no-footer': this.isNoFooter
|
||||
}
|
||||
},
|
||||
modalStyle () {
|
||||
const style = {}
|
||||
// 如果全屏就将top设为 0
|
||||
if (this.innerFullscreen) {
|
||||
style.top = '0'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isNoTitle () {
|
||||
return !this.title && !this.allSlotsKeys.includes('title')
|
||||
},
|
||||
isNoFooter () {
|
||||
return this._attrs.footer === null
|
||||
},
|
||||
slotsKeys () {
|
||||
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
scopedSlotsKeys () {
|
||||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys () {
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon () {
|
||||
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible () {
|
||||
if (this.visible) {
|
||||
this.innerFullscreen = this.fullscreen
|
||||
}
|
||||
},
|
||||
innerFullscreen (val) {
|
||||
this.$emit('update:fullscreen', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getClass (clazz) {
|
||||
return { ...getClass(this), ...clazz }
|
||||
},
|
||||
getStyle (style) {
|
||||
return { ...getStyle(this), ...style }
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
if (this.okClose) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 切换全屏 */
|
||||
toggleFullscreen () {
|
||||
this.innerFullscreen = !this.innerFullscreen
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.j-modal-box {
|
||||
&.fullscreen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
||||
// 兼容1.6.2版本的antdv
|
||||
& .ant-modal {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
& .ant-modal-content {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
|
||||
& .ant-modal-body {
|
||||
/* title 和 footer 各占 55px */
|
||||
height: calc(100% - 55px - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-title, &.no-footer {
|
||||
.ant-modal-body {
|
||||
height: calc(100% - 55px);
|
||||
}
|
||||
}
|
||||
&.no-title.no-footer {
|
||||
.ant-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-modal-title-row {
|
||||
.left {
|
||||
width: calc(100% - 56px - 56px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 56px;
|
||||
position: inherit;
|
||||
|
||||
.ant-modal-close {
|
||||
right: 56px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.no-title{
|
||||
.ant-modal-header {
|
||||
padding: 0 24px;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<j-modal :visible="visible" :confirmLoading="loading" :after-close="afterClose" v-bind="modalProps" @ok="onOk" @cancel="onCancel">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-html="content"></div>
|
||||
<a-form-model ref="form" :model="model" :rules="rules">
|
||||
<a-form-model-item prop="input">
|
||||
<a-input ref="input" v-model="model.input" v-bind="inputProps" @pressEnter="onInputPressEnter"/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-spin>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pick from 'lodash.pick'
|
||||
|
||||
export default {
|
||||
name: 'JPrompt',
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
content: '',
|
||||
// 弹窗参数
|
||||
modalProps: {
|
||||
title: ''
|
||||
},
|
||||
inputProps: {
|
||||
placeholder: ''
|
||||
},
|
||||
// form model
|
||||
model: {
|
||||
input: ''
|
||||
},
|
||||
// 校验
|
||||
rule: [],
|
||||
// 回调函数
|
||||
callback: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rules () {
|
||||
return {
|
||||
input: this.rule
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show (options) {
|
||||
this.content = options.content
|
||||
if (Array.isArray(options.rule)) {
|
||||
this.rule = options.rule
|
||||
}
|
||||
if (options.defaultValue != null) {
|
||||
this.model.input = options.defaultValue
|
||||
}
|
||||
// 取出常用的弹窗参数
|
||||
const pickModalProps = pick(options, 'title', 'centered', 'cancelText', 'closable', 'mask', 'maskClosable', 'okText', 'okType', 'okButtonProps', 'cancelButtonProps', 'width', 'wrapClassName', 'zIndex', 'dialogStyle', 'dialogClass')
|
||||
this.modalProps = Object.assign({}, pickModalProps, options.modalProps)
|
||||
// 取出常用的input参数
|
||||
const pickInputProps = pick(options, 'placeholder', 'allowClear')
|
||||
this.inputProps = Object.assign({}, pickInputProps, options.inputProps)
|
||||
// 回调函数
|
||||
this.callback = pick(options, 'onOk', 'onOkAsync', 'onCancel')
|
||||
this.visible = true
|
||||
this.$nextTick(() => this.$refs.input.focus())
|
||||
},
|
||||
|
||||
onOk () {
|
||||
this.$refs.form.validate((ok) => {
|
||||
if (ok) {
|
||||
const event = { value: this.model.input, target: this }
|
||||
// 异步方法优先级高于同步方法
|
||||
if (typeof this.callback.onOkAsync === 'function') {
|
||||
this.callback.onOkAsync(event)
|
||||
} else if (typeof this.callback.onOk === 'function') {
|
||||
this.callback.onOk(event)
|
||||
this.close()
|
||||
} else {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
if (typeof this.callback.onCancel === 'function') {
|
||||
this.callback.onCancel(this.model.input)
|
||||
}
|
||||
this.close()
|
||||
},
|
||||
|
||||
onInputPressEnter () {
|
||||
this.onOk()
|
||||
},
|
||||
|
||||
close () {
|
||||
this.visible = this.loading ? this.visible : false
|
||||
},
|
||||
|
||||
forceClose () {
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
showLoading () {
|
||||
this.loading = true
|
||||
},
|
||||
hideLoading () {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
afterClose (e) {
|
||||
if (typeof this.modalProps.afterClose === 'function') {
|
||||
this.modalProps.afterClose(e)
|
||||
}
|
||||
this.$emit('after-close', e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
import JModal from './JModal'
|
||||
import JPrompt from './JPrompt'
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.component(JModal.name, JModal)
|
||||
|
||||
const JPromptExtend = Vue.extend(JPrompt)
|
||||
Vue.prototype.$JPrompt = function (options = {}) {
|
||||
// 创建prompt实例
|
||||
const vm = new JPromptExtend().$mount()
|
||||
vm.show(options)
|
||||
// 关闭后销毁
|
||||
vm.$on('after-close', () => vm.$destroy())
|
||||
return vm
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
destroyOnClose
|
||||
>
|
||||
|
||||
<slot></slot>
|
||||
<!--有设置标题-->
|
||||
<template v-if="!isNoTitle" slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col class="left">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</a-col>
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!--没有设置标题-->
|
||||
<template v-else slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 处理 scopedSlots -->
|
||||
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 处理 slots -->
|
||||
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { getClass, getStyle } from '@/utils/props-util'
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JModal',
|
||||
props: {
|
||||
title: String,
|
||||
// 可使用 .sync 修饰符
|
||||
visible: Boolean,
|
||||
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许切换全屏(允许后右上角会出现一个按钮)
|
||||
switchFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击确定按钮的时候是否关闭弹窗
|
||||
okClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部使用的 slots ,不再处理
|
||||
usedSlots: ['title'],
|
||||
// 实际控制是否全屏的参数
|
||||
innerFullscreen: this.fullscreen
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
|
||||
_attrs () {
|
||||
const attrs = { ...this.$attrs }
|
||||
// 如果全屏就将宽度设为 100%
|
||||
if (this.innerFullscreen) {
|
||||
attrs.width = '100%'
|
||||
}
|
||||
return attrs
|
||||
},
|
||||
modalClass () {
|
||||
return {
|
||||
'j-modal-box': true,
|
||||
fullscreen: this.innerFullscreen,
|
||||
'no-title': this.isNoTitle,
|
||||
'no-footer': this.isNoFooter
|
||||
}
|
||||
},
|
||||
modalStyle () {
|
||||
const style = {}
|
||||
// 如果全屏就将top设为 0
|
||||
if (this.innerFullscreen) {
|
||||
style.top = '0'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isNoTitle () {
|
||||
return !this.title && !this.allSlotsKeys.includes('title')
|
||||
},
|
||||
isNoFooter () {
|
||||
return this._attrs.footer === null
|
||||
},
|
||||
slotsKeys () {
|
||||
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
scopedSlotsKeys () {
|
||||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys () {
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon () {
|
||||
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible () {
|
||||
if (this.visible) {
|
||||
this.innerFullscreen = this.fullscreen
|
||||
}
|
||||
},
|
||||
innerFullscreen (val) {
|
||||
this.$emit('update:fullscreen', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getClass (clazz) {
|
||||
return { ...getClass(this), ...clazz }
|
||||
},
|
||||
getStyle (style) {
|
||||
return { ...getStyle(this), ...style }
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
if (this.okClose) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 切换全屏 */
|
||||
toggleFullscreen () {
|
||||
this.innerFullscreen = !this.innerFullscreen
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-modal-box {
|
||||
&.fullscreen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
||||
// 兼容1.6.2版本的antdv
|
||||
& .ant-modal {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
& .ant-modal-content {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
|
||||
& .ant-modal-body {
|
||||
/* title 和 footer 各占 55px */
|
||||
height: calc(100% - 55px - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-title, &.no-footer {
|
||||
.ant-modal-body {
|
||||
height: calc(100% - 55px);
|
||||
}
|
||||
}
|
||||
&.no-title.no-footer {
|
||||
.ant-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-modal-title-row {
|
||||
.left {
|
||||
width: calc(100% - 56px - 56px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 56px;
|
||||
position: inherit;
|
||||
|
||||
.ant-modal-close {
|
||||
right: 56px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.no-title{
|
||||
.ant-modal-header {
|
||||
padding: 0 24px;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="components-input-demo-presuffix" v-if="avalid">
|
||||
<!---->
|
||||
<a-input @click="openModal" :placeholder="placeholder" v-model="showText" readOnly :disabled="disabled">
|
||||
<a-icon slot="prefix" type="cluster" :title="title"/>
|
||||
<a-icon v-if="showText" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</a-input>
|
||||
|
||||
<j-popup-onl-report
|
||||
ref="jPopupOnlReport"
|
||||
:code="code"
|
||||
:multi="multi"
|
||||
:sorter="sorter"
|
||||
:groupId="uniqGroupId"
|
||||
:param="param"
|
||||
@ok="callBack"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JPopupOnlReport from './modal/JPopupOnlReport'
|
||||
|
||||
export default {
|
||||
name: 'JPopup',
|
||||
components: {
|
||||
JPopupOnlReport
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
orgFields: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
destFields: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
/** 排序列,指定要排序的列,使用方式:列名=desc|asc */
|
||||
sorter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 1200,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// popup动态参数 支持系统变量语法
|
||||
param: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {}
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
/** 分组ID,用于将多个popup的请求合并到一起,不传不分组 */
|
||||
groupId: String
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showText: '',
|
||||
title: '',
|
||||
avalid: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uniqGroupId () {
|
||||
if (this.groupId) {
|
||||
const { groupId, code, field, orgFields, destFields } = this
|
||||
return `${groupId}_${code}_${field}_${orgFields}_${destFields}`
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (!val) {
|
||||
this.showText = ''
|
||||
} else {
|
||||
this.showText = val.split(this.spliter).join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
mounted () {
|
||||
if (!this.orgFields || !this.destFields || !this.code) {
|
||||
this.$message.warning('popup参数未正确配置!')
|
||||
this.avalid = false
|
||||
}
|
||||
if (this.destFields.split(',').length !== this.orgFields.split(',').length) {
|
||||
this.$message.warning('popup参数未正确配置,原始值和目标值数量不一致!')
|
||||
this.avalid = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
if (this.disabled === false) {
|
||||
this.$refs.jPopupOnlReport.show()
|
||||
}
|
||||
},
|
||||
handleEmpty () {
|
||||
this.showText = ''
|
||||
const destFieldsArr = this.destFields.split(',')
|
||||
if (destFieldsArr.length === 0) {
|
||||
return
|
||||
}
|
||||
const res = {}
|
||||
for (let i = 0; i < destFieldsArr.length; i++) {
|
||||
res[destFieldsArr[i]] = ''
|
||||
}
|
||||
if (this.triggerChange) {
|
||||
this.$emit('callback', res)
|
||||
} else {
|
||||
this.$emit('input', '', res)
|
||||
}
|
||||
},
|
||||
callBack (rows) {
|
||||
// update--begin--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
|
||||
const orgFieldsArr = this.orgFields.split(',')
|
||||
const destFieldsArr = this.destFields.split(',')
|
||||
let resetText = false
|
||||
if (this.field && this.field.length > 0) {
|
||||
this.showText = ''
|
||||
resetText = true
|
||||
}
|
||||
const res = {}
|
||||
if (orgFieldsArr.length > 0) {
|
||||
for (let i = 0; i < orgFieldsArr.length; i++) {
|
||||
const tempDestArr = []
|
||||
for (const rw of rows) {
|
||||
let val = rw[orgFieldsArr[i]]
|
||||
// update--begin--autor:liusq-----date:20210713------for:处理val等于0的情况issues/I3ZL4T------
|
||||
if (typeof val === 'undefined' || val == null || val.toString() === '') {
|
||||
val = ''
|
||||
}
|
||||
// update--end--autor:liusq-----date:20210713------for:处理val等于0的情况issues/I3ZL4T------
|
||||
tempDestArr.push(val)
|
||||
}
|
||||
res[destFieldsArr[i]] = tempDestArr.join(',')
|
||||
}
|
||||
if (resetText === true) {
|
||||
const tempText = []
|
||||
for (const rw of rows) {
|
||||
let val = rw[orgFieldsArr[destFieldsArr.indexOf(this.field)]]
|
||||
if (!val) {
|
||||
val = ''
|
||||
}
|
||||
tempText.push(val)
|
||||
}
|
||||
this.showText = tempText.join(',')
|
||||
}
|
||||
// update--end--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
|
||||
}
|
||||
if (this.triggerChange) {
|
||||
// v-dec时即triggerChange为true时 将整个对象给form页面 让他自己setFieldsValue
|
||||
this.$emit('callback', res)
|
||||
} else {
|
||||
// v-model时 需要传一个参数field 表示当前这个字段 从而根据这个字段的顺序找到原始值
|
||||
// this.$emit("input",row[orgFieldsArr[destFieldsArr.indexOf(this.field)]])
|
||||
let str = ''
|
||||
if (this.showText) {
|
||||
str = this.showText.split(',').join(this.spliter)
|
||||
}
|
||||
this.$emit('input', str, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.components-input-demo-presuffix .anticon-close-circle {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
transition: color 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.components-input-demo-presuffix .anticon-close-circle:hover {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.components-input-demo-presuffix .anticon-close-circle:active {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
:value="arrayValue"
|
||||
@change="onChange"
|
||||
option-filter-prop="children"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item,index) in newOptions"
|
||||
:key="index"
|
||||
:getPopupContainer="getParentContainer"
|
||||
:value="item.value">
|
||||
{{ item.text || item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// option {label:,value:}
|
||||
export default {
|
||||
name: 'JSelectMultiple',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
newOptions: [],
|
||||
arrayValue: !this.value ? [] : this.value.split(this.spliter) // arrayValue是已选中的数据
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.arrayValue = []
|
||||
} else {
|
||||
this.arrayValue = this.value.split(this.spliter)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.newOptions = this.options
|
||||
},
|
||||
methods: {
|
||||
onChange (selectedValue) {
|
||||
this.newOptions = this.options
|
||||
if (this.triggerChange) {
|
||||
this.$emit('change', selectedValue.join(this.spliter))
|
||||
} else {
|
||||
this.$emit('input', selectedValue.join(this.spliter))
|
||||
}
|
||||
},
|
||||
getParentContainer (node) {
|
||||
if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.querySelector(this.popContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="drag" ref="dragDiv">
|
||||
<div class="drag_bg"></div>
|
||||
<div class="drag_text">{{confirmWords}}</div>
|
||||
<div ref="moveDiv" @mousedown="mousedownFn($event)" :class="{'handler_ok_bg':confirmSuccess}" class="handler handler_bg" style="border: 0.5px solid #fff;height: 34px;position: absolute;top: 0;left: 0;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JSlider',
|
||||
data () {
|
||||
return {
|
||||
beginClientX: 0, /* 距离屏幕左端距离 */
|
||||
mouseMoveStata: false, /* 触发拖动状态 判断 */
|
||||
maxwidth: '', /* 拖动最大宽度,依据滑块宽度算出来的 */
|
||||
confirmWords: '拖动滑块验证', /* 滑块文字 */
|
||||
confirmSuccess: false /* 验证成功判断 */
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isSuccess () {
|
||||
return this.confirmSuccess
|
||||
},
|
||||
mousedownFn: function (e) {
|
||||
if (!this.confirmSuccess) {
|
||||
e.preventDefault && e.preventDefault() // 阻止文字选中等 浏览器默认事件
|
||||
this.mouseMoveStata = true
|
||||
this.beginClientX = e.clientX
|
||||
}
|
||||
}, // mousedoen 事件
|
||||
successFunction () {
|
||||
this.confirmSuccess = true
|
||||
this.confirmWords = '验证通过'
|
||||
if (window.addEventListener) {
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mousemove', this.mouseMoveFn)
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mouseup', this.moseUpFn)
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mouseup', () => {})
|
||||
}
|
||||
document.getElementsByClassName('drag_text')[0].style.color = '#fff'
|
||||
document.getElementsByClassName('handler')[0].style.left = this.maxwidth + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = this.maxwidth + 'px'
|
||||
|
||||
this.$emit('onSuccess', true)
|
||||
}, // 验证成功函数
|
||||
mouseMoveFn (e) {
|
||||
if (this.mouseMoveStata) {
|
||||
const width = e.clientX - this.beginClientX
|
||||
if (width > 0 && width <= this.maxwidth) {
|
||||
document.getElementsByClassName('handler')[0].style.left = width + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = width + 'px'
|
||||
} else if (width > this.maxwidth) {
|
||||
this.successFunction()
|
||||
}
|
||||
}
|
||||
}, // mousemove事件
|
||||
moseUpFn (e) {
|
||||
this.mouseMoveStata = false
|
||||
var width = e.clientX - this.beginClientX
|
||||
if (width < this.maxwidth) {
|
||||
// ---- update-begin- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
|
||||
const handler = document.getElementsByClassName('handler')[0]
|
||||
if (handler) {
|
||||
handler.style.left = 0 + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = 0 + 'px'
|
||||
}
|
||||
// ---- update-end- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
|
||||
}
|
||||
} // mouseup事件
|
||||
},
|
||||
mounted () {
|
||||
this.maxwidth = this.$refs.dragDiv.clientWidth - this.$refs.moveDiv.clientWidth
|
||||
document.getElementsByTagName('html')[0].addEventListener('mousemove', this.mouseMoveFn)
|
||||
document.getElementsByTagName('html')[0].addEventListener('mouseup', this.moseUpFn)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drag{
|
||||
position: relative;
|
||||
background-color: #e8e8e8;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
.handler{
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
.handler_bg{
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
.handler_ok_bg{
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
.drag_bg{
|
||||
background-color: #7ac23c;
|
||||
height: 34px;
|
||||
width: 0;
|
||||
}
|
||||
.drag_text{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;text-align: center;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-o-user-select:none;
|
||||
-ms-user-select:none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,678 @@
|
||||
<template>
|
||||
<div class="j-super-query-box">
|
||||
|
||||
<slot name="button" :isActive="superQueryFlag" :isMobile="izMobile" :open="handleOpen" :reset="handleReset">
|
||||
<a-tooltip v-if="superQueryFlag" v-bind="tooltipProps" :mouseLeaveDelay="0.2">
|
||||
<!-- begin 不知道为什么不加上这段代码就无法生效 -->
|
||||
<span v-show="false">{{tooltipProps}}</span>
|
||||
<!-- end 不知道为什么不加上这段代码就无法生效 -->
|
||||
<template slot="title">
|
||||
<span>{{ $t('superQuery.advancedQueryEffective') }}</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleReset">{{ $t('empty') }}</a>
|
||||
</template>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="handleOpen">
|
||||
<a-icon type="appstore" theme="twoTone" spin/>
|
||||
<span>{{ $t('superQuery.advancedQuery') }}</span>
|
||||
</a-button>
|
||||
<a-button v-if="izMobile" type="primary" icon="delete" @click="handleReset"/>
|
||||
</a-button-group>
|
||||
</a-tooltip>
|
||||
<a-button v-else type="primary" icon="filter" @click="handleOpen">{{ $t('superQuery.advancedQuery') }}</a-button>
|
||||
</slot>
|
||||
|
||||
<j-modal
|
||||
:title="$t('superQuery.advancedQuery')+$t('superQuery.constructor')"
|
||||
:width="1000"
|
||||
:visible="visible"
|
||||
@cancel="handleCancel"
|
||||
:mask="false"
|
||||
:fullscreen="izMobile"
|
||||
class="j-super-query-modal"
|
||||
style="top:5%;max-height: 95%;"
|
||||
>
|
||||
|
||||
<template slot="footer">
|
||||
<div style="float: left">
|
||||
<a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button>
|
||||
<a-button :loading="loading" @click="handleSave">{{ $t('superQuery.saveQueryCriteria') }}</a-button>
|
||||
</div>
|
||||
<a-button :loading="loading" @click="handleCancel">{{ $t('close') }}</a-button>
|
||||
<a-button :loading="loading" type="primary" @click="handleOk">{{ $t('query') }}</a-button>
|
||||
</template>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="24-5">
|
||||
|
||||
<a-empty v-if="queryParamsModel.length === 0" style="margin-bottom: 12px;">
|
||||
<div slot="description">
|
||||
<span>{{ $t('no') }}+{{ $t('query') }}+{{ $t('condition') }}</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleAdd">{{ $t('click') }}+{{ $t('newlyAdded') }}</a>
|
||||
</div>
|
||||
</a-empty>
|
||||
|
||||
<a-form v-else layout="inline">
|
||||
|
||||
<a-row style="margin-bottom: 12px;">
|
||||
<a-col :md="12" :xs="24">
|
||||
<a-form-item :label="$t('superQuery.filterMatching')" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
|
||||
<a-select v-model="matchType" :getPopupContainer="node=>node.parentNode" style="width: 100%;">
|
||||
<a-select-option value="and">AND{{$t('superQuery.allMatching')}}</a-select-option>
|
||||
<a-select-option value="or">OR{{$t('superQuery.anyOneMatches')}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row type="flex" style="margin-bottom:10px" :gutter="16" v-for="(item, index) in queryParamsModel" :key="index">
|
||||
|
||||
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
|
||||
<a-tree-select
|
||||
:showSearch="true"
|
||||
v-model="item.field"
|
||||
:treeData="fieldTreeData"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="$t('superQuery.selectQueryField')"
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
:getPopupContainer="node=>node.parentNode"
|
||||
style="width: 100%"
|
||||
@select="(val,option)=>handleSelected(option,item)"
|
||||
>
|
||||
</a-tree-select>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="4" :xs="24" style="margin-bottom: 12px;">
|
||||
<a-select :placeholder="$t('superQuery.matchRules')" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
|
||||
<a-select-option value="eq">{{ $t('superQuery.beEqualTo') }}</a-select-option>
|
||||
<a-select-option value="like">{{ $t('superQuery.contain') }}</a-select-option>
|
||||
<a-select-option value="right_like">{{ $t('superQuery.withStart') }}</a-select-option>
|
||||
<a-select-option value="left_like">{{ $t('superQuery.withEnd') }}</a-select-option>
|
||||
<a-select-option value="in">{{ $t('superQuery.in') }}</a-select-option>
|
||||
<a-select-option value="ne">{{ $t('superQuery.notEqual') }}</a-select-option>
|
||||
<a-select-option value="gt">{{ $t('superQuery.granter') }}</a-select-option>
|
||||
<a-select-option value="ge">{{ $t('superQuery.greaterOrEqual') }}</a-select-option>
|
||||
<a-select-option value="lt">{{ $t('superQuery.less') }}</a-select-option>
|
||||
<a-select-option value="le">{{ $t('superQuery.lessOrEqual') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
|
||||
<!-- 下拉搜索 -->
|
||||
<j-search-select-tag v-if="item.type==='sel_search'" v-model="item.val" :dict="getDictInfo(item)" placeholder="请选择"/>
|
||||
<!-- 下拉多选 -->
|
||||
<template v-else-if="item.type==='list_multi'">
|
||||
<j-multi-select-tag v-if="item.options" v-model="item.val" :options="item.options" :placeholder="$t('pleaseSelect')"/>
|
||||
<j-multi-select-tag v-else v-model="item.val" :dictCode="getDictInfo(item)" :placeholder="$t('pleaseSelect')"/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.dictCode">
|
||||
<template v-if="item.type === 'table-dict'">
|
||||
<j-popup
|
||||
v-model="item.val"
|
||||
:code="item.dictTable"
|
||||
:field="item.dictCode"
|
||||
:orgFields="item.dictCode"
|
||||
:destFields="item.dictCode"
|
||||
:multi="true"
|
||||
></j-popup>
|
||||
</template>
|
||||
<template v-else>
|
||||
<j-multi-select-tag v-show="allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" :placeholder="$t('pleaseSelect')"/>
|
||||
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" :placeholder="$t('pleaseSelect')"/>
|
||||
</template>
|
||||
</template>
|
||||
<j-popup
|
||||
v-else-if="item.type === 'popup'"
|
||||
:value="item.val"
|
||||
v-bind="item.popup"
|
||||
group-id="superQuery"
|
||||
@input="(e,v)=>handleChangeJPopup(item,e,v)"
|
||||
:multi="true"/>
|
||||
<j-select-multi-user
|
||||
v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
|
||||
v-model="item.val"
|
||||
:buttons="false"
|
||||
:multiple="false"
|
||||
:placeholder="$t('pleaseSelect')+$t('superQuery.user')"
|
||||
:returnKeys="['id', item.customReturnField || 'username']"
|
||||
/>
|
||||
<j-select-depart
|
||||
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
|
||||
v-model="item.val"
|
||||
:multi="false"
|
||||
:placeholder="$t('pleaseSelect')+$t('department')"
|
||||
:customReturnField="item.customReturnField || 'id'"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="item.options instanceof Array"
|
||||
v-model="item.val"
|
||||
:options="item.options"
|
||||
allowClear
|
||||
:placeholder="$t('pleaseSelect')"
|
||||
:mode="allowMultiple(item)?'multiple':''"
|
||||
/>
|
||||
<j-area-linkage v-model="item.val" v-else-if="item.type==='area-linkage' || item.type==='pca'" style="width: 100%"/>
|
||||
<j-date v-else-if=" item.type + '' === 'date' " v-model="item.val" :placeholder="$t('pleaseSelect')+$t('date')" style="width: 100%"></j-date>
|
||||
<j-date v-else-if=" item.type + '' ==='datetime' " v-model="item.val" :placeholder="$t('pleaseSelect')+$t('time')" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"></j-date>
|
||||
<a-time-picker v-else-if="item.type + '' === 'time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
|
||||
<a-input-number v-else-if=" item.type + '' === 'int'||item.type + '' === 'number' " style="width: 100%" :placeholder="$t('pleaseSelect')+$t('superQuery.numericalValue')" v-model="item.val"/>
|
||||
<a-select v-else-if="item.type + '' === 'switch'" :placeholder="$t('pleaseSelect')" v-model="item.val">
|
||||
<a-select-option value="Y">{{ $t('yes') }}</a-select-option>
|
||||
<a-select-option value="N">{{ $t('not') }}</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-else v-model="item.val" :placeholder="$t('superQuery.enterValue')"/>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="4" :xs="0" style="margin-bottom: 12px;">
|
||||
<a-button @click="handleAdd" icon="plus"></a-button>
|
||||
<a-button @click="handleDel( index )" icon="minus"></a-button>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="0" :xs="24" style="margin-bottom: 12px;text-align: right;">
|
||||
<a-button @click="handleAdd" icon="plus"></a-button>
|
||||
<a-button @click="handleDel( index )" icon="minus"></a-button>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="5">
|
||||
<!-- 查询记录 -->
|
||||
|
||||
<a-card class="j-super-query-history-card" :bordered="true">
|
||||
<div slot="title">
|
||||
{{ $t('superQuery.savedQuery') }}
|
||||
</div>
|
||||
|
||||
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" :description="$t('superQuery.noQueriesSaved')"/>
|
||||
<a-tree
|
||||
v-else
|
||||
class="j-super-query-history-tree"
|
||||
:showIcon="true"
|
||||
:treeData="saveTreeData"
|
||||
:selectedKeys="[]"
|
||||
@select="handleTreeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
</a-card>
|
||||
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</a-spin>
|
||||
|
||||
<a-modal :title="$t('pleaseEnter')+$t('superQuery.savedName')" :visible="prompt.visible" @cancel="prompt.visible=false" @ok="handlePromptOk">
|
||||
<a-input v-model="prompt.value"></a-input>
|
||||
</a-modal>
|
||||
|
||||
</j-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import * as utils from '@/utils/util'
|
||||
import { mixinDevice } from '@/utils/mixin'
|
||||
import JDate from '@/components/jero/JDate.vue'
|
||||
import JSelectDepart from '@/components/jerobiz/JSelectDepart'
|
||||
import JSelectMultiUser from '@/components/jerobiz/JSelectMultiUser'
|
||||
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
|
||||
import JAreaLinkage from '@comp/jero/JAreaLinkage'
|
||||
|
||||
export default {
|
||||
name: 'JSuperQuery',
|
||||
mixins: [mixinDevice],
|
||||
components: { JAreaLinkage, JMultiSelectTag, JDate, JSelectDepart, JSelectMultiUser },
|
||||
props: {
|
||||
/*
|
||||
fieldList: [{
|
||||
value:'',
|
||||
text:'',
|
||||
type:'',
|
||||
dictCode:'' // 只要 dictCode 有值,无论 type 是什么,都显示为字典下拉框
|
||||
}]
|
||||
type:date datetime int number string
|
||||
* */
|
||||
fieldList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
/*
|
||||
* 这个回调函数接收一个数组参数 即查询条件
|
||||
* */
|
||||
callback: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'handleSuperQuery'
|
||||
},
|
||||
|
||||
// 当前是否在加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// 保存查询条件的唯一 code,通过该 code 区分
|
||||
// 默认为 null,代表以当前路由全路径为区分Code
|
||||
saveCode: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
moment,
|
||||
fieldTreeData: [],
|
||||
|
||||
prompt: {
|
||||
visible: false,
|
||||
value: ''
|
||||
},
|
||||
|
||||
visible: false,
|
||||
queryParamsModel: [],
|
||||
treeIcon: <a-icon type="file-text"/>,
|
||||
// 保存查询条件的treeData
|
||||
saveTreeData: [],
|
||||
// 保存查询条件的前缀名
|
||||
saveCodeBefore: 'JSuperQuerySaved_',
|
||||
// 查询类型,过滤条件匹配(and、or)
|
||||
matchType: 'and',
|
||||
superQueryFlag: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
izMobile () {
|
||||
return this.device === 'mobile'
|
||||
},
|
||||
tooltipProps () {
|
||||
return this.izMobile ? { visible: false } : {}
|
||||
},
|
||||
fullSaveCode () {
|
||||
let saveCode = this.saveCode
|
||||
if (saveCode == null || saveCode === '') {
|
||||
saveCode = this.$route.fullPath
|
||||
}
|
||||
return this.saveCodeBefore + saveCode
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当 saveCode 变化时,重新查询已保存的条件
|
||||
fullSaveCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
const list = this.$ls.get(this.fullSaveCode)
|
||||
if (list instanceof Array) {
|
||||
this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
|
||||
}
|
||||
}
|
||||
},
|
||||
fieldList: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
const mainData = []; const subData = []
|
||||
val.forEach(item => {
|
||||
const data = { ...item }
|
||||
data.label = data.label || data.text
|
||||
const hasChildren = (data.children instanceof Array)
|
||||
data.disabled = hasChildren
|
||||
data.selectable = !hasChildren
|
||||
if (hasChildren) {
|
||||
data.children = data.children.map(item2 => {
|
||||
const child = { ...item2 }
|
||||
child.label = child.label || child.text
|
||||
child.label = data.label + '-' + child.label
|
||||
child.value = data.value + ',' + child.value
|
||||
child.val = ''
|
||||
return child
|
||||
})
|
||||
data.val = ''
|
||||
subData.push(data)
|
||||
} else {
|
||||
mainData.push(data)
|
||||
}
|
||||
})
|
||||
this.fieldTreeData = mainData.concat(subData)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
show () {
|
||||
if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
|
||||
this.resetLine()
|
||||
}
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
getDictInfo (item) {
|
||||
let str = ''
|
||||
if (!item.dictTable) {
|
||||
str = item.dictCode
|
||||
} else {
|
||||
str = item.dictTable + ',' + item.dictText + ',' + item.dictCode
|
||||
}
|
||||
console.log('高级查询字典信息', str)
|
||||
return str
|
||||
},
|
||||
handleOk () {
|
||||
if (!this.isNullArray(this.queryParamsModel)) {
|
||||
const event = {
|
||||
matchType: this.matchType,
|
||||
params: this.removeEmptyObject(this.queryParamsModel)
|
||||
}
|
||||
// 移动端模式下关闭弹窗
|
||||
if (this.izMobile) {
|
||||
this.visible = false
|
||||
}
|
||||
this.emitCallback(event)
|
||||
} else {
|
||||
this.$message.warn(this.$t('superQuery.cannotQueryEmpty'))
|
||||
}
|
||||
},
|
||||
emitCallback (event = {}) {
|
||||
const { params = [], matchType = this.matchType } = event
|
||||
this.superQueryFlag = (params && params.length > 0)
|
||||
for (const param of params) {
|
||||
if (Array.isArray(param.val)) {
|
||||
param.val = param.val.join(',')
|
||||
}
|
||||
}
|
||||
console.debug('---高级查询参数--->', { params, matchType })
|
||||
this.$emit(this.callback, params, matchType)
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
close () {
|
||||
this.$emit('close')
|
||||
this.visible = false
|
||||
},
|
||||
handleAdd () {
|
||||
this.addNewLine()
|
||||
},
|
||||
addNewLine () {
|
||||
this.queryParamsModel.push({ rule: 'eq' })
|
||||
},
|
||||
resetLine () {
|
||||
this.superQueryFlag = false
|
||||
this.queryParamsModel = []
|
||||
this.addNewLine()
|
||||
},
|
||||
handleDel (index) {
|
||||
this.queryParamsModel.splice(index, 1)
|
||||
},
|
||||
handleSelected (node, item) {
|
||||
const { type, dbType, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
|
||||
item.type = type
|
||||
item.dbType = dbType
|
||||
item.options = options
|
||||
item.dictCode = dictCode
|
||||
item.dictTable = dictTable
|
||||
item.dictText = dictText
|
||||
item.customReturnField = customReturnField
|
||||
if (popup) {
|
||||
item.popup = popup
|
||||
}
|
||||
this.$set(item, 'val', undefined)
|
||||
},
|
||||
handleOpen () {
|
||||
this.show()
|
||||
},
|
||||
handleReset () {
|
||||
this.resetLine()
|
||||
this.emitCallback()
|
||||
},
|
||||
handleSave () {
|
||||
const queryParams = this.removeEmptyObject(this.queryParamsModel)
|
||||
if (this.isNullArray(queryParams)) {
|
||||
this.$message.warning(this.$t('superQuery.emptyCannotSaved'))
|
||||
} else {
|
||||
this.prompt.value = ''
|
||||
this.prompt.visible = true
|
||||
}
|
||||
},
|
||||
handlePromptOk () {
|
||||
const { value } = this.prompt
|
||||
if (!value) {
|
||||
this.$message.warning(this.$t('preservation') + this.$t('name') + this.$t('cannotEmpty'))
|
||||
return
|
||||
}
|
||||
// 取出查询条件
|
||||
const records = this.removeEmptyObject(this.queryParamsModel)
|
||||
// 判断有没有重名的
|
||||
const filterList = this.saveTreeData.filter(i => i.originTitle === value)
|
||||
if (filterList.length > 0) {
|
||||
this.$confirm({
|
||||
content: `${value} ` + this.$t('superQuery.alreadyExists'),
|
||||
onOk: () => {
|
||||
this.prompt.visible = false
|
||||
filterList[0].records = records
|
||||
this.saveToLocalStore()
|
||||
this.$message.success(this.$t('savedSuccessfully'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 没有重名的,直接添加
|
||||
this.prompt.visible = false
|
||||
// 添加到树列表中
|
||||
this.saveTreeData.push(this.renderSaveTreeData({
|
||||
title: value,
|
||||
matchType: this.matchType,
|
||||
records: records
|
||||
}))
|
||||
// 保存到 LocalStore
|
||||
this.saveToLocalStore()
|
||||
this.$message.success(this.$t('SavedSuccessfully'))
|
||||
}
|
||||
},
|
||||
handleTreeSelect (idx, event) {
|
||||
if (event.selectedNodes[0]) {
|
||||
const { matchType, records } = event.selectedNodes[0].data.props
|
||||
// 将保存的matchType取出,兼容旧数据,如果没有保存就还是使用原来的
|
||||
this.matchType = matchType || this.matchType
|
||||
this.queryParamsModel = utils.cloneObject(records)
|
||||
}
|
||||
},
|
||||
handleRemoveSaveTreeItem (event, vNode) {
|
||||
// 阻止事件冒泡
|
||||
event.stopPropagation()
|
||||
|
||||
this.$confirm({
|
||||
content: this.$t('superQuery.deleteQuery'),
|
||||
onOk: () => {
|
||||
const { eventKey } = vNode
|
||||
this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
|
||||
this.saveToLocalStore()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 将查询保存到 LocalStore 里
|
||||
saveToLocalStore () {
|
||||
const saveValue = this.saveTreeData.map(({ originTitle, matchType, records }) => ({ title: originTitle, matchType, records }))
|
||||
this.$ls.set(this.fullSaveCode, saveValue)
|
||||
},
|
||||
|
||||
isNullArray (array) {
|
||||
// 判断是不是空数组对象
|
||||
if (!array || array.length === 0) {
|
||||
return true
|
||||
}
|
||||
if (array.length === 1) {
|
||||
const obj = array[0]
|
||||
if (!obj.field || (obj.val == null || obj.val === '') || !obj.rule) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 去掉数组中的空对象
|
||||
removeEmptyObject (arr) {
|
||||
const array = utils.cloneObject(arr)
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const item = array[i]
|
||||
if (item == null || Object.keys(item).length <= 0) {
|
||||
array.splice(i--, 1)
|
||||
} else {
|
||||
if (Array.isArray(item.options)) {
|
||||
// 如果有字典属性,就不需要保存 options 了
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
delete item.options
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
},
|
||||
|
||||
/** 渲染保存查询条件的 title(加个删除按钮) */
|
||||
renderSaveTreeData (item) {
|
||||
item.icon = this.treeIcon
|
||||
item.originTitle = item.title
|
||||
item.title = (arg1, arg2) => {
|
||||
let vNode
|
||||
// 兼容旧版的Antdv
|
||||
if (arg1.dataRef) {
|
||||
vNode = arg1
|
||||
} else if (arg2.dataRef) {
|
||||
vNode = arg2
|
||||
} else {
|
||||
return <span style="color:red;">Antdv版本不支持</span>
|
||||
}
|
||||
const { originTitle } = vNode.dataRef
|
||||
return (
|
||||
<div class="j-history-tree-title">
|
||||
<span>{originTitle}</span>
|
||||
|
||||
<div class="j-history-tree-title-closer" onClick={e => this.handleRemoveSaveTreeItem(e, vNode)}>
|
||||
<a-icon type="close-circle"/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return item
|
||||
},
|
||||
|
||||
/** 判断是否允许多选 */
|
||||
allowMultiple (item) {
|
||||
return item.rule === 'in'
|
||||
},
|
||||
|
||||
handleRuleChange (item, newValue) {
|
||||
const oldValue = item.rule
|
||||
this.$set(item, 'rule', newValue)
|
||||
// 上一个规则是否是 in,且type是字典或下拉
|
||||
if (oldValue === 'in') {
|
||||
if (item.dictCode || item.options instanceof Array) {
|
||||
let value = item.val
|
||||
if (typeof item.val === 'string') {
|
||||
value = item.val.split(',')[0]
|
||||
} else if (Array.isArray(item.val)) {
|
||||
value = item.val[0]
|
||||
}
|
||||
this.$set(item, 'val', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeJPopup (item, e, values) {
|
||||
item.val = values[item.popup.destFields]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.j-super-query-box {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.j-super-query-modal {
|
||||
|
||||
.j-super-query-history-card {
|
||||
/deep/ .ant-card-body,
|
||||
/deep/ .ant-card-head-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/deep/ .ant-card-head {
|
||||
padding: 4px 8px;
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.j-super-query-history-empty {
|
||||
/deep/ .ant-empty-image {
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/deep/ img {
|
||||
width: 80px;
|
||||
height: 65px;
|
||||
}
|
||||
|
||||
/deep/ .ant-empty-description {
|
||||
color: #afafaf;
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.j-super-query-history-tree {
|
||||
|
||||
.j-history-tree-title {
|
||||
width: calc(100% - 24px);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&-closer {
|
||||
color: #999999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.j-history-tree-title-closer {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/deep/ .ant-tree-switcher {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/deep/ .ant-tree-node-content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-if="query"
|
||||
style="width: 100%"
|
||||
@change="handleSelectChange"
|
||||
>
|
||||
<a-select-option v-for="(item, index) in queryOption" :key="index" :value="item.value">
|
||||
{{ item.text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-switch
|
||||
v-else
|
||||
v-model="checkStatus"
|
||||
:disabled="disabled"
|
||||
@change="handleChange"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'JSwitch',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => ['Y', 'N']
|
||||
},
|
||||
query: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checkStatus: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
queryOption () {
|
||||
const arr = []
|
||||
arr.push({ value: this.options[0], text: '是' })
|
||||
arr.push({ value: this.options[1], text: '否' })
|
||||
return arr
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (!this.query) {
|
||||
if (!val) {
|
||||
this.checkStatus = false
|
||||
this.$emit('change', this.options[1])
|
||||
} else {
|
||||
this.checkStatus = this.options[0] + '' === val + ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (checked) {
|
||||
const flag = checked === false ? this.options[1] : this.options[0]
|
||||
this.$emit('change', flag)
|
||||
},
|
||||
handleSelectChange (value) {
|
||||
this.$emit('change', value)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,674 @@
|
||||
<template>
|
||||
<a-table
|
||||
class="j-table"
|
||||
ref="table"
|
||||
:bordered="bordered"
|
||||
:columns="resultColumns"
|
||||
:components="components"
|
||||
:scroll="scroll || selfScroll"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
|
||||
<!-- 如果插槽是作用域插槽,将作用域插槽的props包装成对象传递给slot -->
|
||||
<template v-for="(_, slotName) in $scopedSlots" :slot="slotName" slot-scope="text, record, index">
|
||||
<slot :name="slotName" v-bind="{text, record, index}" />
|
||||
</template>
|
||||
<!-- 如果是普通插槽,直接使用$slots渲染 -->
|
||||
<slot v-for="(_, slotName) in $slots" :name="slotName" :slot="slotName" />
|
||||
|
||||
<template slot="text" slot-scope="text">
|
||||
<a-tooltip overlay-class-name="tooltip-style">
|
||||
<template slot="title">{{ text || text === 0 ? text : global.emptyLine }}</template>
|
||||
<div class="table-text">{{ text || text === 0 ? text : global.emptyLine }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<template slot="action" slot-scope="text, record, index">
|
||||
<slot name="action" v-bind="{text, record, index}">
|
||||
<div class="action-span-cell">
|
||||
<!-- 传了operSpecialJudgeField,需要特殊判断操作列是否显示更多 -->
|
||||
<template v-if="operSpecialJudgeField">
|
||||
<template v-if="operSpecialJudge(record).length > operateMaxNum + 1">
|
||||
<!-- 需要更多按钮 -->
|
||||
<a @click="operationClick(operSpecialJudge(record)[0],record)">{{ operSpecialJudge(record)[0].text }}</a>
|
||||
<template v-if="operSpecialJudge(record).length > operateMaxNum">
|
||||
<a-divider type="vertical" />
|
||||
<a-dropdown placement="bottomCenter">
|
||||
<a-icon type="more" class="operate-icon-btn operate-icon-more" />
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(operation, index) in operSpecialJudge(record).slice(operateMaxNum)" :key="index">
|
||||
<a @click="operationClick(operation,record)">
|
||||
<div class="operate-btn">
|
||||
{{ operation.text }}
|
||||
</div>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 不需要更多按钮 -->
|
||||
<template v-for="(operation, index) in operSpecialJudge(record)">
|
||||
<a :key="index" @click="operationClick(operation,record)">{{ operation.text }}</a>
|
||||
<a-divider type="vertical" v-if="index !== operSpecialJudge(record).length - 1" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 没有特殊判断,直接渲染operationList -->
|
||||
<!-- 需要更多按钮的情况 -->
|
||||
<template v-if="getLineOperationList(record).length > operateMaxNum + 1">
|
||||
<a @click="operationClick(getLineOperationList(record)[0],record)">{{ getLineOperationList(record)[0].text }}</a>
|
||||
<template v-if="getLineOperationList(record).length > operateMaxNum">
|
||||
<a-divider type="vertical" />
|
||||
<a-dropdown placement="bottomCenter">
|
||||
<a-icon type="more" class="operate-icon-btn operate-icon-more" />
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item v-for="(operation, index) in getLineOperationList(record).slice(operateMaxNum)" :key="index">
|
||||
<a @click="operationClick(operation,record)">
|
||||
<div class="operate-btn">
|
||||
{{ operation.text }}
|
||||
</div>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else-if="getLineOperationList(record).length > 0">
|
||||
<template v-for="(operation, index) in getLineOperationList(record)">
|
||||
<a :key="index" @click="operationClick(operation,record)">{{ operation.text }}</a>
|
||||
<a-divider type="vertical" v-if="index !== getLineOperationList(record).length - 1" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<div slot="settingDropdown">
|
||||
<a-card v-if="needSettingColumnHideOrFreeze">
|
||||
<a-table
|
||||
rowKey="key"
|
||||
:style="settingStyle"
|
||||
tableLayout="fixed"
|
||||
size="small"
|
||||
bordered
|
||||
:pagination="false"
|
||||
:scroll="settingScroll"
|
||||
:data-source="settingDataSource"
|
||||
:columns="settingColumns">
|
||||
|
||||
<a-checkbox slot="hide" slot-scope="text, record"
|
||||
:checked="text"
|
||||
:disabled="record.disabledHide"
|
||||
@change="changeSetting(text, 'hide', record)"></a-checkbox>
|
||||
<a-checkbox slot="freeze" slot-scope="text, record"
|
||||
:checked="text"
|
||||
:disabled="record.disabledFreeze"
|
||||
@change="changeSetting(text, 'freeze', record)"></a-checkbox>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
<a-icon ref="settingBtn" slot="settingIcon" type="setting" :style="{ fontSize:'16px', color: '#108ee9' }"
|
||||
v-if="needSettingColumnHideOrFreeze" />
|
||||
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@assets/less/common.less'
|
||||
import Vue from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import VueDraggableResizable from 'vue-draggable-resizable'
|
||||
import { isHasPermission } from '@/utils/hasPermission'
|
||||
|
||||
Vue.component('vue-draggable-resizable', VueDraggableResizable)
|
||||
|
||||
const actionKey = 'action'
|
||||
// 保存localStorage中的后缀
|
||||
const saveSuffix = ':JTable'
|
||||
// 拿到column的key
|
||||
const getKey = col => col.key || col.dataIndex
|
||||
// 所有JTable的缓存key
|
||||
const J_TABLE_KEYS = 'J_TABLE_KEYS'
|
||||
export default {
|
||||
name: 'JTable',
|
||||
props: {
|
||||
// 用于持久化存储列配置,保证唯一
|
||||
tableKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'j-table'
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
// 配置表的样式
|
||||
settingStyle: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ width: '300px' })
|
||||
},
|
||||
// 配置表的scroll
|
||||
settingScroll: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ y: 300 })
|
||||
},
|
||||
// 如果需要拖拽后增加总列宽而不是占用其他列宽度:需要父组件 :scroll.sync="scroll"
|
||||
scroll: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
// 列最小宽度
|
||||
columnMinWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
},
|
||||
// 是否需要设置冻结列和显隐列
|
||||
needSettingColumnHideOrFreeze: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 是否需要将冻结和显隐的设置存储在localStorage中
|
||||
needSaveSettingInLocal: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 是否可拖拽
|
||||
canDrag: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 操作列数据
|
||||
operationList: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 操作列显示更多前的最大个数
|
||||
operateMaxNum: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 1
|
||||
},
|
||||
// 操作列对于哪个字段有特殊判断
|
||||
operSpecialJudgeField: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
// 操作列需要特殊判断的字段是哪些值时,过滤不符合条件按钮
|
||||
operSpecialJudgeValue: {
|
||||
type: [Array, String, Boolean],
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
// 操作列特殊判断时需要删掉的按钮
|
||||
operSpecialJudgeDelBtn: {
|
||||
type: [String, Array],
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// settingVisible: false,
|
||||
// 设置表格
|
||||
settingColumns: [
|
||||
{ title: '列名', dataIndex: 'title', align: 'center', ellipsis: true, width: 150 },
|
||||
{
|
||||
title: '隐藏',
|
||||
dataIndex: 'hide',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'hide' }
|
||||
},
|
||||
{
|
||||
title: '冻结',
|
||||
dataIndex: 'freeze',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
width: 60,
|
||||
scopedSlots: { customRender: 'freeze' }
|
||||
}
|
||||
],
|
||||
settingDataSource: [],
|
||||
settingColumnsObj: { hide: [], freeze: [] }, // 本地存储的配置对象
|
||||
resultColumns: [],
|
||||
components: {
|
||||
header: {
|
||||
cell: this.initDrag(this.columns)
|
||||
}
|
||||
},
|
||||
selfScroll: {},
|
||||
resizing: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 配置列改变,将配置转换成配置表格数据
|
||||
settingColumnsObj: {
|
||||
deep: true, // 深度监听
|
||||
immediate: true, // 挂载先执行一次
|
||||
handler () {
|
||||
const dataSource = []
|
||||
this.columns.forEach(col => {
|
||||
const tempKey = getKey(col)
|
||||
// 操作栏不可配置
|
||||
if (tempKey === actionKey) {
|
||||
return
|
||||
}
|
||||
// hideSettingColumn: true 可以让不显示字段控制
|
||||
if (col.hideSettingColumn) {
|
||||
return
|
||||
}
|
||||
dataSource.push({
|
||||
title: col.title || col.columnTitle, // 用于显示的字段
|
||||
key: tempKey, // 用于存储的字段
|
||||
hide: this.settingColumnsObj.hide.includes(tempKey), // 是否隐藏
|
||||
freeze: this.settingColumnsObj.freeze.includes(tempKey), // 是否冻结
|
||||
disabledHide: col.disabledHide, // 禁用隐藏
|
||||
disabledFreeze: col.disabledFreeze // 禁用冻结
|
||||
})
|
||||
})
|
||||
this.settingDataSource = dataSource
|
||||
// 自定义列配置修改后,更新表格字段
|
||||
this.setResultColumns(this.columns)
|
||||
}
|
||||
},
|
||||
columns: {
|
||||
handler () {
|
||||
this.setResultColumns(this.columns)
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isHasPermission,
|
||||
// 操作列特殊判断,返回经过特殊判断后的操作列数据
|
||||
operSpecialJudge (record) {
|
||||
let operationList = []
|
||||
if (typeof this.operSpecialJudgeValue === 'boolean' || typeof this.operSpecialJudgeValue === 'string') {
|
||||
// 只需要判断某个字段是否符合某一个值
|
||||
if (record[this.operSpecialJudgeField] === this.operSpecialJudgeValue) {
|
||||
// 符合特殊条件,需要过滤操作按钮
|
||||
if (this.operSpecialJudgeDelBtn && typeof this.operSpecialJudgeDelBtn === 'string') {
|
||||
// 是字符串类型,只需要过滤掉一个操作按钮
|
||||
operationList = this.operationList.filter(item => item.text !== this.operSpecialJudgeDelBtn)
|
||||
} else if (this.operSpecialJudgeDelBtn && typeof this.operSpecialJudgeDelBtn === 'object') {
|
||||
// 是数组类型需要过滤掉多个操作按钮
|
||||
operationList = this.operationList.filter(item => !this.operSpecialJudgeDelBtn.includes(item.text))
|
||||
} else {
|
||||
operationList = this.operationList
|
||||
}
|
||||
} else {
|
||||
// 不符合条件,不需要过滤
|
||||
operationList = this.operationList
|
||||
}
|
||||
} else if (typeof this.operSpecialJudgeValue === 'object') {
|
||||
// 需要判断某个字段是否符合某些值
|
||||
if (this.operSpecialJudgeValue.includes(record[this.operSpecialJudgeField])) {
|
||||
// 符合特殊条件,需要过滤操作按钮
|
||||
if (this.operSpecialJudgeDelBtn && typeof this.operSpecialJudgeDelBtn === 'string') {
|
||||
// 是字符串类型,只需要过滤掉一个操作按钮
|
||||
operationList = this.operationList.filter(item => item.text !== this.operSpecialJudgeDelBtn)
|
||||
} else if (this.operSpecialJudgeDelBtn && typeof this.operSpecialJudgeDelBtn === 'object') {
|
||||
// 师叔祖类型需要过滤掉多个操作按钮
|
||||
operationList = this.operationList.filter(item => !this.operSpecialJudgeDelBtn.includes(item.text))
|
||||
} else {
|
||||
operationList = this.operationList
|
||||
}
|
||||
} else {
|
||||
// 不符合条件,不需要过滤
|
||||
operationList = this.operationList
|
||||
}
|
||||
}
|
||||
return operationList
|
||||
},
|
||||
// 操作栏按钮点击
|
||||
operationClick (operation, record) {
|
||||
this.$emit('operationClick', operation, record)
|
||||
},
|
||||
// 将父组件的columns进行处理:增加设置列按钮
|
||||
setResultColumns (columns) {
|
||||
// 深度克隆,防止直接修改父组件数据
|
||||
columns = cloneDeep(columns)
|
||||
// 有设置功能再进行这个插槽的配置
|
||||
if (this.needSettingColumnHideOrFreeze) {
|
||||
// 设置按钮插槽配置
|
||||
const settingOption = {
|
||||
filterDropdown: 'settingDropdown',
|
||||
filterIcon: 'settingIcon'
|
||||
}
|
||||
let findActionIndex = columns.findIndex(col => col && (getKey(col) === actionKey))
|
||||
// 将设置按钮放到表格右上角,如果有操作栏就固定到右侧(操作栏固定右侧,没有的话就最后一列)
|
||||
if (findActionIndex === -1) {
|
||||
findActionIndex = columns.length - 1
|
||||
}
|
||||
if (columns[findActionIndex].scopedSlots instanceof Object) {
|
||||
Object.assign(columns[findActionIndex].scopedSlots, settingOption)
|
||||
} else {
|
||||
columns[findActionIndex].scopedSlots = settingOption
|
||||
}
|
||||
// 自定义设置弹框(尝试解决表格刷新丢失弹框的问题)此方法会出现两个弹框
|
||||
// columns[findActionIndex].filterDropdownVisible = this.settingVisible
|
||||
// columns[findActionIndex].onFilterDropdownVisibleChange = (visible) => {
|
||||
// this.settingVisible = visible
|
||||
// }
|
||||
}
|
||||
|
||||
// 最后过滤掉需要隐藏的字段,按定位进行排序
|
||||
const startArr = [] // 冻结左侧的列
|
||||
const midArr = [] // 没有冻结的列
|
||||
const endArr = [] // 冻结右侧的列(只能是action,暂时不能自定义固定右侧)
|
||||
columns.forEach(col => {
|
||||
const key = getKey(col)
|
||||
// 如果不需要配置冻结列,就按表头本身传的参数决定固定列
|
||||
const fixed = this.needSettingColumnHideOrFreeze ? this.settingColumnsObj.freeze.includes(key) : col.fixed
|
||||
col.hide = this.settingColumnsObj.hide.includes(key)
|
||||
col.fixed = fixed
|
||||
// 如果是字符串就转换成数字(因为实现拖拽,单位只能是px)
|
||||
col.width = col.width && parseInt(col.width + '')
|
||||
// 如果没有最小宽度就获取统一设置的最小宽度
|
||||
col.minWidth = (col.minWidth && parseInt(col.minWidth + '')) || this.columnMinWidth
|
||||
// 如果没有配置align,就默认居中
|
||||
col.align = col.align || 'center'
|
||||
if (!col.scopedSlots) {
|
||||
col.scopedSlots = {
|
||||
customRender: 'text'
|
||||
}
|
||||
}
|
||||
if (!col.hide) {
|
||||
// 如果没有宽度就取minWidth(不添加原数据的width属性,这里只对需要固定的minWidth生效)
|
||||
const width = col.width || col.minWidth
|
||||
switch (col.fixed) {
|
||||
case true:
|
||||
case 'left':
|
||||
startArr.push({ ...col, width })
|
||||
break
|
||||
case 'right':
|
||||
endArr.push({ ...col, width })
|
||||
break
|
||||
default:
|
||||
midArr.push(col)
|
||||
}
|
||||
}
|
||||
})
|
||||
// 更新父组件的数据,将排序后的进行渲染
|
||||
this.$emit('update:columns', columns)
|
||||
// 按冻结顺序排序
|
||||
this.resultColumns = [
|
||||
...startArr.sort((a, b) => {
|
||||
const freezeArr = this.settingColumnsObj.freeze
|
||||
return freezeArr.indexOf(getKey(a)) - freezeArr.indexOf(getKey(b))
|
||||
}),
|
||||
...midArr,
|
||||
...endArr
|
||||
]
|
||||
return this.resultColumns
|
||||
},
|
||||
// 初始化表格拖拽
|
||||
initDrag (columns) {
|
||||
// 根据配置项决定是否可以拖拽,本身是根据是否有边框配置的,但是有边框且有固定操作列的情况下,拖动会有一个空白列
|
||||
if (!this.canDrag) {
|
||||
return
|
||||
}
|
||||
// 如果没有任何列开启拖拽就不需要拖拽
|
||||
// if (!columns.some(col => col.resizable)) {
|
||||
// return
|
||||
// }
|
||||
// 第一步:列宽映射
|
||||
const draggingMap = {}
|
||||
columns.forEach((col) => {
|
||||
draggingMap[getKey(col)] = col.width
|
||||
})
|
||||
const draggingState = Vue.observable(draggingMap)
|
||||
// 第二步:表头渲染
|
||||
return (h, props, children) => {
|
||||
// 表头DOM
|
||||
let thDom = null
|
||||
// 获取列的key值和特性
|
||||
const { key, ...restProps } = props
|
||||
// 获取最新列配置
|
||||
const columns = cloneDeep(this.columns)
|
||||
let col
|
||||
if (key === 'selection-column') {
|
||||
col = {}
|
||||
} else {
|
||||
col = columns.find(col => getKey(col) === key) || {}
|
||||
}
|
||||
// 没有开启拖拽 或 没有宽度 或 有定位,都不能拖拽(防止布局异常,至少有一列不设width并且不能有fixed)
|
||||
if (!col.width || !!col.fixed) {
|
||||
return <th {...restProps}>{children}</th>
|
||||
}
|
||||
// 开始拖拽监听
|
||||
const onDragging = (x) => {
|
||||
this.resizing = true
|
||||
const beforeWidth = col.width
|
||||
const scroll = cloneDeep(this.scroll) || {}
|
||||
draggingState[key] = 0
|
||||
col.width = Math.max(x, col.minWidth || this.columnMinWidth)
|
||||
// 如果有scroll.x就计算差值,否则获取所有列的和
|
||||
if (scroll.x) {
|
||||
scroll.x = scroll.x + (col.width - beforeWidth)
|
||||
} else {
|
||||
scroll.x = 0
|
||||
this.columns.forEach(col => (scroll.x += (col.width || col.minWidth || this.columnMinWidth)))
|
||||
}
|
||||
// 将计算好的宽度更新
|
||||
if (this.scroll) {
|
||||
this.$emit('update:scroll', scroll)
|
||||
} else {
|
||||
this.selfScroll = scroll
|
||||
}
|
||||
// 解决拖拽重置其他单元格宽度的问题
|
||||
const colIndex = this.resultColumns.findIndex(tt => getKey(tt) === key)
|
||||
const nowResultColumns = cloneDeep(this.resultColumns)
|
||||
nowResultColumns.splice(colIndex, 1, col)
|
||||
// 修改列宽后,更新表格字段
|
||||
this.setResultColumns(nowResultColumns)
|
||||
}
|
||||
// 停止拖拽监听
|
||||
const onDragstop = () => {
|
||||
draggingState[key] = thDom.getBoundingClientRect().width
|
||||
setTimeout(() => {
|
||||
this.resizing = false
|
||||
}, 200)
|
||||
}
|
||||
// 控制最小拖拽宽度
|
||||
const onDrag = (x) => {
|
||||
return x >= (col.minWidth || this.columnMinWidth)
|
||||
}
|
||||
// 解决都有dataIndex时会有横向滚动条的问题,让最后一列的拖拽容器宽度为0
|
||||
return (
|
||||
<th
|
||||
{...restProps}
|
||||
v-ant-ref={(r) => (thDom = r)}
|
||||
width={col.width}
|
||||
class="resize-table-th"
|
||||
>
|
||||
{children}
|
||||
<vue-draggable-resizable
|
||||
key={getKey(col)}
|
||||
class="table-draggable-handle"
|
||||
minw={10}
|
||||
w={10}
|
||||
x={col.width || draggingState[key]}
|
||||
z={1}
|
||||
axis="x"
|
||||
draggable={true}
|
||||
resizable={false}
|
||||
props={{ onDrag }}
|
||||
onDragging={onDragging}
|
||||
onDragstop={onDragstop}
|
||||
></vue-draggable-resizable>
|
||||
</th>
|
||||
)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 配置表中复选框改变状态的事件
|
||||
* @param text 修改前的状态
|
||||
* @param type 隐藏还是冻结
|
||||
* @param record 当前行数据
|
||||
*/
|
||||
changeSetting (text, type, record) {
|
||||
const checked = !text // text是点击前的状态,取反就是要修改的状态
|
||||
// console.log('修改了配置', checked, type, record)
|
||||
// 修改当前行配置
|
||||
record[type] = !checked
|
||||
// 勾选就添加,取消就删除
|
||||
if (checked) {
|
||||
this.settingColumnsObj[type].push(record.key)
|
||||
} else {
|
||||
this.settingColumnsObj[type].splice(this.settingColumnsObj[type].findIndex(item => item === record.key), 1)
|
||||
}
|
||||
this.saveSetting()
|
||||
/**
|
||||
* 弹框消失问题 冻结列出现和消失会导致dom结构改变而从更新结构丢失弹框
|
||||
* 解决方案,刷新前获取dom,刷新后再次获取,如果获取不一致就触发点击事件点开弹框
|
||||
*/
|
||||
const beforeIsFixed = !!this.$refs.table.$el.querySelector('.ant-table-fixed-left')
|
||||
this.$nextTick(() => {
|
||||
const afterIsFixed = !!this.$refs.table.$el.querySelector('.ant-table-fixed-left')
|
||||
// console.log(beforeIsFixed !== afterIsFixed)
|
||||
if (beforeIsFixed !== afterIsFixed) {
|
||||
this.$refs.settingBtn.$el.click()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 初始化配置,从columns里获取
|
||||
initSetting (columns) {
|
||||
const hide = []
|
||||
const freeze = []
|
||||
columns.forEach(col => {
|
||||
const key = getKey(col)
|
||||
// hide属性控制隐藏
|
||||
if (col.hide) {
|
||||
hide.push(key)
|
||||
}
|
||||
// fixed冻结到左侧
|
||||
if (col.fixed === true || col.fixed === 'left') {
|
||||
freeze.push(key)
|
||||
}
|
||||
})
|
||||
this.settingColumnsObj = { hide, freeze }
|
||||
this.saveSetting()
|
||||
},
|
||||
// 配置保存到本地
|
||||
saveSetting () {
|
||||
if (!this.needSaveSettingInLocal) {
|
||||
return
|
||||
}
|
||||
// 获取所有的jtable缓存的key数组(用于清除缓存,没有就初始化数组)
|
||||
const jTableKeys = Vue.ls.get(J_TABLE_KEYS) || []
|
||||
const saveKey = this.tableKey + saveSuffix
|
||||
// 如果不在数组里面就追加进去
|
||||
if (!jTableKeys.includes(saveKey)) {
|
||||
jTableKeys.push(saveKey)
|
||||
}
|
||||
Vue.ls.set(J_TABLE_KEYS, jTableKeys, 7 * 24 * 60 * 60 * 10)
|
||||
Vue.ls.set(saveKey, this.settingColumnsObj, 7 * 24 * 60 * 60 * 10)
|
||||
},
|
||||
// 还原默认配置
|
||||
resteColumns () {
|
||||
this.initSetting(this.columnsBak)
|
||||
},
|
||||
// 清除本地的配置(当前的JTable)
|
||||
clearSetting () {
|
||||
this.settingColumnsObj = { hide: [], freeze: [] }
|
||||
Vue.ls.remove(this.tableKey + saveSuffix)
|
||||
this.resteColumns()
|
||||
this.$message.success('成功清除当前JTable的缓存!')
|
||||
},
|
||||
// 清除所有缓存
|
||||
clearAllCacheSetting () {
|
||||
// 获取到之后遍历删除,最后把keys数组删除
|
||||
const jTableKeys = Vue.ls.get(J_TABLE_KEYS)
|
||||
jTableKeys && jTableKeys.forEach(key => {
|
||||
Vue.ls.remove(key)
|
||||
})
|
||||
Vue.ls.remove(J_TABLE_KEYS)
|
||||
this.resteColumns() // 这里只能刷新当前的表
|
||||
this.$message.success('成功清除全局JTable的缓存!')
|
||||
},
|
||||
getLineOperationList (record) {
|
||||
const hasPermissionList = this.operationList.filter(tt => !tt.has || isHasPermission(tt.has))
|
||||
const lineOperateList = []
|
||||
hasPermissionList.forEach(item => {
|
||||
if (!item.show) {
|
||||
lineOperateList.push(item)
|
||||
} else if (typeof item.show === 'function') {
|
||||
if (item.show(record)) {
|
||||
lineOperateList.push(item)
|
||||
}
|
||||
}
|
||||
})
|
||||
return lineOperateList
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 备份默认配置
|
||||
this.columnsBak = cloneDeep(this.columns)
|
||||
let settingColumnsObj
|
||||
if (this.needSaveSettingInLocal) {
|
||||
settingColumnsObj = Vue.ls.get(this.tableKey + saveSuffix)
|
||||
}
|
||||
// 第一次进页面或清空缓存进行初始化
|
||||
if (settingColumnsObj) {
|
||||
this.settingColumnsObj = settingColumnsObj
|
||||
} else {
|
||||
this.initSetting(this.columns)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-table {
|
||||
.resize-table-th {
|
||||
position: relative;
|
||||
// 让拖动的地方有个线
|
||||
border-right: 1px #e8e8e8 solid;
|
||||
|
||||
.table-draggable-handle {
|
||||
transform: none !important;
|
||||
position: absolute !important;
|
||||
height: 100% !important;
|
||||
bottom: 0;
|
||||
left: auto !important;
|
||||
right: -5px;
|
||||
//width: 10px !important;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
|
||||
// 解决每行都有dataIndex时会有横向滚动条的问题
|
||||
.resize-table-th:last-child {
|
||||
border-right: none;
|
||||
|
||||
.table-draggable-handle {
|
||||
width: 0px !important;
|
||||
min-width: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<a-time-picker
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
@change="handleTimeChange"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'JTime',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const timeStr = this.value
|
||||
return {
|
||||
decorator: '',
|
||||
momVal: !timeStr ? null : moment(timeStr, this.dateFormat)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.momVal = null
|
||||
} else {
|
||||
this.momVal = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleTimeChange (mom, timeStr) {
|
||||
this.$emit('change', timeStr)
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
@change="onChange"
|
||||
@search="onSearch"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeDict',
|
||||
data () {
|
||||
return {
|
||||
treeData: [],
|
||||
treeValue: null,
|
||||
url_root: '/sys/category/loadTreeRoot',
|
||||
url_children: '/sys/category/loadTreeChildren',
|
||||
url_view: '/sys/category/loadOne'
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
parentCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
required: false
|
||||
},
|
||||
root: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {
|
||||
pid: '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
root: {
|
||||
handler (val) {
|
||||
console.log('root-change', val)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
parentCode: {
|
||||
handler () {
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
value: {
|
||||
handler () {
|
||||
this.loadViewInfo()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.loadRoot()
|
||||
this.loadViewInfo()
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
methods: {
|
||||
loadViewInfo () {
|
||||
if (!this.value || this.value + '' === '0') {
|
||||
this.treeValue = null
|
||||
} else {
|
||||
const param = {
|
||||
field: this.field,
|
||||
val: this.value
|
||||
}
|
||||
getAction(this.url_view, param).then(res => {
|
||||
if (res.success) {
|
||||
this.treeValue = {
|
||||
value: this.value,
|
||||
label: res.result.name
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadRoot () {
|
||||
const param = {
|
||||
async: this.async,
|
||||
pcode: this.parentCode
|
||||
}
|
||||
getAction(this.url_root, param).then(res => {
|
||||
if (res.success) {
|
||||
this.handleTreeNodeValue(res.result)
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.async) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid
|
||||
}
|
||||
getAction(this.url_children, param).then(res => {
|
||||
if (res.success) {
|
||||
this.handleTreeNodeValue(res.result)
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') {
|
||||
if (!children || children.length === 0) {
|
||||
item.leaf = true
|
||||
} else {
|
||||
item.children = children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handleTreeNodeValue (result) {
|
||||
const storeField = this.field + '' === 'code' ? 'code' : 'key'
|
||||
for (const i of result) {
|
||||
i.value = i[storeField]
|
||||
i.isLeaf = !(!i.leaf)
|
||||
if (i.children && i.children.length > 0) {
|
||||
this.handleTreeNodeValue(i.children)
|
||||
}
|
||||
}
|
||||
},
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
} else {
|
||||
this.$emit('change', value.value)
|
||||
}
|
||||
this.treeValue = value
|
||||
},
|
||||
onSearch (value) {
|
||||
console.log(value)
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-tree-select
|
||||
v-if="isAsync"
|
||||
allowClear
|
||||
tree-node-filter-prop="title"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
:show-search="!multiple"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
<a-tree-select
|
||||
v-if="!isAsync"
|
||||
allowClear
|
||||
tree-node-filter-prop="title"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
:show-search="!multiple"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
/*
|
||||
* 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
|
||||
* <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
|
||||
* */
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeSelect',
|
||||
props: {
|
||||
isAsync: { // 是否采用异步方式初始化树
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
dict: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pidField: {
|
||||
type: String,
|
||||
default: 'pid',
|
||||
required: false
|
||||
},
|
||||
pidValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
hasChildField: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadTriggleChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: null, // 选中的数据
|
||||
treeData: [], // 渲染树的数组
|
||||
url: '/sys/dict/loadTreeData',
|
||||
view: '/sys/dict/loadDictItem/',
|
||||
tableName: '',
|
||||
text: '',
|
||||
code: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
dict () {
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.validateProp().then(() => {
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
window.that = this
|
||||
},
|
||||
methods: {
|
||||
loadItemByCode () {
|
||||
if (!this.value || this.value + '' === '0') {
|
||||
this.treeValue = null
|
||||
} else {
|
||||
getAction(`${this.view}${this.dict}`, { key: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
this.treeValue = this.value.split(',') // v-model接收的是string || string[]
|
||||
// 将节点名称显示在选择框上
|
||||
this.treeValue = res.result[0]
|
||||
this.onLoadTriggleChange(res.result[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoadTriggleChange (text) {
|
||||
// 只有单选才会触发
|
||||
if (!this.multiple && this.loadTriggleChange) {
|
||||
this.$emit('change', this.value, text)
|
||||
}
|
||||
},
|
||||
initDictInfo () { // 取出父组件传过来的code,text,tableName
|
||||
const arr = this.dict.split(',')
|
||||
this.tableName = arr[0]
|
||||
this.text = arr[1]
|
||||
this.code = arr[2]
|
||||
},
|
||||
// 异步加载树节点
|
||||
asyncLoadTreeData (treeNode) {
|
||||
debugger
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid,
|
||||
tableName: this.tableName,
|
||||
text: this.text,
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
hasChildField: this.hasChildField,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') { // 找到当前元素所在的父节点
|
||||
if (!children || children.length === 0) {
|
||||
item.isLeaf = true
|
||||
} else {
|
||||
item.children = children // 搜索出来的children添加到原树子children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 初始化树
|
||||
* isAsync:true===异步获取 false===同步获取数据 默认异步 */
|
||||
loadRoot () {
|
||||
if (this.isAsync) {
|
||||
const param = {
|
||||
pid: this.pidValue,
|
||||
tableName: this.tableName,
|
||||
text: this.text,
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
hasChildField: this.hasChildField,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success && res.result) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
console.log('数根节点查询结果-else', res)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 同步
|
||||
const param = {
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
tableName: this.tableName,
|
||||
text: this.text
|
||||
}
|
||||
getAction('/sys/dict/queryAllTreeData', param).then((res) => {
|
||||
if (res.success) {
|
||||
this.treeData = deepFor(res.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
this.treeValue = null
|
||||
} else if (value instanceof Array) {
|
||||
// 多选
|
||||
this.$emit('change', value.join(',').toString()) // 修改第一次选中时,选中为空的情况
|
||||
this.treeValue = value
|
||||
} else {
|
||||
// 单选
|
||||
this.$emit('change', value)
|
||||
this.treeValue = value
|
||||
}
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
},
|
||||
validateProp () {
|
||||
const myCondition = this.condition
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!myCondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
const test = JSON.parse(myCondition)
|
||||
if (typeof test === 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.warning('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.warning('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
// 递归整个树,判断是否是叶子节点----同步获取数据时用到
|
||||
function deepFor (source) {
|
||||
for (const i of source) {
|
||||
i.value = i.key
|
||||
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
if (i.children && i.children.length > 0) {
|
||||
deepFor(i.children)
|
||||
}
|
||||
}
|
||||
return source
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<a-table
|
||||
:rowKey="rowKey"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:expandedRowKeys="expandedRowKeys"
|
||||
v-bind="tableAttrs"
|
||||
v-on="$listeners"
|
||||
@expand="handleExpand"
|
||||
@expandedRowsChange="expandedRowKeys=$event">
|
||||
|
||||
<template v-for="(slotItem) of slots" :slot="slotItem" slot-scope="text, record, index">
|
||||
<slot :name="slotItem" v-bind="{text,record,index}"></slot>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeTable',
|
||||
props: {
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
},
|
||||
// 根据什么查询,如果传递 id 就根据 id 查询
|
||||
queryKey: {
|
||||
type: String,
|
||||
default: 'parentId'
|
||||
},
|
||||
queryParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 查询顶级时的值,如果顶级为0,则传0
|
||||
topValue: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
childrenUrl: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
tableProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
/** 是否在创建组件的时候就查询数据 */
|
||||
immediateRequest: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dataSource: [],
|
||||
expandedRowKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getChildrenUrl () {
|
||||
if (this.childrenUrl) {
|
||||
return this.childrenUrl
|
||||
} else {
|
||||
return this.url
|
||||
}
|
||||
},
|
||||
slots () {
|
||||
const slots = []
|
||||
for (const column of this.columns) {
|
||||
if (column.scopedSlots && column.scopedSlots.customRender) {
|
||||
slots.push(column.scopedSlots.customRender)
|
||||
}
|
||||
}
|
||||
return slots
|
||||
},
|
||||
tableAttrs () {
|
||||
return Object.assign(this.$attrs, this.tableProps)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler () {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.immediateRequest) this.loadData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 加载数据 */
|
||||
loadData (id = this.topValue, first = true, url = this.url) {
|
||||
this.$emit('requestBefore', { first })
|
||||
|
||||
if (first) {
|
||||
this.expandedRowKeys = []
|
||||
}
|
||||
|
||||
const params = Object.assign({}, this.queryParams || {})
|
||||
params[this.queryKey] = id
|
||||
if (this.condition && this.condition.length > 0) {
|
||||
params.condition = this.condition
|
||||
}
|
||||
|
||||
return getAction(url, params).then(res => {
|
||||
let list = []
|
||||
if (res.result instanceof Array) {
|
||||
list = res.result
|
||||
} else if (res.result.records instanceof Array) {
|
||||
list = res.result.records
|
||||
} else {
|
||||
throw new Error('返回数据类型不识别')
|
||||
}
|
||||
const dataSource = list.map(item => {
|
||||
// 判断是否标记了带有子级
|
||||
if (item.hasChildren === true) {
|
||||
// 查找第一个带有dataIndex的值的列
|
||||
let firstColumn
|
||||
for (const column of this.columns) {
|
||||
firstColumn = column.dataIndex
|
||||
if (firstColumn) break
|
||||
}
|
||||
// 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
|
||||
const loadChild = { id: `${item.id}_loadChild`, [firstColumn]: 'loading...', isLoading: true }
|
||||
item.children = [loadChild]
|
||||
}
|
||||
return item
|
||||
})
|
||||
if (first) {
|
||||
this.dataSource = dataSource
|
||||
}
|
||||
this.$emit('requestSuccess', { first, dataSource, res })
|
||||
return Promise.resolve(dataSource)
|
||||
}).finally(() => this.$emit('requestFinally', { first }))
|
||||
},
|
||||
|
||||
/** 点击展开图标时触发 */
|
||||
handleExpand (expanded, record) {
|
||||
// 判断是否是展开状态
|
||||
if (expanded) {
|
||||
// 判断子级的首个项的标记是否是“正在加载中”,如果是就加载数据
|
||||
if (record.children[0].isLoading === true) {
|
||||
this.loadData(record.id, false, this.getChildrenUrl).then(dataSource => {
|
||||
// 处理好的数据可直接赋值给children
|
||||
if (dataSource.length === 0) {
|
||||
record.children = null
|
||||
} else {
|
||||
record.children = dataSource
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,512 @@
|
||||
<template>
|
||||
<div :id="containerId" style="position: relative">
|
||||
<a-upload
|
||||
name="file"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{'biz':bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="doBeforeUpload"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:returnUrl="returnUrl"
|
||||
:listType="complistType"
|
||||
@preview="handlePreview"
|
||||
@download="handleDownload"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:showUploadList="showUploadList ? {showDownloadIcon: isDownload} : false"
|
||||
:class="{'uploadty-disabled':disabled}">
|
||||
<template>
|
||||
<slot name="customButton">
|
||||
<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>
|
||||
</slot>
|
||||
</template>
|
||||
</a-upload>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl, downloadFile } from '@/api/manage'
|
||||
import { getFileInfo } from '@/api/api'
|
||||
import { previewPdf } from '../../utils/previewPdf'
|
||||
import { kkFilePreview } from '../../utils/kkFilePreview'
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
// const Base64 = require('js-base64').Base64
|
||||
|
||||
const FILE_TYPE_ALL = 'all'
|
||||
const FILE_TYPE_IMG = 'image'
|
||||
// const FILE_TYPE_TXT = 'file'
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png', 'raw']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
// const uidGenerator = () => {
|
||||
// return '-' + parseInt(Math.random() * 10000 + 1 + '', 10)
|
||||
// }
|
||||
|
||||
const Base64 = require('js-base64').Base64
|
||||
|
||||
// 本系统限制可以上传的文件格式
|
||||
const CAN_UPLOAD_FILE_TYPE = 'pdf,docx,doc,xlsx,xls,ppt,pptx,rar,zip,jpg,jpeg,png,avi,wmv,mov,rm,mp4,cad'
|
||||
|
||||
// 支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']
|
||||
const uidGenerator = () => {
|
||||
return '-' + parseInt(Math.random() * 10000 + 1, 10)
|
||||
}
|
||||
export default {
|
||||
name: 'JUpload',
|
||||
data () {
|
||||
return {
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
headers: {},
|
||||
fileList: [],
|
||||
newFileList: [],
|
||||
uploadGoOn: true,
|
||||
fileTypes: this.fileType,
|
||||
image: false,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '点击上传'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: CAN_UPLOAD_FILE_TYPE
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
// 此属性被废弃了
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url,
|
||||
* true:仅返回url
|
||||
* false:返回fileName filePath fileSize
|
||||
*/
|
||||
returnUrl: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 仅返回文件id
|
||||
* true 仅返回文件id
|
||||
* false 返回reurl
|
||||
* */
|
||||
returnId: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
buttonVisible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
showUploadList: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
isDownload: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
beforeUpload: {
|
||||
type: Function
|
||||
},
|
||||
fileMaxSize: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 500
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
const val = this.value
|
||||
if (val instanceof Array) {
|
||||
if (this.returnUrl) {
|
||||
this.initFileList(val.join(','))
|
||||
} else {
|
||||
this.initFileListArr(val)
|
||||
}
|
||||
} else {
|
||||
this.initFileList(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
fileType () {
|
||||
this.fileTypes = this.fileType
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
},
|
||||
isImageComp () {
|
||||
return this.fileType === FILE_TYPE_IMG
|
||||
},
|
||||
complistType () {
|
||||
return this.fileType === FILE_TYPE_IMG ? 'picture-card' : 'text'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const token = Vue.ls.get(ACCESS_TOKEN)
|
||||
// ---------------------------- begin 图片左右换位置 -------------------------------------
|
||||
this.headers = { 'X-Access-Token': token }
|
||||
this.containerId = 'container-ty-' + new Date().getTime()
|
||||
// ---------------------------- end 图片左右换位置 -------------------------------------
|
||||
},
|
||||
methods: {
|
||||
initFileListArr (val) {
|
||||
if (!val || val.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
for (let a = 0; a < val.length; a++) {
|
||||
this.fileList.forEach((item) => {
|
||||
if (item.url === val[a]) {
|
||||
item.uid = uidGenerator()
|
||||
item.response.status = 'done'
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async initFileList (paths) {
|
||||
if (!paths || paths.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
let arr = paths.split(',')
|
||||
// 返回路径的时候,对路径进行操作,取出所属id
|
||||
if (!this.returnId && arr.length > 0) {
|
||||
const newArr = []
|
||||
arr.map(item => {
|
||||
const itemArr = item.split('/')
|
||||
newArr.push(itemArr[itemArr.length - 1])
|
||||
})
|
||||
arr = newArr
|
||||
}
|
||||
const fileList = []
|
||||
for (let a = 0; a < arr.length; a++) {
|
||||
// 获取每一个文件的信息,组成fileList
|
||||
// 突然想判断现在的fileList 中是否是历史的信息,如果是的话则先进行匹配
|
||||
const isHistory = this.fileList.find(file => file.response.status === 'history' && (file.response.result || {}).id === arr[a])
|
||||
console.log('isHistory===', isHistory)
|
||||
if (isHistory) {
|
||||
fileList.push(isHistory)
|
||||
} else {
|
||||
const fileObj = await this.getFileInfo(arr[a])
|
||||
const url = getFileAccessHttpUrl(arr[a])
|
||||
const fileName = (fileObj || {}).fileName
|
||||
const fileNameNotType = fileName.substring(0, fileName.lastIndexOf('.'))
|
||||
const previewUrl = (fileObj || {}).url || ''
|
||||
const previewName = previewUrl.substring(previewUrl.lastIndexOf(fileNameNotType))
|
||||
fileList.push({
|
||||
uid: uidGenerator(),
|
||||
name: (fileObj || {}).fileName,
|
||||
status: 'done',
|
||||
url: url,
|
||||
previewName,
|
||||
response: {
|
||||
status: 'history',
|
||||
message: arr[a],
|
||||
result: fileObj
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
this.fileList = [...fileList]
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
// 文件id
|
||||
let fileIds = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
// 如果returnid为true 只返回id
|
||||
if (this.returnId) {
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].response.result.id)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
fileIds = arr.join(',')
|
||||
}
|
||||
this.$emit('change', fileIds)
|
||||
return
|
||||
}
|
||||
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
doBeforeUpload (file) {
|
||||
this.uploadGoOn = true
|
||||
file.uploadGoOn = true
|
||||
const fileSize = file.size // 上传的文件大小
|
||||
if (fileSize === 0) {
|
||||
this.$message.warning(this.$t('uploadFile.cannotUploadEmpty'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileMaxSize && fileSize > 1024 * 1024 * this.fileMaxSize) {
|
||||
this.$message.warning(this.$t('uploadFile.pleaseUpload') + this.fileMaxSize + this.$t('uploadFile.theFollowingDocuments'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (fileSize > 1024 * 1024 * 500) {
|
||||
this.$message.warning(this.$t('uploadFile.maxSize'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_ALL) {
|
||||
return true
|
||||
}
|
||||
const fileType = file.type
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = (file.name ? file.name.split('.')[file.name.split('.').length - 1] : '').toLowerCase()
|
||||
if (this.fileType === CAN_UPLOAD_FILE_TYPE && CAN_UPLOAD_FILE_TYPE.split(',').includes(fileSuffix)) {
|
||||
return true
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && fileType.indexOf('image') < 0) {
|
||||
this.$message.warning(this.$t('uploadFile.onlyUploadPic'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
return true
|
||||
}
|
||||
if (this.fileType === FILE_TYPE_IMG && !FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.$message.warning(this.$t('uploadFile.pleaseUpload') + FILE_TYPE_IMGS.join('、') + this.$t('uploadFile.file'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
if (this.fileType.indexOf(fileSuffix) === -1) {
|
||||
this.$message.warning(this.$t('uploadFile.pleaseUpload') + `${this.fileType}` + this.$t('uploadFile.file'))
|
||||
this.uploadGoOn = false
|
||||
file.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
// 扩展 beforeUpload 验证
|
||||
if (typeof this.beforeUpload === 'function') {
|
||||
return this.beforeUpload(file)
|
||||
}
|
||||
return true
|
||||
},
|
||||
handleChange (info) {
|
||||
if (!info.file.status && this.uploadGoOn === false) {
|
||||
info.fileList.pop()
|
||||
}
|
||||
let fileList = info.fileList
|
||||
if (info.file.status === 'done') {
|
||||
if (this.number > 0) {
|
||||
fileList = fileList.slice(-this.number)
|
||||
}
|
||||
if (info.file.response.success) {
|
||||
fileList = fileList.map((file) => {
|
||||
if (file.response) {
|
||||
// let reUrl = `${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.response.result.fileName}`;
|
||||
file.url = getFileAccessHttpUrl(file.response.result.id)
|
||||
}
|
||||
return file
|
||||
})
|
||||
} else {
|
||||
info.fileList.pop()
|
||||
this.uploadGoOn = false
|
||||
this.$message.warn(info.file.response.message)
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.warning(`${info.file.name} 上传失败.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
// returnUrl为true时仅返回文件路径
|
||||
if (this.returnUrl) {
|
||||
this.handlePathChange()
|
||||
} else {
|
||||
// returnUrl为false时返回文件名称、文件路径及文件大小
|
||||
this.newFileList = []
|
||||
for (let a = 0; a < fileList.length; a++) {
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if (fileList[a].status === 'done') {
|
||||
const fileJson = {
|
||||
fileName: fileList[a].name,
|
||||
filePath: fileList[a].url,
|
||||
fileSize: fileList[a].size
|
||||
}
|
||||
this.newFileList.push(fileJson)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
}
|
||||
this.$emit('change', this.newFileList)
|
||||
}
|
||||
}
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
console.log(file)
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = (file.name ? file.name.split('.')[file.name.split('.').length - 1] : '').toLowerCase()
|
||||
const canPreview = CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (!canPreview) {
|
||||
this.$message.loading(this.$t('uploadFile.cannotPreview')).then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/view/${file.response.result.id}?at=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.name}`
|
||||
console.log(fileSuffix)
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.response.result.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (FILE_TYPE_PDF.includes(fileSuffix)) {
|
||||
const url = previewPdf(file.response.result.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
kkFilePreview(fileFullUrl)
|
||||
},
|
||||
getFileUrl (fileId) {
|
||||
return `${window._CONFIG.domianURL}/sys/common/download/${fileId}`
|
||||
},
|
||||
/**
|
||||
* 通过文件的id获取文件的相关信息
|
||||
* @param id
|
||||
*/
|
||||
getFileInfo (id) {
|
||||
return new Promise(resolve => {
|
||||
let result = {}
|
||||
getFileInfo({ id: id }).then(res => {
|
||||
if (res.success) {
|
||||
result = res.result
|
||||
}
|
||||
}).finally(() => {
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 单个文件的下载功能
|
||||
* @param file
|
||||
*/
|
||||
handleDownload (file) {
|
||||
// 下载文件
|
||||
downloadFile(`/sys/common/download/${file.response.result.id}`, file.name)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.uploadty-disabled {
|
||||
.ant-upload-list-item {
|
||||
.anticon-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.anticon-delete {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,512 @@
|
||||
# JDate 日期组件 使用文档
|
||||
|
||||
###### 说明: antd-vue日期组件需要用moment中转一下,用起来不是很方便,特二次封装,使用时只需要传字符串即可
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| readOnly | boolean | | true/false 默认false |
|
||||
| value | string | | 绑定v-model或是v-decorator后不需要设置 |
|
||||
| showTime | boolean | | 是否展示时间true/false 默认false |
|
||||
| dateFormat | string | |日期格式 默认'YYYY-MM-DD' 若showTime设置为true则需要将其设置成对应的时间格式(如:YYYY-MM-DD HH:mm:ss) |
|
||||
| triggerChange | string | |触发组件值改变的事件是否是change,当使用v-decorator时且没有设置decorator的option.trigger为input需要设置该值为true |
|
||||
使用示例
|
||||
----
|
||||
1.组件带有v-model的使用方法
|
||||
```vue
|
||||
<j-date v-model="dateStr"></j-date>
|
||||
```
|
||||
|
||||
2.组件带有v-decorator的使用方法
|
||||
a).设置trigger-change属性为true
|
||||
```vue
|
||||
<j-date :trigger-change="true" v-decorator="['dateStr',{}]"></j-date>
|
||||
```
|
||||
|
||||
b).设置decorator的option.trigger为input
|
||||
```vue
|
||||
<j-date v-decorator="['dateStr',{trigger:'input'}]"></j-date>
|
||||
```
|
||||
|
||||
3.其他使用
|
||||
添加style
|
||||
```vue
|
||||
<j-date v-model="dateStr" style="width:100%"></j-date>
|
||||
```
|
||||
添加placeholder
|
||||
```vue
|
||||
<j-date v-model="dateStr" placeholder="请输入dateStr"></j-date>
|
||||
```
|
||||
添加readOnly
|
||||
```vue
|
||||
<j-date v-model="dateStr" :read-only="true"></j-date>
|
||||
```
|
||||
|
||||
备注:
|
||||
script内需引入jdate
|
||||
```vue
|
||||
<script>
|
||||
import JDate from '@/components/jero/JDate'
|
||||
export default {
|
||||
name: "demo",
|
||||
components: {
|
||||
JDate
|
||||
}
|
||||
//...
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
# JSuperQuery 高级查询 使用文档
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|--------------|---------|----|----------------------|
|
||||
| fieldList | array |✔| 需要查询的列集合示例如下,type类型有:date/datetime/string/int/number |
|
||||
| callback | array | | 回调函数名称(非必须)默认handleSuperQuery |
|
||||
|
||||
fieldList结构示例:
|
||||
```vue
|
||||
const superQueryFieldList=[{
|
||||
type:"date",
|
||||
value:"birthday",
|
||||
text:"生日"
|
||||
},{
|
||||
type:"string",
|
||||
value:"name",
|
||||
text:"用户名"
|
||||
},{
|
||||
type:"int",
|
||||
value:"age",
|
||||
text:"年龄"
|
||||
}]
|
||||
```
|
||||
页面代码概述:
|
||||
----
|
||||
1.import之后再components之内声明
|
||||
```vue
|
||||
import JSuperQuery from '@/components/jero/JSuperQuery.vue';
|
||||
export default {
|
||||
name: "JeroDemoList",
|
||||
components: {
|
||||
JSuperQuery
|
||||
},
|
||||
|
||||
```
|
||||
2.页面引用
|
||||
```vue
|
||||
<!-- 高级查询区域 -->
|
||||
<j-super-query :fieldList="fieldList" ref="superQueryModal" @handleSuperQuery="handleSuperQuery"></j-super-query>
|
||||
```
|
||||
3.list页面data中需要定义三个属性:
|
||||
```vue
|
||||
fieldList:superQueryFieldList,
|
||||
superQueryFlag:false,
|
||||
superQueryParams:""
|
||||
```
|
||||
4.list页面声明回调事件handleSuperQuery(与组件的callback对应即可)
|
||||
```vue
|
||||
//高级查询方法
|
||||
handleSuperQuery(arg) {
|
||||
if(!arg){
|
||||
this.superQueryParams=''
|
||||
this.superQueryFlag = false
|
||||
}else{
|
||||
this.superQueryFlag = true
|
||||
this.superQueryParams=JSON.stringify(arg)
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
```
|
||||
5.改造list页面方法
|
||||
```vue
|
||||
// 获取查询条件
|
||||
getQueryParams() {
|
||||
let sqp = {}
|
||||
if(this.superQueryParams){
|
||||
sqp['superQueryParams']=encodeURI(this.superQueryParams)
|
||||
}
|
||||
var param = Object.assign(sqp, this.queryParam, this.isorter);
|
||||
param.field = this.getQueryField();
|
||||
param.pageNo = this.ipagination.current;
|
||||
param.pageSize = this.ipagination.pageSize;
|
||||
return filterObj(param);
|
||||
},
|
||||
```
|
||||
6.打开弹框调用show方法:
|
||||
```vue
|
||||
this.$refs.superQueryModal.show();
|
||||
```
|
||||
|
||||
# JEllipsis 字符串超长截取省略号显示
|
||||
|
||||
###### 说明: 遇到超长文本展示,通过此标签可以截取省略号显示,鼠标放置会提示全文本
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|--------|---------|----|----------------|
|
||||
| value |string | 必填 | 字符串文本|
|
||||
| length | number | 非必填 | 默认25 |
|
||||
使用示例
|
||||
----
|
||||
1.组件带有v-model的使用方法
|
||||
```vue
|
||||
<j-ellipsis :value="text"/>
|
||||
|
||||
|
||||
# Modal弹框实现最大化功能
|
||||
|
||||
1.定义modal的宽度:
|
||||
```vue
|
||||
<a-modal
|
||||
:width="modalWidth"
|
||||
|
||||
|
||||
/>
|
||||
```
|
||||
2.自定义modal的title,居右显示切换图标
|
||||
```vue
|
||||
<template slot="title">
|
||||
<div style="width: 100%;">
|
||||
<span>{{ title }}</span>
|
||||
<span style="display:inline-block;width:calc(100% - 51px);padding-right:10px;text-align: right">
|
||||
<a-button @click="toggleScreen" icon="appstore" style="height:20px;width:20px;border:0px"></a-button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
3.定义toggleScreen事件,用于切换modal宽度
|
||||
```vue
|
||||
toggleScreen(){
|
||||
if(this.modaltoggleFlag){
|
||||
this.modalWidth = window.innerWidth;
|
||||
}else{
|
||||
this.modalWidth = 800;
|
||||
}
|
||||
this.modaltoggleFlag = !this.modaltoggleFlag;
|
||||
},
|
||||
```
|
||||
4.data中声明上述用到的属性
|
||||
```vue
|
||||
data () {
|
||||
return {
|
||||
modalWidth:800,
|
||||
modaltoggleFlag:true,
|
||||
```
|
||||
|
||||
# <a-select/> 下拉选项滚动错位的解决方法
|
||||
|
||||
## 问题描述
|
||||
|
||||
当使用了 `a-modal` 或其他带有滚动条的组件时,使用`a-select`组件并打开下拉框时滚动滚动条,就会导致错位的问题产生。
|
||||
|
||||
## 解决方法
|
||||
|
||||
大多数情况下,在 `a-select` 上添加一个 `getPopupContainer` 属性,值为`node => node.parentNode`即可解决。
|
||||
但是如果遇到 `a-select` 标签层级过深的情况,可能仍然会显示异常,只需要多加几个`.parentNode` (例:node => node.parentNode.parentNode.parentNode)多尝试几次直到解决问题即可。
|
||||
|
||||
### 代码示例
|
||||
|
||||
```html
|
||||
<a-select
|
||||
placeholder="请选择展示模板"
|
||||
:options="dicts.displayTemplate"
|
||||
:getPopupContainer="node => node.parentNode"
|
||||
/>
|
||||
```
|
||||
|
||||
# JAsyncTreeList 异步数列表组件使用说明
|
||||
|
||||
## 引入组件
|
||||
|
||||
```js
|
||||
import JTreeTable from '@/components/jero/JTreeTable'
|
||||
export default {
|
||||
components: { JTreeTable }
|
||||
}
|
||||
```
|
||||
|
||||
## 所需参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|-------------|--------|--------|--------------------------------------------------------------|
|
||||
| rowKey | String | 非必填 | 表格行 key 的取值,默认为"id" |
|
||||
| columns | Array | 必填 | 表格列的配置描述,具体见Antd官方文档 |
|
||||
| url | String | 必填 | 数据查询url |
|
||||
| childrenUrl | String | 非必填 | 查询子级时的url,若不填则使用url参数查询子级 |
|
||||
| queryKey | String | 非必填 | 根据某个字段查询,如果传递 id 就根据 id 查询,默认为parentId |
|
||||
| queryParams | Object | 非必填 | 查询参数,当查询参数改变的时候会自动重新查询,默认为{} |
|
||||
| topValue | String | 非必填 | 查询顶级时的值,如果顶级为0,则传0,默认为null |
|
||||
| tableProps | Object | 非必填 | 自定义给内部table绑定的props |
|
||||
|
||||
## 代码示例
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-card :bordered="false">
|
||||
<j-tree-table :url="url" :columns="columns" :tableProps="tableProps"/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JTreeTable from '@/components/jero/JTreeTable'
|
||||
|
||||
export default {
|
||||
name: 'AsyncTreeTable',
|
||||
components: { JTreeTable },
|
||||
data() {
|
||||
return {
|
||||
url: '/mock/api/asynTreeList',
|
||||
columns: [
|
||||
{ title: '菜单名称', dataIndex: 'name' },
|
||||
{ title: '组件', dataIndex: 'component' },
|
||||
{ title: '排序', dataIndex: 'orderNum' }
|
||||
],
|
||||
selectedRowKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableProps() {
|
||||
let _this = this
|
||||
return {
|
||||
// 列表项是否可选择
|
||||
// 配置项见:https://vue.ant.design/components/table-cn/#rowSelection
|
||||
rowSelection: {
|
||||
selectedRowKeys: _this.selectedRowKeys,
|
||||
onChange: (selectedRowKeys) => _this.selectedRowKeys = selectedRowKeys
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# JCheckbox 使用文档
|
||||
|
||||
###### 说明: antd-vue checkbox组件处理的是数组,用起来不是很方便,特二次封装,使用时只需处理字符串即可
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| options |array |✔| checkbox需要配置的项,是个数组,数组中每个对象包含两个属性:label(用于显示)和value(用于存储) |
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form :form="form">
|
||||
<a-form-item label="v-model式用法">
|
||||
<j-checkbox v-model="sport" :options="sportOptions"></j-checkbox><span>{{ sport }}</span>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="v-decorator式用法">
|
||||
<j-checkbox v-decorator="['sport']" :options="sportOptions"></j-checkbox><span>{{ getFormFieldValue('sport') }}</span>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JCheckbox from '@/components/jero/JCheckbox'
|
||||
export default {
|
||||
components: {JCheckbox},
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
sport:'',
|
||||
sportOptions:[
|
||||
{
|
||||
label:"足球",
|
||||
value:"1"
|
||||
},{
|
||||
label:"篮球",
|
||||
value:"2"
|
||||
},{
|
||||
label:"乒乓球",
|
||||
value:"3"
|
||||
}]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFormFieldValue(field){
|
||||
return this.form.getFieldValue(field)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# JCodeEditor 使用文档
|
||||
|
||||
###### 说明: 一个简易版的代码编辑器,支持语法高亮
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| language |string | | 表示当前编写代码的类型 javascript/html/css/sql |
|
||||
| placeholder |string | | placeholder |
|
||||
| lineNumbers |Boolean | | 是否显示行号 |
|
||||
| fullScreen |Boolean | | 是否显示全屏按钮 |
|
||||
| zIndex |string | | 全屏以后的z-index |
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<j-code-editor
|
||||
language="javascript"
|
||||
v-model="editorValue"
|
||||
:fullScreen="true"
|
||||
style="min-height: 100px"/>
|
||||
{{ editorValue }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JCodeEditor from '@/components/jero/JCodeEditor'
|
||||
export default {
|
||||
components: {JCodeEditor},
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
editorValue:'',
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# JFormContainer 使用文档
|
||||
|
||||
###### 说明: 暂用于表单禁用
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<!-- 在form下直接写这个组件,设置disabled为true就能将此form中的控件禁用 -->
|
||||
<a-form layout="inline" :form="form" >
|
||||
<j-form-container disabled>
|
||||
<!-- 表单内容省略..... -->
|
||||
</j-form-container>
|
||||
</a-form>
|
||||
```
|
||||
|
||||
# JImportModal 使用文档
|
||||
|
||||
###### 说明: 用于列表页面导入excel功能
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
|
||||
<template>
|
||||
<!-- 此处省略部分代码...... -->
|
||||
<a-button @click="handleImportXls" type="primary" icon="upload">导入</a-button>
|
||||
<!-- 此处省略部分代码...... -->
|
||||
<j-import-modal ref="importModal" :url="getImportUrl()" @ok="importOk"></j-import-modal>
|
||||
<!-- 此处省略部分代码...... -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JCodeEditor from '@/components/jero/JCodeEditor'
|
||||
export default {
|
||||
components: {JCodeEditor},
|
||||
data() {
|
||||
return {
|
||||
//省略代码......
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
//省略部分代码......
|
||||
handleImportXls(){
|
||||
this.$refs.importModal.show()
|
||||
},
|
||||
getImportUrl(){
|
||||
return '你自己处理上传业务的后台地址'
|
||||
},
|
||||
importOk(){
|
||||
this.loadData(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
# JSelectMultiple 多选下拉组件
|
||||
online用 实际开发请使用components/dict/JMultiSelectTag
|
||||
|
||||
# JSlider 滑块验证码
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<div style="width: 300px">
|
||||
<j-slider @onSuccess="sliderSuccess"></j-slider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JSlider from '@/components/jero/JSlider'
|
||||
export default {
|
||||
components: {JSlider},
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
editorValue:'',
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
sliderSuccess(){
|
||||
console.log("验证完成")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
# JTreeSelect 树形下拉组件
|
||||
异步加载的树形下拉组件
|
||||
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| dict |string | ✔| 表名,显示字段名,存储字段名拼接的字符串 |
|
||||
| pidField |string | ✔| 父ID的字段名 |
|
||||
| pidValue |string | | 根节点父ID的值 默认'0' 不可以设置为空,如果想使用此组件,而数据库根节点父ID为空,请修改之 |
|
||||
| multiple |boolean | |是否支持多选 |
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form>
|
||||
<a-form-item label="树形下拉测试" style="width: 300px">
|
||||
<j-tree-select
|
||||
v-model="departId"
|
||||
placeholder="请选择部门"
|
||||
dict="sys_depart,depart_name,id"
|
||||
pidField="parent_id">
|
||||
</j-tree-select>
|
||||
{{ departId }}
|
||||
</a-form-item>
|
||||
</a-form >
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JTreeSelect from '@/components/jero/JTreeSelect'
|
||||
export default {
|
||||
components: {JTreeSelect},
|
||||
data() {
|
||||
return {
|
||||
departId:""
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
# JEditableTable 帮助文档
|
||||
|
||||
## 参数配置
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|--------------|---------|------|---------------------------------------------------------------------------------|
|
||||
| columns | array | ✔️ | 表格列的配置描述,具体项见下表 |
|
||||
| dataSource | array | ✔️ | 表格数据 |
|
||||
| loading | boolean | | 是否正在加载,加载中不会显示任何行,默认false |
|
||||
| actionButton | boolean | | 是否显示操作按钮,包括"新增"、"删除",默认false |
|
||||
| rowNumber | boolean | | 是否显示行号,默认false |
|
||||
| rowSelection | boolean | | 是否可选择行,默认false |
|
||||
| dragSort | boolean | | 是否可拖动排序,默认false |
|
||||
| dragSortKey | string | | 拖动排序存储的Key,无需定义在columns内也能在getValues()时获取到值,默认orderNum |
|
||||
| maxHeight | number | | 设定最大高度(px),默认400 |
|
||||
| disabledRows | object | | 设定禁用的行,被禁用的行无法被选择和编辑,配置方法可以查看示例 |
|
||||
| disabled | boolean | | 是否禁用所有行,默认false |
|
||||
|
||||
### columns 参数详解
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---------------|---------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| title | string | ✔️ | 表格列头显示的问题 |
|
||||
| key | string | ✔️ | 列数据在数据项中对应的 key,必须是唯一的 |
|
||||
| type | string | ✔️ | 表单的类型,可以通过`JEditableTableUtil.FormTypes`赋值 |
|
||||
| width | string | | 列的宽度,可以是百分比,也可以是`px`或其他单位,建议设置为百分比,且每一列的宽度加起来不应超过100%,否则可能会不能达到预期的效果。留空会自动计算百分比 |
|
||||
| placeholder | string | | 表单预期值的提示信息,可以使用`${...}`变量替换文本(详见`${...} 变量使用方式`) |
|
||||
| defaultValue | string | | 默认值,在新增一行时生效 |
|
||||
| validateRules | array | | 表单验证规则,配置方式见[validateRules 配置规则](#validaterules-配置规则) |
|
||||
| props | object | | 设置添加给表单元素的自定义属性,例如:`props:{title: 'show title'}` |
|
||||
| disabled | boolean | | 是否禁用当前列,默认false |
|
||||
|
||||
#### 当 type=checkbox 时所需的参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|----------------|---------|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| defaultChecked | boolean | | 默认值是否选中 |
|
||||
| customValue | array | | 自定义值,checkbox需要的是boolean值,如果数据是其他值(例如`'Y' or 'N'`)时,就会导致错误,所以提供了该属性进行转换,例:`customValue: ['Y','N']`,会将`true`转换为`'Y'`,`false`转换为`'N'`,反之亦然 |
|
||||
|
||||
#### 当 type=select 时所需的参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------------|---------|------|----------------------------------------------------|
|
||||
| options | array | ✔️ | 下拉选项列表,详见下表 |
|
||||
| allowInput | boolean | | 是否允许用户输入内容,并创建新的内容 |
|
||||
| dictCode | String | | 数据字典Code,若options也有值,则拼接在options后面 |
|
||||
|
||||
##### options 所需参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|-----------|------------|------|----------------------------------------------------------------------|
|
||||
| text | string | ✔️ | 显示标题 |
|
||||
| value | string | ✔️ | 真实值 |
|
||||
| ~~title~~ | ~~string~~ | | ~~显示标题(已废弃,若同时填写了 title 和 text 那么优先使用 text)~~ |
|
||||
|
||||
#### 当 type=upload 时所需的参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|--------------|---------|------|--------------------------------------------------------------------------------------|
|
||||
| action | string | ✔️ | 上传文件路径 |
|
||||
| token | boolean | | 上传的时候是否传递token |
|
||||
| responseName | string | ✔️ | 若要从上传成功后从response中取出返回的文件名,那么这里填后台返回的包含文件名的字段名 |
|
||||
|
||||
#### 当 type=slot 时所需的参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|----------|--------|------|------------|
|
||||
| slotName | string | ✔️ | slot的名称 |
|
||||
|
||||
### validateRules 配置规则
|
||||
|
||||
`validateRules` 需要的是一个数组,数组里每项都是一个规则,规则是object类型,规则的各个参数如下
|
||||
|
||||
- `required` 是否必填,可选值为`true`or`false`
|
||||
- `pattern` 正则表达式验证,只有成功匹配该正则的值才能成功通过验证
|
||||
- `handler` 自定义函数校验,使用方法请见[示例五](#示例五)
|
||||
- `message` 当验证未通过时显示的提示文本,可以使用`${...}`变量替换文本(详见`${...} 变量使用方式`)
|
||||
- 配置示例请看[示例二](#示例二)
|
||||
|
||||
## 事件
|
||||
|
||||
| 事件名 | 触发时机 | 参数 |
|
||||
|-----------------|----------------------------------------------------|--------------------------------------------------|
|
||||
| added | 当添加行操作完成后触发 | |
|
||||
| deleted | 当删除行操作完成后触发(批量删除操作只会触发一次) | `deleteIds` 被逻辑删除的id |
|
||||
| selectRowChange | 当行被选中或取消选中时触发 | `selectedRowIds` 被选中行的id |
|
||||
| valueChange | 当数据发生改变的时候触发的事件 | `{ type, row, column, value, target }` Event对象 |
|
||||
|
||||
## 方法
|
||||
|
||||
关于方法的如何调用的问题,请在**FAQ**中查看[方法如何调用](#方法如何调用)
|
||||
|
||||
### initialize
|
||||
|
||||
用于初始化表格(清空表格)
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` 无
|
||||
|
||||
### resetScrollTop
|
||||
|
||||
重置滚动条Top位置
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|--------|------|--------------------------------------------------------------------------------------------------------|
|
||||
| top | number | | 新top位置,留空则滚动到上次记录的位置,用于解决切换tab选项卡时导致白屏以及自动将滚动条滚动到顶部的问题 |
|
||||
|
||||
- `返回值:` 无
|
||||
|
||||
### add
|
||||
|
||||
主动添加行,默认情况下,当用户的滚动条已经在底部的时候,会将滚动条固定在底部,即添加后无需用户手动滚动,而会自动滚动到底部
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|---------------------|---------|------|---------------------------------------------------------------------|
|
||||
| num | number | | 添加几行,默认为1 |
|
||||
| forceScrollToBottom | boolean | | 是否在添加后无论用户的滚动条在什么位置都强制滚动到底部,默认为false |
|
||||
|
||||
- `返回值:` 无
|
||||
|
||||
### removeRows
|
||||
|
||||
主动删除一行或多行
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|-----------------|------|--------------------------------------------------------------------------------------------|
|
||||
| id | string 或 array | ✔️ | 被删除行的id。如果要删除一个,可以直接传id,如果要删除多个,需要将多个id封装成一个数组传入 |
|
||||
|
||||
- `返回值:` 无
|
||||
|
||||
### removeSelectedRows
|
||||
|
||||
主动删除被选中的行
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` 无
|
||||
|
||||
### getValues
|
||||
|
||||
用于获取表格里所有表单的值,可进行表单验证
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|----------|----------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| callback | function | ✔️ | 获取值的回调方法,会传入`error`和`values`两个参数。`error`:未通过验证的数量,当等于`0`时代表验证通过;`values`:获取的值(即使未通过验证该字段也有数据) |
|
||||
| validate | boolean | | 是否进行表单验证,默认为`true`,设为`false`则代表忽略表单验证 |
|
||||
| rowIds | array | | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组 |
|
||||
|
||||
- `返回值:` 无
|
||||
|
||||
|
||||
### getValuesSync
|
||||
|
||||
`getValues`的同步版,会直接将获取到的数据返回
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|---------|--------|------|------------------------|
|
||||
| options | object | | 选项,详见下方所需参数 |
|
||||
|
||||
- - `options` 所需参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|----------|---------|------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| validate | boolean | | 是否进行表单验证,默认为`true`,设为`false`则代表忽略表单验证 |
|
||||
| rowIds | array | | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组 |
|
||||
|
||||
- `返回值:` object
|
||||
- `error` 未通过验证的数量,当等于`0`时代表验证通过
|
||||
- `values` 获取的值(即使未通过验证该字段也有数据)
|
||||
|
||||
- `使用示例`
|
||||
|
||||
```js
|
||||
let { error, values } = this.$refs.editableTable.getValuesSync({ validate: true, rowIds: ['rowId1', 'rowId2'] })
|
||||
if (error === 0) {
|
||||
console.log('表单验证通过,数据:', values);
|
||||
} else {
|
||||
console.log('未通过表单验证,数据:', values);
|
||||
}
|
||||
```
|
||||
|
||||
### getValuesPromise
|
||||
|
||||
`getValues`的promise版,会在`resolve`中传入获取到的值,会在`reject`中传入失败原因,例如`VALIDATE_NO_PASSED`
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|----------|---------|------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| validate | boolean | | 同`getValues`的`validate`参数 |
|
||||
| rowIds | array | | 默认返回所有行的数据,如果传入了`rowIds`,那么就会只返回与该`rowIds`相匹配的数据,如果没有匹配的数据,就会返回空数组 |
|
||||
|
||||
- `返回值:` Promise
|
||||
|
||||
### getDeleteIds
|
||||
|
||||
用于获取被逻辑删除的行的id,返回一个数组,用户可将该数组传入后台,并进行批量删除
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` array
|
||||
|
||||
### getAll
|
||||
|
||||
获取所有的数据,包括values、deleteIds
|
||||
会在`resolve`中传入获取到的值:`{values, deleteIds}`
|
||||
会在`reject`中传入失败原因,例如`VALIDATE_NO_PASSED`
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|----------|---------|------|-------------------------------|
|
||||
| validate | boolean | | 同`getValues`的`validate`参数 |
|
||||
|
||||
- `返回值:` Promise
|
||||
|
||||
### setValues
|
||||
|
||||
主动设置表格中某行某列的值
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|-------|------|------------------------------------------------------------|
|
||||
| values | array | | 传入一个数组,数组中的每项都是一行的新值,具体见下面的示例 |
|
||||
|
||||
- `返回值:` 无
|
||||
- `示例:`
|
||||
|
||||
```js
|
||||
setValues([
|
||||
{
|
||||
rowKey: id1, // 行的id
|
||||
values: { // 在这里 values 中的 name 是你 columns 中配置的 key
|
||||
'name': 'zhangsan',
|
||||
'age': '20'
|
||||
}
|
||||
},
|
||||
{
|
||||
rowKey: id2,
|
||||
values: {
|
||||
'name': 'lisi',
|
||||
'age': '23'
|
||||
}
|
||||
}
|
||||
])
|
||||
```
|
||||
### clearSelection
|
||||
|
||||
主动清空选择的行
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` 无
|
||||
|
||||
## 内置插槽
|
||||
|
||||
| 插槽名 | 说明 |
|
||||
|--------------|------------------------------------------------------|
|
||||
| buttonBefore | 在操作按钮的**前面**插入插槽,不受`actionButton`属性的影响 |
|
||||
| buttonAfter | 在操作按钮的**后面**插入插槽,不受`actionButton`属性的影响 |
|
||||
|
||||
## ${...} 变量使用方式
|
||||
|
||||
在`placeholder`和`message`这两个属性中可以使用`${...}`变量来替换文本
|
||||
在[示例二](#示例二)中,配置了`title`为`名称`的一列,而`placeholder`配置成了`请输入${title}`,那么最终显示效果为`请输入名称`
|
||||
这就是`${...}`变量的使用方式,在`${}`中可以使用的变量有`title`、`key`、`defaultValue`这三个属性的值
|
||||
|
||||
## JEditableTableUtil 使用说明
|
||||
|
||||
在之前配置`columns`时提到过`JEditableTableUtil`这个工具类,那么如果想要知道详细的使用说明就请看这里
|
||||
|
||||
### export 的常量
|
||||
|
||||
#### FormTypes
|
||||
|
||||
这是配置`columns.type`时用到的常量值,其中包括
|
||||
|
||||
- `normal` 默认,直接显示值,不渲染表单
|
||||
- `input` 显示输入框
|
||||
- `inputNumber` 显示数字输入框
|
||||
- `checkbox` 显示多选框
|
||||
- `select` 显示选择器(下拉框)
|
||||
- `date` 日期选择器
|
||||
- `datetime` 日期时间选择器
|
||||
- `upload` 上传组件(文件域)
|
||||
- `slot` 自定义插槽
|
||||
|
||||
### VALIDATE_NO_PASSED
|
||||
|
||||
在判断表单验证是否通过时使用,如果 reject 的值 === VALIDATE_NO_PASSED 则代表表单验证未通过,你可以做相应的其他处理,反之则可能是发生了报错,可以使用 `console.error` 输出
|
||||
|
||||
### 封装的方法
|
||||
|
||||
#### validateTables
|
||||
|
||||
当你的页面中存在多个JEditableTable实例的时候,如果要获取每个实例的值、判断表单验证是否通过,就会让代码变得极其冗余、繁琐,于是我们就将该操作封装成了一个函数供你调用,它可以同时获取并验证多个JEditableTable实例的值,只有当所有实例的表单验证都通过后才会返回值,否则将会告诉你具体哪个实例没有通过验证。具体使用方法请看下面的示例
|
||||
|
||||
- `参数:`
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|-------|------|--------------------------------------------------------|
|
||||
| cases | array | | 传入一个数组,数组中的每项都是一个JEditableTable的实例 |
|
||||
|
||||
- `返回值:` Promise
|
||||
- `示例:`
|
||||
|
||||
```js
|
||||
import { validateTables, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
|
||||
// 封装cases
|
||||
let cases = []
|
||||
cases.push(this.$refs.editableTable1)
|
||||
cases.push(this.$refs.editableTable2)
|
||||
cases.push(this.$refs.editableTable3)
|
||||
cases.push(this.$refs.editableTable4)
|
||||
cases.push(this.$refs.editableTable5)
|
||||
// 同时验证并获取多个实例的值
|
||||
validateTables(cases).then((all) => {
|
||||
// all 是一个数组,每项都对应传入cases的下标,包含values和deleteIds
|
||||
console.log('所有实例的值:', all)
|
||||
}).catch((e = {}) => {
|
||||
// 判断表单验证是否未通过
|
||||
if (e.error === VALIDATE_NO_PASSED) {
|
||||
console.log('未通过验证的实例下标:', e.index)
|
||||
} else {
|
||||
console.error('发生异常:', e)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
### 方法如何调用?
|
||||
|
||||
在[示例一](#示例一)中,设定了一个 `ref="editableTable"` 的属性,那么在vue中就可以使用`this.$refs.editableTable`获取到该表格的实例,并调取其中的方法。
|
||||
假如我要调取`initialize`方法,就可以这么写:`this.$refs.editableTable.initialize()`
|
||||
|
||||
### 如何获取表单的值?
|
||||
|
||||
使用`getValue`方法进行获取,详见[示例三](#示例三)
|
||||
|
||||
### 如何进行表单验证?
|
||||
|
||||
在获取值的时候默认会进行表单验证操作,用户在输入的时候也会对正在输入的表单进行验证,只要配置好规则就可以了
|
||||
|
||||
### 如何添加或删除一行?
|
||||
|
||||
该功能已封装到组件中,你只需要将 `actionButton` 设置为 `true` 即可,当然你也可以在代码中主动调用新增方法或修改,具体见上方的方法介绍。
|
||||
|
||||
### 为什么使用了ATab组件后,切换选项卡会导致白屏或滚动条位置会归零?
|
||||
|
||||
在ATab组件中确实会导致滚动条位置归零,且不会触发`onscroll`方法,所以无法动态加载行,导致白屏的问题出现。
|
||||
解决方法是在ATab组件的`onChange`事件触发时执行实例提供的`resetScrollTop()`方法即可,但是需要注意的是:代码主动改变ATab的`activeKey`不会触发`onChange`事件,还需要你手动调用下。
|
||||
|
||||
- `示例`
|
||||
|
||||
```html
|
||||
<template>
|
||||
<a-tabs @change="handleChangeTab">
|
||||
<a-tab-pane tab="表格1" :forceRender="true" key="1">
|
||||
<j-editable-table
|
||||
ref="editableTable1"
|
||||
:loading="tab1.loading"
|
||||
:columns="tab1.columns"
|
||||
:dataSource="tab1.dataSource"/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="表格2" :forceRender="true" key="2">
|
||||
<j-editable-table
|
||||
ref="editableTable2"
|
||||
:loading="tab2.loading"
|
||||
:columns="tab2.columns"
|
||||
:dataSource="tab2.dataSource"/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</template>
|
||||
```
|
||||
|
||||
```js
|
||||
/*--- 忽略部分代码片段 ---*/
|
||||
methods: {
|
||||
|
||||
/** 切换tab选项卡的时候重置editableTable的滚动条状态 */
|
||||
handleChangeTab(key) {
|
||||
this.$refs[`editableTable${key}`].resetScrollTop()
|
||||
}
|
||||
|
||||
}
|
||||
/*--- 忽略部分代码片段 ---*/
|
||||
```
|
||||
|
||||
### slot(自定义插槽)如何使用?
|
||||
|
||||
代码示例请看:[示例四(slot)](#示例四(slot))
|
||||
|
||||
----------------------------------------------------------------------------------------
|
||||
|
||||
## 示例一
|
||||
|
||||
```html
|
||||
<j-editable-table
|
||||
ref="editableTable"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:rowNumber="true"
|
||||
:rowSelection="true"
|
||||
:actionButton="true"
|
||||
style="margin-top: 8px;"
|
||||
@selectRowChange="handleSelectRowChange"/>
|
||||
```
|
||||
|
||||
## 示例二
|
||||
|
||||
```js
|
||||
|
||||
import { FormTypes } from '@/utils/JEditableTableUtil'
|
||||
|
||||
/*--- 忽略部分代码片断 ---*/
|
||||
columns: [
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
type: FormTypes.input,
|
||||
placeholder: '请输入${title}',
|
||||
defaultValue: '称名',
|
||||
// 表单验证规则
|
||||
validateRules: [
|
||||
{
|
||||
required: true, // 必填
|
||||
message: '${title}不能为空' // 提示的文本
|
||||
},
|
||||
{
|
||||
pattern: /^[a-z|A-Z][a-z|A-Z\d_-]{0,}$/, // 正则
|
||||
message: '${title}必须以字母开头,可包含数字、下划线、横杠'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '年龄',
|
||||
key: 'age',
|
||||
type: FormTypes.inputNumber,
|
||||
placeholder: '请输入${title}',
|
||||
defaultValue: 18,
|
||||
validateRules: [{required: true, message: '${title}不能为空'}]
|
||||
}
|
||||
]
|
||||
/*--- 忽略部分代码片断 ---*/
|
||||
```
|
||||
|
||||
## 示例三
|
||||
|
||||
```js
|
||||
// 获取被逻辑删除的字段id
|
||||
let deleteIds = this.$refs.editableTable.getDeleteIds();
|
||||
// 获取所有表单的值,并进行验证
|
||||
this.$refs.editableTable.getValues((error, values) => {
|
||||
// 错误数 = 0 则代表验证通过
|
||||
if (error === 0) {
|
||||
this.$message.success('验证通过')
|
||||
// 将通过后的数组提交到后台或自行进行其他处理
|
||||
console.log(deleteIds, values)
|
||||
} else {
|
||||
this.$message.warning('验证未通过')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 示例四(slot)
|
||||
|
||||
```html
|
||||
<template>
|
||||
<j-editable-table :columns="columns" :dataSource="dataSource">
|
||||
<!-- 定义插槽 -->
|
||||
<!-- 这种定义插槽的写法是vue推荐的新版写法(https://cn.vuejs.org/v2/guide/components-slots.html#具名插槽),旧版已被废弃的写法不再支持 -->
|
||||
<!-- 若webstorm这样写报错,请看这篇文章:https://blog.csdn.net/lxq_9532/article/details/81870651 -->
|
||||
<template v-slot:action="props">
|
||||
<a @click="handleDelete(props)">删除</a>
|
||||
</template>
|
||||
</j-editable-table>
|
||||
</template>
|
||||
<script>
|
||||
import { FormTypes } from '@/utils/JEditableTableUtil'
|
||||
import JEditableTable from '@/components/jero/JEditableTable'
|
||||
export default {
|
||||
components: { JEditableTable },
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
// ...
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: '8%',
|
||||
type: FormTypes.slot, // 定义该列为 自定义插值列
|
||||
slotName: 'action' // slot 的名称,对应 v-slot 冒号后面和等号前面的内容
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/* a 标签的点击事件,删除当前选中的行 */
|
||||
handleDelete(props) {
|
||||
// 参数解释
|
||||
// props.index :当前行的下标
|
||||
// props.text :当前值,可能是defaultValue定义的值,也可能是从dataSource中取出的值
|
||||
// props.rowId :当前选中行的id,如果是新增行则是临时id
|
||||
// props.column :当前操作的列
|
||||
// props.getValue :这是一个function,执行后可以获取当前行的所有值(禁止在template中使用)
|
||||
// 例:const value = props.getValue()
|
||||
// props.target :触发当前事件的实例,可直接调用该实例内的方法(禁止在template中使用)
|
||||
// 例:target.add()
|
||||
|
||||
// 使用实例:删除当前操作的行
|
||||
let { rowId, target } = props
|
||||
target.removeRows(rowId)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 示例五
|
||||
|
||||
```js
|
||||
// 该示例是自定义函数校验
|
||||
columns: [
|
||||
{
|
||||
title: '字段名称',
|
||||
key: 'dbFieldName',
|
||||
type: FormTypes.input,
|
||||
defaultValue: '',
|
||||
validateRules: [
|
||||
{
|
||||
// 自定义函数校验 handler
|
||||
handler(type, value, row, column, callback, target) {
|
||||
// type 触发校验的类型(input、change、blur)
|
||||
// value 当前校验的值
|
||||
// callback(flag, message) 方法必须执行且只能执行一次
|
||||
// flag = 是否通过了校验,不填写或者填写 null 代表不进行任何操作
|
||||
// message = 提示的类型,默认使用配置的 message
|
||||
// target 行编辑的实例对象
|
||||
|
||||
if (type === 'blur') {
|
||||
|
||||
if (value === 'abc') {
|
||||
callback(false, '${title}不能是abc') // false = 未通过,可以跟自定义提示
|
||||
return
|
||||
}
|
||||
|
||||
let { values } = target.getValuesSync({ validate: false })
|
||||
let count = 0
|
||||
for (let val of values) {
|
||||
if (val['dbFieldName'] === value) {
|
||||
if (++count >= 2) {
|
||||
callback(false, '${title}不能重复')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
callback(true) // true = 通过验证
|
||||
} else {
|
||||
callback() // 不填写或者填写 null 代表不进行任何操作
|
||||
}
|
||||
},
|
||||
message: '${title}默认提示'
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
# JPopup 弹窗选择组件
|
||||
|
||||
## 参数配置
|
||||
| 参数 | 类型 | 必填 |说明|
|
||||
|--------------|---------|----|---------|
|
||||
| placeholder |string | | placeholder |
|
||||
| code |string | | online报表编码 |
|
||||
| orgFields |string | | online报表中显示的列,多个以逗号隔开 |
|
||||
| destFields |string | | 回调对象的属性,多个以逗号隔开,其顺序和orgFields一一对应 |
|
||||
| field |string | | v-model模式专用,表示从destFields中选择一个属性的值返回给当前组件 |
|
||||
| triggerChange |Boolean | | v-decorator模式下需设置成true |
|
||||
| callback(事件) |function | | 回调事件,v-decorator模式下用到,用于设置form控件的值 |
|
||||
|
||||
使用示例
|
||||
----
|
||||
```vue
|
||||
<template>
|
||||
<a-form :form="form">
|
||||
<a-form-item label="v-model模式指定一个值返回至当前组件" style="width: 300px">
|
||||
<j-popup
|
||||
v-model="selectValue"
|
||||
code="user_msg"
|
||||
org-fields="username,realname"
|
||||
dest-fields="popup,other"
|
||||
field="popup"/>
|
||||
{{ selectValue }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="v-decorator模式支持回调多个值至当前表单" style="width: 300px">
|
||||
<j-popup
|
||||
v-decorator="['one']"
|
||||
:trigger-change="true"
|
||||
code="user_msg"
|
||||
org-fields="username,realname"
|
||||
dest-fields="one,two"
|
||||
@callback="popupCallback"/>
|
||||
{{ getFormFieldValue('one') }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="v-decorator模式被回调的值" style="width: 300px">
|
||||
<a-input v-decorator="['two']"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
|
||||
</a-form >
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: this.$form.createForm(this),
|
||||
selectValue:"",
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
getFormFieldValue(field){
|
||||
return this.form.getFieldValue(field)
|
||||
},
|
||||
popupCallback(row){
|
||||
this.form.setFieldsValue(row)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,283 @@
|
||||
# JTable 支持列自定义及可拖拽列宽的表格
|
||||
|
||||
## JTable参数配置
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|--------------|--------|----|---------------------------------------------------------------|
|
||||
| tableKey | String | | 全局`JTable`唯一,持久化存储自定义列配置 |
|
||||
| columns | Array | ✔ | **需要配合`.sync`获取最新的数据**,具体项见下表 |
|
||||
| settingStyle | Object | | 自定义列配置表的样式 |
|
||||
| settingScroll | Object | | 自定义列配置表的滚动配置 |
|
||||
| scroll | Object | | 表格滚动配置,建议使用拖拽属性`resizable`时设置`scroll.x`,**需要配合`.sync`获取最新的数据** |
|
||||
| columnMinWidth | Number | | 所有列共用的最小宽度,在没有`width`和`minWidth`时的冻结列和拖拽列的最小宽度 |
|
||||
| needSettingColumnHideOrFreeze | Boolean | | 是否需要设置冻结列和显隐列 |
|
||||
| needSaveSettingInLocal | Boolean | | 是否需要将冻结和显隐的设置存储在localStorage中 |
|
||||
| canDrag | Boolean | | 是否可拖拽 |
|
||||
|
||||
## columns参数配置
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|-------------------|----------------|----|------------------------------------------------------|
|
||||
| hideSettingColumn | Boolean | | 是否在自定义列配置表中隐藏 |
|
||||
| disabledHide | Boolean | | 是否禁用自定义列配置表中的隐藏复选框 |
|
||||
| disabledFreeze | Boolean | | 是否禁用自定义列配置表中的冻结复选框 |
|
||||
| fixed | String、Boolean | | 默认的冻结列,只能为`true`或`'left'`其他不生效 |
|
||||
| width | Number | | 列宽,只能是数字 |
|
||||
| minWidth | Number | | 最小列宽,只能是数字,在冻结列和拖拽列时生效 |
|
||||
| resizable | Boolean | | 是否启用拖拽,需要表格显示边框`bordered`(这个属性因为加了总体的参数canDrag,暂时无效) |
|
||||
|
||||
## JTable的方法
|
||||
|
||||
### clearAllCacheSetting
|
||||
|
||||
用于清理所有JTable的缓存
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` 无
|
||||
|
||||
### clearSetting
|
||||
|
||||
用于清理当前表的缓存
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` 无
|
||||
|
||||
### resteColumns
|
||||
|
||||
还原初始的配置
|
||||
|
||||
- `参数:` 无
|
||||
- `返回值:` 无
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
### 方法如何调用?
|
||||
|
||||
在[示例](#示例)中,设定了一个 `ref="table"` 的属性,那么在vue中就可以使用`this.$refs.table`获取到该表格的实例,并调取其中的方法。
|
||||
假如我要调取`resteColumns`方法,就可以这么写:`this.$refs.table.resteColumns()`
|
||||
|
||||
### `columns`和`scroll`为什么要使用`.sync`
|
||||
|
||||
保证父组件和子组件数据同步
|
||||
|
||||
`columns`自定义列配置后会同步,
|
||||
`scroll`拖拽后会把最新的总宽度同步
|
||||
|
||||
|
||||
### 对原`a-table`的使用有哪些限制
|
||||
|
||||
- 操作列的`dataIndex`或`key`必须为`action`
|
||||
- 带有设置的列不能使用`scopedSlots.filterIcon` 和 `scopedSlots.filterDropdown`(没有操作列时,设置在最后一列)
|
||||
- `scroll.x`和`width`必须是数字,不支持百分比
|
||||
- 使用作用域插槽,需要使用解构赋值,里面有三个参数`{text, record, index}`,可参考[示例](#示例)写法
|
||||
|
||||
### 拖拽后没有设置宽度的列会被缩小
|
||||
|
||||
- 设置scroll.x配置一个默认宽度即可
|
||||
|
||||
### 出现空白列
|
||||
|
||||
- 设置至少一列没有width(没有width不能被拖拽)
|
||||
- 建议没有`width`的列不能手动控制隐藏和冻结,使用`disabledHide`和`disabledFreeze`或者`hideSettingColumn`
|
||||
|
||||
### `resizable`没有效果
|
||||
|
||||
- 需要设置`resizable: true`,要有`width`,且不能有`fixed`
|
||||
|
||||
### 自定义列的弹框会消失
|
||||
|
||||
- 因为冻结列的出现或消失会刷新表格结构导致弹框丢失
|
||||
- 设置一个冻结列始终存在,不可被手动控制隐藏和取消冻结即可
|
||||
|
||||
### 注意事项
|
||||
|
||||
- `table-key`该属性为全局每个表的唯一属性,建议使用路由+命名的方式,如[示例](#示例)中设置
|
||||
- 固定头和列(ant-design-vue自带的问题) :若列头与内容不对齐或出现列重复,请指定固定列的宽度 width。如果指定 width 不生效或出现白色垂直空隙,请尝试建议留一列不设宽度以适应弹性布局,或者检查是否有超长连续字段破坏布局。
|
||||
建议指定 scroll.x 为大于表格宽度的固定值。注意,且非固定列宽度之和不要超过 `scroll.x`。
|
||||
- 如果表头是从后端获取的,需要写v-if="columns && columns.length > 0",引入ConfigurableTableMixin混入
|
||||
|
||||
## 示例
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
<div style="margin-bottom: 5px;">
|
||||
<a-button @click="resetTable">重置表格配置</a-button>
|
||||
<a-button @click="clearTableCache" style="margin-left: 5px;">清空本表缓存</a-button>
|
||||
<a-button @click="clearAllTableCache" style="margin-left: 5px;">清空全局JTable缓存</a-button>
|
||||
</div>
|
||||
<j-table bordered
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onChange: onSelectChange }"
|
||||
:table-key="$route.name + '_table1'"
|
||||
ref="table"
|
||||
rowKey="key"
|
||||
:data-source="dataSource"
|
||||
:scroll.sync="scroll"
|
||||
:columns.sync="columns">
|
||||
<!-- 使用插槽 -->
|
||||
<a slot="name" slot-scope="{text}">{{ text }}</a>
|
||||
<span slot="customTitle"><a-icon type="smile-o" /> Name</span>
|
||||
<span slot="tags" slot-scope="{text: tags}">
|
||||
<a-tag
|
||||
v-for="tag in tags"
|
||||
:key="tag"
|
||||
:color="tag === 'loser' ? 'volcano' : tag.length > 5 ? 'geekblue' : 'green'"
|
||||
>
|
||||
{{ tag.toUpperCase() }}
|
||||
</a-tag>
|
||||
</span>
|
||||
|
||||
<span slot="action" slot-scope="{record}">
|
||||
<a-popconfirm
|
||||
v-if="dataSource.length"
|
||||
title="是否删除?"
|
||||
@confirm="() => onDelete(record.key)"
|
||||
>
|
||||
<a href="javascript:">Delete</a>
|
||||
</a-popconfirm>
|
||||
</span>
|
||||
|
||||
<template slot="footer" slot-scope="currentPageData">
|
||||
Footer:{{ currentPageData }}
|
||||
</template>
|
||||
</j-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JTable from '@comp/jero/JTable'
|
||||
|
||||
export default {
|
||||
name: 'Demo',
|
||||
components: { JTable },
|
||||
data () {
|
||||
const columns = [
|
||||
{
|
||||
// 是否在配置表中隐藏
|
||||
// hideSettingColumn: true,
|
||||
// 尽量都设置最小宽度,冻结列与拖拽时生效
|
||||
minWidth: 100,
|
||||
width: 100,
|
||||
// 禁用配置表中的隐藏或冻结
|
||||
disabledHide: true,
|
||||
disabledFreeze: true,
|
||||
// 可拖拽属性(不能fixed一起用)
|
||||
// resizable: true,
|
||||
// 默认冻结,值可以是 'left' 或 true
|
||||
fixed: true,
|
||||
title: '#',
|
||||
ellipsis: true,
|
||||
dataIndex: 'key'
|
||||
},
|
||||
{
|
||||
resizable: true,
|
||||
title: 'Date',
|
||||
dataIndex: 'date',
|
||||
minWidth: 200,
|
||||
width: 200
|
||||
},
|
||||
{
|
||||
// 标题使用插槽使用这个代替title(用于设置里显示列名)
|
||||
columnTitle: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
minWidth: 100,
|
||||
slots: { title: 'customTitle' },
|
||||
scopedSlots: { customRender: 'name' }
|
||||
},
|
||||
{
|
||||
title: 'Tags',
|
||||
key: 'tags',
|
||||
dataIndex: 'tags',
|
||||
minWidth: 200,
|
||||
scopedSlots: { customRender: 'tags' }
|
||||
},
|
||||
{
|
||||
resizable: true, // 可拖拽属性
|
||||
title: 'Amount',
|
||||
dataIndex: 'amount',
|
||||
minWidth: 100,
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
dataIndex: 'type',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: 'Note',
|
||||
dataIndex: 'note',
|
||||
// 保留一列自适应
|
||||
// width: 100
|
||||
// 建议:没有宽度的列至少一列禁止自定义(解决冻结或隐藏列导致出现空列)
|
||||
// disabledHide: true,
|
||||
// disabledFreeze: true,
|
||||
hideSettingColumn: true
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
// filterIcon 和 filterDropdown 不能使用
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
const dataSource = [
|
||||
{
|
||||
key: 0,
|
||||
name: 'John Brown',
|
||||
tags: ['nice', 'developer'],
|
||||
date: '2018-02-11',
|
||||
amount: 120,
|
||||
type: 'income',
|
||||
note: 'transfer'
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
name: 'Jim Green',
|
||||
tags: ['loser'],
|
||||
date: '2018-03-11',
|
||||
amount: 243,
|
||||
type: 'income',
|
||||
note: 'transfer'
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: 'Joe Black',
|
||||
tags: ['cool', 'teacher'],
|
||||
date: '2018-04-11',
|
||||
amount: 98,
|
||||
type: 'income',
|
||||
note: 'transfer'
|
||||
}
|
||||
]
|
||||
return {
|
||||
scroll: { x: 1400 },
|
||||
columns: columns,
|
||||
dataSource: dataSource,
|
||||
selectedRowKeys: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 还原表格初始配置
|
||||
resetTable () {
|
||||
this.$refs.table.resteColumns()
|
||||
},
|
||||
// 清除当前表格缓存
|
||||
clearTableCache () {
|
||||
this.$refs.table.clearSetting()
|
||||
},
|
||||
// 清除所有缓存
|
||||
clearAllTableCache () {
|
||||
this.$refs.table.clearAllCacheSetting()
|
||||
},
|
||||
onDelete (key) {
|
||||
this.$message.info(`del:${key}`)
|
||||
},
|
||||
onSelectChange (selectedRowKeys) {
|
||||
console.log('selectedRowKeys changed: ', selectedRowKeys)
|
||||
this.selectedRowKeys = selectedRowKeys
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
@@ -0,0 +1,92 @@
|
||||
import JModal from './JModal'
|
||||
import JFormContainer from './JFormContainer.vue'
|
||||
import JPopup from './JPopup.vue'
|
||||
import JMarkdownEditor from './JMarkdownEditor'
|
||||
import JCodeEditor from './JCodeEditor.vue'
|
||||
import JEditor from './JEditor.vue'
|
||||
import JEditableTable from './JEditableTable.vue'
|
||||
import JAreaLinkage from './JAreaLinkage.vue'
|
||||
import JSuperQuery from './JSuperQuery.vue'
|
||||
import JUpload from './JUpload.vue'
|
||||
import JTreeSelect from './JTreeSelect.vue'
|
||||
import JCategorySelect from './JCategorySelect.vue'
|
||||
import JImageUpload from './JImageUpload.vue'
|
||||
import JImportModal from './JImportModal.vue'
|
||||
import JTreeDict from './JTreeDict.vue'
|
||||
import JCheckbox from './JCheckbox.vue'
|
||||
import JCron from './JCron.vue'
|
||||
import JDate from './JDate.vue'
|
||||
import JEllipsis from './JEllipsis.vue'
|
||||
import JInput from './JInput.vue'
|
||||
import JPopupOnlReport from './modal/JPopupOnlReport.vue'
|
||||
import JFilePop from './minipop/JFilePop.vue'
|
||||
import JInputPop from './minipop/JInputPop.vue'
|
||||
import JSelectMultiple from './JSelectMultiple.vue'
|
||||
import JSlider from './JSlider.vue'
|
||||
import JSwitch from './JSwitch.vue'
|
||||
import JTime from './JTime.vue'
|
||||
import JTreeTable from './JTreeTable.vue'
|
||||
import JEasyCron from '@/components/jero/JEasyCron'
|
||||
// jerobiz
|
||||
import JSelectDepart from '../jerobiz/JSelectDepart.vue'
|
||||
import JSelectMultiUser from '../jerobiz/JSelectMultiUser.vue'
|
||||
import JSelectRole from '../jerobiz/JSelectRole.vue'
|
||||
import JSelectUserByDep from '../jerobiz/JSelectUserByDep.vue'
|
||||
// 引入需要全局注册的js函数和变量
|
||||
import { Modal, notification, message } from 'ant-design-vue'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
import lodash_object from 'lodash'
|
||||
import debounce from 'lodash/debounce'
|
||||
import pick from 'lodash.pick'
|
||||
import data from 'china-area-data'
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.use(JModal)
|
||||
Vue.component('JMarkdownEditor', JMarkdownEditor)
|
||||
Vue.component('JPopupOnlReport', JPopupOnlReport)
|
||||
Vue.component('JFilePop', JFilePop)
|
||||
Vue.component('JInputPop', JInputPop)
|
||||
Vue.component('JAreaLinkage', JAreaLinkage)
|
||||
Vue.component('JCategorySelect', JCategorySelect)
|
||||
Vue.component('JCheckbox', JCheckbox)
|
||||
Vue.component('JCodeEditor', JCodeEditor)
|
||||
Vue.component('JCron', JCron)
|
||||
Vue.component('JDate', JDate)
|
||||
Vue.component('JEditableTable', JEditableTable)
|
||||
Vue.component('JEditor', JEditor)
|
||||
Vue.component('JEllipsis', JEllipsis)
|
||||
Vue.component('JFormContainer', JFormContainer)
|
||||
Vue.component('JImageUpload', JImageUpload)
|
||||
Vue.component('JImportModal', JImportModal)
|
||||
Vue.component('JInput', JInput)
|
||||
Vue.component('JPopup', JPopup)
|
||||
Vue.component('JSelectMultiple', JSelectMultiple)
|
||||
Vue.component('JSlider', JSlider)
|
||||
Vue.component('JSuperQuery', JSuperQuery)
|
||||
Vue.component('JSwitch', JSwitch)
|
||||
Vue.component('JTime', JTime)
|
||||
Vue.component('JTreeDict', JTreeDict)
|
||||
Vue.component('JTreeSelect', JTreeSelect)
|
||||
Vue.component('JTreeTable', JTreeTable)
|
||||
Vue.component('JUpload', JUpload)
|
||||
|
||||
// jerobiz
|
||||
Vue.component('JSelectDepart', JSelectDepart)
|
||||
Vue.component('JSelectMultiUser', JSelectMultiUser)
|
||||
Vue.component('JSelectRole', JSelectRole)
|
||||
Vue.component('JSelectUserByDep', JSelectUserByDep)
|
||||
Vue.component(JEasyCron.name, JEasyCron)
|
||||
|
||||
// 注册全局js函数和变量
|
||||
Vue.prototype.$Jnotification = notification
|
||||
Vue.prototype.$Jmodal = Modal
|
||||
Vue.prototype.$Jmessage = message
|
||||
// eslint-disable-next-line camelcase
|
||||
Vue.prototype.$Jlodash = lodash_object
|
||||
Vue.prototype.$Jdebounce = debounce
|
||||
Vue.prototype.$Jpick = pick
|
||||
Vue.prototype.$Jpcaa = data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-modal
|
||||
:title="fileType === 'image' ? '图片上传' : '文件上传'"
|
||||
:width="width"
|
||||
:visible="visible"
|
||||
@ok="ok"
|
||||
cancelText="取消"
|
||||
@cancel="close">
|
||||
<!--style="top: 20px;"-->
|
||||
<j-upload :file-type="fileType" :value="filePath" @change="handleChange" :disabled="disabled" :number="number"></j-upload>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
|
||||
const getFileName = (path) => {
|
||||
if (path.lastIndexOf('\\') >= 0) {
|
||||
const reg = new RegExp('\\\\', 'g')
|
||||
path = path.replace(reg, '/')
|
||||
}
|
||||
return path.substring(path.lastIndexOf('/') + 1)
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'JFilePop',
|
||||
components: { },
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
required: false
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
required: false
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 520,
|
||||
required: false
|
||||
},
|
||||
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
filePath: '',
|
||||
id: '',
|
||||
fileType: 'file'
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (value) {
|
||||
this.filePath = value
|
||||
},
|
||||
show (id, value, flag) {
|
||||
this.id = id
|
||||
this.filePath = value
|
||||
this.visible = true
|
||||
if (flag === 'img') {
|
||||
this.fileType = 'image'
|
||||
} else {
|
||||
this.fileType = 'file'
|
||||
}
|
||||
},
|
||||
ok () {
|
||||
if (!this.filePath) {
|
||||
this.$message.warning('未上传任何文件')
|
||||
return false
|
||||
}
|
||||
const arr = this.filePath.split(',')
|
||||
const obj = {
|
||||
name: getFileName(arr[0]),
|
||||
url: getFileAccessHttpUrl(arr[0]),
|
||||
path: this.filePath,
|
||||
status: 'done',
|
||||
id: this.id
|
||||
}
|
||||
this.$emit('ok', obj)
|
||||
this.visible = false
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<a-popover trigger="contextmenu" v-model="visible" :placement="position" overlayClassName="j-input-pop">
|
||||
<!--"(node) => node.parentNode.parentNode"-->
|
||||
<div slot="title">
|
||||
<span>{{ title }}</span>
|
||||
<span style="float: right" title="关闭">
|
||||
<a-icon type="close" @click="visible=false"/>
|
||||
</span>
|
||||
</div>
|
||||
<a-input :value="inputContent" :disabled="disabled" @change="handleInputChange">
|
||||
<a-icon slot="suffix" type="fullscreen" @click.stop="pop" />
|
||||
</a-input>
|
||||
<div slot="content">
|
||||
<a-textarea ref="textarea" :value="inputContent" :disabled="disabled" @input="handleInputChange" :style="{ height: height + 'px', width: width + 'px' }"/>
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JInputPop',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
position: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
required: false
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
required: false
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
inputContent: ''
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function () {
|
||||
if (this.value && this.value.length > 0) {
|
||||
this.inputContent = this.value
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
methods: {
|
||||
handleInputChange (event) {
|
||||
this.inputContent = event.target.value
|
||||
this.$emit('change', this.inputContent)
|
||||
},
|
||||
pop () {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.textarea.focus()
|
||||
})
|
||||
},
|
||||
getPopupContainer (node) {
|
||||
if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.getElementById(this.popContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,925 @@
|
||||
<template>
|
||||
<a-modal
|
||||
title="cron表达式"
|
||||
:width="modalWidth"
|
||||
:visible="visible"
|
||||
:confirmLoading="confirmLoading"
|
||||
@ok="handleSubmit"
|
||||
@cancel="close"
|
||||
cancelText="关闭">
|
||||
<div class="card-container">
|
||||
<a-tabs type="card">
|
||||
<a-tab-pane key="1" type="card">
|
||||
<span slot="tab"><a-icon type="schedule" /> 秒</span>
|
||||
<a-radio-group v-model="result.second.cronEvery">
|
||||
<a-row>
|
||||
<a-radio value="1">每一秒钟</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="2">每隔
|
||||
<a-input-number size="small" v-model="result.second.incrementIncrement" :min="1" :max="59"></a-input-number>
|
||||
秒执行 从
|
||||
<a-input-number size="small" v-model="result.second.incrementStart" :min="0" :max="59"></a-input-number>
|
||||
秒开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="3">具体秒数(可多选)</a-radio>
|
||||
<a-select style="width:354px;" size="small" mode="multiple" v-model="result.second.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in 60" :key="index" :value="index">{{ index }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="4">周期从
|
||||
<a-input-number size="small" v-model="result.second.rangeStart" :min="1" :max="59"></a-input-number>
|
||||
到
|
||||
<a-input-number size="small" v-model="result.second.rangeEnd" :min="0" :max="59"></a-input-number>
|
||||
秒
|
||||
</a-radio>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2">
|
||||
<span slot="tab"><a-icon type="schedule" />分</span>
|
||||
<div class="tabBody">
|
||||
<a-radio-group v-model="result.minute.cronEvery">
|
||||
<a-row>
|
||||
<a-radio value="1">每一分钟</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="2">每隔
|
||||
<a-input-number size="small" v-model="result.minute.incrementIncrement" :min="1" :max="60"></a-input-number>
|
||||
分执行 从
|
||||
<a-input-number size="small" v-model="result.minute.incrementStart" :min="0" :max="59"></a-input-number>
|
||||
分开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="3">具体分钟数(可多选)</a-radio>
|
||||
<a-select style="width:340px;" size="small" mode="multiple" v-model="result.minute.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in Array(60)" :key="index" :value="index"> {{ index }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="4">周期从
|
||||
<a-input-number size="small" v-model="result.minute.rangeStart" :min="1" :max="60"></a-input-number>
|
||||
到
|
||||
<a-input-number size="small" v-model="result.minute.rangeEnd" :min="0" :max="59"></a-input-number>
|
||||
分
|
||||
</a-radio>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3">
|
||||
<span slot="tab"><a-icon type="schedule" /> 时</span>
|
||||
<div class="tabBody">
|
||||
<a-radio-group v-model="result.hour.cronEvery">
|
||||
<a-row>
|
||||
<a-radio value="1">每一小时</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="2">每隔
|
||||
<a-input-number size="small" v-model="result.hour.incrementIncrement" :min="0" :max="23"></a-input-number>
|
||||
小时执行 从
|
||||
<a-input-number size="small" v-model="result.hour.incrementStart" :min="0" :max="23"></a-input-number>
|
||||
小时开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio class="long" value="3">具体小时数(可多选)</a-radio>
|
||||
<a-select style="width:340px;" size="small" mode="multiple" v-model="result.hour.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in Array(24)" :key="index" >{{ index }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="4">周期从
|
||||
<a-input-number size="small" v-model="result.hour.rangeStart" :min="0" :max="23"></a-input-number>
|
||||
到
|
||||
<a-input-number size="small" v-model="result.hour.rangeEnd" :min="0" :max="23"></a-input-number>
|
||||
小时
|
||||
</a-radio>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="4">
|
||||
<span slot="tab"><a-icon type="schedule" /> 天</span>
|
||||
<div class="tabBody">
|
||||
<a-radio-group v-model="result.day.cronEvery">
|
||||
<a-row>
|
||||
<a-radio value="1">每一天</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="2">每隔
|
||||
<a-input-number size="small" v-model="result.week.incrementIncrement" :min="1" :max="7"></a-input-number>
|
||||
周执行 从
|
||||
<a-select size="small" v-model="result.week.incrementStart">
|
||||
<a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
|
||||
</a-select>
|
||||
开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="3">每隔
|
||||
<a-input-number size="small" v-model="result.day.incrementIncrement" :min="1" :max="31"></a-input-number>
|
||||
天执行 从
|
||||
<a-input-number size="small" v-model="result.day.incrementStart" :min="1" :max="31"></a-input-number>
|
||||
天开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio class="long" value="4">具体星期几(可多选)</a-radio>
|
||||
<a-select style="width:340px;" size="small" mode="multiple" v-model="result.week.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio class="long" value="5">具体天数(可多选)</a-radio>
|
||||
<a-select style="width:354px;" size="small" mode="multiple" v-model="result.day.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in Array(31)" :key="index" :value="index+1">{{ index+1 }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="6">在这个月的最后一天</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="7">在这个月的最后一个工作日</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="8">在这个月的最后一个
|
||||
<a-select size="small" v-model="result.day.cronLastSpecificDomDay">
|
||||
<a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
|
||||
</a-select>
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="9">
|
||||
在本月底前
|
||||
<a-input-number size="small" v-model="result.day.cronDaysBeforeEomMinus" :min="1" :max="31"></a-input-number>
|
||||
天
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="10">最近的工作日(周一至周五)至本月
|
||||
<a-input-number size="small" v-model="result.day.cronDaysNearestWeekday" :min="1" :max="31"></a-input-number>
|
||||
日
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="11">在这个月的第
|
||||
<a-input-number size="small" v-model="result.week.cronNthDayNth" :min="1" :max="5"></a-input-number>
|
||||
个
|
||||
<a-select size="small" v-model="result.week.cronNthDayDay">
|
||||
<a-select-option v-for="(val,index) in Array(7)" :key="index" :value="index+1">{{ weekDays[index] }}</a-select-option>
|
||||
</a-select>
|
||||
|
||||
</a-radio>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5">
|
||||
<span slot="tab"><a-icon type="schedule" /> 月</span>
|
||||
<div class="tabBody">
|
||||
<a-radio-group v-model="result.month.cronEvery">
|
||||
<a-row>
|
||||
<a-radio value="1">每一月</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="2">每隔
|
||||
<a-input-number size="small" v-model="result.month.incrementIncrement" :min="0" :max="12"></a-input-number>
|
||||
月执行 从
|
||||
<a-input-number size="small" v-model="result.month.incrementStart" :min="0" :max="12"></a-input-number>
|
||||
月开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio class="long" value="3">具体月数(可多选)</a-radio>
|
||||
<a-select style="width:354px;" size="small" filterable mode="multiple" v-model="result.month.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in Array(12)" :key="index" :value="index+1">{{ index+1 }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="4">从
|
||||
<a-input-number size="small" v-model="result.month.rangeStart" :min="1" :max="12"></a-input-number>
|
||||
到
|
||||
<a-input-number size="small" v-model="result.month.rangeEnd" :min="1" :max="12"></a-input-number>
|
||||
月之间的每个月
|
||||
</a-radio>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="6">
|
||||
<span slot="tab"><a-icon type="schedule" /> 年</span>
|
||||
<div class="tabBody">
|
||||
<a-radio-group v-model="result.year.cronEvery">
|
||||
<a-row>
|
||||
<a-radio value="1">每一年</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="2">每隔
|
||||
<a-input-number size="small" v-model="result.year.incrementIncrement" :min="1" :max="99"></a-input-number>
|
||||
年执行 从
|
||||
<a-input-number size="small" v-model="result.year.incrementStart" :min="2019" :max="2119"></a-input-number>
|
||||
年开始
|
||||
</a-radio>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio class="long" value="3">具体年份(可多选)</a-radio>
|
||||
<a-select style="width:354px;" size="small" filterable mode="multiple" v-model="result.year.specificSpecific">
|
||||
<a-select-option v-for="(val,index) in Array(100)" :key="index" :value="2019+index">{{ 2019+index }}</a-select-option>
|
||||
</a-select>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-radio value="4">从
|
||||
<a-input-number size="small" v-model="result.year.rangeStart" :min="2019" :max="2119"></a-input-number>
|
||||
到
|
||||
<a-input-number size="small" v-model="result.year.rangeEnd" :min="2019" :max="2119"></a-input-number>
|
||||
年之间的每一年
|
||||
</a-radio>
|
||||
</a-row>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<div class="bottom">
|
||||
<span class="value">{{this.cron }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'VueCron',
|
||||
props: ['data'],
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
size: 'large',
|
||||
weekDays: ['天', '一', '二', '三', '四', '五', '六'].map(val => '星期' + val),
|
||||
result: {
|
||||
second: {},
|
||||
minute: {},
|
||||
hour: {},
|
||||
day: {},
|
||||
week: {},
|
||||
month: {},
|
||||
year: {}
|
||||
},
|
||||
defaultValue: {
|
||||
second: {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: 0,
|
||||
specificSpecific: []
|
||||
},
|
||||
minute: {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: '0',
|
||||
specificSpecific: []
|
||||
},
|
||||
hour: {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: '0',
|
||||
rangeEnd: '0',
|
||||
specificSpecific: []
|
||||
},
|
||||
day: {
|
||||
cronEvery: '',
|
||||
incrementStart: 1,
|
||||
incrementIncrement: '1',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [],
|
||||
cronLastSpecificDomDay: 1,
|
||||
cronDaysBeforeEomMinus: 1,
|
||||
cronDaysNearestWeekday: 1
|
||||
},
|
||||
week: {
|
||||
cronEvery: '',
|
||||
incrementStart: 1,
|
||||
incrementIncrement: 1,
|
||||
specificSpecific: [],
|
||||
cronNthDayDay: 1,
|
||||
cronNthDayNth: 1
|
||||
},
|
||||
month: {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: 1,
|
||||
specificSpecific: []
|
||||
},
|
||||
year: {
|
||||
cronEvery: '',
|
||||
incrementStart: 2017,
|
||||
incrementIncrement: 1,
|
||||
rangeStart: 2019,
|
||||
rangeEnd: 2019,
|
||||
specificSpecific: []
|
||||
},
|
||||
label: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
modalWidth () {
|
||||
return 608
|
||||
},
|
||||
secondsText () {
|
||||
let seconds = ''
|
||||
const cronEvery = this.result.second.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
seconds = '*'
|
||||
break
|
||||
case '2':
|
||||
seconds = this.result.second.incrementStart + '/' + this.result.second.incrementIncrement
|
||||
break
|
||||
case '3':
|
||||
this.result.second.specificSpecific.map(val => { seconds += val + ',' })
|
||||
seconds = seconds.slice(0, -1)
|
||||
break
|
||||
case '4':
|
||||
seconds = this.result.second.rangeStart + '-' + this.result.second.rangeEnd
|
||||
break
|
||||
}
|
||||
return seconds
|
||||
},
|
||||
minutesText () {
|
||||
let minutes = ''
|
||||
const cronEvery = this.result.minute.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
minutes = '*'
|
||||
break
|
||||
case '2':
|
||||
minutes = this.result.minute.incrementStart + '/' + this.result.minute.incrementIncrement
|
||||
break
|
||||
case '3':
|
||||
this.result.minute.specificSpecific.map(val => {
|
||||
minutes += val + ','
|
||||
})
|
||||
minutes = minutes.slice(0, -1)
|
||||
break
|
||||
case '4':
|
||||
minutes = this.result.minute.rangeStart + '-' + this.result.minute.rangeEnd
|
||||
break
|
||||
}
|
||||
return minutes
|
||||
},
|
||||
hoursText () {
|
||||
let hours = ''
|
||||
const cronEvery = this.result.hour.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
hours = '*'
|
||||
break
|
||||
case '2':
|
||||
hours = this.result.hour.incrementStart + '/' + this.result.hour.incrementIncrement
|
||||
break
|
||||
case '3':
|
||||
this.result.hour.specificSpecific.map(val => {
|
||||
hours += val + ','
|
||||
})
|
||||
hours = hours.slice(0, -1)
|
||||
break
|
||||
case '4':
|
||||
hours = this.result.hour.rangeStart + '-' + this.result.hour.rangeEnd
|
||||
break
|
||||
}
|
||||
return hours
|
||||
},
|
||||
daysText () {
|
||||
let days = ''
|
||||
const cronEvery = this.result.day.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
break
|
||||
case '2':
|
||||
case '4':
|
||||
case '11':
|
||||
days = '?'
|
||||
break
|
||||
case '3':
|
||||
days = this.result.day.incrementStart + '/' + this.result.day.incrementIncrement
|
||||
break
|
||||
case '5':
|
||||
this.result.day.specificSpecific.map(val => {
|
||||
days += val + ','
|
||||
})
|
||||
days = days.slice(0, -1)
|
||||
break
|
||||
case '6':
|
||||
days = 'L'
|
||||
break
|
||||
case '7':
|
||||
days = 'LW'
|
||||
break
|
||||
case '8':
|
||||
days = this.result.day.cronLastSpecificDomDay + 'L'
|
||||
break
|
||||
case '9':
|
||||
days = 'L-' + this.result.day.cronDaysBeforeEomMinus
|
||||
break
|
||||
case '10':
|
||||
days = this.result.day.cronDaysNearestWeekday + 'W'
|
||||
break
|
||||
}
|
||||
return days
|
||||
},
|
||||
weeksText () {
|
||||
let weeks = ''
|
||||
const cronEvery = this.result.day.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
case '3':
|
||||
case '5':
|
||||
weeks = '?'
|
||||
break
|
||||
case '2':
|
||||
weeks = this.result.week.incrementStart + '/' + this.result.week.incrementIncrement
|
||||
break
|
||||
case '4':
|
||||
this.result.week.specificSpecific.map(val => {
|
||||
weeks += val + ','
|
||||
})
|
||||
weeks = weeks.slice(0, -1)
|
||||
break
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '10':
|
||||
weeks = '?'
|
||||
break
|
||||
case '11':
|
||||
weeks = this.result.week.cronNthDayDay + '#' + this.result.week.cronNthDayNth
|
||||
break
|
||||
}
|
||||
return weeks
|
||||
},
|
||||
monthsText () {
|
||||
let months = ''
|
||||
const cronEvery = this.result.month.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
months = '*'
|
||||
break
|
||||
case '2':
|
||||
months = this.result.month.incrementStart + '/' + this.result.month.incrementIncrement
|
||||
break
|
||||
case '3':
|
||||
this.result.month.specificSpecific.map(val => {
|
||||
months += val + ','
|
||||
})
|
||||
months = months.slice(0, -1)
|
||||
break
|
||||
case '4':
|
||||
months = this.result.month.rangeStart + '-' + this.result.month.rangeEnd
|
||||
break
|
||||
}
|
||||
return months
|
||||
},
|
||||
yearsText () {
|
||||
let years = ''
|
||||
const cronEvery = this.result.year.cronEvery || ''
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
years = '*'
|
||||
break
|
||||
case '2':
|
||||
years = this.result.year.incrementStart + '/' + this.result.year.incrementIncrement
|
||||
break
|
||||
case '3':
|
||||
this.result.year.specificSpecific.map(val => {
|
||||
years += val + ','
|
||||
})
|
||||
years = years.slice(0, -1)
|
||||
break
|
||||
case '4':
|
||||
years = this.result.year.rangeStart + '-' + this.result.year.rangeEnd
|
||||
break
|
||||
}
|
||||
return years
|
||||
},
|
||||
cron () {
|
||||
return `${this.secondsText || '*'} ${this.minutesText || '*'} ${this.hoursText || '*'} ${this.daysText || '*'} ${this.monthsText || '*'} ${this.weeksText || '?'} ${this.yearsText || '*'}`
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible: {
|
||||
handler () {
|
||||
// if(this.data){
|
||||
// //this. result = Object.keys(this.data.value).length>0?this.deepCopy(this.data.value):this.deepCopy(this.defaultValue);
|
||||
// //this.result = Object.keys(this.data.value).length>0?clone(this.data.value):clone(this.defaultValue);
|
||||
// //this.result = Object.keys(this.data.value).length>0?clone(JSON.parse(this.data.value)):clone(this.defaultValue);
|
||||
// this.result = Object.keys(this.data.value).length>0?JSON.parse(this.data.value):JSON.parse(JSON.stringify(this.defaultValue));
|
||||
// }else{
|
||||
// //this.result = this.deepCopy(this.defaultValue);
|
||||
// //this.result = clone(this.defaultValue);
|
||||
// this.result = JSON.parse(JSON.stringify(this.defaultValue));
|
||||
// }
|
||||
const label = this.data
|
||||
if (label) {
|
||||
this.secondsReverseExp(label)
|
||||
this.minutesReverseExp(label)
|
||||
this.hoursReverseExp(label)
|
||||
this.daysReverseExp(label)
|
||||
this.daysReverseExp(label)
|
||||
this.monthsReverseExp(label)
|
||||
this.yearReverseExp(label)
|
||||
JSON.parse(JSON.stringify(label))
|
||||
} else {
|
||||
this.result = JSON.parse(JSON.stringify(this.defaultValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show () {
|
||||
this.visible = true
|
||||
// console.log('secondsReverseExp',this.secondsReverseExp(this.data));
|
||||
// console.log('minutesReverseExp',this.minutesReverseExp(this.data));
|
||||
// console.log('hoursReverseExp',this.hoursReverseExp(this.data));
|
||||
// console.log('daysReverseExp',this.daysReverseExp(this.data));
|
||||
// console.log('monthsReverseExp',this.monthsReverseExp(this.data));
|
||||
// console.log('yearReverseExp',this.yearReverseExp(this.data));
|
||||
},
|
||||
handleSubmit () {
|
||||
this.$emit('ok', this.cron)
|
||||
this.close()
|
||||
this.visible = false
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
},
|
||||
secondsReverseExp (seconds) {
|
||||
const val = seconds.split(' ')[0]
|
||||
// alert(val);
|
||||
const second = {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: 0,
|
||||
specificSpecific: []
|
||||
}
|
||||
switch (true) {
|
||||
case val.includes('*'):
|
||||
second.cronEvery = '1'
|
||||
break
|
||||
case val.includes('/'):
|
||||
second.cronEvery = '2'
|
||||
second.incrementStart = val.split('/')[0]
|
||||
second.incrementIncrement = val.split('/')[1]
|
||||
break
|
||||
case val.includes(','):
|
||||
second.cronEvery = '3'
|
||||
second.specificSpecific = val.split(',').map(Number).sort()
|
||||
break
|
||||
case val.includes('-'):
|
||||
second.cronEvery = '4'
|
||||
second.rangeStart = val.split('-')[0]
|
||||
second.rangeEnd = val.split('-')[1]
|
||||
break
|
||||
default:
|
||||
second.cronEvery = '1'
|
||||
}
|
||||
this.result.second = second
|
||||
},
|
||||
minutesReverseExp (minutes) {
|
||||
const val = minutes.split(' ')[1]
|
||||
const minute = {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: 0,
|
||||
specificSpecific: []
|
||||
}
|
||||
switch (true) {
|
||||
case val.includes('*'):
|
||||
minute.cronEvery = '1'
|
||||
break
|
||||
case val.includes('/'):
|
||||
minute.cronEvery = '2'
|
||||
minute.incrementStart = val.split('/')[0]
|
||||
minute.incrementIncrement = val.split('/')[1]
|
||||
break
|
||||
case val.includes(','):
|
||||
minute.cronEvery = '3'
|
||||
minute.specificSpecific = val.split(',').map(Number).sort()
|
||||
break
|
||||
case val.includes('-'):
|
||||
minute.cronEvery = '4'
|
||||
minute.rangeStart = val.split('-')[0]
|
||||
minute.rangeEnd = val.split('-')[1]
|
||||
break
|
||||
default:
|
||||
minute.cronEvery = '1'
|
||||
}
|
||||
this.result.minute = minute
|
||||
},
|
||||
hoursReverseExp (hours) {
|
||||
const val = hours.split(' ')[2]
|
||||
const hour = {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: '0',
|
||||
specificSpecific: []
|
||||
}
|
||||
switch (true) {
|
||||
case val.includes('*'):
|
||||
hour.cronEvery = '1'
|
||||
break
|
||||
case val.includes('/'):
|
||||
hour.cronEvery = '2'
|
||||
hour.incrementStart = val.split('/')[0]
|
||||
hour.incrementIncrement = val.split('/')[1]
|
||||
break
|
||||
case val.includes(','):
|
||||
hour.cronEvery = '3'
|
||||
hour.specificSpecific = val.split(',').map(Number).sort()
|
||||
break
|
||||
case val.includes('-'):
|
||||
hour.cronEvery = '4'
|
||||
hour.rangeStart = val.split('-')[0]
|
||||
hour.rangeEnd = val.split('-')[1]
|
||||
break
|
||||
default:
|
||||
hour.cronEvery = '1'
|
||||
}
|
||||
this.result.hour = hour
|
||||
},
|
||||
daysReverseExp (cron) {
|
||||
const days = cron.split(' ')[3]
|
||||
const weeks = cron.split(' ')[5]
|
||||
const day = {
|
||||
cronEvery: '',
|
||||
incrementStart: 1,
|
||||
incrementIncrement: 1,
|
||||
rangeStart: 1,
|
||||
rangeEnd: 1,
|
||||
specificSpecific: [],
|
||||
cronLastSpecificDomDay: 1,
|
||||
cronDaysBeforeEomMinus: 1,
|
||||
cronDaysNearestWeekday: 1
|
||||
}
|
||||
const week = {
|
||||
cronEvery: '',
|
||||
incrementStart: 1,
|
||||
incrementIncrement: 1,
|
||||
specificSpecific: [],
|
||||
cronNthDayDay: 1,
|
||||
cronNthDayNth: '1'
|
||||
}
|
||||
if (!days.includes('?')) {
|
||||
switch (true) {
|
||||
case days.includes('*'):
|
||||
day.cronEvery = '1'
|
||||
break
|
||||
case days.includes('?'):
|
||||
// 2、4、11
|
||||
break
|
||||
case days.includes('/'):
|
||||
day.cronEvery = '3'
|
||||
day.incrementStart = days.split('/')[0]
|
||||
day.incrementIncrement = days.split('/')[1]
|
||||
break
|
||||
case days.includes(','):
|
||||
day.cronEvery = '5'
|
||||
day.specificSpecific = days.split(',').map(Number).sort()
|
||||
// day.specificSpecific.forEach(function (value, index) {
|
||||
// day.specificSpecific[index] = value -1;
|
||||
// });
|
||||
break
|
||||
case days.includes('LW'):
|
||||
day.cronEvery = '7'
|
||||
break
|
||||
case days.includes('L-'):
|
||||
day.cronEvery = '9'
|
||||
day.cronDaysBeforeEomMinus = days.split('L-')[1]
|
||||
break
|
||||
case days.includes('L'):
|
||||
|
||||
// alert(days);
|
||||
if (days.len + '' === '1') {
|
||||
day.cronEvery = '6'
|
||||
day.cronLastSpecificDomDay = '1'
|
||||
} else {
|
||||
day.cronEvery = '8'
|
||||
day.cronLastSpecificDomDay = Number(days.split('L')[0])
|
||||
}
|
||||
break
|
||||
case days.includes('W'):
|
||||
day.cronEvery = '10'
|
||||
day.cronDaysNearestWeekday = days.split('W')[0]
|
||||
break
|
||||
default:
|
||||
day.cronEvery = '1'
|
||||
}
|
||||
} else {
|
||||
switch (true) {
|
||||
case weeks.includes('/'):
|
||||
day.cronEvery = '2'
|
||||
week.incrementStart = weeks.split('/')[0]
|
||||
week.incrementIncrement = weeks.split('/')[1]
|
||||
break
|
||||
case weeks.includes(','):
|
||||
day.cronEvery = '4'
|
||||
week.specificSpecific = weeks.split(',').map(Number).sort()
|
||||
break
|
||||
case '#':
|
||||
day.cronEvery = '11'
|
||||
week.cronNthDayDay = weeks.split('#')[0]
|
||||
week.cronNthDayNth = weeks.split('#')[1]
|
||||
break
|
||||
default:
|
||||
day.cronEvery = '1'
|
||||
week.cronEvery = '1'
|
||||
}
|
||||
}
|
||||
this.result.day = day
|
||||
this.result.week = week
|
||||
},
|
||||
monthsReverseExp (cron) {
|
||||
const months = cron.split(' ')[4]
|
||||
const month = {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 1,
|
||||
rangeEnd: 1,
|
||||
specificSpecific: []
|
||||
}
|
||||
switch (true) {
|
||||
case months.includes('*'):
|
||||
month.cronEvery = '1'
|
||||
break
|
||||
case months.includes('/'):
|
||||
month.cronEvery = '2'
|
||||
month.incrementStart = months.split('/')[0]
|
||||
month.incrementIncrement = months.split('/')[1]
|
||||
break
|
||||
case months.includes(','):
|
||||
month.cronEvery = '3'
|
||||
month.specificSpecific = months.split(',').map(Number).sort()
|
||||
break
|
||||
case months.includes('-'):
|
||||
month.cronEvery = '4'
|
||||
month.rangeStart = months.split('-')[0]
|
||||
month.rangeEnd = months.split('-')[1]
|
||||
break
|
||||
default:
|
||||
month.cronEvery = '1'
|
||||
}
|
||||
this.result.month = month
|
||||
},
|
||||
yearReverseExp (cron) {
|
||||
const years = cron.split(' ')[6]
|
||||
const year = {
|
||||
cronEvery: '',
|
||||
incrementStart: 3,
|
||||
incrementIncrement: 5,
|
||||
rangeStart: 2019,
|
||||
rangeEnd: 2019,
|
||||
specificSpecific: []
|
||||
}
|
||||
switch (true) {
|
||||
case years.includes('*'):
|
||||
year.cronEvery = '1'
|
||||
break
|
||||
case years.includes('/'):
|
||||
year.cronEvery = '2'
|
||||
year.incrementStart = years.split('/')[0]
|
||||
year.incrementIncrement = years.split('/')[1]
|
||||
break
|
||||
case years.includes(','):
|
||||
year.cronEvery = '3'
|
||||
year.specificSpecific = years.split(',').map(Number).sort()
|
||||
break
|
||||
case years.includes('-'):
|
||||
year.cronEvery = '4'
|
||||
year.rangeStart = years.split('-')[0]
|
||||
year.rangeEnd = years.split('-')[1]
|
||||
break
|
||||
default:
|
||||
year.cronEvery = '1'
|
||||
}
|
||||
this.result.year = year
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.card-container {
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.ant-tabs{
|
||||
border:1px solid #e6ebf5;
|
||||
padding: 0;
|
||||
.ant-tabs-bar {
|
||||
margin: 0;
|
||||
outline: none;
|
||||
border-bottom: none;
|
||||
.ant-tabs-nav-container{
|
||||
margin: 0;
|
||||
.ant-tabs-tab {
|
||||
padding: 0 24px!important;
|
||||
background-color: #f5f7fa!important;
|
||||
margin-right: 0 !important;
|
||||
border-radius: 0;
|
||||
line-height: 38px;
|
||||
border: 1px solid transparent!important;
|
||||
border-bottom: 1px solid #e6ebf5!important;
|
||||
}
|
||||
.ant-tabs-tab-active.ant-tabs-tab{
|
||||
color: #409eff;
|
||||
background-color: #fff!important;
|
||||
border-right:1px solid #e6ebf5!important;
|
||||
border-left:1px solid #e6ebf5!important;
|
||||
border-bottom:1px solid #fff!important;
|
||||
font-weight: normal;
|
||||
transition:none!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-tabs-tabpane{
|
||||
padding: 15px;
|
||||
.ant-row{
|
||||
margin: 10px 0;
|
||||
}
|
||||
.ant-select,.ant-input-number{
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
.container-widthEn{
|
||||
width: 755px;
|
||||
}
|
||||
.container-widthCn{
|
||||
width: 608px;
|
||||
}
|
||||
.language{
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
right: 13px;
|
||||
top: 13px;
|
||||
border: 1px solid transparent;
|
||||
height: 40px;
|
||||
line-height: 38px;
|
||||
font-size: 16px;
|
||||
color: #409eff;
|
||||
z-index: 1;
|
||||
background: #f5f7fa;
|
||||
outline: none;
|
||||
width: 47px;
|
||||
border-bottom: 1px solid #e6ebf5;
|
||||
border-radius: 0;
|
||||
}
|
||||
.card-container{
|
||||
.bottom{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0 0 0;
|
||||
.cronButton{
|
||||
margin: 0 10px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tabBody{
|
||||
.a-row{
|
||||
margin: 10px 0;
|
||||
.long{
|
||||
.a-select{
|
||||
width:354px;
|
||||
}
|
||||
}
|
||||
.a-input-number{
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<a-modal
|
||||
title="图片预览"
|
||||
:width="modalWidth"
|
||||
:visible="visible"
|
||||
:confirmLoading="confirmLoading"
|
||||
:footer="null"
|
||||
@cancel="close"
|
||||
cancelText="关闭">
|
||||
<viewer>
|
||||
<img :src="imageUrl" style="width: 200px;height: 200px">
|
||||
</viewer>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileAccessHttpUrl } from '../../../api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JImagePreviewModal',
|
||||
data () {
|
||||
return {
|
||||
modalWidth: 600,
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open (file) {
|
||||
this.visible = true
|
||||
this.imageUrl = getFileAccessHttpUrl(file.name)
|
||||
},
|
||||
close () {
|
||||
this.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/.ant-modal-body{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,440 @@
|
||||
<template>
|
||||
<j-modal
|
||||
:title="title"
|
||||
:width="modalWidth"
|
||||
:visible="visible"
|
||||
:confirmLoading="confirmLoading"
|
||||
switchFullscreen
|
||||
wrapClassName="j-popup-modal"
|
||||
@ok="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
cancelText="关闭">
|
||||
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline" @keyup.enter.native="searchByquery">
|
||||
<a-row :gutter="24" v-if="showSearchFlag">
|
||||
<template v-for="(item,index) in queryInfo">
|
||||
<template v-if=" item.hidden==='1' ">
|
||||
<a-col :md="8" :sm="24" :key=" 'query'+index " v-show="toggleSearchStatus">
|
||||
<online-query-form-item :queryParam="queryParam" :item="item" :dictOptions="dictOptions"></online-query-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-col :md="8" :sm="24" :key=" 'query'+index ">
|
||||
<online-query-form-item :queryParam="queryParam" :item="item" :dictOptions="dictOptions"></online-query-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<a-col :md="8" :sm="8">
|
||||
<span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
|
||||
<a-button type="primary" @click="searchByquery" icon="search">查询</a-button>
|
||||
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
|
||||
<a @click="handleToggleSearch" style="margin-left: 8px">
|
||||
{{ toggleSearchStatus ? '收起' : '展开' }}
|
||||
<a-icon :type="toggleSearchStatus ? 'up' : 'down'"/>
|
||||
</a>
|
||||
</span>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<div class="ant-alert ant-alert-info" style="margin-bottom: 16px;">
|
||||
<i class="anticon anticon-info-circle ant-alert-icon"></i>
|
||||
已选择 <a style="font-weight: 600">{{ table.selectedRowKeys.length }}</a>项
|
||||
<a style="margin-left: 24px" @click="onClearSelected">清空</a>
|
||||
|
||||
<a v-if="!showSearchFlag" style="margin-left: 24px" @click="onlyReload">刷新</a>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
ref="table"
|
||||
size="middle"
|
||||
bordered
|
||||
:rowKey="combineRowKey"
|
||||
:columns="table.columns"
|
||||
:dataSource="table.dataSource"
|
||||
:pagination="table.pagination"
|
||||
:loading="table.loading"
|
||||
:rowSelection="{fixed:true,selectedRowKeys: table.selectedRowKeys, onChange: handleChangeInTableSelect}"
|
||||
@change="handleChangeInTable"
|
||||
style="min-height: 300px"
|
||||
:scroll="tableScroll"
|
||||
:customRow="clickThenCheck">
|
||||
</a-table>
|
||||
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
import { filterObj } from '@/utils/util'
|
||||
import { filterMultiDictText } from '@/components/dict/JDictSelectUtil'
|
||||
import { httpGroupRequest } from '@/api/GroupRequest.js'
|
||||
|
||||
const MODAL_WIDTH = 1200
|
||||
export default {
|
||||
name: 'JPopupOnlReport',
|
||||
props: ['multi', 'code', 'sorter', 'groupId', 'param'],
|
||||
components: {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
title: '',
|
||||
confirmLoading: false,
|
||||
queryInfo: [],
|
||||
toggleSearchStatus: false,
|
||||
queryParam: {
|
||||
|
||||
},
|
||||
dictOptions: {},
|
||||
url: {
|
||||
getColumns: '/online/cgreport/api/getRpColumns/',
|
||||
getData: '/online/cgreport/api/getData/',
|
||||
getQueryInfo: '/online/cgreport/api/getQueryInfo/'
|
||||
},
|
||||
table: {
|
||||
loading: true,
|
||||
// 表头
|
||||
columns: [],
|
||||
// 数据集
|
||||
dataSource: [],
|
||||
// 选择器
|
||||
selectedRowKeys: [],
|
||||
selectionRows: [],
|
||||
// 分页参数
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: ['10', '20', '30'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' 共' + total + '条'
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 0
|
||||
}
|
||||
},
|
||||
cgRpConfigId: '',
|
||||
modalWidth: MODAL_WIDTH,
|
||||
tableScroll: { x: true },
|
||||
dynamicParam: {},
|
||||
// 排序字段,默认无排序
|
||||
iSorter: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// this.loadColumnsInfo()
|
||||
},
|
||||
watch: {
|
||||
code () {
|
||||
this.loadColumnsInfo()
|
||||
},
|
||||
param: {
|
||||
deep: true,
|
||||
handler () {
|
||||
// update--begin--autor:liusq-----date:20210706------for:JPopup组件在modal中使用报错#2729------
|
||||
if (this.visible) {
|
||||
this.dynamicParamHandler()
|
||||
this.loadData()
|
||||
}
|
||||
// update--begin--autor:liusq-----date:20210706------for:JPopup组件在modal中使用报错#2729------
|
||||
}
|
||||
},
|
||||
sorter: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
if (this.sorter) {
|
||||
const arr = this.sorter.split('=')
|
||||
if (arr.length === 2 && ['asc', 'desc'].includes(arr[1].toLowerCase())) {
|
||||
this.iSorter = { column: arr[0], order: arr[1].toLowerCase() }
|
||||
// 排序字段受控
|
||||
this.table.columns.forEach(col => {
|
||||
if (col.dataIndex === this.iSorter.column) {
|
||||
this.$set(col, 'sortOrder', this.iSorter.order === 'asc' ? 'ascend' : 'descend')
|
||||
} else {
|
||||
this.$set(col, 'sortOrder', false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn('【JPopup】sorter参数不合法')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showSearchFlag () {
|
||||
return this.queryInfo && this.queryInfo.length > 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadColumnsInfo () {
|
||||
const url = `${this.url.getColumns}${this.code}`
|
||||
// 缓存key
|
||||
let groupIdKey
|
||||
if (this.groupId) {
|
||||
groupIdKey = this.groupId + url
|
||||
}
|
||||
httpGroupRequest(() => getAction(url), groupIdKey).then(res => {
|
||||
if (res.success) {
|
||||
this.initDictOptionData(res.result.dictOptions)
|
||||
this.cgRpConfigId = res.result.cgRpConfigId
|
||||
this.title = res.result.cgRpConfigName
|
||||
const currColumns = res.result.columns
|
||||
for (let a = 0; a < currColumns.length; a++) {
|
||||
if (currColumns[a].customRender) {
|
||||
const dictCode = currColumns[a].customRender
|
||||
currColumns[a].customRender = (text) => {
|
||||
return filterMultiDictText(this.dictOptions[dictCode], text + '')
|
||||
}
|
||||
}
|
||||
// 排序字段受控
|
||||
if (this.iSorter && currColumns[a].dataIndex === this.iSorter.column) {
|
||||
currColumns[a].sortOrder = this.iSorter.order === 'asc' ? 'ascend' : 'descend'
|
||||
}
|
||||
}
|
||||
this.table.columns = [...currColumns]
|
||||
this.initQueryInfo()
|
||||
}
|
||||
})
|
||||
},
|
||||
initQueryInfo () {
|
||||
const url = `${this.url.getQueryInfo}${this.cgRpConfigId}`
|
||||
// 缓存key
|
||||
let groupIdKey
|
||||
if (this.groupId) {
|
||||
groupIdKey = this.groupId + url
|
||||
}
|
||||
httpGroupRequest(() => getAction(url), groupIdKey).then((res) => {
|
||||
// console.log("获取查询条件", res);
|
||||
if (res.success) {
|
||||
this.dynamicParamHandler(res.result)
|
||||
this.queryInfo = res.result
|
||||
// 查询条件加载后再请求数据
|
||||
this.loadData(1)
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 处理动态参数
|
||||
dynamicParamHandler (arr) {
|
||||
if (arr && arr.length > 0) {
|
||||
// 第一次加载查询条件前 初始化queryParam为空对象
|
||||
const queryTemp = {}
|
||||
for (const item of arr) {
|
||||
if (item.mode === 'single') {
|
||||
queryTemp[item.field] = ''
|
||||
}
|
||||
}
|
||||
this.queryParam = { ...queryTemp }
|
||||
}
|
||||
const dynamicTemp = {}
|
||||
if (this.param) {
|
||||
Object.keys(this.param).map(key => {
|
||||
let str = this.param[key]
|
||||
if (key in this.queryParam) {
|
||||
if (str && str.startsWith("'") && str.endsWith("'")) {
|
||||
str = str.substring(1, str.length - 1)
|
||||
}
|
||||
// 如果查询条件包含参数 设置值
|
||||
this.queryParam[key] = str
|
||||
}
|
||||
dynamicTemp[key] = this.param[key]
|
||||
})
|
||||
}
|
||||
this.dynamicParam = { ...dynamicTemp }
|
||||
},
|
||||
loadData (arg) {
|
||||
if (arg === 1) {
|
||||
this.table.pagination.current = 1
|
||||
}
|
||||
const params = this.getQueryParams()// 查询条件
|
||||
this.table.loading = true
|
||||
const url = `${this.url.getData}${this.cgRpConfigId}`
|
||||
// 缓存key
|
||||
let groupIdKey
|
||||
if (this.groupId) {
|
||||
groupIdKey = this.groupId + url + JSON.stringify(params)
|
||||
}
|
||||
httpGroupRequest(() => getAction(url, params), groupIdKey).then(res => {
|
||||
this.table.loading = false
|
||||
// console.log("daa",res)
|
||||
const data = res.result
|
||||
if (data) {
|
||||
this.table.pagination.total = Number(data.total)
|
||||
this.table.dataSource = data.records
|
||||
} else {
|
||||
this.table.pagination.total = 0
|
||||
this.table.dataSource = []
|
||||
}
|
||||
})
|
||||
},
|
||||
getQueryParams () {
|
||||
const paramTarget = {}
|
||||
if (this.dynamicParam) {
|
||||
// 处理自定义参数
|
||||
Object.keys(this.dynamicParam).map(key => {
|
||||
paramTarget['self_' + key] = this.dynamicParam[key]
|
||||
})
|
||||
}
|
||||
const param = Object.assign(paramTarget, this.queryParam, this.iSorter)
|
||||
param.pageNo = this.table.pagination.current
|
||||
param.pageSize = this.table.pagination.pageSize
|
||||
return filterObj(param)
|
||||
},
|
||||
handleChangeInTableSelect (selectedRowKeys, selectionRows) {
|
||||
// update-begin-author:taoyan date:2020902 for:【issue】开源online的几个问题 LOWCOD-844
|
||||
if (!selectedRowKeys || selectedRowKeys.length === 0) {
|
||||
this.table.selectionRows = []
|
||||
} else if (selectedRowKeys.length === selectionRows.length) {
|
||||
this.table.selectionRows = selectionRows
|
||||
} else {
|
||||
// 当两者长度不一的时候 需要判断
|
||||
const keys = this.table.selectedRowKeys
|
||||
const rows = this.table.selectionRows
|
||||
// 这个循环 添加新的记录
|
||||
for (let i = 0; i < selectionRows.length; i++) {
|
||||
const combineKey = this.combineRowKey(selectionRows[i])
|
||||
if (keys.indexOf(combineKey) < 0) {
|
||||
// 如果 原来的key 不包含当前记录 push
|
||||
rows.push(selectionRows[i])
|
||||
}
|
||||
}
|
||||
// 这个循环 移除取消选中的数据
|
||||
this.table.selectionRows = rows.filter(item => {
|
||||
const combineKey = this.combineRowKey(item)
|
||||
return selectedRowKeys.indexOf(combineKey) >= 0
|
||||
})
|
||||
}
|
||||
// update-end-author:taoyan date:2020902 for:【issue】开源online的几个问题 LOWCOD-844
|
||||
this.table.selectedRowKeys = selectedRowKeys
|
||||
},
|
||||
handleChangeInTable (pagination, filters, sorter) {
|
||||
// 分页、排序、筛选变化时触发
|
||||
if (Object.keys(sorter).length > 0) {
|
||||
this.iSorter = {
|
||||
column: sorter.field,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc'
|
||||
}
|
||||
// 排序字段受控
|
||||
this.table.columns.forEach(col => {
|
||||
if (col.dataIndex === sorter.field) {
|
||||
this.$set(col, 'sortOrder', sorter.order)
|
||||
} else {
|
||||
this.$set(col, 'sortOrder', false)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.table.pagination = pagination
|
||||
this.loadData()
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
handleSubmit () {
|
||||
if (!this.multi) {
|
||||
if (this.table.selectionRows && this.table.selectionRows.length > 1) {
|
||||
this.$message.warning('请选择一条记录')
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!this.table.selectionRows || this.table.selectionRows.length === 0) {
|
||||
this.$message.warning('请选择一条记录')
|
||||
return false
|
||||
}
|
||||
this.$emit('ok', this.table.selectionRows)
|
||||
this.close()
|
||||
},
|
||||
close () {
|
||||
this.$emit('close')
|
||||
this.visible = false
|
||||
this.onClearSelected()
|
||||
},
|
||||
show () {
|
||||
this.visible = true
|
||||
this.loadColumnsInfo()
|
||||
},
|
||||
handleToggleSearch () {
|
||||
this.toggleSearchStatus = !this.toggleSearchStatus
|
||||
},
|
||||
searchByquery () {
|
||||
this.loadData(1)
|
||||
},
|
||||
onlyReload () {
|
||||
this.loadData()
|
||||
},
|
||||
searchReset () {
|
||||
Object.keys(this.queryParam).forEach(key => {
|
||||
this.queryParam[key] = ''
|
||||
})
|
||||
this.loadData(1)
|
||||
},
|
||||
onClearSelected () {
|
||||
this.table.selectedRowKeys = []
|
||||
this.table.selectionRows = []
|
||||
},
|
||||
combineRowKey (record) {
|
||||
let res = ''
|
||||
Object.keys(record).forEach(key => {
|
||||
// update-begin---author:liusq Date:20210203 for:pop选择器列主键问题 issues/I29P9Q------------
|
||||
if (key + '' === 'id') {
|
||||
res = record[key] + res
|
||||
} else {
|
||||
res += record[key]
|
||||
}
|
||||
// update-end---author:liusq Date:20210203 for:pop选择器列主键问题 issues/I29P9Q------------
|
||||
})
|
||||
if (res.length > 50) {
|
||||
res = res.substring(0, 50)
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
||||
clickThenCheck (record) {
|
||||
return {
|
||||
on: {
|
||||
click: () => {
|
||||
const rowKey = this.combineRowKey(record)
|
||||
if (!this.table.selectedRowKeys || this.table.selectedRowKeys.length === 0) {
|
||||
const arr1 = []; const arr2 = []
|
||||
arr1.push(record)
|
||||
arr2.push(rowKey)
|
||||
this.table.selectedRowKeys = arr2
|
||||
this.table.selectionRows = arr1
|
||||
} else {
|
||||
if (this.table.selectedRowKeys.indexOf(rowKey) < 0) {
|
||||
this.table.selectedRowKeys.push(rowKey)
|
||||
this.table.selectionRows.push(record)
|
||||
} else {
|
||||
const rowKeyIndex = this.table.selectedRowKeys.indexOf(rowKey)
|
||||
this.table.selectedRowKeys.splice(rowKeyIndex, 1)
|
||||
this.table.selectionRows.splice(rowKeyIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 防止字典中有垃圾数据
|
||||
initDictOptionData (dictOptions) {
|
||||
const obj = { }
|
||||
Object.keys(dictOptions).map(k => {
|
||||
obj[k] = dictOptions[k].filter(item => {
|
||||
return item != null
|
||||
})
|
||||
})
|
||||
this.dictOptions = obj
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,387 @@
|
||||
<template>
|
||||
<j-modal
|
||||
centered
|
||||
:title="name + '选择'"
|
||||
:width="width"
|
||||
:visible="visible"
|
||||
switchFullscreen
|
||||
@ok="handleOk"
|
||||
@cancel="close"
|
||||
cancelText="关闭">
|
||||
|
||||
<a-row :gutter="18">
|
||||
<a-col :span="16">
|
||||
<!-- 查询区域 -->
|
||||
<a-form layout="inline" class="j-inline-form">
|
||||
<!-- 固定条件 -->
|
||||
<a-form-item :label="(queryParamText||name)">
|
||||
<j-input v-model="queryParam[queryParamCode||valueKey]" :placeholder="'请输入' + (queryParamText||name)" @pressEnter="searchQuery"/>
|
||||
</a-form-item>
|
||||
<!-- 动态生成的查询条件 -->
|
||||
<j-select-biz-query-item v-if="queryConfig.length>0" v-show="showMoreQueryItems" :queryParam="queryParam" :queryConfig="queryConfig" @pressEnter="searchQuery"/>
|
||||
<!-- 按钮 -->
|
||||
<a-button :style="{marginBottom:'12px'}" type="primary" @click="searchQuery" icon="search">查询</a-button>
|
||||
<a-button :style="{marginBottom:'12px'}" type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
|
||||
<a v-if="queryConfig.length>0" @click="showMoreQueryItems=!showMoreQueryItems" style="margin-left: 8px">
|
||||
{{ showMoreQueryItems ? '收起' : '展开' }}
|
||||
<a-icon :type="showMoreQueryItems ? 'up' : 'down'"/>
|
||||
</a>
|
||||
</a-form>
|
||||
|
||||
<a-table
|
||||
size="middle"
|
||||
bordered
|
||||
:rowKey="rowKey"
|
||||
:columns="innerColumns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="ipagination"
|
||||
:loading="loading"
|
||||
:scroll="{ y: 240 }"
|
||||
:rowSelection="{selectedRowKeys, onChange: onSelectChange, type: multiple ? 'checkbox':'radio'}"
|
||||
:customRow="customRowFn"
|
||||
@change="handleTableChange">
|
||||
</a-table>
|
||||
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-card :title="'已选' + name" :bordered="false" :head-style="{padding:0}" :body-style="{padding:0}">
|
||||
|
||||
<a-table size="middle" :rowKey="rowKey" bordered v-bind="selectedTable">
|
||||
<span slot="action" slot-scope="text, record, index">
|
||||
<a @click="handleDeleteSelected(record, index)">删除</a>
|
||||
</span>
|
||||
</a-table>
|
||||
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
import Ellipsis from '@/components/Ellipsis'
|
||||
import { JeroListMixin } from '@/mixins/JeroListMixin'
|
||||
import { pushIfNotExist } from '@/utils/util'
|
||||
import JSelectBizQueryItem from './JSelectBizQueryItem'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
export default {
|
||||
name: 'JSelectBizComponentModal',
|
||||
mixins: [JeroListMixin],
|
||||
components: { Ellipsis, JSelectBizQueryItem },
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 900
|
||||
},
|
||||
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
listUrl: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
// 根据 value 获取显示文本的地址,例如存的是 username,可以通过该地址获取到 realname
|
||||
valueUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
displayKey: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
// 查询条件Code
|
||||
queryParamCode: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 查询条件文字
|
||||
queryParamText: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 查询配置
|
||||
queryConfig: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
},
|
||||
// 过长裁剪长度,设置为 -1 代表不裁剪
|
||||
ellipsisLength: {
|
||||
type: Number,
|
||||
default: 12
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
innerValue: [],
|
||||
// 已选择列表
|
||||
selectedTable: {
|
||||
pagination: false,
|
||||
scroll: { y: 240 },
|
||||
columns: [
|
||||
{
|
||||
...this.columns[0],
|
||||
width: this.columns[0].widthRight || this.columns[0].width
|
||||
},
|
||||
{ title: '操作', dataIndex: 'action', align: 'center', width: 60, scopedSlots: { customRender: 'action' } }
|
||||
],
|
||||
dataSource: []
|
||||
},
|
||||
renderEllipsis: (value) => (<ellipsis length={this.ellipsisLength}>{value}</ellipsis>),
|
||||
url: { list: this.listUrl },
|
||||
/* 分页参数 */
|
||||
ipagination: {
|
||||
current: 1,
|
||||
pageSize: 5,
|
||||
pageSizeOptions: ['5', '10', '20', '30'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' 共' + total + '条'
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 0
|
||||
},
|
||||
options: [],
|
||||
dataSourceMap: {},
|
||||
showMoreQueryItems: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 表头
|
||||
innerColumns () {
|
||||
const columns = cloneDeep(this.columns)
|
||||
columns.forEach(column => {
|
||||
// 给所有的列加上过长裁剪
|
||||
if (this.ellipsisLength !== -1) {
|
||||
const myCustomRender = column.customRender
|
||||
column.customRender = (text, record, index) => {
|
||||
let value = text
|
||||
if (typeof myCustomRender === 'function') {
|
||||
// noinspection JSVoidFunctionReturnValueUsed
|
||||
value = myCustomRender(text, record, index)
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return this.renderEllipsis(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
})
|
||||
return columns
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
this.innerValue = cloneDeep(val)
|
||||
this.selectedRowKeys = []
|
||||
this.valueWatchHandler(val)
|
||||
this.queryOptionsByValue(val)
|
||||
}
|
||||
},
|
||||
dataSource: {
|
||||
deep: true,
|
||||
handler (val) {
|
||||
this.emitOptions(val)
|
||||
this.valueWatchHandler(this.innerValue)
|
||||
}
|
||||
},
|
||||
selectedRowKeys: {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
handler (val) {
|
||||
// update--begin--autor:scott-----date:20200927------for:选取职务名称出现全选 #1753-----
|
||||
if (this.innerValue) {
|
||||
this.innerValue.length = 0
|
||||
}
|
||||
// update--end--autor:scott-----date:20200927------for:选取职务名称出现全选 #1753-----
|
||||
this.selectedTable.dataSource = val.map(key => {
|
||||
for (const data of this.dataSource) {
|
||||
if (data[this.rowKey] === key) {
|
||||
pushIfNotExist(this.innerValue, data[this.valueKey])
|
||||
return data
|
||||
}
|
||||
}
|
||||
for (const data of this.selectedTable.dataSource) {
|
||||
if (data[this.rowKey] === key) {
|
||||
pushIfNotExist(this.innerValue, data[this.valueKey])
|
||||
return data
|
||||
}
|
||||
}
|
||||
console.warn('未找到选择的行信息,key:' + key)
|
||||
return {}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
/** 关闭弹窗 */
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
valueWatchHandler (val) {
|
||||
val.forEach(item => {
|
||||
this.dataSource.concat(this.selectedTable.dataSource).forEach(data => {
|
||||
if (data[this.valueKey] === item) {
|
||||
pushIfNotExist(this.selectedRowKeys, data[this.rowKey])
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
queryOptionsByValue (value) {
|
||||
if (!value || value.length === 0) {
|
||||
return
|
||||
}
|
||||
// 判断options是否存在value,如果已存在数据就不再请求后台了
|
||||
let notExist = false
|
||||
for (const val of value) {
|
||||
let find = false
|
||||
for (const option of this.options) {
|
||||
if (val === option.value) {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!find) {
|
||||
notExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!notExist) return
|
||||
getAction(this.valueUrl || this.listUrl, {
|
||||
// 这里最后加一个 , 的原因是无论如何都要使用 in 查询,防止后台进行了模糊匹配,导致查询结果不准确
|
||||
[this.valueKey]: value.join(',') + ',',
|
||||
pageNo: 1,
|
||||
pageSize: value.length
|
||||
}).then((res) => {
|
||||
if (res.success) {
|
||||
let dataSource = res.result
|
||||
if (!(dataSource instanceof Array)) {
|
||||
dataSource = res.result.records
|
||||
}
|
||||
this.emitOptions(dataSource, (data) => {
|
||||
pushIfNotExist(this.innerValue, data[this.valueKey])
|
||||
pushIfNotExist(this.selectedRowKeys, data[this.rowKey])
|
||||
pushIfNotExist(this.selectedTable.dataSource, data, this.rowKey)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
emitOptions (dataSource, callback) {
|
||||
dataSource.forEach(data => {
|
||||
const key = data[this.valueKey]
|
||||
this.dataSourceMap[key] = data
|
||||
pushIfNotExist(this.options, { label: data[this.displayKey || this.valueKey], value: key }, 'value')
|
||||
// typeof callback === 'function' ? callback(data) : ''
|
||||
if (typeof callback === 'function') {
|
||||
callback(data)
|
||||
}
|
||||
})
|
||||
this.$emit('options', this.options, this.dataSourceMap)
|
||||
},
|
||||
|
||||
/** 完成选择 */
|
||||
handleOk () {
|
||||
const value = this.selectedTable.dataSource.map(data => data[this.valueKey])
|
||||
this.$emit('input', value)
|
||||
this.close()
|
||||
},
|
||||
/** 删除已选择的 */
|
||||
handleDeleteSelected (record/* , index */) {
|
||||
this.selectedRowKeys.splice(this.selectedRowKeys.indexOf(record[this.rowKey]), 1)
|
||||
// update--begin--autor:wangshuai-----date:20200722------for:JSelectBizComponent组件切换页数值问题------
|
||||
this.selectedTable.dataSource.splice(this.selectedTable.dataSource.indexOf(record), 1)
|
||||
this.innerValue.splice(this.innerValue.indexOf(record[this.valueKey]), 1)
|
||||
console.log('this.selectedRowKeys:', this.selectedRowKeys)
|
||||
console.log('this.selectedTable.dataSource:', this.selectedTable.dataSource)
|
||||
// update--begin--autor:wangshuai-----date:20200722------for:JSelectBizComponent组件切换页数值问题------
|
||||
},
|
||||
|
||||
customRowFn (record) {
|
||||
return {
|
||||
on: {
|
||||
click: () => {
|
||||
const key = record[this.rowKey]
|
||||
if (!this.multiple) {
|
||||
this.selectedRowKeys = [key]
|
||||
this.selectedTable.dataSource = [record]
|
||||
} else {
|
||||
const index = this.selectedRowKeys.indexOf(key)
|
||||
if (index === -1) {
|
||||
this.selectedRowKeys.push(key)
|
||||
this.selectedTable.dataSource.push(record)
|
||||
} else {
|
||||
this.handleDeleteSelected(record, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.full-form-item {
|
||||
display: flex;
|
||||
margin-right: 0;
|
||||
|
||||
/deep/ .ant-form-item-control-wrapper {
|
||||
flex: 1 1;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.j-inline-form {
|
||||
/deep/ .ant-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/deep/ .ant-form-item-label {
|
||||
line-height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/deep/ .ant-form-item-control {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
export default {
|
||||
name: 'JSelectBizQueryItem',
|
||||
props: {
|
||||
queryParam: Object,
|
||||
queryConfig: Array
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
renderQueryItem () {
|
||||
return this.queryConfig.map(queryItem => {
|
||||
const { key, label, queryModel, placeholder, dictCode, props, customRender } = queryItem
|
||||
const options = {
|
||||
props: {},
|
||||
on: {
|
||||
pressEnter: () => this.$emit('pressEnter')
|
||||
}
|
||||
}
|
||||
if (props != null) {
|
||||
Object.assign(options.props, props)
|
||||
}
|
||||
if (placeholder === undefined) {
|
||||
if (dictCode) {
|
||||
options.props.placeholder = `请选择${label}`
|
||||
} else {
|
||||
options.props.placeholder = `请输入${label}`
|
||||
}
|
||||
} else {
|
||||
options.props.placeholder = placeholder
|
||||
}
|
||||
|
||||
let input
|
||||
if (typeof customRender === 'function') {
|
||||
input = customRender.call(this, { key, options, queryParam: this.queryParam })
|
||||
} else if (dictCode) {
|
||||
input = <j-dict-select-tag {...options} vModel={this.queryParam[key]} dictCode={dictCode} style="width:180px;"/>
|
||||
} else if (queryModel && queryModel === 'eq') {
|
||||
input = <a-input {...options} vModel={this.queryParam[key]}/>
|
||||
} else {
|
||||
input = <j-input {...options} vModel={this.queryParam[key]}/>
|
||||
}
|
||||
return <a-form-item key={key} label={label}>{input}</a-form-item>
|
||||
})
|
||||
}
|
||||
},
|
||||
render () {
|
||||
return <span>{this.renderQueryItem()}</span>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# JSelectBizComponent
|
||||
|
||||
Jero 选择组件的公共可复用组件
|
||||
|
||||
## 引用方式
|
||||
|
||||
```js
|
||||
import JSelectBizComponent from '@/src/components/jerobiz/JSelectBizComponent'
|
||||
|
||||
export default {
|
||||
components: { JSelectBizComponent }
|
||||
}
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
### 配置参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 备注 |
|
||||
|-----------------------|---------|------|--------------|--------------------------------------------------------------------------------------|
|
||||
| rowKey | String | | "id" | 唯一标识的字段名 |
|
||||
| value(v-model) | String | | "" | 默认选择的数据,多个用半角逗号分割 |
|
||||
| name | String | | "" | 显示名字,例如选择用户就填写"用户" |
|
||||
| listUrl | String | 是 | | 数据请求地址,必须是封装了分页的地址 |
|
||||
| valueUrl | String | | "" | 获取显示文本的地址,例如存的是 username,可以通过该地址获取到 realname |
|
||||
| displayKey | String | | null | 显示在标签上的字段 key ,不传则直接显示数据 |
|
||||
| returnKeys | Array | | ['id', 'id'] | v-model 绑定的 keys,是个数组,默认使用第二项,当配置了 `returnId=true` 就返回第一项 |
|
||||
| returnId | Boolean | | false | 返回ID,设为true后将返回配置的 `returnKeys` 中的第一项 |
|
||||
| selectButtonText | String | | "选择" | 选择按钮的文字 |
|
||||
| queryParamText | String | | null | 查询条件显示文字,不传则使用 `name` |
|
||||
| columns | Array | 是 | | 列配置项,与antd的table的配置完全一致。列的第一项会被配置成右侧已选择的列表上 |
|
||||
| columns[0].widthRight | String | | null | 仅列的第一项可以应用此配置,表示右侧已选择列表的宽度,建议 `70%`,不传则应用`width` |
|
||||
| placeholder | String | | "请选择" | 占位符 |
|
||||
| disabled | Boolean | | false | 是否禁用 |
|
||||
| multiple | Boolean | | false | 是否可多选 |
|
||||
| buttons | Boolean | | true | 是否显示"选择"按钮,如果不显示,可以直接点击文本框打开选择界面 |
|
||||
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<a-row class="j-select-biz-component-box" type="flex" :gutter="8">
|
||||
<a-col class="left" :class="{'full': !buttons}">
|
||||
<slot name="left">
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
v-model="selectValue"
|
||||
:options="selectOptions"
|
||||
allowClear
|
||||
:disabled="disabled"
|
||||
:open="selectOpen"
|
||||
style="width: 100%;"
|
||||
@dropdownVisibleChange="handleDropdownVisibleChange"
|
||||
@click.native="visible=(buttons || disabled ?visible:true)"
|
||||
/>
|
||||
</slot>
|
||||
</a-col>
|
||||
|
||||
<a-col v-if="buttons" class="right">
|
||||
<a-button type="primary" icon="search" :disabled="disabled" @click="visible=true">{{selectButtonText}}</a-button>
|
||||
</a-col>
|
||||
|
||||
<j-select-biz-component-modal
|
||||
v-model="selectValue"
|
||||
:visible.sync="visible"
|
||||
v-bind="modalProps"
|
||||
@options="handleOptions"
|
||||
/>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JSelectBizComponentModal from './JSelectBizComponentModal'
|
||||
|
||||
export default {
|
||||
name: 'JSelectBizComponent',
|
||||
components: { JSelectBizComponentModal },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/** 是否返回 id,默认 false,返回 code */
|
||||
returnId: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否支持多选,默认 true
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示按钮,默认 true
|
||||
buttons: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 显示的 Key
|
||||
displayKey: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 返回的 key
|
||||
returnKeys: {
|
||||
type: Array,
|
||||
default: () => ['id', 'id']
|
||||
},
|
||||
// 选择按钮文字
|
||||
selectButtonText: {
|
||||
type: String,
|
||||
default: '选择'
|
||||
}
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selectValue: [],
|
||||
selectOptions: [],
|
||||
dataSourceMap: {},
|
||||
visible: false,
|
||||
selectOpen: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valueKey () {
|
||||
return this.returnId ? this.returnKeys[0] : this.returnKeys[1]
|
||||
},
|
||||
modalProps () {
|
||||
return Object.assign({
|
||||
valueKey: this.valueKey,
|
||||
multiple: this.multiple,
|
||||
returnKeys: this.returnKeys,
|
||||
displayKey: this.displayKey || this.valueKey
|
||||
}, this.$attrs)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (val) {
|
||||
this.selectValue = val.split(',')
|
||||
} else {
|
||||
this.selectValue = []
|
||||
}
|
||||
}
|
||||
},
|
||||
selectValue: {
|
||||
deep: true,
|
||||
handler (val) {
|
||||
const rows = val.map(key => this.dataSourceMap[key])
|
||||
const data = val.join(',')
|
||||
if (data !== this.value) {
|
||||
this.$emit('select', rows)
|
||||
this.$emit('input', data)
|
||||
this.$emit('change', data)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleOptions (options, dataSourceMap) {
|
||||
this.selectOptions = options
|
||||
this.dataSourceMap = dataSourceMap
|
||||
},
|
||||
handleDropdownVisibleChange () {
|
||||
// 解决antdv自己的bug —— open 设置为 false 了,点击后还是添加了 open 样式,导致点击事件失效
|
||||
this.selectOpen = true
|
||||
this.$nextTick(() => {
|
||||
this.selectOpen = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.j-select-biz-component-box {
|
||||
|
||||
@width: 82px;
|
||||
|
||||
.left {
|
||||
//width: calc(100% - @width - 8px);
|
||||
width: calc(100% - @width);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: @width;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .ant-select-search__field {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user