init
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="j-area-linkage">
|
||||
<div v-if="reloading">
|
||||
<span> Reloading... </span>
|
||||
</div>
|
||||
<area-cascader
|
||||
v-else-if="_type === enums.type[0]"
|
||||
:value="innerValue"
|
||||
:data="pcaa"
|
||||
:level="1"
|
||||
:style="{width}"
|
||||
v-bind="$attrs"
|
||||
v-on="_listeners"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<area-select
|
||||
v-else-if="_type === enums.type[1]"
|
||||
:value="innerValue"
|
||||
:data="pcaa"
|
||||
:level="2"
|
||||
v-bind="$attrs"
|
||||
v-on="_listeners"
|
||||
@change="handleChange"
|
||||
/>
|
||||
<div v-else>
|
||||
<span style="color:red;"> Bad type value: {{ _type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Area from '@/components/_util/Area'
|
||||
|
||||
export default {
|
||||
name: 'JAreaLinkage',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
// 组件的类型,可选值:
|
||||
// select 下拉样式
|
||||
// cascader 级联样式(默认)
|
||||
type: {
|
||||
type: String,
|
||||
default: 'cascader'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '100%'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
pcaa: this.$Jpcaa,
|
||||
innerValue: [],
|
||||
usedListeners: ['change'],
|
||||
enums: {
|
||||
type: ['cascader', 'select']
|
||||
},
|
||||
reloading: false,
|
||||
areaData: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_listeners () {
|
||||
const listeners = { ...this.$listeners }
|
||||
// 去掉已使用的事件,防止冲突
|
||||
this.usedListeners.forEach(key => {
|
||||
delete listeners[key]
|
||||
})
|
||||
return listeners
|
||||
},
|
||||
_type () {
|
||||
if (this.enums.type.includes(this.type)) {
|
||||
return this.type
|
||||
} else {
|
||||
console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')})`)
|
||||
return this.enums.type[0]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.loadDataByValue(this.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initAreaData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 重新加载组件 */
|
||||
reload () {
|
||||
this.reloading = true
|
||||
this.$nextTick(() => {
|
||||
this.reloading = false
|
||||
})
|
||||
},
|
||||
|
||||
/** 通过 value 反推 options */
|
||||
loadDataByValue (value) {
|
||||
if (!value || value.length === 0) {
|
||||
this.innerValue = []
|
||||
} else {
|
||||
this.initAreaData()
|
||||
this.innerValue = this.areaData.getRealCode(value)
|
||||
}
|
||||
this.reload()
|
||||
},
|
||||
/** 通过地区code获取子级 */
|
||||
loadDataByCode (value) {
|
||||
const options = []
|
||||
const data = this.pcaa[value]
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
||||
options.push({ value: key, label: data[key] })
|
||||
}
|
||||
}
|
||||
return options
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/** 判断是否有子节点 */
|
||||
hasChildren (options) {
|
||||
options.forEach(option => {
|
||||
const data = this.loadDataByCode(option.value)
|
||||
option.isLeaf = data.length === 0
|
||||
})
|
||||
},
|
||||
handleChange (values) {
|
||||
const value = values[values.length - 1]
|
||||
this.$emit('change', value)
|
||||
},
|
||||
initAreaData () {
|
||||
if (!this.areaData) {
|
||||
this.areaData = new Area(this.$Jpcaa)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
model: { prop: 'value', event: 'change' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.j-area-linkage {
|
||||
height: 40px;
|
||||
|
||||
/deep/ .area-cascader-wrap .area-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .area-select .area-selected-trigger {
|
||||
line-height: 1.15;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JCategorySelect',
|
||||
props: {
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadTriggleChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
pid: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
back: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: '',
|
||||
treeData: [],
|
||||
url: '/sys/category/loadTreeData',
|
||||
view: '/sys/category/loadDictItem/',
|
||||
tableName: '',
|
||||
text: '',
|
||||
code: ''
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
pCode () {
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.validateProp().then(() => {
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/** 加载一级节点 */
|
||||
loadRoot () {
|
||||
const param = {
|
||||
pid: this.pid,
|
||||
pcode: !this.pCode ? '0' : this.pCode,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success && res.result) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
console.log('树一级节点查询结果-else', res)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 数据回显 */
|
||||
loadItemByCode () {
|
||||
if (!this.value || this.value === '0') {
|
||||
this.treeValue = []
|
||||
} else {
|
||||
getAction(this.view, { ids: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
if (res.result && res.result.length > 0) {
|
||||
const values = this.value.split(',')
|
||||
this.treeValue = res.result.map((item, index) => ({
|
||||
key: values[index],
|
||||
value: values[index],
|
||||
label: item
|
||||
}))
|
||||
this.onLoadTriggleChange(res.result[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoadTriggleChange (text) {
|
||||
// 只有单选才会触发
|
||||
if (!this.multiple && this.loadTriggleChange) {
|
||||
this.backValue(this.value, text)
|
||||
}
|
||||
},
|
||||
backValue (value, label) {
|
||||
const obj = {}
|
||||
if (this.back) {
|
||||
obj[this.back] = label
|
||||
}
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', value, obj)
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') {
|
||||
if (!children || children.length === 0) {
|
||||
item.isLeaf = true
|
||||
} else {
|
||||
item.children = children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
this.treeValue = ''
|
||||
} else if (Array.isArray(value)) {
|
||||
const labels = []
|
||||
const values = value.map(item => {
|
||||
labels.push(item.label)
|
||||
return item.value
|
||||
})
|
||||
this.backValue(values.join(','), labels.join(','))
|
||||
this.treeValue = value
|
||||
} else {
|
||||
this.backValue(value.value, value.label)
|
||||
this.treeValue = value
|
||||
}
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
},
|
||||
validateProp () {
|
||||
const myCondition = this.condition
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!myCondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
const test = JSON.parse(myCondition)
|
||||
if (typeof test === 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<a-checkbox-group :options="options"
|
||||
:value="checkboxArray"
|
||||
v-bind="$attrs"
|
||||
@change="onChange" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JCheckbox',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
/* label value */
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checkboxArray: !this.value ? [] : this.value.split(',')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.checkboxArray = []
|
||||
} else {
|
||||
this.checkboxArray = this.value.split(',')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (checkedValues) {
|
||||
this.$emit('change', checkedValues.join(','))
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<div v-bind="fullScreenParentProps">
|
||||
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder" />
|
||||
|
||||
<div class="code-editor-cust full-screen-child">
|
||||
<textarea ref="textarea"></textarea>
|
||||
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
|
||||
<template v-if="languageChange">
|
||||
<a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
|
||||
<a-select-option
|
||||
v-for="mode in modes"
|
||||
:key="mode.value"
|
||||
:value="mode.value">
|
||||
{{ mode.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/ecmascript-6">
|
||||
// 引入全局实例
|
||||
import _CodeMirror from 'codemirror'
|
||||
|
||||
// 核心样式
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
// 引入主题后还需要在 options 中指定主题才会生效 darcula gruvbox-dark hopscotch monokai
|
||||
import 'codemirror/theme/panda-syntax.css'
|
||||
// 提示css
|
||||
import 'codemirror/addon/hint/show-hint.css'
|
||||
|
||||
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
||||
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
|
||||
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/css/css.js'
|
||||
import 'codemirror/mode/xml/xml.js'
|
||||
import 'codemirror/mode/clike/clike.js'
|
||||
import 'codemirror/mode/markdown/markdown.js'
|
||||
import 'codemirror/mode/python/python.js'
|
||||
import 'codemirror/mode/r/r.js'
|
||||
import 'codemirror/mode/shell/shell.js'
|
||||
import 'codemirror/mode/sql/sql.js'
|
||||
import 'codemirror/mode/swift/swift.js'
|
||||
import 'codemirror/mode/vue/vue.js'
|
||||
|
||||
import { isIE11, isIE } from '@/utils/browser'
|
||||
|
||||
// 尝试获取全局实例
|
||||
const CodeMirror = window.CodeMirror || _CodeMirror
|
||||
|
||||
export default {
|
||||
name: 'JCodeEditor',
|
||||
props: {
|
||||
// 外部传入的内容,用于实现双向绑定
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外部传入的语法类型
|
||||
language: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
languageChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 显示行号
|
||||
lineNumbers: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 全屏以后的z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
// 是否自适应高度,可以传String或Boolean
|
||||
// 传 String 类型只能写"!ie" ,
|
||||
// 填写这个字符串,代表其他浏览器自适应高度
|
||||
// 唯独IE下不自适应高度,因为IE下不支持min、max-height样式
|
||||
// 如果填写的不是"!ie"就视为true
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 不自适应高度的情况下生效的固定高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '240px'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部真实的内容
|
||||
code: '',
|
||||
iconType: 'fullscreen',
|
||||
hasCode: false,
|
||||
// 默认的语法类型
|
||||
mode: 'javascript',
|
||||
// 编辑器实例
|
||||
coder: null,
|
||||
// 默认配置
|
||||
options: {
|
||||
// 缩进格式
|
||||
tabSize: 2,
|
||||
// 主题,对应主题库 JS 需要提前引入
|
||||
theme: 'panda-syntax',
|
||||
line: true,
|
||||
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
|
||||
hintOptions: {
|
||||
tables: {
|
||||
users: ['name', 'score', 'birthDate'],
|
||||
countries: ['name', 'population', 'size']
|
||||
}
|
||||
}
|
||||
},
|
||||
// 支持切换的语法高亮类型,对应 JS 已经提前引入
|
||||
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
|
||||
modes: [{
|
||||
value: 'css',
|
||||
label: 'CSS'
|
||||
}, {
|
||||
value: 'javascript',
|
||||
label: 'Javascript'
|
||||
}, {
|
||||
value: 'html',
|
||||
label: 'XML/HTML'
|
||||
}, {
|
||||
value: 'x-java',
|
||||
label: 'Java'
|
||||
}, {
|
||||
value: 'x-objectivec',
|
||||
label: 'Objective-C'
|
||||
}, {
|
||||
value: 'x-python',
|
||||
label: 'Python'
|
||||
}, {
|
||||
value: 'x-rsrc',
|
||||
label: 'R'
|
||||
}, {
|
||||
value: 'x-sh',
|
||||
label: 'Shell'
|
||||
}, {
|
||||
value: 'x-sql',
|
||||
label: 'SQL'
|
||||
}, {
|
||||
value: 'x-swift',
|
||||
label: 'Swift'
|
||||
}, {
|
||||
value: 'x-vue',
|
||||
label: 'Vue'
|
||||
}, {
|
||||
value: 'markdown',
|
||||
label: 'Markdown'
|
||||
}],
|
||||
// code 编辑器 是否全屏
|
||||
fullCoder: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fullCoder: {
|
||||
handler (value) {
|
||||
if (value) {
|
||||
this.iconType = 'fullscreen-exit'
|
||||
} else {
|
||||
this.iconType = 'fullscreen'
|
||||
}
|
||||
}
|
||||
},
|
||||
// value: {
|
||||
// immediate: false,
|
||||
// handler(value) {
|
||||
// this._getCoder().then(() => {
|
||||
// this.coder.setValue(value)
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
language: {
|
||||
immediate: true,
|
||||
handler (language) {
|
||||
this._getCoder().then(() => {
|
||||
// 尝试从父容器获取语法类型
|
||||
if (language) {
|
||||
// 获取具体的语法类型对象
|
||||
const modeObj = this._getLanguage(language)
|
||||
|
||||
// 判断父容器传入的语法是否被支持
|
||||
if (modeObj) {
|
||||
this.mode = modeObj.label
|
||||
this.coder.setOption('mode', `text/${modeObj.value}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholderShow () {
|
||||
if (this.placeholder == null) {
|
||||
return `请在此输入${this.language}代码`
|
||||
} else {
|
||||
return this.placeholder
|
||||
}
|
||||
},
|
||||
nullTipStyle () {
|
||||
if (this.lineNumbers) {
|
||||
return { left: '36px' }
|
||||
} else {
|
||||
return { left: '12px' }
|
||||
}
|
||||
},
|
||||
// coder 配置
|
||||
coderOptions () {
|
||||
return {
|
||||
tabSize: this.options.tabSize,
|
||||
theme: this.options.theme,
|
||||
lineNumbers: this.lineNumbers,
|
||||
line: true,
|
||||
hintOptions: this.options.hintOptions
|
||||
}
|
||||
},
|
||||
isAutoHeight () {
|
||||
let { autoHeight } = this
|
||||
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
|
||||
autoHeight = !(isIE() || isIE11())
|
||||
} else {
|
||||
autoHeight = true
|
||||
}
|
||||
return autoHeight
|
||||
},
|
||||
fullScreenParentProps () {
|
||||
const props = {
|
||||
class: {
|
||||
'full-screen-parent': true,
|
||||
'full-screen': this.fullCoder,
|
||||
'auto-height': this.isAutoHeight
|
||||
},
|
||||
style: {}
|
||||
}
|
||||
if (isIE() || isIE11()) {
|
||||
props.style.height = '240px'
|
||||
}
|
||||
if (this.fullCoder) {
|
||||
props.style['z-index'] = this.zIndex
|
||||
}
|
||||
if (!this.isAutoHeight) {
|
||||
props.style.height = (typeof this.height === 'number' ? this.height + 'px' : this.height)
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 初始化
|
||||
this._initialize()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
_initialize () {
|
||||
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
|
||||
this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
|
||||
// 编辑器赋值
|
||||
if (this.value || this.code) {
|
||||
this.hasCode = true
|
||||
// this.coder.setValue(this.value || this.code)
|
||||
this.setCodeContent(this.value || this.code)
|
||||
} else {
|
||||
this.coder.setValue('')
|
||||
this.hasCode = false
|
||||
}
|
||||
// 支持双向绑定
|
||||
this.coder.on('change', (coder) => {
|
||||
this.code = coder.getValue()
|
||||
this.hasCode = !!this.code
|
||||
if (this.$emit) {
|
||||
this.$emit('input', this.code)
|
||||
}
|
||||
})
|
||||
this.coder.on('focus', () => {
|
||||
this.hasCode = true
|
||||
})
|
||||
this.coder.on('blur', () => {
|
||||
this.hasCode = !!this.code
|
||||
})
|
||||
|
||||
/* this.coder.on('cursorActivity',()=>{
|
||||
this.coder.showHint()
|
||||
}) */
|
||||
},
|
||||
getCodeContent () {
|
||||
return this.code
|
||||
},
|
||||
setCodeContent (val) {
|
||||
setTimeout(() => {
|
||||
if (!val) {
|
||||
this.coder.setValue('')
|
||||
} else {
|
||||
this.coder.setValue(val)
|
||||
}
|
||||
}, 300)
|
||||
},
|
||||
// 获取当前语法类型
|
||||
_getLanguage (language) {
|
||||
// 在支持的语法类型列表中寻找传入的语法类型
|
||||
return this.modes.find((mode) => {
|
||||
// 所有的值都忽略大小写,方便比较
|
||||
const currentLanguage = language.toLowerCase()
|
||||
const currentLabel = mode.label.toLowerCase()
|
||||
const currentValue = mode.value.toLowerCase()
|
||||
|
||||
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
||||
return currentLabel === currentLanguage || currentValue === currentLanguage
|
||||
})
|
||||
},
|
||||
_getCoder () {
|
||||
const _this = this
|
||||
return new Promise((resolve) => {
|
||||
(function get () {
|
||||
if (_this.coder) {
|
||||
resolve(_this.coder)
|
||||
} else {
|
||||
setTimeout(get, 10)
|
||||
}
|
||||
})()
|
||||
})
|
||||
},
|
||||
// 更改模式
|
||||
changeMode (val) {
|
||||
// 修改编辑器的语法配置
|
||||
this.coder.setOption('mode', `text/${val}`)
|
||||
|
||||
// 获取修改后的语法
|
||||
const label = this._getLanguage(val).label.toLowerCase()
|
||||
|
||||
// 允许父容器通过以下函数监听当前的语法值
|
||||
this.$emit('language-change', label)
|
||||
},
|
||||
nullTipClick () {
|
||||
this.coder.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.code-editor-cust {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.CodeMirror {
|
||||
flex-grow: 1;
|
||||
z-index: 1;
|
||||
|
||||
.CodeMirror-code {
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.code-mode-select {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
.CodeMirror {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.null-tip {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 36px;
|
||||
z-index: 10;
|
||||
color: #ffffffc9;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.null-tip-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**选中样式偶然出现高度不够的情况*/
|
||||
|
||||
.CodeMirror-selected {
|
||||
min-height: 19px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全屏样式 */
|
||||
.full-screen-parent {
|
||||
position: relative;
|
||||
|
||||
.full-screen-icon {
|
||||
opacity: 0;
|
||||
color: black;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.full-screen-icon {
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.full-screen-icon {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.auto-height {
|
||||
.full-screen-child {
|
||||
min-height: 120px;
|
||||
max-height: 320px;
|
||||
height: unset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.full-screen .full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
height: 18.4px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div class="components-input-demo-presuffix">
|
||||
<a-input @click="openModal" placeholder="cron表达式" v-model="cron" @change="(e)=>handleOK(e.target.value)">
|
||||
<a-icon slot="prefix" type="schedule" title="cron控件"/>
|
||||
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</a-input>
|
||||
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import JCronModal from './modal/JCronModal'
|
||||
export default {
|
||||
name: 'JCron',
|
||||
components: {
|
||||
JCronModal
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
required: false,
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
cron: this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.cron = val
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
this.$refs.innerVueCron.show()
|
||||
},
|
||||
handleOK (val) {
|
||||
this.cron = val
|
||||
this.$emit('change', this.cron)
|
||||
// this.$emit("change", Object.assign({}, this.cron));
|
||||
},
|
||||
handleEmpty () {
|
||||
this.handleOK('')
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.components-input-demo-presuffix .anticon-close-circle {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
transition: color 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
.components-input-demo-presuffix .anticon-close-circle:hover {
|
||||
color: #f5222d;
|
||||
}
|
||||
.components-input-demo-presuffix .anticon-close-circle:active {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<span>
|
||||
<!--YYYY-MM-DD HH:mm:ss-->
|
||||
<a-date-picker
|
||||
v-if="showType === 'time'"
|
||||
dropdownClassName="j-date-picker"
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
@change="handleDateChange"
|
||||
:value="momVal"
|
||||
:showTime="true"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer">
|
||||
</a-date-picker>
|
||||
<!--YYYY-MM-DD-->
|
||||
<a-date-picker
|
||||
v-if="showType === 'day'"
|
||||
dropdownClassName="j-date-picker"
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
@change="handleDateChange"
|
||||
:value="momVal"
|
||||
:showTime="false"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer">
|
||||
</a-date-picker>
|
||||
<!--YYYY-MM-->
|
||||
<a-month-picker
|
||||
v-if="showType === 'month'"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:disabled="disabled || readOnly"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
@change="handleMonthChange">
|
||||
</a-month-picker>
|
||||
<!--YYYY-->
|
||||
<a-date-picker
|
||||
v-if="showType === 'year'"
|
||||
:placeholder="placeholder"
|
||||
mode="year"
|
||||
:format="dateFormat"
|
||||
:value="year"
|
||||
:disabled="disabled || readOnly"
|
||||
:open="yearShowOne"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
@openChange="openChangeOne"
|
||||
@panelChange="panelChangeOne">
|
||||
</a-date-picker>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'JDate',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'YYYY-MM-DD',
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// 控制选择时分秒
|
||||
showType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'time'
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const dateStr = this.value
|
||||
return {
|
||||
yearShowOne: false, // 控制年份模态框的显示与否
|
||||
year: '', // mode==“year” 专用
|
||||
momVal: !dateStr ? null : moment(dateStr, this.dateFormat)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
const fm = this.dateFormat
|
||||
moment.prototype.toJSON = function () {
|
||||
return moment(this).format(fm)
|
||||
}
|
||||
if (!val) {
|
||||
this.momVal = null
|
||||
} else {
|
||||
this.momVal = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleDateChange (mom, dateStr) {
|
||||
this.$emit('change', dateStr)
|
||||
},
|
||||
handleMonthChange (mom, dateStr) {
|
||||
this.$emit('change', dateStr)
|
||||
},
|
||||
/**
|
||||
* 弹出日历和关闭日历的回调
|
||||
* status:打开或关闭的状态
|
||||
* */
|
||||
openChangeOne (status) {
|
||||
this.yearShowOne = status
|
||||
},
|
||||
// 得到年份选择器的值
|
||||
panelChangeOne (value) {
|
||||
this.yearShowOne = false
|
||||
const year = moment(value, this.dateFormat).year().toString() // 处理成字符串
|
||||
this.year = value // 组件显示的
|
||||
this.$emit('change', year)
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-calendar-picker {
|
||||
min-width: 195px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,317 @@
|
||||
<template>
|
||||
<div class="j-easy-cron">
|
||||
<div class="content">
|
||||
<div>
|
||||
<a-tabs size="small" v-model="curtab">
|
||||
<a-tab-pane tab="秒" key="second" v-if="!hideSecond">
|
||||
<second-ui v-model="second" :disabled="disabled"></second-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="分" key="minute">
|
||||
<minute-ui v-model="minute" :disabled="disabled"></minute-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="时" key="hour">
|
||||
<hour-ui v-model="hour" :disabled="disabled"></hour-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="日" key="day">
|
||||
<day-ui v-model="day" :week="week" :disabled="disabled"></day-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="月" key="month">
|
||||
<month-ui v-model="month" :disabled="disabled"></month-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="周" key="week">
|
||||
<week-ui v-model="week" :day="day" :disabled="disabled"></week-ui>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="年" key="year" v-if="!hideYear && !hideSecond">
|
||||
<year-ui v-model="year" :disabled="disabled"></year-ui>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
<a-divider />
|
||||
<!-- 执行时间预览 -->
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="18" style="margin-top: 22px;">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="秒" v-model="inputValues.second" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="分" v-model="inputValues.minute" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="时" v-model="inputValues.hour" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="日" v-model="inputValues.day" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="月" v-model="inputValues.month" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="周" v-model="inputValues.week" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="8" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="年" v-model="inputValues.year" @blur="onInputBlur" />
|
||||
</a-col>
|
||||
<a-col :span="16" style="margin-bottom: 8px;">
|
||||
<a-input addon-before="Cron" v-model="inputValues.cron" @blur="onInputCronBlur" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
|
||||
<div>近十次执行时间(不含年)</div>
|
||||
<a-textarea type="textarea" :value="preTimeList" :rows="5" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SecondUi from './tabs/second'
|
||||
import MinuteUi from './tabs/minute'
|
||||
import HourUi from './tabs/hour'
|
||||
import DayUi from './tabs/day'
|
||||
import WeekUi from './tabs/week'
|
||||
import MonthUi from './tabs/month'
|
||||
import YearUi from './tabs/year'
|
||||
import CronParser from 'cron-parser'
|
||||
import dateFormat from './format-date'
|
||||
import { simpleDebounce } from '@/utils/util'
|
||||
import ACol from 'ant-design-vue/es/grid/Col'
|
||||
|
||||
export default {
|
||||
name: 'easy-cron',
|
||||
components: {
|
||||
ACol,
|
||||
SecondUi,
|
||||
MinuteUi,
|
||||
HourUi,
|
||||
DayUi,
|
||||
WeekUi,
|
||||
MonthUi,
|
||||
YearUi
|
||||
},
|
||||
props: {
|
||||
cronValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideSecond: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideYear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
curtab: this.hideSecond ? 'minute' : 'second',
|
||||
second: '*',
|
||||
minute: '*',
|
||||
hour: '*',
|
||||
day: '*',
|
||||
month: '*',
|
||||
week: '?',
|
||||
year: '*',
|
||||
inputValues: { second: '', minute: '', hour: '', day: '', month: '', week: '', year: '', cron: '' },
|
||||
preTimeList: '执行预览,会忽略年份参数'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cronValue_c () {
|
||||
const result = []
|
||||
if (!this.hideSecond) result.push(this.second ? this.second : '*')
|
||||
result.push(this.minute ? this.minute : '*')
|
||||
result.push(this.hour ? this.hour : '*')
|
||||
result.push(this.day ? this.day : '*')
|
||||
result.push(this.month ? this.month : '*')
|
||||
result.push(this.week ? this.week : '?')
|
||||
if (!this.hideYear && !this.hideSecond) result.push(this.year ? this.year : '*')
|
||||
return result.join(' ')
|
||||
},
|
||||
cronValue_c2 () {
|
||||
const v = this.cronValue_c
|
||||
if (this.hideYear || this.hideSecond) return v
|
||||
const vs = v.split(' ')
|
||||
if (vs.length >= 6) {
|
||||
// 将 Quartz 星期 的规则转换为 CronParser 的规则
|
||||
vs[5] = this.convertQuartzWeekToCParser(vs[5])
|
||||
}
|
||||
return vs.slice(0, vs.length - 1).join(' ')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cronValue (newVal) {
|
||||
if (newVal === this.cronValue_c) {
|
||||
// console.info('same cron value: ' + newVal)
|
||||
return
|
||||
}
|
||||
this.formatValue()
|
||||
},
|
||||
cronValue_c (newVal) {
|
||||
this.calTriggerList()
|
||||
this.$emit('change', newVal)
|
||||
this.assignInput()
|
||||
},
|
||||
minute () {
|
||||
if (this.second === '*') {
|
||||
this.second = '0'
|
||||
}
|
||||
},
|
||||
hour () {
|
||||
if (this.minute === '*') {
|
||||
this.minute = '0'
|
||||
}
|
||||
},
|
||||
day (day) {
|
||||
if (day !== '?' && this.hour === '*') {
|
||||
this.hour = '0'
|
||||
}
|
||||
},
|
||||
week (week) {
|
||||
if (week !== '?' && this.hour === '*') {
|
||||
this.hour = '0'
|
||||
}
|
||||
},
|
||||
month () {
|
||||
if (this.day === '?' && this.week === '*') {
|
||||
this.week = '1'
|
||||
} else if (this.week === '?' && this.day === '*') {
|
||||
this.day = '1'
|
||||
}
|
||||
},
|
||||
year () {
|
||||
if (this.month === '*') {
|
||||
this.month = '1'
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.formatValue()
|
||||
this.$nextTick(() => {
|
||||
this.calTriggerListInner()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
assignInput () {
|
||||
Object.assign(this.inputValues, {
|
||||
second: this.second,
|
||||
minute: this.minute,
|
||||
hour: this.hour,
|
||||
day: this.day,
|
||||
month: this.month,
|
||||
week: this.week,
|
||||
year: this.year,
|
||||
cron: this.cronValue_c
|
||||
})
|
||||
},
|
||||
formatValue () {
|
||||
if (!this.cronValue) return
|
||||
const values = this.cronValue.split(' ').filter(item => !!item)
|
||||
if (!values || values.length <= 0) return
|
||||
let i = 0
|
||||
if (!this.hideSecond) this.second = values[i++]
|
||||
if (values.length > i) this.minute = values[i++]
|
||||
if (values.length > i) this.hour = values[i++]
|
||||
if (values.length > i) this.day = values[i++]
|
||||
if (values.length > i) this.month = values[i++]
|
||||
if (values.length > i) this.week = values[i++]
|
||||
if (values.length > i) this.year = values[i]
|
||||
this.assignInput()
|
||||
},
|
||||
// 将 Quartz 星期 的规则转换为 CronParser 的规则:
|
||||
// Quartz 的规则:1 = 周日,2 = 周一,3 = 周二,4 = 周三,5 = 周四,6 = 周五,7 = 周六
|
||||
// CronParser 的规则: 0 = 周日,1 = 周一,2 = 周二,3 = 周三,4 = 周四,5 = 周五,6 = 周六,7 = 周日
|
||||
convertQuartzWeekToCParser (week) {
|
||||
const convert = (v) => {
|
||||
if (v === '0') {
|
||||
return '1'
|
||||
}
|
||||
if (v === '1') {
|
||||
return '0'
|
||||
}
|
||||
return (Number.parseInt(v) - 1).toString()
|
||||
}
|
||||
// 匹配示例 1-7 or 1/7
|
||||
const patten1 = /^([0-7])([-/])([0-7])$/
|
||||
// 匹配示例 1,4,7
|
||||
const patten2 = /^([0-7])(,[0-7])+$/
|
||||
if (/^[0-7]$/.test(week)) {
|
||||
return convert(week)
|
||||
} else if (patten1.test(week)) {
|
||||
return week.replace(patten1, ($0, before, separator, after) => {
|
||||
if (separator === '/') {
|
||||
return convert(before) + separator + after
|
||||
} else {
|
||||
return convert(before) + separator + convert(after)
|
||||
}
|
||||
})
|
||||
} else if (patten2.test(week)) {
|
||||
return week.split(',').map(v => convert(v)).join(',')
|
||||
}
|
||||
return week
|
||||
},
|
||||
calTriggerList: simpleDebounce(function () {
|
||||
this.calTriggerListInner()
|
||||
}, 500),
|
||||
calTriggerListInner () {
|
||||
// 设置了回调函数
|
||||
if (this.remote) {
|
||||
this.remote(this.cronValue_c, +new Date(), v => {
|
||||
this.preTimeList = v
|
||||
})
|
||||
return
|
||||
}
|
||||
const format = 'yyyy-MM-dd hh:mm:ss'
|
||||
const options = {
|
||||
currentDate: dateFormat(new Date(), format)
|
||||
}
|
||||
const iter = CronParser.parseExpression(this.cronValue_c2, options)
|
||||
const result = []
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
result.push(dateFormat(new Date(iter.next()), format))
|
||||
}
|
||||
this.preTimeList = result.length > 0 ? result.join('\n') : '无执行时间'
|
||||
},
|
||||
onInputBlur () {
|
||||
this.second = this.inputValues.second
|
||||
this.minute = this.inputValues.minute
|
||||
this.hour = this.inputValues.hour
|
||||
this.day = this.inputValues.day
|
||||
this.month = this.inputValues.month
|
||||
this.week = this.inputValues.week
|
||||
this.year = this.inputValues.year
|
||||
},
|
||||
onInputCronBlur (event) {
|
||||
this.$emit('change', event.target.value)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'cronValue',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.j-easy-cron {
|
||||
|
||||
/deep/ .content {
|
||||
.ant-checkbox-wrapper + .ant-checkbox-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div class="input-cron">
|
||||
<a-input :placeholder="placeholder" v-model="editCronValue" :disabled="disabled">
|
||||
<a slot="addonAfter" @click="showConfigDlg" class="config-btn" :disabled="disabled">
|
||||
<a-icon type="setting"></a-icon>
|
||||
选择
|
||||
</a>
|
||||
</a-input>
|
||||
<j-modal :visible.sync="show" title="Cron表达式" width="800px">
|
||||
<easy-cron
|
||||
v-model="editCronValue"
|
||||
:exeStartTime="exeStartTime"
|
||||
:hideYear="hideYear"
|
||||
:remote="remote"
|
||||
:hideSecond="hideSecond"
|
||||
style="width: 100%"
|
||||
></easy-cron>
|
||||
</j-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EasyCron from './EasyCron.vue'
|
||||
|
||||
export default {
|
||||
name: 'input-cron',
|
||||
components: { EasyCron },
|
||||
model: {
|
||||
prop: 'cronValue',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
cronValue: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '800px'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入cron表达式'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
exeStartTime: {
|
||||
type: [Number, String, Object],
|
||||
default: 0
|
||||
},
|
||||
hideSecond: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideYear: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editCronValue: this.cronValue,
|
||||
show: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cronValue (newVal) {
|
||||
if (newVal === this.editCronValue) {
|
||||
return
|
||||
}
|
||||
this.editCronValue = newVal
|
||||
},
|
||||
editCronValue (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showConfigDlg () {
|
||||
if (!this.disabled) {
|
||||
this.show = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.config-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
const dateFormat = (date, block) => {
|
||||
if (!date) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let format = block || 'yyyy-MM-dd'
|
||||
|
||||
date = new Date(date)
|
||||
|
||||
const map = {
|
||||
M: date.getMonth() + 1, // 月份
|
||||
d: date.getDate(), // 日
|
||||
h: date.getHours(), // 小时
|
||||
m: date.getMinutes(), // 分
|
||||
s: date.getSeconds(), // 秒
|
||||
q: Math.floor((date.getMonth() + 3) / 3), // 季度
|
||||
S: date.getMilliseconds() // 毫秒
|
||||
}
|
||||
|
||||
format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
|
||||
let v = map[t]
|
||||
if (v !== undefined) {
|
||||
if (all.length > 1) {
|
||||
v = `0${v}`
|
||||
v = v.substr(v.length - 2)
|
||||
}
|
||||
return v
|
||||
} else if (t === 'y') {
|
||||
return (date.getFullYear().toString()).substr(4 - all.length)
|
||||
}
|
||||
return all
|
||||
})
|
||||
|
||||
return format
|
||||
}
|
||||
|
||||
export default dateFormat
|
||||
@@ -0,0 +1,6 @@
|
||||
// 原开源项目地址:https://gitee.com/toktok/easy-cron
|
||||
|
||||
import InputCron from './InputCron.vue'
|
||||
|
||||
InputCron.name = 'JEasyCron'
|
||||
export default InputCron
|
||||
@@ -0,0 +1,21 @@
|
||||
export const WEEK_MAP_EN = {
|
||||
SUN: '1',
|
||||
MON: '2',
|
||||
TUE: '3',
|
||||
WED: '4',
|
||||
THU: '5',
|
||||
FRI: '6',
|
||||
SAT: '7'
|
||||
}
|
||||
|
||||
export const replaceWeekName = (c) => {
|
||||
// console.info('after: ' + c)
|
||||
if (c) {
|
||||
Object.keys(WEEK_MAP_EN).forEach(k => {
|
||||
c = c.replace(new RegExp(k, 'g'), WEEK_MAP_EN[k])
|
||||
})
|
||||
// c = c.replace(new RegExp('7', 'g'), '0')
|
||||
}
|
||||
// console.info('after: ' + c)
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disableChoice">每日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
日
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
日开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_WORK" class="choice" :disabled="disableChoice">工作日</a-radio>
|
||||
本月
|
||||
<a-input-number :disabled="type!==TYPE_WORK || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueWork" />
|
||||
日,最近的工作日
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LAST" class="choice" :disabled="disableChoice">最后一日</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i of specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'day',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
week: {
|
||||
type: String,
|
||||
default: '?'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
disableChoice () {
|
||||
return (this.week && this.week !== '?') || this.disabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value_c () {
|
||||
// 数值变化
|
||||
this.updateValue()
|
||||
},
|
||||
week () {
|
||||
// console.info('new week: ' + newVal)
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue () {
|
||||
this.$emit('change', this.disableChoice ? '?' : this.value_c)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 1
|
||||
this.maxValue = 31
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 31
|
||||
this.valueLoop.start = 1
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每时</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
时
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
时
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
时开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
时
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'minute',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 23
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 23
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每分</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.start" />
|
||||
分
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueRange.end" />
|
||||
分
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.start" />
|
||||
分开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
分
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'minute',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 59
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 59
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
// 主要用于日和星期的互斥使用
|
||||
const TYPE_NOT_SET = 'TYPE_NOT_SET'
|
||||
const TYPE_EVERY = 'TYPE_EVERY'
|
||||
const TYPE_RANGE = 'TYPE_RANGE'
|
||||
const TYPE_LOOP = 'TYPE_LOOP'
|
||||
const TYPE_WORK = 'TYPE_WORK'
|
||||
const TYPE_LAST = 'TYPE_LAST'
|
||||
const TYPE_SPECIFY = 'TYPE_SPECIFY'
|
||||
|
||||
const DEFAULT_VALUE = '?'
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: 'prop',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
prop: {
|
||||
type: String,
|
||||
default: DEFAULT_VALUE
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const type = TYPE_EVERY
|
||||
return {
|
||||
DEFAULT_VALUE,
|
||||
// 类型
|
||||
type,
|
||||
// 启用日或者星期互斥用
|
||||
TYPE_NOT_SET,
|
||||
TYPE_EVERY,
|
||||
TYPE_RANGE,
|
||||
TYPE_LOOP,
|
||||
TYPE_WORK,
|
||||
TYPE_LAST,
|
||||
TYPE_SPECIFY,
|
||||
// 对于不同的类型,所定义的值也有所不同
|
||||
valueRange: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
valueLoop: {
|
||||
start: 0,
|
||||
interval: 1
|
||||
},
|
||||
valueWeek: {
|
||||
start: 0,
|
||||
end: 0
|
||||
},
|
||||
valueList: [],
|
||||
valueWork: 1,
|
||||
maxValue: 0,
|
||||
minValue: 0,
|
||||
valueLast: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
prop (newVal) {
|
||||
if (newVal === this.value_c) {
|
||||
// console.info('skip ' + newVal)
|
||||
return
|
||||
}
|
||||
this.parseProp(newVal)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value_c () {
|
||||
const result = []
|
||||
switch (this.type) {
|
||||
case TYPE_NOT_SET:
|
||||
result.push('?')
|
||||
break
|
||||
case TYPE_EVERY:
|
||||
result.push('*')
|
||||
break
|
||||
case TYPE_RANGE:
|
||||
result.push(`${this.valueRange.start}-${this.valueRange.end}`)
|
||||
break
|
||||
case TYPE_LOOP:
|
||||
result.push(`${this.valueLoop.start}/${this.valueLoop.interval}`)
|
||||
break
|
||||
case TYPE_WORK:
|
||||
result.push(`${this.valueWork}W`)
|
||||
break
|
||||
case TYPE_LAST:
|
||||
result.push('L')
|
||||
break
|
||||
case TYPE_SPECIFY:
|
||||
if (this.valueList.length === 0) {
|
||||
this.valueList.push(this.minValue)
|
||||
}
|
||||
result.push(this.valueList.join(','))
|
||||
break
|
||||
default:
|
||||
result.push(this.DEFAULT_VALUE)
|
||||
break
|
||||
}
|
||||
return result.length > 0 ? result.join('') : this.DEFAULT_VALUE
|
||||
},
|
||||
// 指定值范围区间,介于最小值和最大值之间
|
||||
specifyRange () {
|
||||
const range = []
|
||||
for (let i = this.minValue; i <= this.maxValue; i++) {
|
||||
range.push(i)
|
||||
}
|
||||
return range
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseProp (value) {
|
||||
if (value === this.value_c) {
|
||||
// console.info('same ' + value)
|
||||
return
|
||||
}
|
||||
if (typeof (this.preProcessProp) === 'function') {
|
||||
value = this.preProcessProp(value)
|
||||
}
|
||||
try {
|
||||
if (!value || value === this.DEFAULT_VALUE) {
|
||||
this.type = TYPE_EVERY
|
||||
} else if (value.indexOf('?') >= 0) {
|
||||
this.type = TYPE_NOT_SET
|
||||
} else if (value.indexOf('-') >= 0) {
|
||||
this.type = TYPE_RANGE
|
||||
const values = value.split('-')
|
||||
if (values.length >= 2) {
|
||||
this.valueRange.start = parseInt(values[0])
|
||||
this.valueRange.end = parseInt(values[1])
|
||||
}
|
||||
} else if (value.indexOf('/') >= 0) {
|
||||
this.type = TYPE_LOOP
|
||||
const values = value.split('/')
|
||||
if (values.length >= 2) {
|
||||
this.valueLoop.start = value[0] === '*' ? 0 : parseInt(values[0])
|
||||
this.valueLoop.interval = parseInt(values[1])
|
||||
}
|
||||
} else if (value.indexOf('W') >= 0) {
|
||||
this.type = TYPE_WORK
|
||||
const values = value.split('W')
|
||||
if (!values[0] && !isNaN(values[0])) {
|
||||
this.valueWork = parseInt(values[0])
|
||||
}
|
||||
} else if (value.indexOf('L') >= 0) {
|
||||
this.type = TYPE_LAST
|
||||
const values = value.split('L')
|
||||
this.valueLast = parseInt(values[0])
|
||||
} else if (value.indexOf(',') >= 0 || !isNaN(value)) {
|
||||
this.type = TYPE_SPECIFY
|
||||
this.valueList = value.split(',').map(item => parseInt(item))
|
||||
} else {
|
||||
this.type = TYPE_EVERY
|
||||
}
|
||||
} catch (e) {
|
||||
// console.info(e)
|
||||
this.type = TYPE_EVERY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
.config-list {
|
||||
text-align: left;
|
||||
margin: 0 10px 10px 10px;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.choice {
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
|
||||
.w60 {
|
||||
width: 60px;
|
||||
}
|
||||
.w80 {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.list-check-item {
|
||||
padding: 1px 3px;
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.tip-info {
|
||||
color: #999
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每月</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
月
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
月
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
月开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
月
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i of specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'month',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 1
|
||||
this.maxValue = 12
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 12
|
||||
this.valueLoop.start = 1
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每秒</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
秒
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
秒
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
秒开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :max="maxValue" :min="minValue" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
秒
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disabled">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{i}}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'second',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 59
|
||||
this.valueRange.start = 0
|
||||
this.valueRange.end = 59
|
||||
this.valueLoop.start = 0
|
||||
this.valueLoop.interval = 1
|
||||
// console.info('created')
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_NOT_SET" class="choice" :disabled="disableChoice">不设置</a-radio>
|
||||
<span class="tip-info">日和周只能设置其中之一</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disableChoice">区间</a-radio>
|
||||
从
|
||||
<a-select v-model="valueRange.start" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
至
|
||||
<a-select v-model="valueRange.end" class="w80" :disabled="type!==TYPE_RANGE || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disableChoice">循环</a-radio>
|
||||
从
|
||||
<a-select v-model="valueLoop.start" class="w80" :disabled="type!==TYPE_LOOP || disableChoice">
|
||||
<template v-for="(v, k) of WEEK_MAP">
|
||||
<a-select-option :value="v" :key="v">{{ k }}</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disableChoice" :max="maxValue" :min="minValue" :precision="0" class="w60"
|
||||
v-model="valueLoop.interval" />
|
||||
天
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_SPECIFY" class="choice" :disabled="disableChoice">指定</a-radio>
|
||||
<div class="list">
|
||||
<a-checkbox-group v-model="valueList">
|
||||
<template v-for="i in specifyRange">
|
||||
<a-checkbox class="list-check-item" :key="`key-${i}`" :value="i" :disabled="type!==TYPE_SPECIFY || disabled">{{ i }}</a-checkbox>
|
||||
</template>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
import { replaceWeekName, WEEK_MAP_EN } from './const.js'
|
||||
|
||||
const WEEK_MAP = {
|
||||
周一: 2,
|
||||
周二: 3,
|
||||
周三: 4,
|
||||
周四: 5,
|
||||
周五: 6,
|
||||
周六: 7,
|
||||
// 按照国人习惯,将周日放到每周的最后一天
|
||||
周日: 1
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'week',
|
||||
mixins: [mixin],
|
||||
props: {
|
||||
day: {
|
||||
type: String,
|
||||
default: '*'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
WEEK_MAP,
|
||||
WEEK_MAP_EN
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disableChoice () {
|
||||
return (this.day && this.day !== '?') || this.disabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value_c () {
|
||||
// 如果设置日,那么星期就直接不设置
|
||||
this.updateValue()
|
||||
},
|
||||
day () {
|
||||
// console.info('new day: ' + newVal)
|
||||
this.updateValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateValue () {
|
||||
this.$emit('change', this.disableChoice ? '?' : this.value_c)
|
||||
},
|
||||
preProcessProp (c) {
|
||||
return replaceWeekName(c)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.DEFAULT_VALUE = '*'
|
||||
// 0,7表示周日 1表示周一
|
||||
this.minValue = 1
|
||||
this.maxValue = 7
|
||||
this.valueRange.start = 1
|
||||
this.valueRange.end = 7
|
||||
this.valueLoop.start = 2
|
||||
this.valueLoop.interval = 1
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="config-list">
|
||||
<a-radio-group v-model="type">
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_EVERY" class="choice" :disabled="disabled">每年</a-radio>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_RANGE" class="choice" :disabled="disabled">区间</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="0" :precision="0" class="w60" v-model="valueRange.start"/>
|
||||
年
|
||||
至
|
||||
<a-input-number :disabled="type!==TYPE_RANGE || disabled" :min="1" :precision="0" class="w60" v-model="valueRange.end"/>
|
||||
年
|
||||
</div>
|
||||
<div class="item">
|
||||
<a-radio value="TYPE_LOOP" class="choice" :disabled="disabled">循环</a-radio>
|
||||
从
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="0" :precision="0" class="w60" v-model="valueLoop.start"/>
|
||||
年开始,间隔
|
||||
<a-input-number :disabled="type!==TYPE_LOOP || disabled" :min="1" :precision="0" class="w60" v-model="valueLoop.interval"/>
|
||||
年
|
||||
</div>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mixin from './mixin'
|
||||
|
||||
export default {
|
||||
name: 'year',
|
||||
mixins: [mixin],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
value_c (newVal) {
|
||||
// console.info('change:' + newVal)
|
||||
this.$emit('change', newVal)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const nowYear = (new Date()).getFullYear()
|
||||
this.DEFAULT_VALUE = '*'
|
||||
this.minValue = 0
|
||||
this.maxValue = 0
|
||||
this.valueRange.start = nowYear
|
||||
this.valueRange.end = nowYear + 100
|
||||
this.valueLoop.start = nowYear
|
||||
this.valueLoop.interval = 1
|
||||
// console.info('created')
|
||||
this.parseProp(this.prop)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import "mixin.less";
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
import CronParser from 'cron-parser'
|
||||
import { replaceWeekName } from './tabs/const'
|
||||
|
||||
export default (rule, value, callback) => {
|
||||
// 没填写就不校验
|
||||
if (!value) {
|
||||
callback()
|
||||
return true
|
||||
}
|
||||
const values = value.split(' ').filter(item => !!item)
|
||||
if (values.length > 7) {
|
||||
callback(new Error('Cron表达式最多7项!'))
|
||||
return false
|
||||
}
|
||||
// 检查第7项
|
||||
let e = value
|
||||
if (values.length === 7) {
|
||||
const year = replaceWeekName(values[6])
|
||||
if (year !== '*' && year !== '?') {
|
||||
let yearValues
|
||||
if (year.indexOf('-') >= 0) {
|
||||
yearValues = year.split('-')
|
||||
} else if (year.indexOf('/')) {
|
||||
yearValues = year.split('/')
|
||||
} else {
|
||||
yearValues = [year]
|
||||
}
|
||||
// console.info(yearValues)
|
||||
// 判断是否都是数字
|
||||
const checkYear = yearValues.some(item => isNaN(item))
|
||||
if (checkYear) {
|
||||
callback(new Error('Cron表达式参数[年]错误:' + year))
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 取其中的前六项
|
||||
e = values.slice(0, 6).join(' ')
|
||||
}
|
||||
// 6位 没有年
|
||||
// 5位没有秒、年
|
||||
let result = true
|
||||
try {
|
||||
const iter = CronParser.parseExpression(e)
|
||||
iter.next()
|
||||
callback()
|
||||
} catch (e) {
|
||||
callback(new Error('Cron表达式错误:' + e))
|
||||
result = false
|
||||
}
|
||||
return result
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="tinymce-editor">
|
||||
<editor
|
||||
v-if="!reloading"
|
||||
v-model="myValue"
|
||||
:init="init"
|
||||
:disabled="disabled"
|
||||
@onClick="onClick">
|
||||
</editor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tinymce from 'tinymce/tinymce'
|
||||
import Editor from '@tinymce/tinymce-vue'
|
||||
import 'tinymce/themes/silver/theme'
|
||||
import 'tinymce/plugins/image'
|
||||
import 'tinymce/plugins/link'
|
||||
import 'tinymce/plugins/media'
|
||||
import 'tinymce/plugins/table'
|
||||
import 'tinymce/plugins/lists'
|
||||
import 'tinymce/plugins/contextmenu'
|
||||
import 'tinymce/plugins/wordcount'
|
||||
import 'tinymce/plugins/colorpicker'
|
||||
import 'tinymce/plugins/textcolor'
|
||||
import 'tinymce/plugins/fullscreen'
|
||||
import 'tinymce/icons/default'
|
||||
import { uploadAction, getFileAccessHttpUrl } from '@/api/manage'
|
||||
import { getVmParentByName } from '@/utils/util'
|
||||
export default {
|
||||
components: {
|
||||
Editor
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plugins: {
|
||||
type: [String, Array],
|
||||
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
|
||||
},
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
|
||||
branding: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 初始化配置
|
||||
init: {
|
||||
language_url: '/tinymce/langs/zh_CN.js',
|
||||
language: 'zh_CN',
|
||||
skin_url: '/tinymce/skins/lightgray',
|
||||
height: 300,
|
||||
plugins: this.plugins,
|
||||
toolbar: this.toolbar,
|
||||
branding: false,
|
||||
menubar: false,
|
||||
toolbar_drawer: false,
|
||||
images_upload_handler: (blobInfo, success) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', blobInfo.blob(), blobInfo.filename())
|
||||
formData.append('biz', 'jeditor')
|
||||
formData.append('jeditor', '1')
|
||||
uploadAction(window._CONFIG.domianURL + '/sys/common/upload', formData).then((res) => {
|
||||
if (res.success) {
|
||||
if (res.message === 'local') {
|
||||
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
|
||||
success(img)
|
||||
} else {
|
||||
const img = getFileAccessHttpUrl(res.message)
|
||||
success(img)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
myValue: this.value,
|
||||
reloading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initATabsChangeAutoReload()
|
||||
},
|
||||
methods: {
|
||||
|
||||
reload () {
|
||||
this.reloading = true
|
||||
this.$nextTick(() => {
|
||||
this.reloading = false
|
||||
})
|
||||
},
|
||||
|
||||
onClick (e) {
|
||||
this.$emit('onClick', e, tinymce)
|
||||
},
|
||||
// 可以添加一些自己的自定义事件,如清空内容
|
||||
clear () {
|
||||
this.myValue = ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 自动判断父级是否是 <a-tabs/> 组件,然后添加事件监听,自动触发reload()
|
||||
*
|
||||
* 由于 tabs 组件切换会导致 tinymce 无法输入,
|
||||
* 只有重新加载才能使用(无论是vue版的还是jQuery版tinymce都有这个通病)
|
||||
*/
|
||||
initATabsChangeAutoReload () {
|
||||
// 获取父级
|
||||
const tabs = getVmParentByName(this, 'ATabs')
|
||||
const tabPane = getVmParentByName(this, 'ATabPane')
|
||||
if (tabs && tabPane) {
|
||||
// 用户自定义的 key
|
||||
const currentKey = tabPane.$vnode.key
|
||||
// 添加事件监听
|
||||
tabs.$on('change', (key) => {
|
||||
// 切换到自己时执行reload
|
||||
if (currentKey === key) {
|
||||
this.reload()
|
||||
}
|
||||
})
|
||||
// update--begin--autor:liusq-----date:20210316------for:富文本编辑器tab父组件可能导致的赋值问题------
|
||||
this.reload()
|
||||
// update--end--autor:liusq-----date:20210316------for:富文本编辑器tab父组件可能导致的赋值问题------
|
||||
} else {
|
||||
// update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
|
||||
const tabLayout = getVmParentByName(this, 'TabLayout')
|
||||
// update--begin--autor:liusq-----date:20210713------for:处理特殊情况excuteCallback不能使用------
|
||||
try {
|
||||
tabLayout.excuteCallback(() => {
|
||||
this.reload()
|
||||
})
|
||||
} catch (error) {
|
||||
if (tabLayout) {
|
||||
this.reload()
|
||||
}
|
||||
}
|
||||
// update--end--autor:liusq-----date:20210713------for:处理特殊情况excuteCallback不能使用------
|
||||
// update--begin--autor:wangshuai-----date:20200724------for:文本编辑器切换tab无法修改------
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
value (newValue) {
|
||||
this.myValue = (newValue == null ? '' : newValue)
|
||||
},
|
||||
myValue (newValue) {
|
||||
if (this.triggerChange) {
|
||||
this.$emit('change', newValue)
|
||||
} else {
|
||||
this.$emit('input', newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
<template slot="title">
|
||||
<span>{{value}}</span>
|
||||
</template>
|
||||
{{ value | ellipsis(length) }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JEllipsis',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
length: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 25
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div :class="disabled?'jero-form-container-disabled':''">
|
||||
<fieldset :disabled="disabled">
|
||||
<slot name="detail"></slot>
|
||||
</fieldset>
|
||||
<slot name="edit"></slot>
|
||||
<fieldset disabled>
|
||||
<slot></slot>
|
||||
</fieldset>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 使用方法
|
||||
* 在form下直接写这个组件就行了,
|
||||
*<a-form layout="inline" :form="form" >
|
||||
* <j-form-container :disabled="true">
|
||||
* <!-- 表单内容省略..... -->
|
||||
* </j-form-container>
|
||||
*</a-form>
|
||||
*/
|
||||
export default {
|
||||
name: 'JFormContainer',
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
console.log('我是表单禁用专用组件,但是我并不支持表单中iframe的内容禁用')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.jero-form-container-disabled{
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.jero-form-container-disabled fieldset[disabled] {
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.jero-form-container-disabled .ant-select{
|
||||
-ms-pointer-events: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jero-form-container-disabled .ant-upload-select{display:none}
|
||||
.jero-form-container-disabled .ant-upload-list{cursor:grabbing}
|
||||
.jero-form-container-disabled fieldset[disabled] .ant-upload-list{
|
||||
-ms-pointer-events: auto !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/*.jero-form-container-disabled .ant-upload-list-item-actions .anticon-delete,*/
|
||||
.jero-form-container-disabled .ant-upload-list-item .anticon-delete,
|
||||
.jero-form-container-disabled .ant-upload-list-item .anticon-close{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div class="img">
|
||||
<a-upload
|
||||
name="file"
|
||||
listType="picture-card"
|
||||
:multiple="isMultiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{biz:bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="beforeUpload"
|
||||
:disabled="disabled"
|
||||
v-bind="$attrs"
|
||||
:accept="accept"
|
||||
v-on="childListeners"
|
||||
@change="handleChange"
|
||||
@preview="handlePreview"
|
||||
:class="[!isMultiple?'imgupload':'', (!isMultiple && picUrl)?'image-upload-single-over':'' ]">
|
||||
<div>
|
||||
<!--<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>-->
|
||||
<div class="iconp">
|
||||
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
|
||||
<div class="ant-upload-text">{{ text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <j-image-preview-modal ref="JImagePreviewModal"></j-image-preview-modal>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
// import JImagePreviewModal from './modal/JImagePreviewModal'
|
||||
// const Base64 = require('js-base64').Base64
|
||||
|
||||
const uidGenerator = () => {
|
||||
return '-' + parseInt(Math.random() * 10000 + 1 + '', 10)
|
||||
}
|
||||
export default {
|
||||
name: 'JImageUpload',
|
||||
// components: { JImagePreviewModal },
|
||||
data () {
|
||||
return {
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
uploadLoading: false,
|
||||
image: false,
|
||||
picUrl: false,
|
||||
headers: {},
|
||||
fileList: [],
|
||||
accept: 'image/png, image/jpeg',
|
||||
previewImage: '',
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '上传'
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
isMultiple: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
}
|
||||
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler (val) {
|
||||
if (val instanceof Array) {
|
||||
this.initFileList(val.join(','))
|
||||
} else {
|
||||
this.initFileList(val)
|
||||
}
|
||||
if (!val || val.length === 0) {
|
||||
this.picUrl = false
|
||||
}
|
||||
},
|
||||
// 立刻执行handler
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const token = Vue.ls.get(ACCESS_TOKEN)
|
||||
this.headers = { 'X-Access-Token': token }
|
||||
},
|
||||
methods: {
|
||||
initFileList (paths) {
|
||||
if (!paths || paths.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
this.picUrl = true
|
||||
const fileList = []
|
||||
const arr = paths.split(',')
|
||||
for (let a = 0; a < arr.length; a++) {
|
||||
const url = getFileAccessHttpUrl(arr[a])
|
||||
fileList.push({
|
||||
uid: uidGenerator(),
|
||||
name: arr[a],
|
||||
status: 'done',
|
||||
url: url,
|
||||
response: {
|
||||
status: 'history',
|
||||
message: arr[a]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.fileList = fileList
|
||||
},
|
||||
beforeUpload: function (file) {
|
||||
const fileType = file.type
|
||||
if (fileType.indexOf('image') < 0) {
|
||||
this.$message.warning('请上传图片')
|
||||
return false
|
||||
}
|
||||
},
|
||||
handleChange (info) {
|
||||
this.picUrl = false
|
||||
let fileList = info.fileList
|
||||
// update-begin-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
|
||||
if (this.number > 0) {
|
||||
fileList = fileList.slice(-this.number)
|
||||
}
|
||||
// update-end-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
|
||||
if (info.file.status === 'done') {
|
||||
if (info.file.response.success) {
|
||||
this.picUrl = true
|
||||
fileList = fileList.map((file) => {
|
||||
if (file.response) {
|
||||
file.url = `${file.response.result.id}`
|
||||
}
|
||||
return file
|
||||
})
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.error(`${info.file.name} 上传失败.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
this.handlePathChange()
|
||||
}
|
||||
},
|
||||
// 预览
|
||||
handlePreview (file) {
|
||||
if (file && file.url) {
|
||||
console.log(file)
|
||||
// const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/${file.url}`
|
||||
// const url = `${window._CONFIG.onlinePreviewDomainURL}?url=${encodeURIComponent(Base64.encode(fileFullUrl))}`
|
||||
// window.open(url)
|
||||
// this.$refs.JImagePreviewModal.open(file)
|
||||
this.imageUrl = getFileAccessHttpUrl(file.name)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
}
|
||||
},
|
||||
getAvatarView () {
|
||||
if (this.fileList.length > 0) {
|
||||
const url = this.fileList[this.fileList.length - 1].url
|
||||
return getFileAccessHttpUrl(url)
|
||||
}
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
if (!this.isMultiple && uploadFiles && uploadFiles.length > 0) {
|
||||
arr.push(uploadFiles[uploadFiles.length - 1].url)
|
||||
} else {
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
this.previewVisible = false
|
||||
},
|
||||
close () {
|
||||
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .imgupload .ant-upload-select{display:block}
|
||||
/deep/ .imgupload .ant-upload.ant-upload-select-picture-card{ width:120px;height: 120px;}
|
||||
/deep/ .imgupload .iconp{padding:32px;}
|
||||
/* update--end--autor:lvdandan-----date:20201016------for:j-image-upload图片组件单张图片详情回显空白*/
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-modal
|
||||
title="导入EXCEL"
|
||||
:width="600"
|
||||
:visible="visible"
|
||||
:confirmLoading="uploading"
|
||||
@cancel="handleClose">
|
||||
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="true"
|
||||
accept=".xls,.xlsx"
|
||||
:fileList="fileList"
|
||||
:remove="handleRemove"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
:beforeUpload="beforeUpload">
|
||||
<a-button>
|
||||
<a-icon type="upload" />
|
||||
选择导入文件
|
||||
</a-button>
|
||||
</a-upload>
|
||||
|
||||
<template slot="footer">
|
||||
<a-button @click="handleClose">关闭</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="handleImport"
|
||||
:disabled="fileList.length === 0"
|
||||
:loading="uploading">
|
||||
{{ uploading ? '上传中...' : '开始上传' }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { postAction } from '@/api/manage'
|
||||
export default {
|
||||
name: 'JImportModal',
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
biz: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
uploading: false,
|
||||
fileList: [],
|
||||
uploadAction: '',
|
||||
foreignKeys: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
url (val) {
|
||||
if (val) {
|
||||
this.uploadAction = window._CONFIG.domianURL + val
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
console.log(this.$attrs)
|
||||
this.uploadAction = window._CONFIG.domianURL + this.url
|
||||
},
|
||||
methods: {
|
||||
handleClose () {
|
||||
this.visible = false
|
||||
},
|
||||
show (arg) {
|
||||
this.fileList = []
|
||||
this.uploading = false
|
||||
this.visible = true
|
||||
this.foreignKeys = arg
|
||||
},
|
||||
handleRemove (file) {
|
||||
const index = this.fileList.indexOf(file)
|
||||
const newFileList = this.fileList.slice()
|
||||
newFileList.splice(index, 1)
|
||||
this.fileList = newFileList
|
||||
},
|
||||
beforeUpload (file) {
|
||||
this.fileList = [...this.fileList, file]
|
||||
return false
|
||||
},
|
||||
handleImport () {
|
||||
const { fileList } = this
|
||||
const formData = new FormData()
|
||||
if (this.biz) {
|
||||
formData.append('isSingleTableImport', this.biz)
|
||||
}
|
||||
if (this.foreignKeys && this.foreignKeys.length > 0) {
|
||||
formData.append('foreignKeys', this.foreignKeys)
|
||||
}
|
||||
fileList.forEach((file) => {
|
||||
formData.append('files[]', file)
|
||||
})
|
||||
this.uploading = true
|
||||
postAction(this.uploadAction, formData).then((res) => {
|
||||
this.uploading = false
|
||||
if (res.success) {
|
||||
this.$message.success(res.message)
|
||||
this.visible = false
|
||||
this.$emit('ok')
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<a-input :placeholder="placeholder"
|
||||
:value="inputVal"
|
||||
@input="backValue"
|
||||
v-bind="$props"
|
||||
v-on="childListeners"
|
||||
></a-input>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Input } from 'ant-design-vue'
|
||||
|
||||
const JINPUT_QUERY_LIKE = 'like'
|
||||
const JINPUT_QUERY_NE = 'ne'
|
||||
const JINPUT_QUERY_GE = 'ge' // 大于等于
|
||||
const JINPUT_QUERY_LE = 'le' // 小于等于
|
||||
|
||||
export default {
|
||||
name: 'JInput',
|
||||
props: {
|
||||
...Input.props, // 拿到全部外部的参数
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: JINPUT_QUERY_LIKE
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
trim: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function () {
|
||||
this.initVal()
|
||||
}
|
||||
},
|
||||
// 当 type 变化的时候重新计算值
|
||||
type () {
|
||||
this.backValue({ target: { value: this.inputVal } })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initVal () {
|
||||
if (!this.value) {
|
||||
this.inputVal = ''
|
||||
} else {
|
||||
let text = this.value
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
// 修复路由传参的值传送到jinput框被前后各截取了一位 #1336
|
||||
if (text.indexOf('*') !== -1) {
|
||||
text = text.substring(1, text.length - 1)
|
||||
}
|
||||
break
|
||||
case JINPUT_QUERY_NE:
|
||||
text = text.substring(1)
|
||||
break
|
||||
case JINPUT_QUERY_GE:
|
||||
text = text.substring(2)
|
||||
break
|
||||
case JINPUT_QUERY_LE:
|
||||
text = text.substring(2)
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.inputVal = text
|
||||
}
|
||||
},
|
||||
backValue (e) {
|
||||
let text = e.target.value
|
||||
if (text && this.trim === true) {
|
||||
text = text.trim()
|
||||
}
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
text = '*' + text + '*'
|
||||
break
|
||||
case JINPUT_QUERY_NE:
|
||||
text = '!' + text
|
||||
break
|
||||
case JINPUT_QUERY_GE:
|
||||
text = '>=' + text
|
||||
break
|
||||
case JINPUT_QUERY_LE:
|
||||
text = '<=' + text
|
||||
break
|
||||
default:
|
||||
}
|
||||
this.$emit('change', text)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
export default {
|
||||
minHeight: '200px',
|
||||
previewStyle: 'vertical',
|
||||
useCommandShortcut: true,
|
||||
useDefaultHTMLSanitizer: true,
|
||||
usageStatistics: false,
|
||||
hideModeSwitch: false,
|
||||
toolbarItems: [
|
||||
'heading',
|
||||
'bold',
|
||||
'italic',
|
||||
'strike',
|
||||
'divider',
|
||||
'hr',
|
||||
'quote',
|
||||
'divider',
|
||||
'ul',
|
||||
'ol',
|
||||
'task',
|
||||
'indent',
|
||||
'outdent',
|
||||
'divider',
|
||||
'table',
|
||||
'link',
|
||||
'divider',
|
||||
'code',
|
||||
'codeblock'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="j-markdown-editor" :id="id"/>
|
||||
<div v-if="isShow">
|
||||
<j-modal
|
||||
title="图片上传"
|
||||
:visible.sync="dialogVisible"
|
||||
width="30%"
|
||||
:before-close="handleClose"
|
||||
@ok="handleOk">
|
||||
<a-tabs default-active-key="1" @change="handleChange">
|
||||
<a-tab-pane tab="本地图片上传" key="1" :forceRender="true">
|
||||
<j-upload v-model="fileList" :number="1"></j-upload>
|
||||
<div style="margin-top: 20px">
|
||||
<a-input v-model="remark" placeholder="请填写备注"></a-input>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="网络图片地址" key="2" :forceRender="true">
|
||||
<a-input v-model="networkPic" placeholder="请填写网络图片地址"></a-input>
|
||||
<a-input style="margin-top: 20px" v-model="remark" placeholder="请填写备注"></a-input>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</j-modal>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import '@toast-ui/editor/dist/toastui-editor.css'
|
||||
import '@toast-ui/editor/dist/i18n/zh-cn'
|
||||
|
||||
import Editor from '@toast-ui/editor'
|
||||
import defaultOptions from './default-options'
|
||||
import JUpload from '@/components/jero/JUpload'
|
||||
import { getFileAccessHttpUrl } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JMarkdownEditor',
|
||||
components: {
|
||||
JUpload
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default () {
|
||||
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default () {
|
||||
return defaultOptions
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'markdown'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '300px'
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'zh-CN'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null,
|
||||
isShow: false,
|
||||
activeIndex: '1',
|
||||
dialogVisible: false,
|
||||
index: '1',
|
||||
fileList: [],
|
||||
remark: '',
|
||||
imageName: '',
|
||||
imageUrl: '',
|
||||
networkPic: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorOptions () {
|
||||
const options = Object.assign({}, defaultOptions, this.options)
|
||||
options.initialEditType = this.mode
|
||||
options.height = this.height
|
||||
options.language = this.language
|
||||
return options
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newValue, preValue) {
|
||||
if (newValue !== preValue && newValue !== this.editor.getMarkdown()) {
|
||||
this.editor.setMarkdown(newValue)
|
||||
}
|
||||
},
|
||||
language () {
|
||||
this.destroyEditor()
|
||||
this.initEditor()
|
||||
},
|
||||
height (newValue) {
|
||||
this.editor.height(newValue)
|
||||
},
|
||||
mode (newValue) {
|
||||
this.editor.changeMode(newValue)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.initEditor()
|
||||
},
|
||||
destroyed () {
|
||||
this.destroyEditor()
|
||||
},
|
||||
methods: {
|
||||
initEditor () {
|
||||
this.editor = new Editor({
|
||||
el: document.getElementById(this.id),
|
||||
...this.editorOptions
|
||||
})
|
||||
if (this.value) {
|
||||
this.editor.setMarkdown(this.value)
|
||||
}
|
||||
this.editor.on('change', () => {
|
||||
this.$emit('change', this.editor.getMarkdown())
|
||||
})
|
||||
// --begin 添加自定义上传按钮
|
||||
/*
|
||||
* 添加自定义按钮
|
||||
*/
|
||||
// 获取编辑器上的功能条
|
||||
const toolbar = this.editor.getUI().getToolbar()
|
||||
// 添加图片点击事件
|
||||
this.editor.eventManager.addEventType('isShowClickEvent')
|
||||
this.editor.eventManager.listen('isShowClickEvent', () => {
|
||||
this.isShow = true
|
||||
this.dialogVisible = true
|
||||
})
|
||||
// addImageBlobHook图片上传、剪切、拖拽都会走此方法
|
||||
// 删除默认监听事件
|
||||
this.editor.eventManager.removeEventHandler('addImageBlobHook')
|
||||
// 添加自定义监听事件
|
||||
this.editor.eventManager.listen('addImageBlobHook', (blob, callback) => {
|
||||
this.upload(blob, url => {
|
||||
callback(url)
|
||||
})
|
||||
})
|
||||
// 添加自定义按钮 第二个参数代表位置,不传默认放在最后
|
||||
toolbar.insertItem(15, {
|
||||
type: 'button',
|
||||
options: {
|
||||
name: 'customize',
|
||||
className: 'tui-image tui-toolbar-icons',
|
||||
event: 'isShowClickEvent',
|
||||
tooltip: '上传图片'
|
||||
}
|
||||
//
|
||||
})
|
||||
// --end 添加自定义上传按钮
|
||||
},
|
||||
destroyEditor () {
|
||||
if (!this.editor) return
|
||||
this.editor.off('change')
|
||||
this.editor.remove()
|
||||
},
|
||||
setMarkdown (value) {
|
||||
this.editor.setMarkdown(value)
|
||||
},
|
||||
getMarkdown () {
|
||||
return this.editor.getMarkdown()
|
||||
},
|
||||
setHtml (value) {
|
||||
this.editor.setHtml(value)
|
||||
},
|
||||
getHtml () {
|
||||
return this.editor.getHtml()
|
||||
},
|
||||
handleOk () {
|
||||
if (this.index === '1') {
|
||||
this.imageUrl = getFileAccessHttpUrl(this.fileList)
|
||||
if (this.remark) {
|
||||
this.addImgToMd(this.imageUrl, this.remark)
|
||||
} else {
|
||||
this.addImgToMd(this.imageUrl, '')
|
||||
}
|
||||
} else {
|
||||
if (this.remark) {
|
||||
this.addImgToMd(this.networkPic, this.remark)
|
||||
} else {
|
||||
this.addImgToMd(this.networkPic, '')
|
||||
}
|
||||
}
|
||||
this.index = '1'
|
||||
this.fileList = []
|
||||
this.imageName = ''
|
||||
this.imageUrl = ''
|
||||
this.remark = ''
|
||||
this.networkPic = ''
|
||||
this.dialogVisible = false
|
||||
this.isShow = false
|
||||
},
|
||||
handleClose (done) {
|
||||
done()
|
||||
},
|
||||
handleChange (val) {
|
||||
this.fileList = []
|
||||
this.remark = ''
|
||||
this.imageName = ''
|
||||
this.imageUrl = ''
|
||||
this.networkPic = ''
|
||||
this.index = val
|
||||
},
|
||||
// 添加图片到markdown
|
||||
addImgToMd (data, name) {
|
||||
const editor = this.editor.getCodeMirror()
|
||||
const editorHtml = this.editor.getCurrentModeEditor()
|
||||
const isMarkdownMode = this.editor.isMarkdownMode()
|
||||
if (isMarkdownMode) {
|
||||
editor.replaceSelection(``)
|
||||
} else {
|
||||
const range = editorHtml.getRange()
|
||||
const img = document.createElement('img')
|
||||
img.src = `${data}`
|
||||
img.alt = name
|
||||
range.insertNode(img)
|
||||
}
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
.j-markdown-editor {
|
||||
/deep/ .tui-editor-defaultUI {
|
||||
.te-mode-switch,
|
||||
.tui-scrollsync
|
||||
{
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
destroyOnClose
|
||||
>
|
||||
|
||||
<slot></slot>
|
||||
<!--有设置标题-->
|
||||
<template v-if="!isNoTitle" slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col class="left">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</a-col>
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!--没有设置标题-->
|
||||
<template v-else slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 处理 scopedSlots -->
|
||||
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 处理 slots -->
|
||||
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { getClass, getStyle } from '@/utils/props-util'
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JModal',
|
||||
props: {
|
||||
title: String,
|
||||
// 可使用 .sync 修饰符
|
||||
visible: Boolean,
|
||||
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许切换全屏(允许后右上角会出现一个按钮)
|
||||
switchFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击确定按钮的时候是否关闭弹窗
|
||||
okClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部使用的 slots ,不再处理
|
||||
usedSlots: ['title'],
|
||||
// 实际控制是否全屏的参数
|
||||
innerFullscreen: this.fullscreen
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
|
||||
_attrs () {
|
||||
const attrs = { ...this.$attrs }
|
||||
// 如果全屏就将宽度设为 100%
|
||||
if (this.innerFullscreen) {
|
||||
attrs.width = '100%'
|
||||
}
|
||||
return attrs
|
||||
},
|
||||
modalClass () {
|
||||
return {
|
||||
'j-modal-box': true,
|
||||
fullscreen: this.innerFullscreen,
|
||||
'no-title': this.isNoTitle,
|
||||
'no-footer': this.isNoFooter
|
||||
}
|
||||
},
|
||||
modalStyle () {
|
||||
const style = {}
|
||||
// 如果全屏就将top设为 0
|
||||
if (this.innerFullscreen) {
|
||||
style.top = '0'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isNoTitle () {
|
||||
return !this.title && !this.allSlotsKeys.includes('title')
|
||||
},
|
||||
isNoFooter () {
|
||||
return this._attrs.footer === null
|
||||
},
|
||||
slotsKeys () {
|
||||
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
scopedSlotsKeys () {
|
||||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys () {
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon () {
|
||||
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible () {
|
||||
if (this.visible) {
|
||||
this.innerFullscreen = this.fullscreen
|
||||
}
|
||||
},
|
||||
innerFullscreen (val) {
|
||||
this.$emit('update:fullscreen', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getClass (clazz) {
|
||||
return { ...getClass(this), ...clazz }
|
||||
},
|
||||
getStyle (style) {
|
||||
return { ...getStyle(this), ...style }
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
if (this.okClose) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 切换全屏 */
|
||||
toggleFullscreen () {
|
||||
this.innerFullscreen = !this.innerFullscreen
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
.j-modal-box {
|
||||
&.fullscreen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
||||
// 兼容1.6.2版本的antdv
|
||||
& .ant-modal {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
& .ant-modal-content {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
|
||||
& .ant-modal-body {
|
||||
/* title 和 footer 各占 55px */
|
||||
height: calc(100% - 55px - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-title, &.no-footer {
|
||||
.ant-modal-body {
|
||||
height: calc(100% - 55px);
|
||||
}
|
||||
}
|
||||
&.no-title.no-footer {
|
||||
.ant-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-modal-title-row {
|
||||
.left {
|
||||
width: calc(100% - 56px - 56px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 56px;
|
||||
position: inherit;
|
||||
|
||||
.ant-modal-close {
|
||||
right: 56px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.no-title{
|
||||
.ant-modal-header {
|
||||
padding: 0 24px;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<j-modal :visible="visible" :confirmLoading="loading" :after-close="afterClose" v-bind="modalProps" @ok="onOk" @cancel="onCancel">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-html="content"></div>
|
||||
<a-form-model ref="form" :model="model" :rules="rules">
|
||||
<a-form-model-item prop="input">
|
||||
<a-input ref="input" v-model="model.input" v-bind="inputProps" @pressEnter="onInputPressEnter"/>
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</a-spin>
|
||||
</j-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pick from 'lodash.pick'
|
||||
|
||||
export default {
|
||||
name: 'JPrompt',
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
content: '',
|
||||
// 弹窗参数
|
||||
modalProps: {
|
||||
title: ''
|
||||
},
|
||||
inputProps: {
|
||||
placeholder: ''
|
||||
},
|
||||
// form model
|
||||
model: {
|
||||
input: ''
|
||||
},
|
||||
// 校验
|
||||
rule: [],
|
||||
// 回调函数
|
||||
callback: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rules () {
|
||||
return {
|
||||
input: this.rule
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show (options) {
|
||||
this.content = options.content
|
||||
if (Array.isArray(options.rule)) {
|
||||
this.rule = options.rule
|
||||
}
|
||||
if (options.defaultValue != null) {
|
||||
this.model.input = options.defaultValue
|
||||
}
|
||||
// 取出常用的弹窗参数
|
||||
const pickModalProps = pick(options, 'title', 'centered', 'cancelText', 'closable', 'mask', 'maskClosable', 'okText', 'okType', 'okButtonProps', 'cancelButtonProps', 'width', 'wrapClassName', 'zIndex', 'dialogStyle', 'dialogClass')
|
||||
this.modalProps = Object.assign({}, pickModalProps, options.modalProps)
|
||||
// 取出常用的input参数
|
||||
const pickInputProps = pick(options, 'placeholder', 'allowClear')
|
||||
this.inputProps = Object.assign({}, pickInputProps, options.inputProps)
|
||||
// 回调函数
|
||||
this.callback = pick(options, 'onOk', 'onOkAsync', 'onCancel')
|
||||
this.visible = true
|
||||
this.$nextTick(() => this.$refs.input.focus())
|
||||
},
|
||||
|
||||
onOk () {
|
||||
this.$refs.form.validate((ok) => {
|
||||
if (ok) {
|
||||
const event = { value: this.model.input, target: this }
|
||||
// 异步方法优先级高于同步方法
|
||||
if (typeof this.callback.onOkAsync === 'function') {
|
||||
this.callback.onOkAsync(event)
|
||||
} else if (typeof this.callback.onOk === 'function') {
|
||||
this.callback.onOk(event)
|
||||
this.close()
|
||||
} else {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onCancel () {
|
||||
if (typeof this.callback.onCancel === 'function') {
|
||||
this.callback.onCancel(this.model.input)
|
||||
}
|
||||
this.close()
|
||||
},
|
||||
|
||||
onInputPressEnter () {
|
||||
this.onOk()
|
||||
},
|
||||
|
||||
close () {
|
||||
this.visible = this.loading ? this.visible : false
|
||||
},
|
||||
|
||||
forceClose () {
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
showLoading () {
|
||||
this.loading = true
|
||||
},
|
||||
hideLoading () {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
afterClose (e) {
|
||||
if (typeof this.modalProps.afterClose === 'function') {
|
||||
this.modalProps.afterClose(e)
|
||||
}
|
||||
this.$emit('after-close', e)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,18 @@
|
||||
import JModal from './JModal'
|
||||
import JPrompt from './JPrompt'
|
||||
|
||||
export default {
|
||||
install (Vue) {
|
||||
Vue.component(JModal.name, JModal)
|
||||
|
||||
const JPromptExtend = Vue.extend(JPrompt)
|
||||
Vue.prototype.$JPrompt = function (options = {}) {
|
||||
// 创建prompt实例
|
||||
const vm = new JPromptExtend().$mount()
|
||||
vm.show(options)
|
||||
// 关闭后销毁
|
||||
vm.$on('after-close', () => vm.$destroy())
|
||||
return vm
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
<template>
|
||||
<a-modal
|
||||
ref="modal"
|
||||
:class="getClass(modalClass)"
|
||||
:style="getStyle(modalStyle)"
|
||||
:visible="visible"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
destroyOnClose
|
||||
>
|
||||
|
||||
<slot></slot>
|
||||
<!--有设置标题-->
|
||||
<template v-if="!isNoTitle" slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col class="left">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</a-col>
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<!--没有设置标题-->
|
||||
<template v-else slot="title">
|
||||
<a-row class="j-modal-title-row" type="flex">
|
||||
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
|
||||
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 处理 scopedSlots -->
|
||||
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
<!-- 处理 slots -->
|
||||
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
|
||||
<slot :name="slotName"></slot>
|
||||
</template>
|
||||
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { getClass, getStyle } from '@/utils/props-util'
|
||||
import { triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JModal',
|
||||
props: {
|
||||
title: String,
|
||||
// 可使用 .sync 修饰符
|
||||
visible: Boolean,
|
||||
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许切换全屏(允许后右上角会出现一个按钮)
|
||||
switchFullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击确定按钮的时候是否关闭弹窗
|
||||
okClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 内部使用的 slots ,不再处理
|
||||
usedSlots: ['title'],
|
||||
// 实际控制是否全屏的参数
|
||||
innerFullscreen: this.fullscreen
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
|
||||
_attrs () {
|
||||
const attrs = { ...this.$attrs }
|
||||
// 如果全屏就将宽度设为 100%
|
||||
if (this.innerFullscreen) {
|
||||
attrs.width = '100%'
|
||||
}
|
||||
return attrs
|
||||
},
|
||||
modalClass () {
|
||||
return {
|
||||
'j-modal-box': true,
|
||||
fullscreen: this.innerFullscreen,
|
||||
'no-title': this.isNoTitle,
|
||||
'no-footer': this.isNoFooter
|
||||
}
|
||||
},
|
||||
modalStyle () {
|
||||
const style = {}
|
||||
// 如果全屏就将top设为 0
|
||||
if (this.innerFullscreen) {
|
||||
style.top = '0'
|
||||
}
|
||||
return style
|
||||
},
|
||||
isNoTitle () {
|
||||
return !this.title && !this.allSlotsKeys.includes('title')
|
||||
},
|
||||
isNoFooter () {
|
||||
return this._attrs.footer === null
|
||||
},
|
||||
slotsKeys () {
|
||||
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
scopedSlotsKeys () {
|
||||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys () {
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon () {
|
||||
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible () {
|
||||
if (this.visible) {
|
||||
this.innerFullscreen = this.fullscreen
|
||||
}
|
||||
},
|
||||
innerFullscreen (val) {
|
||||
this.$emit('update:fullscreen', val)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getClass (clazz) {
|
||||
return { ...getClass(this), ...clazz }
|
||||
},
|
||||
getStyle (style) {
|
||||
return { ...getStyle(this), ...style }
|
||||
},
|
||||
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
if (this.okClose) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 切换全屏 */
|
||||
toggleFullscreen () {
|
||||
this.innerFullscreen = !this.innerFullscreen
|
||||
triggerWindowResizeEvent()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-modal-box {
|
||||
&.fullscreen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
|
||||
// 兼容1.6.2版本的antdv
|
||||
& .ant-modal {
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
& .ant-modal-content {
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
|
||||
& .ant-modal-body {
|
||||
/* title 和 footer 各占 55px */
|
||||
height: calc(100% - 55px - 55px);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-title, &.no-footer {
|
||||
.ant-modal-body {
|
||||
height: calc(100% - 55px);
|
||||
}
|
||||
}
|
||||
&.no-title.no-footer {
|
||||
.ant-modal-body {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-modal-title-row {
|
||||
.left {
|
||||
width: calc(100% - 56px - 56px);
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 56px;
|
||||
position: inherit;
|
||||
|
||||
.ant-modal-close {
|
||||
right: 56px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.no-title{
|
||||
.ant-modal-header {
|
||||
padding: 0 24px;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.j-modal-box.fullscreen {
|
||||
margin: 0;
|
||||
max-width: 100vw;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="components-input-demo-presuffix" v-if="avalid">
|
||||
<!---->
|
||||
<a-input @click="openModal" :placeholder="placeholder" v-model="showText" readOnly :disabled="disabled">
|
||||
<a-icon slot="prefix" type="cluster" :title="title"/>
|
||||
<a-icon v-if="showText" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</a-input>
|
||||
|
||||
<j-popup-onl-report
|
||||
ref="jPopupOnlReport"
|
||||
:code="code"
|
||||
:multi="multi"
|
||||
:sorter="sorter"
|
||||
:groupId="uniqGroupId"
|
||||
:param="param"
|
||||
@ok="callBack"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JPopupOnlReport from './modal/JPopupOnlReport'
|
||||
|
||||
export default {
|
||||
name: 'JPopup',
|
||||
components: {
|
||||
JPopupOnlReport
|
||||
},
|
||||
props: {
|
||||
code: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
orgFields: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
destFields: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
/** 排序列,指定要排序的列,使用方式:列名=desc|asc */
|
||||
sorter: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 1200,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// popup动态参数 支持系统变量语法
|
||||
param: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {}
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
/** 分组ID,用于将多个popup的请求合并到一起,不传不分组 */
|
||||
groupId: String
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showText: '',
|
||||
title: '',
|
||||
avalid: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uniqGroupId () {
|
||||
if (this.groupId) {
|
||||
const { groupId, code, field, orgFields, destFields } = this
|
||||
return `${groupId}_${code}_${field}_${orgFields}_${destFields}`
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: function (val) {
|
||||
if (!val) {
|
||||
this.showText = ''
|
||||
} else {
|
||||
this.showText = val.split(this.spliter).join(',')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
mounted () {
|
||||
if (!this.orgFields || !this.destFields || !this.code) {
|
||||
this.$message.error('popup参数未正确配置!')
|
||||
this.avalid = false
|
||||
}
|
||||
if (this.destFields.split(',').length !== this.orgFields.split(',').length) {
|
||||
this.$message.error('popup参数未正确配置,原始值和目标值数量不一致!')
|
||||
this.avalid = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal () {
|
||||
if (this.disabled === false) {
|
||||
this.$refs.jPopupOnlReport.show()
|
||||
}
|
||||
},
|
||||
handleEmpty () {
|
||||
this.showText = ''
|
||||
const destFieldsArr = this.destFields.split(',')
|
||||
if (destFieldsArr.length === 0) {
|
||||
return
|
||||
}
|
||||
const res = {}
|
||||
for (let i = 0; i < destFieldsArr.length; i++) {
|
||||
res[destFieldsArr[i]] = ''
|
||||
}
|
||||
if (this.triggerChange) {
|
||||
this.$emit('callback', res)
|
||||
} else {
|
||||
this.$emit('input', '', res)
|
||||
}
|
||||
},
|
||||
callBack (rows) {
|
||||
// update--begin--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
|
||||
const orgFieldsArr = this.orgFields.split(',')
|
||||
const destFieldsArr = this.destFields.split(',')
|
||||
let resetText = false
|
||||
if (this.field && this.field.length > 0) {
|
||||
this.showText = ''
|
||||
resetText = true
|
||||
}
|
||||
const res = {}
|
||||
if (orgFieldsArr.length > 0) {
|
||||
for (let i = 0; i < orgFieldsArr.length; i++) {
|
||||
const tempDestArr = []
|
||||
for (const rw of rows) {
|
||||
let val = rw[orgFieldsArr[i]]
|
||||
// update--begin--autor:liusq-----date:20210713------for:处理val等于0的情况issues/I3ZL4T------
|
||||
if (typeof val === 'undefined' || val == null || val.toString() === '') {
|
||||
val = ''
|
||||
}
|
||||
// update--end--autor:liusq-----date:20210713------for:处理val等于0的情况issues/I3ZL4T------
|
||||
tempDestArr.push(val)
|
||||
}
|
||||
res[destFieldsArr[i]] = tempDestArr.join(',')
|
||||
}
|
||||
if (resetText === true) {
|
||||
const tempText = []
|
||||
for (const rw of rows) {
|
||||
let val = rw[orgFieldsArr[destFieldsArr.indexOf(this.field)]]
|
||||
if (!val) {
|
||||
val = ''
|
||||
}
|
||||
tempText.push(val)
|
||||
}
|
||||
this.showText = tempText.join(',')
|
||||
}
|
||||
// update--end--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
|
||||
}
|
||||
if (this.triggerChange) {
|
||||
// v-dec时即triggerChange为true时 将整个对象给form页面 让他自己setFieldsValue
|
||||
this.$emit('callback', res)
|
||||
} else {
|
||||
// v-model时 需要传一个参数field 表示当前这个字段 从而根据这个字段的顺序找到原始值
|
||||
// this.$emit("input",row[orgFieldsArr[destFieldsArr.indexOf(this.field)]])
|
||||
let str = ''
|
||||
if (this.showText) {
|
||||
str = this.showText.split(',').join(this.spliter)
|
||||
}
|
||||
this.$emit('input', str, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.components-input-demo-presuffix .anticon-close-circle {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
transition: color 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.components-input-demo-presuffix .anticon-close-circle:hover {
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.components-input-demo-presuffix .anticon-close-circle:active {
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
:value="arrayValue"
|
||||
@change="onChange"
|
||||
option-filter-prop="children"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="(item,index) in newOptions"
|
||||
:key="index"
|
||||
:getPopupContainer="getParentContainer"
|
||||
:value="item.value">
|
||||
{{ item.text || item.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// option {label:,value:}
|
||||
export default {
|
||||
name: 'JSelectMultiple',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
spliter: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ','
|
||||
},
|
||||
popContainer: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
newOptions: [],
|
||||
arrayValue: !this.value ? [] : this.value.split(this.spliter) // arrayValue是已选中的数据
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.arrayValue = []
|
||||
} else {
|
||||
this.arrayValue = this.value.split(this.spliter)
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.newOptions = this.options
|
||||
},
|
||||
methods: {
|
||||
onChange (selectedValue) {
|
||||
this.newOptions = this.options
|
||||
if (this.triggerChange) {
|
||||
this.$emit('change', selectedValue.join(this.spliter))
|
||||
} else {
|
||||
this.$emit('input', selectedValue.join(this.spliter))
|
||||
}
|
||||
},
|
||||
getParentContainer (node) {
|
||||
if (!this.popContainer) {
|
||||
return node.parentNode
|
||||
} else {
|
||||
return document.querySelector(this.popContainer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div class="drag" ref="dragDiv">
|
||||
<div class="drag_bg"></div>
|
||||
<div class="drag_text">{{confirmWords}}</div>
|
||||
<div ref="moveDiv" @mousedown="mousedownFn($event)" :class="{'handler_ok_bg':confirmSuccess}" class="handler handler_bg" style="border: 0.5px solid #fff;height: 34px;position: absolute;top: 0;left: 0;"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JSlider',
|
||||
data () {
|
||||
return {
|
||||
beginClientX: 0, /* 距离屏幕左端距离 */
|
||||
mouseMoveStata: false, /* 触发拖动状态 判断 */
|
||||
maxwidth: '', /* 拖动最大宽度,依据滑块宽度算出来的 */
|
||||
confirmWords: '拖动滑块验证', /* 滑块文字 */
|
||||
confirmSuccess: false /* 验证成功判断 */
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isSuccess () {
|
||||
return this.confirmSuccess
|
||||
},
|
||||
mousedownFn: function (e) {
|
||||
if (!this.confirmSuccess) {
|
||||
e.preventDefault && e.preventDefault() // 阻止文字选中等 浏览器默认事件
|
||||
this.mouseMoveStata = true
|
||||
this.beginClientX = e.clientX
|
||||
}
|
||||
}, // mousedoen 事件
|
||||
successFunction () {
|
||||
this.confirmSuccess = true
|
||||
this.confirmWords = '验证通过'
|
||||
if (window.addEventListener) {
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mousemove', this.mouseMoveFn)
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mouseup', this.moseUpFn)
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].removeEventListener('mouseup', () => {})
|
||||
}
|
||||
document.getElementsByClassName('drag_text')[0].style.color = '#fff'
|
||||
document.getElementsByClassName('handler')[0].style.left = this.maxwidth + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = this.maxwidth + 'px'
|
||||
|
||||
this.$emit('onSuccess', true)
|
||||
}, // 验证成功函数
|
||||
mouseMoveFn (e) {
|
||||
if (this.mouseMoveStata) {
|
||||
const width = e.clientX - this.beginClientX
|
||||
if (width > 0 && width <= this.maxwidth) {
|
||||
document.getElementsByClassName('handler')[0].style.left = width + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = width + 'px'
|
||||
} else if (width > this.maxwidth) {
|
||||
this.successFunction()
|
||||
}
|
||||
}
|
||||
}, // mousemove事件
|
||||
moseUpFn (e) {
|
||||
this.mouseMoveStata = false
|
||||
var width = e.clientX - this.beginClientX
|
||||
if (width < this.maxwidth) {
|
||||
// ---- update-begin- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
|
||||
const handler = document.getElementsByClassName('handler')[0]
|
||||
if (handler) {
|
||||
handler.style.left = 0 + 'px'
|
||||
document.getElementsByClassName('drag_bg')[0].style.width = 0 + 'px'
|
||||
}
|
||||
// ---- update-end- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
|
||||
}
|
||||
} // mouseup事件
|
||||
},
|
||||
mounted () {
|
||||
this.maxwidth = this.$refs.dragDiv.clientWidth - this.$refs.moveDiv.clientWidth
|
||||
document.getElementsByTagName('html')[0].addEventListener('mousemove', this.mouseMoveFn)
|
||||
document.getElementsByTagName('html')[0].addEventListener('mouseup', this.moseUpFn)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.drag{
|
||||
position: relative;
|
||||
background-color: #e8e8e8;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
.handler{
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
border: 1px solid #ccc;
|
||||
cursor: move;
|
||||
}
|
||||
.handler_bg{
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
.handler_ok_bg{
|
||||
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==") no-repeat center;
|
||||
}
|
||||
.drag_bg{
|
||||
background-color: #7ac23c;
|
||||
height: 34px;
|
||||
width: 0;
|
||||
}
|
||||
.drag_text{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;text-align: center;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-o-user-select:none;
|
||||
-ms-user-select:none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,678 @@
|
||||
<template>
|
||||
<div class="j-super-query-box">
|
||||
|
||||
<slot name="button" :isActive="superQueryFlag" :isMobile="izMobile" :open="handleOpen" :reset="handleReset">
|
||||
<a-tooltip v-if="superQueryFlag" v-bind="tooltipProps" :mouseLeaveDelay="0.2">
|
||||
<!-- begin 不知道为什么不加上这段代码就无法生效 -->
|
||||
<span v-show="false">{{tooltipProps}}</span>
|
||||
<!-- end 不知道为什么不加上这段代码就无法生效 -->
|
||||
<template slot="title">
|
||||
<span>{{ $t('superQuery.advancedQueryEffective') }}</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleReset">{{ $t('empty') }}</a>
|
||||
</template>
|
||||
<a-button-group>
|
||||
<a-button type="primary" @click="handleOpen">
|
||||
<a-icon type="appstore" theme="twoTone" spin/>
|
||||
<span>{{ $t('superQuery.advancedQuery') }}</span>
|
||||
</a-button>
|
||||
<a-button v-if="izMobile" type="primary" icon="delete" @click="handleReset"/>
|
||||
</a-button-group>
|
||||
</a-tooltip>
|
||||
<a-button v-else type="primary" icon="filter" @click="handleOpen">{{ $t('superQuery.advancedQuery') }}</a-button>
|
||||
</slot>
|
||||
|
||||
<j-modal
|
||||
:title="$t('superQuery.advancedQuery')+$t('superQuery.constructor')"
|
||||
:width="1000"
|
||||
:visible="visible"
|
||||
@cancel="handleCancel"
|
||||
:mask="false"
|
||||
:fullscreen="izMobile"
|
||||
class="j-super-query-modal"
|
||||
style="top:5%;max-height: 95%;"
|
||||
>
|
||||
|
||||
<template slot="footer">
|
||||
<div style="float: left">
|
||||
<a-button :loading="loading" @click="handleReset">{{ $t('reset') }}</a-button>
|
||||
<a-button :loading="loading" @click="handleSave">{{ $t('superQuery.saveQueryCriteria') }}</a-button>
|
||||
</div>
|
||||
<a-button :loading="loading" @click="handleCancel">{{ $t('close') }}</a-button>
|
||||
<a-button :loading="loading" type="primary" @click="handleOk">{{ $t('query') }}</a-button>
|
||||
</template>
|
||||
|
||||
<a-spin :spinning="loading">
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="24-5">
|
||||
|
||||
<a-empty v-if="queryParamsModel.length === 0" style="margin-bottom: 12px;">
|
||||
<div slot="description">
|
||||
<span>{{ $t('no') }}+{{ $t('query') }}+{{ $t('condition') }}</span>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="handleAdd">{{ $t('click') }}+{{ $t('newlyAdded') }}</a>
|
||||
</div>
|
||||
</a-empty>
|
||||
|
||||
<a-form v-else layout="inline">
|
||||
|
||||
<a-row style="margin-bottom: 12px;">
|
||||
<a-col :md="12" :xs="24">
|
||||
<a-form-item :label="$t('superQuery.filterMatching')" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
|
||||
<a-select v-model="matchType" :getPopupContainer="node=>node.parentNode" style="width: 100%;">
|
||||
<a-select-option value="and">AND{{$t('superQuery.allMatching')}}</a-select-option>
|
||||
<a-select-option value="or">OR{{$t('superQuery.anyOneMatches')}}</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row type="flex" style="margin-bottom:10px" :gutter="16" v-for="(item, index) in queryParamsModel" :key="index">
|
||||
|
||||
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
|
||||
<a-tree-select
|
||||
:showSearch="true"
|
||||
v-model="item.field"
|
||||
:treeData="fieldTreeData"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="$t('superQuery.selectQueryField')"
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
:getPopupContainer="node=>node.parentNode"
|
||||
style="width: 100%"
|
||||
@select="(val,option)=>handleSelected(option,item)"
|
||||
>
|
||||
</a-tree-select>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="4" :xs="24" style="margin-bottom: 12px;">
|
||||
<a-select :placeholder="$t('superQuery.matchRules')" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
|
||||
<a-select-option value="eq">{{ $t('superQuery.beEqualTo') }}</a-select-option>
|
||||
<a-select-option value="like">{{ $t('superQuery.contain') }}</a-select-option>
|
||||
<a-select-option value="right_like">{{ $t('superQuery.withStart') }}</a-select-option>
|
||||
<a-select-option value="left_like">{{ $t('superQuery.withEnd') }}</a-select-option>
|
||||
<a-select-option value="in">{{ $t('superQuery.in') }}</a-select-option>
|
||||
<a-select-option value="ne">{{ $t('superQuery.notEqual') }}</a-select-option>
|
||||
<a-select-option value="gt">{{ $t('superQuery.granter') }}</a-select-option>
|
||||
<a-select-option value="ge">{{ $t('superQuery.greaterOrEqual') }}</a-select-option>
|
||||
<a-select-option value="lt">{{ $t('superQuery.less') }}</a-select-option>
|
||||
<a-select-option value="le">{{ $t('superQuery.lessOrEqual') }}</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
|
||||
<!-- 下拉搜索 -->
|
||||
<j-search-select-tag v-if="item.type==='sel_search'" v-model="item.val" :dict="getDictInfo(item)" placeholder="请选择"/>
|
||||
<!-- 下拉多选 -->
|
||||
<template v-else-if="item.type==='list_multi'">
|
||||
<j-multi-select-tag v-if="item.options" v-model="item.val" :options="item.options" :placeholder="$t('pleaseSelect')"/>
|
||||
<j-multi-select-tag v-else v-model="item.val" :dictCode="getDictInfo(item)" :placeholder="$t('pleaseSelect')"/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.dictCode">
|
||||
<template v-if="item.type === 'table-dict'">
|
||||
<j-popup
|
||||
v-model="item.val"
|
||||
:code="item.dictTable"
|
||||
:field="item.dictCode"
|
||||
:orgFields="item.dictCode"
|
||||
:destFields="item.dictCode"
|
||||
:multi="true"
|
||||
></j-popup>
|
||||
</template>
|
||||
<template v-else>
|
||||
<j-multi-select-tag v-show="allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" :placeholder="$t('pleaseSelect')"/>
|
||||
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" :placeholder="$t('pleaseSelect')"/>
|
||||
</template>
|
||||
</template>
|
||||
<j-popup
|
||||
v-else-if="item.type === 'popup'"
|
||||
:value="item.val"
|
||||
v-bind="item.popup"
|
||||
group-id="superQuery"
|
||||
@input="(e,v)=>handleChangeJPopup(item,e,v)"
|
||||
:multi="true"/>
|
||||
<j-select-multi-user
|
||||
v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
|
||||
v-model="item.val"
|
||||
:buttons="false"
|
||||
:multiple="false"
|
||||
:placeholder="$t('pleaseSelect')+$t('superQuery.user')"
|
||||
:returnKeys="['id', item.customReturnField || 'username']"
|
||||
/>
|
||||
<j-select-depart
|
||||
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
|
||||
v-model="item.val"
|
||||
:multi="false"
|
||||
:placeholder="$t('pleaseSelect')+$t('department')"
|
||||
:customReturnField="item.customReturnField || 'id'"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="item.options instanceof Array"
|
||||
v-model="item.val"
|
||||
:options="item.options"
|
||||
allowClear
|
||||
:placeholder="$t('pleaseSelect')"
|
||||
:mode="allowMultiple(item)?'multiple':''"
|
||||
/>
|
||||
<j-area-linkage v-model="item.val" v-else-if="item.type==='area-linkage' || item.type==='pca'" style="width: 100%"/>
|
||||
<j-date v-else-if=" item.type + '' === 'date' " v-model="item.val" :placeholder="$t('pleaseSelect')+$t('date')" style="width: 100%"></j-date>
|
||||
<j-date v-else-if=" item.type + '' ==='datetime' " v-model="item.val" :placeholder="$t('pleaseSelect')+$t('time')" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"></j-date>
|
||||
<a-time-picker v-else-if="item.type + '' === 'time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
|
||||
<a-input-number v-else-if=" item.type + '' === 'int'||item.type + '' === 'number' " style="width: 100%" :placeholder="$t('pleaseSelect')+$t('superQuery.numericalValue')" v-model="item.val"/>
|
||||
<a-select v-else-if="item.type + '' === 'switch'" :placeholder="$t('pleaseSelect')" v-model="item.val">
|
||||
<a-select-option value="Y">{{ $t('yes') }}</a-select-option>
|
||||
<a-select-option value="N">{{ $t('not') }}</a-select-option>
|
||||
</a-select>
|
||||
<a-input v-else v-model="item.val" :placeholder="$t('superQuery.enterValue')"/>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="4" :xs="0" style="margin-bottom: 12px;">
|
||||
<a-button @click="handleAdd" icon="plus"></a-button>
|
||||
<a-button @click="handleDel( index )" icon="minus"></a-button>
|
||||
</a-col>
|
||||
|
||||
<a-col :md="0" :xs="24" style="margin-bottom: 12px;text-align: right;">
|
||||
<a-button @click="handleAdd" icon="plus"></a-button>
|
||||
<a-button @click="handleDel( index )" icon="minus"></a-button>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
|
||||
</a-form>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="5">
|
||||
<!-- 查询记录 -->
|
||||
|
||||
<a-card class="j-super-query-history-card" :bordered="true">
|
||||
<div slot="title">
|
||||
{{ $t('superQuery.savedQuery') }}
|
||||
</div>
|
||||
|
||||
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" :description="$t('superQuery.noQueriesSaved')"/>
|
||||
<a-tree
|
||||
v-else
|
||||
class="j-super-query-history-tree"
|
||||
:showIcon="true"
|
||||
:treeData="saveTreeData"
|
||||
:selectedKeys="[]"
|
||||
@select="handleTreeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
</a-card>
|
||||
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
</a-spin>
|
||||
|
||||
<a-modal :title="$t('pleaseEnter')+$t('superQuery.savedName')" :visible="prompt.visible" @cancel="prompt.visible=false" @ok="handlePromptOk">
|
||||
<a-input v-model="prompt.value"></a-input>
|
||||
</a-modal>
|
||||
|
||||
</j-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import * as utils from '@/utils/util'
|
||||
import { mixinDevice } from '@/utils/mixin'
|
||||
import JDate from '@/components/jero/JDate.vue'
|
||||
import JSelectDepart from '@/components/jerobiz/JSelectDepart'
|
||||
import JSelectMultiUser from '@/components/jerobiz/JSelectMultiUser'
|
||||
import JMultiSelectTag from '@/components/dict/JMultiSelectTag'
|
||||
import JAreaLinkage from '@comp/jero/JAreaLinkage'
|
||||
|
||||
export default {
|
||||
name: 'JSuperQuery',
|
||||
mixins: [mixinDevice],
|
||||
components: { JAreaLinkage, JMultiSelectTag, JDate, JSelectDepart, JSelectMultiUser },
|
||||
props: {
|
||||
/*
|
||||
fieldList: [{
|
||||
value:'',
|
||||
text:'',
|
||||
type:'',
|
||||
dictCode:'' // 只要 dictCode 有值,无论 type 是什么,都显示为字典下拉框
|
||||
}]
|
||||
type:date datetime int number string
|
||||
* */
|
||||
fieldList: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
/*
|
||||
* 这个回调函数接收一个数组参数 即查询条件
|
||||
* */
|
||||
callback: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'handleSuperQuery'
|
||||
},
|
||||
|
||||
// 当前是否在加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// 保存查询条件的唯一 code,通过该 code 区分
|
||||
// 默认为 null,代表以当前路由全路径为区分Code
|
||||
saveCode: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
moment,
|
||||
fieldTreeData: [],
|
||||
|
||||
prompt: {
|
||||
visible: false,
|
||||
value: ''
|
||||
},
|
||||
|
||||
visible: false,
|
||||
queryParamsModel: [],
|
||||
treeIcon: <a-icon type="file-text"/>,
|
||||
// 保存查询条件的treeData
|
||||
saveTreeData: [],
|
||||
// 保存查询条件的前缀名
|
||||
saveCodeBefore: 'JSuperQuerySaved_',
|
||||
// 查询类型,过滤条件匹配(and、or)
|
||||
matchType: 'and',
|
||||
superQueryFlag: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
izMobile () {
|
||||
return this.device === 'mobile'
|
||||
},
|
||||
tooltipProps () {
|
||||
return this.izMobile ? { visible: false } : {}
|
||||
},
|
||||
fullSaveCode () {
|
||||
let saveCode = this.saveCode
|
||||
if (saveCode == null || saveCode === '') {
|
||||
saveCode = this.$route.fullPath
|
||||
}
|
||||
return this.saveCodeBefore + saveCode
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当 saveCode 变化时,重新查询已保存的条件
|
||||
fullSaveCode: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
const list = this.$ls.get(this.fullSaveCode)
|
||||
if (list instanceof Array) {
|
||||
this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
|
||||
}
|
||||
}
|
||||
},
|
||||
fieldList: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
const mainData = []; const subData = []
|
||||
val.forEach(item => {
|
||||
const data = { ...item }
|
||||
data.label = data.label || data.text
|
||||
const hasChildren = (data.children instanceof Array)
|
||||
data.disabled = hasChildren
|
||||
data.selectable = !hasChildren
|
||||
if (hasChildren) {
|
||||
data.children = data.children.map(item2 => {
|
||||
const child = { ...item2 }
|
||||
child.label = child.label || child.text
|
||||
child.label = data.label + '-' + child.label
|
||||
child.value = data.value + ',' + child.value
|
||||
child.val = ''
|
||||
return child
|
||||
})
|
||||
data.val = ''
|
||||
subData.push(data)
|
||||
} else {
|
||||
mainData.push(data)
|
||||
}
|
||||
})
|
||||
this.fieldTreeData = mainData.concat(subData)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
show () {
|
||||
if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
|
||||
this.resetLine()
|
||||
}
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
getDictInfo (item) {
|
||||
let str = ''
|
||||
if (!item.dictTable) {
|
||||
str = item.dictCode
|
||||
} else {
|
||||
str = item.dictTable + ',' + item.dictText + ',' + item.dictCode
|
||||
}
|
||||
console.log('高级查询字典信息', str)
|
||||
return str
|
||||
},
|
||||
handleOk () {
|
||||
if (!this.isNullArray(this.queryParamsModel)) {
|
||||
const event = {
|
||||
matchType: this.matchType,
|
||||
params: this.removeEmptyObject(this.queryParamsModel)
|
||||
}
|
||||
// 移动端模式下关闭弹窗
|
||||
if (this.izMobile) {
|
||||
this.visible = false
|
||||
}
|
||||
this.emitCallback(event)
|
||||
} else {
|
||||
this.$message.warn(this.$t('superQuery.cannotQueryEmpty'))
|
||||
}
|
||||
},
|
||||
emitCallback (event = {}) {
|
||||
const { params = [], matchType = this.matchType } = event
|
||||
this.superQueryFlag = (params && params.length > 0)
|
||||
for (const param of params) {
|
||||
if (Array.isArray(param.val)) {
|
||||
param.val = param.val.join(',')
|
||||
}
|
||||
}
|
||||
console.debug('---高级查询参数--->', { params, matchType })
|
||||
this.$emit(this.callback, params, matchType)
|
||||
},
|
||||
handleCancel () {
|
||||
this.close()
|
||||
},
|
||||
close () {
|
||||
this.$emit('close')
|
||||
this.visible = false
|
||||
},
|
||||
handleAdd () {
|
||||
this.addNewLine()
|
||||
},
|
||||
addNewLine () {
|
||||
this.queryParamsModel.push({ rule: 'eq' })
|
||||
},
|
||||
resetLine () {
|
||||
this.superQueryFlag = false
|
||||
this.queryParamsModel = []
|
||||
this.addNewLine()
|
||||
},
|
||||
handleDel (index) {
|
||||
this.queryParamsModel.splice(index, 1)
|
||||
},
|
||||
handleSelected (node, item) {
|
||||
const { type, dbType, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
|
||||
item.type = type
|
||||
item.dbType = dbType
|
||||
item.options = options
|
||||
item.dictCode = dictCode
|
||||
item.dictTable = dictTable
|
||||
item.dictText = dictText
|
||||
item.customReturnField = customReturnField
|
||||
if (popup) {
|
||||
item.popup = popup
|
||||
}
|
||||
this.$set(item, 'val', undefined)
|
||||
},
|
||||
handleOpen () {
|
||||
this.show()
|
||||
},
|
||||
handleReset () {
|
||||
this.resetLine()
|
||||
this.emitCallback()
|
||||
},
|
||||
handleSave () {
|
||||
const queryParams = this.removeEmptyObject(this.queryParamsModel)
|
||||
if (this.isNullArray(queryParams)) {
|
||||
this.$message.warning(this.$t('superQuery.emptyCannotSaved'))
|
||||
} else {
|
||||
this.prompt.value = ''
|
||||
this.prompt.visible = true
|
||||
}
|
||||
},
|
||||
handlePromptOk () {
|
||||
const { value } = this.prompt
|
||||
if (!value) {
|
||||
this.$message.warning(this.$t('preservation') + this.$t('name') + this.$t('cannotEmpty'))
|
||||
return
|
||||
}
|
||||
// 取出查询条件
|
||||
const records = this.removeEmptyObject(this.queryParamsModel)
|
||||
// 判断有没有重名的
|
||||
const filterList = this.saveTreeData.filter(i => i.originTitle === value)
|
||||
if (filterList.length > 0) {
|
||||
this.$confirm({
|
||||
content: `${value} ` + this.$t('superQuery.alreadyExists'),
|
||||
onOk: () => {
|
||||
this.prompt.visible = false
|
||||
filterList[0].records = records
|
||||
this.saveToLocalStore()
|
||||
this.$message.success(this.$t('savedSuccessfully'))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 没有重名的,直接添加
|
||||
this.prompt.visible = false
|
||||
// 添加到树列表中
|
||||
this.saveTreeData.push(this.renderSaveTreeData({
|
||||
title: value,
|
||||
matchType: this.matchType,
|
||||
records: records
|
||||
}))
|
||||
// 保存到 LocalStore
|
||||
this.saveToLocalStore()
|
||||
this.$message.success(this.$t('SavedSuccessfully'))
|
||||
}
|
||||
},
|
||||
handleTreeSelect (idx, event) {
|
||||
if (event.selectedNodes[0]) {
|
||||
const { matchType, records } = event.selectedNodes[0].data.props
|
||||
// 将保存的matchType取出,兼容旧数据,如果没有保存就还是使用原来的
|
||||
this.matchType = matchType || this.matchType
|
||||
this.queryParamsModel = utils.cloneObject(records)
|
||||
}
|
||||
},
|
||||
handleRemoveSaveTreeItem (event, vNode) {
|
||||
// 阻止事件冒泡
|
||||
event.stopPropagation()
|
||||
|
||||
this.$confirm({
|
||||
content: this.$t('superQuery.deleteQuery'),
|
||||
onOk: () => {
|
||||
const { eventKey } = vNode
|
||||
this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
|
||||
this.saveToLocalStore()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 将查询保存到 LocalStore 里
|
||||
saveToLocalStore () {
|
||||
const saveValue = this.saveTreeData.map(({ originTitle, matchType, records }) => ({ title: originTitle, matchType, records }))
|
||||
this.$ls.set(this.fullSaveCode, saveValue)
|
||||
},
|
||||
|
||||
isNullArray (array) {
|
||||
// 判断是不是空数组对象
|
||||
if (!array || array.length === 0) {
|
||||
return true
|
||||
}
|
||||
if (array.length === 1) {
|
||||
const obj = array[0]
|
||||
if (!obj.field || (obj.val == null || obj.val === '') || !obj.rule) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
// 去掉数组中的空对象
|
||||
removeEmptyObject (arr) {
|
||||
const array = utils.cloneObject(arr)
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const item = array[i]
|
||||
if (item == null || Object.keys(item).length <= 0) {
|
||||
array.splice(i--, 1)
|
||||
} else {
|
||||
if (Array.isArray(item.options)) {
|
||||
// 如果有字典属性,就不需要保存 options 了
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
delete item.options
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
},
|
||||
|
||||
/** 渲染保存查询条件的 title(加个删除按钮) */
|
||||
renderSaveTreeData (item) {
|
||||
item.icon = this.treeIcon
|
||||
item.originTitle = item.title
|
||||
item.title = (arg1, arg2) => {
|
||||
let vNode
|
||||
// 兼容旧版的Antdv
|
||||
if (arg1.dataRef) {
|
||||
vNode = arg1
|
||||
} else if (arg2.dataRef) {
|
||||
vNode = arg2
|
||||
} else {
|
||||
return <span style="color:red;">Antdv版本不支持</span>
|
||||
}
|
||||
const { originTitle } = vNode.dataRef
|
||||
return (
|
||||
<div class="j-history-tree-title">
|
||||
<span>{originTitle}</span>
|
||||
|
||||
<div class="j-history-tree-title-closer" onClick={e => this.handleRemoveSaveTreeItem(e, vNode)}>
|
||||
<a-icon type="close-circle"/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return item
|
||||
},
|
||||
|
||||
/** 判断是否允许多选 */
|
||||
allowMultiple (item) {
|
||||
return item.rule === 'in'
|
||||
},
|
||||
|
||||
handleRuleChange (item, newValue) {
|
||||
const oldValue = item.rule
|
||||
this.$set(item, 'rule', newValue)
|
||||
// 上一个规则是否是 in,且type是字典或下拉
|
||||
if (oldValue === 'in') {
|
||||
if (item.dictCode || item.options instanceof Array) {
|
||||
let value = item.val
|
||||
if (typeof item.val === 'string') {
|
||||
value = item.val.split(',')[0]
|
||||
} else if (Array.isArray(item.val)) {
|
||||
value = item.val[0]
|
||||
}
|
||||
this.$set(item, 'val', value)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeJPopup (item, e, values) {
|
||||
item.val = values[item.popup.destFields]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
.j-super-query-box {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.j-super-query-modal {
|
||||
|
||||
.j-super-query-history-card {
|
||||
/deep/ .ant-card-body,
|
||||
/deep/ .ant-card-head-title {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/deep/ .ant-card-head {
|
||||
padding: 4px 8px;
|
||||
min-height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.j-super-query-history-empty {
|
||||
/deep/ .ant-empty-image {
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/deep/ img {
|
||||
width: 80px;
|
||||
height: 65px;
|
||||
}
|
||||
|
||||
/deep/ .ant-empty-description {
|
||||
color: #afafaf;
|
||||
margin: 8px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.j-super-query-history-tree {
|
||||
|
||||
.j-history-tree-title {
|
||||
width: calc(100% - 24px);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
&-closer {
|
||||
color: #999999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s, color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.j-history-tree-title-closer {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/deep/ .ant-tree-switcher {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/deep/ .ant-tree-node-content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-select
|
||||
v-if="query"
|
||||
style="width: 100%"
|
||||
@change="handleSelectChange"
|
||||
>
|
||||
<a-select-option v-for="(item, index) in queryOption" :key="index" :value="item.value">
|
||||
{{ item.text }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-switch
|
||||
v-else
|
||||
v-model="checkStatus"
|
||||
:disabled="disabled"
|
||||
@change="handleChange"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"/>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'JSwitch',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => ['Y', 'N']
|
||||
},
|
||||
query: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
checkStatus: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
queryOption () {
|
||||
const arr = []
|
||||
arr.push({ value: this.options[0], text: '是' })
|
||||
arr.push({ value: this.options[1], text: '否' })
|
||||
return arr
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (!this.query) {
|
||||
if (!val) {
|
||||
this.checkStatus = false
|
||||
this.$emit('change', this.options[1])
|
||||
} else {
|
||||
this.checkStatus = this.options[0] + '' === val + ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (checked) {
|
||||
const flag = checked === false ? this.options[1] : this.options[0]
|
||||
this.$emit('change', flag)
|
||||
},
|
||||
handleSelectChange (value) {
|
||||
this.$emit('change', value)
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,431 @@
|
||||
<template>
|
||||
<a-table
|
||||
class="j-table"
|
||||
ref="table"
|
||||
:bordered="bordered"
|
||||
:table-key="tableKey"
|
||||
:columns="resultColumns"
|
||||
:components="components"
|
||||
:scroll="scroll || selfScroll"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
|
||||
<!-- 如果插槽是作用域插槽,将作用域插槽的props包装成对象传递给slot -->
|
||||
<template v-for="(_, slotName) in $scopedSlots" :slot="slotName" slot-scope="text, record, index">
|
||||
<slot :name="slotName" v-bind="{text, record, index}" />
|
||||
</template>
|
||||
<!-- 如果是普通插槽,直接使用$slots渲染 -->
|
||||
<slot v-for="(_, slotName) in $slots" :name="slotName" :slot="slotName"/>
|
||||
|
||||
<div slot="settingDropdown">
|
||||
<a-card>
|
||||
<a-table
|
||||
rowKey="key"
|
||||
:style="settingStyle"
|
||||
tableLayout="fixed"
|
||||
size="small"
|
||||
bordered
|
||||
:pagination="false"
|
||||
:scroll="settingScroll"
|
||||
:data-source="settingDataSource"
|
||||
:columns="settingColumns">
|
||||
|
||||
<a-checkbox slot="hide" slot-scope="text, record"
|
||||
:checked="text"
|
||||
:disabled="record.disabledHide"
|
||||
@change="changeSetting(text, 'hide', record)"></a-checkbox>
|
||||
<a-checkbox slot="freeze" slot-scope="text, record"
|
||||
:checked="text"
|
||||
:disabled="record.disabledFreeze"
|
||||
@change="changeSetting(text, 'freeze', record)"></a-checkbox>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</div>
|
||||
<a-icon ref="settingBtn" slot="settingIcon" type="setting" :style="{ fontSize:'16px', color: '#108ee9' }" />
|
||||
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import VueDraggableResizable from 'vue-draggable-resizable'
|
||||
|
||||
Vue.component('vue-draggable-resizable', VueDraggableResizable)
|
||||
|
||||
const actionKey = 'action'
|
||||
// 保存localStorage中的后缀
|
||||
const saveSuffix = ':JTable'
|
||||
// 拿到column的key
|
||||
const getKey = col => col.key || col.dataIndex
|
||||
// 所有JTable的缓存key
|
||||
const J_TABLE_KEYS = 'J_TABLE_KEYS'
|
||||
export default {
|
||||
name: 'JTable',
|
||||
props: {
|
||||
// 用于持久化存储列配置,保证唯一
|
||||
tableKey: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
// 配置表的样式
|
||||
settingStyle: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ width: '300px' })
|
||||
},
|
||||
// 配置表的scroll
|
||||
settingScroll: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({ y: 300 })
|
||||
},
|
||||
// 如果需要拖拽后增加总列宽而不是占用其他列宽度:需要父组件 :scroll.sync="scroll"
|
||||
scroll: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
// 列最小宽度
|
||||
columnMinWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 100
|
||||
},
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// settingVisible: false,
|
||||
// 设置表格
|
||||
settingColumns: [
|
||||
{ title: '列名', dataIndex: 'title', align: 'center', ellipsis: true, width: 150 },
|
||||
{ title: '隐藏', dataIndex: 'hide', align: 'center', ellipsis: true, width: 60, scopedSlots: { customRender: 'hide' } },
|
||||
{ title: '冻结', dataIndex: 'freeze', align: 'center', ellipsis: true, width: 60, scopedSlots: { customRender: 'freeze' } }
|
||||
],
|
||||
settingDataSource: [],
|
||||
settingColumnsObj: { hide: [], freeze: [] }, // 本地存储的配置对象
|
||||
resultColumns: [],
|
||||
components: {
|
||||
header: {
|
||||
cell: this.initDrag(this.columns)
|
||||
}
|
||||
},
|
||||
selfScroll: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 配置列改变,将配置转换成配置表格数据
|
||||
settingColumnsObj: {
|
||||
deep: true, // 深度监听
|
||||
immediate: true, // 挂载先执行一次
|
||||
handler () {
|
||||
const dataSource = []
|
||||
this.columns.forEach(col => {
|
||||
const tempKey = getKey(col)
|
||||
// 操作栏不可配置
|
||||
if (tempKey === actionKey) {
|
||||
return
|
||||
}
|
||||
// hideSettingColumn: true 可以让不显示字段控制
|
||||
if (col.hideSettingColumn) {
|
||||
return
|
||||
}
|
||||
dataSource.push({
|
||||
title: col.title || col.columnTitle, // 用于显示的字段
|
||||
key: tempKey, // 用于存储的字段
|
||||
hide: this.settingColumnsObj.hide.includes(tempKey), // 是否隐藏
|
||||
freeze: this.settingColumnsObj.freeze.includes(tempKey), // 是否冻结
|
||||
disabledHide: col.disabledHide, // 禁用隐藏
|
||||
disabledFreeze: col.disabledFreeze // 禁用冻结
|
||||
})
|
||||
})
|
||||
this.settingDataSource = dataSource
|
||||
// 自定义列配置修改后,更新表格字段
|
||||
this.setResultColumns(this.columns)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 将父组件的columns进行处理:增加设置列按钮
|
||||
setResultColumns (columns) {
|
||||
// 深度克隆,防止直接修改父组件数据
|
||||
columns = cloneDeep(columns)
|
||||
// 设置按钮插槽配置
|
||||
const settingOption = {
|
||||
filterDropdown: 'settingDropdown',
|
||||
filterIcon: 'settingIcon'
|
||||
}
|
||||
let findActionIndex = columns.findIndex(col => col && (getKey(col) === actionKey))
|
||||
// 将设置按钮放到表格右上角,如果有操作栏就固定到右侧(操作栏固定右侧,没有的话就最后一列)
|
||||
if (findActionIndex === -1) {
|
||||
findActionIndex = columns.length - 1
|
||||
}
|
||||
if (columns[findActionIndex].scopedSlots instanceof Object) {
|
||||
Object.assign(columns[findActionIndex].scopedSlots, settingOption)
|
||||
} else {
|
||||
columns[findActionIndex].scopedSlots = settingOption
|
||||
}
|
||||
// 自定义设置弹框(尝试解决表格刷新丢失弹框的问题)此方法会出现两个弹框
|
||||
// columns[findActionIndex].filterDropdownVisible = this.settingVisible
|
||||
// columns[findActionIndex].onFilterDropdownVisibleChange = (visible) => {
|
||||
// this.settingVisible = visible
|
||||
// }
|
||||
|
||||
// 最后过滤掉需要隐藏的字段,按定位进行排序
|
||||
const startArr = [] // 冻结左侧的列
|
||||
const midArr = [] // 没有冻结的列
|
||||
const endArr = [] // 冻结右侧的列(只能是action,暂时不能自定义固定右侧)
|
||||
columns.forEach(col => {
|
||||
const key = getKey(col)
|
||||
const fixed = this.settingColumnsObj.freeze.includes(key)
|
||||
col.hide = this.settingColumnsObj.hide.includes(key)
|
||||
col.fixed = key === actionKey ? 'right' : fixed
|
||||
// 如果是字符串就转换成数字(因为实现拖拽,单位只能是px)
|
||||
col.width = col.width && parseInt(col.width + '')
|
||||
// 如果没有最小宽度就获取统一设置的最小宽度
|
||||
col.minWidth = (col.minWidth && parseInt(col.minWidth + '')) || this.columnMinWidth
|
||||
if (!col.hide) {
|
||||
// 如果没有宽度就取minWidth(不添加原数据的width属性,这里只对需要固定的minWidth生效)
|
||||
const width = col.width || col.minWidth
|
||||
switch (col.fixed) {
|
||||
case true:
|
||||
case 'left':
|
||||
startArr.push({ ...col, width })
|
||||
break
|
||||
case 'right':
|
||||
endArr.push({ ...col, width })
|
||||
break
|
||||
default:
|
||||
midArr.push(col)
|
||||
}
|
||||
}
|
||||
})
|
||||
// 更新父组件的数据,将排序后的进行渲染
|
||||
this.$emit('update:columns', columns)
|
||||
// 按冻结顺序排序
|
||||
this.resultColumns = [
|
||||
...startArr.sort((a, b) => {
|
||||
const freezeArr = this.settingColumnsObj.freeze
|
||||
return freezeArr.indexOf(getKey(a)) - freezeArr.indexOf(getKey(b))
|
||||
}),
|
||||
...midArr,
|
||||
...endArr
|
||||
]
|
||||
return this.resultColumns
|
||||
},
|
||||
// 初始化表格拖拽
|
||||
initDrag (columns) {
|
||||
// 没有边框就不能拖拽
|
||||
if (!this.bordered) {
|
||||
return
|
||||
}
|
||||
// 如果没有任何列开启拖拽就不需要拖拽
|
||||
if (!columns.some(col => col.resizable)) {
|
||||
return
|
||||
}
|
||||
// 第一步:列宽映射
|
||||
const draggingMap = {}
|
||||
columns.forEach((col) => {
|
||||
draggingMap[getKey(col)] = col.width
|
||||
})
|
||||
const draggingState = Vue.observable(draggingMap)
|
||||
// 第二步:表头渲染
|
||||
return (h, props, children) => {
|
||||
// 表头DOM
|
||||
let thDom = null
|
||||
// 获取列的key值和特性
|
||||
const { key, ...restProps } = props
|
||||
// 获取最新列配置
|
||||
const columns = cloneDeep(this.columns)
|
||||
let col
|
||||
if (key === 'selection-column') {
|
||||
col = {}
|
||||
} else {
|
||||
col = columns.find(col => getKey(col) === key)
|
||||
}
|
||||
// 没有开启拖拽 或 没有宽度 或 有定位,都不能拖拽(防止布局异常,至少有一列不设width并且不能有fixed)
|
||||
if (!col.resizable || !col.width || !!col.fixed) {
|
||||
return <th {...restProps}>{children}</th>
|
||||
}
|
||||
// 开始拖拽监听
|
||||
const onDragging = (x) => {
|
||||
const beforeWidth = col.width
|
||||
const scroll = cloneDeep(this.scroll) || {}
|
||||
draggingState[key] = 0
|
||||
col.width = Math.max(x, col.minWidth || this.columnMinWidth)
|
||||
// 如果有scroll.x就计算差值,否则获取所有列的和
|
||||
if (scroll.x) {
|
||||
scroll.x = scroll.x + (col.width - beforeWidth)
|
||||
} else {
|
||||
scroll.x = 0
|
||||
this.columns.forEach(col => (scroll.x += (col.width || col.minWidth || this.columnMinWidth)))
|
||||
}
|
||||
// 将计算好的宽度更新
|
||||
if (this.scroll) {
|
||||
this.$emit('update:scroll', scroll)
|
||||
} else {
|
||||
this.selfScroll = scroll
|
||||
}
|
||||
// 修改列宽后,更新表格字段
|
||||
this.setResultColumns(columns)
|
||||
}
|
||||
// 停止拖拽监听
|
||||
const onDragstop = () => {
|
||||
draggingState[key] = thDom.getBoundingClientRect().width
|
||||
}
|
||||
// 控制最小拖拽宽度
|
||||
const onDrag = (x) => {
|
||||
return x >= (col.minWidth || this.columnMinWidth)
|
||||
}
|
||||
return (
|
||||
<th
|
||||
{...restProps}
|
||||
v-ant-ref={(r) => (thDom = r)}
|
||||
width={col.width}
|
||||
class="resize-table-th"
|
||||
>
|
||||
{children}
|
||||
<vue-draggable-resizable
|
||||
key={getKey(col)}
|
||||
class="table-draggable-handle"
|
||||
minw={10}
|
||||
w={10}
|
||||
x={col.width || draggingState[key]}
|
||||
z={1}
|
||||
axis="x"
|
||||
draggable={true}
|
||||
resizable={false}
|
||||
props={{ onDrag }}
|
||||
onDragging={onDragging}
|
||||
onDragstop={onDragstop}
|
||||
></vue-draggable-resizable>
|
||||
</th>
|
||||
)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 配置表中复选框改变状态的事件
|
||||
* @param text 修改前的状态
|
||||
* @param type 隐藏还是冻结
|
||||
* @param record 当前行数据
|
||||
*/
|
||||
changeSetting (text, type, record) {
|
||||
const checked = !text // text是点击前的状态,取反就是要修改的状态
|
||||
// console.log('修改了配置', checked, type, record)
|
||||
// 修改当前行配置
|
||||
record[type] = !checked
|
||||
// 勾选就添加,取消就删除
|
||||
if (checked) {
|
||||
this.settingColumnsObj[type].push(record.key)
|
||||
} else {
|
||||
this.settingColumnsObj[type].splice(this.settingColumnsObj[type].findIndex(item => item === record.key), 1)
|
||||
}
|
||||
this.saveSetting()
|
||||
/**
|
||||
* 弹框消失问题 冻结列出现和消失会导致dom结构改变而从更新结构丢失弹框
|
||||
* 解决方案,刷新前获取dom,刷新后再次获取,如果获取不一致就触发点击事件点开弹框
|
||||
*/
|
||||
const beforeIsFixed = !!this.$refs.table.$el.querySelector('.ant-table-fixed-left')
|
||||
this.$nextTick(() => {
|
||||
const afterIsFixed = !!this.$refs.table.$el.querySelector('.ant-table-fixed-left')
|
||||
// console.log(beforeIsFixed !== afterIsFixed)
|
||||
if (beforeIsFixed !== afterIsFixed) {
|
||||
this.$refs.settingBtn.$el.click()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 初始化配置,从columns里获取
|
||||
initSetting (columns) {
|
||||
const hide = []
|
||||
const freeze = []
|
||||
columns.forEach(col => {
|
||||
const key = getKey(col)
|
||||
// hide属性控制隐藏
|
||||
if (col.hide) {
|
||||
hide.push(key)
|
||||
}
|
||||
// fixed冻结到左侧
|
||||
if (col.fixed === true || col.fixed === 'left') {
|
||||
freeze.push(key)
|
||||
}
|
||||
})
|
||||
this.settingColumnsObj = { hide, freeze }
|
||||
this.saveSetting()
|
||||
},
|
||||
// 配置保存到本地
|
||||
saveSetting () {
|
||||
// 获取所有的jtable缓存的key数组(用于清除缓存,没有就初始化数组)
|
||||
const jTableKeys = Vue.ls.get(J_TABLE_KEYS) || []
|
||||
const saveKey = this.tableKey + saveSuffix
|
||||
// 如果不在数组里面就追加进去
|
||||
if (!jTableKeys.includes(saveKey)) {
|
||||
jTableKeys.push(saveKey)
|
||||
}
|
||||
Vue.ls.set(J_TABLE_KEYS, jTableKeys, 7 * 24 * 60 * 60 * 10)
|
||||
Vue.ls.set(saveKey, this.settingColumnsObj, 7 * 24 * 60 * 60 * 10)
|
||||
},
|
||||
// 还原默认配置
|
||||
resteColumns () {
|
||||
this.initSetting(this.columnsBak)
|
||||
},
|
||||
// 清除本地的配置(当前的JTable)
|
||||
clearSetting () {
|
||||
this.settingColumnsObj = { hide: [], freeze: [] }
|
||||
Vue.ls.remove(this.tableKey + saveSuffix)
|
||||
this.resteColumns()
|
||||
this.$message.success('成功清除当前JTable的缓存!')
|
||||
},
|
||||
// 清除所有缓存
|
||||
clearAllCacheSetting () {
|
||||
// 获取到之后遍历删除,最后把keys数组删除
|
||||
const jTableKeys = Vue.ls.get(J_TABLE_KEYS)
|
||||
jTableKeys && jTableKeys.forEach(key => {
|
||||
Vue.ls.remove(key)
|
||||
})
|
||||
Vue.ls.remove(J_TABLE_KEYS)
|
||||
this.resteColumns() // 这里只能刷新当前的表
|
||||
this.$message.success('成功清除全局JTable的缓存!')
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 备份默认配置
|
||||
this.columnsBak = cloneDeep(this.columns)
|
||||
const settingColumnsObj = Vue.ls.get(this.tableKey + saveSuffix)
|
||||
// 第一次进页面或清空缓存进行初始化
|
||||
if (settingColumnsObj) {
|
||||
this.settingColumnsObj = settingColumnsObj
|
||||
} else {
|
||||
this.initSetting(this.columns)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-table {
|
||||
.resize-table-th {
|
||||
position: relative;
|
||||
|
||||
.table-draggable-handle {
|
||||
transform: none !important;
|
||||
position: absolute !important;
|
||||
height: 100% !important;
|
||||
bottom: 0;
|
||||
left: auto !important;
|
||||
right: -5px;
|
||||
//width: 10px !important;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<a-time-picker
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:format="dateFormat"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
@change="handleTimeChange"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'JTime',
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
dateFormat: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
required: false
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const timeStr = this.value
|
||||
return {
|
||||
decorator: '',
|
||||
momVal: !timeStr ? null : moment(timeStr, this.dateFormat)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if (!val) {
|
||||
this.momVal = null
|
||||
} else {
|
||||
this.momVal = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleTimeChange (mom, timeStr) {
|
||||
this.$emit('change', timeStr)
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<a-tree-select
|
||||
allowClear
|
||||
labelInValue
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
@change="onChange"
|
||||
@search="onSearch"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners">
|
||||
</a-tree-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeDict',
|
||||
data () {
|
||||
return {
|
||||
treeData: [],
|
||||
treeValue: null,
|
||||
url_root: '/sys/category/loadTreeRoot',
|
||||
url_children: '/sys/category/loadTreeChildren',
|
||||
url_view: '/sys/category/loadOne'
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
parentCode: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
required: false
|
||||
},
|
||||
root: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => {
|
||||
return {
|
||||
pid: '0'
|
||||
}
|
||||
}
|
||||
},
|
||||
async: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
root: {
|
||||
handler (val) {
|
||||
console.log('root-change', val)
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
parentCode: {
|
||||
handler () {
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
value: {
|
||||
handler () {
|
||||
this.loadViewInfo()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.loadRoot()
|
||||
this.loadViewInfo()
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
methods: {
|
||||
loadViewInfo () {
|
||||
if (!this.value || this.value + '' === '0') {
|
||||
this.treeValue = null
|
||||
} else {
|
||||
const param = {
|
||||
field: this.field,
|
||||
val: this.value
|
||||
}
|
||||
getAction(this.url_view, param).then(res => {
|
||||
if (res.success) {
|
||||
this.treeValue = {
|
||||
value: this.value,
|
||||
label: res.result.name
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
loadRoot () {
|
||||
const param = {
|
||||
async: this.async,
|
||||
pcode: this.parentCode
|
||||
}
|
||||
getAction(this.url_root, param).then(res => {
|
||||
if (res.success) {
|
||||
this.handleTreeNodeValue(res.result)
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
this.$message.error(res.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
asyncLoadTreeData (treeNode) {
|
||||
return new Promise((resolve) => {
|
||||
if (!this.async) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid
|
||||
}
|
||||
getAction(this.url_children, param).then(res => {
|
||||
if (res.success) {
|
||||
this.handleTreeNodeValue(res.result)
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') {
|
||||
if (!children || children.length === 0) {
|
||||
item.leaf = true
|
||||
} else {
|
||||
item.children = children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
handleTreeNodeValue (result) {
|
||||
const storeField = this.field + '' === 'code' ? 'code' : 'key'
|
||||
for (const i of result) {
|
||||
i.value = i[storeField]
|
||||
i.isLeaf = !(!i.leaf)
|
||||
if (i.children && i.children.length > 0) {
|
||||
this.handleTreeNodeValue(i.children)
|
||||
}
|
||||
}
|
||||
},
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
} else {
|
||||
this.$emit('change', value.value)
|
||||
}
|
||||
this.treeValue = value
|
||||
},
|
||||
onSearch (value) {
|
||||
console.log(value)
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-tree-select
|
||||
v-if="isAsync"
|
||||
allowClear
|
||||
tree-node-filter-prop="title"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:loadData="asyncLoadTreeData"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
:show-search="!multiple"
|
||||
v-bind="_attrs"
|
||||
v-on="childListeners"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
<a-tree-select
|
||||
v-if="!isAsync"
|
||||
allowClear
|
||||
tree-node-filter-prop="title"
|
||||
:getPopupContainer="(node) => node.parentNode"
|
||||
style="width: 100%"
|
||||
:disabled="disabled"
|
||||
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
|
||||
:placeholder="placeholder"
|
||||
:value="treeValue"
|
||||
:treeData="treeData"
|
||||
:multiple="multiple"
|
||||
:show-search="!multiple"
|
||||
v-bind="_attrs"
|
||||
v-on="$listeners"
|
||||
@change="onChange">
|
||||
</a-tree-select>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
/*
|
||||
* 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
|
||||
* <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
|
||||
* */
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeSelect',
|
||||
props: {
|
||||
isAsync: { // 是否采用异步方式初始化树
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
// type: String,
|
||||
required: false
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
required: false
|
||||
},
|
||||
dict: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
pidField: {
|
||||
type: String,
|
||||
default: 'pid',
|
||||
required: false
|
||||
},
|
||||
pidValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
},
|
||||
hasChildField: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
// 是否支持多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadTriggleChange: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
treeValue: null, // 选中的数据
|
||||
treeData: [], // 渲染树的数组
|
||||
url: '/sys/dict/loadTreeData',
|
||||
view: '/sys/dict/loadDictItem/',
|
||||
tableName: '',
|
||||
text: '',
|
||||
code: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.loadItemByCode()
|
||||
},
|
||||
dict () {
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_attrs () {
|
||||
return { ...this.$attrs }
|
||||
},
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.validateProp().then(() => {
|
||||
this.initDictInfo()
|
||||
this.loadRoot()
|
||||
this.loadItemByCode()
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
window.that = this
|
||||
},
|
||||
methods: {
|
||||
loadItemByCode () {
|
||||
if (!this.value || this.value + '' === '0') {
|
||||
this.treeValue = null
|
||||
} else {
|
||||
getAction(`${this.view}${this.dict}`, { key: this.value }).then(res => {
|
||||
if (res.success) {
|
||||
this.treeValue = this.value.split(',') // v-model接收的是string || string[]
|
||||
// 将节点名称显示在选择框上
|
||||
this.treeValue = res.result[0]
|
||||
this.onLoadTriggleChange(res.result[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoadTriggleChange (text) {
|
||||
// 只有单选才会触发
|
||||
if (!this.multiple && this.loadTriggleChange) {
|
||||
this.$emit('change', this.value, text)
|
||||
}
|
||||
},
|
||||
initDictInfo () { // 取出父组件传过来的code,text,tableName
|
||||
const arr = this.dict.split(',')
|
||||
this.tableName = arr[0]
|
||||
this.text = arr[1]
|
||||
this.code = arr[2]
|
||||
},
|
||||
// 异步加载树节点
|
||||
asyncLoadTreeData (treeNode) {
|
||||
debugger
|
||||
return new Promise((resolve) => {
|
||||
if (treeNode.$vnode.children) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const pid = treeNode.$vnode.key
|
||||
const param = {
|
||||
pid: pid,
|
||||
tableName: this.tableName,
|
||||
text: this.text,
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
hasChildField: this.hasChildField,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.addChildren(pid, res.result, this.treeData)
|
||||
this.treeData = [...this.treeData]
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
addChildren (pid, children, treeArray) {
|
||||
if (treeArray && treeArray.length > 0) {
|
||||
for (const item of treeArray) {
|
||||
if (item.key + '' === pid + '') { // 找到当前元素所在的父节点
|
||||
if (!children || children.length === 0) {
|
||||
item.isLeaf = true
|
||||
} else {
|
||||
item.children = children // 搜索出来的children添加到原树子children
|
||||
}
|
||||
break
|
||||
} else {
|
||||
this.addChildren(pid, children, item.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 初始化树
|
||||
* isAsync:true===异步获取 false===同步获取数据 默认异步 */
|
||||
loadRoot () {
|
||||
if (this.isAsync) {
|
||||
const param = {
|
||||
pid: this.pidValue,
|
||||
tableName: this.tableName,
|
||||
text: this.text,
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
hasChildField: this.hasChildField,
|
||||
condition: this.condition
|
||||
}
|
||||
getAction(this.url, param).then(res => {
|
||||
if (res.success && res.result) {
|
||||
for (const i of res.result) {
|
||||
i.value = i.key
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
}
|
||||
this.treeData = [...res.result]
|
||||
} else {
|
||||
console.log('数根节点查询结果-else', res)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 同步
|
||||
const param = {
|
||||
code: this.code,
|
||||
pidField: this.pidField,
|
||||
tableName: this.tableName,
|
||||
text: this.text
|
||||
}
|
||||
getAction('/sys/dict/queryAllTreeData', param).then((res) => {
|
||||
if (res.success) {
|
||||
this.treeData = deepFor(res.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onChange (value) {
|
||||
if (!value) {
|
||||
/*
|
||||
* 使用$listeners向上暴露事件---和$emit一起使用出现的问题:change事件会执行两遍
|
||||
* 解决办法:改变选中时提交的事件名 */
|
||||
this.$emit('change', '')
|
||||
this.treeValue = null
|
||||
} else if (value instanceof Array) {
|
||||
// 多选
|
||||
this.$emit('change', value.join(',').toString()) // 修改第一次选中时,选中为空的情况
|
||||
this.treeValue = value
|
||||
} else {
|
||||
// 单选
|
||||
this.$emit('change', value)
|
||||
this.treeValue = value
|
||||
}
|
||||
},
|
||||
getCurrTreeData () {
|
||||
return this.treeData
|
||||
},
|
||||
validateProp () {
|
||||
const myCondition = this.condition
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!myCondition) {
|
||||
resolve()
|
||||
} else {
|
||||
try {
|
||||
const test = JSON.parse(myCondition)
|
||||
if (typeof test === 'object' && test) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error('组件JTreeSelect-condition传值有误,需要一个json字符串!')
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
// 递归整个树,判断是否是叶子节点----同步获取数据时用到
|
||||
function deepFor (source) {
|
||||
for (const i of source) {
|
||||
i.value = i.key
|
||||
|
||||
if (i.leaf === false) {
|
||||
i.isLeaf = false
|
||||
} else if (i.leaf === true) {
|
||||
i.isLeaf = true
|
||||
}
|
||||
if (i.children && i.children.length > 0) {
|
||||
deepFor(i.children)
|
||||
}
|
||||
}
|
||||
return source
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<a-table
|
||||
:rowKey="rowKey"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:expandedRowKeys="expandedRowKeys"
|
||||
v-bind="tableAttrs"
|
||||
v-on="$listeners"
|
||||
@expand="handleExpand"
|
||||
@expandedRowsChange="expandedRowKeys=$event">
|
||||
|
||||
<template v-for="(slotItem) of slots" :slot="slotItem" slot-scope="text, record, index">
|
||||
<slot :name="slotItem" v-bind="{text,record,index}"></slot>
|
||||
</template>
|
||||
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JTreeTable',
|
||||
props: {
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
},
|
||||
// 根据什么查询,如果传递 id 就根据 id 查询
|
||||
queryKey: {
|
||||
type: String,
|
||||
default: 'parentId'
|
||||
},
|
||||
queryParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 查询顶级时的值,如果顶级为0,则传0
|
||||
topValue: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
childrenUrl: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
tableProps: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
/** 是否在创建组件的时候就查询数据 */
|
||||
immediateRequest: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
condition: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dataSource: [],
|
||||
expandedRowKeys: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getChildrenUrl () {
|
||||
if (this.childrenUrl) {
|
||||
return this.childrenUrl
|
||||
} else {
|
||||
return this.url
|
||||
}
|
||||
},
|
||||
slots () {
|
||||
const slots = []
|
||||
for (const column of this.columns) {
|
||||
if (column.scopedSlots && column.scopedSlots.customRender) {
|
||||
slots.push(column.scopedSlots.customRender)
|
||||
}
|
||||
}
|
||||
return slots
|
||||
},
|
||||
tableAttrs () {
|
||||
return Object.assign(this.$attrs, this.tableProps)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
queryParams: {
|
||||
deep: true,
|
||||
handler () {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.immediateRequest) this.loadData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 加载数据 */
|
||||
loadData (id = this.topValue, first = true, url = this.url) {
|
||||
this.$emit('requestBefore', { first })
|
||||
|
||||
if (first) {
|
||||
this.expandedRowKeys = []
|
||||
}
|
||||
|
||||
const params = Object.assign({}, this.queryParams || {})
|
||||
params[this.queryKey] = id
|
||||
if (this.condition && this.condition.length > 0) {
|
||||
params.condition = this.condition
|
||||
}
|
||||
|
||||
return getAction(url, params).then(res => {
|
||||
let list = []
|
||||
if (res.result instanceof Array) {
|
||||
list = res.result
|
||||
} else if (res.result.records instanceof Array) {
|
||||
list = res.result.records
|
||||
} else {
|
||||
throw new Error('返回数据类型不识别')
|
||||
}
|
||||
const dataSource = list.map(item => {
|
||||
// 判断是否标记了带有子级
|
||||
if (item.hasChildren === true) {
|
||||
// 查找第一个带有dataIndex的值的列
|
||||
let firstColumn
|
||||
for (const column of this.columns) {
|
||||
firstColumn = column.dataIndex
|
||||
if (firstColumn) break
|
||||
}
|
||||
// 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
|
||||
const loadChild = { id: `${item.id}_loadChild`, [firstColumn]: 'loading...', isLoading: true }
|
||||
item.children = [loadChild]
|
||||
}
|
||||
return item
|
||||
})
|
||||
if (first) {
|
||||
this.dataSource = dataSource
|
||||
}
|
||||
this.$emit('requestSuccess', { first, dataSource, res })
|
||||
return Promise.resolve(dataSource)
|
||||
}).finally(() => this.$emit('requestFinally', { first }))
|
||||
},
|
||||
|
||||
/** 点击展开图标时触发 */
|
||||
handleExpand (expanded, record) {
|
||||
// 判断是否是展开状态
|
||||
if (expanded) {
|
||||
// 判断子级的首个项的标记是否是“正在加载中”,如果是就加载数据
|
||||
if (record.children[0].isLoading === true) {
|
||||
this.loadData(record.id, false, this.getChildrenUrl).then(dataSource => {
|
||||
// 处理好的数据可直接赋值给children
|
||||
if (dataSource.length === 0) {
|
||||
record.children = null
|
||||
} else {
|
||||
record.children = dataSource
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div :id="containerId" style="position: relative">
|
||||
<a-upload
|
||||
name="file"
|
||||
:multiple="multiple"
|
||||
:action="uploadAction"
|
||||
:headers="headers"
|
||||
:data="{'biz':bizPath}"
|
||||
:fileList="fileList"
|
||||
:beforeUpload="doBeforeUpload"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
:returnUrl="returnUrl"
|
||||
:listType="complistType"
|
||||
@preview="handlePreview"
|
||||
@download="handleDownload"
|
||||
:showUploadList="{
|
||||
showDownloadIcon: isDownload
|
||||
}"
|
||||
v-bind="$attrs"
|
||||
v-on="childListeners"
|
||||
:class="{'uploadty-disabled':disabled}">
|
||||
<template>
|
||||
<div v-if="isImageComp">
|
||||
<a-icon type="plus" />
|
||||
<div class="ant-upload-text">{{ text }}</div>
|
||||
</div>
|
||||
<a-button v-else-if="buttonVisible">
|
||||
<a-icon type="upload" />
|
||||
{{ text }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
|
||||
<div id="images">
|
||||
<div class="image" v-viewer="{movable: false}">
|
||||
<img v-show="image" :src="imageUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<j-image-preview-modal ref="imagePreviewModal" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Vue from 'vue'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl, downloadFile } from '@/api/manage'
|
||||
import { previewPdf } from '@/utils/previewPdf'
|
||||
import JImagePreviewModal from '@comp/jero/modal/JImagePreviewModal.vue'
|
||||
|
||||
const FILE_TYPE_ALL = 'all'
|
||||
const FILE_TYPE_IMG = 'image'
|
||||
const FILE_TYPE_IMGS = ['jpg', 'jpeg', 'png', 'raw']
|
||||
const FILE_TYPE_PDF = 'pdf'
|
||||
|
||||
// const uidGenerator = () => {
|
||||
// return '-' + parseInt(Math.random() * 10000 + 1 + '', 10)
|
||||
// }
|
||||
|
||||
const Base64 = require('js-base64').Base64
|
||||
|
||||
// 支持预览的文件类型
|
||||
const CAN_PREVIEW_FILE_TYPE = ['pdf', 'image']
|
||||
// 不支持预览的文件后缀
|
||||
const CAN_PREVIEW_FILE_SUFFIX = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'mp4', 'flv']
|
||||
|
||||
export default {
|
||||
name: 'JUpload',
|
||||
components: { JImagePreviewModal },
|
||||
data () {
|
||||
return {
|
||||
uploadAction: window._CONFIG.domianURL + '/sys/common/upload',
|
||||
headers: {},
|
||||
fileList: [],
|
||||
newFileList: [],
|
||||
image: false,
|
||||
uploadGoOn: true,
|
||||
containerId: null,
|
||||
imageUrl: null
|
||||
}
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '点击上传'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: FILE_TYPE_ALL
|
||||
},
|
||||
/* 这个属性用于控制文件上传的业务路径 */
|
||||
bizPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
value: {
|
||||
type: [String, Array],
|
||||
required: false
|
||||
},
|
||||
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
// update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
|
||||
// 此属性被废弃了
|
||||
triggerChange: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url,
|
||||
* true:仅返回url
|
||||
* false:返回fileName filePath fileSize
|
||||
*/
|
||||
returnUrl: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0
|
||||
},
|
||||
buttonVisible: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
beforeUpload: {
|
||||
type: Function
|
||||
},
|
||||
isDownload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
const val = this.value
|
||||
this.initFileList(val)
|
||||
// if (val instanceof Array) {
|
||||
// if (this.returnUrl) {
|
||||
// this.initFileList(val.join(','))
|
||||
// } else {
|
||||
// this.initFileListArr(val)
|
||||
// }
|
||||
// } else {
|
||||
// this.initFileList(val)
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 透传给下级组件的事件,需要排除本组件使用的change事件
|
||||
childListeners () {
|
||||
const result = Object.assign({},
|
||||
this.$listeners
|
||||
)
|
||||
delete result.change
|
||||
return result
|
||||
},
|
||||
isImageComp () {
|
||||
return this.fileType === FILE_TYPE_IMG
|
||||
},
|
||||
complistType () {
|
||||
return this.fileType === FILE_TYPE_IMG ? 'picture-card' : 'text'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const token = Vue.ls.get(ACCESS_TOKEN)
|
||||
// ---------------------------- begin 图片左右换位置 -------------------------------------
|
||||
this.headers = { 'X-Access-Token': token }
|
||||
this.containerId = 'container-ty-' + new Date().getTime()
|
||||
// ---------------------------- end 图片左右换位置 -------------------------------------
|
||||
},
|
||||
methods: {
|
||||
// 将url的参数拆分成对象
|
||||
urlToParams (url) {
|
||||
const commonUrl = window._CONFIG.staticDomainURL
|
||||
// url截取参数的部分
|
||||
const paramsStr = url.slice(url.indexOf('?') + 1)
|
||||
const paramsObj = {
|
||||
url,
|
||||
id: url.slice(url.indexOf(commonUrl) + commonUrl.length + 1, url.indexOf('?'))
|
||||
}
|
||||
paramsStr.split('&').forEach(item => {
|
||||
const arr = item.split('=')
|
||||
paramsObj[arr[0]] = arr[1]
|
||||
})
|
||||
return paramsObj
|
||||
},
|
||||
initFileList (val) {
|
||||
if (!val || val.length === 0) {
|
||||
this.fileList = []
|
||||
return
|
||||
}
|
||||
// 所有文件url的数组
|
||||
let arr = []
|
||||
// 用于临时存储文件的数组,最终会被赋值到this.fileList
|
||||
const fileList = []
|
||||
if (val instanceof Array) {
|
||||
// url数组直接返回,如果是对象数组,就将每个对象的url取出
|
||||
arr = this.returnUrl ? val : val.map(item => item.filePath)
|
||||
} else {
|
||||
// 将字符串拆分数组(props声明value只能是Array或String)
|
||||
arr = val.split(',')
|
||||
}
|
||||
arr.forEach(url => {
|
||||
if (url) {
|
||||
const params = this.urlToParams(url)
|
||||
fileList.push({
|
||||
uid: params.id,
|
||||
name: params.fullfilename,
|
||||
status: 'done',
|
||||
url,
|
||||
// response用于下载和预览
|
||||
response: {
|
||||
success: true,
|
||||
result: {
|
||||
id: params.id,
|
||||
fileName: params.fullfilename
|
||||
},
|
||||
status: 'history'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// 将处理好的数据回显
|
||||
this.fileList = fileList
|
||||
},
|
||||
handlePathChange () {
|
||||
const uploadFiles = this.fileList
|
||||
let path = ''
|
||||
if (!uploadFiles || uploadFiles.length === 0) {
|
||||
path = ''
|
||||
}
|
||||
const arr = []
|
||||
|
||||
for (let a = 0; a < uploadFiles.length; a++) {
|
||||
if (uploadFiles[a].status === 'done') {
|
||||
arr.push(uploadFiles[a].url)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (arr.length > 0) {
|
||||
path = arr.join(',')
|
||||
}
|
||||
this.$emit('change', path)
|
||||
},
|
||||
doBeforeUpload (file) {
|
||||
this.uploadGoOn = true
|
||||
const fileType = file.type
|
||||
if (this.fileType === FILE_TYPE_IMG) {
|
||||
if (fileType.indexOf('image') < 0) {
|
||||
this.$message.warning('请上传图片')
|
||||
this.uploadGoOn = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 扩展 beforeUpload 验证
|
||||
if (typeof this.beforeUpload === 'function') {
|
||||
return this.beforeUpload(file)
|
||||
}
|
||||
return true
|
||||
},
|
||||
handleChange (info) {
|
||||
if (!info.file.status && this.uploadGoOn === false) {
|
||||
info.fileList.pop()
|
||||
}
|
||||
let fileList = info.fileList
|
||||
if (info.file.status === 'done') {
|
||||
if (this.number > 0) {
|
||||
fileList = fileList.slice(-this.number)
|
||||
}
|
||||
if (info.file.response.success) {
|
||||
fileList = fileList.map((file) => {
|
||||
if (file.response) {
|
||||
// const reUrl = `${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.response.result.fileName}`
|
||||
// TODO getFileAccessHttpUrl方法会追加token,在之后拼参数
|
||||
file.url = getFileAccessHttpUrl(file.response.result.id) + '&fullfilename=' + file.response.result.fileName
|
||||
}
|
||||
return file
|
||||
})
|
||||
} else {
|
||||
this.$message.error(info.file.response.message)
|
||||
}
|
||||
// this.$message.success(`${info.file.name} 上传成功!`);
|
||||
} else if (info.file.status === 'error') {
|
||||
this.$message.error(`${info.file.name} 上传失败.`)
|
||||
} else if (info.file.status === 'removed') {
|
||||
this.handleDelete(info.file)
|
||||
}
|
||||
this.fileList = fileList
|
||||
if (info.file.status === 'done' || info.file.status === 'removed') {
|
||||
// returnUrl为true时仅返回文件路径
|
||||
if (this.returnUrl) {
|
||||
this.handlePathChange()
|
||||
} else {
|
||||
// returnUrl为false时返回文件名称、文件路径及文件大小
|
||||
this.newFileList = []
|
||||
for (let a = 0; a < fileList.length; a++) {
|
||||
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
if (fileList[a].status === 'done') {
|
||||
const fileJson = {
|
||||
fileName: fileList[a].name,
|
||||
filePath: fileList[a].url,
|
||||
fileSize: fileList[a].size
|
||||
}
|
||||
this.newFileList.push(fileJson)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
|
||||
}
|
||||
this.$emit('change', this.newFileList)
|
||||
}
|
||||
}
|
||||
},
|
||||
handleDelete (file) {
|
||||
// 如有需要新增 删除逻辑
|
||||
console.log(file)
|
||||
},
|
||||
handlePreview (file) {
|
||||
if (!file || !file.url) {
|
||||
return
|
||||
}
|
||||
const fileType = file.type
|
||||
// 截取文件后缀名
|
||||
const fileSuffix = file.name ? file.name.split('.')[file.name.split('.').length - 1] : ''
|
||||
const canPreview = fileType ? CAN_PREVIEW_FILE_TYPE.some(tt => fileType.indexOf(tt) !== -1) : CAN_PREVIEW_FILE_SUFFIX.some(tt => fileSuffix === tt)
|
||||
// 判断是否为可预览格式的文件
|
||||
if (!canPreview) {
|
||||
this.$message.loading('该文件类型不支持预览,正在为您准备下载...').then(() => {
|
||||
this.handleDownload(file)
|
||||
})
|
||||
return
|
||||
}
|
||||
const fileFullUrl = `${window._CONFIG.domianWebSocketURL}/sys/common/download/${file.response.result.id}?token=${Vue.ls.get(ACCESS_TOKEN)}&fullfilename=${file.name}`
|
||||
// 图片预览,使用自己添加的组件
|
||||
if (canPreview && FILE_TYPE_IMGS.includes(fileSuffix)) {
|
||||
this.imageUrl = getFileAccessHttpUrl(file.response.result.id)
|
||||
// 获取viewer实例
|
||||
const viewer = this.$el.querySelector('.image').$viewer
|
||||
// 调用show方法进行显示预览图
|
||||
viewer.show()
|
||||
// this.$refs.imagePreviewModal.open(file)
|
||||
return
|
||||
}
|
||||
// pdf预览
|
||||
if (canPreview && FILE_TYPE_PDF.includes(fileSuffix)) {
|
||||
const url = previewPdf(file.response.result.id)
|
||||
window.open(url)
|
||||
return
|
||||
}
|
||||
// 其余可预览文件仍使用KKFile进行预览
|
||||
const url = `${window._CONFIG.onlinePreviewDomainURL}?url=${encodeURIComponent(Base64.encode(fileFullUrl))}`
|
||||
window.open(url)
|
||||
},
|
||||
handleDownload (file) {
|
||||
// 下载文件
|
||||
downloadFile(`/sys/common/download/${file.response.result.id}`, file.name)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.uploadty-disabled {
|
||||
.ant-upload-list-item {
|
||||
.anticon-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.anticon-delete {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<j-modal
|
||||
title="详细信息"
|
||||
:width="1200"
|
||||
:visible="visible"
|
||||
@ok="handleOk"
|
||||
@cancel="close"
|
||||
switch-fullscreen
|
||||
:fullscreen.sync="fullscreen"
|
||||
>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-if="visible">
|
||||
<slot name="mainForm" :row="row" :column="column"/>
|
||||
<slot name="subForm" :row="row" :column="column"/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</j-modal>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { cloneObject } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDetailsModal',
|
||||
inject: ['superTrigger'],
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
fullscreen: false,
|
||||
row: null,
|
||||
column: null
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
methods: {
|
||||
|
||||
open (event) {
|
||||
const { row, column } = event
|
||||
this.row = cloneObject(row)
|
||||
this.column = column
|
||||
this.visible = true
|
||||
},
|
||||
|
||||
close () {
|
||||
this.visible = false
|
||||
},
|
||||
|
||||
handleOk () {
|
||||
this.superTrigger('detailsConfirm', {
|
||||
row: this.row,
|
||||
column: this.column,
|
||||
callback: (success) => {
|
||||
this.visible = !success
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div :class="boxClass">
|
||||
<a-pagination
|
||||
:disabled="disabled"
|
||||
v-bind="bindProps"
|
||||
@change="handleChange"
|
||||
@showSizeChange="handleShowSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PropTypes from 'ant-design-vue/es/_util/vue-types'
|
||||
|
||||
export default {
|
||||
name: 'JVxePagination',
|
||||
props: {
|
||||
size: String,
|
||||
disabled: PropTypes.bool,
|
||||
pagination: PropTypes.object.def({})
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
defaultPagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: ['10', '20', '30'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' 共 ' + total + ' 条'
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
bindProps () {
|
||||
return {
|
||||
...this.defaultPagination,
|
||||
...this.pagination,
|
||||
size: this.size === 'tiny' ? 'small' : ''
|
||||
}
|
||||
},
|
||||
boxClass () {
|
||||
return {
|
||||
'j-vxe-pagination': true,
|
||||
'show-quick-jumper': !!this.bindProps.showQuickJumper
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (current, pageSize) {
|
||||
this.$set(this.pagination, 'current', current)
|
||||
this.$emit('change', { current, pageSize })
|
||||
},
|
||||
handleShowSizeChange (current, pageSize) {
|
||||
this.$set(this.pagination, 'pageSize', pageSize)
|
||||
this.$emit('change', { current, pageSize })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<a-popover :visible="visible" :placement="placement" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
|
||||
<div class="j-vxe-popover-title" slot="title">
|
||||
<div>子表</div>
|
||||
<div class="j-vxe-popover-title-close" @click="close">
|
||||
<a-icon type="close"/>
|
||||
</div>
|
||||
</div>
|
||||
<template slot="content">
|
||||
<transition name="fade">
|
||||
<slot v-if="visible" name="subForm" :row="row" :column="column"/>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<div ref="div" class="j-vxe-popover-div"></div>
|
||||
|
||||
</a-popover>
|
||||
</template>
|
||||
<script>
|
||||
import domAlign from 'dom-align'
|
||||
import { getParentNodeByTagName } from '../utils/vxeUtils'
|
||||
import { cloneObject, triggerWindowResizeEvent } from '@/utils/util'
|
||||
|
||||
export default {
|
||||
name: 'JVxeSubPopover',
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
// 当前行
|
||||
row: null,
|
||||
column: null,
|
||||
|
||||
overlayStyle: {
|
||||
width: null,
|
||||
zIndex: 100
|
||||
},
|
||||
placement: 'bottom'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
methods: {
|
||||
|
||||
toggle (event) {
|
||||
// update-begin-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
|
||||
if (document.body.clientHeight - event.$event.clientY > 350) {
|
||||
this.placement = 'bottom'
|
||||
} else {
|
||||
this.placement = 'top'
|
||||
}
|
||||
// update-end-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
|
||||
if (this.row == null) {
|
||||
this.open(event)
|
||||
} else {
|
||||
this.row.id === event.row.id ? this.close() : this.reopen(event)
|
||||
}
|
||||
},
|
||||
|
||||
open (event, level = 0) {
|
||||
if (level > 3) {
|
||||
this.$message.error('打开子表失败')
|
||||
console.warn('【JVxeSubPopover】打开子表失败')
|
||||
return
|
||||
}
|
||||
|
||||
const { row, column, $table, $event: { target } } = event
|
||||
this.row = cloneObject(row)
|
||||
this.column = column
|
||||
|
||||
let className = target.className || ''
|
||||
className = typeof className === 'string' ? className : className.toString()
|
||||
|
||||
// 点击的是expand,不做处理
|
||||
if (className.includes('vxe-table--expand-btn')) {
|
||||
return
|
||||
}
|
||||
// 点击的是checkbox,不做处理
|
||||
if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) {
|
||||
return
|
||||
}
|
||||
// 点击的是radio,不做处理
|
||||
if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) {
|
||||
return
|
||||
}
|
||||
const table = $table.$el
|
||||
const tr = getParentNodeByTagName(target, 'tr')
|
||||
if (table && tr) {
|
||||
const clientWidth = table.clientWidth
|
||||
const clientHeight = tr.clientHeight
|
||||
this.$refs.div.style.width = clientWidth + 'px'
|
||||
this.$refs.div.style.height = clientHeight + 'px'
|
||||
this.overlayStyle.width = Number.parseInt((clientWidth - clientWidth * 0.04) + '') + 'px'
|
||||
this.overlayStyle.maxWidth = this.overlayStyle.width
|
||||
// update-begin-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
|
||||
// let realTable = getParentNodeByTagName(tr, 'table')
|
||||
// let left = realTable.parentNode.scrollLeft
|
||||
let h = event.$event.clientY
|
||||
if (h) {
|
||||
h = h - 140
|
||||
}
|
||||
const toolbar = this.$refs.div.nextSibling
|
||||
domAlign(this.$refs.div, toolbar, {
|
||||
points: ['tl', 'tl'],
|
||||
offset: [0, h],
|
||||
overflow: {
|
||||
alwaysByViewport: true
|
||||
}
|
||||
})
|
||||
// update-end-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
|
||||
this.$nextTick(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
triggerWindowResizeEvent()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const num = ++level
|
||||
console.warn('【JVxeSubPopover】table or tr 获取失败,正在进行第 ' + num + '次重试', { event, table, tr })
|
||||
window.setTimeout(() => this.open(event, num), 100)
|
||||
}
|
||||
},
|
||||
close () {
|
||||
if (this.visible) {
|
||||
this.row = null
|
||||
this.visible = false
|
||||
}
|
||||
},
|
||||
reopen (event) {
|
||||
this.close()
|
||||
this.open(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.j-vxe-popover-title {
|
||||
.j-vxe-popover-title-close {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
text-align: center;
|
||||
line-height: 31px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
cursor: pointer;
|
||||
transition: color 300ms;
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-popover-div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 31px;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.j-vxe-popover-overlay.ant-popover {
|
||||
.ant-popover-title {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div :class="boxClass">
|
||||
<!-- 工具按钮 -->
|
||||
<div class="j-vxe-tool-button div" :size="btnSize">
|
||||
<slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize"/>
|
||||
|
||||
<a-button v-if="showAdd" icon="plus" @click="trigger('add')" :disabled="disabled" type="primary">新增</a-button>
|
||||
<a-button v-if="showSave" icon="save" @click="trigger('save')" :disabled="disabled">保存</a-button>
|
||||
<template v-if="selectedRowIds.length > 0">
|
||||
<a-popconfirm
|
||||
v-if="showRemove"
|
||||
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
|
||||
@confirm="trigger('remove')"
|
||||
>
|
||||
<a-button icon="minus" :disabled="disabled">删除</a-button>
|
||||
</a-popconfirm>
|
||||
<template v-if="showClearSelection">
|
||||
<a-button icon="delete" @click="trigger('clearSelection')">清空选择</a-button>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<slot v-if="showSuffix" name="toolbarSuffix" :size="btnSize"/>
|
||||
<a v-if="showCollapse" @click="toggleCollapse" style="margin-left: 4px">
|
||||
<span>{{ collapsed ? '展开' : '收起' }}</span>
|
||||
<a-icon :type="collapsed ? 'down' : 'up'"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'JVxeToolbar',
|
||||
props: {
|
||||
toolbarConfig: Object,
|
||||
excludeCode: Array,
|
||||
size: String,
|
||||
disabled: Boolean,
|
||||
disabledRows: Object,
|
||||
selectedRowIds: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// 是否收起
|
||||
collapsed: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
boxClass () {
|
||||
return {
|
||||
'j-vxe-toolbar': true,
|
||||
'j-vxe-toolbar-collapsed': this.collapsed
|
||||
}
|
||||
},
|
||||
|
||||
btns () {
|
||||
const arr = this.toolbarConfig.btn || ['add', 'remove', 'clearSelection']
|
||||
const exclude = [...this.excludeCode]
|
||||
// TODO 需要将remove替换batch_delete
|
||||
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
|
||||
if (exclude.indexOf('batch_delete') >= 0) {
|
||||
exclude.push('remove')
|
||||
}
|
||||
// 按钮权限 需要去掉不被授权的按钮
|
||||
return arr.filter(item => {
|
||||
return exclude.indexOf(item) < 0
|
||||
})
|
||||
},
|
||||
slots () {
|
||||
return this.toolbarConfig.slot || ['prefix', 'suffix']
|
||||
},
|
||||
showPrefix () {
|
||||
return this.slots.includes('prefix')
|
||||
},
|
||||
showSuffix () {
|
||||
return this.slots.includes('suffix')
|
||||
},
|
||||
showAdd () {
|
||||
return this.btns.includes('add')
|
||||
},
|
||||
showSave () {
|
||||
return this.btns.includes('save')
|
||||
},
|
||||
showRemove () {
|
||||
return this.btns.includes('remove')
|
||||
},
|
||||
showClearSelection () {
|
||||
if (this.btns.includes('clearSelection')) {
|
||||
// 有禁用行时才显示清空选择按钮
|
||||
// 因为禁用行会阻止选择行,导致无法取消全选
|
||||
const length = Object.keys(this.disabledRows).length
|
||||
return length > 0
|
||||
}
|
||||
return false
|
||||
},
|
||||
showCollapse () {
|
||||
return this.btns.includes('collapse')
|
||||
},
|
||||
|
||||
btnSize () {
|
||||
return this.size === 'tiny' ? 'small' : null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 触发事件 */
|
||||
trigger (name) {
|
||||
this.$emit(name)
|
||||
},
|
||||
// 切换展开收起
|
||||
toggleCollapse () {
|
||||
this.collapsed = !this.collapsed
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-vxe-toolbar-collapsed {
|
||||
[data-collapse] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-tool-button.div .ant-btn {
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div :class="clazz" :style="boxStyle">
|
||||
<a-checkbox
|
||||
ref="checkbox"
|
||||
:checked="innerValue"
|
||||
v-bind="cellProps"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { neverNull } from '@/utils/util'
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeCheckboxCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
computed: {
|
||||
bordered () {
|
||||
return !!this.renderOptions.bordered
|
||||
},
|
||||
scrolling () {
|
||||
return !!this.renderOptions.scrolling
|
||||
},
|
||||
clazz () {
|
||||
return {
|
||||
'j-vxe-checkbox': true,
|
||||
'no-animation': this.scrolling
|
||||
}
|
||||
},
|
||||
boxStyle () {
|
||||
const style = {}
|
||||
// 如果有边框且未设置align属性,就强制居中
|
||||
if (this.bordered && !this.originColumn.align) {
|
||||
style['text-align'] = 'center'
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (event) {
|
||||
this.handleChangeCommon(event.target.checked)
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true
|
||||
},
|
||||
getValue (value) {
|
||||
const { params: col } = this.column
|
||||
// 处理 customValue
|
||||
if (Array.isArray(col.customValue)) {
|
||||
const customValue = getCustomValue(col)
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? customValue[0] : customValue[1]
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setValue (value) {
|
||||
const { params: col } = this.column
|
||||
// 判断是否设定了customValue(自定义值)
|
||||
if (Array.isArray(col.customValue)) {
|
||||
const customValue = getCustomValue(col)
|
||||
return neverNull(value).toString() === customValue[0].toString()
|
||||
} else {
|
||||
return !!value
|
||||
}
|
||||
},
|
||||
createValue ({ column }) {
|
||||
const { params: col } = column
|
||||
if (Array.isArray(col.customValue)) {
|
||||
const customValue = getCustomValue(col)
|
||||
return col.defaultChecked ? customValue[0] : customValue[1]
|
||||
} else {
|
||||
return !!col.defaultChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCustomValue (col) {
|
||||
const customTrue = neverNull(col.customValue[0], true)
|
||||
const customFalse = neverNull(col.customValue[1], false)
|
||||
return [customTrue, customFalse]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// 关闭动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-checkbox.no-animation {
|
||||
.ant-checkbox-inner,
|
||||
.ant-checkbox-inner::after {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
ref="datePicker"
|
||||
:value="innerDateValue"
|
||||
allowClear
|
||||
:format="dateFormat"
|
||||
:showTime="isDatetime"
|
||||
dropdownClassName="j-vxe-date-picker"
|
||||
style="min-width: 0;"
|
||||
v-bind="cellProps"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { JVXETypes } from '@comp/jero/JVxeTable'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDateCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
data () {
|
||||
return {
|
||||
innerDateValue: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDatetime () {
|
||||
return this.$type === JVXETypes.datetime
|
||||
},
|
||||
dateFormat () {
|
||||
const format = this.originColumn.format
|
||||
return format || (this.isDatetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (val == null || val === '') {
|
||||
this.innerDateValue = null
|
||||
} else {
|
||||
this.innerDateValue = moment(val, this.dateFormat)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (mom, dateStr) {
|
||||
this.handleChangeCommon(dateStr)
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived (event) {
|
||||
dispatchEvent.call(this, event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input
|
||||
v-show="!departIds"
|
||||
@click="openSelect"
|
||||
placeholder="请点击选择部门"
|
||||
v-model="departNames"
|
||||
readOnly
|
||||
:disabled="componentDisabled"
|
||||
class="jvxe-select-input">
|
||||
<a-icon slot="prefix" type="cluster" title="部门选择控件"/>
|
||||
</a-input>
|
||||
<j-select-depart-modal
|
||||
ref="innerDepartSelectModal"
|
||||
:modal-width="modalWidth"
|
||||
:multi="multi"
|
||||
:rootOpened="rootOpened"
|
||||
:depart-id="departIds"
|
||||
@ok="handleOK"
|
||||
@initComp="initComp"/>
|
||||
<span style="display: inline-block;height:100%;padding-left:14px" v-if="departIds" >
|
||||
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ departNames }}</span>
|
||||
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
import JSelectDepartModal from '@/components/jerobiz/modal/JSelectDepartModal'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDepartSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {
|
||||
JSelectDepartModal
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
departNames: '',
|
||||
departIds: '',
|
||||
selectedOptions: [],
|
||||
customReturnField: 'id'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
custProps () {
|
||||
const { departIds, originColumn: col, caseId, cellProps } = this
|
||||
return {
|
||||
...cellProps,
|
||||
value: departIds,
|
||||
field: col.field || col.key,
|
||||
groupId: caseId,
|
||||
class: 'jvxe-select'
|
||||
}
|
||||
},
|
||||
componentDisabled () {
|
||||
return this.cellProps.disabled === true
|
||||
},
|
||||
modalWidth () {
|
||||
if (this.cellProps.modalWidth) {
|
||||
return this.cellProps.modalWidth
|
||||
} else {
|
||||
return 500
|
||||
}
|
||||
},
|
||||
multi () {
|
||||
console.log(this.cellProps)
|
||||
return this.cellProps.multi !== false
|
||||
},
|
||||
rootOpened () {
|
||||
return this.cellProps.open !== false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (val == null || val === '') {
|
||||
this.departIds = ''
|
||||
} else {
|
||||
this.departIds = val
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSelect () {
|
||||
this.$refs.innerDepartSelectModal.show()
|
||||
},
|
||||
handleEmpty () {
|
||||
this.handleOK('')
|
||||
},
|
||||
handleOK (rows, idstr) {
|
||||
// let value = ''
|
||||
if (!rows && rows.length <= 0) {
|
||||
this.departNames = ''
|
||||
this.departIds = ''
|
||||
} else {
|
||||
// value = rows.map(row => row[this.customReturnField]).join(',')
|
||||
this.departNames = rows.map(row => row.departName).join(',')
|
||||
this.departIds = idstr
|
||||
}
|
||||
this.handleChangeCommon(this.departIds)
|
||||
},
|
||||
initComp (departNames) {
|
||||
this.departNames = departNames
|
||||
},
|
||||
handleChange (value) {
|
||||
this.handleChangeCommon(value)
|
||||
}
|
||||
},
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true
|
||||
},
|
||||
translate: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .jvxe-select-input .ant-input {
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['click']">
|
||||
<div class="j-vxe-ds-icons">
|
||||
<a-icon type="align-left" />
|
||||
<a-icon type="align-right" />
|
||||
</div>
|
||||
|
||||
<!-- <div class="j-vxe-ds-btns">-->
|
||||
<!-- <a-button icon="caret-up" size="small" :disabled="disabledMoveUp" @click="handleRowMoveUp"/>-->
|
||||
<!-- <a-button icon="caret-down" size="small" :disabled="disabledMoveDown" @click="handleRowMoveDown"/>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<a-menu slot="overlay">
|
||||
<a-menu-item key="0" :disabled="disabledMoveUp" @click="handleRowMoveUp">向上移</a-menu-item>
|
||||
<a-menu-item key="1" :disabled="disabledMoveDown" @click="handleRowMoveDown">向下移</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="3" @click="handleRowInsertDown">插入一行</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDragSortCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
// 排序结果保存字段
|
||||
dragSortKey () {
|
||||
return this.renderOptions.dragSortKey || 'orderNum'
|
||||
},
|
||||
disabledMoveUp () {
|
||||
return this.rowIndex === 0
|
||||
},
|
||||
disabledMoveDown () {
|
||||
return this.rowIndex === (this.fullDataLength - 1)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 向上移 */
|
||||
handleRowMoveUp () {
|
||||
// event.target.blur()
|
||||
if (!this.disabledMoveUp) {
|
||||
this.trigger('rowMoveUp', this.rowIndex)
|
||||
}
|
||||
},
|
||||
/** 向下移 */
|
||||
handleRowMoveDown () {
|
||||
// event.target.blur()
|
||||
if (!this.disabledMoveDown) {
|
||||
this.trigger('rowMoveDown', this.rowIndex)
|
||||
}
|
||||
},
|
||||
/** 插入一行 */
|
||||
handleRowInsertDown () {
|
||||
this.trigger('rowInsertDown', this.rowIndex)
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
// 【功能开关】
|
||||
switches: {
|
||||
editRender: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-vxe-ds-icons {
|
||||
position: relative;
|
||||
/*cursor: move;*/
|
||||
cursor: pointer;
|
||||
width: 14px;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
|
||||
.anticon-align-left,
|
||||
.anticon-align-right {
|
||||
position: absolute;
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.anticon-align-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.anticon-align-right {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.j-vxe-ds-btns {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 24px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-content: center;
|
||||
|
||||
.ant-btn {
|
||||
border: none;
|
||||
|
||||
z-index: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
/*height: 30%;*/
|
||||
height: 40%;
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
|
||||
&:hover {
|
||||
z-index: 1;
|
||||
/* height: 40%;*/
|
||||
|
||||
/* & .anticon-caret-up,*/
|
||||
/* & .anticon-caret-down {*/
|
||||
/* top: 2px;*/
|
||||
/* }*/
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
& .anticon-caret-up,
|
||||
& .anticon-caret-down {
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
top: 0;
|
||||
transition: top 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<a-input
|
||||
ref="input"
|
||||
:value="innerValue"
|
||||
v-bind="cellProps"
|
||||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { JVXETypes } from '@/components/jero/JVxeTable'
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
const NumberRegExp = /^-?\d+\.?\d*$/
|
||||
export default {
|
||||
name: 'JVxeInputCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
methods: {
|
||||
|
||||
/** 处理change事件 */
|
||||
handleChange (event) {
|
||||
const { $type } = this
|
||||
const { target } = event
|
||||
let { value, selectionStart } = target
|
||||
let change = true
|
||||
if ($type === JVXETypes.inputNumber) {
|
||||
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
|
||||
if (!NumberRegExp.test(value) && (value !== '' && value !== '-')) {
|
||||
change = false
|
||||
value = this.innerValue
|
||||
target.value = value || ''
|
||||
if (typeof selectionStart === 'number') {
|
||||
target.selectionStart = selectionStart - 1
|
||||
target.selectionEnd = selectionStart - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
// 触发事件,存储输入的值
|
||||
if (change) {
|
||||
this.handleChangeCommon(value)
|
||||
}
|
||||
|
||||
if ($type === JVXETypes.inputNumber) {
|
||||
// this.recalcOneStatisticsColumn(col.key)
|
||||
}
|
||||
},
|
||||
|
||||
/** 处理blur失去焦点事件 */
|
||||
handleBlur (event) {
|
||||
const { $type } = this
|
||||
const { target } = event
|
||||
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
|
||||
if ($type === JVXETypes.inputNumber) {
|
||||
if (!NumberRegExp.test(target.value)) {
|
||||
target.value = ''
|
||||
} else {
|
||||
target.value = Number.parseFloat(target.value)
|
||||
}
|
||||
this.handleChangeCommon(target.value)
|
||||
}
|
||||
|
||||
this.handleBlurCommon(target.value)
|
||||
}
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
// 自动聚焦的 class 类名
|
||||
autofocus: '.ant-input'
|
||||
},
|
||||
getValue (value) {
|
||||
if (this.$type === JVXETypes.inputNumber && typeof value === 'string') {
|
||||
if (NumberRegExp.test(value)) {
|
||||
return Number.parseFloat(value)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<reload-effect
|
||||
:vNode="innerValue"
|
||||
:effect="reloadEffect"
|
||||
@effect-end="handleEffectEnd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ReloadEffect from './ReloadEffect'
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeNormalCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: { ReloadEffect },
|
||||
computed: {
|
||||
reloadEffectRowKeysMap () {
|
||||
return this.renderOptions.reloadEffectRowKeysMap
|
||||
},
|
||||
reloadEffect () {
|
||||
return (this.renderOptions.reloadEffect && this.reloadEffectRowKeysMap[this.row.id]) === true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 特效结束
|
||||
handleEffectEnd () {
|
||||
this.$delete(this.reloadEffectRowKeysMap, this.row.id)
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<a-progress
|
||||
:class="clazz"
|
||||
:percent="innerValue"
|
||||
size="small"
|
||||
v-bind="cellProps"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
// JVxe 进度条组件
|
||||
export default {
|
||||
name: 'JVxeProgressCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
clazz () {
|
||||
return {
|
||||
'j-vxe-progress': true,
|
||||
'no-animation': this.scrolling
|
||||
}
|
||||
},
|
||||
scrolling () {
|
||||
return !!this.renderOptions.scrolling
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false
|
||||
},
|
||||
setValue (value) {
|
||||
try {
|
||||
if (typeof value !== 'number') {
|
||||
return Number.parseFloat(value)
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
// 关闭进度条的动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-progress.no-animation {
|
||||
/deep/ .ant-progress-success-bg,
|
||||
/deep/ .ant-progress-bg {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<a-select
|
||||
ref="select"
|
||||
:value="innerValue"
|
||||
allowClear
|
||||
:filterOption="handleSelectFilterOption"
|
||||
v-bind="selectProps"
|
||||
style="width: 100%;"
|
||||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
@search="handleSearchSelect">
|
||||
|
||||
<div v-if="loading" slot="notFoundContent">
|
||||
<a-icon type="loading" />
|
||||
<span> 加载中…</span>
|
||||
</div>
|
||||
|
||||
<template v-for="option of selectOptions">
|
||||
<a-select-option :key="option.value" :value="option.value" :disabled="option.disabled">
|
||||
<span>{{ option.text || option.label || option.title || option.value }}</span>
|
||||
</a-select-option>
|
||||
</template>
|
||||
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { JVXETypes } from '@comp/jero/JVxeTable'
|
||||
import { filterDictText } from '@comp/dict/JDictSelectUtil'
|
||||
|
||||
export default {
|
||||
name: 'JVxeSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
// 异步加载的options(用于多级联动)
|
||||
asyncOptions: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectOptions () {
|
||||
if (this.asyncOptions) {
|
||||
return this.asyncOptions
|
||||
}
|
||||
const { linkage } = this.renderOptions
|
||||
if (linkage) {
|
||||
return this.handleComputedSelectOptions(linkage)
|
||||
}
|
||||
return this.originColumn.options
|
||||
},
|
||||
// 下拉选项
|
||||
selectProps () {
|
||||
const props = { ...this.cellProps }
|
||||
// 判断select是否允许输入
|
||||
const { allowSearch, allowInput } = this.originColumn
|
||||
if (allowInput === true || allowSearch === true) {
|
||||
props.showSearch = true
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
created () {
|
||||
const multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
|
||||
const search = [JVXETypes.selectSearch, JVXETypes.sel_search]
|
||||
if (multiple.includes(this.$type)) {
|
||||
// 处理多选
|
||||
const props = this.originColumn.props || {}
|
||||
props.mode = 'multiple'
|
||||
props.maxTagCount = 1
|
||||
this.$set(this.originColumn, 'props', props)
|
||||
} else if (search.includes(this.$type)) {
|
||||
// 处理搜索
|
||||
this.$set(this.originColumn, 'allowSearch', true)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (value) {
|
||||
// 处理下级联动
|
||||
const linkage = this.renderOptions.linkage
|
||||
if (linkage) {
|
||||
linkage.linkageSelectChange(this.row, this.originColumn, linkage.config, value)
|
||||
}
|
||||
this.handleChangeCommon(value)
|
||||
},
|
||||
/** 处理blur失去焦点事件 */
|
||||
handleBlur (value) {
|
||||
const { allowInput, options } = this.originColumn
|
||||
|
||||
if (allowInput === true) {
|
||||
// 删除无用的因搜索(用户输入)而创建的项
|
||||
if (typeof value === 'string') {
|
||||
const indexes = []
|
||||
options.forEach((option, index) => {
|
||||
if (option.value.toLocaleString() === value.toLocaleString()) {
|
||||
delete option.searchAdd
|
||||
} else if (option.searchAdd === true) {
|
||||
indexes.push(index)
|
||||
}
|
||||
})
|
||||
// 翻转删除数组中的项
|
||||
for (const index of indexes.reverse()) {
|
||||
options.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handleBlurCommon(value)
|
||||
},
|
||||
|
||||
/** 用于搜索下拉框中的内容 */
|
||||
handleSelectFilterOption (input, option) {
|
||||
const { allowSearch, allowInput } = this.originColumn
|
||||
if (allowSearch === true || allowInput === true) {
|
||||
// update-begin-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
|
||||
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
// update-end-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
/** select 搜索时的事件,用于动态添加options */
|
||||
handleSearchSelect (value) {
|
||||
const { allowSearch, allowInput, options } = this.originColumn
|
||||
|
||||
if (allowSearch !== true && allowInput === true) {
|
||||
// 是否找到了对应的项,找不到则添加这一项
|
||||
let flag = false
|
||||
for (const option of options) {
|
||||
if (option.value.toLocaleString() === value.toLocaleString()) {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// !!value :不添加空值
|
||||
if (!flag && !!value) {
|
||||
// searchAdd 是否是通过搜索添加的
|
||||
options.push({ title: value, value: value, searchAdd: true })
|
||||
}
|
||||
}
|
||||
},
|
||||
handleComputedSelectOptions (linkage) {
|
||||
const { getLinkageOptionsSibling, config } = linkage
|
||||
const res = getLinkageOptionsSibling(this.row, this.originColumn, config, true)
|
||||
// 当返回Promise时,说明是多级联动
|
||||
if (res instanceof Promise) {
|
||||
this.loading = true
|
||||
res.then(opt => {
|
||||
this.asyncOptions = opt
|
||||
this.loading = false
|
||||
}).catch(e => {
|
||||
console.error(e)
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
this.asyncOptions = null
|
||||
return res
|
||||
}
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived (event) {
|
||||
dispatchEvent.call(this, event, 'ant-select')
|
||||
}
|
||||
},
|
||||
translate: {
|
||||
enabled: true,
|
||||
async handler (value) {
|
||||
let options
|
||||
const { linkage } = this.renderOptions
|
||||
// 判断是否是多级联动,如果是就通过接口异步翻译
|
||||
if (linkage) {
|
||||
const { getLinkageOptionsSibling, config } = linkage
|
||||
options = getLinkageOptionsSibling(this.row, this.originColumn, config, true)
|
||||
if (options instanceof Promise) {
|
||||
return new Promise(resolve => {
|
||||
options.then(opt => {
|
||||
resolve(filterDictText(opt, value))
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
options = (this.column.params || {}).options
|
||||
}
|
||||
return filterDictText(options, value)
|
||||
}
|
||||
},
|
||||
getValue (value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(',')
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setValue (value) {
|
||||
const { column: { params: col }, params: { $table } } = this
|
||||
// 判断是否是多选
|
||||
if ((col.props || {}).mode === 'multiple') {
|
||||
$table.$set(col.props, 'maxTagCount', 1)
|
||||
}
|
||||
if (value != null && value !== '') {
|
||||
if (typeof value === 'string') {
|
||||
return value === '' ? [] : value.split(',')
|
||||
}
|
||||
return value
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,46 @@
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
// 插槽
|
||||
export default {
|
||||
name: 'JVxeSlotCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
slotProps () {
|
||||
return {
|
||||
value: this.innerValue,
|
||||
row: this.row,
|
||||
column: this.originColumn,
|
||||
|
||||
params: this.params,
|
||||
$table: this.params.$table,
|
||||
rowId: this.params.rowid,
|
||||
index: this.params.rowIndex,
|
||||
rowIndex: this.params.rowIndex,
|
||||
columnIndex: this.params.columnIndex,
|
||||
|
||||
target: this.renderOptions.target,
|
||||
caseId: this.renderOptions.target.caseId,
|
||||
scrolling: this.renderOptions.scrolling,
|
||||
reloadEffect: this.renderOptions.reloadEffect,
|
||||
|
||||
triggerChange: (v) => this.handleChangeCommon(v)
|
||||
}
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
const { slot } = this.renderOptions
|
||||
if (slot) {
|
||||
return h('div', {}, slot(this.slotProps))
|
||||
} else {
|
||||
return h('div')
|
||||
}
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// :isNotPass="notPassedIds.includes(col.key+row.id)"
|
||||
@@ -0,0 +1,145 @@
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
// tags 组件的显示组件
|
||||
export const TagsSpanCell = {
|
||||
name: 'JVxeTagsCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data () {
|
||||
return {
|
||||
innerTags: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler (value) {
|
||||
if (value !== this.innerTags.join(';')) {
|
||||
const rv = replaceValue(value)
|
||||
this.innerTags = rv.split(';')
|
||||
this.handleChangeCommon(rv)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderTags (h) {
|
||||
const tags = []
|
||||
for (const tag of this.innerTags) {
|
||||
if (tag) {
|
||||
const tagProps = {}
|
||||
const tagStyle = {}
|
||||
const setTagColor = this.originColumn.setTagColor
|
||||
if (typeof setTagColor === 'function') {
|
||||
/**
|
||||
* 设置 tag 颜色
|
||||
*
|
||||
* @param event 包含的字段:
|
||||
* event.tagValue 当前tag的值
|
||||
* event.value 当前原始值
|
||||
* event.row 当前行的所有值
|
||||
* event.column 当前列的配置
|
||||
* event.column.own 当前列的原始配置
|
||||
* @return Array | String 可以返回一个数组,数据第一项是tag背景颜色,第二项是字体颜色。也可以返回一个字符串,即tag背景颜色
|
||||
*/
|
||||
const color = setTagColor({
|
||||
tagValue: tag,
|
||||
value: this.innerValue,
|
||||
row: this.row,
|
||||
column: this.column
|
||||
})
|
||||
if (Array.isArray(color)) {
|
||||
tagProps.color = color[0]
|
||||
tagStyle.color = color[1]
|
||||
} else if (color && typeof color === 'string') {
|
||||
tagProps.color = color
|
||||
}
|
||||
}
|
||||
tags.push(h('a-tag', {
|
||||
props: tagProps,
|
||||
style: tagStyle
|
||||
}, [tag]))
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h('div', {}, [
|
||||
this.renderTags(h)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
// tags 组件的输入框
|
||||
export const TagsInputCell = {
|
||||
name: 'JVxeTagsInputCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
data () {
|
||||
return {
|
||||
innerTagValue: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler (value) {
|
||||
if (value !== this.innerTagValue) {
|
||||
this.handleInputChange(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleInputChange (value, event) {
|
||||
this.innerTagValue = replaceValue(value, event)
|
||||
this.handleChangeCommon(this.innerTagValue)
|
||||
return this.innerTagValue
|
||||
}
|
||||
|
||||
},
|
||||
render (h) {
|
||||
return h('a-input', {
|
||||
props: {
|
||||
value: this.innerValue,
|
||||
...this.cellProps
|
||||
},
|
||||
on: {
|
||||
change: (event) => {
|
||||
const { target, target: { value } } = event
|
||||
const newValue = this.handleInputChange(value, event)
|
||||
if (newValue !== value) {
|
||||
target.value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 将值每隔两位加上一个分号
|
||||
function replaceValue (value, event) {
|
||||
if (value) {
|
||||
// 首先去掉现有的分号
|
||||
value = value.replace(/;/g, '')
|
||||
// 然后再遍历添加分号
|
||||
let rv = ''
|
||||
const splitArr = value.split('')
|
||||
let count = 0
|
||||
splitArr.forEach((val, index) => {
|
||||
rv += val
|
||||
const position = index + 1
|
||||
if (position % 2 === 0 && position < splitArr.length) {
|
||||
count++
|
||||
rv += ';'
|
||||
}
|
||||
})
|
||||
if (event && count > 0) {
|
||||
const { target, target: { selectionStart } } = event
|
||||
target.selectionStart = selectionStart + count
|
||||
target.selectionEnd = selectionStart + count
|
||||
}
|
||||
return rv
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<j-input-pop
|
||||
:value="innerValue"
|
||||
:width="300"
|
||||
:height="210"
|
||||
v-bind="cellProps"
|
||||
style="width: 100%;"
|
||||
@change="handleChangeCommon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JInputPop from '@/components/jero/minipop/JInputPop'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeTextareaCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: { JInputPop },
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
autofocus: '.ant-input'
|
||||
},
|
||||
aopEvents: {
|
||||
editActived (event) {
|
||||
dispatchEvent.call(this, event, 'anticon-fullscreen')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile">
|
||||
<template v-for="(file, fileKey) of [innerFile || {}]">
|
||||
<a-input :key="fileKey" :readOnly="true" :value="file.name">
|
||||
|
||||
<template slot="addonBefore" style="width: 30px">
|
||||
<a-tooltip v-if="file.status === 'uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
|
||||
<a-icon type="loading" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else-if="file.status === 'done'" title="上传完成">
|
||||
<a-icon type="check-circle" style="color:#00DB00;" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else :title="file.message||'上传失败'">
|
||||
<a-icon type="exclamation-circle" style="color:red;" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<span v-if="file.status === 'uploading'" slot="addonAfter">{{ Math.floor(file.percent) }}%</span>
|
||||
<template v-else-if="originColumn.allowDownload !== false || originColumn.allowRemove !== false" slot="addonAfter">
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight">
|
||||
<a-tooltip title="操作">
|
||||
<a-icon type="setting" style="cursor: pointer;" />
|
||||
</a-tooltip>
|
||||
|
||||
<a-menu slot="overlay">
|
||||
<!-- <a-menu-item @click="handleClickPreviewFile">-->
|
||||
<!-- <span><a-icon type="eye"/> 预览</span>-->
|
||||
<!-- </a-menu-item>-->
|
||||
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
|
||||
<span><a-icon type="download" /> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><a-icon type="delete" /> 删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
</a-input>
|
||||
</template>
|
||||
</template>
|
||||
<a-upload
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{'isup': 1}"
|
||||
:multiple="false"
|
||||
:action="uploadAction"
|
||||
:headers="uploadHeaders"
|
||||
:showUploadList="false"
|
||||
v-bind="cellProps"
|
||||
@change="handleChangeUpload">
|
||||
<a-button icon="upload">{{ originColumn.btnText || '点击上传' }}</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import { getFileAccessHttpUrl } from '@api/manage'
|
||||
|
||||
export default {
|
||||
name: 'JVxeUploadCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
data () {
|
||||
return {
|
||||
innerFile: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders () {
|
||||
const { originColumn: col } = this
|
||||
const headers = {}
|
||||
if (col.token === true) {
|
||||
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
hasFile () {
|
||||
return !!this.innerFile
|
||||
},
|
||||
uploadAction () {
|
||||
if (this.originColumn.action) {
|
||||
return this.originColumn.action
|
||||
}
|
||||
return window._CONFIG.domianURL + '/sys/common/upload'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler () {
|
||||
if (this.innerValue) {
|
||||
this.innerFile = this.innerValue
|
||||
} else {
|
||||
this.innerFile = null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleChangeUpload (info) {
|
||||
const { originColumn: col } = this
|
||||
const { file } = info
|
||||
const value = {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
status: file.status,
|
||||
percent: file.percent
|
||||
}
|
||||
if (col.responseName && file.response) {
|
||||
value.responseName = file.response[col.responseName]
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
if (typeof file.response.success === 'boolean') {
|
||||
if (file.response.success) {
|
||||
value.path = col.responseName ? file.response[col.responseName] : file.response.result.url
|
||||
} else {
|
||||
value.status = 'error'
|
||||
value.message = file.response.message || '未知错误'
|
||||
}
|
||||
} else {
|
||||
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
|
||||
value.path = file.response[col.responseName]
|
||||
}
|
||||
} else if (file.status === 'error') {
|
||||
value.message = file.response.message || '未知错误'
|
||||
}
|
||||
this.innerFile = value
|
||||
// 上传完成后触发通用change事件,将值抛出去
|
||||
if (file.status === 'done') {
|
||||
this.handleChangeCommon(this.innerFile)
|
||||
}
|
||||
},
|
||||
|
||||
// handleClickPreviewFile(id) {
|
||||
// this.$message.info('尚未实现')
|
||||
// },
|
||||
|
||||
handleClickDownloadFile () {
|
||||
const { path } = this.value || {}
|
||||
if (path) {
|
||||
const url = getFileAccessHttpUrl(path)
|
||||
window.open(url)
|
||||
}
|
||||
},
|
||||
|
||||
handleClickDeleteFile () {
|
||||
this.handleChangeCommon(null)
|
||||
}
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
switches: { visible: true },
|
||||
getValue: value => fileGetValue(value),
|
||||
setValue: value => fileSetValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
function fileGetValue (value) {
|
||||
if (value && value.path) {
|
||||
return value.path
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function fileSetValue (value) {
|
||||
if (value) {
|
||||
const first = value.split(',')[0]
|
||||
const name = first.substring(first.lastIndexOf('/') + 1)
|
||||
return {
|
||||
name: name,
|
||||
path: value,
|
||||
status: 'done'
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input
|
||||
v-show="!userIds"
|
||||
@click="openSelect"
|
||||
placeholder="请选择用户"
|
||||
v-model="userNames"
|
||||
readOnly
|
||||
class="jvxe-select-input"
|
||||
:disabled="componentDisabled">
|
||||
<a-icon slot="prefix" type="user" title="用户选择控件"/>
|
||||
</a-input>
|
||||
<j-select-user-by-dep-modal
|
||||
ref="selectModal"
|
||||
:modal-width="modalWidth"
|
||||
:multi="multi"
|
||||
:user-ids="userIds"
|
||||
@ok="selectOK"
|
||||
@initComp="initComp"/>
|
||||
<span style="display: inline-block;height:100%;padding-left:14px" v-if="userIds" >
|
||||
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ userNames }}</span>
|
||||
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- <j-select-user-by-dep
|
||||
v-bind="custProps"
|
||||
@change="handleChange"
|
||||
:trigger-change="true">
|
||||
</j-select-user-by-dep>-->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
|
||||
import JSelectUserByDepModal from '@/components/jerobiz/modal/JSelectUserByDepModal'
|
||||
|
||||
export default {
|
||||
name: 'JVxeUserSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: { JSelectUserByDepModal },
|
||||
data () {
|
||||
return {
|
||||
userIds: '',
|
||||
userNames: '',
|
||||
innerUserValue: '',
|
||||
selectedOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
custProps () {
|
||||
const { userIds, originColumn: col, caseId, cellProps } = this
|
||||
return {
|
||||
...cellProps,
|
||||
value: userIds,
|
||||
field: col.field || col.key,
|
||||
groupId: caseId,
|
||||
class: 'jvxe-select'
|
||||
}
|
||||
},
|
||||
componentDisabled () {
|
||||
console.log('333', this.cellProps)
|
||||
return this.cellProps.disabled === true
|
||||
},
|
||||
modalWidth () {
|
||||
if (this.cellProps.modalWidth) {
|
||||
return this.cellProps.modalWidth
|
||||
} else {
|
||||
return 1250
|
||||
}
|
||||
},
|
||||
multi () {
|
||||
return this.cellProps.multi !== false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
if (val == null || val === '') {
|
||||
this.userIds = ''
|
||||
} else {
|
||||
this.userIds = val
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openSelect () {
|
||||
this.$refs.selectModal.showModal()
|
||||
},
|
||||
selectOK (rows, idstr) {
|
||||
console.log('当前选中用户', rows)
|
||||
console.log('当前选中用户ID', idstr)
|
||||
if (!rows) {
|
||||
this.userNames = ''
|
||||
this.userIds = ''
|
||||
} else {
|
||||
let temp = ''
|
||||
for (const item of rows) {
|
||||
temp += ',' + item.realname
|
||||
}
|
||||
this.userNames = temp.substring(1)
|
||||
this.userIds = idstr
|
||||
}
|
||||
this.handleChangeCommon(this.userIds)
|
||||
},
|
||||
handleEmpty () {
|
||||
this.selectOK('')
|
||||
},
|
||||
initComp (userNames) {
|
||||
this.userNames = userNames
|
||||
}
|
||||
},
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true
|
||||
},
|
||||
translate: {
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/deep/ .jvxe-select-input .ant-input {
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
import '../../less/reload-effect.less'
|
||||
import { randomString } from '@/utils/util'
|
||||
|
||||
// 修改数据特效
|
||||
export default {
|
||||
props: {
|
||||
vNode: null,
|
||||
// 是否启用特效
|
||||
effect: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// vNode: null,
|
||||
innerEffect: false,
|
||||
// 应付同时多个特效
|
||||
effectIdx: 0,
|
||||
effectList: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
vNode: {
|
||||
deep: true,
|
||||
immediate: true,
|
||||
handler (vNode, old) {
|
||||
this.innerEffect = this.effect
|
||||
if (this.innerEffect && old != null) {
|
||||
const topLayer = this.renderSpan(old, 'top')
|
||||
this.effectList.push(topLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 条件渲染内容 span
|
||||
renderVNode () {
|
||||
if (this.vNode == null) {
|
||||
return null
|
||||
}
|
||||
const bottom = this.renderSpan(this.vNode, 'bottom')
|
||||
// 启用了特效,并且有旧数据,就渲染特效顶层
|
||||
if (this.innerEffect && this.effectList.length > 0) {
|
||||
this.$emit('effect-begin')
|
||||
// 1.4s 以后关闭特效
|
||||
window.setTimeout(() => {
|
||||
const item = this.effectList[this.effectIdx]
|
||||
if (item && item.elm) {
|
||||
// 特效结束后,展示先把 display 设为 none,而不是直接删掉该元素,
|
||||
// 目的是为了防止页面重新渲染,导致动画重置
|
||||
item.elm.style.display = 'none'
|
||||
}
|
||||
// 当所有的层级动画都结束时,再删掉所有元素
|
||||
if (++this.effectIdx === this.effectList.length) {
|
||||
this.innerEffect = false
|
||||
this.effectIdx = 0
|
||||
this.effectList = []
|
||||
this.$emit('effect-end')
|
||||
}
|
||||
}, 1400)
|
||||
return [this.effectList, bottom]
|
||||
} else {
|
||||
return bottom
|
||||
}
|
||||
},
|
||||
// 渲染内容 span
|
||||
renderSpan (vNode, layer) {
|
||||
const options = {
|
||||
key: layer + this.effectIdx + randomString(6),
|
||||
class: ['j-vxe-reload-effect-span', `layer-${layer}`],
|
||||
style: {}
|
||||
}
|
||||
if (layer === 'top') {
|
||||
// 最新渲染的在下面
|
||||
options.style['z-index'] = (9999 - this.effectIdx)
|
||||
}
|
||||
return this.$createElement('span', options, [vNode])
|
||||
}
|
||||
},
|
||||
render (h) {
|
||||
return h('div', {
|
||||
class: ['j-vxe-reload-effect-box']
|
||||
}, [this.renderVNode()])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import * as jvxeTypes from './jvxeTypes'
|
||||
import { installCell, mapCell } from './install'
|
||||
import JVxeTable from './components/JVxeTable'
|
||||
|
||||
import JVxeSlotCell from './components/cells/JVxeSlotCell'
|
||||
import JVxeNormalCell from './components/cells/JVxeNormalCell'
|
||||
import JVxeInputCell from './components/cells/JVxeInputCell'
|
||||
import JVxeDateCell from './components/cells/JVxeDateCell'
|
||||
import JVxeSelectCell from './components/cells/JVxeSelectCell'
|
||||
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell'
|
||||
import JVxeUploadCell from './components/cells/JVxeUploadCell'
|
||||
import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell'
|
||||
import JVxeProgressCell from './components/cells/JVxeProgressCell'
|
||||
import JVxeTextareaCell from './components/cells/JVxeTextareaCell'
|
||||
import JVxeDragSortCell from './components/cells/JVxeDragSortCell'
|
||||
import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell'
|
||||
import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell'
|
||||
|
||||
// update--begin--autor:lvdandan-----date:20201216------for:JVxeTable--JVXETypes 【online】代码结构调整,便于online打包
|
||||
// 组件类型
|
||||
export const JVXETypes = jvxeTypes.JVXETypes
|
||||
// update--end--autor:lvdandan-----date:20201216------for:JVxeTable--JVXETypes 【online】代码结构调整,便于online打包
|
||||
|
||||
// 注册自定义组件
|
||||
export const AllCells = {
|
||||
...mapCell(JVXETypes.normal, JVxeNormalCell),
|
||||
...mapCell(JVXETypes.input, JVxeInputCell),
|
||||
...mapCell(JVXETypes.inputNumber, JVxeInputCell),
|
||||
...mapCell(JVXETypes.checkbox, JVxeCheckboxCell),
|
||||
...mapCell(JVXETypes.select, JVxeSelectCell),
|
||||
...mapCell(JVXETypes.selectSearch, JVxeSelectCell), // 下拉搜索
|
||||
...mapCell(JVXETypes.selectMultiple, JVxeSelectCell), // 下拉多选
|
||||
...mapCell(JVXETypes.date, JVxeDateCell),
|
||||
...mapCell(JVXETypes.datetime, JVxeDateCell),
|
||||
...mapCell(JVXETypes.upload, JVxeUploadCell),
|
||||
...mapCell(JVXETypes.textarea, JVxeTextareaCell),
|
||||
|
||||
...mapCell(JVXETypes.tags, TagsInputCell, TagsSpanCell),
|
||||
...mapCell(JVXETypes.progress, JVxeProgressCell),
|
||||
|
||||
...mapCell(JVXETypes.rowDragSort, JVxeDragSortCell),
|
||||
...mapCell(JVXETypes.slot, JVxeSlotCell),
|
||||
...mapCell(JVXETypes.departSelect, JVxeDepartSelectCell),
|
||||
...mapCell(JVXETypes.userSelect, JVxeUserSelectCell)
|
||||
|
||||
/* hidden 是特殊的组件,不在这里注册 */
|
||||
}
|
||||
|
||||
export { installCell, mapCell }
|
||||
|
||||
export default JVxeTable
|
||||
@@ -0,0 +1,105 @@
|
||||
import Vue from 'vue'
|
||||
import { getEventPath, evil } from '@/utils/util'
|
||||
import JVxeTable, { AllCells, JVXETypes } from './index'
|
||||
import './less/j-vxe-table.less'
|
||||
// 引入 vxe-table
|
||||
import 'xe-utils'
|
||||
import VXETable, { Grid } from 'vxe-table'
|
||||
import VXETablePluginAntd from 'vxe-table-plugin-antd'
|
||||
import 'vxe-table/lib/index.css'
|
||||
import 'vxe-table-plugin-antd/dist/style.css'
|
||||
import { getEnhancedMixins, installAllCell, installOneCell } from '@/components/jero/JVxeTable/utils/cellUtils'
|
||||
|
||||
// VxeGrid所有的方法映射
|
||||
const VxeGridMethodsMap = {}
|
||||
Object.keys(Grid.methods).forEach(key => {
|
||||
// 使用eval可以避免闭包(但是要注意不要写es6的代码)
|
||||
VxeGridMethodsMap[key] = evil(`(function(){return this.$refs.vxe.${key}.apply(this.$refs.vxe,arguments)})`)
|
||||
})
|
||||
// 将Grid所有的方法都映射(继承)到JVxeTable上
|
||||
JVxeTable.methods = Object.assign({}, VxeGridMethodsMap, JVxeTable.methods)
|
||||
|
||||
// VXETable 全局配置
|
||||
const VXETableSettings = {
|
||||
// z-index 起始值
|
||||
zIndex: 1000,
|
||||
table: {
|
||||
validConfig: {
|
||||
// 校验提示方式:强制使用tooltip
|
||||
message: 'tooltip'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行注册方法
|
||||
Vue.use(VXETable, VXETableSettings)
|
||||
VXETable.use(VXETablePluginAntd)
|
||||
Vue.component(JVxeTable.name, JVxeTable)
|
||||
|
||||
// 注册自定义组件
|
||||
installAllCell(VXETable)
|
||||
|
||||
// 添加事件拦截器 event.clearActived
|
||||
// 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
|
||||
VXETable.interceptor.add('event.clearActived', function (params) {
|
||||
// 获取组件增强
|
||||
const col = params.column.params || {}
|
||||
const interceptor = getEnhancedMixins(col.$type, 'interceptor')
|
||||
// 执行增强
|
||||
const flag = interceptor['event.clearActived'].apply(this, arguments)
|
||||
if (flag === false) {
|
||||
return false
|
||||
}
|
||||
// 此处判断一下event是否存在,防止后续内容报错
|
||||
const path = params.$event ? getEventPath(params.$event) : []
|
||||
for (const p of path) {
|
||||
let className = p.className || ''
|
||||
className = typeof className === 'string' ? className : className.toString()
|
||||
|
||||
/* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */
|
||||
|
||||
// 点击的标签是JInputPop
|
||||
if (className.includes('j-input-pop')) {
|
||||
return false
|
||||
}
|
||||
// 点击的标签是JPopup的弹出层、部门选择、用户选择
|
||||
if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) {
|
||||
return false
|
||||
}
|
||||
// 执行增强
|
||||
const flag = interceptor['event.clearActived.className'].apply(this, [className, ...arguments])
|
||||
if (flag === false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 注册map
|
||||
* @param type 类型
|
||||
* @param cell 输入组件
|
||||
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
|
||||
*/
|
||||
export function mapCell (type, cell, span) {
|
||||
const cells = { [type]: cell }
|
||||
if (span) {
|
||||
cells[type + ':span'] = span
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册自定义组件
|
||||
*
|
||||
* @param type 类型
|
||||
* @param cell 输入组件
|
||||
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
|
||||
*/
|
||||
export function installCell (type, cell, span) {
|
||||
const exclude = [JVXETypes.rowNumber, JVXETypes.rowCheckbox, JVXETypes.rowRadio, JVXETypes.rowExpand, JVXETypes.rowDragSort]
|
||||
if (exclude.includes(type)) {
|
||||
throw new Error(`【installCell】不能使用"${type}"作为组件的type,因为这是关键字。`)
|
||||
}
|
||||
Object.assign(AllCells, mapCell(type, cell, span))
|
||||
installOneCell(VXETable, type)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// 组件类型
|
||||
export const JVXETypes = {
|
||||
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
|
||||
// 前缀是自动加的,代码中直接用就行(JVXETypes.input)
|
||||
_prefix: 'j-',
|
||||
|
||||
// 行号列
|
||||
rowNumber: 'row-number',
|
||||
// 选择列
|
||||
rowCheckbox: 'row-checkbox',
|
||||
// 单选列
|
||||
rowRadio: 'row-radio',
|
||||
// 展开列
|
||||
rowExpand: 'row-expand',
|
||||
// 上下排序
|
||||
rowDragSort: 'row-drag-sort',
|
||||
|
||||
input: 'input',
|
||||
inputNumber: 'inputNumber',
|
||||
textarea: 'textarea',
|
||||
select: 'select',
|
||||
date: 'date',
|
||||
datetime: 'datetime',
|
||||
checkbox: 'checkbox',
|
||||
upload: 'upload',
|
||||
// 下拉搜索
|
||||
selectSearch: 'select-search',
|
||||
// 下拉多选
|
||||
selectMultiple: 'select-multiple',
|
||||
// 进度条
|
||||
progress: 'progress',
|
||||
// 部门选择
|
||||
departSelect: 'sel_depart',
|
||||
// 用户选择
|
||||
userSelect: 'sel_user',
|
||||
|
||||
// 拖轮Tags(暂无用)
|
||||
tags: 'tags',
|
||||
|
||||
slot: 'slot',
|
||||
normal: 'normal',
|
||||
hidden: 'hidden'
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
@import "size/tiny";
|
||||
|
||||
.j-vxe-table-box {
|
||||
|
||||
// 工具栏
|
||||
.j-vxe-toolbar {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// 分页器
|
||||
.j-vxe-pagination {
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
|
||||
.ant-pagination-options-size-changer.ant-select {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&.show-quick-jumper {
|
||||
.ant-pagination-options-size-changer.ant-select {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更改 header 底色
|
||||
.vxe-table.border--default .vxe-table--header-wrapper,
|
||||
.vxe-table.border--full .vxe-table--header-wrapper,
|
||||
.vxe-table.border--outer .vxe-table--header-wrapper {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 更改 tooltip 校验失败的颜色
|
||||
.vxe-table--tooltip-wrapper.vxe-table--valid-error {
|
||||
background-color: #f5222d !important;
|
||||
}
|
||||
|
||||
// 更改 输入框 校验失败的颜色
|
||||
.col--valid-error > .vxe-cell > .ant-input,
|
||||
.col--valid-error > .vxe-cell > .ant-select .ant-input,
|
||||
.col--valid-error > .vxe-cell > .ant-select .ant-select-selection,
|
||||
.col--valid-error > .vxe-cell > .ant-input-number,
|
||||
.col--valid-error > .vxe-cell > .ant-cascader-picker .ant-cascader-input,
|
||||
.col--valid-error > .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input,
|
||||
.col--valid-error > .vxe-tree-cell > .ant-input,
|
||||
.col--valid-error > .vxe-tree-cell > .ant-select .ant-input,
|
||||
.col--valid-error > .vxe-tree-cell > .ant-select .ant-select-selection,
|
||||
.col--valid-error > .vxe-tree-cell > .ant-input-number,
|
||||
.col--valid-error > .vxe-tree-cell > .ant-cascader-picker .ant-cascader-input,
|
||||
.col--valid-error > .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input {
|
||||
border-color: #f5222d !important;
|
||||
}
|
||||
|
||||
// 拖拽排序列样式
|
||||
.vxe-table .col--row-drag-sort .vxe-cell {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
.j-vxe-reload-effect-box {
|
||||
|
||||
&,
|
||||
.j-vxe-reload-effect-span {
|
||||
display: inline;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.j-vxe-reload-effect-span {
|
||||
|
||||
&.layer-top {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
background-color: white;
|
||||
|
||||
transform-origin: 0 0;
|
||||
animation: reload-effect 1.5s forwards;
|
||||
}
|
||||
|
||||
&.layer-bottom {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 定义动画
|
||||
@keyframes reload-effect {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: rotateX(0);
|
||||
}
|
||||
10% {
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
.j-vxe-table-box {
|
||||
|
||||
@height: 24px;
|
||||
@lineHeight: 1.5;
|
||||
@spacing: 4px;
|
||||
@fontSize: 14px;
|
||||
@borderRadius: 2px;
|
||||
|
||||
&.size--tiny {
|
||||
|
||||
.vxe-table--header .vxe-cell--checkbox {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.vxe-table--body .vxe-cell--checkbox {
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.vxe-cell {
|
||||
padding: 0 5px;
|
||||
font-size: @fontSize;
|
||||
line-height: @lineHeight;
|
||||
}
|
||||
|
||||
.vxe-table .vxe-header--column .vxe-cell {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.vxe-body--column.col--actived {
|
||||
padding: 0;
|
||||
|
||||
.vxe-cell {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ant输入框
|
||||
.ant-input,
|
||||
// ant下拉框
|
||||
.ant-select-selection {
|
||||
padding: 2px @spacing;
|
||||
height: @height;
|
||||
font-size: @fontSize;
|
||||
border-radius: @borderRadius;
|
||||
line-height: @lineHeight;
|
||||
}
|
||||
|
||||
// 输入框图标对齐
|
||||
.ant-input-affix-wrapper {
|
||||
& .ant-input-prefix {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
& .ant-input:not(:first-child) {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮 addon
|
||||
.ant-input-group-addon {
|
||||
border-color: transparent;
|
||||
border-radius: @borderRadius;
|
||||
}
|
||||
|
||||
|
||||
// ant下拉多选框
|
||||
.ant-select-selection--multiple {
|
||||
min-height: @height;
|
||||
|
||||
& .ant-select-selection__rendered > ul > li {
|
||||
height: calc(@height - 6px);
|
||||
font-size: calc(@fontSize - 2px);
|
||||
margin-top: 0;
|
||||
line-height: @lineHeight;
|
||||
padding: 0 18px 0 4px;
|
||||
|
||||
}
|
||||
|
||||
& .ant-select-selection__clear,
|
||||
& .ant-select-arrow {
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ant按钮
|
||||
.ant-upload {
|
||||
width: 100%;
|
||||
|
||||
.ant-btn {
|
||||
width: 100%;
|
||||
height: @height;
|
||||
padding: 0 8px;
|
||||
font-size: @fontSize;
|
||||
border-color: transparent;
|
||||
background-color: transparent;
|
||||
border-radius: @borderRadius;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection__rendered {
|
||||
line-height: @lineHeight;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
|
||||
// 工具栏
|
||||
.j-vxe-toolbar {
|
||||
margin-bottom: 4px;
|
||||
|
||||
.ant-form-item-label,
|
||||
.ant-form-item-control {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.ant-form-inline .ant-form-item {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** 内置属性 */
|
||||
|
||||
.vxe-table.size--tiny {
|
||||
& .vxe-table--expanded {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
& .vxe-body--expanded-cell {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.size--tiny .vxe-loading .vxe-loading--spinner {
|
||||
width: 38px;
|
||||
height: 38px
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny .vxe-body--column.col--ellipsis,
|
||||
.vxe-table.size--tiny .vxe-footer--column.col--ellipsis,
|
||||
.vxe-table.size--tiny .vxe-header--column.col--ellipsis,
|
||||
.vxe-table.vxe-editable.size--tiny .vxe-body--column {
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny .vxe-table--empty-block,
|
||||
.vxe-table.size--tiny .vxe-table--empty-placeholder {
|
||||
min-height: @height;
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny .vxe-body--column:not(.col--ellipsis),
|
||||
.vxe-table.size--tiny .vxe-footer--column:not(.col--ellipsis),
|
||||
.vxe-table.size--tiny .vxe-header--column:not(.col--ellipsis) {
|
||||
padding: 4px 0
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny .vxe-cell .vxe-default-input,
|
||||
.vxe-table.size--tiny .vxe-cell .vxe-default-select,
|
||||
.vxe-table.size--tiny .vxe-cell .vxe-default-textarea {
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny .vxe-cell .vxe-default-input[type=date]::-webkit-inner-spin-button {
|
||||
margin-top: 1px
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny.virtual--x .col--ellipsis .vxe-cell,
|
||||
.vxe-table.size--tiny.virtual--y .col--ellipsis .vxe-cell,
|
||||
.vxe-table.size--tiny .vxe-body--column.col--ellipsis .vxe-cell,
|
||||
.vxe-table.size--tiny .vxe-footer--column.col--ellipsis .vxe-cell,
|
||||
.vxe-table.size--tiny .vxe-header--column.col--ellipsis .vxe-cell {
|
||||
max-height: @height;
|
||||
}
|
||||
|
||||
.vxe-table.size--tiny .vxe-cell--checkbox .vxe-checkbox--icon,
|
||||
.vxe-table.size--tiny .vxe-cell--radio .vxe-radio--icon {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
|
||||
.vxe-table.size--tiny .vxe-table--filter-option > .vxe-checkbox--icon,
|
||||
.vxe-table.size--small .vxe-table--filter-option > .vxe-checkbox--icon {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.vxe-modal--wrapper.size--tiny .vxe-export--panel-column-option > .vxe-checkbox--icon,
|
||||
.vxe-modal--wrapper.size--small .vxe-export--panel-column-option > .vxe-checkbox--icon {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.vxe-grid.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-toolbar.size--tiny {
|
||||
font-size: 12px;
|
||||
height: 46px
|
||||
}
|
||||
|
||||
.vxe-toolbar.size--tiny .vxe-custom--option > .vxe-checkbox--icon {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.vxe-pager.size--tiny {
|
||||
font-size: 12px;
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.vxe-checkbox.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-checkbox.size--tiny .vxe-checkbox--icon {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.vxe-radio-button.size--tiny .vxe-radio--label {
|
||||
line-height: 26px
|
||||
}
|
||||
|
||||
.vxe-radio.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-radio.size--tiny .vxe-radio--icon {
|
||||
font-size: 14px
|
||||
}
|
||||
|
||||
.vxe-input.size--tiny {
|
||||
font-size: 12px;
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.vxe-input.size--tiny .vxe-input--inner[type=date]::-webkit-inner-spin-button,
|
||||
.vxe-input.size--tiny .vxe-input--inner[type=month]::-webkit-inner-spin-button,
|
||||
.vxe-input.size--tiny .vxe-input--inner[type=week]::-webkit-inner-spin-button {
|
||||
margin-top: 0
|
||||
}
|
||||
|
||||
.vxe-dropdown--panel.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-textarea--autosize.size--tiny,
|
||||
.vxe-textarea.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-textarea.size--tiny:not(.is--autosize) {
|
||||
min-height: @height;
|
||||
}
|
||||
|
||||
.vxe-button.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-button.size--tiny.type--button {
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.vxe-button.size--tiny.type--button.is--circle {
|
||||
min-width: @height;
|
||||
}
|
||||
|
||||
.vxe-button.size--tiny.type--button.is--round {
|
||||
border-radius: 14px
|
||||
}
|
||||
|
||||
.vxe-button.size--tiny .vxe-button--icon,
|
||||
.vxe-button.size--tiny .vxe-button--loading-icon {
|
||||
min-width: 12px
|
||||
}
|
||||
|
||||
.vxe-modal--wrapper.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-form.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-form.size--tiny .vxe-form--item-inner {
|
||||
min-height: 30px
|
||||
}
|
||||
|
||||
.vxe-form.size--tiny .vxe-default-input[type=reset],
|
||||
.vxe-form.size--tiny .vxe-default-input[type=submit] {
|
||||
line-height: 26px
|
||||
}
|
||||
|
||||
.vxe-form.size--tiny .vxe-default-input,
|
||||
.vxe-form.size--tiny .vxe-default-select {
|
||||
height: @height;
|
||||
}
|
||||
|
||||
.vxe-select--panel.size--tiny,
|
||||
.vxe-select.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.vxe-select--panel.size--tiny .vxe-optgroup--title,
|
||||
.vxe-select--panel.size--tiny .vxe-select-option {
|
||||
height: 24px;
|
||||
line-height: 24px
|
||||
}
|
||||
|
||||
.vxe-switch.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
|
||||
.vxe-pulldown--panel.size--tiny,
|
||||
.vxe-pulldown.size--tiny {
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
import PropTypes from 'ant-design-vue/es/_util/vue-types'
|
||||
import { filterDictText } from '@/components/dict/JDictSelectUtil'
|
||||
import { getEnhancedMixins, JVXERenderType, replaceProps } from '@/components/jero/JVxeTable/utils/cellUtils'
|
||||
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
export default {
|
||||
inject: {
|
||||
getParentContainer: { default: () => (node) => node.parentNode }
|
||||
},
|
||||
props: {
|
||||
value: PropTypes.any,
|
||||
row: PropTypes.object,
|
||||
column: PropTypes.object,
|
||||
// 组件参数
|
||||
params: PropTypes.object,
|
||||
// 渲染选项
|
||||
renderOptions: PropTypes.object,
|
||||
// 渲染类型
|
||||
renderType: PropTypes.string.def('default')
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
innerValue: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
caseId () {
|
||||
return this.renderOptions.caseId
|
||||
},
|
||||
originColumn () {
|
||||
return this.column.params || {}
|
||||
},
|
||||
$type () {
|
||||
return this.originColumn.type
|
||||
},
|
||||
rows () {
|
||||
return this.params.data
|
||||
},
|
||||
fullDataLength () {
|
||||
return this.params.$table.tableFullData.length
|
||||
},
|
||||
rowIndex () {
|
||||
return this.params.rowIndex
|
||||
},
|
||||
columnIndex () {
|
||||
return this.params.columnIndex
|
||||
},
|
||||
cellProps () {
|
||||
const { originColumn: col, renderOptions } = this
|
||||
|
||||
const props = {}
|
||||
|
||||
// 输入占位符
|
||||
props.placeholder = replaceProps(col, col.placeholder)
|
||||
|
||||
// 解析props
|
||||
if (typeof col.props === 'object') {
|
||||
Object.keys(col.props).forEach(key => {
|
||||
props[key] = replaceProps(col, col.props[key])
|
||||
})
|
||||
}
|
||||
|
||||
// 判断是否是禁用的列
|
||||
props.disabled = (typeof col.disabled === 'boolean' ? col.disabled : props.disabled)
|
||||
|
||||
// TODO 判断是否是禁用的行
|
||||
// if (props['disabled'] !== true) {
|
||||
// props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
|
||||
// }
|
||||
|
||||
// 判断是否禁用所有组件
|
||||
if (renderOptions.disabled === true) {
|
||||
props.disabled = true
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$type: {
|
||||
immediate: true,
|
||||
handler ($type) {
|
||||
this.enhanced = getEnhancedMixins($type)
|
||||
this.listeners = getListeners.call(this)
|
||||
}
|
||||
},
|
||||
value: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
let value = val
|
||||
|
||||
// 验证值格式
|
||||
const originValue = this.row[this.column.property]
|
||||
const getValue = this.enhanced.getValue.call(this, originValue)
|
||||
if (originValue !== getValue) {
|
||||
// 值格式不正确,重新赋值
|
||||
value = getValue
|
||||
vModel.call(this, value)
|
||||
}
|
||||
|
||||
this.innerValue = this.enhanced.setValue.call(this, value)
|
||||
|
||||
// 判断是否启用翻译
|
||||
if (this.renderType === JVXERenderType.spaner && this.enhanced.translate.enabled) {
|
||||
const res = this.enhanced.translate.handler.call(this, value)
|
||||
// 异步翻译,目前仅【多级联动】使用
|
||||
if (res instanceof Promise) {
|
||||
res.then(value => {
|
||||
this.innerValue = value
|
||||
})
|
||||
} else {
|
||||
this.innerValue = res
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 通用处理change事件 */
|
||||
handleChangeCommon (value) {
|
||||
const handle = this.enhanced.getValue.call(this, value)
|
||||
this.trigger('change', { value: handle })
|
||||
// 触发valueChange事件
|
||||
this.parentTrigger('valueChange', {
|
||||
type: this.$type,
|
||||
value: handle,
|
||||
oldValue: this.value,
|
||||
col: this.originColumn,
|
||||
rowIndex: this.params.rowIndex,
|
||||
columnIndex: this.params.columnIndex
|
||||
})
|
||||
},
|
||||
/** 通用处理blur事件 */
|
||||
handleBlurCommon (value) {
|
||||
this.trigger('blur', { value })
|
||||
},
|
||||
|
||||
/**
|
||||
* 如果事件存在的话,就触发
|
||||
* @param name 事件名
|
||||
* @param event 事件参数
|
||||
* @param args 其他附带参数
|
||||
*/
|
||||
trigger (name, event, args = []) {
|
||||
const listener = this.listeners[name]
|
||||
if (typeof listener === 'function') {
|
||||
if (typeof event === 'object') {
|
||||
event = this.packageEvent(name, event)
|
||||
}
|
||||
listener(event, ...args)
|
||||
}
|
||||
},
|
||||
parentTrigger (name, event, args = []) {
|
||||
args.unshift(this.packageEvent(name, event))
|
||||
this.trigger('trigger', name, args)
|
||||
},
|
||||
packageEvent (name, event = {}) {
|
||||
event.row = this.row
|
||||
event.column = this.column
|
||||
// online增强参数兼容
|
||||
event.column.key = this.column.property
|
||||
event.cellTarget = this
|
||||
if (!event.type) {
|
||||
event.type = name
|
||||
}
|
||||
if (!event.cellType) {
|
||||
event.cellType = this.$type
|
||||
}
|
||||
// 是否校验表单,默认为true
|
||||
if (typeof event.validate !== 'boolean') {
|
||||
event.validate = true
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
/**
|
||||
* 【自定义增强】用于实现一些增强事件
|
||||
* 【注】这里只是定义接口,具体功能需要到各个组件内实现(也有部分功能实现)
|
||||
* 【注】该属性不是Vue官方属性,是JVxeTable组件自定义的
|
||||
* 所以方法内的 this 指向并不是当前组件,而是方法自身,
|
||||
* 也就是说并不能 this 打点调实例里的任何方法
|
||||
*/
|
||||
enhanced: {
|
||||
// 注册参数(详见:https://xuliangzhan_admin.gitee.io/vxe-table/#/table/renderer/edit)
|
||||
installOptions: {
|
||||
// 自动聚焦的 class 类名
|
||||
autofocus: ''
|
||||
},
|
||||
// 事件拦截器(用于兼容)
|
||||
interceptor: {
|
||||
// 已实现:event.clearActived
|
||||
// 说明:比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
|
||||
'event.clearActived' (params, event, target) {
|
||||
return true
|
||||
},
|
||||
// 自定义:event.clearActived.className
|
||||
// 说明:比原生的多了一个参数:className,用于判断点击的元素的样式名(递归到顶层)
|
||||
'event.clearActived.className' (params, event, target) {
|
||||
return true
|
||||
}
|
||||
},
|
||||
// 【功能开关】
|
||||
switches: {
|
||||
// 是否使用 editRender 模式(仅当前组件,并非全局)
|
||||
// 如果设为true,则表头上方会出现一个可编辑的图标
|
||||
editRender: true,
|
||||
// false = 组件触发后可视);true = 组件一直可视
|
||||
visible: false
|
||||
},
|
||||
// 【切面增强】切面事件处理,一般在某些方法执行后同步执行
|
||||
aopEvents: {
|
||||
// 单元格被激活编辑时会触发该事件
|
||||
editActived () {
|
||||
},
|
||||
// 单元格编辑状态下被关闭时会触发该事件
|
||||
editClosed () {
|
||||
}
|
||||
},
|
||||
// 【翻译增强】可以实现例如select组件保存的value,但是span模式下需要显示成text
|
||||
translate: {
|
||||
// 是否启用翻译
|
||||
enabled: false,
|
||||
/**
|
||||
* 【翻译处理方法】如果handler留空,则使用默认的翻译方法
|
||||
* (this指向当前组件)
|
||||
*
|
||||
* @param value 需要翻译的值
|
||||
* @returns{*} 返回翻译后的数据
|
||||
*/
|
||||
handler (value) {
|
||||
// 默认翻译方法
|
||||
return filterDictText(this.column.own.options, value)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 【获取值增强】组件抛出的值
|
||||
* (this指向当前组件)
|
||||
*
|
||||
* @param value 保存到数据库里的值
|
||||
* @returns{*} 返回处理后的值
|
||||
*/
|
||||
getValue (value) {
|
||||
return value
|
||||
},
|
||||
/**
|
||||
* 【设置值增强】设置给组件的值
|
||||
* (this指向当前组件)
|
||||
*
|
||||
* @param value 组件触发的值
|
||||
* @returns{*} 返回处理后的值
|
||||
*/
|
||||
setValue (value) {
|
||||
return value
|
||||
},
|
||||
/**
|
||||
* 【新增行增强】在用户点击新增时触发的事件,返回新行的默认值
|
||||
*
|
||||
* @param row 行数据
|
||||
* @param column 列配置,.own 是用户配置的参数
|
||||
* @param $table vxe 实例
|
||||
* @param renderOptions 渲染选项
|
||||
* @param params 可以在这里获取 $table
|
||||
*
|
||||
* @returns 返回新值
|
||||
*/
|
||||
createValue ({ row, column, $table, renderOptions, params }) {
|
||||
return (column.params || {}).defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getListeners () {
|
||||
const listeners = Object.assign({}, (this.renderOptions.listeners || {}))
|
||||
if (!listeners.change) {
|
||||
listeners.change = async (event) => {
|
||||
vModel.call(this, event.value)
|
||||
await this.$nextTick()
|
||||
// 处理 change 事件相关逻辑(例如校验)
|
||||
this.params.$table.updateStatus(this.params)
|
||||
}
|
||||
}
|
||||
return listeners
|
||||
}
|
||||
|
||||
export function vModel (value, row, property) {
|
||||
if (!row) {
|
||||
row = this.row
|
||||
}
|
||||
if (!property) {
|
||||
property = this.column.property
|
||||
}
|
||||
this.$set(row, property, value)
|
||||
}
|
||||
|
||||
/** 模拟触发事件 */
|
||||
export function dispatchEvent ({ cell, $event }, className, handler) {
|
||||
// alwaysEdit 下不模拟触发事件,否者会导致触发两次
|
||||
if (this && this.alwaysEdit) {
|
||||
return
|
||||
}
|
||||
window.setTimeout(() => {
|
||||
const element = cell ? cell.getElementsByClassName(className) : null
|
||||
if (element && element.length > 0) {
|
||||
if (typeof handler === 'function') {
|
||||
handler(element[0])
|
||||
} else {
|
||||
// 模拟触发点击事件
|
||||
if ($event) {
|
||||
element[0].dispatchEvent($event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 10)
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
import store from '@/store/'
|
||||
import { randomUUID } from '@/utils/util'
|
||||
// vxe socket
|
||||
const vs = {
|
||||
// 页面唯一 id,用于标识同一用户,不同页面的websocket
|
||||
pageId: randomUUID(),
|
||||
// webSocket 对象
|
||||
ws: null,
|
||||
// 一些常量
|
||||
constants: {
|
||||
// 消息类型
|
||||
TYPE: 'type',
|
||||
// 消息数据
|
||||
DATA: 'data',
|
||||
// 消息类型:心跳检测
|
||||
TYPE_HB: 'heart_beat',
|
||||
// 消息类型:通用数据传递
|
||||
TYPE_CSD: 'common_send_date',
|
||||
// 消息类型:更新vxe table数据
|
||||
TYPE_UVT: 'update_vxe_table'
|
||||
},
|
||||
// 心跳检测
|
||||
heartCheck: {
|
||||
// 间隔时间,间隔多久发送一次心跳消息
|
||||
interval: 10000,
|
||||
// 心跳消息超时时间,心跳消息多久没有回复后重连
|
||||
timeout: 6000,
|
||||
timeoutTimer: null,
|
||||
clear () {
|
||||
clearTimeout(this.timeoutTimer)
|
||||
return this
|
||||
},
|
||||
start () {
|
||||
vs.sendMessage(vs.constants.TYPE_HB, '')
|
||||
// 如果超过一定时间还没重置,说明后端主动断开了
|
||||
this.timeoutTimer = window.setTimeout(() => {
|
||||
vs.reconnect()
|
||||
}, this.timeout)
|
||||
return this
|
||||
},
|
||||
// 心跳消息返回
|
||||
back () {
|
||||
this.clear()
|
||||
window.setTimeout(() => this.start(), this.interval)
|
||||
}
|
||||
},
|
||||
|
||||
/** 初始化 WebSocket */
|
||||
initialWebSocket () {
|
||||
if (this.ws === null) {
|
||||
const userId = store.getters.userInfo.id
|
||||
const domain = window._CONFIG.domianWebSocketURL.replace('https://', 'wss://').replace('http://', 'ws://')
|
||||
const url = `${domain}/vxeSocket/${userId}/${this.pageId}`
|
||||
|
||||
this.ws = new WebSocket(url)
|
||||
this.ws.onopen = this.on.open.bind(this)
|
||||
this.ws.onerror = this.on.error.bind(this)
|
||||
this.ws.onmessage = this.on.message.bind(this)
|
||||
this.ws.onclose = this.on.close.bind(this)
|
||||
|
||||
console.log('this.ws: ', this.ws)
|
||||
}
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
sendMessage (type, message) {
|
||||
try {
|
||||
const ws = this.ws
|
||||
if (ws != null && ws.readyState === ws.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: type,
|
||||
data: message
|
||||
}))
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('【VXEWebSocket】发送消息失败:(' + err.code + ')')
|
||||
}
|
||||
},
|
||||
|
||||
/** 绑定全局VXE表格 */
|
||||
tableMap: new Map(),
|
||||
CSDMap: new Map(),
|
||||
/** 添加绑定 */
|
||||
addBind (map, key, value) {
|
||||
const binds = map.get(key)
|
||||
if (Array.isArray(binds)) {
|
||||
binds.push(value)
|
||||
} else {
|
||||
map.set(key, [value])
|
||||
}
|
||||
},
|
||||
/** 移除绑定 */
|
||||
removeBind (map, key, value) {
|
||||
const binds = map.get(key)
|
||||
if (Array.isArray(binds)) {
|
||||
for (let i = 0; i < binds.length; i++) {
|
||||
const bind = binds[i]
|
||||
if (bind === value) {
|
||||
binds.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (binds.length === 0) {
|
||||
map.delete(key)
|
||||
}
|
||||
} else {
|
||||
map.delete(key)
|
||||
}
|
||||
},
|
||||
// 呼叫绑定的表单
|
||||
callBind (map, key, callback) {
|
||||
const binds = map.get(key)
|
||||
if (Array.isArray(binds)) {
|
||||
binds.forEach(callback)
|
||||
}
|
||||
},
|
||||
|
||||
lockReconnect: false,
|
||||
/** 尝试重连 */
|
||||
reconnect () {
|
||||
if (this.lockReconnect) return
|
||||
this.lockReconnect = true
|
||||
setTimeout(() => {
|
||||
if (this.ws && this.ws.close) {
|
||||
this.ws.close()
|
||||
}
|
||||
this.ws = null
|
||||
console.info('【VXEWebSocket】尝试重连...')
|
||||
this.initialWebSocket()
|
||||
this.lockReconnect = false
|
||||
}, 5000)
|
||||
},
|
||||
|
||||
on: {
|
||||
open () {
|
||||
console.log('【VXEWebSocket】连接成功')
|
||||
this.heartCheck.start()
|
||||
},
|
||||
error (e) {
|
||||
console.warn('【VXEWebSocket】连接发生错误:', e)
|
||||
this.reconnect()
|
||||
},
|
||||
message (e) {
|
||||
// 解析消息
|
||||
let json
|
||||
try {
|
||||
json = JSON.parse(e.data)
|
||||
} catch (e) {
|
||||
console.warn('【VXEWebSocket】收到无法解析的消息:', e.data)
|
||||
return
|
||||
}
|
||||
const type = json[this.constants.TYPE]
|
||||
const data = json[this.constants.DATA]
|
||||
switch (type) {
|
||||
// 心跳检测
|
||||
case this.constants.TYPE_HB:
|
||||
this.heartCheck.back()
|
||||
break
|
||||
// 通用数据传递
|
||||
case this.constants.TYPE_CSD:
|
||||
this.callBind(this.CSDMap, data.key, (fn) => fn.apply(this, data.args))
|
||||
break
|
||||
// 更新form数据
|
||||
case this.constants.TYPE_UVT:
|
||||
this.callBind(this.tableMap, data.socketKey, (vm) => this.onVM.onUpdateTable.apply(vm, data.args))
|
||||
break
|
||||
default:
|
||||
console.warn('【VXEWebSocket】收到不识别的消息类型:' + type)
|
||||
break
|
||||
}
|
||||
},
|
||||
close (e) {
|
||||
console.log('【VXEWebSocket】连接被关闭:', e)
|
||||
this.reconnect()
|
||||
}
|
||||
},
|
||||
|
||||
onVM: {
|
||||
/** 收到更新表格的消息 */
|
||||
onUpdateTable (row, caseId) {
|
||||
// 判断是不是自己发的消息
|
||||
if (this.caseId !== caseId) {
|
||||
const tableRow = this.getIfRowById(row.id).row
|
||||
// 局部保更新数据
|
||||
if (tableRow) {
|
||||
// 特殊处理拖轮状态
|
||||
if (row.tug_status && tableRow.tug_status) {
|
||||
row.tug_status = Object.assign({}, tableRow.tug_status, row.tug_status)
|
||||
}
|
||||
// 判断是否启用重载特效
|
||||
if (this.reloadEffect) {
|
||||
this.$set(this.reloadEffectRowKeysMap, row.id, true)
|
||||
}
|
||||
Object.keys(row).forEach(key => {
|
||||
if (key !== 'id') {
|
||||
this.$set(tableRow, key, row[key])
|
||||
}
|
||||
})
|
||||
this.$refs.vxe.reloadRow(tableRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 是否开启使用 webSocket 无痕刷新
|
||||
socketReload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
socketKey: {
|
||||
type: String,
|
||||
default: 'vxe-default'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
mounted () {
|
||||
if (this.socketReload) {
|
||||
vs.initialWebSocket()
|
||||
vs.addBind(vs.tableMap, this.socketKey, this)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 发送socket消息更新行 */
|
||||
socketSendUpdateRow (row) {
|
||||
vs.sendMessage(vs.constants.TYPE_UVT, {
|
||||
socketKey: this.socketKey,
|
||||
args: [row, this.caseId]
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
beforeDestroy () {
|
||||
vs.removeBind(vs.tableMap, this.socketKey, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加WebSocket通用数据传递绑定,相同的key可以添加多个方法绑定
|
||||
* @param key 唯一key
|
||||
* @param fn 当消息来的时候触发的回调方法
|
||||
*/
|
||||
export function addBindSocketCSD (key, fn) {
|
||||
if (typeof fn === 'function') {
|
||||
vs.addBind(vs.CSDMap, key, fn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除WebSocket通用数据传递绑定
|
||||
* @param key 唯一key
|
||||
* @param fn 要移除的方法,必须和添加时的方法内存层面上保持一致才可以正确移除
|
||||
*/
|
||||
export function removeBindSocketCSD (key, fn) {
|
||||
if (typeof fn === 'function') {
|
||||
vs.removeBind(vs.CSDMap, key, fn)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { AllCells, JVXETypes } from '@/components/jero/JVxeTable'
|
||||
import JVxeCellMixins from '../mixins/JVxeCellMixins'
|
||||
|
||||
export const JVXERenderType = {
|
||||
editer: 'editer',
|
||||
spaner: 'spaner',
|
||||
default: 'default'
|
||||
}
|
||||
|
||||
/** 安装所有vxe组件 */
|
||||
export function installAllCell (VXETable) {
|
||||
// 遍历所有组件批量注册
|
||||
Object.keys(AllCells).forEach(type => installOneCell(VXETable, type))
|
||||
}
|
||||
|
||||
/** 安装单个vxe组件 */
|
||||
export function installOneCell (VXETable, type) {
|
||||
const switches = getEnhancedMixins(type, 'switches')
|
||||
if (switches.editRender === false) {
|
||||
installCellRender(VXETable, type, AllCells[type])
|
||||
} else {
|
||||
installEditRender(VXETable, type, AllCells[type])
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册可编辑组件 */
|
||||
export function installEditRender (VXETable, type, comp, spanComp) {
|
||||
// 获取当前组件的增强
|
||||
const enhanced = getEnhancedMixins(type)
|
||||
// span 组件
|
||||
if (!spanComp && AllCells[type + ':span']) {
|
||||
spanComp = AllCells[type + ':span']
|
||||
} else {
|
||||
spanComp = AllCells[JVXETypes.normal]
|
||||
}
|
||||
// 如果是常显的cell,显示模板和编辑模板都用编辑模板
|
||||
const cellRender = enhanced.switches.visible ? createRender(comp, enhanced, JVXERenderType.editer) : createRender(spanComp, enhanced, JVXERenderType.spaner)
|
||||
// 添加渲染
|
||||
VXETable.renderer.add(JVXETypes._prefix + type, {
|
||||
// 可编辑模板
|
||||
renderEdit: createRender(comp, enhanced, JVXERenderType.editer),
|
||||
// 显示模板
|
||||
renderCell: cellRender,
|
||||
// 增强注册
|
||||
...enhanced.installOptions
|
||||
})
|
||||
}
|
||||
|
||||
/** 注册普通组件 */
|
||||
export function installCellRender (VXETable, type, comp = AllCells[JVXETypes.normal]) {
|
||||
// 获取当前组件的增强
|
||||
const enhanced = getEnhancedMixins(type)
|
||||
VXETable.renderer.add(JVXETypes._prefix + type, {
|
||||
// 默认显示模板
|
||||
renderDefault: createRender(comp, enhanced, JVXERenderType.default),
|
||||
// 增强注册
|
||||
...enhanced.installOptions
|
||||
})
|
||||
}
|
||||
|
||||
export function createRender (comp, enhanced, renderType) {
|
||||
return function (h, renderOptions, params) {
|
||||
return [h(comp, {
|
||||
props: {
|
||||
value: params.row[params.column.property],
|
||||
row: params.row,
|
||||
column: params.column,
|
||||
params: params,
|
||||
renderOptions: renderOptions,
|
||||
renderType: renderType
|
||||
}
|
||||
})]
|
||||
}
|
||||
}
|
||||
|
||||
// 已混入的组件增强
|
||||
const AllCellsMixins = new Map()
|
||||
|
||||
/** 获取某个组件的增强 */
|
||||
export function getEnhanced (type) {
|
||||
const cell = AllCells[type]
|
||||
if (cell && cell.enhanced) {
|
||||
return cell.enhanced
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某个组件的增强(混入默认值)
|
||||
*
|
||||
* @param type JVXETypes
|
||||
* @param name 可空,增强名称,留空返回所有增强
|
||||
*/
|
||||
export function getEnhancedMixins (type, name) {
|
||||
const getByName = (e) => name ? e[name] : e
|
||||
if (AllCellsMixins.has(type)) {
|
||||
return getByName(AllCellsMixins.get(type))
|
||||
}
|
||||
const defEnhanced = JVxeCellMixins.enhanced
|
||||
const enhanced = getEnhanced(type)
|
||||
if (enhanced) {
|
||||
Object.keys(defEnhanced).forEach(key => {
|
||||
const def = defEnhanced[key]
|
||||
if (Object.prototype.hasOwnProperty.call(enhanced, key)) {
|
||||
// 方法如果存在就不覆盖
|
||||
if (typeof def !== 'function' && typeof def !== 'string') {
|
||||
enhanced[key] = Object.assign({}, def, enhanced[key])
|
||||
}
|
||||
} else {
|
||||
enhanced[key] = def
|
||||
}
|
||||
})
|
||||
AllCellsMixins.set(type, enhanced)
|
||||
return getByName(enhanced)
|
||||
}
|
||||
AllCellsMixins.set(type, defEnhanced)
|
||||
return getByName(defEnhanced)
|
||||
}
|
||||
|
||||
/** 辅助方法:替换${...}变量 */
|
||||
export function replaceProps (col, value) {
|
||||
if (value && typeof value === 'string') {
|
||||
let text = value
|
||||
text = text.replace(/\${title}/g, col.title)
|
||||
text = text.replace(/\${key}/g, col.key)
|
||||
text = text.replace(/\${defaultValue}/g, col.defaultValue)
|
||||
return text
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
import { getVmParentByName } from '@/utils/util'
|
||||
import { JVXETypes } from '@comp/jero/JVxeTable'
|
||||
|
||||
export const VALIDATE_FAILED = Symbol('')
|
||||
|
||||
/**
|
||||
* 获取指定的 $refs 对象
|
||||
* 有时候可能会遇到组件未挂载到页面中的情况,导致无法获取 $refs 中的某个对象
|
||||
* 这个方法可以等待挂载完成之后再返回 $refs 的对象,避免报错
|
||||
* @author sunjianlei
|
||||
**/
|
||||
export function getRefPromise (vm, name) {
|
||||
return new Promise((resolve) => {
|
||||
(function next () {
|
||||
const ref = vm.$refs[name]
|
||||
if (ref) {
|
||||
resolve(ref)
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
next()
|
||||
}, 10)
|
||||
}
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取某一数字输入框列中的最大的值 */
|
||||
export function getInputNumberMaxValue (col, rowsValues) {
|
||||
let maxNum = 0
|
||||
Object.values(rowsValues).forEach((rowValue, index) => {
|
||||
const val = rowValue[col.key]; let num
|
||||
try {
|
||||
num = Number.parseFloat(val)
|
||||
} catch {
|
||||
num = 0
|
||||
}
|
||||
// 把首次循环的结果当成最大值
|
||||
if (index === 0) {
|
||||
maxNum = num
|
||||
} else {
|
||||
maxNum = (num > maxNum) ? num : maxNum
|
||||
}
|
||||
})
|
||||
return maxNum
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 根据 tagName 获取父级节点
|
||||
*
|
||||
* @param dom 一级dom节点
|
||||
* @param tagName 标签名,不区分大小写
|
||||
* @return {HTMLElement}
|
||||
*/
|
||||
export function getParentNodeByTagName (dom, tagName = 'body') {
|
||||
if (tagName === 'body') {
|
||||
return document.body
|
||||
}
|
||||
if (dom.parentNode) {
|
||||
if (dom.parentNode.tagName.toLowerCase() === tagName.trim().toLowerCase()) {
|
||||
return dom.parentNode
|
||||
} else {
|
||||
return getParentNodeByTagName(dom.parentNode, tagName)
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* vxe columns 封装成高级查询可识别的选项
|
||||
* @param columns
|
||||
* @param handler 单独处理方法
|
||||
*/
|
||||
export function vxePackageToSuperQuery (columns, handler) {
|
||||
if (Array.isArray(columns)) {
|
||||
// 高级查询所需要的参数
|
||||
const fieldList = []
|
||||
// 遍历列
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const col = columns[i]
|
||||
if (col.type === JVXETypes.rowCheckbox ||
|
||||
col.type === JVXETypes.rowRadio ||
|
||||
col.type === JVXETypes.rowExpand ||
|
||||
col.type === JVXETypes.rowNumber
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const field = {
|
||||
type: 'string',
|
||||
value: col.key,
|
||||
text: col.title,
|
||||
dictCode: col.dictCode || col.dict
|
||||
}
|
||||
if (col.type === JVXETypes.date || col.type === JVXETypes.datetime) {
|
||||
field.type = col.type
|
||||
field.format = col.format
|
||||
}
|
||||
if (col.type === JVXETypes.inputNumber) {
|
||||
field.type = 'int'
|
||||
}
|
||||
if (Array.isArray(col.options)) {
|
||||
field.options = col.options
|
||||
}
|
||||
if (typeof handler === 'function') {
|
||||
Object.assign(field, handler(col))
|
||||
}
|
||||
fieldList.push(field)
|
||||
}
|
||||
return fieldList
|
||||
} else {
|
||||
console.error('columns必须是一个数组')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性验证主表单和所有的次表单
|
||||
* @param form 主表单 form 对象
|
||||
* @param cases 接收一个数组,每项都是一个JVxeTable实例
|
||||
* @param autoJumpTab
|
||||
* @returns {Promise<any>}
|
||||
* @author sunjianlei
|
||||
*/
|
||||
export async function validateFormAndTables (form, cases, autoJumpTab) {
|
||||
if (!(form && typeof form.validateFields === 'function')) {
|
||||
throw new Error(`form 参数需要的是一个form对象,而传入的却是${typeof form}`)
|
||||
}
|
||||
let dataMap = {}
|
||||
const values = await new Promise((resolve, reject) => {
|
||||
// 验证主表表单
|
||||
form.validateFields((err, values) => {
|
||||
err ? reject({ error: VALIDATE_FAILED, originError: err }) : resolve(values)
|
||||
})
|
||||
})
|
||||
Object.assign(dataMap, { formValue: values })
|
||||
// 验证所有子表的表单
|
||||
const subData = await validateTables(cases, autoJumpTab)
|
||||
// 合并最终数据
|
||||
dataMap = Object.assign(dataMap, { tablesValue: subData })
|
||||
return dataMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次性验证主表单和所有的次表单
|
||||
* @param form 主表单 form 对象
|
||||
* @param formData
|
||||
* @param cases 接收一个数组,每项都是一个JVxeTable实例
|
||||
* @param autoJumpTab
|
||||
* @returns {Promise<any>}
|
||||
* @author sunjianlei
|
||||
*/
|
||||
export async function validateFormModelAndTables (form, formData, cases, autoJumpTab) {
|
||||
if (!(form && typeof form.validate === 'function')) {
|
||||
throw new Error(`form 参数需要的是一个form对象,而传入的却是${typeof form}`)
|
||||
}
|
||||
let dataMap = {}
|
||||
const values = await new Promise((resolve, reject) => {
|
||||
// 验证主表表单
|
||||
form.validate((valid) => {
|
||||
valid ? resolve(formData) : reject({ error: VALIDATE_FAILED, originError: valid })
|
||||
})
|
||||
})
|
||||
Object.assign(dataMap, { formValue: values })
|
||||
// 验证所有子表的表单
|
||||
const subData = await validateTables(cases, autoJumpTab)
|
||||
// 合并最终数据
|
||||
dataMap = Object.assign(dataMap, { tablesValue: subData })
|
||||
return dataMap
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并获取一个或多个表格的所有值
|
||||
*
|
||||
* @param cases 接收一个数组,每项都是一个JVxeTable实例
|
||||
* @param autoJumpTab 校验失败后,是否自动跳转tab选项
|
||||
*/
|
||||
export function validateTables (cases, autoJumpTab = true) {
|
||||
if (!Array.isArray(cases)) {
|
||||
throw new Error(`'validateTables'函数的'cases'参数需要的是一个数组,而传入的却是${typeof cases}`)
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const tablesData = []
|
||||
let index = 0
|
||||
if (!cases || cases.length === 0) {
|
||||
resolve()
|
||||
}
|
||||
(function next () {
|
||||
const vm = cases[index]
|
||||
vm.validateTable().then(errMap => {
|
||||
// 校验通过
|
||||
if (!errMap) {
|
||||
tablesData[index] = vm.getAll()
|
||||
// 判断校验是否全部完成,完成返回成功,否则继续进行下一步校验
|
||||
if (++index === cases.length) {
|
||||
resolve(tablesData)
|
||||
} else {
|
||||
(
|
||||
next()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 尝试获取tabKey,如果在ATab组件内即可获取
|
||||
let paneKey
|
||||
const tabPane = getVmParentByName(vm, 'ATabPane')
|
||||
if (tabPane) {
|
||||
paneKey = tabPane.$vnode.key
|
||||
// 自动跳转到该表格
|
||||
if (autoJumpTab) {
|
||||
const tabs = getVmParentByName(tabPane, 'Tabs')
|
||||
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey)
|
||||
}
|
||||
}
|
||||
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
|
||||
reject({ error: VALIDATE_FAILED, index, paneKey, errMap })
|
||||
}
|
||||
})
|
||||
})()
|
||||
})
|
||||
}
|
||||
@@ -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.error('验证未通过')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## 示例四(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,279 @@
|
||||
# JTable 支持列自定义及可拖拽列宽的表格
|
||||
|
||||
## JTable参数配置
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|--------------|--------|-----|----------------------------------------------------------------|
|
||||
| tableKey | String | ✔ | 全局`JTable`唯一,持久化存储自定义列配置 |
|
||||
| columns | Array | ✔ | **需要配合`.sync`获取最新的数据**,具体项见下表 |
|
||||
| settingStyle | Object | | 自定义列配置表的样式 |
|
||||
| settingScroll | Object | | 自定义列配置表的滚动配置 |
|
||||
| scroll | Object | | 表格滚动配置,建议使用拖拽属性`resizable`时设置`scroll.x`,**需要配合`.sync`获取最新的数据** |
|
||||
| columnMinWidth | Number | | 所有列共用的最小宽度,在没有`width`和`minWidth`时的冻结列和拖拽列的最小宽度 |
|
||||
|
||||
## columns参数配置
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|-------------------|----------------|----|--------------------------------|
|
||||
| hideSettingColumn | Boolean | | 是否在自定义列配置表中隐藏 |
|
||||
| disabledHide | Boolean | | 是否禁用自定义列配置表中的隐藏复选框 |
|
||||
| disabledFreeze | Boolean | | 是否禁用自定义列配置表中的冻结复选框 |
|
||||
| fixed | String、Boolean | | 默认的冻结列,只能为`true`或`'left'`其他不生效 |
|
||||
| width | Number | | 列宽,只能是数字 |
|
||||
| minWidth | Number | | 最小列宽,只能是数字,在冻结列和拖拽列时生效 |
|
||||
| resizable | Boolean | | 是否启用拖拽,需要表格显示边框`bordered` |
|
||||
|
||||
## 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`。
|
||||
|
||||
## 示例
|
||||
|
||||
```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.error('未上传任何文件')
|
||||
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>
|
||||
Reference in New Issue
Block a user