dbsd_kczx_java/ant-design-vue-jeecg/src/components/jeecg/JEditableTable.vue

3098 lines
111 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- JEditableTable -->
<!-- @version 1.6.1 -->
<!-- @author sjlei -->
<template>
<a-spin :spinning="loading">
<a-row type="flex">
<a-col>
<slot name="buttonBefore" :target="getVM()"/>
</a-col>
<a-col>
<!-- 操作按钮 -->
<div v-if="actionButton" class="action-button">
<a-button type="primary" icon="plus" @click="handleClickAdd" :disabled="disabled">新增</a-button>
<span class="gap"></span>
<template v-if="selectedRowIds.length>0">
<a-popconfirm
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
@confirm="handleConfirmDelete">
<a-button type="primary" icon="minus" :disabled="disabled">删除</a-button>
<span class="gap"></span>
</a-popconfirm>
<template v-if="showClearSelectButton">
<a-button icon="delete" @click="handleClickClearSelection">清空选择</a-button>
<span class="gap"></span>
</template>
</template>
</div>
</a-col>
<a-col>
<slot name="buttonAfter" :target="getVM()"/>
</a-col>
</a-row>
<slot name="actionButtonAfter" :target="getVM()"/>
<div :id="`${caseId}inputTable`" class="input-table">
<!-- 渲染表头 -->
<div class="thead" ref="thead">
<div class="tr" :style="{width: this.realTrWidth}">
<!-- 左侧固定td -->
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft">
<span></span>
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
<!--:indeterminate="true"-->
<a-checkbox
:checked="getSelectAll"
:indeterminate="getSelectIndeterminate"
@change="handleChangeCheckedAll"
/>
</div>
<div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
<span>#</span>
</div>
<!-- 右侧动态生成td -->
<template v-for="col in columns">
<div
v-show="col.type !== formTypes.hidden"
class="td"
:key="col.key"
:style="buildTdStyle(col,true)">
<span>{{ col.title }}</span>
</div>
</template>
</div>
</div>
<div class="scroll-view" ref="scrollView" :style="{'max-height':maxHeight+'px'}">
<!-- 渲染主体 body -->
<div :id="`${caseId}tbody`" class="tbody" :style="tbodyStyle">
<!-- 扩展高度 -->
<div class="tr-expand" :style="`height:${getExpandHeight}px; z-index:${loading?'11':'9'};`"></div>
<!-- 无数据时显示 -->
<div v-if="rows.length===0" class="tr-nodata">
<span>暂无数据</span>
</div>
<!-- v-model="rows"-->
<draggable
:value="rows"
handle=".td-ds-icons"
@start="handleDragMoveStart"
@end="handleDragMoveEnd"
>
<!-- 动态生成tr -->
<template v-for="(row,rowIndex) in rows">
<!-- tr 只加载可见的和预加载的总共十条数据 -->
<div
v-if="
rowIndex >= parseInt(`${(scrollTop-rowHeight) / rowHeight}`) &&
(parseInt(`${scrollTop / rowHeight}`) + 9) > rowIndex
"
:id="`${caseId}tbody-tr-${rowIndex}`"
:data-idx="rowIndex"
class="tr"
:class="selectedRowIds.indexOf(row.id) !== -1 ? 'tr-checked' : ''"
:style="buildTrStyle(rowIndex)"
:key="row.id"
@click="handleClickTableRow"
>
<!-- 左侧固定td -->
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft" @dblclick="_handleRowInsertDown(rowIndex)" >
<a-dropdown :trigger="['click']" :getPopupContainer="getParentContainer">
<div class="td-ds-icons">
<a-icon type="align-left"/>
<a-icon type="align-right"/>
</div>
<a-menu slot="overlay">
<a-menu-item key="0" :disabled="rowIndex===0" @click="_handleRowMoveUp(rowIndex)">向上移</a-menu-item>
<a-menu-item key="1" :disabled="rowIndex===(rows.length-1)" @click="_handleRowMoveDown(rowIndex)">向下移</a-menu-item>
<a-menu-divider/>
<a-menu-item key="3" @click="_handleRowInsertDown(rowIndex)">插入一行</a-menu-item>
</a-menu>
</a-dropdown>
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
<!-- 此 v-for 只是为了拼接 id 字符串 -->
<template v-for="(id,i) in [`${row.id}`]">
<a-checkbox
:id="id"
:key="i"
:checked="selectedRowIds.indexOf(id) !== -1"
@change="handleChangeLeftCheckbox"/>
</template>
</div>
<div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
<span>{{ rowIndex+1 }}</span>
</div>
<!-- 右侧动态生成td -->
<div
class="td"
v-for="col in columns"
v-show="col.type !== formTypes.hidden"
:key="col.key"
:style="buildTdStyle(col)">
<!-- 此 v-for 只是为了拼接 id 字符串 -->
<template v-for="(id,i) in [`${col.key}${row.id}`]">
<!-- native input -->
<label :key="i" v-if="col.type === formTypes.input || col.type === formTypes.inputNumber">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<input
v-if="isEditRow(row, col)"
:id="id"
v-bind="buildProps(row,col)"
:data-input-number="col.type === formTypes.inputNumber"
:placeholder="replaceProps(col, col.placeholder)"
@blur="(e)=>{handleBlurCommono(e.target,rowIndex,row,col)}"
@input="(e)=>{handleInputCommono(e.target,rowIndex,row,col)}"
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ inputValues[rowIndex][col.key] }}</span>
</a-tooltip>
</label>
<!-- checkbox -->
<template v-else-if="col.type === formTypes.checkbox">
<a-checkbox
:key="i"
:id="id"
v-bind="buildProps(row,col)"
:checked="checkboxValues[id]"
@change="(e)=>handleChangeCheckboxCommon(e,row,col)"
/>
</template>
<!-- select -->
<template v-else-if="col.type === formTypes.select">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-select
v-if="isEditRow(row, col)"
:id="id"
:key="i"
v-bind="buildProps(row,col)"
style="width: 100%;"
:value="selectValues[id]"
:options="col.options"
:getPopupContainer="getParentContainer"
:placeholder="replaceProps(col, col.placeholder)"
:filterOption="(i,o)=>handleSelectFilterOption(i,o,col)"
@change="(v)=>handleChangeSelectCommon(v,id,row,col)"
@search="(v)=>handleSearchSelect(v,id,row,col)"
@blur="(v)=>handleBlurSearch(v,id,row,col)"
allowClear
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click.stop="handleEditRow(row, col)"
>{{ getSelectTranslateText(selectValues[id], row, col) }}</span>
</a-tooltip>
</template>
<!-- date -->
<template v-else-if="col.type === formTypes.date || col.type === formTypes.datetime">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<j-date
v-if="isEditRow(row, col)"
:id="id"
:key="i"
v-bind="buildProps(row,col)"
style="width: 100%;"
:value="jdateValues[id]"
:getCalendarContainer="getParentContainer"
:placeholder="replaceProps(col, col.placeholder)"
:trigger-change="true"
:showTime="col.type === formTypes.datetime"
:dateFormat="col.type === formTypes.date? 'YYYY-MM-DD':'YYYY-MM-DD HH:mm:ss'"
allowClear
@change="(v)=>handleChangeJDateCommon(v,id,row,col,col.type === formTypes.datetime)"
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ jdateValues[id] }}</span>
</a-tooltip>
</template>
<!-- input_pop -->
<template v-else-if="col.type === formTypes.input_pop">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<j-input-pop
v-if="isEditRow(row, col)"
:id="id"
:key="i"
:width="300"
:height="210"
:pop-container="`${caseId}tbody`"
v-bind="buildProps(row,col)"
style="width: 100%;"
:value="jInputPopValues[id]"
:getCalendarContainer="getParentContainer"
:placeholder="replaceProps(col, col.placeholder)"
@change="(v)=>handleChangeJInputPopCommon(v,id,row,col)"
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ jInputPopValues[id] }}</span>
</a-tooltip>
</template>
<!-- upload -->
<div v-else-if="col.type === formTypes.upload" :key="i">
<template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<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>
<template v-if="col.allowDownload!==false || col.allowRemove!==false" slot="addonAfter" style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer">
<a-tooltip title="操作" :getPopupContainer="getParentContainer">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownloadFile(id)">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="col.allowRemove!==false" @click="handleClickDelFile(id)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-input>
</template>
<div :hidden="uploadValues[id] != null">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-upload
name="file"
:data="{'isup':1}"
:multiple="false"
:action="col.action"
:headers="uploadGetHeaders(row,col)"
:showUploadList="false"
v-bind="buildProps(row,col)"
@change="(v)=>handleChangeUpload(v,id,row,col)"
>
<a-button icon="upload">{{ col.placeholder }}</a-button>
</a-upload>
</a-tooltip>
</div>
</div>
<!-- update-begin-author:taoyan date:0827 forpopup -->
<template v-else-if="col.type === formTypes.popup">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<j-popup
v-if="isEditRow(row, col)"
:id="id"
:key="i"
v-bind="buildProps(row,col)"
:placeholder="replaceProps(col, col.placeholder)"
style="width: 100%;"
:value="getPopupValue(id)"
:field="col.field || col.key"
:org-fields="col.orgFields"
:dest-fields="col.destFields"
:code="col.popupCode"
:groupId="caseId"
@input="(value,others)=>popupCallback(value,others,id,row,col,rowIndex)"
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ getPopupValue(id) }}</span>
</a-tooltip>
</template>
<!-- update-end-author:taoyan date:0827 forpopup -->
<!-- update-beign-author:taoyan date:0827 for文件/图片逻辑新增 -->
<div v-else-if="col.type === formTypes.file" :key="i">
<template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<div :key="fileKey" style="position: relative;">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading" style="color:red;"/>
<span style="color:red;margin-left:5px">{{ file.status }}</span>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" :title="file.name">
<a-icon type="paper-clip" />
<span style="margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
</a-tooltip>
<a-tooltip v-else :title="file.name">
<a-icon type="paper-clip" style="color:red;"/>
<span style="color:red;margin-left:5px">{{ getEllipsisWord(file.name,5) }}</span>
</a-tooltip>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
<a-tooltip title="操作" :getPopupContainer="getParentContainer">
<a-icon v-if="file.status!=='uploading'" type="setting" style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item @click="handleClickDelFile(id)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(id,col,col)">
<span><a-icon type="bars" /> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
<div :hidden="uploadValues[id] != null">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-upload
name="file"
:data="{'isup':1}"
:multiple="false"
:action="getUploadAction(col.action)"
:headers="uploadGetHeaders(row,col)"
:showUploadList="false"
v-bind="buildProps(row,col)"
@change="(v)=>handleChangeUpload(v,id,row,col)"
>
<a-button icon="upload">上传文件</a-button>
</a-upload>
</a-tooltip>
</div>
</div>
<div v-else-if="col.type === formTypes.image" :key="i">
<template v-if="uploadValues[id] != null" v-for="(file,fileKey) of [(uploadValues[id]||{})]">
<div :key="fileKey" style="position: relative;">
<template v-if="!uploadValues[id] || !(uploadValues[id]['url'] || uploadValues[id]['path'] || uploadValues[id]['message'])">
<a-icon type="loading"/>
</template>
<template v-else-if="uploadValues[id]['path']">
<img class="j-editable-image" :src="getCellImageView(id)" alt="无图片" @click="handleMoreOperation(id,'img',col)"/>
</template>
<template v-else>
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError(id)"/>
</template>
<template slot="addonBefore" style="width: 30px">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;"/>
</a-tooltip>
<a-tooltip v-else title="上传失败">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
</template>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" :getPopupContainer="getParentContainer" style="margin-left: 10px;">
<a-tooltip title="操作" :getPopupContainer="getParentContainer">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="col.allowDownload!==false" @click="handleClickDownFileByUrl(id)">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item @click="handleClickDelFile(id)">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(id,'img',col)">
<span><a-icon type="bars" /> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
<div :hidden="uploadValues[id] != null">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-upload
name="file"
:data="{'isup':1}"
:multiple="false"
:action="getUploadAction(col.action)"
:headers="uploadGetHeaders(row,col)"
:showUploadList="false"
v-bind="buildProps(row,col)"
@change="(v)=>handleChangeUpload(v,id,row,col)"
>
<a-button icon="upload">上传图片</a-button>
</a-upload>
</a-tooltip>
</div>
</div>
<!-- update-end-author:taoyan date:0827 for图片逻辑新增 -->
<!-- radio-begin -->
<template v-else-if="col.type === formTypes.radio">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-radio-group
:id="id"
:key="i"
v-bind="buildProps(row,col)"
:value="radioValues[id]"
@change="(e)=>handleRadioChange(e.target.value,id,row,col)">
<a-radio v-for="(item, key) in col.options" :key="key" :value="item.value">{{ item.text }}</a-radio>
</a-radio-group>
</a-tooltip>
</template>
<!-- radio-end -->
<!-- select多选 -begin -->
<template v-else-if="col.type === formTypes.list_multi">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-select
v-if="isEditRow(row, col)"
:id="id"
:key="i"
mode="multiple"
:maxTagCount="1"
v-bind="buildProps(row,col)"
style="width: 100%;"
:value="multiSelectValues[id]"
:options="col.options"
:getPopupContainer="getParentContainer"
:placeholder="replaceProps(col, col.placeholder)"
@change="(v)=>handleMultiSelectChange(v,id,row,col)"
allowClear
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ getSelectTranslateText(multiSelectValues[id], row, col) }} </span>
</a-tooltip>
</template>
<!-- select多选 -end -->
<!-- select搜索 -begin -->
<template v-else-if="col.type === formTypes.sel_search">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<a-select
v-if="isEditRow(row, col)"
:id="id"
:key="i"
showSearch
optionFilterProp="children"
:filterOption="filterOption"
v-bind="buildProps(row,col)"
style="width: 100%;"
:value="searchSelectValues[id]"
:options="col.options"
:getPopupContainer="getParentContainer"
:placeholder="replaceProps(col, col.placeholder)"
@change="(v)=>handleSearchSelectChange(v,id,row,col)"
allowClear
/>
<span
v-else
class="j-td-span no-edit"
:class="{disabled: buildProps(row,col).disabled}"
@click="handleEditRow(row, col)"
>{{ getSelectTranslateText(searchSelectValues[id], row, col) }}</span>
</a-tooltip>
</template>
<!-- select搜索 -end -->
<div v-else-if="col.type === formTypes.slot" :key="i">
<a-tooltip v-bind="buildTooltipProps(row, col, id)">
<slot
:name="(col.slot || col.slotName) || col.key"
:index="rowIndex"
:text="slotValues[id]"
:value="slotValues[id]"
:column="col"
:rowId="getCleanId(row.id)"
:getValue="()=>_getValueForSlot(row.id)"
:caseId="caseId"
:allValues="_getAllValuesForSlot()"
:target="getVM()"
:handleChange="(v)=>handleChangeSlotCommon(v,id,row,col)"
:isNotPass="notPassedIds.includes(col.key+row.id)"
/>
</a-tooltip>
</div>
<!-- else (normal) -->
<span v-else :key="i" v-bind="buildProps(row,col)">{{ inputValues[rowIndex][col.key] }}</span>
</template>
</div>
</div>
<!-- -- tr end -- -->
</template>
</draggable>
<!-- 统计行 -->
<div
v-if="showStatisticsRow"
class="tr"
:style="{
...buildTrStyle(rows.length),
height: '32px'
}"
>
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft">
</div>
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
统计
</div>
<div v-if="rowNumber" class="td td-num" :style="style.tdLeft">
<span v-if="!rowSelection">统计</span>
</div>
<!-- 右侧动态生成td -->
<template v-for="col in columns">
<div
:key="col.key"
class="td"
v-show="col.type !== formTypes.hidden"
:style="buildTdStyle(col)"
>
<span
v-show="col.type === formTypes.inputNumber"
style="padding: 0 5px;"
>{{statisticsColumns[col.key]}}</span>
</div>
</template>
</div>
</div>
</div>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"></j-file-pop>
</div>
</a-spin>
</template>
<script>
import Vue from 'vue'
import Draggable from 'vuedraggable'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { FormTypes, VALIDATE_NO_PASSED } from '@/utils/JEditableTableUtil'
import { cloneObject, randomString, randomNumber, getEventPath } from '@/utils/util'
import JDate from '@/components/jeecg/JDate'
import { filterDictText, initDictOptions } from '@/components/dict/JDictSelectUtil'
import { getFileAccessHttpUrl } from '@/api/manage';
import JInputPop from '@/components/jeecg/minipop/JInputPop'
import JFilePop from '@/components/jeecg/minipop/JFilePop'
// 行高,需要在实例加载完成前用到
let rowHeight = 61
export default {
name: 'JEditableTable',
components: { JDate, Draggable, JInputPop, JFilePop },
provide() {
return {
parentIsJEditableTable: true,
getDestroyCleanGroupRequest: () => this.destroyCleanGroupRequest,
}
},
props: {
// 列信息
columns: {
type: Array,
required: true
},
// 数据源
dataSource: {
type: Array,
required: true,
default: () => []
},
// 是否显示操作按钮
actionButton: {
type: Boolean,
default: false
},
// 是否显示行号
rowNumber: {
type: Boolean,
default: false
},
// 是否可选择行
rowSelection: {
type: Boolean,
default: false
},
// 页面是否在加载中
loading: {
type: Boolean,
default: false
},
// 表格内容区域最大高度
maxHeight: {
type: Number,
default: 400
},
// 要禁用的行
disabledRows: {
type: Object,
default() {
return {}
}
},
// 是否禁用全部组件
disabled: {
type: Boolean,
default: false
},
// 是否可拖拽排序
dragSort: {
type: Boolean,
default: false
},
dragSortKey: {
type: String,
default: 'orderNum'
},
// 是否一直显示编辑框如果为false则只有点击的时候才出现输入框
alwaysEdit: {
type: Boolean,
default: true
},
},
data() {
return {
// 是否首次运行
isFirst: true,
// 当前实例是否是行编辑
isJEditableTable: true,
// caseId用于防止有多个实例的时候会冲突
caseId: `_jet-${randomString(6)}-`,
// 临时ID标识凡是以该标识结尾的ID都是临时ID不添加到数据库中
tempId: `_tid-${randomString(6)}`,
// 存储document element 对象
el: {
inputTable: null,
tbody: null
},
// 存储各个div的style
style: {
// 'max-height': '400px'
tbody: { left: '0px' },
// 左侧固定td的style
tdLeft: {},
},
// 表单的类型
formTypes: FormTypes,
// 行数据
rows: [],
// 行高height + padding + border
rowHeight,
// 滚动条顶部距离
scrollTop: 0,
// 绑定 select 的值
selectValues: {},
// 绑定 checkbox 的值
checkboxValues: {},
// 绑定 jdate 的值
jdateValues: {},
// 绑定jinputpop
jInputPopValues:{},
// 绑定插槽数据
slotValues: {},
// file 信息
uploadValues: {},
//popup信息
popupValues: {},
radioValues: {},
metaCheckboxValues: {},
multiSelectValues: {},
searchSelectValues: {},
// 绑定左侧选择框已选择的id
selectedRowIds: [],
// 存储被删除行的id
deleteIds: [],
// 存储显示tooltip的信息
tooltips: {},
// 存储没有通过验证的inputId
notPassedIds: [],
// 当前是否正在拖拽排序
dragging: false,
// 是否有统计列
hasStatisticsColumn: false,
statisticsColumns: {},
// 只有在行编辑被销毁时才主动清空GroupRequest的内存
destroyCleanGroupRequest: false,
// 当前正在编辑的行的id
currentEditRows: {},
// 上次push数据的事件用于判断是否点击过快
lastPushTimeMap: new Map(),
number:0,
}
},
created() {
this.inputValues = []
// 当前显示的tr
this.visibleTrEls = []
this.disabledRowIds = (this.disabledRowIds || [])
// 解决火狐浏览器下拖拽会打开新的Tab的问题
document.body.ondrop = (event) => {
if (this.dragging) {
event.preventDefault()
event.stopPropagation()
}
}
},
// 计算属性
computed: {
// expandHeight = rows.length * rowHeight
getExpandHeight() {
let length = this.rows.length * this.rowHeight
if (this.showStatisticsRow) {
length += 34
}
return length
},
// 是否显示统计行
showStatisticsRow() {
return this.hasStatisticsColumn && this.rows.length > 0
},
// 获取是否选择了部分
getSelectIndeterminate() {
return (this.selectedRowIds.length > 0 &&
this.selectedRowIds.length < this.rows.length)
},
// 获取是否选择了全部
getSelectAll() {
return (this.selectedRowIds.length === this.rows.length) && this.rows.length > 0
},
tbodyStyle() {
let style = Object.assign({}, this.style.tbody)
// style['max-height'] = `${this.maxHeight}px`
style['width'] = this.realTrWidth
return style
},
showClearSelectButton() {
let count = 0
for (let key in this.disabledRows) {
if (this.disabledRows.hasOwnProperty(key)) count++
}
return count > 0
},
accessToken() {
return Vue.ls.get(ACCESS_TOKEN)
},
realTrWidth() {
let splice = ' + '
let calcWidth = 'calc('
this.columns.forEach((column, i) => {
let { type, width } = column
// 隐藏字段不参与计算
if (type !== FormTypes.hidden) {
if (typeof width === 'number') {
calcWidth += width + 'px'
} else if (typeof width === 'string') {
calcWidth += width
} else {
calcWidth += '120px'
}
calcWidth += splice
}
})
if (calcWidth.endsWith(splice)) {
calcWidth = calcWidth.substring(0, calcWidth.length - splice.length)
}
calcWidth += ')'
// console.log('calcWidth: ', calcWidth)
return calcWidth
}
},
// 侦听器
watch: {
rows: {
immediate: true,
handler(val, old) {
// val.forEach(item => {
// for (let inputValue of this.inputValues) {
// if (inputValue.id === item.id) {
// item['dbFieldName'] = inputValue['dbFieldName']
// break
// }
// }
// })
// console.log('watch.rows:', cloneObject({ val, old }))
}
},
dataSource: {
immediate: true,
handler: function (newValue) {
// 兼容IE
this.getElementPromise('tbody').then(() => {
this.initialize()
this._pushByDataSource(newValue)
})
}
},
columns: {
immediate: true,
handler(columns) {
// 兼容IE
this.getElementPromise('tbody').then(() => {
columns.forEach(column => {
if (column.type === FormTypes.select || column.type === FormTypes.list_multi || column.type === FormTypes.sel_search) {
// 兼容 旧版本 options
if (column.options instanceof Array) {
column.options = column.options.map(item => {
if (item) {
return {
...item,
text: item.text || item.title,
title: item.text || item.title
}
}
return {}
})
}
if (column.dictCode) {
this._loadDictConcatToOptions(column)
}
}
})
})
}
},
// 当selectRowIds改变时触发事件
selectedRowIds(newValue) {
this.$emit('selectRowChange', cloneObject(newValue).map(i => this.getCleanId(i)))
}
},
mounted() {
let vm = this
/** 监听滚动条事件 */
this.getElement('inputTable').onscroll = function (event) {
vm.syncScrollBar(event.target.scrollLeft)
}
this.getElement('tbody').onscroll = function (event) {
// vm.recalcTrHiddenItem(event.target.scrollTop)
}
let { thead, scrollView } = this.$refs
scrollView.onscroll = function (event) {
// console.log(event.target.scrollTop, ' - ', event.target.scrollLeft)
thead.scrollLeft = event.target.scrollLeft
vm.recalcTrHiddenItem(event.target.scrollTop)
}
// 添加事件监听
this.addEventListener()
},
methods: {
getElement(id, noCaseId = false) {
if (!this.el[id]) {
this.el[id] = document.getElementById((noCaseId ? '' : this.caseId) + id)
}
return this.el[id]
},
getElementPromise(id, noCaseId = false) {
return new Promise((resolve) => {
let timer = setInterval(() => {
let element = this.getElement(id, noCaseId)
if (element) {
clearInterval(timer)
resolve(element)
}
}, 10)
})
},
/** 初始化列表 */
initialize() {
this.visibleTrEls = []
// 判断是否是首次进入该方法,如果是就不清空行,防止删除了预添加的数据
if (!this.isFirst) {
// inputValues用来存储input表单的值
// 数组里的每项都是一个对象对象里每个key都是input的rowKey值就是input的值其中有个id的字段来区分
// 示例:
// [{
// id: "_jet-4sp0iu-15541771111770"
// dbDefaultVal: "aaa",
// dbFieldName: "bbb",
// dbFieldTxt: "ccc",
// dbLength: 32
// }]
this.inputValues = []
this.rows = []
this.deleteIds = []
this.selectValues = {}
this.checkboxValues = {}
this.jdateValues = {}
this.jInputPopValues = {}
this.slotValues = {}
this.selectedRowIds = []
this.tooltips = {}
this.notPassedIds = []
this.uploadValues = []
this.popupValues = []
this.radioValues = []
this.multiSelectValues = []
this.searchSelectValues = []
this.scrollTop = 0
this.$nextTick(() => {
this.getElement('tbody').scrollTop = 0
})
} else {
this.isFirst = false
}
},
/** 同步滚动条状态 */
syncScrollBar(scrollLeft) {
// this.style.tbody.left = `${scrollLeft}px`
// this.getElement('tbody').scrollLeft = scrollLeft
},
/** 重置滚动条位置,参数留空则滚动到上次记录的位置 */
resetScrollTop(top) {
let { scrollView } = this.$refs
if (top != null && typeof top === 'number') {
scrollView.scrollTop = top
} else {
scrollView.scrollTop = this.scrollTop
}
},
/** 重新计算需要隐藏或显示的tr */
recalcTrHiddenItem(top) {
let diff = top - this.scrollTop
if (diff < 0) {
diff = this.scrollTop - top
}
// 只有在滚动了百分之三十的行高的距离时才进行更新
if (diff >= this.rowHeight * 0.3) {
this.scrollTop = top
// 更新form表单的值
this.$nextTick(() => {
this.updateFormValues()
})
}
},
/** 生成id */
generateId(rows) {
if (!(rows instanceof Array)) {
rows = this.rows || []
}
let timestamp = new Date().getTime()
return `${this.caseId}${timestamp}${rows.length}${randomNumber(6)}${this.tempId}`
},
/** push 一条数据 */
push(record, update = true, rows, insertIndex = null, setDefaultValue = true) {
return this._pushByDataSource([record], [insertIndex], update, rows, setDefaultValue)
},
/**
* push 数据
*
* @param dataSource 数据源
* @param insertIndexes 行插入位置和dataSource的下标一一对应
* @param update 是否更新
* @param rows 若不传就使用 this.rows
* @param setDefaultValue 是否填充默认值
*
*/
_pushByDataSource(dataSource, insertIndexes = null, update = true, rows = null, setDefaultValue = false) {
if (!(rows instanceof Array)) {
rows = [...this.rows] || []
}
let checkboxValues = { ...this.checkboxValues }
let selectValues = { ...this.selectValues }
let jdateValues = { ...this.jdateValues }
let jInputPopValues = { ...this.jInputPopValues }
let slotValues = { ...this.slotValues }
let uploadValues = { ...this.uploadValues }
let popupValues = { ...this.popupValues }
let radioValues = { ...this.radioValues }
let multiSelectValues = { ...this.multiSelectValues }
let searchSelectValues = { ...this.searchSelectValues }
// 禁用行的id
let disabledRowIds = (this.disabledRowIds || [])
dataSource.forEach((data, newValueIndex) => {
// 不能直接更改数据源的id
let dataId = data.id
// 判断源数据是否带有id
if (dataId == null || dataId === '') {
dataId = this.generateId(rows)
} else if(!this.hasCaseId(dataId)) {
dataId = this.caseId + dataId
}
let row = { id: dataId }
let value = { id: dataId }
let disabled = false
this.columns.forEach(column => {
let inputId = column.key + value.id
let sourceValue = (data[column.key] == null ? '' : data[column.key]).toString()
let defaultValue = null;
if (setDefaultValue) {
defaultValue = column.defaultValue || (column.defaultValue === 0 ? 0 : '')
if (defaultValue instanceof Array) {
defaultValue = defaultValue.join(',')
}
sourceValue = (typeof sourceValue === 'number' || sourceValue) ? sourceValue : defaultValue
}
let sourceValueIsEmpty = (sourceValue == null || sourceValue === '')
if (column.type === FormTypes.inputNumber) {
// 判断是否是排序字段,如果是就赋最大值
if (column.isOrder === true) {
value[column.key] = this.getInputNumberMaxValue(column) + 1
} else {
value[column.key] = sourceValue
}
// 判断是否是统计列
if (column.statistics) {
this.hasStatisticsColumn = true
if (!this.statisticsColumns[column.key]) {
this.$set(this.statisticsColumns, column.key, 0)
}
}
} else if (column.type === FormTypes.checkbox) {
// 判断是否设定了customValue自定义值
if (column.customValue instanceof Array) {
let customValue = (column.customValue[0] || '').toString()
if (sourceValueIsEmpty && setDefaultValue) {
sourceValue = column.defaultChecked ? customValue : sourceValue
}
checkboxValues[inputId] = (sourceValue === customValue)
} else {
if (sourceValueIsEmpty && setDefaultValue) {
checkboxValues[inputId] = !!column.defaultChecked
} else {
checkboxValues[inputId] = sourceValue
}
}
} else if (column.type === FormTypes.select) {
if (!sourceValueIsEmpty) {
// 判断是否是多选
if (typeof sourceValue === 'string' && (column.props || {})['mode'] === 'multiple') {
sourceValue = sourceValue === '' ? [] : sourceValue.split(',')
}
selectValues[inputId] = sourceValue
} else {
selectValues[inputId] = undefined
}
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
jdateValues[inputId] = sourceValue
} else if (column.type === FormTypes.slot) {
slotValues[inputId] = sourceValue
} else if (column.type === FormTypes.popup) {
popupValues[inputId] = sourceValue
} else if (column.type === FormTypes.input_pop) {
jInputPopValues[inputId] = sourceValue
} else if (column.type === FormTypes.radio) {
radioValues[inputId] = sourceValue
} else if (column.type === FormTypes.sel_search) {
searchSelectValues[inputId] = sourceValue
} else if (column.type === FormTypes.list_multi) {
if (typeof sourceValue === 'string' && sourceValue.length > 0) {
multiSelectValues[inputId] = sourceValue.split(',')
} else {
multiSelectValues[inputId] = []
}
} else if (column.type === FormTypes.upload || column.type === FormTypes.file || column.type === FormTypes.image) {
if (sourceValue) {
let fileName = ''
if (sourceValue.indexOf(',') > 0) {
let sourceValue2 = sourceValue.split(',')[0]
fileName = sourceValue2.substring(sourceValue2.lastIndexOf('/') + 1)
} else {
fileName = sourceValue.substring(sourceValue.lastIndexOf('/') + 1)
}
uploadValues[inputId] = {
name: fileName,
status: 'done',
path: sourceValue
}
}
} else {
value[column.key] = sourceValue
}
// 解析disabledRows
for (let columnKey in this.disabledRows) {
// 判断是否有该属性
if (this.disabledRows.hasOwnProperty(columnKey) && data.hasOwnProperty(columnKey)) {
if (disabled !== true) {
let temp = this.disabledRows[columnKey]
// 禁用规则可以是一个数组
if (temp instanceof Array) {
disabled = temp.includes(data[columnKey])
} else {
disabled = (temp === data[columnKey])
}
if (disabled) {
disabledRowIds.push(row.id)
}
}
}
}
})
// 插入行而不是添加到最后
let added = false
if (insertIndexes instanceof Array) {
let insertIndex = insertIndexes[newValueIndex]
if (typeof insertIndex === 'number') {
added = true
rows.splice(insertIndex, 0, row)
this.inputValues.splice(insertIndex, 0, value)
}
}
//update-begin-author:lvdandan date:20201105 for:LOWCOD-987 【online】js增强的问题--数据对象带有id且和现有数据一致时替换患有数据
if(-1 !== rows.findIndex(item => item.id === row.id)){
added = true
this.inputValues = this.inputValues.map(item => item.id === row.id ? value : item)
}
//update-begin-author:lvdandan date:20201105 for:LOWCOD-987 【online】js增强的问题--数据对象带有id且和现有数据一致时替换患有数据
if (!added) {
rows.push(row)
this.inputValues.push(value)
}
})
// 启用了拖动排序,就重新计算排序编号
if (this.dragSort) {
this.inputValues.forEach((item, index) => {
item[this.dragSortKey] = (index + 1)
})
}
this.disabledRowIds = disabledRowIds
this.checkboxValues = checkboxValues
this.selectValues = selectValues
this.jdateValues = jdateValues
this.jInputPopValues = jInputPopValues
this.slotValues = slotValues
this.uploadValues = uploadValues
this.popupValues = popupValues
this.radioValues = radioValues
this.multiSelectValues = multiSelectValues
this.searchSelectValues = searchSelectValues
// 重新计算所有统计列
this.recalcAllStatisticsColumns()
// 更新到 dom
if (update) {
this.rows = rows
// 更新form表单的值
this.$nextTick(() => {
this.forceUpdateFormValues()
})
}
return rows
},
/** 获取某一数字输入框列中的最大的值 */
getInputNumberMaxValue(column) {
let maxNum = 0
this.inputValues.forEach((item, index) => {
let val = item[column.key], num
try {
num = parseInt(val)
} catch {
num = 0
}
// 把首次循环的结果当成最大值
if (index === 0) {
maxNum = num
} else {
maxNum = (num > maxNum) ? num : maxNum
}
})
return maxNum
},
/** 添加一行 */
add(num = 1, forceScrollToBottom = false) {
if (num < 1) return
// let timestamp = new Date().getTime()
let rows = this.rows
let row
for (let i = 0; i < num; i++) {
rows = this.push({}, false, rows)
row = rows[rows.length - 1]
}
this.rows = rows
this.$nextTick(() => {
this.updateFormValues()
})
// 触发add事件
this.$emit('added', {
row: (() => {
let r = Object.assign({}, row)
r.id = this.getCleanId(r.id)
return r
})(),
target: this
})
// 设置滚动条位置
let tbody = this.getElement('tbody')
let offsetHeight = tbody.offsetHeight
let realScrollTop = tbody.scrollTop + offsetHeight
if (forceScrollToBottom === false) {
// 只有滚动条在底部的时候才自动滚动
if (!((tbody.scrollHeight - realScrollTop) <= 10)) {
return
}
}
this.$nextTick(() => {
tbody.scrollTop = tbody.scrollHeight
})
},
/**
* 在指定位置添加一行
* @param insertIndex 添加位置下标
* @param num 添加的行数默认1
*/
insert(insertIndex, num = 1) {
if (this.checkTooFastClick('insert', 1500)) {
return
}
if (!insertIndex && num < 1) return
let rows = this.rows
let newRows = []
for (let i = 0; i < num; i++) {
let row = { id: this.generateId(rows) }
rows = this.push(row, false, rows, insertIndex)
newRows.push(row)
}
// 同步更改
this.rows = rows
this.$nextTick(() => {
this.recalcSortNumber()
this.forceUpdateFormValues()
})
// 触发 insert 事件
this.$emit('inserted', {
rows: newRows.map(row => {
let r = cloneObject(row)
r.id = this.getCleanId(r.id)
return r
}),
num, insertIndex,
target: this
})
},
/** 删除被选中的行 */
removeSelectedRows() {
this.removeRows(this.selectedRowIds)
this.selectedRowIds = []
},
/** 删除一行或多行 */
removeRows(id) {
let ids = id
if (!(id instanceof Array)) {
if (typeof id === 'string') {
ids = [id]
} else {
throw `JEditableTable.removeRows() 函数需要的参数可以是string或Array类型但提供的却是${typeof id}`
}
}
let rows = cloneObject(this.rows)
ids.forEach(removeId => {
removeId = this.getCleanId(removeId)
// 找到每个id对应的真实index并删除
const findAndDelete = (arr) => {
for (let i = 0; i < arr.length; i++) {
let currentId = this.getCleanId(arr[i].id)
if (currentId === removeId) {
arr.splice(i, 1)
return true
}
}
}
// 找到rows对应的index并删除
if (findAndDelete(rows)) {
// 找到values对应的index并删除
findAndDelete(this.inputValues)
// 将caseId去除
let id = this.getCleanId(removeId)
this.deleteIds.push(id)
}
})
this.rows = rows
this.$emit('deleted', this.getDeleteIds(), this)
this.$nextTick(() => {
// 更新formValues
this.updateFormValues()
// 重新计算统计
this.recalcAllStatisticsColumns()
})
return true
},
/** 获取表格表单里的值(异步版) */
getValuesAsync(options = {}, callback) {
let { validate, rowIds, deleteTempId } = options
if (typeof validate !== 'boolean') validate = true
if (!(rowIds instanceof Array)) rowIds = null
// 是否删除临时ID默认为 false
if (typeof deleteTempId !== 'boolean') deleteTempId = false
// console.log('options:', { validate, rowIds })
let asyncCount = 0
let error = 0
let inputValues = cloneObject(this.inputValues)
let tooltips = Object.assign({}, this.tooltips)
let notPassedIds = cloneObject(this.notPassedIds)
// 用于存储合并后的值
let values = []
// 遍历inputValues来获取每行的值
for (let value of inputValues) {
let rowIdsFlag = false
// 如果带有rowIds那么就只存这几行的数据
if (rowIds == null) {
rowIdsFlag = true
} else {
for (let rowId of rowIds) {
if (this.getCleanId(rowId) === this.getCleanId(value.id)) {
rowIdsFlag = true
break
}
}
}
if (!rowIdsFlag) continue
this.columns.forEach(column => {
let inputId = column.key + value.id
if (column.type === FormTypes.checkbox) {
let checked = this.checkboxValues[inputId]
if (column.customValue instanceof Array) {
value[column.key] = checked ? column.customValue[0] : column.customValue[1]
} else {
value[column.key] = checked
}
} else if (column.type === FormTypes.select) {
let selected = this.selectValues[inputId]
if (selected instanceof Array) {
value[column.key] = cloneObject(selected)
} else {
value[column.key] = selected
}
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
value[column.key] = this.jdateValues[inputId]
} else if (column.type === FormTypes.input_pop) {
value[column.key] = this.jInputPopValues[inputId]
} else if (column.type === FormTypes.upload) {
value[column.key] = cloneObject(this.uploadValues[inputId] || null)
} else if (column.type === FormTypes.image || column.type === FormTypes.file) {
let currUploadObj = cloneObject(this.uploadValues[inputId] || null)
if (currUploadObj) {
value[column.key] = currUploadObj['path'] || null
}
} else if (column.type === FormTypes.popup) {
if (!value[column.key]) {
value[column.key] = this.popupValues[inputId] || null
}
} else if (column.type === FormTypes.radio) {
value[column.key] = this.radioValues[inputId]
} else if (column.type === FormTypes.sel_search) {
value[column.key] = this.searchSelectValues[inputId]
} else if (column.type === FormTypes.list_multi) {
if (!this.multiSelectValues[inputId] || this.multiSelectValues[inputId].length === 0) {
value[column.key] = ''
} else {
value[column.key] = this.multiSelectValues[inputId].join(',')
}
} else if (column.type === FormTypes.slot) {
value[column.key] = this.slotValues[inputId]
}
// 检查表单验证
if (validate === true) {
const handleValidateOneInput = (results) => {
tooltips[inputId] = results[0]
if (tooltips[inputId].passed === false) {
error++
// if (error++ === 0) {
// let element = document.getElementById(inputId)
// while (element.className !== 'tr') {
// element = element.parentElement
// }
// this.jumpToId(inputId, element)
// }
}
tooltips[inputId].visible = false
notPassedIds = results[1]
}
asyncCount++
let results = this.validateOneInputAsync(value[column.key], value, column, notPassedIds, false, 'getValues', (results) => {
handleValidateOneInput(results)
asyncCount--
})
handleValidateOneInput(results)
}
})
// 删除 tempId
if (deleteTempId && this.isTempId(value.id)) {
delete value.id
} else {
value.id = this.getCleanId(value.id)
}
values.push(value)
}
if (validate === true) {
this.tooltips = tooltips
this.notPassedIds = notPassedIds
}
const timer = setInterval(() => {
if (asyncCount === 0) {
clearInterval(timer)
if (typeof callback === 'function') {
callback({ error, values })
}
}
}, 10)
return { error, values }
},
/** 获取表格表单里的值(同步版) */
getValuesSync(options = {}) {
return this.getValuesAsync(options)
},
/** 获取表格表单里的值 */
getValues(callback, validate = true, rowIds) {
this.getValuesAsync({ validate, rowIds }, ({ error, values }) => {
if (typeof callback === 'function') {
callback(error, values)
}
})
},
/** getValues的Promise版 */
getValuesPromise(validate = true, rowIds, deleteTempId) {
return new Promise((resolve, reject) => {
this.getValuesAsync({ validate, rowIds, deleteTempId }, ({ error, values }) => {
if (error === 0) {
resolve(values)
} else {
reject(VALIDATE_NO_PASSED)
}
})
})
},
/** 获取被删除项的id */
getDeleteIds() {
return cloneObject(this.deleteIds)
},
/** 获取所有的数据包括values、deleteIds */
getAll(validate, deleteTempId) {
return new Promise((resolve, reject) => {
let deleteIds = this.getDeleteIds()
this.getValuesPromise(validate, null, deleteTempId).then((values) => {
resolve({ values, deleteIds })
}).catch(error => {
reject(error)
})
})
},
/** Sync 获取所有的数据包括values、deleteIds */
getAllSync(validate, rowIds, deleteTempId) {
let result = this.getValuesSync({ validate, rowIds, deleteTempId })
result.deleteIds = this.getDeleteIds()
return result
},
// slot 获取值
_getValueForSlot(rowId) {
return this.getValuesSync({ rowIds: [rowId] }).values[0]
},
_getAllValuesForSlot() {
return cloneObject({
inputValues: this.inputValues,
selectValues: this.selectValues,
checkboxValues: this.checkboxValues,
jdateValues: this.jdateValues,
jInputPopValues: this.jInputPopValues,
slotValues: this.slotValues,
uploadValues: this.uploadValues,
popupValues: this.popupValues,
radioValues: this.radioValues,
multiSelectValues: this.multiSelectValues,
searchSelectValues: this.searchSelectValues,
})
},
/** 设置某行某列的值 */
setValues(values) {
values.forEach(item => {
let { rowKey, values: newValues } = item
rowKey = this.getCleanId(rowKey)
for (let newValueKey in newValues) {
if (newValues.hasOwnProperty(newValueKey)) {
let edited = false // 已被修改
for (let column of this.columns) {
if (column.key === newValueKey) {
let newValue = newValues[newValueKey]
this.inputValues.forEach(value => {
// 在inputValues中找到了该字段
if (rowKey === this.getCleanId(value.id)) {
if (value.hasOwnProperty(newValueKey)) {
edited = true
value[newValueKey] = newValue
}
}
})
if (!edited) {
let modelKey = `${newValueKey}${this.caseId}${rowKey}`
if (column.type === FormTypes.select) {
if (newValue !== 0 && !newValue) {
edited = this.setOneValue(this.selectValues, modelKey, undefined)
} else {
edited = this.setOneValue(this.selectValues, modelKey, newValue)
}
} else if (column.type === FormTypes.checkbox) {
// checkbox 特殊处理 CustomValue
let key = this.valuesHasOwnProperty(this.checkboxValues, modelKey)
// 找到对应的column
let sourceValue
// 判断是否设定了customValue自定义值
if (column.customValue instanceof Array) {
let customValue = (column.customValue[0] || '').toString()
sourceValue = (newValue === customValue)
} else {
sourceValue = !!newValue
}
this.$set(this.checkboxValues, key, sourceValue)
edited = true
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
edited = this.setOneValue(this.jdateValues, modelKey, newValue)
} else if (column.type === FormTypes.input_pop) {
edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
} else if (column.type === FormTypes.slot) {
edited = this.setOneValue(this.slotValues, modelKey, newValue)
} else if (column.type === FormTypes.upload || column.type === FormTypes.image || column.type === FormTypes.file) {
edited = this.setOneValue(this.uploadValues, modelKey, newValue)
} else if (column.type === FormTypes.popup) {
edited = this.setOneValue(this.popupValues, modelKey, newValue)
} else if (column.type === FormTypes.radio) {
edited = this.setOneValue(this.radioValues, modelKey, newValue)
} else if (column.type === FormTypes.list_multi) {
edited = this.setOneValue(this.multiSelectValues, modelKey, newValue, true)
} else if (column.type === FormTypes.sel_search) {
edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
} else {
edited = false
}
}
if (edited) {
this.elemValueChange(column.type, {[newValueKey]: newValue}, column, newValue)
}
}
}
if (!edited) {
console.warn(`JEditableTable.setValues没有找到"${newValueKey}"列`)
}
}
}
})
// 强制更新formValues
this.forceUpdateFormValues()
},
/**
* 设置单个组件的值
* @param valuesObject 组件存储值的对象
* @param modelKey 组件存储值的对象里的key
* @param value 新值
* @param isMultiple 是否多选,如果是就会对 value 进行一个 split(',') 的操作
*/
setOneValue(valuesObject, modelKey, value, isMultiple = false) {
let key = this.valuesHasOwnProperty(valuesObject, modelKey)
if (key) {
// 处理多选数组
if (isMultiple && !Array.isArray(value)) {
value = (value || '').toString().trim()
value = value === '' ? [] : value.split(',')
}
this.$set(valuesObject, key, value)
return true
}
return false
},
valuesHasOwnProperty(values, ownProperty) {
let key = ownProperty
if (values.hasOwnProperty(key)) {
return key
}
if (values.hasOwnProperty(key + this.tempId)) {
return key + this.tempId
}
return null
},
/** 跳转到指定位置 */
// jumpToId(id, element) {
// if (element == null) {
// element = document.getElementById(id)
// }
// if (element != null) {
// console.log(this.getElement('tbody').scrollTop, element.offsetTop)
// this.getElement('tbody').scrollTop = element.offsetTop
// console.log(this.getElement('tbody').scrollTop, element.offsetTop)
// }
// },
/**
* 验证单个表单,异步版
*
* @param value 校验的值
* @param row 校验的行
* @param column 校验的列
* @param notPassedIds 没有通过校验的 id
* @param update 是否更新到vue中
* @param validType 校验触发的方式input、blur等
* @param callback
*/
validateOneInputAsync(value, row, column, notPassedIds, update = false, validType = 'input', callback) {
let tooltips = Object.assign({}, this.tooltips)
// let notPassedIds = cloneObject(this.notPassedIds)
let inputId = column.key + row.id
tooltips[inputId] = tooltips[inputId] ? tooltips[inputId] : {}
let [passed, message] = this.validateValue(column, value)
const nextThen = res => {
let [passed, message] = res
// !(passed == null && tooltips[inputId].visible != null)
if (passed != null) {
tooltips[inputId].visible = !passed
tooltips[inputId].passed = passed
let index = notPassedIds.indexOf(inputId)
if (!passed) {
tooltips[inputId].title = this.replaceProps(column, message)
if (index === -1) notPassedIds.push(inputId)
} else {
if (index !== -1) notPassedIds.splice(index, 1)
}
}
// 是否更新到data
if (update) {
this.tooltips = tooltips
this.notPassedIds = notPassedIds
}
if (typeof callback === 'function') {
callback([tooltips[inputId], notPassedIds])
}
}
if (typeof passed === 'function') {
let executed = false
passed(validType, value, { id: this.getCleanId(row.id) }, { ...column }, (flag, msg) => {
if (executed) return
executed = true
if (typeof msg === 'string') {
message = msg
}
if (flag == null) {
nextThen([true, message])
} else {
nextThen([!!flag, message])
}
}, this)
} else {
nextThen([passed, message])
}
return [tooltips[inputId], notPassedIds]
},
/** 验证单个表单 */
validateOneInput(value, row, column, notPassedIds, update = false, validType = 'input') {
return this.validateOneInputAsync(value, row, column, notPassedIds, update, validType)
},
/** 通过规则验证值是否正确 */
validateValue(column, value) {
let rules = column.validateRules
let passed = true, message = ''
// 判断有没有验证规则或验证规则格式正不正确,若条件不符合则默认通过
if (rules instanceof Array) {
for (let rule of rules) {
// 当前值是否为空
let isNull = (value == null || value === '')
// 验证规则:非空
if (rule.required === true && isNull) {
passed = false
} else // 使用 else-if 是为了防止一个 rule 中出现两个规则
// 验证规则:唯一校验
if (rule.unique === true || rule.pattern === 'only') {
let { values } = this.getValuesSync({ validate: false })
let findCount = 0
for (let val of values) {
if (val[column.key] === value) {
if (++findCount >= 2) {
passed = false
break
}
}
}
} else
// 验证规则:正则表达式
if (!!rule.pattern && !isNull) {
// 兼容 online 的规则
let foo = [
{ title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,18}$/ },
{ title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/ },
{ title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/ },
{ title: '网址', value: 'url', pattern: /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/ },
{ title: '电子邮件', value: 'e', pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/ },
{ title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/ },
{ title: '邮政编码', value: 'p', pattern: /^[1-9]\d{5}$/ },
{ title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/ },
{ title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/ },
{ title: '整数', value: 'z', pattern: /^-?\d+$/ },
{ title: '非空', value: '*', pattern: /^.+$/ },
{ title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/ },
]
let flag = false
for (let item of foo) {
if (rule.pattern === item.value && item.pattern) {
passed = new RegExp(item.pattern).test(value)
flag = true
break
}
}
if (!flag) passed = new RegExp(rule.pattern).test(value)
} else
// 校验规则:自定义函数校验
if (typeof rule.handler === 'function') {
return [rule.handler, rule.message]
}
// 如果没有通过验证,则跳出循环。如果通过了验证,则继续验证下一条规则
if (!passed) {
message = rule.message
break
}
}
}
return [passed, message]
},
/** 动态更新表单的值 */
updateFormValues() {
let trs = this.getElement('tbody').getElementsByClassName('tr')
let trEls = []
for (let tr of trs) {
trEls.push(tr)
}
// 获取新增的 tr
let newTrEls = trEls
if (this.visibleTrEls.length > 0) {
newTrEls = []
for (let tr of trEls) {
let isNewest = true
for (let vtr of this.visibleTrEls) {
if (vtr.id === tr.id) {
isNewest = false
break
}
}
if (isNewest) {
newTrEls.push(tr)
}
}
}
this.visibleTrEls = trEls
// 向新增的tr中赋值
newTrEls.forEach(tr => {
let { idx } = tr.dataset
let value = this.inputValues[idx]
for (let key in value) {
if (value.hasOwnProperty(key)) {
let elid = `${key}${value.id}`
let el = document.getElementById(elid)
if (el) {
el.value = value[key]
}
}
}
})
},
/** 强制更新FormValues */
forceUpdateFormValues() {
this.visibleTrEls = []
this.$forceUpdate()
this.$nextTick(() => this.updateFormValues())
},
// 重新计算所有统计列
recalcAllStatisticsColumns() {
if (this.hasStatisticsColumn) {
Object.keys(this.statisticsColumns).forEach(key => this.recalcOneStatisticsColumn(key))
}
},
// 重新计算单个统计列
recalcOneStatisticsColumn(key) {
if (this.hasStatisticsColumn) {
if (this.statisticsColumns.hasOwnProperty(key)) {
// 计算合计值
let count = 0
this.inputValues.forEach(item => {
let value = item[key]
if (value && count !== '-') {
try {
count += Number.parseInt(value)
} catch (e) {
count = '-'
}
}
})
this.statisticsColumns[key] = count
}
}
},
/** 获取某个统计字段的值 */
getStatisticsValue(key) {
if (this.hasStatisticsColumn) {
if (this.statisticsColumns.hasOwnProperty(key)) {
return this.statisticsColumns[key]
}
}
return null
},
/** 全选或取消全选 */
handleChangeCheckedAll() {
let selectedRowIds = []
if (!this.getSelectAll) {
this.rows.forEach(row => {
if ((this.disabledRowIds || []).indexOf(row.id) === -1) {
selectedRowIds.push(row.id)
}
})
}
this.selectedRowIds = selectedRowIds
},
/** 左侧行选择框change事件 */
handleChangeLeftCheckbox(event) {
let { id } = event.target
if ((this.disabledRowIds || []).indexOf(id) !== -1) {
return
}
let index = this.selectedRowIds.indexOf(id)
if (index !== -1) {
this.selectedRowIds.splice(index, 1)
} else {
this.selectedRowIds.push(id)
}
},
handleClickAdd() {
this.add()
},
handleConfirmDelete() {
this.removeSelectedRows()
},
handleClickClearSelection() {
this.clearSelection()
},
clearSelection() {
this.selectedRowIds = []
},
/** 用于搜索下拉框中的内容 */
handleSelectFilterOption(input, option, column) {
if (column.allowSearch === true || column.allowInput === true) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
return true
},
/** select 搜索时的事件用于动态添加options */
handleSearchSelect(value, id, row, col) {
if (col.allowSearch !== true && col.allowInput === true) {
// 是否找到了对应的项,找不到则添加这一项
let flag = false
for (let option of col.options) {
if (option.value.toLocaleString() === value.toLocaleString()) {
flag = true
break
}
}
// !!value :不添加空值
if (!flag && !!value) {
// searchAdd 是否是通过搜索添加的
col.options.push({ title: value, value: value, searchAdd: true })
}
}
},
// blur 失去焦点
handleBlurSearch(value, id, row, col) {
if (col.allowInput === true) {
// 删除无用的因搜索(用户输入)而创建的项
if (typeof value === 'string') {
let indexs = []
col.options.forEach((option, index) => {
if (option.value.toLocaleString() === value.toLocaleString()) {
delete option.searchAdd
} else if (option.searchAdd === true) {
indexs.push(index)
}
})
// 翻转删除数组中的项
for (let index of indexs.reverse()) {
col.options.splice(index, 1)
}
}
}
// 做单个表单验证
this.validateOneInput(value, row, col, this.notPassedIds, true, 'blur')
},
/** 触发已拖动事件 */
emitDragged(oldIndex, newIndex) {
this.$emit('dragged', { oldIndex, newIndex, target: this })
},
handleDragMoveStart(event) {
this.dragging = true
this.$refs.scrollView.style.overflow = 'hidden'
},
/** 拖动结束交换inputValue中的值 */
handleDragMoveEnd(event) {
this.dragging = false
this.$refs.scrollView.style.overflow = 'auto'
let { oldIndex, newIndex, item: { dataset: { idx: dataIdx } } } = event
// 由于动态显示隐藏行导致index有误差需要算出真实的index
let diff = Number.parseInt(dataIdx) - oldIndex
oldIndex += diff
newIndex += diff
this.rowResort(oldIndex, newIndex)
this.emitDragged(oldIndex, newIndex)
},
/** 行重新排序 */
rowResort(oldIndex, newIndex) {
const sort = (array) => {
// 存储旧数据,并删除旧项目
let temp = array[oldIndex]
array.splice(oldIndex, 1)
// 向新项目里添加旧数据
array.splice(newIndex, 0, temp)
}
sort(this.rows)
sort(this.inputValues)
this.recalcSortNumber()
this.forceUpdateFormValues()
},
/** 重新计算排序字段的数值 */
recalcSortNumber() {
if (this.dragSort) {
// 重置排序字段
this.inputValues.forEach((val, idx) => val[this.dragSortKey] = (idx + 1))
}
},
/** 当前行向上移一位 */
_handleRowMoveUp(rowIndex) {
if (rowIndex > 0) {
let newIndex = rowIndex - 1
this.rowResort(rowIndex, newIndex)
this.emitDragged(rowIndex, newIndex)
}
},
/** 当前行向下移一位 */
_handleRowMoveDown(rowIndex) {
if (rowIndex < (this.rows.length - 1)) {
let newIndex = rowIndex + 1
this.rowResort(rowIndex, newIndex)
this.emitDragged(rowIndex, newIndex)
}
},
/** 在当前行下面插入一行 */
_handleRowInsertDown(rowIndex) {
let insertIndex = (rowIndex + 1)
this.insert(insertIndex)
},
/* --- common function begin --- */
/** input事件 */
handleInputCommono(target, index, row, column) {
let oldValue = this.inputValues[index][column.key] || ''
let { value, dataset, selectionStart } = target
let type = FormTypes.input
let change = true
if (`${dataset.inputNumber}` === 'true') {
type = FormTypes.inputNumber
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
if (!/^-?\d+\.?\d*$/.test(value) && (value !== '' && value !== '-')) {
change = false
value = oldValue
target.value = value
if (typeof selectionStart === 'number') {
target.selectionStart = selectionStart - 1
target.selectionEnd = selectionStart - 1
}
}
}
// 存储输入的值
this.inputValues[index][column.key] = value
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'input')
if (type === FormTypes.inputNumber) {
this.recalcOneStatisticsColumn(column.key)
}
// 触发valueChange 事件
if (change) {
this.elemValueChange(type, row, column, value)
}
},
/** slot Change */
handleChangeSlotCommon(value, id, row, column) {
this.slotValues = this.bindValuesChange(value, id, 'slotValues')
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange(FormTypes.slot, row, column, value)
},
handleBlurCommono(target, index, row, column) {
let { value, dataset } = target
if (dataset && `${dataset.inputNumber}` === 'true') {
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
if (!/^-?\d+\.?\d*$/.test(value)) {
value = ''
} else {
value = Number.parseFloat(value)
}
target.value = value
}
//update--begin--autor:lvdandan-----date:20201126------forLOWCOD-1088 JEditableTable输入校验提示框位置偏移 #2005
setTimeout(()=>{
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'blur')
}, 100)
//update--end--autor:lvdandan-----date:20201126------forLOWCOD-1088 JEditableTable输入校验提示框位置偏移 #2005
},
handleChangeCheckboxCommon(event, row, column) {
let { id, checked } = event.target
this.checkboxValues = this.bindValuesChange(checked, id, 'checkboxValues')
// 触发valueChange 事件
this.elemValueChange(FormTypes.checkbox, row, column, checked)
},
handleChangeSelectCommon(value, id, row, column) {
this.selectValues = this.bindValuesChange(value, id, 'selectValues')
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange(FormTypes.select, row, column, value)
},
handleChangeJDateCommon(value, id, row, column, showTime) {
this.jdateValues = this.bindValuesChange(value, id, 'jdateValues')
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
if (showTime) {
this.elemValueChange(FormTypes.datetime, row, column, value)
} else {
this.elemValueChange(FormTypes.date, row, column, value)
}
},
handleChangeJInputPopCommon(value, id, row, column){
this.jInputPopValues = this.bindValuesChange(value, id, 'jInputPopValues')
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange(FormTypes.input_pop, row, column, value)
},
handleChangeUpload(info, id, row, column) {
let { file } = info
let value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (column.responseName && file.response) {
value['responseName'] = file.response[column.responseName]
}
if (file.status === 'done') {
value['path'] = file.response[column.responseName]
} else if (file.status === 'error') {
value['message'] = file.response.message || '未知错误'
}
this.uploadValues = this.bindValuesChange(value, id, 'uploadValues')
// 触发valueChange 事件
this.elemValueChange(column.type, row, column, value)
},
handleMoreOperation(id,flag,column){
//update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(column.number){
this.number = column.number;
}else{
this.number = 0;
}
//update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number用于控制上传文件
if(column && column.fieldExtendJson){
let json = JSON.parse(column.fieldExtendJson);
this.number = json.uploadnum?json.uploadnum:0;
}
//console.log("this.uploadValues[id]",this.uploadValues[id])
let path = ''
if(this.uploadValues && this.uploadValues[id]){
path = this.uploadValues[id].path
}
this.$refs.filePop.show(id,path,flag)
},
handleFileSuccess(obj){
if(obj.id){
this.uploadValues = this.bindValuesChange(obj, obj.id, 'uploadValues')
}
},
handleClickTableRow(event) {
let {target} = event
if (target.className === 'td' || target.className === 'tr') {
// 清空编辑状态
this.currentEditRows = {}
}
},
// 点击后编辑当前行
handleEditRow(row, col) {
if (this.alwaysEdit) {
return
}
// 将点击的组件置为可编辑并还原其他组件的编辑状态
this.currentEditRows = {
[row.id]: {
[col.key]: true
}
}
if (col.type === FormTypes.input || col.type === FormTypes.inputNumber) {
this.$nextTick(() => {
this.forceUpdateFormValues()
// 自动获取焦点
let el = document.getElementById(`${col.key}${row.id}`)
if (el) {
el.focus()
}
})
}
},
/** 记录用到数据绑定的组件的值 */
bindValuesChange(value, id, key) {
this.$set(this[key], id, value)
return this[key]
},
/** value 触发valueChange事件 */
elemValueChange(type, rowSource, columnSource, value) {
let column = Object.assign({}, columnSource)
// 将caseId去除
let row = Object.assign({}, rowSource)
row.id = this.getCleanId(row.id)
// 获取整行的数据
let { values } = this.getValuesSync({ validate: false, rowIds: [row.id] })
if (values.length > 0) {
Object.assign(row, values[0])
}
this.$emit('valueChange', { type, row, column, value, target: this })
},
/** 获取干净的ID不包含任何杂质的ID */
getCleanId(id) {
id = this.removeCaseId(id)
id = this.removeTempId(id)
return id
},
/** 判断某个ID是否包含了caseId */
hasCaseId(id) {
return id && id.startsWith(this.caseId)
},
/** 将caseId去除 */
removeCaseId(id) {
if (this.hasCaseId(id)) {
return id.substring(this.caseId.length, id.length)
}
return id
},
// 判断 id 是否是临时Id
isTempId(id) {
return (id || '').endsWith(this.tempId)
},
/** 将tempId去除 */
removeTempId(id) {
if (this.isTempId(id)) {
return id.substring(0, id.length - this.tempId.length)
}
return id;
},
handleClickDelFile(id) {
this.uploadValues[id] = null
},
handleClickDownloadFile(id) {
let { path } = this.uploadValues[id] || {}
if (path) {
let url = getFileAccessHttpUrl(path)
window.open(url)
}
},
handleClickDownFileByUrl(id){
let { url,path } = this.uploadValues[id] || {}
if (!url || url.length===0) {
if(path && path.length>0){
url = getFileAccessHttpUrl(path.split(',')[0])
}
}
if(url){
window.open(url)
}
},
handleClickShowImageError(id) {
let currUploadObj = this.uploadValues[id] || null
if (currUploadObj && currUploadObj['message']) {
this.$error({ title: '上传出错', content: '错误信息:' + currUploadObj['message'], maskClosable: true })
}
},
/** 加载数据字典并合并到 options */
_loadDictConcatToOptions(column) {
initDictOptions(column.dictCode).then((res) => {
if (res.success) {
let newOptions = (column.options || [])// .concat(res.result)
res.result.forEach(item => {
// 过滤重复数据
for (let option of newOptions) if (option.value === item.value) return
newOptions.push(item)
})
this.$set(column, 'options', newOptions)
} else {
console.group(`JEditableTable 查询字典(${column.dictCode})发生异常`)
console.log(res.message)
console.groupEnd()
}
})
},
/* --- common function end --- */
/* --- 以下是辅助方法,多用于动态构造页面中的数据 --- */
/** 辅助方法:打印日志 */
log() {
if (this.$attrs.logger) {
console.log.apply(null, arguments)
}
},
getVM() {
return this
},
/** 辅助方法动态构造Tooltip的Props防止出现不消失的情况 */
buildTooltipProps(row, col, id) {
let {notPassedIds, tooltips} = this
let props = {
title: (tooltips[id] || {}).title,
placement: 'top',
autoAdjustOverflow: true,
getPopupContainer: this.getParentContainer,
class: {
'j-check-failed': false
},
}
let isCheckFailed = notPassedIds.includes(id)
if (isCheckFailed) {
props.class['j-check-failed'] = true
} else {
props['visible'] = false
}
return props
},
/** 辅助方法指定a-select 和 j-data 的父容器 */
getParentContainer(node) {
let element = (() => {
// nodeType 8 : Comment : 注释
if (this.$el && this.$el.nodeType !== 8) {
return this.$el
}
let doc = document.getElementById(this.caseId + 'inputTable')
if (doc != null) {
return doc
}
return node.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
})()
// 递归判断是否带有 overflow: hidden的父元素
const ifParent = (child) => {
let currentOverflow = null
if (child['currentStyle']) {
currentOverflow = child['currentStyle']['overflow']
} else if (window.getComputedStyle) {
currentOverflow = window.getComputedStyle(child)['overflow']
}
if (currentOverflow != null) {
if (currentOverflow === 'hidden') {
// 找到了带有 hidden 的标签,判断它的父级是否还有 hidden直到遇到完全没有 hidden 或 body 的时候才停止递归
let temp = ifParent(child.parentNode)
return temp != null ? temp : child.parentNode
} else
// 当前标签没有 hidden ,如果有父级并且父级不是 body 的话就继续递归判断父级
if (child.parentNode && child.parentNode.tagName.toLocaleLowerCase() !== 'body') {
return ifParent(child.parentNode)
} else {
// 直到 body 都没有遇到有 hidden 的标签
return null
}
} else {
return child
}
}
let temp = ifParent(element)
return (temp != null) ? temp : element
},
/** 辅助方法:替换${...}变量 */
replaceProps(col, value) {
if (value && typeof value === 'string') {
value = value.replace(/\${title}/g, col.title)
value = value.replace(/\${key}/g, col.key)
value = value.replace(/\${defaultValue}/g, col.defaultValue)
}
return value
},
/** view辅助方法构建 tr style */
buildTrStyle(index) {
return {
'top': `${rowHeight * index}px`
}
},
/** view辅助方法构建 td style */
buildTdStyle(col,isTitle) {
const isEmptyWidth = (column) => (column.type === FormTypes.hidden || column.width === '0px' || column.width === '0' || column.width === 0)
let style = {}
// 计算宽度
if (col.width) {
style['width'] = col.width
} else if (this.columns) {
style['width'] = `${(100 - 4 * 2) / (this.columns.filter(column => !isEmptyWidth(column))).length}%`
} else {
style['width'] = '120px'
}
//update-begin-author:lvdandan date:20201116 for:LOWCOD-984 默认风格功能测试附表样式问题 日期时间控件长度太大
//是否为标题如果是时间控件设为200时间控件的标题设为240 时间
if(col.type === FormTypes.datetime){
if(true === isTitle){
style['width'] = '240px'
}else{
style['width'] = '200px'
}
}
//update-end-author:lvdandan date:20201116 for:LOWCOD-984 默认风格功能测试附表样式问题 日期时间控件长度太大
// checkbox 居中显示
let isCheckbox = col.type === FormTypes.checkbox
if (isCheckbox) {
style['align-items'] = 'center'
style['text-align'] = 'center'
style['padding-left'] = '0'
style['padding-right'] = '0'
}
if (isEmptyWidth(col)) {
style['padding-left'] = '0'
style['padding-right'] = '0'
}
return style
},
/** view辅助方法构造props */
buildProps(row, col) {
let props = {}
// 解析props
if (typeof col.props === 'object') {
for (let prop in col.props) {
if (col.props.hasOwnProperty(prop)) {
props[prop] = this.replaceProps(col, col.props[prop])
}
}
}
// 判断select是否允许输入
if (col.type === FormTypes.select && (col.allowInput === true || col.allowSearch === true)) {
props['showSearch'] = true
}
// 判断是否是禁用的列
props['disabled'] = (typeof col['disabled'] === 'boolean' ? col['disabled'] : props['disabled'])
// 判断是否为禁用的行
if (props['disabled'] !== true) {
props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
}
// 判断是否禁用全部组件
if (this.disabled === true) {
props['disabled'] = true
}
return props
},
/** 辅助方法:防止过快点击,如果点击过快的话就返回 true */
checkTooFastClick(key = 'default', ms = 300) {
let nowTime = Date.now()
let lastTime = this.lastPushTimeMap.get(key)
if (!lastTime) {
lastTime = nowTime
this.lastPushTimeMap.set(key, nowTime)
return false
}
let diffTime = nowTime - lastTime
if (diffTime <= ms) {
this.$message.warn('你点击的太快了,请慢点点击!')
return true
}
this.lastPushTimeMap.set(key, nowTime)
return false
},
/** upload 辅助方法:获取 headers */
uploadGetHeaders(row, column) {
let headers = {}
if (column.token === true) {
headers['X-Access-Token'] = this.accessToken
}
return headers
},
/** 上传请求地址 */
getUploadAction(value) {
if (!value) {
return window._CONFIG['domianURL'] + '/sys/common/upload'
} else {
return value
}
},
/** 预览图片地址 */
getCellImageView(id) {
let currUploadObj = this.uploadValues[id] || null
if (currUploadObj) {
if(currUploadObj['url']){
return currUploadObj['url'];
}else if(currUploadObj['path']){
let readpath = currUploadObj['path'].split(',')[0]
return getFileAccessHttpUrl(readpath)
}
}
return ''
},
/** popup回调 */
popupCallback(value, others, id, row, column, index) {
// 存储输入的值
let popupValue = value
if (others) {
let rowKey = this.getCleanId(row.id)
let setValueItem = {rowKey, values: {}}
Object.keys(others).forEach(key => {
// 当前列直接赋值其他列通过setValues赋值
if (key === column.key) {
popupValue = others[key]
} else {
setValueItem.values[key] = others[key]
}
})
if (Object.keys(setValueItem).length > 0) {
this.setValues([setValueItem])
}
}
this.setOneValue(this.popupValues, id, popupValue)
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange('input', row, column, value)
},
/** popup输入框回显 */
getPopupValue(id) {
return this.popupValues[id]
},
handleRadioChange(value, id, row, column) {
this.radioValues = this.bindValuesChange(value, id, 'radioValues')
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange(FormTypes.radio, row, column, value)
},
handleMultiSelectChange(value, id, row, column) {
this.multiSelectValues = this.bindValuesChange(value, id, 'multiSelectValues')
// 做单个表单验证
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
// 触发valueChange 事件
this.elemValueChange(FormTypes.list_multi, row, column, value)
},
handleSearchSelectChange(value, id, row, column) {
this.searchSelectValues = this.bindValuesChange(value, id, 'searchSelectValues')
this.validateOneInput(value, row, column, this.notPassedIds, true, 'change')
this.elemValueChange(FormTypes.sel_search, row, column, value)
},
filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
},
getEllipsisWord(content, len){
if(!content || content.length === 0){
return ''
}
if(content.length>len){
return content.substr(0,len)
}
return content;
},
/* --------------------------- 2020年5月18日 默认span模式 ------------------------------ */
/** 获取Select等组件翻译后的文本 */
getSelectTranslateText(value, row, col) {
// 翻译支持单选和多选(数组、逗号分割)
return filterDictText(col.options, value)
},
// 判定当前行是否是正在编辑的
isEditRow(row, col) {
if (this.alwaysEdit) {
return true
}
let current = this.currentEditRows[row.id]
return !!(current && current[col.key] === true)
},
/* ---- 事件监听 ---- */
// 鼠标弹起事件,用于清空输入状态
handleMouseup(event) {
if (this.alwaysEdit || Object.keys(this.currentEditRows).length === 0) {
return
}
// console.log(this.caseId + 'handleMouseup: ', event)
let {target} = event
if (!target){
return
}
let className = target.className || ''
if (typeof className === 'string') {
// 点击的标签是span
if (className.includes('j-td-span') && className.includes('no-edit')) {
return
}
// 点击的标签是下拉
if (className.includes('ant-select-dropdown-menu-item')) {
return
}
}
// 事件冒泡路径
let path = getEventPath(event)
for (let p of path) {
// 如果点击的是 tr 就不处理tr单独处理
if ((p.id || '').startsWith(`${this.caseId}tbody-tr`)) {
return
}
let pClassName = p.className || ''
pClassName = typeof pClassName === 'string' ? pClassName : pClassName.toString()
/* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */
// 点击的标签是JInputPop
if (pClassName.includes('j-input-pop')) {
return
}
// 点击的标签是JPopup的弹出层
if (pClassName.includes('j-popup-modal')) {
return
}
// 点击的标签是日期选择器的弹出层
if (pClassName.includes('j-date-picker') || pClassName.includes('ant-calendar-picker-container')) {
return
}
}
// 清空编辑状态
this.currentEditRows = {}
},
// 添加事件监听
addEventListener() {
window.addEventListener('mouseup', this.handleMouseup)
},
// 移除事件监听
removeEventListener() {
window.removeEventListener('mouseup', this.handleMouseup)
},
/* --------------------------- 2020年5月18日 默认span模式 ------------------------------ */
},
beforeDestroy() {
this.removeEventListener()
this.destroyCleanGroupRequest = true
},
}
</script>
<style lang="less" scoped>
.action-button {
margin-bottom: 8px;
.gap {
padding-left: 8px;
}
}
/* 设定边框参数 */
@borderColor: #e8e8e8;
@border: 1px solid @borderColor;
/* tr & td 之间的间距 */
@spacing: 8px;
.input-table {
max-width: 100%;
overflow-x: hidden;
overflow-y: hidden;
position: relative;
border: @border;
.thead, .tbody {
.tr, .td {
display: flex;
}
.td {
/*border-right: 1px solid red;*/
/*color: white;*/
/*background-color: black;*/
/*margin-right: @spacing !important;*/
padding-left: @spacing;
flex-direction: column;
&.td-cb, &.td-num {
width: 45px;
min-width: 45px;
max-width: 50px;
margin-right: 0;
padding-left: 0;
padding-right: 0;
justify-content: center;
align-items: center;
}
&.td-ds {
width: 30px;
min-width: 30px;
max-width: 35px;
margin-right: 0;
padding-left: 0;
padding-right: 0;
justify-content: center;
align-items: center;
.td-ds-icons {
position: relative;
cursor: move;
width: 100%;
/*padding: 25% 0;*/
height: 100%;
.anticon-align-left,
.anticon-align-right {
position: absolute;
top: 30%;
}
.anticon-align-left {
left: 25%;
}
.anticon-align-right {
right: 25%;
}
}
}
}
}
.thead {
overflow-y: scroll;
overflow-x: hidden;
border-bottom: @border;
/** 隐藏thead的滑块 */
&::-webkit-scrollbar-thumb {
box-shadow: none !important;
background-color: transparent !important;
}
.tr {
min-width: 100%;
overflow-y: scroll;
}
.td {
/*flex: 1;*/
padding: 8px @spacing;
justify-content: center;
}
}
.tbody {
position: relative;
top: 0;
left: 0;
overflow-x: hidden;
overflow-y: hidden;
min-height: 61px;
/*max-height: 400px;*/
min-width: 100%;
.tr-nodata {
color: #999;
line-height: 61px;
text-align: center;
}
.tr {
/*line-height: 50px;*/
border-bottom: @border;
transition: background-color 300ms;
width: 100%;
position: absolute;
left: 0;
z-index: 10;
&.tr-checked {
background-color: #fafafa;
}
&:hover {
background-color: #E6F7FF;
}
}
.tr-expand {
position: relative;
z-index: 9;
background-color: white;
}
.td {
/*flex: 1;*/
padding: 14px @spacing 14px 0;
justify-content: center;
&:last-child {
padding-right: @spacing;
}
input {
font-variant: tabular-nums;
box-sizing: border-box;
margin: 0;
list-style: none;
position: relative;
display: inline-block;
padding: 4px 11px;
width: 100%;
height: 32px;
font-size: 14px;
line-height: 1.5;
color: rgba(0, 0, 0, 0.65);
background-color: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
transition: all 0.3s;
outline: none;
&:hover {
border-color: #4D90FE
}
&:focus {
border-color: #40a9ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
border-right-width: 1px !important;
}
&:disabled {
color: rgba(0, 0, 0, 0.25);
background: #f5f5f5;
cursor: not-allowed;
}
/* 设置placeholder的颜色 */
&::-webkit-input-placeholder { /* WebKit browsers */
color: #ccc;
}
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: #ccc;
}
&::-moz-placeholder { /* Mozilla Firefox 19+ */
color: #ccc;
}
&:-ms-input-placeholder { /* Internet Explorer 10+ */
color: #ccc;
}
}
.j-editable-image {
height: 32px;
max-width: 100px !important;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
/* --------------------------- 2020年5月18日 begin 默认span模式 ------------------------------ */
label {
height: 32px;
&.ant-checkbox-wrapper {
height: auto;
}
}
.j-td-span {
position: relative;
padding: 4px 11px;
border: 1px solid transparent;
display: inline-block;
width: 100%;
max-width: 100%;
height: 32px;
cursor: text;
transition: all 0.3s;
box-sizing: border-box;
font-size: 14px;
line-height: 1.5;
color: rgba(0, 0, 0, 0.65);
border-radius: 4px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
&:hover {
background-color: white;
}
&.disabled {
cursor: not-allowed;
&:hover {
color: rgba(0, 0, 0, 0.25);
background-color: #F5F5F5;
}
}
}
/* --------------------------- 2020年5月18日 end 默认span模式 ------------------------------ */
/* --------------------------- 2020年5月28日 begin 新增校验未通过的样式 ------------------------------ */
.j-check-failed.j-td-span {
background-color: rgba(255, 0, 0, 0.05);
&:hover {
background-color: rgba(255, 0, 0, 0.1);
}
}
.j-check-failed.j-td-span,
input.j-check-failed,
.j-check-failed /deep/ input,
.ant-select.j-check-failed /deep/ .ant-select-selection,
.ant-upload.j-check-failed /deep/ .ant-btn {
border-color: red;
box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.2);
}
/* --------------------------- 2020年5月28日 end 新增校验未通过的样式 ------------------------------ */
}
}
.scroll-view {
overflow: auto;
overflow-y: scroll;
}
.thead, .thead .tr, .scroll-view {
@scrollBarSize: 6px;
/* 定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
&::-webkit-scrollbar {
width: @scrollBarSize;
height: @scrollBarSize;
background-color: transparent;
}
/* 定义滚动条轨道 */
&::-webkit-scrollbar-track {
background-color: #f0f0f0;
}
/* 定义滑块 */
&::-webkit-scrollbar-thumb {
background-color: #eee;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
&:hover {
background-color: #bbb;
}
&:active {
background-color: #888;
}
}
}
.thead .tr {
&::-webkit-scrollbar-track {
background-color: transparent;
}
/* IE模式下隐藏 */
-ms-overflow-style: none;
-ms-scroll-chaining: chained;
-ms-content-zooming: zoom;
-ms-scroll-rails: none;
-ms-content-zoom-limit-min: 100%;
-ms-content-zoom-limit-max: 500%;
-ms-scroll-snap-type: proximity;
-ms-scroll-snap-points-x: snapList(100%, 200%, 300%, 400%, 500%);
}
}
</style>