[修改]修改两个组件的父名称

This commit is contained in:
zer0Black
2021-03-11 09:53:44 +08:00
parent 042d25053f
commit 35eb1449d4
135 changed files with 164 additions and 165 deletions
@@ -0,0 +1,163 @@
<template>
<div class="j-area-linkage">
<div v-if="reloading">
<span> Reloading... </span>
</div>
<area-cascader
v-else-if="_type === enums.type[0]"
:value="innerValue"
:data="pcaa"
:level="1"
:style="{width}"
v-bind="$attrs"
v-on="_listeners"
@change="handleChange"
/>
<area-select
v-else-if="_type === enums.type[1]"
:value="innerValue"
:data="pcaa"
:level="2"
v-bind="$attrs"
v-on="_listeners"
@change="handleChange"
/>
<div v-else>
<span style="color:red;"> Bad type value: {{_type}}</span>
</div>
</div>
</template>
<script>
import { pcaa } from 'area-data'
import Area from '@/components/_util/Area'
export default {
name: 'JAreaLinkage',
props: {
value: {
type: String,
required:false
},
// 组件的类型,可选值:
// select 下拉样式
// cascader 级联样式(默认)
type: {
type: String,
default: 'cascader'
},
width: {
type: String,
default: '100%'
}
},
data() {
return {
pcaa,
innerValue: [],
usedListeners: ['change'],
enums: {
type: ['cascader', 'select']
},
reloading: false,
areaData:''
}
},
computed: {
_listeners() {
let listeners = { ...this.$listeners }
// 去掉已使用的事件,防止冲突
this.usedListeners.forEach(key => {
delete listeners[key]
})
return listeners
},
_type() {
if (this.enums.type.includes(this.type)) {
return this.type
} else {
console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')}`)
return this.enums.type[0]
}
},
},
watch: {
value: {
immediate: true,
handler() {
this.loadDataByValue(this.value)
}
},
},
created() {
this.initAreaData();
},
methods: {
/** 重新加载组件 */
reload() {
this.reloading = true
this.$nextTick(() => this.reloading = false)
},
/** 通过 value 反推 options */
loadDataByValue(value) {
if (!value || value.length === 0) {
this.innerValue = []
} else {
this.initAreaData()
let arr = this.areaData.getRealCode(value)
this.innerValue = arr
}
this.reload()
},
/** 通过地区code获取子级 */
loadDataByCode(value) {
let options = []
let data = pcaa[value]
if (data) {
for (let key in data) {
if (data.hasOwnProperty(key)) {
options.push({ value: key, label: data[key], })
}
}
return options
} else {
return []
}
},
/** 判断是否有子节点 */
hasChildren(options) {
options.forEach(option => {
let data = this.loadDataByCode(option.value)
option.isLeaf = data.length === 0
})
},
handleChange(values) {
let value = values[values.length - 1]
this.$emit('change', value)
},
initAreaData(){
if(!this.areaData){
this.areaData = new Area();
}
},
},
model: { prop: 'value', event: 'change' },
}
</script>
<style lang="less" scoped>
.j-area-linkage {
height:40px;
/deep/ .area-cascader-wrap .area-select {
width: 100%;
}
/deep/ .area-select .area-selected-trigger {
line-height: 1.15;
}
}
</style>
@@ -0,0 +1,243 @@
<template>
<a-tree-select
allowClear
labelInValue
style="width: 100%"
:disabled="disabled"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="placeholder"
:loadData="asyncLoadTreeData"
:value="treeValue"
:treeData="treeData"
:multiple="multiple"
@change="onChange">
</a-tree-select>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
name: 'JCategorySelect',
props: {
value:{
type: String,
required: false
},
placeholder:{
type: String,
default: '请选择',
required: false
},
disabled:{
type:Boolean,
default:false,
required:false
},
condition:{
type:String,
default:'',
required:false
},
// 是否支持多选
multiple: {
type: Boolean,
default: false,
},
loadTriggleChange:{
type: Boolean,
default: false,
required:false
},
pid:{
type:String,
default:'',
required:false
},
pcode:{
type:String,
default:'',
required:false
},
back:{
type:String,
default:'',
required:false
}
},
data () {
return {
treeValue:"",
treeData:[],
url:"/sys/category/loadTreeData",
view:'/sys/category/loadDictItem/',
tableName:"",
text:"",
code:"",
}
},
watch: {
value () {
this.loadItemByCode()
},
pcode(){
this.loadRoot();
}
},
created(){
this.validateProp().then(()=>{
this.loadRoot()
this.loadItemByCode()
})
},
methods: {
/**加载一级节点 */
loadRoot(){
let param = {
pid:this.pid,
pcode:!this.pcode?'0':this.pcode,
condition:this.condition
}
getAction(this.url,param).then(res=>{
if(res.success && res.result){
for(let i of res.result){
i.value = i.key
if(i.leaf==false){
i.isLeaf=false
}else if(i.leaf==true){
i.isLeaf=true
}
}
this.treeData = [...res.result]
}else{
console.log("树一级节点查询结果-else",res)
}
})
},
/** 数据回显*/
loadItemByCode(){
if(!this.value || this.value=="0"){
this.treeValue = []
}else{
getAction(this.view,{ids:this.value}).then(res=>{
if(res.success){
let values = this.value.split(',')
this.treeValue = res.result.map((item, index) => ({
key: values[index],
value: values[index],
label: item
}))
this.onLoadTriggleChange(res.result[0]);
}
})
}
},
onLoadTriggleChange(text){
//只有单选才会触发
if(!this.multiple && this.loadTriggleChange){
this.backValue(this.value,text)
}
},
backValue(value,label){
let obj = {}
if(this.back){
obj[this.back] = label
}
this.$emit('change', value, obj)
},
asyncLoadTreeData (treeNode) {
return new Promise((resolve) => {
if (treeNode.$vnode.children) {
resolve()
return
}
let pid = treeNode.$vnode.key
let param = {
pid:pid,
condition:this.condition
}
getAction(this.url,param).then(res=>{
if(res.success){
for(let i of res.result){
i.value = i.key
if(i.leaf==false){
i.isLeaf=false
}else if(i.leaf==true){
i.isLeaf=true
}
}
this.addChildren(pid,res.result,this.treeData)
this.treeData = [...this.treeData]
}
resolve()
})
})
},
addChildren(pid,children,treeArray){
if(treeArray && treeArray.length>0){
for(let item of treeArray){
if(item.key == pid){
if(!children || children.length==0){
item.isLeaf=true
}else{
item.children = children
}
break
}else{
this.addChildren(pid,children,item.children)
}
}
}
},
onChange(value){
if(!value){
this.$emit('change', '');
this.treeValue = ''
} else if (Array.isArray(value)) {
let labels = []
let values = value.map(item => {
labels.push(item.label)
return item.value
})
this.backValue(values.join(','), labels.join(','))
this.treeValue = value
} else {
this.backValue(value.value,value.label)
this.treeValue = value
}
},
getCurrTreeData(){
return this.treeData
},
validateProp(){
let mycondition = this.condition
return new Promise((resolve,reject)=>{
if(!mycondition){
resolve();
}else{
try {
let test=JSON.parse(mycondition);
if(typeof test == 'object' && test){
resolve()
}else{
this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
reject()
}
} catch(e) {
this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
reject()
}
}
})
}
},
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
model: {
prop: 'value',
event: 'change'
}
}
</script>
@@ -0,0 +1,43 @@
<template>
<a-checkbox-group :options="options" :value="checkboxArray" v-bind="$attrs" @change="onChange" />
</template>
<script>
export default {
name: 'JCheckbox',
props: {
value:{
type: String,
required: false
},
/*label value*/
options:{
type: Array,
required: true
}
},
data(){
return {
checkboxArray:!this.value?[]:this.value.split(",")
}
},
watch:{
value (val) {
if(!val){
this.checkboxArray = []
}else{
this.checkboxArray = this.value.split(",")
}
}
},
methods:{
onChange (checkedValues) {
this.$emit('change', checkedValues.join(","));
},
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
@@ -0,0 +1,479 @@
<template>
<div v-bind="fullScreenParentProps">
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
<div class="code-editor-cust full-screen-child">
<textarea ref="textarea"></textarea>
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
<template v-if="languageChange">
<a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
<a-select-option
v-for="mode in modes"
:key="mode.value"
:value="mode.value">
{{ mode.label }}
</a-select-option>
</a-select>
</template>
</div>
</div>
</template>
<script type="text/ecmascript-6">
// 引入全局实例
import _CodeMirror from 'codemirror'
// 核心样式
import 'codemirror/lib/codemirror.css'
// 引入主题后还需要在 options 中指定主题才会生效 darcula gruvbox-dark hopscotch monokai
import 'codemirror/theme/panda-syntax.css'
//提示css
import "codemirror/addon/hint/show-hint.css";
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/mode/css/css.js'
import 'codemirror/mode/xml/xml.js'
import 'codemirror/mode/clike/clike.js'
import 'codemirror/mode/markdown/markdown.js'
import 'codemirror/mode/python/python.js'
import 'codemirror/mode/r/r.js'
import 'codemirror/mode/shell/shell.js'
import 'codemirror/mode/sql/sql.js'
import 'codemirror/mode/swift/swift.js'
import 'codemirror/mode/vue/vue.js'
import { isIE11, isIE } from '@/utils/browser'
// 尝试获取全局实例
const CodeMirror = window.CodeMirror || _CodeMirror
export default {
name: 'JCodeEditor',
props: {
// 外部传入的内容,用于实现双向绑定
value: {
type: String,
default: ''
},
// 外部传入的语法类型
language: {
type: String,
default: null
},
languageChange:{
type: Boolean,
default:false,
required:false
},
placeholder: {
type: String,
default: null
},
// 显示行号
lineNumbers: {
type: Boolean,
default: true
},
// 是否显示全屏按钮
fullScreen: {
type: Boolean,
default: false
},
// 全屏以后的z-index
zIndex: {
type: [Number, String],
default: 999
},
// 是否自适应高度,可以传String或Boolean
// 传 String 类型只能写"!ie"
// 填写这个字符串,代表其他浏览器自适应高度
// 唯独IE下不自适应高度,因为IE下不支持min、max-height样式
// 如果填写的不是"!ie"就视为true
autoHeight: {
type: [String, Boolean],
default: true
},
// 不自适应高度的情况下生效的固定高度
height: {
type: [String, Number],
default: '240px'
},
},
data () {
return {
// 内部真实的内容
code: '',
iconType: 'fullscreen',
hasCode:false,
// 默认的语法类型
mode: 'javascript',
// 编辑器实例
coder: null,
// 默认配置
options: {
// 缩进格式
tabSize: 2,
// 主题,对应主题库 JS 需要提前引入
theme: 'panda-syntax',
line: true,
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
hintOptions: {
tables: {
users: ['name', 'score', 'birthDate'],
countries: ['name', 'population', 'size']
}
},
},
// 支持切换的语法高亮类型,对应 JS 已经提前引入
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
modes: [{
value: 'css',
label: 'CSS'
}, {
value: 'javascript',
label: 'Javascript'
}, {
value: 'html',
label: 'XML/HTML'
}, {
value: 'x-java',
label: 'Java'
}, {
value: 'x-objectivec',
label: 'Objective-C'
}, {
value: 'x-python',
label: 'Python'
}, {
value: 'x-rsrc',
label: 'R'
}, {
value: 'x-sh',
label: 'Shell'
}, {
value: 'x-sql',
label: 'SQL'
}, {
value: 'x-swift',
label: 'Swift'
}, {
value: 'x-vue',
label: 'Vue'
}, {
value: 'markdown',
label: 'Markdown'
}],
// code 编辑器 是否全屏
fullCoder: false
}
},
watch: {
fullCoder:{
handler(value) {
if(value){
this.iconType="fullscreen-exit"
}else{
this.iconType="fullscreen"
}
}
},
// value: {
// immediate: false,
// handler(value) {
// this._getCoder().then(() => {
// this.coder.setValue(value)
// })
// }
// },
language: {
immediate: true,
handler(language) {
this._getCoder().then(() => {
// 尝试从父容器获取语法类型
if (language) {
// 获取具体的语法类型对象
let modeObj = this._getLanguage(language)
// 判断父容器传入的语法是否被支持
if (modeObj) {
this.mode = modeObj.label
this.coder.setOption('mode', `text/${modeObj.value}`)
}
}
})
}
}
},
computed: {
placeholderShow() {
if (this.placeholder == null) {
return `请在此输入${this.language}代码`
} else {
return this.placeholder
}
},
nullTipStyle(){
if (this.lineNumbers) {
return { left: '36px' }
} else {
return { left: '12px' }
}
},
// coder 配置
coderOptions() {
return {
tabSize: this.options.tabSize,
theme: this.options.theme,
lineNumbers: this.lineNumbers,
line: true,
hintOptions: this.options.hintOptions
}
},
isAutoHeight() {
let {autoHeight} = this
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
autoHeight = !(isIE() || isIE11())
} else {
autoHeight = true
}
return autoHeight
},
fullScreenParentProps() {
let props = {
class: {
'full-screen-parent': true,
'full-screen': this.fullCoder,
'auto-height': this.isAutoHeight
},
style: {}
}
if(isIE() || isIE11()){
props.style['height'] = '240px'
}
if (this.fullCoder) {
props.style['z-index'] = this.zIndex
}
if (!this.isAutoHeight) {
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
}
return props
}
},
mounted () {
// 初始化
this._initialize()
},
methods: {
// 初始化
_initialize () {
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
// 编辑器赋值
if(this.value||this.code){
this.hasCode=true
//this.coder.setValue(this.value || this.code)
this.setCodeContent(this.value || this.code)
}else{
this.coder.setValue('')
this.hasCode=false
}
// 支持双向绑定
this.coder.on('change', (coder) => {
this.code = coder.getValue()
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
if (this.$emit) {
this.$emit('input', this.code)
}
})
this.coder.on('focus', () => {
this.hasCode=true
})
this.coder.on('blur', () => {
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
})
/* this.coder.on('cursorActivity',()=>{
this.coder.showHint()
})*/
},
getCodeContent(){
return this.code
},
setCodeContent(val){
setTimeout(()=>{
if(!val){
this.coder.setValue('')
}else{
this.coder.setValue(val)
}
},300)
},
// 获取当前语法类型
_getLanguage (language) {
// 在支持的语法类型列表中寻找传入的语法类型
return this.modes.find((mode) => {
// 所有的值都忽略大小写,方便比较
let currentLanguage = language.toLowerCase()
let currentLabel = mode.label.toLowerCase()
let currentValue = mode.value.toLowerCase()
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
return currentLabel === currentLanguage || currentValue === currentLanguage
})
},
_getCoder() {
let _this = this
return new Promise((resolve) => {
(function get() {
if (_this.coder) {
resolve(_this.coder)
} else {
setTimeout(get, 10)
}
})()
})
},
// 更改模式
changeMode (val) {
// 修改编辑器的语法配置
this.coder.setOption('mode', `text/${val}`)
// 获取修改后的语法
let label = this._getLanguage(val).label.toLowerCase()
// 允许父容器通过以下函数监听当前的语法值
this.$emit('language-change', label)
},
nullTipClick(){
this.coder.focus()
}
}
}
</script>
<style lang="less">
.code-editor-cust{
flex-grow:1;
display:flex;
position:relative;
height:100%;
.CodeMirror{
flex-grow:1;
z-index:1;
.CodeMirror-code{
line-height:19px;
}
}
.code-mode-select{
position:absolute;
z-index:2;
right:10px;
top:10px;
max-width:130px;
}
.CodeMirror{
height: auto;
min-height:100%;
}
.null-tip{
position: absolute;
top: 4px;
left: 36px;
z-index: 10;
color: #ffffffc9;
line-height: initial;
}
.null-tip-hidden{
display: none;
}
}
/* 全屏样式 */
.full-screen-parent {
position: relative;
.full-screen-icon {
opacity: 0;
color: black;
width: 20px;
height: 20px;
line-height: 24px;
background-color: white;
position: absolute;
top: 2px;
right: 2px;
z-index: 9;
cursor: pointer;
transition: opacity 0.3s;
}
&:hover {
.full-screen-icon {
opacity: 1;
&:hover {
background-color: rgba(255, 255, 255, 0.88);
}
}
}
&.full-screen {
position: fixed;
top: 10px;
left: 10px;
width: calc(100% - 20px);
height: calc(100% - 20px);
padding: 10px;
background-color: #f5f5f5;
.full-screen-icon {
top: 12px;
right: 12px;
}
.full-screen-child {
height: 100%;
max-height: 100%;
min-height: 100%;
}
}
.full-screen-child {
height: 100%;
}
&.auto-height {
.full-screen-child {
min-height: 120px;
max-height: 320px;
height: unset;
overflow: hidden;
}
&.full-screen .full-screen-child {
height: 100%;
max-height: 100%;
min-height: 100%;
}
}
}
.CodeMirror-cursor{
height:18.4px !important;
}
</style>
+65
View File
@@ -0,0 +1,65 @@
<template>
<div class="components-input-demo-presuffix">
<a-input @click="openModal" placeholder="corn表达式" v-model="cron" @change="handleOK">
<a-icon slot="prefix" type="schedule" title="corn控件"/>
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
</div>
</template>
<script>
import JCronModal from "./modal/JCronModal";
export default {
name: 'JCron',
components: {
JCronModal
},
props: {
value: {
required: false,
type: String,
}
},
data(){
return {
cron: this.value,
}
},
watch:{
value(val){
this.cron = val
}
},
methods:{
openModal(){
this.$refs.innerVueCron.show();
},
handleOK(val){
this.cron = val;
this.$emit("change", this.cron);
//this.$emit("change", Object.assign({}, this.cron));
},
handleEmpty(){
this.handleOK('')
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>
+86
View File
@@ -0,0 +1,86 @@
<template>
<a-date-picker
dropdownClassName="j-date-picker"
:disabled="disabled || readOnly"
:placeholder="placeholder"
@change="handleDateChange"
:value="momVal"
:showTime="showTime"
:format="dateFormat"
:getCalendarContainer="getCalendarContainer"
/>
</template>
<script>
import moment from 'moment'
export default {
name: 'JDate',
props: {
placeholder:{
type: String,
default: '',
required: false
},
value:{
type: String,
required: false
},
dateFormat:{
type: String,
default: 'YYYY-MM-DD',
required: false
},
//此属性可以被废弃了
triggerChange:{
type: Boolean,
required: false,
default: false
},
readOnly:{
type: Boolean,
required: false,
default: false
},
disabled:{
type: Boolean,
required: false,
default: false
},
showTime:{
type: Boolean,
required: false,
default: false
},
getCalendarContainer: {
type: Function,
default: (node) => node.parentNode
}
},
data () {
let dateStr = this.value;
return {
decorator:"",
momVal:!dateStr?null:moment(dateStr,this.dateFormat)
}
},
watch: {
value (val) {
if(!val){
this.momVal = null
}else{
this.momVal = moment(val,this.dateFormat)
}
}
},
methods: {
moment,
handleDateChange(mom,dateStr){
this.$emit('change', dateStr);
}
},
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
model: {
prop: 'value',
event: 'change'
}
}
</script>
File diff suppressed because it is too large Load Diff
+158
View File
@@ -0,0 +1,158 @@
<template>
<div class="tinymce-editor">
<editor
v-if="!reloading"
v-model="myValue"
:init="init"
:disabled="disabled"
@onClick="onClick">
</editor>
</div>
</template>
<script>
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/media'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/fullscreen'
import 'tinymce/icons/default'
import { uploadAction,getFileAccessHttpUrl } from '@/api/manage'
import { getVmParentByName } from '@/utils/util'
export default {
components: {
Editor
},
props: {
value: {
type: String,
required:false
},
triggerChange:{
type: Boolean,
default: false,
required:false
},
disabled: {
type: Boolean,
default: false
},
plugins: {
type: [String, Array],
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
},
toolbar: {
type: [String, Array],
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
branding:false
}
},
data() {
return {
//初始化配置
init: {
language_url: '/tinymce/langs/zh_CN.js',
language: 'zh_CN',
skin_url: '/tinymce/skins/lightgray',
height: 300,
plugins: this.plugins,
toolbar: this.toolbar,
branding: false,
menubar: false,
toolbar_drawer: false,
images_upload_handler: (blobInfo, success) => {
let formData = new FormData()
formData.append('file', blobInfo.blob(), blobInfo.filename());
formData.append('biz', "jeditor");
formData.append("jeditor","1");
uploadAction(window._CONFIG['domianURL']+"/sys/common/upload", formData).then((res) => {
if (res.success) {
if(res.message == 'local'){
const img = 'data:image/jpeg;base64,' + blobInfo.base64()
success(img)
}else{
let img = getFileAccessHttpUrl(res.message)
success(img)
}
}
})
}
},
myValue: this.value,
reloading: false,
}
},
mounted() {
this.initATabsChangeAutoReload()
},
methods: {
reload() {
this.reloading = true
this.$nextTick(() => this.reloading = false)
},
onClick(e) {
this.$emit('onClick', e, tinymce)
},
//可以添加一些自己的自定义事件,如清空内容
clear() {
this.myValue = ''
},
/**
* 自动判断父级是否是 <a-tabs/> 组件,然后添加事件监听,自动触发reload()
*
* 由于 tabs 组件切换会导致 tinymce 无法输入,
* 只有重新加载才能使用(无论是vue版的还是jQuery版tinymce都有这个通病)
*/
initATabsChangeAutoReload() {
// 获取父级
let tabs = getVmParentByName(this, 'ATabs')
let tabPane = getVmParentByName(this, 'ATabPane')
if (tabs && tabPane) {
// 用户自定义的 key
let currentKey = tabPane.$vnode.key
// 添加事件监听
tabs.$on('change', (key) => {
// 切换到自己时执行reload
if (currentKey === key) {
this.reload()
}
})
}else{
//update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
let tabLayout = getVmParentByName(this, 'TabLayout')
tabLayout.excuteCallback(()=>{
this.reload()
})
//update--begin--autor:wangshuai-----date:20200724------for:文本编辑器切换tab无法修改------
}
},
},
watch: {
value(newValue) {
this.myValue = (newValue == null ? '' : newValue)
},
myValue(newValue) {
if(this.triggerChange){
this.$emit('change', newValue)
}else{
this.$emit('input', newValue)
}
}
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,29 @@
<template>
<a-tooltip placement="topLeft">
<template slot="title">
<span>{{value}}</span>
</template>
{{ value | ellipsis(length) }}
</a-tooltip>
</template>
<script>
export default {
name: 'JEllipsis',
props: {
value: {
type: String,
required: false,
},
length: {
type: Number,
required: false,
default: 25,
}
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,61 @@
<template>
<div :class="disabled?'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>
.jeecg-form-container-disabled{
cursor: not-allowed;
}
.jeecg-form-container-disabled fieldset[disabled] {
-ms-pointer-events: none;
pointer-events: none;
}
.jeecg-form-container-disabled .ant-select{
-ms-pointer-events: none;
pointer-events: none;
}
.jeecg-form-container-disabled .ant-upload-select{display:none}
.jeecg-form-container-disabled .ant-upload-list{cursor:grabbing}
.jeecg-form-container-disabled fieldset[disabled] .ant-upload-list{
-ms-pointer-events: auto !important;
pointer-events: auto !important;
}
.jeecg-form-container-disabled .ant-upload-list-item-actions .anticon-delete,
.jeecg-form-container-disabled .ant-upload-list-item .anticon-close{
display: none;
}
</style>
@@ -0,0 +1,238 @@
<template>
<div class="img">
<a-upload
name="file"
listType="picture-card"
:multiple="isMultiple"
:action="uploadAction"
:headers="headers"
:data="{biz:bizPath}"
:fileList="fileList"
:beforeUpload="beforeUpload"
:disabled="disabled"
:isMultiple="isMultiple"
:showUploadList="isMultiple"
@change="handleChange"
@preview="handlePreview"
:class="!isMultiple?'imgupload':''">
<div :style="{'width':(!isMultiple?'104px':'auto'),'height':(!isMultiple?'104px':'auto')}">
<img v-if="!isMultiple && picUrl" :src="getAvatarView()" style="width:100%;height:100%"/>
<div v-else class="iconp">
<a-icon :type="uploadLoading ? 'loading' : 'plus'" />
<div class="ant-upload-text">{{ text }}</div>
</div>
</div>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel()">
<img alt="example" style="width: 100%" :src="previewImage"/>
</a-modal>
</a-upload>
</div>
</template>
<script>
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import { getFileAccessHttpUrl } from '@/api/manage'
const uidGenerator=()=>{
return '-'+parseInt(Math.random()*10000+1,10);
}
const getFileName=(path)=>{
if(path.lastIndexOf("\\")>=0){
let reg=new RegExp("\\\\","g");
path = path.replace(reg,"/");
}
return path.substring(path.lastIndexOf("/")+1);
}
export default {
name: 'JImageUpload',
data(){
return {
uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
uploadLoading:false,
picUrl:false,
headers:{},
fileList: [],
previewImage:"",
previewVisible: false,
}
},
props:{
text:{
type:String,
required:false,
default:"上传"
},
/*这个属性用于控制文件上传的业务路径*/
bizPath:{
type:String,
required:false,
default:"temp"
},
value:{
type:[String,Array],
required:false
},
disabled:{
type:Boolean,
required:false,
default: false
},
isMultiple:{
type:Boolean,
required:false,
default: false
},
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
number:{
type:Number,
required:false,
default:0
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 新增number属性,用于判断上传数量
},
watch:{
value: {
handler(val,oldValue) {
if (val instanceof Array) {
this.initFileList(val.join(','))
} else {
this.initFileList(val)
}
if(!val || val.length==0){
this.picUrl = false;
}
},
//立刻执行handler
immediate: true
}
},
created(){
const token = Vue.ls.get(ACCESS_TOKEN);
this.headers = {"X-Access-Token":token}
},
methods:{
initFileList(paths){
if(!paths || paths.length==0){
this.fileList = [];
return;
}
this.picUrl = true;
let fileList = [];
let arr = paths.split(",")
for(var a=0;a<arr.length;a++){
let url = getFileAccessHttpUrl(arr[a]);
fileList.push({
uid: uidGenerator(),
name: getFileName(arr[a]),
status: 'done',
url: url,
response:{
status:"history",
message:arr[a]
}
})
}
this.fileList = fileList
},
beforeUpload: function(file){
var fileType = file.type;
if(fileType.indexOf('image')<0){
this.$message.warning('请上传图片');
return false;
}
},
handleChange(info) {
this.picUrl = false;
let fileList = info.fileList
//update-begin-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
if(this.number>0 && this.isMultiple){
fileList = fileList.slice(-this.number);
}
//update-end-author:wangshuai date:20201022 for:LOWCOD-969 判断number是否大于0和是否多选,返回选定的元素。
if(info.file.status==='done'){
if(info.file.response.success){
this.picUrl = true;
fileList = fileList.map((file) => {
if (file.response) {
file.url = file.response.message;
}
return file;
});
}
//this.$message.success(`${info.file.name} 上传成功!`);
}else if (info.file.status === 'error') {
this.$message.error(`${info.file.name} 上传失败.`);
}else if(info.file.status === 'removed'){
this.handleDelete(info.file)
}
this.fileList = fileList
if(info.file.status==='done' || info.file.status === 'removed'){
this.handlePathChange()
}
},
// 预览
handlePreview (file) {
this.previewImage = file.url || file.thumbUrl
this.previewVisible = true
},
getAvatarView(){
if(this.fileList.length>0){
let url = this.fileList[0].url
return getFileAccessHttpUrl(url)
}
},
handlePathChange(){
let uploadFiles = this.fileList
let path = ''
if(!uploadFiles || uploadFiles.length==0){
path = ''
}
let arr = [];
if(!this.isMultiple){
arr.push(uploadFiles[uploadFiles.length-1].response.message)
}else{
for(let a=0;a<uploadFiles.length;a++){
// update-begin-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
if(uploadFiles[a].status === 'done' ) {
arr.push(uploadFiles[a].response.message)
}else{
return;
}
// update-end-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
}
}
if(arr.length>0){
path = arr.join(",")
}
this.$emit('change', path);
},
handleDelete(file){
//如有需要新增 删除逻辑
console.log(file)
},
handleCancel() {
this.close();
this.previewVisible = false;
},
close () {
},
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
/* update--begin--autor:lvdandan-----date:20201016------forj-image-upload图片组件单张图片详情回显空白
* https://github.com/zhangdaiscott/jeecg-boot/issues/1810
* https://github.com/zhangdaiscott/jeecg-boot/issues/1779
*/
/deep/ .imgupload .ant-upload-select{display:block}
/deep/ .imgupload .ant-upload.ant-upload-select-picture-card{ width:120px;height: 120px;}
/deep/ .imgupload .iconp{padding:32px;}
/* update--end--autor:lvdandan-----date:20201016------forj-image-upload图片组件单张图片详情回显空白*/
</style>
@@ -0,0 +1,123 @@
<template>
<a-modal
title="导入EXCEL"
:width="600"
:visible="visible"
:confirmLoading="uploading"
@cancel="handleClose">
<a-upload
name="file"
:multiple="true"
accept=".xls,.xlsx"
:fileList="fileList"
:remove="handleRemove"
:beforeUpload="beforeUpload">
<a-button>
<a-icon type="upload" />
选择导入文件
</a-button>
</a-upload>
<template slot="footer">
<a-button @click="handleClose">关闭</a-button>
<a-button
type="primary"
@click="handleImport"
:disabled="fileList.length === 0"
:loading="uploading">
{{ uploading ? '上传中...' : '开始上传' }}
</a-button>
</template>
</a-modal>
</template>
<script>
import { postAction } from '@/api/manage'
export default {
name: 'JImportModal',
props:{
url:{
type: String,
default: '',
required: false
},
biz:{
type: String,
default: '',
required: false
}
},
data(){
return {
visible:false,
uploading:false,
fileList:[],
uploadAction:'',
foreignKeys:''
}
},
watch: {
url (val) {
if(val){
this.uploadAction = window._CONFIG['domianURL']+val
}
}
},
created () {
this.uploadAction = window._CONFIG['domianURL']+this.url
},
methods:{
handleClose(){
this.visible=false
},
show(arg){
this.fileList = []
this.uploading = false
this.visible = true
this.foreignKeys = arg;
},
handleRemove(file) {
const index = this.fileList.indexOf(file);
const newFileList = this.fileList.slice();
newFileList.splice(index, 1);
this.fileList = newFileList
},
beforeUpload(file) {
this.fileList = [...this.fileList, file]
return false;
},
handleImport() {
const { fileList } = this;
const formData = new FormData();
if(this.biz){
formData.append('isSingleTableImport',this.biz);
}
if(this.foreignKeys && this.foreignKeys.length>0){
formData.append('foreignKeys',this.foreignKeys);
}
fileList.forEach((file) => {
formData.append('files[]', file);
});
this.uploading = true
postAction(this.uploadAction, formData).then((res) => {
this.uploading = false
if(res.success){
this.$message.success(res.message)
this.visible=false
this.$emit('ok')
}else{
this.$message.warning(res.message)
}
})
}
}
}
</script>
<style scoped>
</style>
+111
View File
@@ -0,0 +1,111 @@
<template>
<a-input :placeholder="placeholder" :value="inputVal" @input="backValue"></a-input>
</template>
<script>
const JINPUT_QUERY_LIKE = 'like';
const JINPUT_QUERY_NE = 'ne';
const JINPUT_QUERY_GE = 'ge'; //大于等于
const JINPUT_QUERY_LE = 'le'; //小于等于
export default {
name: 'JInput',
props:{
value:{
type:String,
required:false
},
type:{
type:String,
required:false,
default:JINPUT_QUERY_LIKE
},
placeholder:{
type:String,
required:false,
default:''
},
trim:{
type: Boolean,
required: false,
default:false
}
},
watch:{
value:{
immediate:true,
handler:function(){
this.initVal();
}
},
// update-begin author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
type() {
this.backValue({ target: { value: this.inputVal } })
},
// update-end author:sunjianlei date:20200225 for:当 type 变化的时候重新计算值 ------
},
model: {
prop: 'value',
event: 'change'
},
data(){
return {
inputVal:''
}
},
methods:{
initVal(){
if(!this.value){
this.inputVal = ''
}else{
let text = this.value
switch (this.type) {
case JINPUT_QUERY_LIKE:
//修复路由传参的值传送到jinput框被前后各截取了一位 #1336
if(text.indexOf("*") != -1){
text = text.substring(1,text.length-1);
}
break;
case JINPUT_QUERY_NE:
text = text.substring(1);
break;
case JINPUT_QUERY_GE:
text = text.substring(2);
break;
case JINPUT_QUERY_LE:
text = text.substring(2);
break;
default:
}
this.inputVal = text
}
},
backValue(e){
let text = e.target.value
if(text && this.trim===true){
text = text.trim()
}
switch (this.type) {
case JINPUT_QUERY_LIKE:
text = "*"+text+"*";
break;
case JINPUT_QUERY_NE:
text = "!"+text;
break;
case JINPUT_QUERY_GE:
text = ">="+text;
break;
case JINPUT_QUERY_LE:
text = "<="+text;
break;
default:
}
this.$emit("change",text)
}
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,29 @@
export default {
minHeight: '200px',
previewStyle: 'vertical',
useCommandShortcut: true,
useDefaultHTMLSanitizer: true,
usageStatistics: false,
hideModeSwitch: false,
toolbarItems: [
'heading',
'bold',
'italic',
'strike',
'divider',
'hr',
'quote',
'divider',
'ul',
'ol',
'task',
'indent',
'outdent',
'divider',
'table',
'link',
'divider',
'code',
'codeblock'
]
}
@@ -0,0 +1,255 @@
<template>
<div>
<div class="j-markdown-editor" :id="id"/>
<div v-if="isShow">
<j-modal
title="图片上传"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose"
@ok="handleOk">
<a-tabs default-active-key="1" @change="handleChange">
<a-tab-pane tab="本地图片上传" key="1" :forceRender="true">
<j-upload v-model="fileList" :number="1"></j-upload>
<div style="margin-top: 20px">
<a-input v-model="remark" placeholder="请填写备注"></a-input>
</div>
</a-tab-pane>
<a-tab-pane tab="网络图片地址" key="2" :forceRender="true">
<a-input v-model="networkPic" placeholder="请填写网络图片地址"></a-input>
<a-input style="margin-top: 20px" v-model="remark" placeholder="请填写备注"></a-input>
</a-tab-pane>
</a-tabs>
</j-modal>
</div>
</div>
</template>
<script>
import 'codemirror/lib/codemirror.css'
import '@toast-ui/editor/dist/toastui-editor.css';
import '@toast-ui/editor/dist/i18n/zh-cn';
import Editor from '@toast-ui/editor';
import defaultOptions from './default-options'
import JUpload from '@/components/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(val) {
this.destroyEditor()
this.initEditor()
},
height(newValue) {
this.editor.height(newValue)
},
mode(newValue) {
this.editor.changeMode(newValue)
}
},
mounted() {
this.initEditor()
},
destroyed() {
this.destroyEditor()
},
methods: {
initEditor() {
this.editor = new Editor({
el: document.getElementById(this.id),
...this.editorOptions
})
if (this.value) {
this.editor.setMarkdown(this.value)
}
this.editor.on('change', () => {
this.$emit('change', this.editor.getMarkdown())
})
//--begin 添加自定义上传按钮
/*
* 添加自定义按钮
*/
//获取编辑器上的功能条
let toolbar = this.editor.getUI().getToolbar();
let fileDom = this.$refs.files;
//添加图片点击事件
this.editor.eventManager.addEventType('isShowClickEvent');
this.editor.eventManager.listen('isShowClickEvent', () => {
this.isShow = true
this.dialogVisible = true
});
//addImageBlobHook图片上传、剪切、拖拽都会走此方法
// 删除默认监听事件
this.editor.eventManager.removeEventHandler('addImageBlobHook')
// 添加自定义监听事件
this.editor.eventManager.listen('addImageBlobHook', (blob, callback) => {
this.upload(blob, url => {
callback(url)
})
})
// 添加自定义按钮 第二个参数代表位置,不传默认放在最后
toolbar.insertItem(15,{
type: 'button',
options:{
name: 'customize',
className: 'tui-image tui-toolbar-icons',
event: 'isShowClickEvent',
tooltip: '上传图片',
}
//
});
//--end 添加自定义上传按钮
},
destroyEditor() {
if (!this.editor) return
this.editor.off('change')
this.editor.remove()
},
setMarkdown(value) {
this.editor.setMarkdown(value)
},
getMarkdown() {
return this.editor.getMarkdown()
},
setHtml(value) {
this.editor.setHtml(value)
},
getHtml() {
return this.editor.getHtml()
},
handleOk(){
if(this.index=='1'){
this.imageUrl = getFileAccessHttpUrl(this.fileList)
if(this.remark){
this.addImgToMd(this.imageUrl,this.remark)
}else{
this.addImgToMd(this.imageUrl,"")
}
}else{
if(this.remark){
this.addImgToMd(this.networkPic,this.remark)
}else{
this.addImgToMd(this.networkPic,"")
}
}
this.index="1"
this.fileList=[]
this.imageName="";
this.imageUrl="";
this.remark=""
this.networkPic=""
this.dialogVisible=false
this.isShow=false;
},
handleClose(done) {
done();
},
handleChange(val){
this.fileList=[]
this.remark=""
this.imageName=""
this.imageUrl=""
this.networkPic=""
this.index=val
},
//添加图片到markdown
addImgToMd(data,name) {
let editor = this.editor.getCodeMirror();
let editorHtml = this.editor.getCurrentModeEditor();
let isMarkdownMode = this.editor.isMarkdownMode();
if (isMarkdownMode) {
editor.replaceSelection(`![${name}](${data})`);
} else {
let range = editorHtml.getRange();
let img = document.createElement('img');
img.src = `${data}`;
img.alt = name;
range.insertNode(img);
}
},
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped lang="less">
.j-markdown-editor {
/deep/ .tui-editor-defaultUI {
.te-mode-switch,
.tui-scrollsync
{
line-height: 1.5;
}
}
}
</style>
@@ -0,0 +1,228 @@
<template>
<a-modal
ref="modal"
:class="getClass(modalClass)"
:style="getStyle(modalStyle)"
:visible="visible"
v-bind="_attrs"
v-on="$listeners"
@ok="handleOk"
@cancel="handleCancel"
>
<slot></slot>
<template v-if="!isNoTitle" slot="title">
<a-row class="j-modal-title-row" type="flex">
<a-col class="left">
<slot name="title">{{ title }}</slot>
</a-col>
<a-col v-if="switchFullscreen" class="right" @click="toggleFullscreen">
<a-button class="ant-modal-close ant-modal-close-x" ghost type="link" :icon="fullscreenButtonIcon"/>
</a-col>
</a-row>
</template>
<!-- 处理 scopedSlots -->
<template v-for="slotName of scopedSlotsKeys" :slot="slotName">
<slot :name="slotName"></slot>
</template>
<!-- 处理 slots -->
<template v-for="slotName of slotsKeys" v-slot:[slotName]>
<slot :name="slotName"></slot>
</template>
</a-modal>
</template>
<script>
import { getClass, getStyle } from '@/utils/props-util'
import { triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'JModal',
props: {
title: String,
// 可使用 .sync 修饰符
visible: Boolean,
// 是否全屏弹窗,当全屏时无论如何都会禁止 body 滚动。可使用 .sync 修饰符
fullscreen: {
type: Boolean,
default: false
},
// 是否允许切换全屏(允许后右上角会出现一个按钮)
switchFullscreen: {
type: Boolean,
default: false
},
// 点击确定按钮的时候是否关闭弹窗
okClose: {
type: Boolean,
default: true
},
},
data() {
return {
// 内部使用的 slots ,不再处理
usedSlots: ['title'],
// 实际控制是否全屏的参数
innerFullscreen: this.fullscreen,
}
},
computed: {
// 一些未处理的参数或特殊处理的参数绑定到 a-modal 上
_attrs() {
let attrs = { ...this.$attrs }
// 如果全屏就将宽度设为 100%
if (this.innerFullscreen) {
attrs['width'] = '100%'
}
return attrs
},
modalClass() {
return {
'j-modal-box': true,
'fullscreen': this.innerFullscreen,
'no-title': this.isNoTitle,
'no-footer': this.isNoFooter,
}
},
modalStyle() {
let style = {}
// 如果全屏就将top设为 0
if (this.innerFullscreen) {
style['top'] = '0'
}
return style
},
isNoTitle() {
return !this.title && !this.allSlotsKeys.includes('title')
},
isNoFooter() {
return this._attrs['footer'] === null
},
slotsKeys() {
return Object.keys(this.$slots).filter(key => !this.usedSlots.includes(key))
},
scopedSlotsKeys() {
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
},
allSlotsKeys() {
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
},
// 切换全屏的按钮图标
fullscreenButtonIcon() {
return this.innerFullscreen ? 'fullscreen-exit' : 'fullscreen'
},
},
watch: {
visible() {
if (this.visible) {
this.innerFullscreen = this.fullscreen
}
},
innerFullscreen(val) {
this.$emit('update:fullscreen', val)
},
},
methods: {
getClass(clazz) {
return { ...getClass(this), ...clazz }
},
getStyle(style) {
return { ...getStyle(this), ...style }
},
close() {
this.$emit('update:visible', false)
},
handleOk() {
if (this.okClose) {
this.close()
}
},
handleCancel() {
this.close()
},
/** 切换全屏 */
toggleFullscreen() {
this.innerFullscreen = !this.innerFullscreen
triggerWindowResizeEvent()
},
}
}
</script>
<style lang="less">
.j-modal-box {
&.fullscreen {
top: 0;
left: 0;
padding: 0;
// 兼容1.6.2版本的antdv
& .ant-modal {
top: 0;
padding: 0;
height: 100vh;
}
& .ant-modal-content {
height: 100vh;
border-radius: 0;
& .ant-modal-body {
/* title 和 footer 各占 55px */
height: calc(100% - 55px - 55px);
overflow: auto;
}
}
&.no-title, &.no-footer {
.ant-modal-body {
height: calc(100% - 55px);
}
}
&.no-title.no-footer {
.ant-modal-body {
height: 100%;
}
}
}
.j-modal-title-row {
.left {
width: calc(100% - 56px - 56px);
}
.right {
width: 56px;
position: inherit;
.ant-modal-close {
right: 56px;
color: rgba(0, 0, 0, 0.45);
&:hover {
color: rgba(0, 0, 0, 0.75);
}
}
}
}
}
@media (max-width: 767px) {
.j-modal-box.fullscreen {
margin: 0;
max-width: 100vw;
}
}
</style>
+221
View File
@@ -0,0 +1,221 @@
<template>
<div class="components-input-demo-presuffix" v-if="avalid">
<!---->
<a-input @click="openModal" :placeholder="placeholder" v-model="showText" readOnly :disabled="disabled">
<a-icon slot="prefix" type="cluster" :title="title"/>
<a-icon v-if="showText" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<j-popup-onl-report
ref="jPopupOnlReport"
:code="code"
:multi="multi"
:groupId="uniqGroupId"
:param="param"
@ok="callBack"
/>
</div>
</template>
<script>
import JPopupOnlReport from './modal/JPopupOnlReport'
export default {
name: 'JPopup',
components: {
JPopupOnlReport
},
props: {
code: {
type: String,
default: '',
required: false
},
field: {
type: String,
default: '',
required: false
},
orgFields: {
type: String,
default: '',
required: false
},
destFields: {
type: String,
default: '',
required: false
},
width: {
type: Number,
default: 1200,
required: false
},
placeholder: {
type: String,
default: '请选择',
required: false
},
value: {
type: String,
required: false
},
triggerChange: {
type: Boolean,
required: false,
default: false
},
disabled: {
type: Boolean,
required: false,
default: false
},
multi: {
type: Boolean,
required: false,
default: false
},
//popup动态参数 支持系统变量语法
param:{
type: Object,
required: false,
default: ()=>{}
},
spliter:{
type: String,
required: false,
default: ','
},
/** 分组ID,用于将多个popup的请求合并到一起,不传不分组 */
groupId: String
},
data() {
return {
showText: '',
title: '',
avalid: true
}
},
computed: {
uniqGroupId() {
if (this.groupId) {
let { groupId, code, field, orgFields, destFields } = this
return `${groupId}_${code}_${field}_${orgFields}_${destFields}`
}
}
},
watch: {
value: {
immediate: true,
handler: function(val) {
if (!val) {
this.showText = ''
} else {
this.showText = val.split(this.spliter).join(',')
}
}
}
},
created() {
},
mounted() {
if (!this.orgFields || !this.destFields || !this.code) {
this.$message.error('popup参数未正确配置!')
this.avalid = false
}
if (this.destFields.split(',').length != this.orgFields.split(',').length) {
this.$message.error('popup参数未正确配置,原始值和目标值数量不一致!')
this.avalid = false
}
},
methods: {
openModal() {
if (this.disabled === false) {
this.$refs.jPopupOnlReport.show()
}
},
handleEmpty() {
this.showText = ''
let destFieldsArr = this.destFields.split(',')
if (destFieldsArr.length === 0) {
return
}
let res = {}
for (let i = 0; i < destFieldsArr.length; i++) {
res[destFieldsArr[i]] = ''
}
if (this.triggerChange) {
this.$emit('callback', res)
} else {
this.$emit('input', '', res)
}
},
callBack(rows) {
// update--begin--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
let orgFieldsArr = this.orgFields.split(',')
let destFieldsArr = this.destFields.split(',')
let resetText = false
if (this.field && this.field.length > 0) {
this.showText = ''
resetText = true
}
let res = {}
if (orgFieldsArr.length > 0) {
for (let i = 0; i < orgFieldsArr.length; i++) {
let tempDestArr = []
for(let rw of rows){
let val = rw[orgFieldsArr[i]]
if(!val){
val = ""
}
tempDestArr.push(val)
}
res[destFieldsArr[i]] = tempDestArr.join(",")
}
if (resetText === true) {
let tempText = []
for(let rw of rows){
let val = rw[orgFieldsArr[destFieldsArr.indexOf(this.field)]]
if(!val){
val = ""
}
tempText.push(val)
}
this.showText = tempText.join(",")
}
// update--end--autor:lvdandan-----date:20200630------for:多选时未带回多个值------
}
if (this.triggerChange) {
//v-dec时即triggerChange为true时 将整个对象给form页面 让他自己setFieldsValue
this.$emit('callback', res)
} else {
//v-model时 需要传一个参数field 表示当前这个字段 从而根据这个字段的顺序找到原始值
// this.$emit("input",row[orgFieldsArr[destFieldsArr.indexOf(this.field)]])
let str = ''
if(this.showText){
str = this.showText.split(',').join(this.spliter)
}
this.$emit('input', str, res)
}
}
}
}
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>
@@ -0,0 +1,84 @@
<template>
<a-select :value="arrayValue" @change="onChange" mode="multiple" :placeholder="placeholder">
<a-select-option
v-for="(item,index) in options"
:key="index"
:getPopupContainer="getParentContainer"
:value="item.value">
{{ item.text || item.label }}
</a-select-option>
</a-select>
</template>
<script>
//option {label:,value:}
export default {
name: 'JSelectMultiple',
props: {
placeholder:{
type: String,
default:'',
required: false
},
value:{
type: String,
required: false
},
readOnly:{
type: Boolean,
required: false,
default: false
},
options:{
type: Array,
required: true
},
triggerChange:{
type: Boolean,
required: false,
default: false
},
spliter:{
type: String,
required: false,
default: ','
},
popContainer:{
type:String,
default:'',
required:false
},
},
data(){
return {
arrayValue:!this.value?[]:this.value.split(this.spliter)
}
},
watch:{
value (val) {
if(!val){
this.arrayValue = []
}else{
this.arrayValue = this.value.split(this.spliter)
}
}
},
methods:{
onChange (selectedValue) {
if(this.triggerChange){
this.$emit('change', selectedValue.join(this.spliter));
}else{
this.$emit('input', selectedValue.join(this.spliter));
}
},
getParentContainer(node){
if(!this.popContainer){
return node.parentNode
}else{
return document.querySelector(this.popContainer)
}
}
},
}
</script>
+116
View File
@@ -0,0 +1,116 @@
<template>
<div class="drag" ref="dragDiv">
<div class="drag_bg"></div>
<div class="drag_text">{{confirmWords}}</div>
<div ref="moveDiv" @mousedown="mousedownFn($event)" :class="{'handler_ok_bg':confirmSuccess}" class="handler handler_bg" style="border: 0.5px solid #fff;height: 34px;position: absolute;top: 0px;left: 0px;"></div>
</div>
</template>
<script>
export default {
name:"JSlider",
data(){
return {
beginClientX:0, /*距离屏幕左端距离*/
mouseMoveStata:false, /*触发拖动状态 判断*/
maxwidth:'', /*拖动最大宽度,依据滑块宽度算出来的*/
confirmWords:'拖动滑块验证', /*滑块文字*/
confirmSuccess:false /*验证成功判断*/
}
},
methods: {
isSuccess(){
return this.confirmSuccess
},
mousedownFn:function (e) {
if(!this.confirmSuccess){
e.preventDefault && e.preventDefault(); //阻止文字选中等 浏览器默认事件
this.mouseMoveStata = true;
this.beginClientX = e.clientX;
}
}, //mousedoen 事件
successFunction(){
this.confirmSuccess = true
this.confirmWords = '验证通过';
if(window.addEventListener){
document.getElementsByTagName('html')[0].removeEventListener('mousemove',this.mouseMoveFn);
document.getElementsByTagName('html')[0].removeEventListener('mouseup',this.moseUpFn);
}else {
document.getElementsByTagName('html')[0].removeEventListener('mouseup',()=>{});
}
document.getElementsByClassName('drag_text')[0].style.color = '#fff'
document.getElementsByClassName('handler')[0].style.left = this.maxwidth + 'px';
document.getElementsByClassName('drag_bg')[0].style.width = this.maxwidth + 'px';
this.$emit("onSuccess",true)
}, //验证成功函数
mouseMoveFn(e){
if(this.mouseMoveStata){
let width = e.clientX - this.beginClientX;
if(width>0 && width<=this.maxwidth){
document.getElementsByClassName('handler')[0].style.left = width + 'px';
document.getElementsByClassName('drag_bg')[0].style.width = width + 'px';
}else if(width>this.maxwidth){
this.successFunction();
}
}
}, //mousemove事件
moseUpFn(e){
this.mouseMoveStata = false;
var width = e.clientX - this.beginClientX;
if(width<this.maxwidth){
// ---- update-begin- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
let handler = document.getElementsByClassName('handler')[0]
if (handler) {
handler.style.left = 0 + 'px'
document.getElementsByClassName('drag_bg')[0].style.width = 0 + 'px'
}
// ---- update-end- author:sunjianlei --- date:20191009 --- for: 修复获取不到 handler 的时候报错 ----
}
} //mouseup事件
},
mounted(){
this.maxwidth = this.$refs.dragDiv.clientWidth - this.$refs.moveDiv.clientWidth;
document.getElementsByTagName('html')[0].addEventListener('mousemove',this.mouseMoveFn);
document.getElementsByTagName('html')[0].addEventListener('mouseup',this.moseUpFn)
}
}
</script>
<style scoped>
.drag{
position: relative;
background-color: #e8e8e8;
width: 100%;
height: 34px;
line-height: 34px;
text-align: center;
}
.handler{
width: 40px;
height: 32px;
border: 1px solid #ccc;
cursor: move;
}
.handler_bg{
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTEyNTVEMURGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTEyNTVEMUNGMkVFMTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MTc5NzNmZS02OTQxLTQyOTYtYTIwNi02NDI2YTNkOWU5YmUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YiRG4AAAALFJREFUeNpi/P//PwMlgImBQkA9A+bOnfsIiBOxKcInh+yCaCDuByoswaIOpxwjciACFegBqZ1AvBSIS5OTk/8TkmNEjwWgQiUgtQuIjwAxUF3yX3xyGIEIFLwHpKyAWB+I1xGSwxULIGf9A7mQkBwTlhBXAFLHgPgqEAcTkmNCU6AL9d8WII4HOvk3ITkWJAXWUMlOoGQHmsE45ViQ2KuBuASoYC4Wf+OUYxz6mQkgwAAN9mIrUReCXgAAAABJRU5ErkJggg==") no-repeat center;
}
.handler_ok_bg{
background: #fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NTc3MiwgMjAxNC8wMS8xMy0xOTo0NDowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0ZDhlNWY5My05NmI0LTRlNWQtOGFjYi03ZTY4OGYyMTU2ZTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDlBRDI3NjVGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDlBRDI3NjRGMkQ2MTFFNEI5NDBCMjQ2M0ExMDQ1OUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTQgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDphNWEzMWNhMC1hYmViLTQxNWEtYTEwZS04Y2U5NzRlN2Q4YTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NGQ4ZTVmOTMtOTZiNC00ZTVkLThhY2ItN2U2ODhmMjE1NmU2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+k+sHwwAAASZJREFUeNpi/P//PwMyKD8uZw+kUoDYEYgloMIvgHg/EM/ptHx0EFk9I8wAoEZ+IDUPiIMY8IN1QJwENOgj3ACo5gNAbMBAHLgAxA4gQ5igAnNJ0MwAVTsX7IKyY7L2UNuJAf+AmAmJ78AEDTBiwGYg5gbifCSxFCZoaBMCy4A4GOjnH0D6DpK4IxNSVIHAfSDOAeLraJrjgJp/AwPbHMhejiQnwYRmUzNQ4VQgDQqXK0ia/0I17wJiPmQNTNBEAgMlQIWiQA2vgWw7QppBekGxsAjIiEUSBNnsBDWEAY9mEFgMMgBk00E0iZtA7AHEctDQ58MRuA6wlLgGFMoMpIG1QFeGwAIxGZo8GUhIysmwQGSAZgwHaEZhICIzOaBkJkqyM0CAAQDGx279Jf50AAAAAABJRU5ErkJggg==") no-repeat center;
}
.drag_bg{
background-color: #7ac23c;
height: 34px;
width: 0px;
}
.drag_text{
position: absolute;
top: 0px;
width: 100%;text-align: center;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select:none;
-ms-user-select:none;
}
</style>
@@ -0,0 +1,679 @@
<template>
<div class="j-super-query-box">
<slot name="button" :isActive="superQueryFlag" :isMobile="izMobile" :open="handleOpen" :reset="handleReset">
<a-tooltip v-if="superQueryFlag" v-bind="tooltipProps" :mouseLeaveDelay="0.2">
<!-- begin 不知道为什么不加上这段代码就无法生效 -->
<span v-show="false">{{tooltipProps}}</span>
<!-- end 不知道为什么不加上这段代码就无法生效 -->
<template slot="title">
<span>已有高级查询条件生效</span>
<a-divider type="vertical"/>
<a @click="handleReset">清空</a>
</template>
<a-button-group>
<a-button type="primary" @click="handleOpen">
<a-icon type="appstore" theme="twoTone" spin/>
<span>高级查询</span>
</a-button>
<a-button v-if="izMobile" type="primary" icon="delete" @click="handleReset"/>
</a-button-group>
</a-tooltip>
<a-button v-else type="primary" icon="filter" @click="handleOpen">高级查询</a-button>
</slot>
<j-modal
title="高级查询构造器"
:width="1000"
:visible="visible"
@cancel="handleCancel"
:mask="false"
:fullscreen="izMobile"
class="j-super-query-modal"
style="top:5%;max-height: 95%;"
>
<template slot="footer">
<div style="float: left">
<a-button :loading="loading" @click="handleReset">重置</a-button>
<a-button :loading="loading" @click="handleSave">保存查询条件</a-button>
</div>
<a-button :loading="loading" @click="handleCancel">关闭</a-button>
<a-button :loading="loading" type="primary" @click="handleOk">查询</a-button>
</template>
<a-spin :spinning="loading">
<a-row>
<a-col :sm="24" :md="24-5">
<a-empty v-if="queryParamsModel.length === 0" style="margin-bottom: 12px;">
<div slot="description">
<span>没有任何查询条件</span>
<a-divider type="vertical"/>
<a @click="handleAdd">点击新增</a>
</div>
</a-empty>
<a-form v-else layout="inline">
<a-row style="margin-bottom: 12px;">
<a-col :md="12" :xs="24">
<a-form-item label="过滤条件匹配" :labelCol="{md: 6,xs:24}" :wrapperCol="{md: 18,xs:24}" style="width: 100%;">
<a-select v-model="matchType" :getPopupContainer="node=>node.parentNode" style="width: 100%;">
<a-select-option value="and">AND(所有条件都要求匹配)</a-select-option>
<a-select-option value="or">OR(条件中的任意一个匹配)</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row type="flex" style="margin-bottom:10px" :gutter="16" v-for="(item, index) in queryParamsModel" :key="index">
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<a-tree-select
showSearch
v-model="item.field"
:treeData="fieldTreeData"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
placeholder="选择查询字段"
allowClear
treeDefaultExpandAll
:getPopupContainer="node=>node.parentNode"
style="width: 100%"
@select="(val,option)=>handleSelected(option,item)"
>
</a-tree-select>
</a-col>
<a-col :md="4" :xs="24" style="margin-bottom: 12px;">
<a-select placeholder="匹配规则" :value="item.rule" :getPopupContainer="node=>node.parentNode" @change="handleRuleChange(item,$event)">
<a-select-option value="eq">等于</a-select-option>
<a-select-option value="like">包含</a-select-option>
<a-select-option value="right_like">以..开始</a-select-option>
<a-select-option value="left_like">以..结尾</a-select-option>
<a-select-option value="in">在...中</a-select-option>
<a-select-option value="ne">不等于</a-select-option>
<a-select-option value="gt">大于</a-select-option>
<a-select-option value="ge">大于等于</a-select-option>
<a-select-option value="lt">小于</a-select-option>
<a-select-option value="le">小于等于</a-select-option>
</a-select>
</a-col>
<a-col :md="8" :xs="24" style="margin-bottom: 12px;">
<!-- 下拉搜索 -->
<j-search-select-tag v-if="item.type==='sel_search'" v-model="item.val" :dict="getDictInfo(item)" placeholder="请选择"/>
<!-- 下拉多选 -->
<template v-else-if="item.type==='list_multi'">
<j-multi-select-tag v-if="item.options" v-model="item.val" :options="item.options" placeholder="请选择"/>
<j-multi-select-tag v-else v-model="item.val" :dictCode="getDictInfo(item)" placeholder="请选择"/>
</template>
<template v-else-if="item.dictCode">
<template v-if="item.type === 'table-dict'">
<j-popup
v-model="item.val"
:code="item.dictTable"
:field="item.dictCode"
:orgFields="item.dictCode"
:destFields="item.dictCode"
:multi="true"
></j-popup>
</template>
<template v-else>
<j-multi-select-tag v-show="allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
<j-dict-select-tag v-show="!allowMultiple(item)" v-model="item.val" :dictCode="item.dictCode" placeholder="请选择"/>
</template>
</template>
<j-popup
v-else-if="item.type === 'popup'"
:value="item.val"
v-bind="item.popup"
group-id="superQuery"
@input="(e,v)=>handleChangeJPopup(item,e,v)"
:multi="true"/>
<j-select-multi-user
v-else-if="item.type === 'select-user' || item.type === 'sel_user'"
v-model="item.val"
:buttons="false"
:multiple="false"
placeholder="请选择用户"
:returnKeys="['id', item.customReturnField || 'username']"
/>
<j-select-depart
v-else-if="item.type === 'select-depart' || item.type === 'sel_depart'"
v-model="item.val"
:multi="false"
placeholder="请选择部门"
:customReturnField="item.customReturnField || 'id'"
/>
<a-select
v-else-if="item.options instanceof Array"
v-model="item.val"
:options="item.options"
allowClear
placeholder="请选择"
:mode="allowMultiple(item)?'multiple':''"
/>
<j-area-linkage v-model="item.val" v-else-if="item.type==='area-linkage' || item.type==='pca'" style="width: 100%"/>
<j-date v-else-if=" item.type=='date' " v-model="item.val" placeholder="请选择日期" style="width: 100%"></j-date>
<j-date v-else-if=" item.type=='datetime' " v-model="item.val" placeholder="请选择时间" :show-time="true" date-format="YYYY-MM-DD HH:mm:ss" style="width: 100%"></j-date>
<a-time-picker v-else-if="item.type==='time'" :value="item.val ? moment(item.val,'HH:mm:ss') : null" format="HH:mm:ss" style="width: 100%" @change="(time,value)=>item.val=value"/>
<a-input-number v-else-if=" item.type=='int'||item.type=='number' " style="width: 100%" placeholder="请输入数值" v-model="item.val"/>
<a-select v-else-if="item.type=='switch'" placeholder="请选择" v-model="item.val">
<a-select-option value="Y">是</a-select-option>
<a-select-option value="N">否</a-select-option>
</a-select>
<a-input v-else v-model="item.val" placeholder="请输入值"/>
</a-col>
<a-col :md="4" :xs="0" style="margin-bottom: 12px;">
<a-button @click="handleAdd" icon="plus"></a-button>&nbsp;
<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>&nbsp;
<a-button @click="handleDel( index )" icon="minus"></a-button>
</a-col>
</a-row>
</a-form>
</a-col>
<a-col :sm="24" :md="5">
<!-- 查询记录 -->
<a-card class="j-super-query-history-card" :bordered="true">
<div slot="title">
保存的查询
</div>
<a-empty v-if="saveTreeData.length === 0" class="j-super-query-history-empty" description="没有保存任何查询"/>
<a-tree
v-else
class="j-super-query-history-tree"
showIcon
:treeData="saveTreeData"
:selectedKeys="[]"
@select="handleTreeSelect"
>
</a-tree>
</a-card>
</a-col>
</a-row>
</a-spin>
<a-modal title="请输入保存的名称" :visible="prompt.visible" @cancel="prompt.visible=false" @ok="handlePromptOk">
<a-input v-model="prompt.value"></a-input>
</a-modal>
</j-modal>
</div>
</template>
<script>
import moment from 'moment'
import * as utils from '@/utils/util'
import { mixinDevice } from '@/utils/mixin'
import JDate from '@/components/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() {
let list = this.$ls.get(this.fullSaveCode)
if (list instanceof Array) {
this.saveTreeData = list.map(i => this.renderSaveTreeData(i))
}
}
},
fieldList: {
deep: true,
immediate: true,
handler(val) {
let mainData = [], subData = []
val.forEach(item => {
let data = { ...item }
data.label = data.label || data.text
let hasChildren = (data.children instanceof Array)
data.disabled = hasChildren
data.selectable = !hasChildren
if (hasChildren) {
data.children = data.children.map(item2 => {
let child = { ...item2 }
child.label = child.label || child.text
child.label = data.label + '-' + child.label
child.value = data.value + ',' + child.value
child.val = ''
return child
})
data.val = ''
subData.push(data)
} else {
mainData.push(data)
}
})
this.fieldTreeData = mainData.concat(subData)
}
}
},
methods: {
show() {
if (!this.queryParamsModel || this.queryParamsModel.length === 0) {
this.resetLine()
}
this.visible = true
},
getDictInfo(item) {
let str = ''
if(!item.dictTable){
str = item.dictCode
}else{
str = item.dictTable+','+item.dictText+','+item.dictCode
}
console.log('高级查询字典信息',str)
return str
},
handleOk() {
if (!this.isNullArray(this.queryParamsModel)) {
let event = {
matchType: this.matchType,
params: this.removeEmptyObject(this.queryParamsModel)
}
// 移动端模式下关闭弹窗
if (this.izMobile) {
this.visible = false
}
this.emitCallback(event)
} else {
this.$message.warn("不能查询空条件")
}
},
emitCallback(event = {}) {
let { params = [], matchType = this.matchType } = event
this.superQueryFlag = (params && params.length > 0)
for (let param of params) {
if (Array.isArray(param.val)) {
param.val = param.val.join(',')
}
}
console.debug('---高级查询参数--->', { params, matchType })
this.$emit(this.callback, params, matchType)
},
handleCancel() {
this.close()
},
close() {
this.$emit('close')
this.visible = false
},
handleAdd() {
this.addNewLine()
},
addNewLine() {
this.queryParamsModel.push({ rule: 'eq' })
},
resetLine() {
this.superQueryFlag = false
this.queryParamsModel = []
this.addNewLine()
},
handleDel(index) {
this.queryParamsModel.splice(index, 1)
},
handleSelected(node, item) {
let { type, options, dictCode, dictTable, dictText, customReturnField, popup } = node.dataRef
item['type'] = type
item['options'] = options
item['dictCode'] = dictCode
item['dictTable'] = dictTable
item['dictText'] = dictText
item['customReturnField'] = customReturnField
if (popup) {
item['popup'] = popup
}
this.$set(item, 'val', undefined)
},
handleOpen() {
this.show()
},
handleReset() {
this.resetLine()
this.emitCallback()
},
handleSave() {
let queryParams = this.removeEmptyObject(this.queryParamsModel)
if (this.isNullArray(queryParams)) {
this.$message.warning('空条件不能保存')
} else {
this.prompt.value = ''
this.prompt.visible = true
}
},
handlePromptOk() {
let { value } = this.prompt
if(!value){
this.$message.warning('保存名称不能为空')
return
}
// 取出查询条件
let records = this.removeEmptyObject(this.queryParamsModel)
// 判断有没有重名的
let filterList = this.saveTreeData.filter(i => i.originTitle === value)
if (filterList.length > 0) {
this.$confirm({
content: `${value} 已存在,是否覆盖?`,
onOk: () => {
this.prompt.visible = false
filterList[0].records = records
this.saveToLocalStore()
this.$message.success('保存成功')
}
})
} else {
// 没有重名的,直接添加
this.prompt.visible = false
// 添加到树列表中
this.saveTreeData.push(this.renderSaveTreeData({
title: value,
matchType: this.matchType,
records: records
}))
// 保存到 LocalStore
this.saveToLocalStore()
this.$message.success('保存成功')
}
},
handleTreeSelect(idx, event) {
if (event.selectedNodes[0]) {
let { matchType, records } = event.selectedNodes[0].data.props
// 将保存的matchType取出,兼容旧数据,如果没有保存就还是使用原来的
this.matchType = matchType || this.matchType
this.queryParamsModel = utils.cloneObject(records)
}
},
handleRemoveSaveTreeItem(event, vNode) {
// 阻止事件冒泡
event.stopPropagation()
this.$confirm({
content: '是否删除当前查询?',
onOk: () => {
let { eventKey } = vNode
this.saveTreeData.splice(Number.parseInt(eventKey.substring(2)), 1)
this.saveToLocalStore()
},
})
},
// 将查询保存到 LocalStore 里
saveToLocalStore() {
let saveValue = this.saveTreeData.map(({ originTitle, matchType, records }) => ({ title: originTitle, matchType, records }))
this.$ls.set(this.fullSaveCode, saveValue)
},
isNullArray(array) {
//判断是不是空数组对象
if (!array || array.length === 0) {
return true
}
if (array.length === 1) {
let obj = array[0]
if (!obj.field || (obj.val == null || obj.val === '') || !obj.rule) {
return true
}
}
return false
},
// 去掉数组中的空对象
removeEmptyObject(arr) {
let array = utils.cloneObject(arr)
for (let i = 0; i < array.length; i++) {
let item = array[i]
if (item == null || Object.keys(item).length <= 0) {
array.splice(i--, 1)
} else {
if (Array.isArray(item.options)) {
// 如果有字典属性,就不需要保存 options 了
//update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
delete item.options
//update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
}
}
}
return array
},
/** 渲染保存查询条件的 title(加个删除按钮) */
renderSaveTreeData(item) {
item.icon = this.treeIcon
item.originTitle = item['title']
item.title = (arg1, arg2) => {
let vNode
// 兼容旧版的Antdv
if (arg1.dataRef) {
vNode = arg1
} else if (arg2.dataRef) {
vNode = arg2
} else {
return <span style="color:red;">Antdv版本不支持</span>
}
let {originTitle} = vNode.dataRef
return (
<div class="j-history-tree-title">
<span>{originTitle}</span>
<div class="j-history-tree-title-closer" onClick={e => this.handleRemoveSaveTreeItem(e, vNode)}>
<a-icon type="close-circle"/>
</div>
</div>
)
}
return item
},
/** 判断是否允许多选 */
allowMultiple(item) {
return item.rule === 'in'
},
handleRuleChange(item, newValue) {
let oldValue = item.rule
this.$set(item, 'rule', newValue)
// 上一个规则是否是 in,且type是字典或下拉
if (oldValue === 'in') {
if (item.dictCode || item.options instanceof Array) {
let value = item.val
if (typeof item.val === 'string') {
value = item.val.split(',')[0]
} else if (Array.isArray(item.val)) {
value = item.val[0]
}
this.$set(item, 'val', value)
}
}
},
handleChangeJPopup(item, e, values) {
item.val = values[item.popup['destFields']]
},
}
}
</script>
<style lang="less" scoped>
.j-super-query-box {
display: inline-block;
}
.j-super-query-modal {
.j-super-query-history-card {
/deep/ .ant-card-body,
/deep/ .ant-card-head-title {
padding: 0;
}
/deep/ .ant-card-head {
padding: 4px 8px;
min-height: initial;
}
}
.j-super-query-history-empty {
/deep/ .ant-empty-image {
height: 80px;
line-height: 80px;
margin-bottom: 0;
}
/deep/ img {
width: 80px;
height: 65px;
}
/deep/ .ant-empty-description {
color: #afafaf;
margin: 8px 0;
}
}
.j-super-query-history-tree {
.j-history-tree-title {
width: calc(100% - 24px);
position: relative;
display: inline-block;
&-closer {
color: #999999;
position: absolute;
top: 0;
right: 0;
width: 24px;
height: 24px;
text-align: center;
opacity: 0;
transition: opacity 0.3s, color 0.3s;
&:hover {
color: #666666;
}
&:active {
color: #333333;
}
}
&:hover {
.j-history-tree-title-closer {
opacity: 1;
}
}
}
/deep/ .ant-tree-switcher {
display: none;
}
/deep/ .ant-tree-node-content-wrapper {
width: 100%;
}
}
}
</style>
+83
View File
@@ -0,0 +1,83 @@
<template>
<div>
<a-select v-if="query" style="width: 100%" @change="handleSelectChange">
<a-select-option v-for="(item, index) in queryOption" :key="index" :value="item.value">
{{ item.text }}
</a-select-option>
</a-select>
<a-switch v-else v-model="checkStatus" :disabled="disabled" @change="handleChange"/>
</div>
</template>
<script>
export default {
name: 'JSwitch',
props: {
value:{
type: String | Number,
required: false
},
disabled:{
type: Boolean,
required: false,
default: false
},
options:{
type:Array,
required:false,
default:()=>['Y','N']
},
query:{
type: Boolean,
required: false,
default: false
}
},
data () {
return {
checkStatus: false
}
},
watch: {
value:{
immediate: true,
handler(val){
if(!this.query){
if(!val){
this.checkStatus = false
this.$emit('change', this.options[1]);
}else{
if(this.options[0]==val){
this.checkStatus = true
}else{
this.checkStatus = false
}
}
}
}
}
},
computed:{
queryOption(){
let arr = []
arr.push({value:this.options[0],text:'是'})
arr.push({value:this.options[1],text:'否'})
return arr;
}
},
methods: {
handleChange(checked){
let flag = checked===false?this.options[1]:this.options[0];
this.$emit('change', flag);
},
handleSelectChange(value){
this.$emit('change', value);
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
+77
View File
@@ -0,0 +1,77 @@
<template>
<a-time-picker
:disabled="disabled || readOnly"
:placeholder="placeholder"
:value="momVal"
:format="dateFormat"
:getCalendarContainer="getCalendarContainer"
@change="handleTimeChange"/>
</template>
<script>
import moment from 'moment'
export default {
name: 'JTime',
props: {
placeholder:{
type: String,
default: '',
required: false
},
value:{
type: String,
required: false
},
dateFormat:{
type: String,
default: 'HH:mm:ss',
required: false
},
readOnly:{
type: Boolean,
required: false,
default: false
},
disabled:{
type: Boolean,
required: false,
default: false
},
getCalendarContainer: {
type: Function,
default: (node) => node.parentNode
}
},
data () {
let timeStr = this.value;
return {
decorator:"",
momVal:!timeStr?null:moment(timeStr,this.dateFormat)
}
},
watch: {
value (val) {
if(!val){
this.momVal = null
}else{
this.momVal = moment(val,this.dateFormat)
}
}
},
methods: {
moment,
handleTimeChange(mom,timeStr){
this.$emit('change', timeStr);
}
},
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
</style>
+198
View File
@@ -0,0 +1,198 @@
<template>
<a-tree-select
allowClear
labelInValue
style="width: 100%"
:disabled="disabled"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="placeholder"
:loadData="asyncLoadTreeData"
:value="treeValue"
:treeData="treeData"
@change="onChange"
@search="onSearch">
</a-tree-select>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
name: 'JTreeDict',
data(){
return {
treeData:[],
treeValue: null,
url_root:"/sys/category/loadTreeRoot",
url_children:"/sys/category/loadTreeChildren",
url_view:'/sys/category/loadOne',
}
},
props:{
value:{
type: String,
required: false
},
placeholder:{
type: String,
default: '请选择',
required: false
},
parentCode:{
type: String,
default: '',
required: false
},
field:{
type: String,
default: 'id',
required: false
},
root:{
type:Object,
required:false,
default:()=>{
return {
pid:'0'
}
}
},
async:{
type:Boolean,
default:false,
required:false
},
disabled:{
type:Boolean,
default:false,
required:false
}
},
watch:{
root:{
handler(val){
console.log("root-change",val)
},
deep:true
},
parentCode:{
handler(){
this.loadRoot()
}
},
value:{
handler(){
this.loadViewInfo()
}
}
},
created(){
this.loadRoot()
this.loadViewInfo()
},
model: {
prop: 'value',
event: 'change'
},
methods:{
loadViewInfo(){
if(!this.value || this.value=="0"){
this.treeValue = null
}else{
let param = {
field:this.field,
val:this.value
}
getAction(this.url_view,param).then(res=>{
if(res.success){
this.treeValue = {
value:this.value,
label:res.result.name
}
}
})
}
},
loadRoot(){
let param = {
async:this.async,
pcode:this.parentCode
}
getAction(this.url_root,param).then(res=>{
if(res.success){
this.handleTreeNodeValue(res.result)
this.treeData = [...res.result]
}else{
this.$message.error(res.message)
}
})
},
asyncLoadTreeData (treeNode) {
return new Promise((resolve) => {
if(!this.async){
resolve()
return
}
if (treeNode.$vnode.children) {
resolve()
return
}
let pid = treeNode.$vnode.key
let param = {
pid:pid
}
getAction(this.url_children,param).then(res=>{
if(res.success){
this.handleTreeNodeValue(res.result)
this.addChildren(pid,res.result,this.treeData)
this.treeData = [...this.treeData]
}
resolve()
})
})
},
addChildren(pid,children,treeArray){
if(treeArray && treeArray.length>0){
for(let item of treeArray){
if(item.key == pid){
if(!children || children.length==0){
item.leaf = true
}else{
item.children = children
}
break
}else{
this.addChildren(pid,children,item.children)
}
}
}
},
handleTreeNodeValue(result){
let storeField = this.field=='code'?'code':'key'
for(let i of result){
i.value = i[storeField]
i.isLeaf = (!i.leaf)?false:true
if(i.children && i.children.length>0){
this.handleTreeNodeValue(i.children)
}
}
},
onChange(value){
console.log(value)
if(!value){
this.$emit('change', '');
}else{
this.$emit('change', value.value);
}
this.treeValue = value
},
onSearch(value){
console.log(value)
},
getCurrTreeData(){
return this.treeData
}
}
}
</script>
@@ -0,0 +1,259 @@
<template>
<a-tree-select
allowClear
labelInValue
:getPopupContainer="(node) => node.parentNode"
style="width: 100%"
:disabled="disabled"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="placeholder"
:loadData="asyncLoadTreeData"
:value="treeValue"
:treeData="treeData"
:multiple="multiple"
@change="onChange"
@search="onSearch">
</a-tree-select>
</template>
<script>
/*
* 异步树加载组件 通过传入表名 显示字段 存储字段 加载一个树控件
* <j-tree-select dict="aa_tree_test,aad,id" pid-field="pid" ></j-tree-select>
* */
import { getAction } from '@/api/manage'
export default {
name: 'JTreeSelect',
props: {
value:{
type: String,
required: false
},
placeholder:{
type: String,
default: '请选择',
required: false
},
dict:{
type: String,
default: '',
required: false
},
pidField:{
type: String,
default: 'pid',
required: false
},
pidValue:{
type: String,
default: '',
required: false
},
disabled:{
type:Boolean,
default:false,
required:false
},
hasChildField:{
type: String,
default: '',
required: false
},
condition:{
type:String,
default:'',
required:false
},
// 是否支持多选
multiple: {
type: Boolean,
default: false,
},
loadTriggleChange:{
type: Boolean,
default: false,
required:false
}
},
data () {
return {
treeValue: null,
treeData:[],
url:"/sys/dict/loadTreeData",
view:'/sys/dict/loadDictItem/',
tableName:"",
text:"",
code:"",
}
},
watch: {
value () {
this.loadItemByCode()
},
dict(){
this.initDictInfo()
this.loadRoot();
}
},
created(){
this.validateProp().then(()=>{
this.initDictInfo()
this.loadRoot()
this.loadItemByCode()
})
},
methods: {
loadItemByCode(){
if(!this.value || this.value=="0"){
this.treeValue = null
}else{
getAction(`${this.view}${this.dict}`,{key:this.value}).then(res=>{
if(res.success){
let values = this.value.split(',')
this.treeValue = res.result.map((item, index) => ({
key: values[index],
value: values[index],
label: item
}))
this.onLoadTriggleChange(res.result[0]);
}
})
}
},
onLoadTriggleChange(text){
//只有单选才会触发
if(!this.multiple && this.loadTriggleChange){
this.$emit('change', this.value,text)
}
},
initDictInfo(){
let arr = this.dict.split(",")
this.tableName = arr[0]
this.text = arr[1]
this.code = arr[2]
},
asyncLoadTreeData (treeNode) {
return new Promise((resolve) => {
if (treeNode.$vnode.children) {
resolve()
return
}
let pid = treeNode.$vnode.key
let param = {
pid:pid,
tableName:this.tableName,
text:this.text,
code:this.code,
pidField:this.pidField,
hasChildField:this.hasChildField,
condition:this.condition
}
getAction(this.url,param).then(res=>{
if(res.success){
for(let i of res.result){
i.value = i.key
if(i.leaf==false){
i.isLeaf=false
}else if(i.leaf==true){
i.isLeaf=true
}
}
this.addChildren(pid,res.result,this.treeData)
this.treeData = [...this.treeData]
}
resolve()
})
})
},
addChildren(pid,children,treeArray){
if(treeArray && treeArray.length>0){
for(let item of treeArray){
if(item.key == pid){
if(!children || children.length==0){
item.isLeaf=true
}else{
item.children = children
}
break
}else{
this.addChildren(pid,children,item.children)
}
}
}
},
loadRoot(){
let param = {
pid:this.pidValue,
tableName:this.tableName,
text:this.text,
code:this.code,
pidField:this.pidField,
hasChildField:this.hasChildField,
condition:this.condition
}
getAction(this.url,param).then(res=>{
if(res.success && res.result){
for(let i of res.result){
i.value = i.key
if(i.leaf==false){
i.isLeaf=false
}else if(i.leaf==true){
i.isLeaf=true
}
}
this.treeData = [...res.result]
}else{
console.log("数根节点查询结果-else",res)
}
})
},
onChange(value){
if(!value){
this.$emit('change', '');
this.treeValue = null
} else if (value instanceof Array) {
this.$emit('change', value.map(item => item.value).join(','))
this.treeValue = value
} else {
this.$emit('change', value.value,value.label)
this.treeValue = value
}
},
onSearch(value){
console.log(value)
},
getCurrTreeData(){
return this.treeData
},
validateProp(){
let mycondition = this.condition
return new Promise((resolve,reject)=>{
if(!mycondition){
resolve();
}else{
try {
let test=JSON.parse(mycondition);
if(typeof test == 'object' && test){
resolve()
}else{
this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
reject()
}
} catch(e) {
this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
reject()
}
}
})
}
},
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
model: {
prop: 'value',
event: 'change'
}
}
</script>
+180
View File
@@ -0,0 +1,180 @@
<template>
<a-table
:rowKey="rowKey"
:columns="columns"
:dataSource="dataSource"
:expandedRowKeys="expandedRowKeys"
v-bind="tableAttrs"
v-on="$listeners"
@expand="handleExpand"
@expandedRowsChange="expandedRowKeys=$event">
<template v-for="(slotItem) of slots" :slot="slotItem" slot-scope="text, record, index">
<slot :name="slotItem" v-bind="{text,record,index}"></slot>
</template>
</a-table>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
name: 'JTreeTable',
props: {
rowKey: {
type: String,
default: 'id'
},
// 根据什么查询,如果传递 id 就根据 id 查询
queryKey: {
type: String,
default: 'parentId'
},
queryParams: {
type: Object,
default: () => ({})
},
// 查询顶级时的值,如果顶级为0,则传0
topValue: {
type: String,
default: null
},
columns: {
type: Array,
required: true
},
url: {
type: String,
required: true
},
childrenUrl: {
type: String,
default: null
},
tableProps: {
type: Object,
default: () => ({})
},
/** 是否在创建组件的时候就查询数据 */
immediateRequest: {
type: Boolean,
default: true
},
condition:{
type:String,
default:'',
required:false
}
},
data() {
return {
dataSource: [],
expandedRowKeys: []
}
},
computed: {
getChildrenUrl() {
if (this.childrenUrl) {
return this.childrenUrl
} else {
return this.url
}
},
slots() {
let slots = []
for (let column of this.columns) {
if (column.scopedSlots && column.scopedSlots.customRender) {
slots.push(column.scopedSlots.customRender)
}
}
return slots
},
tableAttrs() {
return Object.assign(this.$attrs, this.tableProps)
}
},
watch: {
queryParams: {
deep: true,
handler() {
this.loadData()
}
}
},
created() {
if (this.immediateRequest) this.loadData()
},
methods: {
/** 加载数据*/
loadData(id = this.topValue, first = true, url = this.url) {
this.$emit('requestBefore', { first })
if (first) {
this.expandedRowKeys = []
}
let params = Object.assign({}, this.queryParams || {})
params[this.queryKey] = id
if(this.condition && this.condition.length>0){
params['condition'] = this.condition
}
return getAction(url, params).then(res => {
let list = []
if (res.result instanceof Array) {
list = res.result
} else if (res.result.records instanceof Array) {
list = res.result.records
} else {
throw '返回数据类型不识别'
}
let dataSource = list.map(item => {
// 判断是否标记了带有子级
if (item.hasChildren === true) {
// 查找第一个带有dataIndex的值的列
let firstColumn
for (let column of this.columns) {
firstColumn = column.dataIndex
if (firstColumn) break
}
// 定义默认展开时显示的loading子级,实际子级数据只在展开时加载
let loadChild = { id: `${item.id}_loadChild`, [firstColumn]: 'loading...', isLoading: true }
item.children = [loadChild]
}
return item
})
if (first) {
this.dataSource = dataSource
}
this.$emit('requestSuccess', { first, dataSource, res })
return Promise.resolve(dataSource)
}).finally(() => this.$emit('requestFinally', { first }))
},
/** 点击展开图标时触发 */
handleExpand(expanded, record) {
// 判断是否是展开状态
if (expanded) {
// 判断子级的首个项的标记是否是“正在加载中”,如果是就加载数据
if (record.children[0].isLoading === true) {
this.loadData(record.id, false, this.getChildrenUrl).then(dataSource => {
// 处理好的数据可直接赋值给children
if (dataSource.length === 0) {
record.children = null
} else {
record.children = dataSource
}
})
}
}
}
}
}
</script>
<style scoped>
</style>
+449
View File
@@ -0,0 +1,449 @@
<template>
<div :id="containerId" style="position: relative">
<!-- ---------------------------- begin 图片左右换位置 ------------------------------------- -->
<div class="movety-container" :style="{top:top+'px',left:left+'px',display:moveDisplay}" style="padding:0 8px;position: absolute;z-index: 91;height: 32px;width: 104px;text-align: center;">
<div :id="containerId+'-mover'" :class="showMoverTask?'uploadty-mover-mask':'movety-opt'" style="margin-top: 12px">
<a @click="moveLast" style="margin: 0 5px;"><a-icon type="arrow-left" style="color: #fff;font-size: 16px"/></a>
<a @click="moveNext" style="margin: 0 5px;"><a-icon type="arrow-right" style="color: #fff;font-size: 16px"/></a>
</div>
</div>
<!-- ---------------------------- end 图片左右换位置 ------------------------------------- -->
<a-upload
name="file"
:multiple="true"
:action="uploadAction"
:headers="headers"
:data="{'biz':bizPath}"
:fileList="fileList"
:beforeUpload="beforeUpload"
@change="handleChange"
:disabled="disabled"
:returnUrl="returnUrl"
:listType="complistType"
@preview="handlePreview"
:class="{'uploadty-disabled':disabled}">
<template>
<div v-if="isImageComp">
<a-icon type="plus" />
<div class="ant-upload-text">{{ text }}</div>
</div>
<a-button v-else-if="buttonVisible">
<a-icon type="upload" />{{ text }}
</a-button>
</template>
</a-upload>
<a-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal>
</div>
</template>
<script>
import Vue from 'vue'
import { ACCESS_TOKEN } from "@/store/mutation-types"
import { getFileAccessHttpUrl } from '@/api/manage';
const FILE_TYPE_ALL = "all"
const FILE_TYPE_IMG = "image"
const FILE_TYPE_TXT = "file"
const uidGenerator=()=>{
return '-'+parseInt(Math.random()*10000+1,10);
}
const getFileName=(path)=>{
if(path.lastIndexOf("\\")>=0){
let reg=new RegExp("\\\\","g");
path = path.replace(reg,"/");
}
return path.substring(path.lastIndexOf("/")+1);
}
export default {
name: 'JUpload',
data(){
return {
uploadAction:window._CONFIG['domianURL']+"/sys/common/upload",
headers:{},
fileList: [],
newFileList: [],
uploadGoOn:true,
previewVisible: false,
//---------------------------- begin 图片左右换位置 -------------------------------------
previewImage: '',
containerId:'',
top:'',
left:'',
moveDisplay:'none',
showMoverTask:false,
moverHold:false,
currentImg:''
//---------------------------- end 图片左右换位置 -------------------------------------
}
},
props:{
text:{
type:String,
required:false,
default:"点击上传"
},
fileType:{
type:String,
required:false,
default:FILE_TYPE_ALL
},
/*这个属性用于控制文件上传的业务路径*/
bizPath:{
type:String,
required:false,
default:"temp"
},
value:{
type:[String,Array],
required:false
},
// update-begin- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
disabled:{
type:Boolean,
required:false,
default: false
},
// update-end- --- author:wangshuai ------ date:20190929 ---- for:Jupload组件增加是否能够点击
//此属性被废弃了
triggerChange:{
type: Boolean,
required: false,
default: false
},
/**
* update -- author:lvdandan -- date:20190219 -- for:Jupload组件增加是否返回url
* true:仅返回url
* false:返回fileName filePath fileSize
*/
returnUrl:{
type:Boolean,
required:false,
default: true
},
number:{
type:Number,
required:false,
default: 0
},
buttonVisible:{
type:Boolean,
required:false,
default: true
},
},
watch:{
value:{
immediate: true,
handler() {
let val = this.value
if (val instanceof Array) {
if(this.returnUrl){
this.initFileList(val.join(','))
}else{
this.initFileListArr(val);
}
} else {
this.initFileList(val)
}
}
}
},
computed:{
isImageComp(){
return this.fileType === FILE_TYPE_IMG
},
complistType(){
return this.fileType === FILE_TYPE_IMG?'picture-card':'text'
}
},
created(){
const token = Vue.ls.get(ACCESS_TOKEN);
//---------------------------- begin 图片左右换位置 -------------------------------------
this.headers = {"X-Access-Token":token};
this.containerId = 'container-ty-'+new Date().getTime();
//---------------------------- end 图片左右换位置 -------------------------------------
},
methods:{
initFileListArr(val){
if(!val || val.length==0){
this.fileList = [];
return;
}
let fileList = [];
for(var a=0;a<val.length;a++){
let url = getFileAccessHttpUrl(val[a].filePath);
fileList.push({
uid:uidGenerator(),
name:val[a].fileName,
status: 'done',
url: url,
response:{
status:"history",
message:val[a].filePath
}
})
}
this.fileList = fileList
},
initFileList(paths){
if(!paths || paths.length==0){
//return [];
// update-begin- --- author:os_chengtgen ------ date:20190729 ---- for:issues:326,Jupload组件初始化bug
this.fileList = [];
return;
// update-end- --- author:os_chengtgen ------ date:20190729 ---- for:issues:326,Jupload组件初始化bug
}
let fileList = [];
let arr = paths.split(",")
for(var a=0;a<arr.length;a++){
let url = getFileAccessHttpUrl(arr[a]);
fileList.push({
uid:uidGenerator(),
name:getFileName(arr[a]),
status: 'done',
url: url,
response:{
status:"history",
message:arr[a]
}
})
}
this.fileList = fileList
},
handlePathChange(){
let uploadFiles = this.fileList
let path = ''
if(!uploadFiles || uploadFiles.length==0){
path = ''
}
let arr = [];
for(var a=0;a<uploadFiles.length;a++){
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
if(uploadFiles[a].status === 'done' ) {
arr.push(uploadFiles[a].response.message)
}else{
return;
}
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
}
if(arr.length>0){
path = arr.join(",")
}
this.$emit('change', path);
},
beforeUpload(file){
this.uploadGoOn=true
var fileType = file.type;
if(this.fileType===FILE_TYPE_IMG){
if(fileType.indexOf('image')<0){
this.$message.warning('请上传图片');
this.uploadGoOn=false
return false;
}
}
//TODO 扩展功能验证文件大小
return true
},
handleChange(info) {
console.log("--文件列表改变--")
if(!info.file.status && this.uploadGoOn === false){
info.fileList.pop();
}
let fileList = info.fileList
if(info.file.status==='done'){
if(this.number>0){
fileList = fileList.slice(-this.number);
}
if(info.file.response.success){
fileList = fileList.map((file) => {
if (file.response) {
let reUrl = file.response.message;
file.url = getFileAccessHttpUrl(reUrl);
}
return file;
});
}
//this.$message.success(`${info.file.name} 上传成功!`);
}else if (info.file.status === 'error') {
this.$message.error(`${info.file.name} 上传失败.`);
}else if(info.file.status === 'removed'){
this.handleDelete(info.file)
}
this.fileList = fileList
if(info.file.status==='done' || info.file.status === 'removed'){
//returnUrl为true时仅返回文件路径
if(this.returnUrl){
this.handlePathChange()
}else{
//returnUrl为false时返回文件名称、文件路径及文件大小
this.newFileList = [];
for(var a=0;a<fileList.length;a++){
// update-begin-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
if(fileList[a].status === 'done' ) {
var fileJson = {
fileName:fileList[a].name,
filePath:fileList[a].response.message,
fileSize:fileList[a].size
};
this.newFileList.push(fileJson);
}else{
return;
}
// update-end-author:lvdandan date:20200603 for:【TESTA-514】【开源issue】多个文件同时上传时,控制台报错
}
this.$emit('change', this.newFileList);
}
}
},
handleDelete(file){
//如有需要新增 删除逻辑
console.log(file)
},
handlePreview(file){
if(this.fileType === FILE_TYPE_IMG){
this.previewImage = file.url || file.thumbUrl;
this.previewVisible = true;
}else{
location.href=file.url
}
},
handleCancel(){
this.previewVisible = false;
},
//---------------------------- begin 图片左右换位置 -------------------------------------
moveLast(){
//console.log(ev)
//console.log(this.fileList)
//console.log(this.currentImg)
let index = this.getIndexByUrl();
if(index==0){
this.$message.warn('未知的操作')
}else{
let curr = this.fileList[index].url;
let last = this.fileList[index-1].url;
let arr =[]
for(let i=0;i<this.fileList.length;i++){
if(i==index-1){
arr.push(curr)
}else if(i==index){
arr.push(last)
}else{
arr.push(this.fileList[i].url)
}
}
this.currentImg = last
this.$emit('change',arr.join(','))
}
},
moveNext(){
let index = this.getIndexByUrl();
if(index==this.fileList.length-1){
this.$message.warn('已到最后~')
}else{
let curr = this.fileList[index].url;
let next = this.fileList[index+1].url;
let arr =[]
for(let i=0;i<this.fileList.length;i++){
if(i==index+1){
arr.push(curr)
}else if(i==index){
arr.push(next)
}else{
arr.push(this.fileList[i].url)
}
}
this.currentImg = next
this.$emit('change',arr.join(','))
}
},
getIndexByUrl(){
for(let i=0;i<this.fileList.length;i++){
if(this.fileList[i].url === this.currentImg || encodeURI(this.fileList[i].url) === this.currentImg){
return i;
}
}
return -1;
}
},
mounted(){
const moverObj = document.getElementById(this.containerId+'-mover');
if(moverObj){
moverObj.addEventListener('mouseover',()=>{
this.moverHold = true
this.moveDisplay = 'block';
});
moverObj.addEventListener('mouseout',()=>{
this.moverHold = false
this.moveDisplay = 'none';
});
}
let picList = document.getElementById(this.containerId)?document.getElementById(this.containerId).getElementsByClassName('ant-upload-list-picture-card'):[];
if(picList && picList.length>0){
picList[0].addEventListener('mouseover',(ev)=>{
ev = ev || window.event;
let target = ev.target || ev.srcElement;
if('ant-upload-list-item-info' == target.className){
this.showMoverTask=false
let item = target.parentElement
this.left = item.offsetLeft
this.top=item.offsetTop+item.offsetHeight-50;
this.moveDisplay = 'block';
this.currentImg = target.getElementsByTagName('img')[0].src
}
});
picList[0].addEventListener('mouseout',(ev)=>{
ev = ev || window.event;
let target = ev.target || ev.srcElement;
//console.log('移除',target)
if('ant-upload-list-item-info' == target.className){
this.showMoverTask=true
setTimeout(()=>{
if(this.moverHold === false)
this.moveDisplay = 'none';
},100)
}
if('ant-upload-list-item ant-upload-list-item-done' == target.className || 'ant-upload-list ant-upload-list-picture-card'== target.className){
this.moveDisplay = 'none';
}
})
//---------------------------- end 图片左右换位置 -------------------------------------
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style lang="less">
.uploadty-disabled{
.ant-upload-list-item {
.anticon-close{
display: none;
}
.anticon-delete{
display: none;
}
}
}
//---------------------------- begin 图片左右换位置 -------------------------------------
.uploadty-mover-mask{
background-color: rgba(0, 0, 0, 0.5);
opacity: .8;
color: #fff;
height: 28px;
line-height: 28px;
}
//---------------------------- end 图片左右换位置 -------------------------------------
</style>
@@ -0,0 +1,75 @@
<template>
<j-modal
title="详细信息"
:width="1200"
:visible="visible"
@ok="handleOk"
@cancel="close"
switch-fullscreen
:fullscreen.sync="fullscreen"
>
<transition name="fade">
<div v-if="visible">
<slot name="mainForm" :row="row" :column="column"/>
<slot name="subForm" :row="row" :column="column"/>
</div>
</transition>
</j-modal>
</template>
<script>
import { cloneObject } from '@/utils/util'
export default {
name: 'JVxeDetailsModal',
inject: ['superTrigger'],
data() {
return {
visible: false,
fullscreen: false,
row: null,
column: null,
}
},
created() {
},
methods: {
open(event) {
let {row, column} = event
this.row = cloneObject(row)
this.column = column
this.visible = true
},
close() {
this.visible = false
},
handleOk() {
this.superTrigger('detailsConfirm', {
row: this.row,
column: this.column,
callback: (success) => {
this.visible = !success
},
})
},
},
}
</script>
<style lang="less">
.fade-enter-active,
.fade-leave-active {
opacity: 1;
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
@@ -0,0 +1,67 @@
<template>
<div :class="boxClass">
<a-pagination
:disabled="disabled"
v-bind="bindProps"
@change="handleChange"
@showSizeChange="handleShowSizeChange"
/>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types'
export default {
name: 'JVxePagination',
props: {
size: String,
disabled: PropTypes.bool,
pagination: PropTypes.object.def({}),
},
data() {
return {
defaultPagination: {
current: 1,
pageSize: 10,
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return range[0] + '-' + range[1] + ' 共 ' + total + ' 条'
},
showQuickJumper: true,
showSizeChanger: true,
total: 100
}
}
},
computed: {
bindProps() {
return {
...this.defaultPagination,
...this.pagination,
size: this.size === 'tiny' ? 'small' : ''
}
},
boxClass() {
return {
'j-vxe-pagination': true,
'show-quick-jumper': !!this.bindProps.showQuickJumper
}
},
},
methods: {
handleChange(current, pageSize) {
this.$set(this.pagination, 'current', current)
this.$emit('change', {current, pageSize})
},
handleShowSizeChange(current, pageSize) {
this.$set(this.pagination, 'pageSize', pageSize)
this.$emit('change', {current, pageSize})
},
},
}
</script>
<style lang="less" scoped>
</style>
@@ -0,0 +1,182 @@
<template>
<a-popover :visible="visible" :placement="placement" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
<div class="j-vxe-popover-title" slot="title">
<div>子表</div>
<div class="j-vxe-popover-title-close" @click="close">
<a-icon type="close"/>
</div>
</div>
<template slot="content">
<transition name="fade">
<slot v-if="visible" name="subForm" :row="row" :column="column"/>
</transition>
</template>
<div ref="div" class="j-vxe-popover-div"></div>
</a-popover>
</template>
<script>
import domAlign from 'dom-align'
import { getParentNodeByTagName } from '../utils/vxeUtils'
import { cloneObject, triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'JVxeSubPopover',
data() {
return {
visible: false,
// 当前行
row: null,
column: null,
overlayStyle: {
width: null,
zIndex: 100
},
placement: 'bottom'
}
},
created() {
},
methods: {
toggle(event) {
//update-begin-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
if(document.body.clientHeight - event.$event.clientY > 350){
this.placement = 'bottom'
}else{
this.placement = 'top'
}
//update-end-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
if (this.row == null) {
this.open(event)
} else {
this.row.id === event.row.id ? this.close() : this.reopen(event)
}
},
open(event, level = 0) {
if (level > 3) {
this.$message.error('打开子表失败')
console.warn('【JVxeSubPopover】打开子表失败')
return
}
let {row, column, $table, $event: {target}} = event
this.row = cloneObject(row)
this.column = column
let className = target.className || ''
className = typeof className === 'string' ? className : className.toString()
// 点击的是expand,不做处理
if (className.includes('vxe-table--expand-btn')) {
return
}
// 点击的是checkbox,不做处理
if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) {
return
}
// 点击的是radio,不做处理
if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) {
return
}
let table = $table.$el
let tr = getParentNodeByTagName(target, 'tr')
if (table && tr) {
let clientWidth = table.clientWidth
let clientHeight = tr.clientHeight
this.$refs.div.style.width = clientWidth + 'px'
this.$refs.div.style.height = clientHeight + 'px'
this.overlayStyle.width = Number.parseInt((clientWidth - clientWidth * 0.04)) + 'px'
this.overlayStyle.maxWidth = this.overlayStyle.width
//update-begin-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
//let realTable = getParentNodeByTagName(tr, 'table')
//let left = realTable.parentNode.scrollLeft
let h = event.$event.clientY
if(h){
h = h-140
}
let toolbar = this.$refs.div.nextSibling
domAlign(this.$refs.div, toolbar, {
points: ['tl', 'tl'],
offset: [0, h],
overflow: {
alwaysByViewport: true
},
})
//update-end-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
this.$nextTick(() => {
this.visible = true
this.$nextTick(() => {
triggerWindowResizeEvent()
})
})
} else {
let num = ++level
console.warn('【JVxeSubPopover】table or tr 获取失败,正在进行第 ' + num + '次重试', {event, table, tr})
window.setTimeout(() => this.open(event, num), 100)
}
},
close() {
if (this.visible) {
this.row = null
this.visible = false
}
},
reopen(event) {
this.close()
this.open(event)
},
},
}
</script>
<style scoped lang="less">
.j-vxe-popover-title {
.j-vxe-popover-title-close {
position: absolute;
right: 0;
top: 0;
width: 31px;
height: 31px;
text-align: center;
line-height: 31px;
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: color 300ms;
&:hover {
color: rgba(0, 0, 0, 0.8);
}
}
}
.j-vxe-popover-div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 31px;
z-index: -1;
}
</style>
<style lang="less">
.j-vxe-popover-overlay.ant-popover {
.ant-popover-title {
position: relative;
}
}
.fade-enter-active,
.fade-leave-active {
opacity: 1;
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,127 @@
<template>
<div :class="boxClass">
<!-- 工具按钮 -->
<div class="j-vxe-tool-button div" :size="btnSize">
<slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize"/>
<a-button v-if="showAdd" icon="plus" @click="trigger('add')" :disabled="disabled" type="primary">新增</a-button>
<a-button v-if="showSave" icon="save" @click="trigger('save')" :disabled="disabled">保存</a-button>
<template v-if="selectedRowIds.length > 0">
<a-popconfirm
v-if="showRemove"
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
@confirm="trigger('remove')"
>
<a-button icon="minus" :disabled="disabled">删除</a-button>
</a-popconfirm>
<template v-if="showClearSelection">
<a-button icon="delete" @click="trigger('clearSelection')">清空选择</a-button>
</template>
</template>
<slot v-if="showSuffix" name="toolbarSuffix" :size="btnSize"/>
<a v-if="showCollapse" @click="toggleCollapse" style="margin-left: 4px">
<span>{{ collapsed ? '展开' : '收起' }}</span>
<a-icon :type="collapsed ? 'down' : 'up'"/>
</a>
</div>
</div>
</template>
<script>
export default {
name: 'JVxeToolbar',
props: {
toolbarConfig: Object,
excludeCode: Array,
size: String,
disabled: Boolean,
disabledRows: Object,
selectedRowIds: Array,
},
data() {
return {
// 是否收起
collapsed: true,
}
},
computed: {
boxClass() {
return {
'j-vxe-toolbar': true,
'j-vxe-toolbar-collapsed': this.collapsed,
}
},
btns() {
let arr = this.toolbarConfig.btn || ['add', 'remove', 'clearSelection']
let exclude = [...this.excludeCode]
// TODO 需要将remove替换batch_delete
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
if(exclude.indexOf('batch_delete')>=0){
exclude.push('remove')
}
// 按钮权限 需要去掉不被授权的按钮
return arr.filter(item=>{
return exclude.indexOf(item)<0
})
},
slots() {
return this.toolbarConfig.slot || ['prefix', 'suffix']
},
showPrefix() {
return this.slots.includes('prefix')
},
showSuffix() {
return this.slots.includes('suffix')
},
showAdd() {
return this.btns.includes('add')
},
showSave() {
return this.btns.includes('save')
},
showRemove() {
return this.btns.includes('remove')
},
showClearSelection() {
if (this.btns.includes('clearSelection')) {
// 有禁用行时才显示清空选择按钮
// 因为禁用行会阻止选择行,导致无法取消全选
let length = Object.keys(this.disabledRows).length
return length > 0
}
return false
},
showCollapse() {
return this.btns.includes('collapse')
},
btnSize() {
return this.size === 'tiny' ? 'small' : null
},
},
methods: {
/** 触发事件 */
trigger(name) {
this.$emit(name)
},
// 切换展开收起
toggleCollapse() {
this.collapsed = !this.collapsed
},
},
}
</script>
<style lang="less">
.j-vxe-toolbar-collapsed {
[data-collapse] {
display: none;
}
}
.j-vxe-tool-button.div .ant-btn {
margin-right: 8px;
}
</style>
@@ -0,0 +1,103 @@
<template>
<div :class="clazz" :style="boxStyle">
<a-checkbox
ref="checkbox"
:checked="innerValue"
v-bind="cellProps"
@change="handleChange"
/>
</div>
</template>
<script>
import { neverNull } from '@/utils/util'
import JVxeCellMixins from '@/components/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) {
let {own: col} = this.column
// 处理 customValue
if (Array.isArray(col.customValue)) {
let customValue = getCustomValue(col)
if (typeof value === 'boolean') {
return value ? customValue[0] : customValue[1]
} else {
return value
}
} else {
return value
}
},
setValue(value) {
let {own: col} = this.column
// 判断是否设定了customValue(自定义值)
if (Array.isArray(col.customValue)) {
let customValue = getCustomValue(col)
return neverNull(value).toString() === customValue[0].toString()
} else {
return !!value
}
},
createValue({column}) {
let {own: col} = column
if (Array.isArray(col.customValue)) {
let customValue = getCustomValue(col)
return col.defaultChecked ? customValue[0] : customValue[1]
} else {
return !!col.defaultChecked
}
},
}
}
function getCustomValue(col) {
let customTrue = neverNull(col.customValue[0], true)
let customFalse = neverNull(col.customValue[1], false)
return [customTrue, customFalse]
}
</script>
<style lang="less">
// 关闭动画,防止滚动时动态赋值出现问题
.j-vxe-checkbox.no-animation {
.ant-checkbox-inner,
.ant-checkbox-inner::after {
transition: none !important;
}
}
</style>
@@ -0,0 +1,66 @@
<template>
<a-date-picker
ref="datePicker"
:value="innerDateValue"
allowClear
:format="dateFormat"
:showTime="isDatetime"
dropdownClassName="j-vxe-date-picker"
style="min-width: 0;"
v-bind="cellProps"
@change="handleChange"
/>
</template>
<script>
import moment from 'moment'
import { JVXETypes } from '@/components/jero/JVxeTable/index'
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() {
let format = this.originColumn.format
return format ? format : (this.isDatetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')
},
},
watch: {
innerValue: {
immediate: true,
handler(val) {
if (val == null || val === '') {
this.innerDateValue = null
} else {
this.innerDateValue = moment(val, this.dateFormat)
}
}
}
},
methods: {
handleChange(mom, dateStr) {
this.handleChangeCommon(dateStr)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event)),
},
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,138 @@
<template>
<div>
<a-input
v-show="!departIds"
@click="openSelect"
placeholder="请点击选择部门"
v-model="departNames"
readOnly
:disabled="componentDisabled"
class="jvxe-select-input">
<a-icon slot="prefix" type="cluster" title="部门选择控件"/>
</a-input>
<j-select-depart-modal
ref="innerDepartSelectModal"
:modal-width="modalWidth"
:multi="multi"
:rootOpened="rootOpened"
:depart-id="departIds"
@ok="handleOK"
@initComp="initComp"/>
<span style="display: inline-block;height:100%;padding-left:14px" v-if="departIds" >
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ departNames }}</span>
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
</span>
</div>
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/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(){
if(this.cellProps.disabled==true){
return true
}
return false
},
modalWidth(){
if(this.cellProps.modalWidth){
return this.cellProps.modalWidth
}else{
return 500
}
},
multi(){
if(this.cellProps.multi==false){
return false
}else{
return true
}
},
rootOpened(){
if(this.cellProps.open==false){
return false
}else{
return true
}
}
},
watch: {
innerValue: {
immediate: true,
handler(val) {
if (val == null || val === '') {
this.departIds = ''
} else {
this.departIds = val
}
}
}
},
methods: {
openSelect(){
this.$refs.innerDepartSelectModal.show()
},
handleEmpty(){
this.handleOK('')
},
handleOK(rows, idstr) {
let value = ''
if (!rows && rows.length <= 0) {
this.departNames = ''
this.departIds = ''
} else {
value = rows.map(row => row[this.customReturnField]).join(',')
this.departNames = rows.map(row => row['departName']).join(',')
this.departIds = idstr
}
this.handleChangeCommon(this.departIds)
},
initComp(departNames){
this.departNames = departNames
},
handleChange(value) {
this.handleChangeCommon(value)
}
},
enhanced: {
switches: {
visible: true
},
translate: {
enabled: false
}
}
}
</script>
<style scoped>
/deep/ .jvxe-select-input .ant-input{
border: none !important;
}
</style>
@@ -0,0 +1,138 @@
<template>
<a-dropdown :trigger="['click']">
<div class="j-vxe-ds-icons">
<a-icon type="align-left"/>
<a-icon type="align-right"/>
</div>
<!-- <div class="j-vxe-ds-btns">-->
<!-- <a-button icon="caret-up" size="small" :disabled="disabledMoveUp" @click="handleRowMoveUp"/>-->
<!-- <a-button icon="caret-down" size="small" :disabled="disabledMoveDown" @click="handleRowMoveDown"/>-->
<!-- </div>-->
<a-menu slot="overlay">
<a-menu-item key="0" :disabled="disabledMoveUp" @click="handleRowMoveUp">向上移</a-menu-item>
<a-menu-item key="1" :disabled="disabledMoveDown" @click="handleRowMoveDown">向下移</a-menu-item>
<a-menu-divider/>
<a-menu-item key="3" @click="handleRowInsertDown">插入一行</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
import JVxeCellMixins from '@/components/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.rows.length - 1)
},
},
methods: {
/** 向上移 */
handleRowMoveUp(event) {
// event.target.blur()
if (!this.disabledMoveUp) {
this.trigger('rowMoveUp', this.rowIndex)
}
},
/** 向下移 */
handleRowMoveDown(event) {
// event.target.blur()
if (!this.disabledMoveDown) {
this.trigger('rowMoveDown', this.rowIndex)
}
},
/** 插入一行 */
handleRowInsertDown() {
this.trigger('rowInsertDown', this.rowIndex)
},
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
// 【功能开关】
switches: {
editRender: false
},
},
}
</script>
<style lang="less">
.j-vxe-ds-icons {
position: relative;
/*cursor: move;*/
cursor: pointer;
width: 14px;
height: 100%;
display: inline-block;
.anticon-align-left,
.anticon-align-right {
position: absolute;
top: 30%;
}
.anticon-align-left {
left: 0;
}
.anticon-align-right {
right: 0;
}
}
.j-vxe-ds-btns {
position: relative;
cursor: pointer;
width: 24px;
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-content: center;
.ant-btn {
border: none;
z-index: 0;
padding: 0;
width: 100%;
/*height: 30%;*/
height: 40%;
display: block;
border-radius: 0;
&:hover {
z-index: 1;
/* height: 40%;*/
/* & .anticon-caret-up,*/
/* & .anticon-caret-down {*/
/* top: 2px;*/
/* }*/
}
&:last-child {
margin-top: -1px;
}
& .anticon-caret-up,
& .anticon-caret-down {
vertical-align: top;
position: relative;
top: 0;
transition: top 0.3s;
}
}
}
</style>
@@ -0,0 +1,87 @@
<template>
<a-input
ref="input"
:value="innerValue"
v-bind="cellProps"
@blur="handleBlur"
@change="handleChange"
/>
</template>
<script>
import { JVXETypes } from '@/components/jero/JVxeTable'
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
const NumberRegExp = /^-?\d+\.?\d*$/
export default {
name: 'JVxeInputCell',
mixins: [JVxeCellMixins],
methods: {
/** 处理change事件 */
handleChange(event) {
let {$type} = this
let {target} = event
let {value, selectionStart} = target
let change = true
if ($type === JVXETypes.inputNumber) {
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
if (!NumberRegExp.test(value) && (value !== '' && value !== '-')) {
change = false
value = this.innerValue
target.value = value || ''
if (typeof selectionStart === 'number') {
target.selectionStart = selectionStart - 1
target.selectionEnd = selectionStart - 1
}
}
}
// 触发事件,存储输入的值
if (change) {
this.handleChangeCommon(value)
}
if ($type === JVXETypes.inputNumber) {
// this.recalcOneStatisticsColumn(col.key)
}
},
/** 处理blur失去焦点事件 */
handleBlur(event) {
let {$type} = this
let {target} = event
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
if ($type === JVXETypes.inputNumber) {
if (!NumberRegExp.test(target.value)) {
target.value = ''
} else {
target.value = Number.parseFloat(target.value)
}
this.handleChangeCommon(target.value)
}
this.handleBlurCommon(target.value)
},
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
installOptions: {
// 自动聚焦的 class 类名
autofocus: '.ant-input',
},
getValue(value) {
if (this.$type === JVXETypes.inputNumber && typeof value === 'string') {
if (NumberRegExp.test(value)) {
return Number.parseFloat(value)
}
}
return value
},
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,42 @@
<template>
<reload-effect
:vNode="innerValue"
:effect="reloadEffect"
@effect-end="handleEffectEnd"
/>
</template>
<script>
import ReloadEffect from './ReloadEffect'
import JVxeCellMixins from '@/components/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,150 @@
<template>
<a-select
ref="select"
:value="innerValue"
allowClear
:filterOption="handleSelectFilterOption"
v-bind="selectProps"
style="width: 100%;"
@blur="handleBlur"
@change="handleChangeCommon"
@search="handleSearchSelect"
>
<template v-for="option of originColumn.options">
<a-select-option :key="option.value" :value="option.value" :disabled="option.disabled">
<span>{{option.text || option.label || option.title|| option.value}}</span>
</a-select-option>
</template>
</a-select>
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import { JVXETypes } from '@comp/jero/JVxeTable/index'
export default {
name: 'JVxeSelectCell',
mixins: [JVxeCellMixins],
computed: {
selectProps() {
let props = {...this.cellProps}
// 判断select是否允许输入
let {allowSearch, allowInput} = this.originColumn
if (allowInput === true || allowSearch === true) {
props['showSearch'] = true
}
return props
},
},
created() {
let multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
let search = [JVXETypes.selectSearch, JVXETypes.sel_search]
if (multiple.includes(this.$type)) {
// 处理多选
let props = this.originColumn.props || {}
props['mode'] = 'multiple'
props['maxTagCount'] = 1
this.$set(this.originColumn, 'props', props)
} else if (search.includes(this.$type)) {
// 处理搜索
this.$set(this.originColumn, 'allowSearch', true)
}
},
methods: {
/** 处理blur失去焦点事件 */
handleBlur(value) {
let {allowInput, options} = this.originColumn
if (allowInput === true) {
// 删除无用的因搜索(用户输入)而创建的项
if (typeof value === 'string') {
let indexes = []
options.forEach((option, index) => {
if (option.value.toLocaleString() === value.toLocaleString()) {
delete option.searchAdd
} else if (option.searchAdd === true) {
indexes.push(index)
}
})
// 翻转删除数组中的项
for (let index of indexes.reverse()) {
options.splice(index, 1)
}
}
}
this.handleBlurCommon(value)
},
/** 用于搜索下拉框中的内容 */
handleSelectFilterOption(input, option) {
let {allowSearch, allowInput} = this.originColumn
if (allowSearch === true || allowInput === true) {
//update-begin-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
//update-end-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
}
return true
},
/** select 搜索时的事件,用于动态添加options */
handleSearchSelect(value) {
let {allowSearch, allowInput, options} = this.originColumn
if (allowSearch !== true && allowInput === true) {
// 是否找到了对应的项,找不到则添加这一项
let flag = false
for (let option of options) {
if (option.value.toLocaleString() === value.toLocaleString()) {
flag = true
break
}
}
// !!value :不添加空值
if (!flag && !!value) {
// searchAdd 是否是通过搜索添加的
options.push({title: value, value: value, searchAdd: true})
}
}
},
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived: event => dispatchEvent(event, 'ant-select'),
},
translate: {enabled: true},
getValue(value) {
if (Array.isArray(value)) {
return value.join(',')
} else {
return value
}
},
setValue(value) {
let {column: {own: col}, params: {$table}} = this
// 判断是否是多选
if ((col.props || {})['mode'] === 'multiple') {
$table.$set(col.props, 'maxTagCount', 1)
}
if (value != null && value !== '') {
if (typeof value === 'string') {
return value === '' ? [] : value.split(',')
}
return value
} else {
return undefined
}
}
}
}
</script>
<style scoped>
</style>
@@ -0,0 +1,46 @@
import JVxeCellMixins from '@/components/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) {
let {slot} = this.renderOptions
if (slot) {
return h('div', {}, slot(this.slotProps))
} else {
return h('div')
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: {
editRender: false
},
}
}
// :isNotPass="notPassedIds.includes(col.key+row.id)"
@@ -0,0 +1,145 @@
import JVxeCellMixins from '@/components/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(';')) {
let rv = replaceValue(value)
this.innerTags = rv.split(';')
this.handleChangeCommon(rv)
}
},
},
},
methods: {
renderTags(h) {
let tags = []
for (let tag of this.innerTags) {
if (tag) {
let tagProps = {}
let tagStyle = {}
let setTagColor = this.originColumn.setTagColor
if (typeof setTagColor === 'function') {
/**
* 设置 tag 颜色
*
* @param event 包含的字段:
* event.tagValue 当前tag的值
* event.value 当前原始值
* event.row 当前行的所有值
* event.column 当前列的配置
* event.column.own 当前列的原始配置
* @return Array | String 可以返回一个数组,数据第一项是tag背景颜色,第二项是字体颜色。也可以返回一个字符串,即tag背景颜色
*/
let color = setTagColor({
tagValue: tag,
value: this.innerValue,
row: this.row,
column: this.column,
})
if (Array.isArray(color)) {
tagProps.color = color[0]
tagStyle.color = color[1]
} else if (color && typeof color === 'string') {
tagProps.color = color
}
}
tags.push(h('a-tag', {
props: tagProps,
style: tagStyle,
}, [tag]))
}
}
return tags
},
},
render(h) {
return h('div', {}, [
this.renderTags(h)
])
},
}
// tags 组件的输入框
export const TagsInputCell = {
name: 'JVxeTagsInputCell',
mixins: [JVxeCellMixins],
data() {
return {
innerTagValue: '',
}
},
watch: {
innerValue: {
immediate: true,
handler(value) {
if (value !== this.innerTagValue) {
this.handleInputChange(value)
}
},
},
},
methods: {
handleInputChange(value, event) {
this.innerTagValue = replaceValue(value, event)
this.handleChangeCommon(this.innerTagValue)
return this.innerTagValue
},
},
render(h) {
return h('a-input', {
props: {
value: this.innerValue,
...this.cellProps
},
on: {
change: (event) => {
let {target, target: {value}} = event
let newValue = this.handleInputChange(value, event)
if (newValue !== value) {
target.value = newValue
}
}
},
})
},
}
// 将值每隔两位加上一个分号
function replaceValue(value, event) {
if (value) {
// 首先去掉现有的分号
value = value.replace(/;/g, '')
// 然后再遍历添加分号
let rv = ''
let splitArr = value.split('')
let count = 0
splitArr.forEach((val, index) => {
rv += val
let position = index + 1
if (position % 2 === 0 && position < splitArr.length) {
count++
rv += ';'
}
})
if (event && count > 0) {
let {target, target: {selectionStart}} = event
target.selectionStart = selectionStart + count
target.selectionEnd = selectionStart + count
}
return rv
}
return ''
}
@@ -0,0 +1,34 @@
<template>
<j-input-pop
:value="innerValue"
:width="300"
:height="210"
v-bind="cellProps"
style="width: 100%;"
@change="handleChangeCommon"
/>
</template>
<script>
import JInputPop from '@/components/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(event, 'anticon-fullscreen'),
},
},
}
</script>
<style scoped>
</style>
@@ -0,0 +1,178 @@
<template>
<div>
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]">
<a-input
:key="fileKey"
:readOnly="true"
:value="file.name"
>
<template slot="addonBefore" style="width: 30px">
<a-tooltip v-if="file.status === 'uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
</a-tooltip>
<a-tooltip v-else-if="file.status === 'done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
<span v-if="file.status === 'uploading'" slot="addonAfter">{{ Math.floor(file.percent) }}%</span>
<template v-else-if="originColumn.allowDownload !== false || originColumn.allowRemove !== false" slot="addonAfter">
<a-dropdown :trigger="['click']" placement="bottomRight">
<a-tooltip title="操作">
<a-icon
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<!-- <a-menu-item @click="handleClickPreviewFile">-->
<!-- <span><a-icon type="eye"/>&nbsp;预览</span>-->
<!-- </a-menu-item>-->
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-input>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="originColumn.action"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '点击上传'}}</a-button>
</a-upload>
</div>
</template>
<script>
import JVxeCellMixins from '@/components/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() {
let {originColumn: col} = this
let headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
hasFile() {
return this.innerFile != null
},
},
watch: {
innerValue: {
immediate: true,
handler() {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
},
},
},
methods: {
handleChangeUpload(info) {
let {row, originColumn: col} = this
let {file} = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (col.responseName && file.response) {
value['responseName'] = file.response[col.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[col.responseName]
this.handleChangeCommon(value)
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.innerFile = value
},
// handleClickPreviewFile(id) {
// this.$message.info('尚未实现')
// },
handleClickDownloadFile(id) {
let {path} = this.value || {}
if (path) {
let url = getFileAccessHttpUrl(path)
window.open(url)
}
},
handleClickDeleteFile() {
this.handleChangeCommon(null)
},
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: {visible: true},
getValue: value => fileGetValue(value),
setValue: value => fileSetValue(value),
}
}
function fileGetValue(value) {
if (value && value.path) {
return value.path
}
return value
}
function fileSetValue(value) {
if (value) {
let first = value.split(',')[0]
let name = first.substring(first.lastIndexOf('/') + 1)
return {
name: name,
path: value,
status: 'done',
}
}
return value
}
</script>
<style scoped>
</style>
@@ -0,0 +1,136 @@
<template>
<div>
<a-input
v-show="!userIds"
@click="openSelect"
placeholder="请选择用户"
v-model="userNames"
readOnly
class="jvxe-select-input"
:disabled="componentDisabled">
<a-icon slot="prefix" type="user" title="用户选择控件"/>
</a-input>
<j-select-user-by-dep-modal
ref="selectModal"
:modal-width="modalWidth"
:multi="multi"
:user-ids="userIds"
@ok="selectOK"
@initComp="initComp"/>
<span style="display: inline-block;height:100%;padding-left:14px" v-if="userIds" >
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ userNames }}</span>
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
</span>
</div>
<!-- <j-select-user-by-dep
v-bind="custProps"
@change="handleChange"
:trigger-change="true">
</j-select-user-by-dep>-->
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/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)
if(this.cellProps.disabled==true){
return true
}
return false
},
modalWidth(){
if(this.cellProps.modalWidth){
return this.cellProps.modalWidth
}else{
return 1250
}
},
multi(){
if(this.cellProps.multi==false){
return false
}else{
return true
}
}
},
watch: {
innerValue: {
immediate: true,
handler(val) {
if (val == null || val === '') {
this.userIds = ''
} else {
this.userIds = val
}
}
}
},
methods: {
openSelect() {
this.$refs.selectModal.showModal()
},
selectOK(rows, idstr) {
console.log("当前选中用户", rows)
console.log("当前选中用户ID", idstr)
if (!rows) {
this.userNames = ''
this.userIds = ''
} else {
let temp = ''
for (let item of rows) {
temp += ',' + item.realname
}
this.userNames = temp.substring(1)
this.userIds = idstr
}
this.handleChangeCommon(this.userIds)
},
handleEmpty(){
this.selectOK('')
},
initComp(userNames) {
this.userNames = userNames
},
},
enhanced: {
switches: {
visible: true
},
translate: {
enabled: false
}
}
}
</script>
<style scoped>
/deep/ .jvxe-select-input .ant-input {
border: none !important;
}
</style>
@@ -0,0 +1,84 @@
import '../../less/reload-effect.less'
import { randomString } from '@/utils/util'
// 修改数据特效
export default {
props: {
vNode: null,
// 是否启用特效
effect: Boolean,
},
data() {
return {
// vNode: null,
innerEffect: false,
// 应付同时多个特效
effectIdx: 0,
effectList: [],
}
},
watch: {
vNode: {
deep: true,
immediate: true,
handler(vNode, old) {
this.innerEffect = this.effect
if (this.innerEffect && old != null) {
let topLayer = this.renderSpan(old, 'top')
this.effectList.push(topLayer)
}
},
},
},
methods: {
// 条件渲染内容 span
renderVNode() {
if (this.vNode == null) {
return null
}
let bottom = this.renderSpan(this.vNode, 'bottom')
// 启用了特效,并且有旧数据,就渲染特效顶层
if (this.innerEffect && this.effectList.length > 0) {
this.$emit('effect-begin')
// 1.4s 以后关闭特效
window.setTimeout(() => {
let item = this.effectList[this.effectIdx]
if (item && item.elm) {
// 特效结束后,展示先把 display 设为 none,而不是直接删掉该元素,
// 目的是为了防止页面重新渲染,导致动画重置
item.elm.style.display = 'none'
}
// 当所有的层级动画都结束时,再删掉所有元素
if (++this.effectIdx === this.effectList.length) {
this.innerEffect = false
this.effectIdx = 0
this.effectList = []
this.$emit('effect-end')
}
}, 1400)
return [this.effectList, bottom]
} else {
return bottom
}
},
// 渲染内容 span
renderSpan(vNode, layer) {
let options = {
key: layer + this.effectIdx + randomString(6),
class: ['j-vxe-reload-effect-span', `layer-${layer}`],
style: {},
}
if (layer === 'top') {
// 最新渲染的在下面
options.style['z-index'] = (9999 - this.effectIdx)
}
return this.$createElement('span', options, [vNode])
},
},
render(h) {
return h('div', {
class: ['j-vxe-reload-effect-box'],
}, [this.renderVNode()])
},
}
@@ -0,0 +1,51 @@
import * as jvxeTypes from './jvxeTypes'
import { installCell, mapCell } from './install'
import JVxeTable from './components/JVxeTable'
import JVxeSlotCell from './components/cells/JVxeSlotCell'
import JVxeNormalCell from './components/cells/JVxeNormalCell'
import JVxeInputCell from './components/cells/JVxeInputCell'
import JVxeDateCell from './components/cells/JVxeDateCell'
import JVxeSelectCell from './components/cells/JVxeSelectCell'
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell'
import JVxeUploadCell from './components/cells/JVxeUploadCell'
import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell'
import JVxeProgressCell from './components/cells/JVxeProgressCell'
import JVxeTextareaCell from './components/cells/JVxeTextareaCell'
import JVxeDragSortCell from './components/cells/JVxeDragSortCell'
import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell'
import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell'
//update--begin--autor:lvdandan-----date:20201216------forJVxeTable--JVXETypes 【online】代码结构调整,便于online打包
// 组件类型
export const JVXETypes = jvxeTypes.JVXETypes
//update--end--autor:lvdandan-----date:20201216------forJVxeTable--JVXETypes 【online】代码结构调整,便于online打包
// 注册自定义组件
export const AllCells = {
...mapCell(JVXETypes.normal, JVxeNormalCell),
...mapCell(JVXETypes.input, JVxeInputCell),
...mapCell(JVXETypes.inputNumber, JVxeInputCell),
...mapCell(JVXETypes.checkbox, JVxeCheckboxCell),
...mapCell(JVXETypes.select, JVxeSelectCell),
...mapCell(JVXETypes.selectSearch, JVxeSelectCell), // 下拉搜索
...mapCell(JVXETypes.selectMultiple, JVxeSelectCell), // 下拉多选
...mapCell(JVXETypes.date, JVxeDateCell),
...mapCell(JVXETypes.datetime, JVxeDateCell),
...mapCell(JVXETypes.upload, JVxeUploadCell),
...mapCell(JVXETypes.textarea, JVxeTextareaCell),
...mapCell(JVXETypes.tags, TagsInputCell, TagsSpanCell),
...mapCell(JVXETypes.progress, JVxeProgressCell),
...mapCell(JVXETypes.rowDragSort, JVxeDragSortCell),
...mapCell(JVXETypes.slot, JVxeSlotCell),
...mapCell(JVXETypes.departSelect, JVxeDepartSelectCell),
...mapCell(JVXETypes.userSelect, JVxeUserSelectCell)
/* hidden 是特殊的组件,不在这里注册 */
}
export { installCell, mapCell }
export default JVxeTable
@@ -0,0 +1,105 @@
import Vue from 'vue'
import { getEventPath } from '@/utils/util'
import JVxeTable, { AllCells, JVXETypes } from './index'
import './less/j-vxe-table.less'
// 引入 vxe-table
import 'xe-utils'
import VXETable, { Grid } from 'vxe-table'
import VXETablePluginAntd from 'vxe-table-plugin-antd'
import 'vxe-table/lib/index.css'
import 'vxe-table-plugin-antd/dist/style.css'
import { getEnhancedMixins, installAllCell, installOneCell } from '@/components/jero/JVxeTable/utils/cellUtils'
// VxeGrid所有的方法映射
const VxeGridMethodsMap = {}
Object.keys(Grid.methods).forEach(key => {
// 使用eval可以避免闭包(但是要注意不要写es6的代码)
VxeGridMethodsMap[key] = eval(`(function(){return this.$refs.vxe.${key}.apply(this.$refs.vxe,arguments)})`)
})
// 将Grid所有的方法都映射(继承)到JVxeTable上
JVxeTable.methods = Object.assign({}, VxeGridMethodsMap, JVxeTable.methods)
// VXETable 全局配置
const VXETableSettings = {
// z-index 起始值
zIndex: 1000,
table: {
validConfig: {
// 校验提示方式:强制使用tooltip
message: 'tooltip'
}
}
}
// 执行注册方法
Vue.use(VXETable, VXETableSettings)
VXETable.use(VXETablePluginAntd)
Vue.component(JVxeTable.name, JVxeTable)
// 注册自定义组件
installAllCell(VXETable)
// 添加事件拦截器 event.clearActived
// 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
VXETable.interceptor.add('event.clearActived', function (params, event, target) {
// 获取组件增强
let col = params.column.own
const interceptor = getEnhancedMixins(col.$type, 'interceptor')
// 执行增强
let flag = interceptor['event.clearActived'].apply(this, arguments)
if (flag === false) {
return false
}
let path = getEventPath(event)
for (let p of path) {
let className = p.className || ''
className = typeof className === 'string' ? className : className.toString()
/* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */
// 点击的标签是JInputPop
if (className.includes('j-input-pop')) {
return false
}
// 点击的标签是JPopup的弹出层、部门选择、用户选择
if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) {
return false
}
// 执行增强
let flag = interceptor['event.clearActived.className'].apply(this, [className, ...arguments])
if (flag === false) {
return false
}
}
})
/**
* 注册map
* @param type 类型
* @param cell 输入组件
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
*/
export function mapCell(type, cell, span) {
let cells = {[type]: cell}
if (span) {
cells[type + ':span'] = span
}
return cells
}
/**
* 注册自定义组件
*
* @param type 类型
* @param cell 输入组件
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
*/
export function installCell(type, cell, span) {
let exclude = [JVXETypes.rowNumber, JVXETypes.rowCheckbox, JVXETypes.rowRadio, JVXETypes.rowExpand, JVXETypes.rowDragSort]
if (exclude.includes(type)) {
throw new Error(`installCell不能使用"${type}"作为组件的type因为这是关键字`)
}
Object.assign(AllCells, mapCell(type, cell, span))
installOneCell(VXETable, type)
}
@@ -0,0 +1,44 @@
// 组件类型
export default JVXETypes
export const JVXETypes = {
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
// 前缀是自动加的,代码中直接用就行(JVXETypes.input
_prefix: 'j-',
// 行号列
rowNumber: 'row-number',
// 选择列
rowCheckbox: 'row-checkbox',
// 单选列
rowRadio: 'row-radio',
// 展开列
rowExpand: 'row-expand',
// 上下排序
rowDragSort: 'row-drag-sort',
input: 'input',
inputNumber: 'inputNumber',
textarea: 'textarea',
select: 'select',
date: 'date',
datetime: 'datetime',
checkbox: 'checkbox',
upload: 'upload',
// 下拉搜索
selectSearch: 'select-search',
// 下拉多选
selectMultiple: 'select-multiple',
// 进度条
progress: 'progress',
//部门选择
departSelect: 'sel_depart',
//用户选择
userSelect: 'sel_user',
// 拖轮Tags(暂无用)
tags: 'tags',
slot: 'slot',
normal: 'normal',
hidden: 'hidden',
}
@@ -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,305 @@
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.own
},
$type() {
return this.originColumn.$type
},
rows() {
return this.params.data
},
rowIndex() {
return this.params.rowIndex
},
columnIndex() {
return this.params.columnIndex
},
cellProps() {
let {originColumn: col, renderOptions} = this
let 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
// 验证值格式
let originValue = this.row[this.column.property]
let 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) {
this.innerValue = this.enhanced.translate.handler.call(this, value)
}
},
},
},
created() {
},
methods: {
/** 通用处理change事件 */
handleChangeCommon(value) {
let 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 = []) {
let 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.own.defaultValue
},
}
}
function getListeners() {
let 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) {
window.setTimeout(() => {
let element = cell.getElementsByClassName(className)
if (element && element.length > 0) {
if (typeof handler === 'function') {
handler(element[0])
} else {
// 模拟触发点击事件
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['domianURL'].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 {
let 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) {
let binds = map.get(key)
if (Array.isArray(binds)) {
binds.push(value)
} else {
map.set(key, [value])
}
},
/** 移除绑定 */
removeBind(map, key, value) {
let binds = map.get(key)
if (Array.isArray(binds)) {
for (let i = 0; i < binds.length; i++) {
let 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) {
let 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
}
let type = json[this.constants.TYPE]
let 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,129 @@
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]
}
// 添加渲染
VXETable.renderer.add(JVXETypes._prefix + type, {
// 可编辑模板
renderEdit: createRender(comp, enhanced, JVXERenderType.editer),
// 显示模板
renderCell: createRender(spanComp, enhanced, JVXERenderType.spaner),
// 增强注册
...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,
})
}
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) {
let 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))
}
let defEnhanced = JVxeCellMixins.enhanced
let enhanced = getEnhanced(type)
if (enhanced) {
Object.keys(defEnhanced).forEach(key => {
let def = defEnhanced[key]
if (enhanced.hasOwnProperty(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,190 @@
import { getVmParentByName } from '@/utils/util'
import { JVXETypes } from '@comp/jero/JVxeTable/index'
export const VALIDATE_FAILED = Symbol()
/**
* 获取指定的 $refs 对象
* 有时候可能会遇到组件未挂载到页面中的情况,导致无法获取 $refs 中的某个对象
* 这个方法可以等待挂载完成之后再返回 $refs 的对象,避免报错
* @author sunjianlei
**/
export function getRefPromise(vm, name) {
return new Promise((resolve) => {
(function next() {
let 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) => {
let val = rowValue[col.key], 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 | NULL}
*/
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)) {
// 高级查询所需要的参数
let fieldList = []
// 遍历列
for (let i = 0; i < columns.length; i++) {
let col = columns[i]
if (col.type === JVXETypes.rowCheckbox ||
col.type === JVXETypes.rowRadio ||
col.type === JVXETypes.rowExpand ||
col.type === JVXETypes.rowNumber
) {
continue
}
let 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, idx))
}
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 `form 参数需要的是一个form对象而传入的却是${typeof form}`
}
let dataMap = {}
let values = await new Promise((resolve, reject) => {
// 验证主表表单
form.validateFields((err, values) => {
err ? reject({error: VALIDATE_FAILED, originError: err}) : resolve(values)
})
})
Object.assign(dataMap, {formValue: values})
// 验证所有子表的表单
let 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 `'validateTables'函数的'cases'参数需要的是一个数组而传入的却是${typeof cases}`
}
return new Promise((resolve, reject) => {
let tablesData = []
let index = 0
if (!cases || cases.length === 0) {
resolve()
}
(function next() {
let 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
let tabPane = getVmParentByName(vm, 'ATabPane')
if (tabPane) {
paneKey = tabPane.$vnode.key
// 自动跳转到该表格
if (autoJumpTab) {
let tabs = getVmParentByName(tabPane, 'Tabs')
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey)
}
}
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
reject({error: VALIDATE_FAILED, index, paneKey, errMap})
}
})
})()
})
}
+512
View File
@@ -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/jeecg/JSuperQuery.vue';
export default {
name: "JeecgDemoList",
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>
+75
View File
@@ -0,0 +1,75 @@
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'
//jerobiz
import JSelectDepart from '../jerobiz/JSelectDepart.vue'
import JSelectMultiUser from '../jerobiz/JSelectMultiUser.vue'
import JSelectPosition from '../jerobiz/JSelectPosition.vue'
import JSelectRole from '../jerobiz/JSelectRole.vue'
import JSelectUserByDep from '../jerobiz/JSelectUserByDep.vue'
export default {
install(Vue) {
Vue.component('JMarkdownEditor', JMarkdownEditor)
Vue.component(JModal.name, JModal)
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('JSelectPosition', JSelectPosition)
Vue.component('JSelectRole', JSelectRole)
Vue.component('JSelectUserByDep', JSelectUserByDep)
}
}
@@ -0,0 +1,117 @@
<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){
let 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;
}
let arr = this.filePath.split(",")
let 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,105 @@
<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,928 @@
<template>
<a-modal
title="corn表达式"
: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 = '';
let 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 = '';
let 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 = '';
let 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='';
let 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 = '';
let 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 = '';
let 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 = '';
let 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));
// }
let 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) {
let val = seconds.split(" ")[0];
//alert(val);
let 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) {
let val = minutes.split(" ")[1];
let 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) {
let val = hours.split(" ")[2];
let 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) {
let days = cron.split(" ")[3];
let weeks = cron.split(" ")[5];
let day ={
cronEvery:'',
incrementStart:1,
incrementIncrement:1,
rangeStart:1,
rangeEnd:1,
specificSpecific:[],
cronLastSpecificDomDay:1,
cronDaysBeforeEomMinus:1,
cronDaysNearestWeekday:1,
};
let 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) {
let months = cron.split(" ")[4];
let 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) {
let years = cron.split(" ")[6];
let 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: 0px!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,395 @@
<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>
已选择&nbsp;<a style="font-weight: 600">{{ table.selectedRowKeys.length }}</a>项&nbsp;&nbsp;
<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', '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:{}
}
},
mounted() {
//this.loadColumnsInfo()
},
watch: {
code() {
this.loadColumnsInfo()
},
param:{
deep:true,
handler(){
this.dynamicParamHandler()
this.loadData();
},
}
},
computed:{
showSearchFlag(){
return this.queryInfo && this.queryInfo.length>0
}
},
methods:{
loadColumnsInfo(){
let 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
let currColumns = res.result.columns
for(let a=0;a<currColumns.length;a++){
if(currColumns[a].customRender){
let dictCode = currColumns[a].customRender;
currColumns[a].customRender=(text)=>{
return filterMultiDictText(this.dictOptions[dictCode], text+"");
}
}
}
this.table.columns = [...currColumns]
this.initQueryInfo()
}
})
},
initQueryInfo() {
let 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为空对象
let queryTemp = {}
for(let item of arr){
if(item.mode==='single'){
queryTemp[item.field] = ''
}
}
this.queryParam = {...queryTemp}
}
let 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
}
let params = this.getQueryParams();//查询条件
this.table.loading = true
let 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)
let 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() {
let paramTarget = {}
if(this.dynamicParam){
//处理自定义参数
Object.keys(this.dynamicParam).map(key=>{
paramTarget['self_'+key] = this.dynamicParam[key]
})
}
let param = Object.assign(paramTarget, this.queryParam, this.sorter);
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{
//当两者长度不一的时候 需要判断
let keys = this.table.selectedRowKeys
let rows = this.table.selectionRows;
//这个循环 添加新的记录
for(let i=0;i<selectionRows.length;i++){
let combineKey = this.combineRowKey(selectionRows[i])
if(keys.indexOf(combineKey)<0){
//如果 原来的key 不包含当前记录 push
rows.push(selectionRows[i])
}
}
//这个循环 移除取消选中的数据
this.table.selectionRows = rows.filter(item=>{
let 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.sorter.column = sorter.field
this.sorter.order = 'ascend' == sorter.order ? 'asc' : 'desc'
}
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=>{
res+=record[key]
})
if(res.length>50){
res = res.substring(0,50)
}
return res
},
clickThenCheck(record){
return {
on: {
click: () => {
let rowKey = this.combineRowKey(record)
if(!this.table.selectedRowKeys || this.table.selectedRowKeys.length==0){
let arr1=[],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{
let rowKey_index = this.table.selectedRowKeys.indexOf(rowKey)
this.table.selectedRowKeys.splice(rowKey_index,1);
this.table.selectionRows.splice(rowKey_index,1);
}
}
}
}
}
},
//防止字典中有垃圾数据
initDictOptionData(dictOptions){
let obj = { }
Object.keys(dictOptions).map(k=>{
obj[k] = dictOptions[k].filter(item=>{
return item!=null
});
});
this.dictOptions = obj
}
}
}
</script>
<style scoped>
</style>