mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2026-02-04 09:35:20 +08:00
前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题
This commit is contained in:
2
jeecgboot-vue3/src/components/jeecg/JVxeTable/hooks.ts
Normal file
2
jeecgboot-vue3/src/components/jeecg/JVxeTable/hooks.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { useJVxeCompProps, useJVxeComponent } from './src/hooks/useJVxeComponent';
|
||||
export { useResolveComponent } from './src/hooks/useData';
|
||||
4
jeecgboot-vue3/src/components/jeecg/JVxeTable/index.ts
Normal file
4
jeecgboot-vue3/src/components/jeecg/JVxeTable/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as JVxeTable } from './src/JVxeTable';
|
||||
export { registerJVxeTable } from './src/install';
|
||||
export { deleteComponent } from './src/componentMap';
|
||||
export { registerComponent, registerAsyncComponent, registerASyncComponentReal } from './src/utils/registerUtils';
|
||||
@ -0,0 +1,82 @@
|
||||
import { defineComponent, h, nextTick, ref, useSlots } from 'vue';
|
||||
import { vxeEmits, vxeProps } from './vxe.data';
|
||||
import { useData, useRefs, useResolveComponent as rc } from './hooks/useData';
|
||||
import { useColumns } from './hooks/useColumns';
|
||||
import { useColumnsCache } from './hooks/useColumnsCache';
|
||||
import { useMethods } from './hooks/useMethods';
|
||||
import { useDataSource } from './hooks/useDataSource';
|
||||
import { useDragSort } from './hooks/useDragSort';
|
||||
import { useRenderComponents } from './hooks/useRenderComponents';
|
||||
import { useFinallyProps } from './hooks/useFinallyProps';
|
||||
import { JVxeTableProps } from './types';
|
||||
import './style/index.less';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeTable',
|
||||
inheritAttrs: false,
|
||||
props: vxeProps(),
|
||||
emits: [...vxeEmits],
|
||||
setup(props: JVxeTableProps, context) {
|
||||
const instanceRef = ref();
|
||||
const refs = useRefs();
|
||||
const slots = useSlots();
|
||||
const data = useData(props);
|
||||
const { methods, publicMethods, created } = useMethods(props, context, data, refs, instanceRef);
|
||||
created();
|
||||
useColumns(props, data, methods, slots);
|
||||
useDataSource(props, data, methods, refs);
|
||||
useDragSort(props, methods);
|
||||
// update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-8566】JVXETable无法记住列设置
|
||||
const { initSetting } = useColumnsCache({ cacheColumnsKey: props.cacheColumnsKey });
|
||||
initSetting(props);
|
||||
// update-end--author:liaozhiyang---date:20240321---for:【QQYUN-8566】JVXETable无法记住列设置
|
||||
// 最终传入到 template 里的 props
|
||||
const finallyProps = useFinallyProps(props, data, methods);
|
||||
// 渲染子组件
|
||||
const renderComponents = useRenderComponents(props, data, methods, slots);
|
||||
return {
|
||||
instanceRef,
|
||||
...refs,
|
||||
...publicMethods,
|
||||
...finallyProps,
|
||||
...renderComponents,
|
||||
vxeDataSource: data.vxeDataSource,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: this.$attrs.class,
|
||||
style: this.$attrs.style,
|
||||
},
|
||||
h(
|
||||
rc('a-spin'),
|
||||
{
|
||||
spinning: this.loading,
|
||||
wrapperClassName: this.prefixCls,
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
this.renderSubPopover(),
|
||||
this.renderToolbar(),
|
||||
this.renderToolbarAfterSlot(),
|
||||
h(
|
||||
rc('vxe-grid'),
|
||||
{
|
||||
...this.vxeProps,
|
||||
data: this.vxeDataSource,
|
||||
},
|
||||
this.$slots
|
||||
),
|
||||
this.renderPagination(),
|
||||
this.renderDetailsModal(),
|
||||
],
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
created() {
|
||||
this.instanceRef = this;
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,97 @@
|
||||
import type { JVxeVueComponent } from './types';
|
||||
import { JVxeTypes } from './types/JVxeTypes';
|
||||
|
||||
import JVxeSlotCell from './components/cells/JVxeSlotCell';
|
||||
import JVxeNormalCell from './components/cells/JVxeNormalCell.vue';
|
||||
import JVxeDragSortCell from './components/cells/JVxeDragSortCell.vue';
|
||||
|
||||
import JVxeInputCell from './components/cells/JVxeInputCell.vue';
|
||||
import JVxeDateCell from './components/cells/JVxeDateCell.vue';
|
||||
import JVxeTimeCell from './components/cells/JVxeTimeCell.vue';
|
||||
import JVxeSelectCell from './components/cells/JVxeSelectCell.vue';
|
||||
import JVxeRadioCell from './components/cells/JVxeRadioCell.vue';
|
||||
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell.vue';
|
||||
import JVxeUploadCell from './components/cells/JVxeUploadCell.vue';
|
||||
// import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell.vue'
|
||||
import JVxeProgressCell from './components/cells/JVxeProgressCell.vue';
|
||||
import JVxeTextareaCell from './components/cells/JVxeTextareaCell.vue';
|
||||
// import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell.vue'
|
||||
// import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell.vue'
|
||||
|
||||
let componentMap = new Map<JVxeTypes | string, JVxeVueComponent>();
|
||||
// update-begin--author:liaozhiyang---date:20231208---for:【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
|
||||
const JVxeComponents = 'JVxeComponents__';
|
||||
if (import.meta.env.DEV && componentMap.size === 0 && window[JVxeComponents] && window[JVxeComponents].size > 0) {
|
||||
componentMap = window[JVxeComponents];
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231027---for:【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
|
||||
/** span 组件结尾 */
|
||||
export const spanEnds: string = ':span';
|
||||
|
||||
/** 定义不能用于注册的关键字 */
|
||||
export const excludeKeywords: Array<JVxeTypes> = [
|
||||
JVxeTypes.hidden,
|
||||
JVxeTypes.rowNumber,
|
||||
JVxeTypes.rowCheckbox,
|
||||
JVxeTypes.rowRadio,
|
||||
JVxeTypes.rowExpand,
|
||||
];
|
||||
|
||||
/**
|
||||
* 注册组件
|
||||
*
|
||||
* @param type 组件 type
|
||||
* @param component Vue组件
|
||||
* @param spanComponent 显示组件,可空,默认为 JVxeNormalCell 组件
|
||||
*/
|
||||
export function addComponent(type: JVxeTypes, component: JVxeVueComponent, spanComponent?: JVxeVueComponent) {
|
||||
if (excludeKeywords.includes(type)) {
|
||||
throw new Error(`【addComponent】不能使用"${type}"作为组件的name,因为这是关键字。`);
|
||||
}
|
||||
if (componentMap.has(type)) {
|
||||
throw new Error(`【addComponent】组件"${type}"已存在`);
|
||||
}
|
||||
componentMap.set(type, component);
|
||||
if (spanComponent) {
|
||||
componentMap.set(type + spanEnds, spanComponent);
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20231208---for:【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
|
||||
import.meta.env.DEV && (window[JVxeComponents] = componentMap);
|
||||
// update-end--author:liaozhiyang---date:20231208---for:【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
|
||||
}
|
||||
|
||||
export function deleteComponent(type: JVxeTypes) {
|
||||
componentMap.delete(type);
|
||||
componentMap.delete(type + spanEnds);
|
||||
// update-begin--author:liaozhiyang---date:20231208---for:【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
|
||||
import.meta.env.DEV && (window[JVxeComponents] = componentMap);
|
||||
// update-end--author:liaozhiyang---date:20231208---for:【issues/860】生成的一对多代码,热更新之后点击新增卡死[暂时先解决]
|
||||
}
|
||||
|
||||
/** 定义内置自定义组件 */
|
||||
export function definedComponent() {
|
||||
addComponent(JVxeTypes.slot, JVxeSlotCell);
|
||||
addComponent(JVxeTypes.normal, JVxeNormalCell);
|
||||
addComponent(JVxeTypes.rowDragSort, JVxeDragSortCell);
|
||||
|
||||
addComponent(JVxeTypes.input, JVxeInputCell);
|
||||
addComponent(JVxeTypes.inputNumber, JVxeInputCell);
|
||||
addComponent(JVxeTypes.radio, JVxeRadioCell);
|
||||
addComponent(JVxeTypes.checkbox, JVxeCheckboxCell);
|
||||
addComponent(JVxeTypes.select, JVxeSelectCell);
|
||||
addComponent(JVxeTypes.selectSearch, JVxeSelectCell); // 下拉搜索
|
||||
addComponent(JVxeTypes.selectMultiple, JVxeSelectCell); // 下拉多选
|
||||
addComponent(JVxeTypes.date, JVxeDateCell);
|
||||
addComponent(JVxeTypes.datetime, JVxeDateCell);
|
||||
addComponent(JVxeTypes.time, JVxeTimeCell);
|
||||
addComponent(JVxeTypes.upload, JVxeUploadCell);
|
||||
addComponent(JVxeTypes.textarea, JVxeTextareaCell);
|
||||
|
||||
// addComponent(JVxeTypes.tags, TagsInputCell, TagsSpanCell)
|
||||
addComponent(JVxeTypes.progress, JVxeProgressCell);
|
||||
|
||||
// addComponent(JVxeTypes.departSelect, JVxeDepartSelectCell)
|
||||
// addComponent(JVxeTypes.userSelect, JVxeUserSelectCell)
|
||||
}
|
||||
|
||||
export { componentMap };
|
||||
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<BasicModal @register="registerModel" title="详细信息" :width="1200" :keyboard="true" @ok="handleOk" @cancel="close">
|
||||
<transition name="fade">
|
||||
<div v-if="getVisible">
|
||||
<slot name="mainForm" :row="row" :column="column" />
|
||||
</div>
|
||||
</transition>
|
||||
</BasicModal>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useModal } from '/@/components/Modal/src/hooks/useModal';
|
||||
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BasicModal: createAsyncComponent(() => import('/@/components/Modal/src/BasicModal.vue'), {
|
||||
loading: true,
|
||||
}),
|
||||
},
|
||||
props: {
|
||||
trigger: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const row = ref(null);
|
||||
const column = ref(null);
|
||||
|
||||
const [registerModel, { openModal, closeModal, getVisible }] = useModal();
|
||||
|
||||
function open(event) {
|
||||
let { row: $row, column: $column } = event;
|
||||
row.value = cloneDeep($row);
|
||||
column.value = $column;
|
||||
openModal();
|
||||
}
|
||||
|
||||
function close() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
props.trigger('detailsConfirm', {
|
||||
row: row.value,
|
||||
column: column.value,
|
||||
callback: (success) => {
|
||||
success ? closeModal() : openModal();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getVisible,
|
||||
row,
|
||||
column,
|
||||
open,
|
||||
close,
|
||||
handleOk,
|
||||
registerModel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped 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,93 @@
|
||||
import { defineComponent, h, ref, watch } from 'vue';
|
||||
import { randomString } from '/@/utils/common/compUtils';
|
||||
import '../style/reload-effect.less';
|
||||
|
||||
// 修改数据特效
|
||||
export default defineComponent({
|
||||
props: {
|
||||
vNode: null,
|
||||
// 是否启用特效
|
||||
effect: Boolean,
|
||||
},
|
||||
emits: ['effectBegin', 'effectEnd'],
|
||||
setup(props, { emit }) {
|
||||
// vNode: null,
|
||||
const innerEffect = ref(props.effect);
|
||||
// 应付同时多个特效
|
||||
const effectIdx = ref(0);
|
||||
const effectList = ref<any[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.effect,
|
||||
() => (innerEffect.value = props.effect)
|
||||
);
|
||||
watch(
|
||||
() => props.vNode,
|
||||
(_vNode, old) => {
|
||||
if (props.effect && old != null) {
|
||||
let topLayer = renderSpan(old, 'top');
|
||||
effectList.value.push(topLayer);
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
|
||||
// 条件渲染内容 span
|
||||
function renderVNode() {
|
||||
if (props.vNode == null) {
|
||||
return null;
|
||||
}
|
||||
let bottom = renderSpan(props.vNode, 'bottom');
|
||||
// 启用了特效,并且有旧数据,就渲染特效顶层
|
||||
if (innerEffect.value && effectList.value.length > 0) {
|
||||
emit('effectBegin');
|
||||
// 1.4s 以后关闭特效
|
||||
window.setTimeout(() => {
|
||||
let item = effectList.value[effectIdx.value];
|
||||
if (item && item.elm) {
|
||||
// 特效结束后,展示先把 display 设为 none,而不是直接删掉该元素,
|
||||
// 目的是为了防止页面重新渲染,导致动画重置
|
||||
item.elm.style.display = 'none';
|
||||
}
|
||||
// 当所有的层级动画都结束时,再删掉所有元素
|
||||
if (++effectIdx.value === effectList.value.length) {
|
||||
innerEffect.value = false;
|
||||
effectIdx.value = 0;
|
||||
effectList.value = [];
|
||||
emit('effectEnd');
|
||||
}
|
||||
}, 1400);
|
||||
return [effectList.value, bottom];
|
||||
} else {
|
||||
return bottom;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染内容 span
|
||||
function renderSpan(vNode, layer) {
|
||||
let options = {
|
||||
key: layer + effectIdx.value + randomString(6),
|
||||
class: ['j-vxe-reload-effect-span', `layer-${layer}`],
|
||||
style: {},
|
||||
// update-begin--author:liaozhiyang---date:20240424---for:【issues/1175】解决vxetable鼠标hover之后title显示不对的问题
|
||||
title: vNode,
|
||||
// update-end--author:liaozhiyang---date:20240424---for:【issues/1175】解决vxetable鼠标hover之后title显示不对的问题
|
||||
|
||||
};
|
||||
if (layer === 'top') {
|
||||
// 最新渲染的在下面
|
||||
options.style['z-index'] = 9999 - effectIdx.value;
|
||||
}
|
||||
return h('span', options, [vNode]);
|
||||
}
|
||||
|
||||
return () =>
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
class: ['j-vxe-reload-effect-box'],
|
||||
},
|
||||
[renderVNode()]
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<a-popover :open="visible" :placement="placement" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
|
||||
<template #title>
|
||||
<div class="j-vxe-popover-title">
|
||||
<div>子表</div>
|
||||
<div class="j-vxe-popover-title-close" @click="close">
|
||||
<a-icon type="close" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<transition name="fade">
|
||||
<slot v-if="visible" name="subForm" :row="row" :column="column" />
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<div ref="divRef" class="j-vxe-popover-div"></div>
|
||||
</a-popover>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, reactive, nextTick, defineComponent } from 'vue';
|
||||
import domAlign from 'dom-align';
|
||||
import { getParentNodeByTagName } from '../utils/vxeUtils';
|
||||
import { triggerWindowResizeEvent } from '/@/utils/common/compUtils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { isString } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeSubPopover',
|
||||
setup() {
|
||||
const visible = ref(false);
|
||||
const row = ref<any>(null);
|
||||
const column = ref<any>(null);
|
||||
const overlayStyle = reactive<{
|
||||
width?: number | string;
|
||||
maxWidth?: number | string;
|
||||
zIndex: number;
|
||||
}>({
|
||||
zIndex: 100,
|
||||
});
|
||||
const placement = ref('bottom');
|
||||
const divRef = ref<HTMLElement>();
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
function toggle(event) {
|
||||
if (document.body.clientHeight - event.$event.clientY > 350) {
|
||||
placement.value = 'bottom';
|
||||
} else {
|
||||
placement.value = 'top';
|
||||
}
|
||||
if (row.value == null) {
|
||||
open(event);
|
||||
} else {
|
||||
row.value.id === event.row.id ? close() : reopen(event);
|
||||
}
|
||||
}
|
||||
|
||||
function open(event, level = 0) {
|
||||
if (level > 3) {
|
||||
createMessage.error('打开子表失败');
|
||||
console.warn('【JVxeSubPopover】打开子表失败');
|
||||
return;
|
||||
}
|
||||
|
||||
let {
|
||||
row: $row,
|
||||
column: $column,
|
||||
$table,
|
||||
$event: { target },
|
||||
} = event;
|
||||
row.value = cloneDeep($row);
|
||||
column.value = $column;
|
||||
|
||||
let className = target.className || '';
|
||||
className = isString(className) ? className : className.toString();
|
||||
|
||||
// 获取 td 父级
|
||||
let td = getParentNodeByTagName(target, 'td');
|
||||
// 点击的是拖拽排序列,不做处理
|
||||
if (td && td.querySelector('.j-vxe-drag-box')) {
|
||||
return;
|
||||
}
|
||||
// 点击的是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 parentElem = $table.getParentElem();
|
||||
let tr = getParentNodeByTagName(target, 'tr');
|
||||
if (parentElem && tr) {
|
||||
let clientWidth = parentElem.clientWidth;
|
||||
let clientHeight = tr.clientHeight;
|
||||
divRef.value!.style.width = clientWidth + 'px';
|
||||
divRef.value!.style.height = clientHeight + 'px';
|
||||
overlayStyle.width = Number.parseInt(`${clientWidth - clientWidth * 0.04}`) + 'px';
|
||||
overlayStyle.maxWidth = overlayStyle.width;
|
||||
//let realTable = getParentNodeByTagName(tr, 'table')
|
||||
//let left = realTable.parentNode.scrollLeft
|
||||
let h = event.$event.clientY;
|
||||
if (h) {
|
||||
h = h - 140;
|
||||
}
|
||||
let toolbar = divRef.value!.nextElementSibling;
|
||||
domAlign(divRef.value, toolbar, {
|
||||
points: ['tl', 'tl'],
|
||||
offset: [0, h],
|
||||
overflow: {
|
||||
alwaysByViewport: true,
|
||||
},
|
||||
});
|
||||
nextTick(() => {
|
||||
visible.value = true;
|
||||
nextTick(() => {
|
||||
triggerWindowResizeEvent();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
let num = ++level;
|
||||
console.warn('【JVxeSubPopover】table or tr 获取失败,正在进行第 ' + num + '次重试', {
|
||||
event,
|
||||
table: parentElem,
|
||||
tr,
|
||||
});
|
||||
window.setTimeout(() => open(event, num), 100);
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (visible.value) {
|
||||
row.value = null;
|
||||
visible.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function reopen(event) {
|
||||
open(event);
|
||||
}
|
||||
|
||||
return {
|
||||
divRef,
|
||||
row,
|
||||
column,
|
||||
visible,
|
||||
placement,
|
||||
overlayStyle,
|
||||
close,
|
||||
toggle,
|
||||
};
|
||||
},
|
||||
});
|
||||
</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>
|
||||
@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div :class="boxClass">
|
||||
<vxe-toolbar ref="xToolbarRef" :custom="custom">
|
||||
<!-- 工具按钮 -->
|
||||
<template #buttons>
|
||||
<div :class="`${prefixCls}-button div`" :size="btnSize">
|
||||
<slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize" />
|
||||
<a-button v-if="showAdd" type="primary" preIcon="ant-design:plus-outlined" :disabled="disabled" @click="trigger('add')">
|
||||
<span>新增</span>
|
||||
</a-button>
|
||||
<a-button v-if="showSave" preIcon="ant-design:save-outlined" :disabled="disabled" @click="trigger('save')">
|
||||
<span>保存</span>
|
||||
</a-button>
|
||||
<template v-if="selectedRowIds.length > 0">
|
||||
<Popconfirm v-if="showRemove" :title="`确定要删除这 ${selectedRowIds.length} 项吗?`" @confirm="trigger('remove')">
|
||||
<a-button preIcon="ant-design:minus-outlined" :disabled="disabled">删除</a-button>
|
||||
</Popconfirm>
|
||||
<template v-if="showClearSelection">
|
||||
<a-button preIcon="ant-design:delete-outlined" @click="trigger('clearSelection')">清空选择</a-button>
|
||||
</template>
|
||||
</template>
|
||||
<slot v-if="showSuffix" name="toolbarSuffix" :size="btnSize" />
|
||||
<a v-if="showCollapse" style="margin-left: 4px" @click="toggleCollapse">
|
||||
<span>{{ collapsed ? '展开' : '收起' }}</span>
|
||||
<Icon :icon="collapsed ? 'ant-design:down-outlined' : 'ant-design:up-outlined'" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, onMounted } from 'vue';
|
||||
// noinspection ES6UnusedImports
|
||||
import { Popconfirm } from 'ant-design-vue';
|
||||
import { VxeToolbarInstance } from 'vxe-table';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
const props = defineProps({
|
||||
size: propTypes.string,
|
||||
disabled: propTypes.bool.def(false),
|
||||
custom: propTypes.bool.def(false),
|
||||
toolbarConfig: propTypes.object,
|
||||
disabledRows: propTypes.object,
|
||||
hasBtnAuth: propTypes.func,
|
||||
selectedRowIds: propTypes.array.def(() => []),
|
||||
});
|
||||
const emit = defineEmits(['save', 'add', 'remove', 'clearSelection', 'register']);
|
||||
const xToolbarRef = ref({} as VxeToolbarInstance);
|
||||
const prefixCls = `${inject('prefixCls')}-toolbar`;
|
||||
const boxClass = computed(() => [
|
||||
prefixCls,
|
||||
{
|
||||
[`${prefixCls}-collapsed`]: collapsed.value,
|
||||
},
|
||||
]);
|
||||
// 是否收起
|
||||
const collapsed = ref(true);
|
||||
// 配置的按钮
|
||||
const btns = computed(() => {
|
||||
let { btn, btns } = props.toolbarConfig || {};
|
||||
btns = btn || btns || ['add', 'remove', 'clearSelection'];
|
||||
// 排除掉没有授权的按钮
|
||||
return btns.filter((btn) => {
|
||||
// 系统默认的批量删除编码配置为 batch_delete 此处需要兼容一下
|
||||
if (btn === 'remove') {
|
||||
//update-begin-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制
|
||||
return hasBtnAuth(btn) && hasBtnAuth('batch_delete');
|
||||
//update-end-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制
|
||||
}
|
||||
return hasBtnAuth(btn);
|
||||
});
|
||||
});
|
||||
const showAdd = computed(() => btns.value.includes('add'));
|
||||
const showSave = computed(() => btns.value.includes('save'));
|
||||
const showRemove = computed(() => btns.value.includes('remove'));
|
||||
// 配置的插槽
|
||||
const slots = computed(() => props.toolbarConfig?.slot || ['prefix', 'suffix']);
|
||||
const showPrefix = computed(() => slots.value.includes('prefix'));
|
||||
const showSuffix = computed(() => slots.value.includes('suffix'));
|
||||
// 是否显示清除选择按钮
|
||||
const showClearSelection = computed(() => {
|
||||
if (btns.value.includes('clearSelection')) {
|
||||
// 有禁用行时才显示清空选择按钮
|
||||
// 因为禁用行会阻止选择行,导致无法取消全选
|
||||
// return Object.keys(props.disabledRows).length > 0
|
||||
}
|
||||
return false;
|
||||
});
|
||||
// 是否显示展开收起按钮
|
||||
const showCollapse = computed(() => btns.value.includes('collapse'));
|
||||
// 按钮 size
|
||||
const btnSize = computed(() => (props.size === 'tiny' ? 'small' : null));
|
||||
|
||||
onMounted(() => {
|
||||
// 注册 vxe-toolbar
|
||||
emit('register', {
|
||||
xToolbarRef,
|
||||
});
|
||||
});
|
||||
|
||||
// 判断按钮是否已授权
|
||||
function hasBtnAuth(key: string) {
|
||||
return props.hasBtnAuth ? props.hasBtnAuth(key) : true;
|
||||
}
|
||||
|
||||
/** 触发事件 */
|
||||
function trigger(name) {
|
||||
emit(name);
|
||||
}
|
||||
|
||||
// 切换展开收起
|
||||
function toggleCollapse() {
|
||||
collapsed.value = !collapsed.value;
|
||||
}
|
||||
</script>
|
||||
@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div :class="boxClass" :style="boxStyle" title="">
|
||||
<a-checkbox :checked="innerValue" v-bind="cellProps" @change="handleChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { isArray, isBoolean } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeCheckboxCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, originColumn, scrolling, handleChangeCommon } = useJVxeComponent(props);
|
||||
// 是否启用边框
|
||||
const bordered = computed(() => !!props.renderOptions.bordered);
|
||||
// box 类名
|
||||
const boxClass = computed(() => {
|
||||
return {
|
||||
'j-vxe-checkbox': true,
|
||||
'no-animation': scrolling.value,
|
||||
};
|
||||
});
|
||||
// box 行内样式
|
||||
const boxStyle = computed(() => {
|
||||
const style = {};
|
||||
// 如果有边框且未设置align属性,就强制居中
|
||||
if (bordered.value && !originColumn.value.align) {
|
||||
style['text-align'] = 'center';
|
||||
}
|
||||
return style;
|
||||
});
|
||||
|
||||
// onChange 事件
|
||||
function handleChange(event) {
|
||||
handleChangeCommon(event.target.checked);
|
||||
}
|
||||
|
||||
return {
|
||||
cellProps,
|
||||
innerValue,
|
||||
boxClass,
|
||||
boxStyle,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
switches: {
|
||||
visible: true,
|
||||
},
|
||||
getValue(value, ctx) {
|
||||
let { context } = ctx!;
|
||||
let { originColumn } = context;
|
||||
// 处理 customValue
|
||||
if (isArray(originColumn.value.customValue)) {
|
||||
let customValue = getCustomValue(originColumn.value);
|
||||
if (isBoolean(value)) {
|
||||
return value ? customValue[0] : customValue[1];
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
setValue(value, ctx) {
|
||||
let { context } = ctx!;
|
||||
let { originColumn } = context;
|
||||
// 判断是否设定了customValue(自定义值)
|
||||
if (isArray(originColumn.value.customValue)) {
|
||||
let customValue = getCustomValue(originColumn.value);
|
||||
return neverNull(value).toString() === customValue[0].toString();
|
||||
} else {
|
||||
return !!value;
|
||||
}
|
||||
},
|
||||
createValue(_defaultValue, ctx) {
|
||||
let { context } = ctx!;
|
||||
let {
|
||||
column: { params: col },
|
||||
} = context;
|
||||
if (isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col);
|
||||
return col.defaultChecked ? customValue[0] : customValue[1];
|
||||
} else {
|
||||
return !!col.defaultChecked;
|
||||
}
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
|
||||
function neverNull(value, def?) {
|
||||
return value == null ? neverNull(def, '') : value;
|
||||
}
|
||||
|
||||
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,94 @@
|
||||
<template>
|
||||
<a-date-picker
|
||||
:value="innerDateValue"
|
||||
allowClear
|
||||
:format="picker ? null : dateFormat"
|
||||
:showTime="isDatetime"
|
||||
:valueFormat="picker ? dateFormat : null"
|
||||
popupClassName="j-vxe-date-picker"
|
||||
style="min-width: 0"
|
||||
v-model:open="openPicker"
|
||||
v-bind="cellProps"
|
||||
:picker="picker"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, watch, defineComponent } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { JVxeComponent, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { isEmpty } from '/@/utils/is';
|
||||
import { getWeekMonthQuarterYear } from '/@/utils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeDateCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, originColumn, handleChangeCommon } = useJVxeComponent(props);
|
||||
const innerDateValue = ref<any>(null);
|
||||
const isDatetime = computed(() => props.type === JVxeTypes.datetime);
|
||||
const dateFormat = computed(() => {
|
||||
let format = originColumn.value.format;
|
||||
return format ? format : isDatetime.value ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
|
||||
});
|
||||
const openPicker = ref(true);
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
const picker = computed(() => {
|
||||
const picker = originColumn.value.picker;
|
||||
return picker ? picker : null;
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
watch(
|
||||
innerValue,
|
||||
(val) => {
|
||||
if (val == null || isEmpty(val)) {
|
||||
innerDateValue.value = null;
|
||||
} else {
|
||||
innerDateValue.value = dayjs(val, dateFormat.value);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function handleChange(_mom, dateStr) {
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
if (picker.value) {
|
||||
handleChangeCommon(_mom);
|
||||
} else {
|
||||
handleChangeCommon(dateStr);
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
}
|
||||
|
||||
return {
|
||||
cellProps,
|
||||
isDatetime,
|
||||
dateFormat,
|
||||
innerDateValue,
|
||||
openPicker,
|
||||
handleChange,
|
||||
picker,
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
},
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
translate: {
|
||||
enabled: true,
|
||||
handler(value, ctx) {
|
||||
let { props, context } = ctx!;
|
||||
let { row, originColumn } = context;
|
||||
if (originColumn.value.picker && value) {
|
||||
return getWeekMonthQuarterYear(value)[originColumn.value.picker];
|
||||
}
|
||||
return value;
|
||||
},
|
||||
},
|
||||
// update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="j-vxe-drag-box">
|
||||
<span v-if="!isAllowDrag"><span class="not-drag-btn"> <Icon icon="mi:drag" /> </span
|
||||
></span>
|
||||
<a-dropdown v-else :trigger="['click']" >
|
||||
<span
|
||||
><span class="drag-btn"> <Icon icon="mi:drag" /> </span
|
||||
></span>
|
||||
<template #overlay >
|
||||
<a-menu>
|
||||
<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>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeDragSortCell',
|
||||
components: { Icon },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { rowIndex, originColumn, fullDataLength, trigger } = useJVxeComponent(props);
|
||||
// update-begin--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
const isAllowDrag = computed(() => {
|
||||
const notAllowDrag = originColumn.value.notAllowDrag;
|
||||
if (notAllowDrag.length) {
|
||||
const row = props.params.row;
|
||||
const find = notAllowDrag.find((item: any) => {
|
||||
const { key, value } = item;
|
||||
return row[key] == value;
|
||||
});
|
||||
return !find;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
const disabledMoveUp = computed(() => rowIndex.value === 0);
|
||||
const disabledMoveDown = computed(() => rowIndex.value === fullDataLength.value - 1);
|
||||
|
||||
/** 向上移 */
|
||||
function handleRowMoveUp() {
|
||||
if (!disabledMoveUp.value) {
|
||||
trigger('rowResort', {
|
||||
oldIndex: rowIndex.value,
|
||||
newIndex: rowIndex.value - 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 向下移 */
|
||||
function handleRowMoveDown() {
|
||||
if (!disabledMoveDown.value) {
|
||||
trigger('rowResort', {
|
||||
oldIndex: rowIndex.value,
|
||||
newIndex: rowIndex.value + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** 插入一行 */
|
||||
function handleRowInsertDown() {
|
||||
trigger('rowInsertDown', rowIndex.value);
|
||||
}
|
||||
|
||||
return {
|
||||
disabledMoveUp,
|
||||
disabledMoveDown,
|
||||
handleRowMoveUp,
|
||||
handleRowMoveDown,
|
||||
handleRowInsertDown,
|
||||
isAllowDrag
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
// 【功能开关】
|
||||
switches: {
|
||||
editRender: false,
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.j-vxe-drag-box {
|
||||
.app-iconify {
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
.vxe-table--fixed-wrapper {
|
||||
.j-vxe-drag-box {
|
||||
.app-iconify {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped>
|
||||
.not-drag-btn {
|
||||
opacity: 0.5;
|
||||
.app-iconify {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<a-input ref="input" :value="innerValue" v-bind="cellProps" @blur="handleBlur" @change="handleChange" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { isString } from '/@/utils/is';
|
||||
import { JVxeComponent, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
const NumberRegExp = /^-?\d+\.?\d*$/;
|
||||
export default defineComponent({
|
||||
name: 'JVxeInputCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, handleChangeCommon, handleBlurCommon } = useJVxeComponent(props);
|
||||
|
||||
/** 处理change事件 */
|
||||
function handleChange(event) {
|
||||
let { target } = event;
|
||||
let { value, selectionStart } = target;
|
||||
let change = true;
|
||||
if (props.type === JVxeTypes.inputNumber) {
|
||||
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
|
||||
if (!NumberRegExp.test(value) && value !== '' && value !== '-') {
|
||||
change = false;
|
||||
value = innerValue.value;
|
||||
target.value = value || '';
|
||||
if (typeof selectionStart === 'number') {
|
||||
target.selectionStart = selectionStart - 1;
|
||||
target.selectionEnd = selectionStart - 1;
|
||||
}
|
||||
} else {
|
||||
// update-begin--author:liaozhiyang---date:20240227---for:【QQYUN-8347】小数点后大于两位且最后一位是0,输入框不可输入了
|
||||
// 例如:41.1 -> 41.10, 100.1 -> 100.10 不执行handleChangeCommon 函数。
|
||||
if (value.indexOf('.') != -1) {
|
||||
const result = value.split('.').pop();
|
||||
if (result && result.length >= 2 && result.substr(-1) === '0') {
|
||||
change = false;
|
||||
innerValue.value = value;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240227---for:【QQYUN-8347】小数点后大于两位且最后一位是0,输入框不可输入了
|
||||
}
|
||||
}
|
||||
// 触发事件,存储输入的值
|
||||
if (change) {
|
||||
handleChangeCommon(value);
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理blur失去焦点事件 */
|
||||
function handleBlur(event) {
|
||||
let { target } = event;
|
||||
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
|
||||
if (props.type === JVxeTypes.inputNumber) {
|
||||
if (!NumberRegExp.test(target.value)) {
|
||||
target.value = '';
|
||||
} else {
|
||||
target.value = Number.parseFloat(target.value);
|
||||
}
|
||||
}
|
||||
handleChangeCommon(target.value, true);
|
||||
handleBlurCommon(target.value);
|
||||
}
|
||||
|
||||
return {
|
||||
innerValue,
|
||||
cellProps,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
};
|
||||
},
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
autofocus: '.ant-input',
|
||||
},
|
||||
getValue(value, ctx) {
|
||||
if (ctx?.props?.type === JVxeTypes.inputNumber && isString(value)) {
|
||||
if (NumberRegExp.test(value)) {
|
||||
// 【issues/I5IHN7】修复无法输入小数点的bug
|
||||
if (/\.0*$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return Number.parseFloat(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<JVxeReloadEffect :vNode="innerValue" :effect="isEffect" @effectEnd="handleEffectEnd" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent } from 'vue';
|
||||
import JVxeReloadEffect from '../JVxeReloadEffect';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { watch } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeNormalCell',
|
||||
components: { JVxeReloadEffect },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const setup = useJVxeComponent(props);
|
||||
const { innerValue, row } = setup;
|
||||
|
||||
const reloadEffect = props.renderOptions.reloadEffect;
|
||||
const isEffect = ref<boolean>(false);
|
||||
|
||||
watch(
|
||||
innerValue,
|
||||
() => {
|
||||
if (reloadEffect.enabled) {
|
||||
if (reloadEffect.isEffect(row.value.id)) {
|
||||
isEffect.value = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 特效结束
|
||||
function handleEffectEnd() {
|
||||
isEffect.value = false;
|
||||
reloadEffect.removeEffect(row.value.id);
|
||||
}
|
||||
|
||||
return {
|
||||
innerValue,
|
||||
isEffect,
|
||||
handleEffectEnd,
|
||||
};
|
||||
},
|
||||
enhanced: {
|
||||
switches: { editRender: false },
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<Progress :class="clazz" :percent="innerValue" size="small" v-bind="cellProps" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { Progress } from 'ant-design-vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeCheckboxCell',
|
||||
components: { Progress },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, scrolling } = useJVxeComponent(props);
|
||||
const clazz = computed(() => {
|
||||
return {
|
||||
'j-vxe-progress': true,
|
||||
'no-animation': scrolling.value,
|
||||
};
|
||||
});
|
||||
return { innerValue, cellProps, clazz };
|
||||
},
|
||||
// 【组件增强】注释详见::JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false,
|
||||
},
|
||||
setValue(value) {
|
||||
try {
|
||||
if (typeof value !== 'number') {
|
||||
return Number.parseFloat(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
// 关闭进度条的动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-progress.no-animation {
|
||||
:deep(.ant-progress-bg) {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<a-radio-group :class="clazz" :value="innerValue" v-bind="cellProps" @change="(e) => handleChangeCommon(e.target.value)">
|
||||
<a-radio v-for="item of originColumn.options" :key="item.value" :value="item.value" @click="(e) => handleRadioClick(item, e)"
|
||||
>{{ item.text || item.label || item.title || item.value }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeRadioCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, originColumn, handleChangeCommon } = useJVxeComponent(props);
|
||||
const scrolling = computed(() => !!props.renderOptions.scrolling);
|
||||
const clazz = computed(() => {
|
||||
return {
|
||||
'j-vxe-radio': true,
|
||||
'no-animation': scrolling.value,
|
||||
};
|
||||
});
|
||||
|
||||
function handleRadioClick(item) {
|
||||
if (originColumn.value.allowClear === true) {
|
||||
// 取消选择
|
||||
if (item.value === innerValue.value) {
|
||||
handleChangeCommon(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
clazz,
|
||||
innerValue,
|
||||
originColumn,
|
||||
cellProps,
|
||||
handleRadioClick,
|
||||
handleChangeCommon,
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
switches: { visible: true },
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// 关闭动画,防止滚动时动态赋值出现问题
|
||||
.j-vxe-radio.no-animation {
|
||||
.ant-radio-inner,
|
||||
.ant-radio-inner::after {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<a-select :value="innerValue" v-bind="selectProps">
|
||||
<template v-if="loading" #notFoundContent>
|
||||
<LoadingOutlined />
|
||||
<span> 加载中…</span>
|
||||
</template>
|
||||
<template v-for="option of selectOptions" :key="option.value">
|
||||
<a-select-option :value="option.value" :title="option.text || option.label || option.title" :disabled="option.disabled">
|
||||
<span>{{ option.text || option.label || option.title || option.value }}</span>
|
||||
</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, defineComponent } from 'vue';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
import { filterDictText } from '/@/utils/dict/JDictSelectUtil';
|
||||
import { JVxeComponent, JVxeTypes } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { isPromise } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeSelectCell',
|
||||
components: { LoadingOutlined },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, row, originColumn, scrolling, handleChangeCommon, handleBlurCommon } = useJVxeComponent(props);
|
||||
const loading = ref(false);
|
||||
// 异步加载的options(用于多级联动)
|
||||
const asyncOptions = ref<any[] | null>(null);
|
||||
// 下拉框 props
|
||||
const selectProps = computed(() => {
|
||||
let selProps = {
|
||||
...cellProps.value,
|
||||
allowClear: true,
|
||||
autofocus: true,
|
||||
defaultOpen: !scrolling.value,
|
||||
style: { width: '100%' },
|
||||
filterOption: handleSelectFilterOption,
|
||||
onBlur: handleBlur,
|
||||
onChange: handleChange,
|
||||
};
|
||||
// 判断select是否允许输入
|
||||
let { allowSearch, allowInput } = originColumn.value;
|
||||
if (allowInput === true || allowSearch === true) {
|
||||
selProps['showSearch'] = true;
|
||||
selProps['onSearch'] = handleSearchSelect;
|
||||
}
|
||||
return selProps;
|
||||
});
|
||||
// 下拉选项
|
||||
const selectOptions = computed(() => {
|
||||
if (asyncOptions.value) {
|
||||
return asyncOptions.value;
|
||||
}
|
||||
let { linkage } = props.renderOptions;
|
||||
if (linkage) {
|
||||
let { getLinkageOptionsSibling, config } = linkage;
|
||||
let res = getLinkageOptionsSibling(row.value, originColumn.value, config, true);
|
||||
// 当返回Promise时,说明是多级联动
|
||||
if (res instanceof Promise) {
|
||||
loading.value = true;
|
||||
res
|
||||
.then((opt) => {
|
||||
asyncOptions.value = opt;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
loading.value = false;
|
||||
});
|
||||
} else {
|
||||
asyncOptions.value = null;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return originColumn.value.options;
|
||||
});
|
||||
|
||||
// --------- created ---------
|
||||
|
||||
// 多选、搜索type
|
||||
let multipleTypes = [JVxeTypes.selectMultiple, 'list_multi'];
|
||||
let searchTypes = [JVxeTypes.selectSearch, 'sel_search'];
|
||||
if (multipleTypes.includes(props.type)) {
|
||||
// 处理多选
|
||||
let props = originColumn.value.props || {};
|
||||
props['mode'] = 'multiple';
|
||||
props['maxTagCount'] = 1;
|
||||
//update-begin-author:taoyan date:2022-12-5 for: issues/271 Online表单主子表单下拉多选无法搜索
|
||||
originColumn.value.allowSearch = true;
|
||||
//update-end-author:taoyan date:2022-12-5 for: issues/271 Online表单主子表单下拉多选无法搜索
|
||||
originColumn.value.props = props;
|
||||
} else if (searchTypes.includes(props.type)) {
|
||||
// 处理搜索
|
||||
originColumn.value.allowSearch = true;
|
||||
}
|
||||
|
||||
/** 处理 change 事件 */
|
||||
function handleChange(value) {
|
||||
// 处理下级联动
|
||||
let linkage = props.renderOptions.linkage;
|
||||
if (linkage) {
|
||||
linkage.handleLinkageSelectChange(row.value, originColumn.value, linkage.config, value);
|
||||
}
|
||||
handleChangeCommon(value);
|
||||
}
|
||||
|
||||
/** 处理blur失去焦点事件 */
|
||||
function handleBlur(value) {
|
||||
let { allowInput, options } = originColumn.value;
|
||||
if (allowInput === true) {
|
||||
// 删除无用的因搜索(用户输入)而创建的项
|
||||
if (typeof value === 'string') {
|
||||
let indexes: number[] = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
handleBlurCommon(value);
|
||||
}
|
||||
|
||||
/** 用于搜索下拉框中的内容 */
|
||||
function handleSelectFilterOption(input, option) {
|
||||
|
||||
let { allowSearch, allowInput } = originColumn.value;
|
||||
if (allowSearch === true || allowInput === true) {
|
||||
// update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options (防止option.title为null报错)
|
||||
if (option.title == null) return false;
|
||||
// update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options (防止option.title为null报错)
|
||||
// update-begin--author:liaozhiyang---date:20230904---for:【issues/5305】JVxeTypes.select 无法按照预期进行搜索
|
||||
return option.title.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
// update-begin--author:liaozhiyang---date:20230904---for:【issues/5305】JVxeTypes.select 无法按照预期进行搜索
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** select 搜索时的事件,用于动态添加options */
|
||||
function handleSearchSelect(value) {
|
||||
let { allowSearch, allowInput, options } = originColumn.value;
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
innerValue,
|
||||
selectProps,
|
||||
selectOptions,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived({ $event, row, column }) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
row,
|
||||
column,
|
||||
props: this.props,
|
||||
instance: this,
|
||||
className: '.ant-select .ant-select-selection-search-input',
|
||||
isClick: false,
|
||||
handler: (el) => el.focus(),
|
||||
});
|
||||
},
|
||||
},
|
||||
translate: {
|
||||
enabled: true,
|
||||
async handler(value, ctx) {
|
||||
let { props, context } = ctx!;
|
||||
let { row, originColumn } = context;
|
||||
let options;
|
||||
let linkage = props?.renderOptions.linkage;
|
||||
// 判断是否是多级联动,如果是就通过接口异步翻译
|
||||
if (linkage) {
|
||||
let { getLinkageOptionsSibling, config } = linkage;
|
||||
let linkageOptions = getLinkageOptionsSibling(row.value, originColumn.value, config, true);
|
||||
options = isPromise(linkageOptions) ? await linkageOptions : linkageOptions;
|
||||
} else if (isPromise(originColumn.value.optionsPromise)) {
|
||||
options = await originColumn.value.optionsPromise;
|
||||
} else {
|
||||
options = originColumn.value.options;
|
||||
}
|
||||
return filterDictText(options, value);
|
||||
},
|
||||
},
|
||||
getValue(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(',');
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
setValue(value, ctx) {
|
||||
let { context } = ctx!;
|
||||
let { originColumn } = context;
|
||||
// 判断是否是多选
|
||||
if ((originColumn.value.props || {})['mode'] === 'multiple') {
|
||||
originColumn.value.props['maxTagCount'] = 1;
|
||||
}
|
||||
if (value != null && value !== '') {
|
||||
if (typeof value === 'string') {
|
||||
return value === '' ? [] : value.split(',');
|
||||
}
|
||||
return value;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,41 @@
|
||||
import { computed, defineComponent, h } from 'vue';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/src/hooks/useJVxeComponent';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/src/types/JVxeComponent';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeSlotCell',
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const data = useJVxeComponent(props);
|
||||
const slotProps = computed(() => {
|
||||
return {
|
||||
value: data.innerValue.value,
|
||||
row: data.row.value,
|
||||
column: data.originColumn.value,
|
||||
params: props.params,
|
||||
$table: props.params.$table,
|
||||
rowId: props.params.rowid,
|
||||
index: props.params.rowIndex,
|
||||
rowIndex: props.params.rowIndex,
|
||||
columnIndex: props.params.columnIndex,
|
||||
scrolling: props.renderOptions.scrolling,
|
||||
reloadEffect: props.renderOptions.reloadEffect.enabled,
|
||||
triggerChange: (v) => data.handleChangeCommon(v),
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
let { slot } = props.renderOptions;
|
||||
if (slot) {
|
||||
return h('div', {}, slot(slotProps.value));
|
||||
} else {
|
||||
return h('div');
|
||||
}
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
switches: {
|
||||
editRender: false,
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<JInputPop
|
||||
:value="innerValue"
|
||||
:width="300"
|
||||
:height="210"
|
||||
:pop-container="getPopupContainer"
|
||||
v-bind="cellProps"
|
||||
style="width: 100%"
|
||||
@blur="handleBlurCommon"
|
||||
@change="handleChangeCommon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import JInputPop from '/@/components/Form/src/jeecg/components/JInputPop.vue';
|
||||
import { dispatchEvent } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeTextareaCell',
|
||||
components: { JInputPop },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, handleChangeCommon, handleBlurCommon } = useJVxeComponent(props);
|
||||
|
||||
function getPopupContainer() {
|
||||
return document.body;
|
||||
}
|
||||
|
||||
return { innerValue, cellProps, handleChangeCommon, handleBlurCommon, getPopupContainer };
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
autofocus: '.ant-input',
|
||||
},
|
||||
aopEvents: {
|
||||
editActived({ $event, row, column }) {
|
||||
// 是否默认打开右侧弹窗
|
||||
if (column.params.defaultOpen ?? false) {
|
||||
dispatchEvent({
|
||||
$event,
|
||||
row,
|
||||
column,
|
||||
props: this.props,
|
||||
instance: this,
|
||||
className: '.ant-input-suffix .app-iconify',
|
||||
isClick: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<TimePicker
|
||||
:value="innerTimeValue"
|
||||
allowClear
|
||||
:format="format"
|
||||
popupClassName="j-vxe-time-picker"
|
||||
style="min-width: 0"
|
||||
v-model:open="openPicker"
|
||||
v-bind="cellProps"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, computed, watch, defineComponent } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { TimePicker } from 'ant-design-vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeComponent, useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { isEmpty } from '/@/utils/is';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeTimeCell',
|
||||
components: { TimePicker },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const { innerValue, cellProps, originColumn, handleChangeCommon } = useJVxeComponent(props);
|
||||
const innerTimeValue = ref<any>(null);
|
||||
const format = computed(() => {
|
||||
let format = originColumn.value.format;
|
||||
return format ? format : 'HH:mm:ss';
|
||||
});
|
||||
const openPicker = ref(true);
|
||||
watch(
|
||||
innerValue,
|
||||
(val) => {
|
||||
if (val == null || isEmpty(val)) {
|
||||
innerTimeValue.value = null;
|
||||
} else {
|
||||
innerTimeValue.value = dayjs(val, format.value);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function handleChange(_mom, dateStr) {
|
||||
handleChangeCommon(dateStr);
|
||||
}
|
||||
|
||||
return {
|
||||
cellProps,
|
||||
format,
|
||||
innerTimeValue,
|
||||
openPicker,
|
||||
handleChange,
|
||||
};
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
},
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" v-for="(file, fileKey) of [innerFile || {}]" :key="fileKey">
|
||||
<a-input :readOnly="true" :value="file.name">
|
||||
<template #addonBefore style="width: 30px">
|
||||
<a-tooltip v-if="file.status === 'uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
|
||||
<LoadingOutlined />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else-if="file.status === 'done'" title="上传完成">
|
||||
<Icon icon="ant-design:check-circle-outlined" style="color: #00db00" />
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else :title="file.message || '上传失败'">
|
||||
<Icon icon="ant-design:exclamation-circle-outlined" style="color: red" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<span v-if="file.status === 'uploading'" slot="addonAfter">{{ Math.floor(file.percent) }}%</span>
|
||||
<template v-if="originColumn.allowDownload !== false || originColumn.allowRemove !== false" #addonAfter>
|
||||
<Dropdown :trigger="['click']" placement="bottomRight">
|
||||
<a-tooltip title="操作">
|
||||
<Icon icon="ant-design:setting-outlined" style="cursor: pointer" />
|
||||
</a-tooltip>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
|
||||
<span><Icon icon="ant-design:download-outlined" /> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><Icon icon="ant-design:delete-outlined" /> 删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</Dropdown>
|
||||
</template>
|
||||
</a-input>
|
||||
</template>
|
||||
<a-upload
|
||||
v-if="!cellProps.disabledTable"
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{ isup: 1 }"
|
||||
:multiple="false"
|
||||
:action="originColumn.action"
|
||||
:headers="uploadHeaders"
|
||||
:showUploadList="false"
|
||||
v-bind="cellProps"
|
||||
@change="handleChangeUpload"
|
||||
>
|
||||
<a-button preIcon="ant-design:upload-outlined">{{ originColumn.btnText || '点击上传' }}</a-button>
|
||||
</a-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { Dropdown } from 'ant-design-vue';
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue';
|
||||
import { JVxeComponent } from '/@/components/jeecg/JVxeTable/types';
|
||||
import { useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
import { useJVxeUploadCell, fileGetValue, fileSetValue } from '../../hooks/cells/useJVxeUploadCell';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JVxeUploadCell',
|
||||
components: { Icon, Dropdown, LoadingOutlined },
|
||||
props: useJVxeCompProps(),
|
||||
setup(props: JVxeComponent.Props) {
|
||||
const setup = useJVxeUploadCell(props);
|
||||
return { ...setup };
|
||||
},
|
||||
// 【组件增强】注释详见::JVxeComponent.Enhanced
|
||||
enhanced: {
|
||||
switches: { visible: true },
|
||||
getValue: (value) => fileGetValue(value),
|
||||
setValue: (value) => fileSetValue(value),
|
||||
} as JVxeComponent.EnhancedPartial,
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,139 @@
|
||||
import { ref, computed, watch } from 'vue';
|
||||
|
||||
import {getTenantId, getToken} from '/@/utils/auth';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { JVxeComponent } from '../../types/JVxeComponent';
|
||||
import { useJVxeComponent } from '../useJVxeComponent';
|
||||
|
||||
/**
|
||||
* use 公共上传组件
|
||||
* @param props
|
||||
* @param options 组件选项,token:默认是否传递token,action:默认上传路径,multiple:是否允许多文件
|
||||
*/
|
||||
export function useJVxeUploadCell(props: JVxeComponent.Props, options?) {
|
||||
const setup = useJVxeComponent(props);
|
||||
const { innerValue, originColumn, handleChangeCommon } = setup;
|
||||
|
||||
const innerFile = ref<any>(null);
|
||||
|
||||
/** upload headers */
|
||||
const uploadHeaders = computed(() => {
|
||||
let headers = {};
|
||||
if ((originColumn.value.token ?? options?.token ?? false) === true) {
|
||||
headers['X-Access-Token'] = getToken();
|
||||
}
|
||||
let tenantId = getTenantId();
|
||||
headers['X-Tenant-Id'] = tenantId ? tenantId : '0';
|
||||
return headers;
|
||||
});
|
||||
|
||||
/** 上传请求地址 */
|
||||
const uploadAction = computed(() => {
|
||||
if (!originColumn.value.action) {
|
||||
return options?.action ?? '';
|
||||
} else {
|
||||
return originColumn.value.action;
|
||||
}
|
||||
});
|
||||
const hasFile = computed(() => innerFile.value != null);
|
||||
const responseName = computed(() => originColumn.value.responseName ?? 'message');
|
||||
|
||||
watch(
|
||||
innerValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
innerFile.value = val;
|
||||
} else {
|
||||
innerFile.value = null;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function handleChangeUpload(info) {
|
||||
let { file } = info;
|
||||
let value = {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
status: file.status,
|
||||
percent: file.percent,
|
||||
path: innerFile.value?.path ?? '',
|
||||
};
|
||||
if (file.response) {
|
||||
value['responseName'] = file.response[responseName.value];
|
||||
}
|
||||
let paths: string[] = [];
|
||||
if (options?.multiple && innerFile.value && innerFile.value.path) {
|
||||
paths = innerFile.value.path.split(',');
|
||||
}
|
||||
if (file.status === 'done') {
|
||||
if (typeof file.response.success === 'boolean') {
|
||||
if (file.response.success) {
|
||||
paths.push(file.response[responseName.value]);
|
||||
value['path'] = paths.join(',');
|
||||
handleChangeCommon(value);
|
||||
} else {
|
||||
value['status'] = 'error';
|
||||
value['message'] = file.response.message || '未知错误';
|
||||
}
|
||||
} else {
|
||||
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
|
||||
paths.push(file.response[responseName.value]);
|
||||
value['path'] = paths.join(',');
|
||||
handleChangeCommon(value);
|
||||
}
|
||||
} else if (file.status === 'error') {
|
||||
value['message'] = file.response.message || '未知错误';
|
||||
}
|
||||
innerFile.value = value;
|
||||
}
|
||||
|
||||
function handleClickDownloadFile() {
|
||||
let { url, path } = innerFile.value || {};
|
||||
if (!url || url.length === 0) {
|
||||
if (path && path.length > 0) {
|
||||
url = getFileAccessHttpUrl(path.split(',')[0]);
|
||||
}
|
||||
}
|
||||
if (url) {
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickDeleteFile() {
|
||||
handleChangeCommon(null);
|
||||
}
|
||||
|
||||
return {
|
||||
...setup,
|
||||
innerFile,
|
||||
uploadAction,
|
||||
uploadHeaders,
|
||||
hasFile,
|
||||
responseName,
|
||||
handleChangeUpload,
|
||||
handleClickDownloadFile,
|
||||
handleClickDeleteFile,
|
||||
};
|
||||
}
|
||||
|
||||
export function fileGetValue(value) {
|
||||
if (value && value.path) {
|
||||
return value.path;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
@ -0,0 +1,420 @@
|
||||
import type { JVxeColumn, JVxeDataProps, JVxeTableProps } from '../types';
|
||||
import { computed, nextTick } from 'vue';
|
||||
import { isArray, isEmpty, isPromise } from '/@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { JVxeTypePrefix, JVxeTypes } from '../types/JVxeTypes';
|
||||
import { initDictOptions } from '/@/utils/dict';
|
||||
import { pushIfNotExist } from '/@/utils/common/compUtils';
|
||||
import { getEnhanced } from '../utils/enhancedUtils';
|
||||
import { isRegistered } from '../utils/registerUtils';
|
||||
import { JVxeComponent } from '../types/JVxeComponent';
|
||||
import { useValidateRules } from './useValidateRules';
|
||||
import { JVxeTableMethods } from '../types';
|
||||
|
||||
// handle 方法参数
|
||||
export interface HandleArgs {
|
||||
props: JVxeTableProps;
|
||||
slots: any;
|
||||
data: JVxeDataProps;
|
||||
methods: JVxeTableMethods;
|
||||
col?: JVxeColumn;
|
||||
columns: JVxeColumn[];
|
||||
renderOptions?: any;
|
||||
enhanced?: JVxeComponent.Enhanced;
|
||||
}
|
||||
|
||||
export function useColumns(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods, slots) {
|
||||
data.vxeColumns = computed(() => {
|
||||
let columns: JVxeColumn[] = [];
|
||||
if (isArray(props.columns)) {
|
||||
// handle 方法参数
|
||||
const args: HandleArgs = { props, slots, data, methods, columns };
|
||||
let seqColumn, selectionColumn, expandColumn, dragSortColumn;
|
||||
props.columns.forEach((column: JVxeColumn) => {
|
||||
// 排除未授权的列 1 = 显示/隐藏; 2 = 禁用
|
||||
let auth = methods.getColAuth(column.key);
|
||||
if (auth?.type == '1' && !auth.isAuth) {
|
||||
return;
|
||||
} else if (auth?.type == '2' && !auth.isAuth) {
|
||||
column.disabled = true;
|
||||
}
|
||||
// type 不填,默认为 normal
|
||||
if (column.type == null || isEmpty(column.type)) {
|
||||
column.type = JVxeTypes.normal;
|
||||
}
|
||||
let col: JVxeColumn = cloneDeep(column);
|
||||
// 处理隐藏列
|
||||
if (col.type === JVxeTypes.hidden) {
|
||||
return handleInnerColumn(args, col, handleHiddenColumn);
|
||||
}
|
||||
// 组件未注册,自动设置为 normal
|
||||
if (!isRegistered(col.type)) {
|
||||
col.type = JVxeTypes.normal;
|
||||
}
|
||||
args.enhanced = getEnhanced(col.type);
|
||||
args.col = col;
|
||||
args.renderOptions = {
|
||||
bordered: props.bordered,
|
||||
disabled: props.disabled,
|
||||
scrolling: data.scrolling,
|
||||
isDisabledRow: methods.isDisabledRow,
|
||||
listeners: {
|
||||
trigger: (name, event) => methods.trigger(name, event),
|
||||
valueChange: (event) => methods.trigger('valueChange', event),
|
||||
/** 重新排序行 */
|
||||
rowResort: (event) => {
|
||||
methods.doSort(event.oldIndex, event.newIndex);
|
||||
methods.trigger('dragged', event);
|
||||
},
|
||||
/** 在当前行下面插入一行 */
|
||||
rowInsertDown: (rowIndex) => methods.insertRows({}, rowIndex + 1),
|
||||
},
|
||||
};
|
||||
if (col.type === JVxeTypes.rowNumber) {
|
||||
seqColumn = col;
|
||||
columns.push(col);
|
||||
} else if (col.type === JVxeTypes.rowRadio || col.type === JVxeTypes.rowCheckbox) {
|
||||
selectionColumn = col;
|
||||
columns.push(col);
|
||||
} else if (col.type === JVxeTypes.rowExpand) {
|
||||
expandColumn = col;
|
||||
columns.push(col);
|
||||
} else if (col.type === JVxeTypes.rowDragSort) {
|
||||
dragSortColumn = col;
|
||||
columns.push(col);
|
||||
} else {
|
||||
col.params = column;
|
||||
handlerCol(args);
|
||||
}
|
||||
});
|
||||
handleInnerColumn(args, seqColumn, handleSeqColumn);
|
||||
handleInnerColumn(args, selectionColumn, handleSelectionColumn);
|
||||
handleInnerColumn(args, expandColumn, handleExpandColumn);
|
||||
handleInnerColumn(args, dragSortColumn, handleDragSortColumn, true);
|
||||
// update-begin--author:liaozhiyang---date:2024-05-30---for【TV360X-371】不可编辑组件必填缺少*号
|
||||
customComponentAddStar(columns);
|
||||
// update-end--author:liaozhiyang---date:2024-05-30---for:【TV360X-371】不可编辑组件必填缺少*号
|
||||
}
|
||||
return columns;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 2024-05-30
|
||||
* liaozhiyang
|
||||
* 不可编辑组件必填通过title人为加*号
|
||||
*/
|
||||
function customComponentAddStar(columns) {
|
||||
columns.forEach((column) => {
|
||||
const { params } = column;
|
||||
if (params) {
|
||||
const { validateRules, type } = params;
|
||||
if (
|
||||
validateRules?.length &&
|
||||
[
|
||||
JVxeTypes.checkbox,
|
||||
JVxeTypes.radio,
|
||||
JVxeTypes.upload,
|
||||
JVxeTypes.progress,
|
||||
JVxeTypes.departSelect,
|
||||
JVxeTypes.userSelect,
|
||||
JVxeTypes.image,
|
||||
JVxeTypes.file,
|
||||
].includes(type)
|
||||
) {
|
||||
if (validateRules.find((item) => item.required)) {
|
||||
column.title = ` * ${column.title}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 处理内置列 */
|
||||
function handleInnerColumn(args: HandleArgs, col: JVxeColumn, handler: (args: HandleArgs) => void, assign?: boolean) {
|
||||
let renderOptions = col?.editRender || col?.cellRender;
|
||||
return handler({
|
||||
...args,
|
||||
col: col,
|
||||
renderOptions: assign ? Object.assign({}, args.renderOptions, renderOptions) : renderOptions,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理隐藏列
|
||||
*/
|
||||
function handleHiddenColumn({ col, columns }: HandleArgs) {
|
||||
col!.params = cloneDeep(col);
|
||||
delete col!.type;
|
||||
col!.field = col!.key;
|
||||
col!.visible = false;
|
||||
columns.push(col!);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理行号列
|
||||
*/
|
||||
function handleSeqColumn({ props, col, columns }: HandleArgs) {
|
||||
// 判断是否开启了行号列
|
||||
if (props.rowNumber) {
|
||||
let column = {
|
||||
type: 'seq',
|
||||
title: '#',
|
||||
width: 60,
|
||||
// 【QQYUN-8405】
|
||||
fixed: props.rowNumberFixed,
|
||||
align: 'center',
|
||||
};
|
||||
// update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8405】vxetable支持序号是否固定(移动端需要)
|
||||
if (props.rowNumberFixed === 'none') {
|
||||
delete column.fixed;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240306---for:QQYUN-8405】vxetable支持序号是否固定(移动端需要)
|
||||
if (col) {
|
||||
Object.assign(col, column);
|
||||
} else {
|
||||
columns.unshift(column as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理可选择列
|
||||
*/
|
||||
function handleSelectionColumn({ props, data, col, columns }: HandleArgs) {
|
||||
// 判断是否开启了可选择行
|
||||
if (props.rowSelection) {
|
||||
let width = 45;
|
||||
if (data.statistics.has && !props.rowExpand && !props.dragSort) {
|
||||
width = 60;
|
||||
}
|
||||
let column: any = {
|
||||
type: props.rowSelectionType,
|
||||
width: width,
|
||||
fixed: 'left',
|
||||
align: 'center',
|
||||
};
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序
|
||||
if (props.rowSelectionFixed === 'none') {
|
||||
delete column.fixed;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240509---for:【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序
|
||||
if (col) {
|
||||
Object.assign(col, column);
|
||||
} else {
|
||||
columns.unshift(column as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理可展开行
|
||||
*/
|
||||
function handleExpandColumn({ props, data, col, columns }: HandleArgs) {
|
||||
// 是否可展开行
|
||||
if (props.rowExpand) {
|
||||
let width = 40;
|
||||
if (data.statistics.has && !props.dragSort) {
|
||||
width = 60;
|
||||
}
|
||||
let column = {
|
||||
type: 'expand',
|
||||
title: '',
|
||||
width: width,
|
||||
fixed: 'left',
|
||||
align: 'center',
|
||||
slots: { content: 'expandContent' },
|
||||
};
|
||||
if (col) {
|
||||
Object.assign(col, column);
|
||||
} else {
|
||||
columns.unshift(column as any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理可排序列 */
|
||||
function handleDragSortColumn({ props, data, col, columns, renderOptions }: HandleArgs) {
|
||||
// 是否可拖动排序
|
||||
if (props.dragSort) {
|
||||
let width = 40;
|
||||
if (data.statistics.has) {
|
||||
width = 60;
|
||||
}
|
||||
let column: any = {
|
||||
title: '',
|
||||
width: width,
|
||||
fixed: 'left',
|
||||
align: 'center',
|
||||
// update-begin--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
params: {
|
||||
notAllowDrag: props.notAllowDrag,
|
||||
...col?.params,
|
||||
},
|
||||
// update-end--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
};
|
||||
// update-begin--author:liaozhiyang---date:20240506---for:【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序
|
||||
if (props.dragSortFixed === 'none') {
|
||||
delete column.fixed;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240506---for:【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序
|
||||
let cellRender = {
|
||||
name: JVxeTypePrefix + JVxeTypes.rowDragSort,
|
||||
sortKey: props.sortKey,
|
||||
};
|
||||
if (renderOptions) {
|
||||
column.cellRender = Object.assign(renderOptions, cellRender);
|
||||
} else {
|
||||
column.cellRender = cellRender;
|
||||
}
|
||||
if (col) {
|
||||
Object.assign(col, column);
|
||||
} else {
|
||||
columns.unshift(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理自定义组件列 */
|
||||
function handlerCol(args: HandleArgs) {
|
||||
const { props, col, columns, enhanced } = args;
|
||||
if (!col) return;
|
||||
let { type } = col;
|
||||
col.field = col.key;
|
||||
delete col.type;
|
||||
let renderName = 'cellRender';
|
||||
// 渲染选项
|
||||
let $renderOptions: any = { name: JVxeTypePrefix + type };
|
||||
if (enhanced?.switches.editRender) {
|
||||
if (!(enhanced.switches.visible || props.alwaysEdit)) {
|
||||
renderName = 'editRender';
|
||||
}
|
||||
// $renderOptions.type = (enhanced.switches.visible || props.alwaysEdit) ? 'visible' : 'default'
|
||||
}
|
||||
col[renderName] = $renderOptions;
|
||||
// update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options(添加customOptions为true不读字典,走自己的options)
|
||||
!col.params.customOptions && handleDict(args);
|
||||
// update-end--author:liaozhiyang---date:20240321---for:【QQYUN-5806】js增强改变下拉搜索options(添加customOptions为true不读字典,走自己的options)
|
||||
handleRules(args);
|
||||
handleStatistics(args);
|
||||
handleSlots(args);
|
||||
handleLinkage(args);
|
||||
handleReloadEffect(args);
|
||||
|
||||
if (col.editRender) {
|
||||
Object.assign(col.editRender, args.renderOptions);
|
||||
}
|
||||
if (col.cellRender) {
|
||||
Object.assign(col.cellRender, args.renderOptions);
|
||||
}
|
||||
|
||||
columns.push(col);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理字典
|
||||
*/
|
||||
async function handleDict({ col, methods }: HandleArgs) {
|
||||
if (col && col.params.dictCode) {
|
||||
/** 加载数据字典并合并到 options */
|
||||
try {
|
||||
// 查询字典
|
||||
if (!isPromise(col.params.optionsPromise)) {
|
||||
col.params.optionsPromise = new Promise(async (resolve) => {
|
||||
//update-begin-author:taoyan date:2022-6-1 for: VUEN-1180 【代码生成】子表不支持带条件?
|
||||
let dictCodeString = col.params.dictCode;
|
||||
if (dictCodeString) {
|
||||
dictCodeString = encodeURI(dictCodeString);
|
||||
}
|
||||
const dictOptions: any = await initDictOptions(dictCodeString);
|
||||
//update-end-author:taoyan date:2022-6-1 for: VUEN-1180 【代码生成】子表不支持带条件?
|
||||
let options = col.params.options ?? [];
|
||||
dictOptions.forEach((dict) => {
|
||||
// 过滤重复数据
|
||||
if (options.findIndex((o) => o.value === dict.value) === -1) {
|
||||
options.push(dict);
|
||||
}
|
||||
});
|
||||
resolve(options);
|
||||
});
|
||||
}
|
||||
col.params.options = await col.params.optionsPromise;
|
||||
await nextTick();
|
||||
await methods.getXTable().updateData();
|
||||
} catch (e) {
|
||||
console.group(`[JVxeTable] 查询字典 "${col.params.dictCode}" 时发生异常!`);
|
||||
console.warn(e);
|
||||
console.groupEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理校验
|
||||
*/
|
||||
function handleRules(args: HandleArgs) {
|
||||
if (isArray(args.col?.validateRules)) {
|
||||
useValidateRules(args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理统计列
|
||||
*/
|
||||
function handleStatistics({ col, data }: HandleArgs) {
|
||||
// sum = 求和、average = 平均值
|
||||
if (col && isArray(col.statistics)) {
|
||||
data.statistics.has = true;
|
||||
col.statistics.forEach((item) => {
|
||||
if (!isEmpty(item)) {
|
||||
let arr = data.statistics[(item as string).toLowerCase()];
|
||||
if (isArray(arr)) {
|
||||
pushIfNotExist(arr, col.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理插槽
|
||||
*/
|
||||
function handleSlots({ slots, col, renderOptions }: HandleArgs) {
|
||||
// slot 组件特殊处理
|
||||
if (col && col.params.type === JVxeTypes.slot) {
|
||||
if (!isEmpty(col.slotName) && slots.hasOwnProperty(col.slotName)) {
|
||||
renderOptions.slot = slots[col.slotName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理联动列 */
|
||||
function handleLinkage({ data, col, renderOptions, methods }: HandleArgs) {
|
||||
// 处理联动列,联动列只能作用于 select 组件
|
||||
if (col && col.params.type === JVxeTypes.select && data.innerLinkageConfig != null) {
|
||||
// 判断当前列是否是联动列
|
||||
if (data.innerLinkageConfig.has(col.key)) {
|
||||
renderOptions.linkage = {
|
||||
config: data.innerLinkageConfig.get(col.key),
|
||||
getLinkageOptionsAsync: methods.getLinkageOptionsAsync,
|
||||
getLinkageOptionsSibling: methods.getLinkageOptionsSibling,
|
||||
handleLinkageSelectChange: methods.handleLinkageSelectChange,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleReloadEffect({ props, data, renderOptions }: HandleArgs) {
|
||||
renderOptions.reloadEffect = {
|
||||
enabled: props.reloadEffect,
|
||||
getMap() {
|
||||
return data.reloadEffectRowKeysMap;
|
||||
},
|
||||
isEffect(rowId) {
|
||||
return data.reloadEffectRowKeysMap[rowId] === true;
|
||||
},
|
||||
removeEffect(rowId) {
|
||||
return (data.reloadEffectRowKeysMap[rowId] = false);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
import { computed } from 'vue';
|
||||
import { router } from '/@/router';
|
||||
import { createLocalStorage } from '/@/utils/cache';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
|
||||
export function useColumnsCache({ cacheColumnsKey, refs }: any) {
|
||||
const $ls = createLocalStorage();
|
||||
const { createMessage: $message } = useMessage();
|
||||
const cacheKey = computed(() => {
|
||||
const path = router.currentRoute.value.fullPath;
|
||||
let key = path.replace(/[\/\\]/g, '_');
|
||||
if (cacheColumnsKey) {
|
||||
key += ':' + cacheColumnsKey;
|
||||
}
|
||||
return 'vxe-columnCache:' + key;
|
||||
});
|
||||
const initSetting = (props) => {
|
||||
const columnCache = $ls.get(cacheKey.value);
|
||||
if (columnCache) {
|
||||
columnCache.forEach((key) => {
|
||||
const column = props.columns.find((item) => item.key === key);
|
||||
if (column) {
|
||||
column.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
// const initSetting = (refs) => {
|
||||
// let columnCache = $ls.get(cacheKey.value);
|
||||
// if (columnCache) {
|
||||
// const $grid = refs.gridRef.value!.getRefMaps().refTable.value;
|
||||
// console.log('refs.gridRef', $grid);
|
||||
// const { fullColumn } = $grid.getTableColumn();
|
||||
// const hideColumns = getHideColumn(fullColumn, columnCache);
|
||||
// if (hideColumns?.length) {
|
||||
// hideColumns.forEach((column) => {
|
||||
// $grid.hideColumn(column);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// console.log(columnCache);
|
||||
// };
|
||||
function saveSetting($grid: any) {
|
||||
console.log($grid);
|
||||
const { fullColumn, visibleColumn } = $grid.getTableColumn();
|
||||
const hideColumnKey = getHideColumnKey(fullColumn, visibleColumn);
|
||||
if (hideColumnKey.length) {
|
||||
$ls.set(cacheKey.value, hideColumnKey);
|
||||
$message.success('保存成功');
|
||||
}
|
||||
}
|
||||
const resetSetting = ($grid) => {
|
||||
const columnCache = $ls.get(cacheKey.value);
|
||||
if (columnCache) {
|
||||
const { fullColumn } = $grid.getTableColumn();
|
||||
const hideColumns = getHideColumn(fullColumn, columnCache);
|
||||
if (hideColumns?.length) {
|
||||
hideColumns.forEach((column) => {
|
||||
if (columnCache.includes(column?.params?.key)) {
|
||||
$grid.showColumn(column);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
$ls.remove(cacheKey.value);
|
||||
$message.success('重置成功');
|
||||
};
|
||||
const getHideColumn = (fullColumn, columnCache) => {
|
||||
const result: any = [];
|
||||
if (columnCache?.length) {
|
||||
console.log('--fullColumn:',fullColumn);
|
||||
columnCache.forEach((key) => {
|
||||
const column = fullColumn.find((item) => item?.params?.key === key);
|
||||
if (column) {
|
||||
result.push(column);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const getHideColumnKey = (fullColumn, visibleColumn) => {
|
||||
const reuslt: any = [];
|
||||
if (fullColumn.length === visibleColumn.length) {
|
||||
return reuslt;
|
||||
} else {
|
||||
fullColumn.forEach((item) => {
|
||||
const fKey = item?.params?.key;
|
||||
if (fKey) {
|
||||
const vItem = visibleColumn.find((item) => {
|
||||
return item?.params?.key === fKey;
|
||||
});
|
||||
if (!vItem) {
|
||||
reuslt.push(fKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
return reuslt;
|
||||
}
|
||||
};
|
||||
return {
|
||||
initSetting,
|
||||
resetSetting,
|
||||
saveSetting,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
import { ref, reactive, provide, resolveComponent } from 'vue';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { JVxeDataProps, JVxeRefs, JVxeTableProps } from '../types';
|
||||
import { VxeGridInstance } from 'vxe-table';
|
||||
import { randomString } from '/@/utils/common/compUtils';
|
||||
|
||||
export function useData(props: JVxeTableProps): JVxeDataProps {
|
||||
const { prefixCls } = useDesign('j-vxe-table');
|
||||
provide('prefixCls', prefixCls);
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
caseId: `j-vxe-${randomString(8)}`,
|
||||
vxeDataSource: ref([]),
|
||||
scroll: reactive({ top: 0, left: 0 }),
|
||||
scrolling: ref(false),
|
||||
defaultVxeProps: reactive({
|
||||
// update-begin--author:liaozhiyang---date:20240607---for:【TV360X-327】vxetable警告
|
||||
// rowId: props.rowKey,
|
||||
rowConfig: {
|
||||
keyField: props.rowKey,
|
||||
},
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-327】vxetable警告
|
||||
// 高亮hover的行
|
||||
highlightHoverRow: true,
|
||||
|
||||
// --- 【issues/209】自带的tooltip会错位,所以替换成原生的title ---
|
||||
// 溢出隐藏并显示tooltip
|
||||
showOverflow: "title",
|
||||
// 表头溢出隐藏并显示tooltip
|
||||
showHeaderOverflow: "title",
|
||||
// --- 【issues/209】自带的tooltip会错位,所以替换成原生的title ---
|
||||
|
||||
showFooterOverflow: true,
|
||||
// 可编辑配置
|
||||
editConfig: {
|
||||
trigger: 'click',
|
||||
mode: 'cell',
|
||||
// update-begin--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
//activeMethod: () => !props.disabled,
|
||||
beforeEditMethod: () => !props.disabled,
|
||||
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
},
|
||||
expandConfig: {
|
||||
iconClose: 'ant-table-row-expand-icon ant-table-row-expand-icon-collapsed',
|
||||
iconOpen: 'ant-table-row-expand-icon ant-table-row-expand-icon-expanded',
|
||||
},
|
||||
// 虚拟滚动配置,y轴大于xx条数据时启用虚拟滚动
|
||||
scrollY: {
|
||||
gt: 30,
|
||||
},
|
||||
scrollX: {
|
||||
gt: 20,
|
||||
// 暂时关闭左右虚拟滚动
|
||||
enabled: false,
|
||||
},
|
||||
radioConfig: { highlight: true },
|
||||
checkboxConfig: { highlight: true },
|
||||
mouseConfig: { selected: false },
|
||||
keyboardConfig: {
|
||||
// 删除键功能
|
||||
isDel: false,
|
||||
// Esc键关闭编辑功能
|
||||
isEsc: true,
|
||||
// Tab 键功能
|
||||
isTab: true,
|
||||
// 任意键进入编辑(功能键除外)
|
||||
isEdit: true,
|
||||
// 方向键功能
|
||||
isArrow: true,
|
||||
// 回车键功能
|
||||
isEnter: true,
|
||||
// 如果功能被支持,用于 column.type=checkbox|radio,开启空格键切换复选框或单选框状态功能
|
||||
isChecked: true,
|
||||
},
|
||||
}),
|
||||
selectedRows: ref<any[]>([]),
|
||||
selectedRowIds: ref<string[]>([]),
|
||||
disabledRowIds: [],
|
||||
statistics: reactive({
|
||||
has: false,
|
||||
sum: [],
|
||||
average: [],
|
||||
}),
|
||||
authsMap: ref(null),
|
||||
innerEditRules: {},
|
||||
innerLinkageConfig: new Map<string, any>(),
|
||||
reloadEffectRowKeysMap: reactive({}),
|
||||
};
|
||||
}
|
||||
|
||||
export function useRefs(): JVxeRefs {
|
||||
return {
|
||||
gridRef: ref<VxeGridInstance>(),
|
||||
subPopoverRef: ref<any>(),
|
||||
detailsModalRef: ref<any>(),
|
||||
};
|
||||
}
|
||||
|
||||
export function useResolveComponent(...t: any[]): any {
|
||||
// @ts-ignore
|
||||
return resolveComponent(...t);
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
import { nextTick, watch } from 'vue';
|
||||
import { JVxeDataProps, JVxeRefs, JVxeTableMethods } from '../types';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export function useDataSource(props, data: JVxeDataProps, methods: JVxeTableMethods, refs: JVxeRefs) {
|
||||
watch(
|
||||
() => props.dataSource,
|
||||
async () => {
|
||||
data.disabledRowIds = [];
|
||||
data.vxeDataSource.value = cloneDeep(props.dataSource);
|
||||
data.vxeDataSource.value.forEach((row, rowIndex) => {
|
||||
// 判断是否是禁用行
|
||||
if (methods.isDisabledRow(row, rowIndex)) {
|
||||
data.disabledRowIds.push(row.id);
|
||||
}
|
||||
// 处理联动回显数据
|
||||
methods.handleLinkageBackData(row);
|
||||
});
|
||||
await waitRef(refs.gridRef);
|
||||
methods.recalcSortNumber();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
}
|
||||
|
||||
function waitRef($ref) {
|
||||
return new Promise<any>((resolve) => {
|
||||
(function next() {
|
||||
if ($ref.value) {
|
||||
resolve($ref);
|
||||
} else {
|
||||
nextTick(() => next());
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
import { onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { JVxeTableMethods, JVxeTableProps } from '/@/components/jeecg/JVxeTable/src/types';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
export function useDragSort(props: JVxeTableProps, methods: JVxeTableMethods) {
|
||||
if (props.dragSort) {
|
||||
let sortable2: Sortable;
|
||||
let initTime: any;
|
||||
|
||||
onMounted(() => {
|
||||
// 加载完成之后再绑定拖动事件
|
||||
initTime = setTimeout(createSortable, 300);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearTimeout(initTime);
|
||||
if (sortable2) {
|
||||
sortable2.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
function createSortable() {
|
||||
let xTable = methods.getXTable();
|
||||
// let dom = xTable.$el.querySelector('.vxe-table--fixed-wrapper .vxe-table--body tbody')
|
||||
let dom = xTable.$el.querySelector('.body--wrapper>.vxe-table--body tbody');
|
||||
let startChildren = [];
|
||||
sortable2 = Sortable.create(dom as HTMLElement, {
|
||||
handle: '.drag-btn',
|
||||
// update-begin--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
filter: '.not-allow-drag',
|
||||
draggable: ".allow-drag",
|
||||
// update-end--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
direction: 'vertical',
|
||||
animation: 300,
|
||||
onStart(e) {
|
||||
let from = e.from;
|
||||
// @ts-ignore
|
||||
startChildren = [...from.children];
|
||||
},
|
||||
onEnd(e) {
|
||||
let oldIndex = e.oldIndex as number;
|
||||
let newIndex = e.newIndex as number;
|
||||
if (oldIndex === newIndex) {
|
||||
return;
|
||||
}
|
||||
// 【VUEN-2505】获取当前行数据
|
||||
let rowNode = xTable.getRowNode(e.item);
|
||||
if (!rowNode) {
|
||||
return;
|
||||
}
|
||||
let from = e.from;
|
||||
let element = startChildren[oldIndex];
|
||||
let target = null;
|
||||
if (oldIndex > newIndex) {
|
||||
// 向上移动
|
||||
if (oldIndex + 1 < startChildren.length) {
|
||||
target = startChildren[oldIndex + 1];
|
||||
}
|
||||
} else {
|
||||
// 向下移动
|
||||
target = startChildren[oldIndex + 1];
|
||||
}
|
||||
from.removeChild(element);
|
||||
from.insertBefore(element, target);
|
||||
nextTick(() => {
|
||||
// 【VUEN-2505】算出因虚拟滚动导致的偏移量
|
||||
let diffIndex = rowNode!.index - oldIndex;
|
||||
if (diffIndex > 0) {
|
||||
oldIndex = oldIndex + diffIndex;
|
||||
newIndex = newIndex + diffIndex;
|
||||
}
|
||||
methods.doSort(oldIndex, newIndex);
|
||||
methods.trigger('dragged', { oldIndex, newIndex });
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
import { unref, computed } from 'vue';
|
||||
import { merge } from 'lodash-es';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useKeyboardEdit } from '../hooks/useKeyboardEdit';
|
||||
import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types';
|
||||
|
||||
export function useFinallyProps(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods) {
|
||||
const attrs = useAttrs();
|
||||
// vxe 键盘操作配置
|
||||
const { keyboardEditConfig } = useKeyboardEdit(props);
|
||||
// vxe 最终 editRules
|
||||
const vxeEditRules = computed(() => merge({}, props.editRules, data.innerEditRules));
|
||||
// vxe 最终 events
|
||||
const vxeEvents = computed(() => {
|
||||
let listeners = { ...unref(attrs) };
|
||||
let events = {
|
||||
onScroll: methods.handleVxeScroll,
|
||||
onCellClick: methods.handleCellClick,
|
||||
onEditClosed: methods.handleEditClosed,
|
||||
onEditActived: methods.handleEditActived,
|
||||
onRadioChange: methods.handleVxeRadioChange,
|
||||
onCheckboxAll: methods.handleVxeCheckboxAll,
|
||||
onCheckboxChange: methods.handleVxeCheckboxChange,
|
||||
// update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-8566】JVXETable无法记住列设置
|
||||
onCustom: methods.handleCustom,
|
||||
// update-begin--author:liaozhiyang---date:20240321---for:【QQYUN-8566】JVXETable无法记住列设置
|
||||
};
|
||||
// 用户传递的事件,进行合并操作
|
||||
Object.keys(listeners).forEach((key) => {
|
||||
let listen = listeners[key];
|
||||
if (events.hasOwnProperty(key)) {
|
||||
if (isArray(listen)) {
|
||||
listen.push(events[key]);
|
||||
} else {
|
||||
listen = [events[key], listen];
|
||||
}
|
||||
}
|
||||
events[key] = listen;
|
||||
});
|
||||
return events;
|
||||
});
|
||||
// vxe 最终 props
|
||||
const vxeProps = computed(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
let rowClass = {};
|
||||
if (props.dragSort) {
|
||||
rowClass = {
|
||||
rowClassName: (params) => {
|
||||
let { row } = params;
|
||||
const find = props.notAllowDrag?.find((item:any) => {
|
||||
const {key, value} = item;
|
||||
return row[key] == value;
|
||||
});
|
||||
// 业务传进的来的rowClassName
|
||||
const popsRowClassName = props.rowClassName ?? '';
|
||||
let outClass = '';
|
||||
if(typeof popsRowClassName==='string'){
|
||||
popsRowClassName && (outClass = popsRowClassName);
|
||||
}else if(typeof popsRowClassName==='function'){
|
||||
outClass = popsRowClassName(params)
|
||||
}
|
||||
return find ? `not-allow-drag ${outClass}` : `allow-drag ${outClass}`;
|
||||
},
|
||||
};
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
return merge(
|
||||
{},
|
||||
data.defaultVxeProps,
|
||||
{
|
||||
showFooter: data.statistics.has,
|
||||
},
|
||||
unref(attrs),
|
||||
{
|
||||
ref: 'gridRef',
|
||||
size: props.size,
|
||||
loading: false,
|
||||
disabled: props.disabled,
|
||||
columns: unref(data.vxeColumns),
|
||||
editRules: unref(vxeEditRules),
|
||||
height: props.height === 'auto' ? null : props.height,
|
||||
maxHeight: props.maxHeight,
|
||||
// update-begin--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
scrollY: props.scrollY,
|
||||
scrollX: props.scrollX,
|
||||
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
border: props.bordered,
|
||||
footerMethod: methods.handleFooterMethod,
|
||||
// 展开行配置
|
||||
expandConfig: {
|
||||
toggleMethod: methods.handleExpandToggleMethod,
|
||||
},
|
||||
// 可编辑配置
|
||||
editConfig: {
|
||||
// update-begin--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
//activeMethod: methods.handleActiveMethod,
|
||||
beforeEditMethod: methods.handleActiveMethod,
|
||||
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
},
|
||||
radioConfig: {
|
||||
checkMethod: methods.handleCheckMethod,
|
||||
},
|
||||
checkboxConfig: {
|
||||
checkMethod: methods.handleCheckMethod,
|
||||
},
|
||||
...rowClass
|
||||
// rowClassName:(params)=>{
|
||||
// const { row } = params;
|
||||
// return row.dbFieldName=='id'?"not-allow-drag":"allow-drag"
|
||||
// }
|
||||
},
|
||||
unref(vxeEvents),
|
||||
unref(keyboardEditConfig)
|
||||
);
|
||||
});
|
||||
return {
|
||||
vxeProps,
|
||||
prefixCls: data.prefixCls,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,298 @@
|
||||
import { computed, nextTick, ref, unref, watch } from 'vue';
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { useDesign } from '/@/hooks/web/useDesign';
|
||||
import { getEnhanced, replaceProps } from '../utils/enhancedUtils';
|
||||
import { vModel } from '/@/components/jeecg/JVxeTable/utils';
|
||||
import { JVxeRenderType } from '../types/JVxeTypes';
|
||||
import { isBoolean, isFunction, isObject, isPromise } from '/@/utils/is';
|
||||
import { JVxeComponent } from '../types/JVxeComponent';
|
||||
import { filterDictText } from '/@/utils/dict/JDictSelectUtil';
|
||||
|
||||
export function useJVxeCompProps() {
|
||||
return {
|
||||
// 组件类型
|
||||
type: propTypes.string,
|
||||
// 渲染类型
|
||||
renderType: propTypes.string.def('default'),
|
||||
// 渲染参数
|
||||
params: propTypes.object,
|
||||
// 渲染自定义选项
|
||||
renderOptions: propTypes.object,
|
||||
};
|
||||
}
|
||||
|
||||
export function useJVxeComponent(props: JVxeComponent.Props) {
|
||||
const value = computed(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240430---for:【QQYUN-9125】oracle数据库日期类型字段会默认带上时分秒
|
||||
const val = props.params.row[props.params.column.property];
|
||||
if (props.type === 'date' && typeof val === 'string') {
|
||||
return val.split(' ').shift();
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240430---for:【QQYUN-9125】oracle数据库日期类型字段会默认带上时分秒
|
||||
});
|
||||
const innerValue = ref(value.value);
|
||||
const row = computed(() => props.params.row);
|
||||
const rows = computed(() => props.params.data);
|
||||
const column = computed(() => props.params.column);
|
||||
// 用户配置的原始 column
|
||||
const originColumn = computed(() => column.value.params);
|
||||
const rowIndex = computed(() => props.params._rowIndex);
|
||||
const columnIndex = computed(() => props.params._columnIndex);
|
||||
// 表格数据长度
|
||||
const fullDataLength = computed(() => props.params.$table.internalData.tableFullData.length);
|
||||
// 是否正在滚动中
|
||||
const scrolling = computed(() => !!props.renderOptions.scrolling);
|
||||
const cellProps = computed(() => {
|
||||
let renderOptions = props.renderOptions;
|
||||
let col = originColumn.value;
|
||||
|
||||
let cellProps = {};
|
||||
|
||||
// 输入占位符
|
||||
cellProps['placeholder'] = replaceProps(col, col.placeholder);
|
||||
|
||||
// 解析props
|
||||
if (isObject(col.props)) {
|
||||
Object.keys(col.props).forEach((key) => {
|
||||
cellProps[key] = replaceProps(col, col.props[key]);
|
||||
});
|
||||
}
|
||||
|
||||
// 判断是否是禁用的列
|
||||
cellProps['disabled'] = isBoolean(col['disabled']) ? col['disabled'] : cellProps['disabled'];
|
||||
// 判断是否禁用行
|
||||
if (renderOptions.isDisabledRow(row.value, rowIndex.value)) {
|
||||
cellProps['disabled'] = true;
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240528---for:【TV360X-291】没勾选同步数据库禁用排序功能
|
||||
if (col.props && col.props.isDisabledCell) {
|
||||
if (col.props.isDisabledCell({ row: row.value, rowIndex: rowIndex.value, column: col, columnIndex: columnIndex.value })) {
|
||||
cellProps['disabled'] = true;
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240528---for:【TV360X-291】没勾选同步数据库禁用排序功能
|
||||
// 判断是否禁用所有组件
|
||||
if (renderOptions.disabled === true) {
|
||||
cellProps['disabled'] = true;
|
||||
// update-begin--author:liaozhiyang---date:20240607---for:【TV360X-1068】行编辑整体禁用时上传按钮不显示
|
||||
cellProps['disabledTable'] = true;
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-1068】行编辑整体禁用时上传按钮不显示
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-5-25 for: VUEN-1111 一对多子表 部门选择 不应该级联
|
||||
if (col.checkStrictly === true) {
|
||||
cellProps['checkStrictly'] = true;
|
||||
}
|
||||
//update-end-author:taoyan date:2022-5-25 for: VUEN-1111 一对多子表 部门选择 不应该级联
|
||||
|
||||
//update-begin-author:taoyan date:2022-5-27 for: 用户组件 控制单选多选新的参数配置
|
||||
if (col.isRadioSelection === true) {
|
||||
cellProps['isRadioSelection'] = true;
|
||||
} else if (col.isRadioSelection === false) {
|
||||
cellProps['isRadioSelection'] = false;
|
||||
}
|
||||
//update-end-author:taoyan date:2022-5-27 for: 用户组件 控制单选多选新的参数配置
|
||||
|
||||
return cellProps;
|
||||
});
|
||||
|
||||
const listeners = computed(() => {
|
||||
let listeners = Object.assign({}, props.renderOptions.listeners || {});
|
||||
// 默认change事件
|
||||
if (!listeners.change) {
|
||||
listeners.change = async (event) => {
|
||||
vModel(event.value, row, column);
|
||||
await nextTick();
|
||||
// 处理 change 事件相关逻辑(例如校验)
|
||||
props.params.$table.updateStatus(props.params);
|
||||
};
|
||||
}
|
||||
return listeners;
|
||||
});
|
||||
const context = {
|
||||
innerValue,
|
||||
row,
|
||||
rows,
|
||||
rowIndex,
|
||||
column,
|
||||
columnIndex,
|
||||
originColumn,
|
||||
fullDataLength,
|
||||
cellProps,
|
||||
scrolling,
|
||||
handleChangeCommon,
|
||||
handleBlurCommon,
|
||||
};
|
||||
const ctx = { props, context };
|
||||
|
||||
// 获取组件增强
|
||||
const enhanced = getEnhanced(props.type);
|
||||
|
||||
watch(
|
||||
value,
|
||||
(newValue) => {
|
||||
// 验证值格式
|
||||
let getValue = enhanced.getValue(newValue, ctx);
|
||||
if (newValue !== getValue) {
|
||||
// 值格式不正确,重新赋值
|
||||
newValue = getValue;
|
||||
vModel(newValue, row, column);
|
||||
}
|
||||
innerValue.value = enhanced.setValue(newValue, ctx);
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
if (props.type === 'date' && props.renderType === JVxeRenderType.spaner && enhanced.translate.enabled === true) {
|
||||
if (isFunction(enhanced.translate.handler)) {
|
||||
innerValue.value = enhanced.translate.handler(newValue, ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240509---for:【QQYUN-9205】一对多(jVxetable组件date)支持年,年月,年度度,年周
|
||||
// 判断是否启用翻译
|
||||
if (props.renderType === JVxeRenderType.spaner && enhanced.translate.enabled === true) {
|
||||
if (isFunction(enhanced.translate.handler)) {
|
||||
let res = enhanced.translate.handler(newValue, ctx);
|
||||
// 异步翻译,可解决字典查询慢的问题
|
||||
if (isPromise(res)) {
|
||||
res.then((v) => (innerValue.value = v));
|
||||
} else {
|
||||
innerValue.value = res;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/** 通用处理 change 事件 */
|
||||
function handleChangeCommon($value, force = false) {
|
||||
const newValue = enhanced.getValue($value, ctx);
|
||||
const oldValue = value.value;
|
||||
// update-begin--author:liaozhiyang---date:20230718---for:【issues-5025】JVueTable的事件 @valueChange重复触发问题
|
||||
const execute = force ? true : newValue !== oldValue;
|
||||
if (execute) {
|
||||
trigger('change', { value: newValue });
|
||||
// 触发valueChange事件
|
||||
parentTrigger('valueChange', {
|
||||
type: props.type,
|
||||
value: newValue,
|
||||
oldValue: oldValue,
|
||||
col: originColumn.value,
|
||||
rowIndex: rowIndex.value,
|
||||
columnIndex: columnIndex.value,
|
||||
});
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20230718---for:【issues-5025】JVueTable的事件 @valueChange重复触发问题
|
||||
}
|
||||
|
||||
/** 通用处理 blur 事件 */
|
||||
function handleBlurCommon($value) {
|
||||
// update-begin--author:liaozhiyang---date:20230817---for:【issues/636】JVxeTable加上blur事件
|
||||
const newValue = enhanced.getValue($value, ctx);
|
||||
const oldValue = value.value;
|
||||
//trigger('blur', { value });
|
||||
// 触发blur事件
|
||||
parentTrigger('blur', {
|
||||
type: props.type,
|
||||
value: newValue,
|
||||
oldValue: oldValue,
|
||||
col: originColumn.value,
|
||||
rowIndex: rowIndex.value,
|
||||
columnIndex: columnIndex.value,
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20230817---for:【issues/636】JVxeTable加上blur事件
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果事件存在的话,就触发
|
||||
* @param name 事件名
|
||||
* @param event 事件参数
|
||||
* @param args 其他附带参数
|
||||
*/
|
||||
function trigger(name, event?, args: any[] = []) {
|
||||
let listener = listeners.value[name];
|
||||
if (isFunction(listener)) {
|
||||
if (isObject(event)) {
|
||||
event = packageEvent(name, event);
|
||||
}
|
||||
listener(event, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
function parentTrigger(name, event, args: any[] = []) {
|
||||
args.unshift(packageEvent(name, event));
|
||||
trigger('trigger', name, args);
|
||||
}
|
||||
|
||||
function packageEvent(name, event: any = {}) {
|
||||
event.row = row.value;
|
||||
event.column = column.value;
|
||||
// online增强参数兼容
|
||||
event.column['key'] = column.value['property'];
|
||||
// event.cellTarget = this
|
||||
if (!event.type) {
|
||||
event.type = name;
|
||||
}
|
||||
if (!event.cellType) {
|
||||
event.cellType = props.type;
|
||||
}
|
||||
// 是否校验表单,默认为true
|
||||
if (isBoolean(event.validate)) {
|
||||
event.validate = true;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 防样式冲突类名生成器
|
||||
* @param scope
|
||||
*/
|
||||
function useCellDesign(scope: string) {
|
||||
return useDesign(`vxe-cell-${scope}`);
|
||||
}
|
||||
|
||||
return {
|
||||
...context,
|
||||
enhanced,
|
||||
trigger,
|
||||
useCellDesign,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件默认增强
|
||||
*/
|
||||
export function useDefaultEnhanced(): JVxeComponent.EnhancedPartial {
|
||||
return {
|
||||
installOptions: {
|
||||
autofocus: '',
|
||||
},
|
||||
interceptor: {
|
||||
'event.clearActived': () => true,
|
||||
'event.clearActived.className': () => true,
|
||||
},
|
||||
switches: {
|
||||
editRender: true,
|
||||
visible: false,
|
||||
},
|
||||
aopEvents: {
|
||||
editActived() {},
|
||||
editClosed() {},
|
||||
activeMethod: () => true,
|
||||
},
|
||||
translate: {
|
||||
enabled: false,
|
||||
handler(value, ctx) {
|
||||
// 默认翻译方法
|
||||
if (ctx) {
|
||||
return filterDictText(unref(ctx.context.column).params.options, value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
},
|
||||
},
|
||||
getValue: (value) => value,
|
||||
setValue: (value) => value,
|
||||
createValue: (defaultValue) => defaultValue,
|
||||
} as JVxeComponent.Enhanced;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* JVxeTable 键盘操作
|
||||
*/
|
||||
import type { VxeTablePropTypes } from 'vxe-table';
|
||||
import type { JVxeTableProps } from '../types';
|
||||
import { computed } from 'vue';
|
||||
|
||||
/**
|
||||
* JVxeTable 键盘操作
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
export function useKeyboardEdit(props: JVxeTableProps) {
|
||||
// 是否开启了键盘操作
|
||||
const enabledKeyboard = computed(() => props.keyboardEdit ?? false);
|
||||
// 重写 keyboardConfig
|
||||
const keyboardConfig: VxeTablePropTypes.KeyboardConfig = {
|
||||
editMethod({ row, column, $table }) {
|
||||
// 重写默认的覆盖式,改为追加式
|
||||
$table.setActiveCell(row, column);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
// 键盘操作配置
|
||||
const keyboardEditConfig = computed(() => {
|
||||
return {
|
||||
mouseConfig: {
|
||||
selected: enabledKeyboard.value,
|
||||
},
|
||||
keyboardConfig,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
keyboardEditConfig,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
import { watch } from 'vue';
|
||||
import { isFunction, isPromise, isArray } from '/@/utils/is';
|
||||
import { JVxeColumn, JVxeDataProps, JVxeTableProps, JVxeLinkageConfig } from '../types';
|
||||
|
||||
/**
|
||||
* 多级联动
|
||||
*/
|
||||
export function useLinkage(props: JVxeTableProps, data: JVxeDataProps, methods) {
|
||||
// 整理多级联动配置
|
||||
watch(
|
||||
() => props.linkageConfig,
|
||||
(linkageConfig: JVxeLinkageConfig[]) => {
|
||||
data.innerLinkageConfig.clear();
|
||||
if (isArray(linkageConfig) && linkageConfig.length > 0) {
|
||||
linkageConfig.forEach((config) => {
|
||||
let keys = getLinkageKeys(config.key, []);
|
||||
// 多个key共享一个,引用地址
|
||||
let configItem = {
|
||||
...config,
|
||||
keys,
|
||||
optionsMap: new Map(),
|
||||
};
|
||||
keys.forEach((k) => data.innerLinkageConfig.set(k, configItem));
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 获取联动的key顺序
|
||||
function getLinkageKeys(key: string, keys: string[]): string[] {
|
||||
let col = props.columns?.find((col: JVxeColumn) => col.key === key) as JVxeColumn;
|
||||
if (col) {
|
||||
keys.push(col.key);
|
||||
// 寻找下级
|
||||
if (col.linkageKey) {
|
||||
return getLinkageKeys(col.linkageKey, keys);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// 处理联动回显数据
|
||||
function handleLinkageBackData(row) {
|
||||
if (data.innerLinkageConfig.size > 0) {
|
||||
for (let configItem of data.innerLinkageConfig.values()) {
|
||||
autoSetLinkageOptionsByData(row, '', configItem, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 【多级联动】获取同级联动下拉选项 */
|
||||
function getLinkageOptionsSibling(row, col, config, request) {
|
||||
// 如果当前列不是顶级列
|
||||
let key = '';
|
||||
if (col.key !== config.key) {
|
||||
// 就找出联动上级列
|
||||
let idx = config.keys.findIndex((k) => col.key === k);
|
||||
let parentKey = config.keys[idx - 1];
|
||||
key = row[parentKey];
|
||||
// 如果联动上级列没有选择数据,就直接返回空数组
|
||||
if (key === '' || key == null) {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
key = 'root';
|
||||
}
|
||||
let options = config.optionsMap.get(key);
|
||||
if (!Array.isArray(options)) {
|
||||
if (request) {
|
||||
let parent = key === 'root' ? '' : key;
|
||||
return getLinkageOptionsAsync(config, parent);
|
||||
} else {
|
||||
options = [];
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/** 【多级联动】获取联动下拉选项(异步) */
|
||||
function getLinkageOptionsAsync(config, parent) {
|
||||
return new Promise((resolve) => {
|
||||
let key = parent ? parent : 'root';
|
||||
let options;
|
||||
if (config.optionsMap.has(key)) {
|
||||
options = config.optionsMap.get(key);
|
||||
if (isPromise(options)) {
|
||||
options.then((opt) => {
|
||||
config.optionsMap.set(key, opt);
|
||||
resolve(opt);
|
||||
});
|
||||
} else {
|
||||
resolve(options);
|
||||
}
|
||||
} else if (isFunction(config.requestData)) {
|
||||
// 调用requestData方法,通过传入parent来获取子级
|
||||
// noinspection JSVoidFunctionReturnValueUsed,TypeScriptValidateJSTypes
|
||||
let promise = config.requestData(parent);
|
||||
config.optionsMap.set(key, promise);
|
||||
promise.then((opt) => {
|
||||
config.optionsMap.set(key, opt);
|
||||
resolve(opt);
|
||||
});
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 【多级联动】 用于回显数据,自动填充 optionsMap
|
||||
function autoSetLinkageOptionsByData(data, parent, config, level) {
|
||||
if (level === 0) {
|
||||
getLinkageOptionsAsync(config, '');
|
||||
} else {
|
||||
getLinkageOptionsAsync(config, parent);
|
||||
}
|
||||
if (config.keys.length - 1 > level) {
|
||||
let value = data[config.keys[level]];
|
||||
if (value) {
|
||||
autoSetLinkageOptionsByData(data, value, config, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 【多级联动】联动组件change时,清空下级组件
|
||||
function handleLinkageSelectChange(row, col, config, value) {
|
||||
if (col.linkageKey) {
|
||||
getLinkageOptionsAsync(config, value);
|
||||
let idx = config.keys.findIndex((k) => k === col.key);
|
||||
let values = {};
|
||||
for (let i = idx; i < config.keys.length; i++) {
|
||||
values[config.keys[i]] = '';
|
||||
}
|
||||
// 清空后几列的数据
|
||||
methods.setValues([{ rowKey: row.id, values }]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getLinkageOptionsAsync,
|
||||
getLinkageOptionsSibling,
|
||||
handleLinkageSelectChange,
|
||||
handleLinkageBackData,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,886 @@
|
||||
import { Ref, watch } from 'vue';
|
||||
import XEUtils from 'xe-utils';
|
||||
import { simpleDebounce } from '/@/utils/common/compUtils';
|
||||
import { JVxeDataProps, JVxeRefs, JVxeTableProps, JVxeTypes } from '../types';
|
||||
import { getEnhanced } from '../utils/enhancedUtils';
|
||||
import { VxeTableInstance, VxeTablePrivateMethods } from 'vxe-table';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isArray, isEmpty, isNull, isString } from '/@/utils/is';
|
||||
import { useLinkage } from './useLinkage';
|
||||
import { useWebSocket } from './useWebSocket';
|
||||
import { getPrefix, getJVxeAuths } from '../utils/authUtils';
|
||||
import { excludeKeywords } from '../componentMap';
|
||||
import { useColumnsCache } from './useColumnsCache';
|
||||
|
||||
export function useMethods(props: JVxeTableProps, { emit }, data: JVxeDataProps, refs: JVxeRefs, instanceRef: Ref) {
|
||||
let xTableTemp: VxeTableInstance & VxeTablePrivateMethods;
|
||||
|
||||
function getXTable() {
|
||||
if (!xTableTemp) {
|
||||
// !. 为 typescript 的非空断言
|
||||
xTableTemp = refs.gridRef.value!.getRefMaps().refTable.value;
|
||||
}
|
||||
return xTableTemp;
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
const hookMethods = {
|
||||
getXTable,
|
||||
addRows,
|
||||
pushRows,
|
||||
insertRows,
|
||||
addOrInsert,
|
||||
setValues,
|
||||
getValues,
|
||||
getTableData,
|
||||
getNewData,
|
||||
getNewDataWithId,
|
||||
getIfRowById,
|
||||
getNewRowById,
|
||||
getDeleteData,
|
||||
getSelectionData,
|
||||
getSelectedData,
|
||||
removeRows,
|
||||
removeRowsById,
|
||||
removeSelection,
|
||||
resetScrollTop,
|
||||
validateTable,
|
||||
fullValidateTable,
|
||||
clearSelection,
|
||||
filterNewRows,
|
||||
isDisabledRow,
|
||||
recalcDisableRows,
|
||||
rowResort,
|
||||
};
|
||||
|
||||
// 多级联动
|
||||
const linkageMethods = useLinkage(props, data, hookMethods);
|
||||
// WebSocket 无痕刷新
|
||||
const socketMethods = useWebSocket(props, data, hookMethods);
|
||||
|
||||
// 可显式供外部调用的方法
|
||||
const publicMethods = {
|
||||
...hookMethods,
|
||||
...linkageMethods,
|
||||
...socketMethods,
|
||||
};
|
||||
|
||||
/** 监听vxe滚动条位置 */
|
||||
function handleVxeScroll(event) {
|
||||
let { scroll } = data;
|
||||
|
||||
// 记录滚动条的位置
|
||||
scroll.top = event.scrollTop;
|
||||
scroll.left = event.scrollLeft;
|
||||
|
||||
refs.subPopoverRef.value?.close();
|
||||
data.scrolling.value = true;
|
||||
closeScrolling();
|
||||
}
|
||||
|
||||
// 当手动勾选单选时触发的事件
|
||||
function handleVxeRadioChange(event) {
|
||||
let row = event.$table.getRadioRecord();
|
||||
data.selectedRows.value = row ? [row] : [];
|
||||
handleSelectChange('radio', data.selectedRows.value, event);
|
||||
}
|
||||
|
||||
// 当手动勾选全选时触发的事件
|
||||
function handleVxeCheckboxAll(event) {
|
||||
data.selectedRows.value = event.$table.getCheckboxRecords();
|
||||
handleSelectChange('checkbox-all', data.selectedRows.value, event);
|
||||
}
|
||||
|
||||
// 当手动勾选并且值发生改变时触发的事件
|
||||
function handleVxeCheckboxChange(event) {
|
||||
data.selectedRows.value = event.$table.getCheckboxRecords();
|
||||
handleSelectChange('checkbox', data.selectedRows.value, event);
|
||||
}
|
||||
|
||||
// 行选择change事件
|
||||
function handleSelectChange(type, selectedRows, $event) {
|
||||
let action;
|
||||
if (type === 'radio') {
|
||||
action = 'selected';
|
||||
} else if (type === 'checkbox') {
|
||||
action = selectedRows.includes($event.row) ? 'selected' : 'unselected';
|
||||
} else {
|
||||
action = 'selected-all';
|
||||
}
|
||||
|
||||
data.selectedRowIds.value = selectedRows.map((row) => row.id);
|
||||
trigger('selectRowChange', {
|
||||
type: type,
|
||||
action: action,
|
||||
$event: $event,
|
||||
row: $event.row,
|
||||
selectedRows: data.selectedRows.value,
|
||||
selectedRowIds: data.selectedRowIds.value,
|
||||
});
|
||||
}
|
||||
|
||||
// 点击单元格时触发的事件
|
||||
function handleCellClick(event) {
|
||||
let { row, column, $event, $table } = event;
|
||||
|
||||
// 点击了可编辑的
|
||||
if (column.editRender) {
|
||||
refs.subPopoverRef.value?.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示详细信息
|
||||
if (column.params?.showDetails) {
|
||||
refs.detailsModalRef.value?.open(event);
|
||||
} else if (refs.subPopoverRef.value) {
|
||||
refs.subPopoverRef.value.toggle(event);
|
||||
} else if (props.clickSelectRow) {
|
||||
let className = $event.target.className || '';
|
||||
className = isString(className) ? 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;
|
||||
}
|
||||
if (props.rowSelectionType === 'radio') {
|
||||
$table.setRadioRow(row);
|
||||
handleVxeRadioChange(event);
|
||||
} else {
|
||||
$table.toggleCheckboxRow(row);
|
||||
handleVxeCheckboxChange(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 单元格被激活编辑时会触发该事件
|
||||
function handleEditActived({ column }) {
|
||||
// 执行增强
|
||||
getEnhanced(column.params.type).aopEvents.editActived!.apply(instanceRef.value, arguments as any);
|
||||
}
|
||||
|
||||
// 单元格编辑状态下被关闭时会触发该事件
|
||||
function handleEditClosed({ column }) {
|
||||
// 执行增强
|
||||
getEnhanced(column.params.type).aopEvents.editClosed!.apply(instanceRef.value, arguments as any);
|
||||
}
|
||||
|
||||
// 返回值决定行是否可选中
|
||||
function handleCheckMethod({ row }) {
|
||||
if (props.disabled) {
|
||||
return false;
|
||||
}
|
||||
return !data.disabledRowIds.includes(row.id);
|
||||
}
|
||||
|
||||
// 返回值决定单元格是否可以编辑
|
||||
function handleActiveMethod({ row, column }) {
|
||||
let flag = (() => {
|
||||
if (props.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (data.disabledRowIds.includes(row.id)) {
|
||||
return false;
|
||||
}
|
||||
if (column.params?.disabled) {
|
||||
return false;
|
||||
}
|
||||
// 执行增强
|
||||
return getEnhanced(column.params.type).aopEvents.activeMethod!.apply(instanceRef.value, arguments as any) ?? true;
|
||||
})();
|
||||
if (!flag) {
|
||||
getXTable().clearActived();
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是禁用行
|
||||
* @param row 行数据
|
||||
* @param rowIndex 行号
|
||||
* @param force 是否强制判断
|
||||
*/
|
||||
function isDisabledRow(row, rowIndex: number | boolean = -1, force = true) {
|
||||
if(typeof rowIndex === 'boolean'){
|
||||
force = rowIndex;
|
||||
rowIndex = -1;
|
||||
}
|
||||
if (!force) {
|
||||
return !data.disabledRowIds.includes(row.id);
|
||||
}
|
||||
if (props.disabledRows == null || isEmpty(props.disabledRows)) {
|
||||
return false;
|
||||
}
|
||||
let disabled: boolean = false;
|
||||
let keys: string[] = Object.keys(props.disabledRows);
|
||||
for (const key of keys) {
|
||||
// 判断是否有该属性
|
||||
if (row.hasOwnProperty(key)) {
|
||||
let value = row[key];
|
||||
let temp: any = props.disabledRows![key];
|
||||
// 禁用规则可以是一个函数
|
||||
if (typeof temp === 'function') {
|
||||
disabled = temp(value, row, rowIndex);
|
||||
} else if (isArray(temp)) {
|
||||
// 禁用规则可以是一个数组
|
||||
disabled = temp.includes(value);
|
||||
} else {
|
||||
// 禁用规则可以是一个具体值
|
||||
disabled = temp === value;
|
||||
}
|
||||
if (disabled) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return disabled;
|
||||
}
|
||||
|
||||
// 重新计算禁用行
|
||||
function recalcDisableRows() {
|
||||
let xTable = getXTable();
|
||||
data.disabledRowIds = [];
|
||||
const { tableFullData } = xTable.internalData;
|
||||
tableFullData.forEach((row, rowIndex) => {
|
||||
// 判断是否是禁用行
|
||||
if (isDisabledRow(row, rowIndex)) {
|
||||
data.disabledRowIds.push(row.id);
|
||||
}
|
||||
});
|
||||
xTable.updateData();
|
||||
}
|
||||
|
||||
// 监听 disabledRows,更改时重新计算禁用行
|
||||
watch(
|
||||
() => props.disabledRows,
|
||||
() => recalcDisableRows()
|
||||
);
|
||||
|
||||
// 返回值决定是否允许展开、收起行
|
||||
function handleExpandToggleMethod({ expanded }) {
|
||||
return !(expanded && props.disabled);
|
||||
}
|
||||
|
||||
// 设置 data.scrolling 防抖模式
|
||||
const closeScrolling = simpleDebounce(function () {
|
||||
data.scrolling.value = false;
|
||||
}, 100);
|
||||
|
||||
/** 表尾数据处理方法,用于显示统计信息 */
|
||||
function handleFooterMethod({ columns, data: $data }) {
|
||||
const { statistics } = data;
|
||||
let footers: any[] = [];
|
||||
if (statistics.has) {
|
||||
if (statistics.sum.length > 0) {
|
||||
footers.push(
|
||||
getFooterStatisticsMap({
|
||||
columns: columns,
|
||||
title: '合计',
|
||||
checks: statistics.sum,
|
||||
method: (column) => XEUtils.sum($data, column.property),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (statistics.average.length > 0) {
|
||||
footers.push(
|
||||
getFooterStatisticsMap({
|
||||
columns: columns,
|
||||
title: '平均',
|
||||
checks: statistics.average,
|
||||
method: (column) => XEUtils.mean($data, column.property),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
return footers;
|
||||
}
|
||||
|
||||
/** 获取底部统计Map */
|
||||
function getFooterStatisticsMap({ columns, title, checks, method }) {
|
||||
return columns.map((column, columnIndex) => {
|
||||
if (columnIndex === 0) {
|
||||
return title;
|
||||
}
|
||||
if (checks.includes(column.property)) {
|
||||
return method(column, columnIndex);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// 创建新行,自动添加默认值
|
||||
function createRow(record: Recordable = {}) {
|
||||
let xTable = getXTable();
|
||||
// 添加默认值
|
||||
xTable.internalData.tableFullColumn.forEach((column) => {
|
||||
let col = column.params;
|
||||
// 不能被注册的列不获取增强
|
||||
if (col && !excludeKeywords.includes(col.type)) {
|
||||
if (col.key && (record[col.key] == null || record[col.key] === '')) {
|
||||
// 设置默认值
|
||||
let createValue = getEnhanced(col.type).createValue;
|
||||
let defaultValue = col.defaultValue ?? '';
|
||||
let ctx = { context: { row: record, column, $table: xTable } };
|
||||
record[col.key] = createValue(defaultValue, ctx);
|
||||
}
|
||||
// 处理联动列
|
||||
if (col.type === JVxeTypes.select && data.innerLinkageConfig.size > 0) {
|
||||
// 判断当前列是否是联动列
|
||||
if (data.innerLinkageConfig.has(col.key)) {
|
||||
let configItem = data.innerLinkageConfig.get(col.key);
|
||||
linkageMethods.getLinkageOptionsAsync(configItem, '');
|
||||
}
|
||||
}
|
||||
} else if (col?.type === JVxeTypes.hidden) {
|
||||
record[col.key] = col.defaultValue ?? '';
|
||||
}
|
||||
});
|
||||
return record;
|
||||
}
|
||||
|
||||
async function addOrInsert(rows: Recordable | Recordable[] = {}, index, triggerName, options?: IAddRowsOptions) {
|
||||
let xTable = getXTable();
|
||||
let records;
|
||||
if (isArray(rows)) {
|
||||
records = rows;
|
||||
} else {
|
||||
records = [rows];
|
||||
}
|
||||
// 遍历添加默认值
|
||||
records.forEach((record) => createRow(record));
|
||||
let setActive = options?.setActive ?? props.addSetActive ?? true;
|
||||
let result = await pushRows(records, { index: index, setActive });
|
||||
// 遍历插入的行
|
||||
// online js增强时以传过来值为准,不再赋默认值
|
||||
if (!(options?.isOnlineJS ?? false)) {
|
||||
if (triggerName != null) {
|
||||
for (let i = 0; i < result.rows.length; i++) {
|
||||
let row = result.rows[i];
|
||||
trigger(triggerName, {
|
||||
row: row,
|
||||
rows: result.rows,
|
||||
insertIndex: index,
|
||||
$table: xTable,
|
||||
target: instanceRef.value,
|
||||
isModalData: options?.isModalData
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 新增、插入一行时的可选参数
|
||||
interface IAddRowsOptions {
|
||||
// 是否是 onlineJS增强 触发的
|
||||
isOnlineJS?: boolean;
|
||||
// 是否激活编辑状态
|
||||
setActive?: boolean;
|
||||
//是否需要触发change事件
|
||||
emitChange?:boolean
|
||||
// 是否是modal弹窗添加的数据
|
||||
isModalData?:boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一行或多行
|
||||
*
|
||||
* @param rows
|
||||
* @param options 参数
|
||||
* @return
|
||||
*/
|
||||
async function addRows(rows: Recordable | Recordable[] = {}, options?: IAddRowsOptions) {
|
||||
//update-begin-author:taoyan date:2022-8-12 for: VUEN-1892【online子表弹框】有主从关联js时,子表弹框修改了数据,主表字段未修改
|
||||
let result = await addOrInsert(rows, -1, 'added', options);
|
||||
if(options && options!.emitChange==true){
|
||||
trigger('valueChange', {column: 'all', row: result.row})
|
||||
}
|
||||
// update-begin--author:liaozhiyang---date:20240607---for:【TV360X-279】行编辑添加新字段滚动对应位置
|
||||
let xTable = getXTable();
|
||||
setTimeout(() => {
|
||||
xTable.scrollToRow(result.row);
|
||||
}, 0);
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-279】行编辑添加新字段滚动对应位置
|
||||
return result;
|
||||
//update-end-author:taoyan date:2022-8-12 for: VUEN-1892【online子表弹框】有主从关联js时,子表弹框修改了数据,主表字段未修改
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一行或多行临时数据,不会填充默认值,传什么就添加进去什么
|
||||
* @param rows
|
||||
* @param options 选项
|
||||
* @param options.setActive 是否激活最后一行的编辑模式
|
||||
*/
|
||||
async function pushRows(rows: Recordable | Recordable[] = {}, options = { setActive: false, index: -1 }) {
|
||||
let xTable = getXTable();
|
||||
let { setActive, index } = options;
|
||||
index = index === -1 ? index : xTable.internalData.tableFullData[index];
|
||||
// 插入行
|
||||
let result = await xTable.insertAt(rows, index);
|
||||
if (setActive) {
|
||||
// 激活最后一行的编辑模式
|
||||
xTable.setActiveRow(result.rows[result.rows.length - 1]);
|
||||
}
|
||||
await recalcSortNumber();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入一行或多行临时数据
|
||||
*
|
||||
* @param rows
|
||||
* @param index 添加下标,数字,必填
|
||||
* @param options 参数
|
||||
* @return
|
||||
*/
|
||||
function insertRows(rows: Recordable | Recordable[] = {}, index: number, options?: IAddRowsOptions) {
|
||||
if (index < 0) {
|
||||
console.warn(`【JVxeTable】insertRows:index必须传递数字,且大于-1`);
|
||||
return;
|
||||
}
|
||||
return addOrInsert(rows, index, 'inserted', options);
|
||||
}
|
||||
|
||||
/** 获取表格表单里的值 */
|
||||
function getValues(callback, rowIds) {
|
||||
let tableData = getTableData({ rowIds: rowIds });
|
||||
callback('', tableData);
|
||||
}
|
||||
|
||||
/** 获取表格数据 */
|
||||
function getTableData(options: any = {}) {
|
||||
let { rowIds } = options;
|
||||
let tableData;
|
||||
// 仅查询指定id的行
|
||||
if (isArray(rowIds) && rowIds.length > 0) {
|
||||
tableData = [];
|
||||
rowIds.forEach((rowId) => {
|
||||
let { row } = getIfRowById(rowId);
|
||||
if (row) {
|
||||
tableData.push(row);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 查询所有行
|
||||
tableData = getXTable().getTableData().fullData;
|
||||
}
|
||||
return filterNewRows(tableData, false);
|
||||
}
|
||||
|
||||
/** 仅获取新增的数据 */
|
||||
function getNewData() {
|
||||
let newData = getNewDataWithId();
|
||||
newData.forEach((row) => delete row.id);
|
||||
return newData;
|
||||
}
|
||||
|
||||
/** 仅获取新增的数据,带有id */
|
||||
function getNewDataWithId() {
|
||||
let xTable = getXTable();
|
||||
return cloneDeep(xTable.getInsertRecords());
|
||||
}
|
||||
|
||||
/** 根据ID获取行,新增的行也能查出来 */
|
||||
function getIfRowById(id) {
|
||||
let xTable = getXTable();
|
||||
let row = xTable.getRowById(id),
|
||||
isNew = false;
|
||||
if (!row) {
|
||||
row = getNewRowById(id);
|
||||
if (!row) {
|
||||
console.warn(`JVxeTable.getIfRowById:没有找到id为"${id}"的行`);
|
||||
return { row: null };
|
||||
}
|
||||
isNew = true;
|
||||
}
|
||||
return { row, isNew };
|
||||
}
|
||||
|
||||
/** 通过临时ID获取新增的行 */
|
||||
function getNewRowById(id) {
|
||||
let records = getXTable().getInsertRecords();
|
||||
for (let record of records) {
|
||||
if (record.id === id) {
|
||||
return record;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤添加的行
|
||||
* @param rows 要筛选的行数据
|
||||
* @param remove true = 删除新增,false=只删除id
|
||||
* @param handler function
|
||||
*/
|
||||
function filterNewRows(rows, remove = true, handler?: Fn) {
|
||||
let insertRecords = getXTable().getInsertRecords();
|
||||
let records: Recordable[] = [];
|
||||
for (let row of rows) {
|
||||
let item = cloneDeep(row);
|
||||
if (insertRecords.includes(row)) {
|
||||
handler ? handler({ item, row, insertRecords }) : null;
|
||||
if (remove) {
|
||||
continue;
|
||||
}
|
||||
delete item.id;
|
||||
}
|
||||
records.push(item);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置滚动条Top位置
|
||||
* @param top 新top位置,留空则滚动到上次记录的位置,用于解决切换tab选项卡时导致白屏以及自动将滚动条滚动到顶部的问题
|
||||
*/
|
||||
function resetScrollTop(top?) {
|
||||
let xTable = getXTable();
|
||||
xTable.scrollTo(null, top == null || top === '' ? data.scroll.top : top);
|
||||
}
|
||||
|
||||
/** 校验table,失败返回errMap,成功返回null */
|
||||
async function validateTable(rows?) {
|
||||
let xTable = getXTable();
|
||||
const errMap = await xTable.validate(rows ?? true).catch((errMap) => errMap);
|
||||
return errMap ? errMap : null;
|
||||
}
|
||||
|
||||
/** 完整校验 */
|
||||
async function fullValidateTable(rows?) {
|
||||
let xTable = getXTable();
|
||||
const errMap = await xTable.fullValidate(rows ?? true).catch((errMap) => errMap);
|
||||
return errMap ? errMap : null;
|
||||
}
|
||||
|
||||
type setValuesParam = { rowKey: string; values: Recordable };
|
||||
|
||||
/**
|
||||
* 设置某行某列的值
|
||||
*
|
||||
* @param values
|
||||
* @return 返回受影响的单元格数量
|
||||
*/
|
||||
function setValues(values: setValuesParam[]): number {
|
||||
if (!isArray(values)) {
|
||||
console.warn(`[JVxeTable] setValues 必须传递数组`);
|
||||
return 0;
|
||||
}
|
||||
let xTable = getXTable();
|
||||
let count = 0;
|
||||
values.forEach((item) => {
|
||||
let { rowKey, values: record } = item;
|
||||
let { row } = getIfRowById(rowKey);
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
Object.keys(record).forEach((colKey) => {
|
||||
let column = xTable.getColumnByField(colKey);
|
||||
if (column) {
|
||||
let oldValue = row[colKey];
|
||||
let newValue = record[colKey];
|
||||
if (newValue !== oldValue) {
|
||||
row[colKey] = newValue;
|
||||
// 触发 valueChange 事件
|
||||
trigger('valueChange', {
|
||||
type: column.params.type,
|
||||
value: newValue,
|
||||
oldValue: oldValue,
|
||||
col: column.params,
|
||||
column: column,
|
||||
isSetValues: true,
|
||||
row: {...row}
|
||||
});
|
||||
count++;
|
||||
}
|
||||
} else {
|
||||
console.warn(`[JVxeTable] setValues 没有找到key为"${colKey}"的列`);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (count > 0) {
|
||||
xTable.updateData();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** 清空选择行 */
|
||||
async function clearSelection() {
|
||||
const xTable = getXTable();
|
||||
let event = { $table: xTable, target: instanceRef.value };
|
||||
if (props.rowSelectionType === JVxeTypes.rowRadio) {
|
||||
await xTable.clearRadioRow();
|
||||
handleVxeRadioChange(event);
|
||||
} else {
|
||||
await xTable.clearCheckboxRow();
|
||||
handleVxeCheckboxChange(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中数据
|
||||
* @param isFull 如果 isFull=true 则获取全表已选中的数据
|
||||
*/
|
||||
function getSelectionData(isFull?: boolean) {
|
||||
const xTable = getXTable();
|
||||
if (props.rowSelectionType === JVxeTypes.rowRadio) {
|
||||
let row = xTable.getRadioRecord(isFull);
|
||||
if (isNull(row)) {
|
||||
return [];
|
||||
}
|
||||
return filterNewRows([row], false);
|
||||
} else {
|
||||
return filterNewRows(xTable.getCheckboxRecords(isFull), false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 仅获取被删除的数据(新增又被删除的数据不会被获取到) */
|
||||
function getDeleteData() {
|
||||
return filterNewRows(getXTable().getRemoveRecords(), false);
|
||||
}
|
||||
|
||||
/** 删除一行或多行数据 */
|
||||
async function removeRows(rows, asyncRemove = false) {
|
||||
// update-begin--author:liaozhiyang---date:20231123---for:vxe-table removeRows方法加上异步删除
|
||||
const xTable = getXTable();
|
||||
const removeEvent: any = { deleteRows: rows, $table: xTable };
|
||||
if (asyncRemove) {
|
||||
const selectedRows = Array.isArray(rows) ? rows : [rows];
|
||||
const deleteOldRows = filterNewRows(selectedRows);
|
||||
if (deleteOldRows.length) {
|
||||
return new Promise((resolve) => {
|
||||
// 确认删除,只有调用这个方法才会真删除
|
||||
removeEvent.confirmRemove = async () => {
|
||||
const insertRecords = xTable.getInsertRecords();
|
||||
selectedRows.forEach((item) => {
|
||||
// 删除新添加的数据id
|
||||
if (insertRecords.includes(item)) {
|
||||
delete item.id;
|
||||
}
|
||||
});
|
||||
const res = await xTable.remove(rows);
|
||||
await recalcSortNumber();
|
||||
resolve(res);
|
||||
};
|
||||
trigger('removed', removeEvent);
|
||||
});
|
||||
} else {
|
||||
// 全新的行立马删除,不等待。
|
||||
const res = await xTable.remove(rows);
|
||||
removeEvent.confirmRemove = () => {};
|
||||
trigger('removed', removeEvent);
|
||||
await recalcSortNumber();
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
const res = await xTable.remove(rows);
|
||||
trigger('removed', removeEvent);
|
||||
await recalcSortNumber();
|
||||
return res;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231123---for:vxe-table removeRows方法加上异步删除
|
||||
}
|
||||
|
||||
/** 根据id删除一行或多行 */
|
||||
function removeRowsById(rowId) {
|
||||
let rowIds;
|
||||
if (isArray(rowId)) {
|
||||
rowIds = rowId;
|
||||
} else {
|
||||
rowIds = [rowId];
|
||||
}
|
||||
let rows = rowIds
|
||||
.map((id) => {
|
||||
let { row } = getIfRowById(id);
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
if (row) {
|
||||
return row;
|
||||
} else {
|
||||
console.warn(`【JVxeTable】removeRowsById:${id}不存在`);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((row) => row != null);
|
||||
return removeRows(rows);
|
||||
}
|
||||
|
||||
// 删除选中的数据
|
||||
async function removeSelection() {
|
||||
let xTable = getXTable();
|
||||
let res;
|
||||
if (props.rowSelectionType === JVxeTypes.rowRadio) {
|
||||
res = await xTable.removeRadioRow();
|
||||
} else {
|
||||
res = await xTable.removeCheckboxRow();
|
||||
}
|
||||
await clearSelection();
|
||||
await recalcSortNumber();
|
||||
return res;
|
||||
}
|
||||
|
||||
/** 重新计算排序字段的数值 */
|
||||
async function recalcSortNumber(force = false) {
|
||||
if (props.dragSort || force) {
|
||||
let xTable = getXTable();
|
||||
let sortKey = props.sortKey ?? 'orderNum';
|
||||
let sortBegin = props.sortBegin ?? 0;
|
||||
xTable.internalData.tableFullData.forEach((data) => (data[sortKey] = sortBegin++));
|
||||
// update-begin--author:liaozhiyang---date:20231011---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
// 4.1.0
|
||||
//await xTable.updateCache();
|
||||
// 4.1.1
|
||||
await xTable.cacheRowMap()
|
||||
// update-end--author:liaozhiyang---date:20231011---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
return await xTable.updateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序表格
|
||||
* @param oldIndex
|
||||
* @param newIndex
|
||||
* @param force 强制排序
|
||||
*/
|
||||
async function doSort(oldIndex: number, newIndex: number, force = false) {
|
||||
if (props.dragSort || force) {
|
||||
let xTable = getXTable();
|
||||
let sort = (array) => {
|
||||
// 存储old数据,并删除该项
|
||||
let row = array.splice(oldIndex, 1)[0];
|
||||
// 向newIndex处添加old数据
|
||||
array.splice(newIndex, 0, row);
|
||||
};
|
||||
sort(xTable.internalData.tableFullData);
|
||||
if (xTable.keepSource) {
|
||||
sort(xTable.internalData.tableSourceData);
|
||||
}
|
||||
return await recalcSortNumber(force);
|
||||
}
|
||||
}
|
||||
|
||||
/** 行重新排序 */
|
||||
function rowResort(oldIndex: number, newIndex: number) {
|
||||
return doSort(oldIndex, newIndex, true);
|
||||
}
|
||||
|
||||
// ---------------- begin 权限控制 ----------------
|
||||
// 加载权限
|
||||
function loadAuthsMap() {
|
||||
if (!props.authPre || props.authPre.length == 0) {
|
||||
data.authsMap.value = null;
|
||||
} else {
|
||||
data.authsMap.value = getJVxeAuths(props.authPre);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 权限code 获取权限
|
||||
* @param authCode
|
||||
*/
|
||||
function getAuth(authCode) {
|
||||
if (data.authsMap.value != null && props.authPre) {
|
||||
let prefix = getPrefix(props.authPre);
|
||||
return data.authsMap.value.get(prefix + authCode);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取列权限
|
||||
function getColAuth(key: string) {
|
||||
return getAuth(key);
|
||||
}
|
||||
|
||||
// 判断按钮权限
|
||||
function hasBtnAuth(key: string) {
|
||||
return getAuth('btn:' + key)?.isAuth ?? true;
|
||||
}
|
||||
|
||||
// ---------------- end 权限控制 ----------------
|
||||
|
||||
/* --- 辅助方法 ---*/
|
||||
|
||||
function created() {
|
||||
loadAuthsMap();
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
function trigger(name, event: any = {}) {
|
||||
event.$target = instanceRef.value;
|
||||
event.$table = getXTable();
|
||||
//online增强参数兼容
|
||||
event.target = instanceRef.value;
|
||||
emit(name, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选中的行-和 getSelectionData 区别在于对于新增的行也会返回ID
|
||||
* 用于onlinePopForm
|
||||
* @param isFull
|
||||
*/
|
||||
function getSelectedData(isFull?: boolean) {
|
||||
const xTable = getXTable();
|
||||
let rows:any[] = []
|
||||
if (props.rowSelectionType === JVxeTypes.rowRadio) {
|
||||
let row = xTable.getRadioRecord(isFull);
|
||||
if (isNull(row)) {
|
||||
return [];
|
||||
}
|
||||
rows = [row]
|
||||
} else {
|
||||
rows = xTable.getCheckboxRecords(isFull)
|
||||
}
|
||||
let records: Recordable[] = [];
|
||||
for (let row of rows) {
|
||||
let item = cloneDeep(row);
|
||||
records.push(item);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
/**
|
||||
* 2024-03-21
|
||||
* liaozhiyang
|
||||
* VXETable列设置保存缓存字段名
|
||||
* */
|
||||
function handleCustom({ type, $grid }) {
|
||||
const { saveSetting, resetSetting } = useColumnsCache({ cacheColumnsKey: props.cacheColumnsKey });
|
||||
if (type === 'confirm') {
|
||||
saveSetting($grid);
|
||||
} else if (type == 'reset') {
|
||||
resetSetting($grid);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
methods: {
|
||||
trigger,
|
||||
...publicMethods,
|
||||
closeScrolling,
|
||||
doSort,
|
||||
recalcSortNumber,
|
||||
handleVxeScroll,
|
||||
handleVxeRadioChange,
|
||||
handleVxeCheckboxAll,
|
||||
handleVxeCheckboxChange,
|
||||
handleFooterMethod,
|
||||
handleCellClick,
|
||||
handleEditActived,
|
||||
handleEditClosed,
|
||||
handleCheckMethod,
|
||||
handleActiveMethod,
|
||||
handleExpandToggleMethod,
|
||||
getColAuth,
|
||||
hasBtnAuth,
|
||||
handleCustom,
|
||||
},
|
||||
publicMethods,
|
||||
created,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
import { computed, reactive, h } from 'vue';
|
||||
import { JVxeTableMethods, JVxeTableProps } from '/@/components/jeecg/JVxeTable/src/types';
|
||||
import { isEmpty } from '/@/utils/is';
|
||||
import { Pagination } from 'ant-design-vue';
|
||||
|
||||
export function usePagination(props: JVxeTableProps, methods: JVxeTableMethods) {
|
||||
const innerPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: ['10', '20', '30'],
|
||||
showTotal: (total, range) => {
|
||||
return range[0] + '-' + range[1] + ' 共 ' + total + ' 条';
|
||||
},
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
total: 100,
|
||||
});
|
||||
|
||||
const bindProps = computed(() => {
|
||||
return {
|
||||
...innerPagination,
|
||||
...props.pagination,
|
||||
size: props.size === 'tiny' ? 'small' : '',
|
||||
};
|
||||
});
|
||||
|
||||
const boxClass = computed(() => {
|
||||
return {
|
||||
'j-vxe-pagination': true,
|
||||
'show-quick-jumper': !!bindProps.value.showQuickJumper,
|
||||
};
|
||||
});
|
||||
|
||||
function handleChange(current, pageSize) {
|
||||
innerPagination.current = current;
|
||||
methods.trigger('pageChange', { current, pageSize });
|
||||
}
|
||||
|
||||
function handleShowSizeChange(current, pageSize) {
|
||||
innerPagination.pageSize = pageSize;
|
||||
methods.trigger('pageChange', { current, pageSize });
|
||||
}
|
||||
|
||||
/** 渲染分页器 */
|
||||
function renderPagination() {
|
||||
if (props.pagination && !isEmpty(props.pagination)) {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: boxClass.value,
|
||||
},
|
||||
[
|
||||
h(Pagination, {
|
||||
...bindProps.value,
|
||||
disabled: props.disabled,
|
||||
onChange: handleChange,
|
||||
onShowSizeChange: handleShowSizeChange,
|
||||
}),
|
||||
]
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return { renderPagination };
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
import { h } from 'vue';
|
||||
import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types';
|
||||
import JVxeSubPopover from '../components/JVxeSubPopover.vue';
|
||||
import JVxeDetailsModal from '../components/JVxeDetailsModal.vue';
|
||||
import { useToolbar } from '/@/components/jeecg/JVxeTable/src/hooks/useToolbar';
|
||||
import { usePagination } from '/@/components/jeecg/JVxeTable/src/hooks/usePagination';
|
||||
|
||||
export function useRenderComponents(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods, slots) {
|
||||
// 渲染 toolbar
|
||||
const { renderToolbar } = useToolbar(props, data, methods, slots);
|
||||
// 渲染分页器
|
||||
const { renderPagination } = usePagination(props, methods);
|
||||
|
||||
// 渲染 toolbarAfter 插槽
|
||||
function renderToolbarAfterSlot() {
|
||||
if (slots['toolbarAfter']) {
|
||||
return slots['toolbarAfter']();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 渲染点击时弹出的子表
|
||||
function renderSubPopover() {
|
||||
if (props.clickRowShowSubForm && slots.subForm) {
|
||||
return h(
|
||||
JVxeSubPopover,
|
||||
{
|
||||
ref: 'subPopoverRef',
|
||||
},
|
||||
{
|
||||
subForm: slots.subForm,
|
||||
}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 渲染点击时弹出的详细信息
|
||||
function renderDetailsModal() {
|
||||
if (props.clickRowShowMainForm && slots.mainForm) {
|
||||
return h(
|
||||
JVxeDetailsModal,
|
||||
{
|
||||
ref: 'detailsModalRef',
|
||||
trigger: methods.trigger,
|
||||
},
|
||||
{
|
||||
mainForm: slots.mainForm,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
renderToolbar,
|
||||
renderPagination,
|
||||
renderSubPopover,
|
||||
renderDetailsModal,
|
||||
renderToolbarAfterSlot,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import { h } from 'vue';
|
||||
import JVxeToolbar from '../components/JVxeToolbar.vue';
|
||||
import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types';
|
||||
|
||||
export function useToolbar(props: JVxeTableProps, data: JVxeDataProps, methods: JVxeTableMethods, $slots) {
|
||||
/** 渲染工具栏 */
|
||||
function renderToolbar() {
|
||||
if (props.toolbar) {
|
||||
return h(
|
||||
JVxeToolbar,
|
||||
{
|
||||
size: props.size,
|
||||
disabled: props.disabled,
|
||||
toolbarConfig: props.toolbarConfig,
|
||||
disabledRows: props.disabledRows,
|
||||
hasBtnAuth: methods.hasBtnAuth,
|
||||
selectedRowIds: data.selectedRowIds.value,
|
||||
custom: props.custom,
|
||||
// 新增事件
|
||||
onAdd: () => {
|
||||
// update-begin--author:liaozhiyang---date:20240521---for:【TV360X-212】online新增字段就出校验提示
|
||||
setTimeout(() => {
|
||||
methods.addRows();
|
||||
}, 0);
|
||||
// update-end--author:liaozhiyang---date:20240521---for:【TV360X-212】online新增字段就出校验提示
|
||||
},
|
||||
// 保存事件
|
||||
onSave: () => methods.trigger('save'),
|
||||
onRemove() {
|
||||
const $table = methods.getXTable();
|
||||
// update-begin--author:liaozhiyang---date:20231018---for:【QQYUN-6805】修复asyncRemove字段不生效
|
||||
// 触发删除事件
|
||||
if (data.selectedRows.value.length > 0) {
|
||||
const deleteOldRows = methods.filterNewRows(data.selectedRows.value);
|
||||
const removeEvent: any = { deleteRows: data.selectedRows.value, $table };
|
||||
const insertRecords = $table.getInsertRecords();
|
||||
if (props.asyncRemove && deleteOldRows.length) {
|
||||
data.selectedRows.value.forEach((item) => {
|
||||
// 删除新添加的数据id
|
||||
if (insertRecords.includes(item)) {
|
||||
delete item.id;
|
||||
}
|
||||
});
|
||||
// 确认删除,只有调用这个方法才会真删除
|
||||
removeEvent.confirmRemove = () => methods.removeSelection();
|
||||
} else {
|
||||
if (props.asyncRemove) {
|
||||
// asyncRemove删除的只有新增的数据时,防止调用confirmRemove报错
|
||||
removeEvent.confirmRemove = () => {};
|
||||
}
|
||||
methods.removeSelection();
|
||||
}
|
||||
methods.trigger('removed', removeEvent);
|
||||
} else {
|
||||
methods.removeSelection();
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20231018---for:【QQYUN-6805】修复asyncRemove字段不生效
|
||||
},
|
||||
// 清除选择事件
|
||||
onClearSelection: () => methods.clearSelection(),
|
||||
onRegister: ({ xToolbarRef }) => methods.getXTable().connect(xToolbarRef.value),
|
||||
},
|
||||
{
|
||||
toolbarPrefix: $slots.toolbarPrefix,
|
||||
toolbarSuffix: $slots.toolbarSuffix,
|
||||
}
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return { renderToolbar };
|
||||
}
|
||||
@ -0,0 +1,108 @@
|
||||
import { VxeTablePropTypes } from 'vxe-table';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { HandleArgs } from './useColumns';
|
||||
import { replaceProps } from '../utils/enhancedUtils';
|
||||
|
||||
export function useValidateRules(args: HandleArgs) {
|
||||
const { data } = args;
|
||||
const col = args.col!;
|
||||
let rules: VxeTablePropTypes.EditRules[] = [];
|
||||
if (isArray(col.validateRules)) {
|
||||
for (let rule of col.validateRules) {
|
||||
let replace = {
|
||||
message: replaceProps(col, rule.message),
|
||||
};
|
||||
if (rule.unique || rule.pattern === 'only') {
|
||||
// 唯一校验器
|
||||
rule.validator = uniqueValidator(args);
|
||||
} else if (rule.pattern) {
|
||||
// 非空
|
||||
if (rule.pattern === fooPatterns[0].value) {
|
||||
rule.required = true;
|
||||
delete rule.pattern;
|
||||
} else {
|
||||
// 兼容Online表单的特殊规则
|
||||
for (let foo of fooPatterns) {
|
||||
if (foo.value === rule.pattern) {
|
||||
rule.pattern = foo.pattern;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof rule.handler === 'function') {
|
||||
// 自定义函数校验
|
||||
rule.validator = handlerConvertToValidator;
|
||||
}
|
||||
rules.push(Object.assign({}, rule, replace));
|
||||
}
|
||||
}
|
||||
data.innerEditRules[col.key] = rules;
|
||||
}
|
||||
|
||||
/** 唯一校验器 */
|
||||
function uniqueValidator({ methods }: HandleArgs) {
|
||||
return function (event) {
|
||||
const { cellValue, column, rule } = event;
|
||||
// update-begin--author:liaozhiyang---date:20240522---for:【TV360X-299】JVxetable组件中唯一校验过滤掉空字符串
|
||||
if (cellValue == '') return Promise.resolve();
|
||||
// update-end--author:liaozhiyang---date:20240522---for:【TV360X-299】JVxetable组件中唯一校验过滤掉空字符串
|
||||
let tableData = methods.getTableData();
|
||||
let findCount = 0;
|
||||
for (let rowData of tableData) {
|
||||
if (rowData[column.params.key] === cellValue) {
|
||||
if (++findCount >= 2) {
|
||||
return Promise.reject(new Error(rule.message));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
/** 旧版handler转为新版Validator */
|
||||
function handlerConvertToValidator(event) {
|
||||
const { column, rule } = event;
|
||||
return new Promise((resolve, reject) => {
|
||||
rule.handler(event, (flag, msg) => {
|
||||
let message = rule.message;
|
||||
if (typeof msg === 'string') {
|
||||
message = replaceProps(column.params, msg);
|
||||
}
|
||||
if (flag == null) {
|
||||
resolve(message);
|
||||
} else if (!!flag) {
|
||||
resolve(message);
|
||||
} else {
|
||||
reject(new Error(message));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 兼容 online 的规则
|
||||
const fooPatterns = [
|
||||
{ title: '非空', value: '*', pattern: /^.+$/ },
|
||||
{ title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,16}$/ },
|
||||
{ title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/ },
|
||||
{ title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/ },
|
||||
//update-begin-author:taoyan date:2022-6-1 for: VUEN-1160 对多子表,网址校验不正确
|
||||
{
|
||||
title: '网址',
|
||||
value: 'url',
|
||||
pattern: /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/,
|
||||
},
|
||||
//update-end-author:taoyan date:2022-6-1 for: VUEN-1160 对多子表,网址校验不正确
|
||||
// update-begin--author:liaozhiyang---date:20240527---for:【TV360X-466】邮箱跟一对第一校验规则一致
|
||||
{ title: '电子邮件', value: 'e', pattern: /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/ },
|
||||
// update-end--author:liaozhiyang---date:20240527---for:【TV360X-466】邮箱跟一对第一校验规则一致
|
||||
{ title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/ },
|
||||
{ title: '邮政编码', value: 'p', pattern: /^\d{6}$/ },
|
||||
{ title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/ },
|
||||
{ title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/ },
|
||||
{ title: '整数', value: 'z', pattern: /^-?\d+$/ },
|
||||
{
|
||||
title: '金额',
|
||||
value: 'money',
|
||||
pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,5}))$/,
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,236 @@
|
||||
import { watch, onUnmounted } from 'vue';
|
||||
import { buildUUID } from '/@/utils/uuid';
|
||||
import { useGlobSetting } from '/@/hooks/setting';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { JVxeDataProps, JVxeTableMethods, JVxeTableProps } from '../types';
|
||||
import { isArray } from '/@/utils/is';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
|
||||
// vxe socket
|
||||
const vs = {
|
||||
// 页面唯一 id,用于标识同一用户,不同页面的websocket
|
||||
pageId: buildUUID(),
|
||||
// webSocket 对象
|
||||
ws: null,
|
||||
// 一些常量
|
||||
constants: {
|
||||
// 消息类型
|
||||
TYPE: 'type',
|
||||
// 消息数据
|
||||
DATA: 'data',
|
||||
// 消息类型:心跳检测
|
||||
TYPE_HB: 'heart_beat',
|
||||
// 消息类型:更新vxe table数据
|
||||
TYPE_UVT: 'update_vxe_table',
|
||||
},
|
||||
// 心跳检测
|
||||
heartCheck: {
|
||||
// 间隔时间,间隔多久发送一次心跳消息
|
||||
interval: 10000,
|
||||
// 心跳消息超时时间,心跳消息多久没有回复后重连
|
||||
timeout: 6000,
|
||||
timeoutTimer: -1,
|
||||
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 = useUserStore().getUserInfo?.id;
|
||||
const domainURL = useGlobSetting().uploadUrl!;
|
||||
const domain = domainURL.replace('https://', 'wss://').replace('http://', 'ws://');
|
||||
const url = `${domain}/vxeSocket/${userId}/${this.pageId}`;
|
||||
//update-begin-author:taoyan date:2022-4-24 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
|
||||
let token = (getToken() || '') as string;
|
||||
this.ws = new WebSocket(url, [token]);
|
||||
//update-end-author:taoyan date:2022-4-24 for: v2.4.6 的 websocket 服务端,存在性能和安全问题。 #3278
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
// 发送消息
|
||||
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: any) {
|
||||
console.warn('【JVxeWebSocket】发送消息失败:(' + err.code + ')');
|
||||
}
|
||||
},
|
||||
|
||||
/** 绑定全局VXE表格 */
|
||||
tableMap: new Map(),
|
||||
/** 添加绑定 */
|
||||
addBind(map, key, value: VmArgs) {
|
||||
let binds = map.get(key);
|
||||
if (isArray(binds)) {
|
||||
binds.push(value);
|
||||
} else {
|
||||
map.set(key, [value]);
|
||||
}
|
||||
},
|
||||
/** 移除绑定 */
|
||||
removeBind(map, key, value: VmArgs) {
|
||||
let binds = map.get(key);
|
||||
if (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 (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('【JVxeWebSocket】尝试重连...');
|
||||
this.initialWebSocket();
|
||||
this.lockReconnect = false;
|
||||
}, 5000);
|
||||
},
|
||||
|
||||
on: {
|
||||
open() {
|
||||
console.info('【JVxeWebSocket】连接成功');
|
||||
this.heartCheck.start();
|
||||
},
|
||||
error(e) {
|
||||
console.warn('【JVxeWebSocket】连接发生错误:', e);
|
||||
this.reconnect();
|
||||
},
|
||||
message(e) {
|
||||
// 解析消息
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(e.data);
|
||||
} catch (e: any) {
|
||||
console.warn('【JVxeWebSocket】收到无法解析的消息:', 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;
|
||||
// 更新form数据
|
||||
case this.constants.TYPE_UVT:
|
||||
this.callBind(this.tableMap, data.socketKey, (args) => this.onVM.onUpdateTable(args, ...data.args));
|
||||
break;
|
||||
default:
|
||||
console.warn('【JVxeWebSocket】收到不识别的消息类型:' + type);
|
||||
break;
|
||||
}
|
||||
},
|
||||
close(e) {
|
||||
console.info('【JVxeWebSocket】连接被关闭:', e);
|
||||
this.reconnect();
|
||||
},
|
||||
},
|
||||
|
||||
onVM: {
|
||||
/** 收到更新表格的消息 */
|
||||
onUpdateTable({ props, data, methods }: VmArgs, row, caseId) {
|
||||
if (data.caseId !== caseId) {
|
||||
const tableRow = methods.getIfRowById(row.id).row;
|
||||
// 局部保更新数据
|
||||
if (tableRow) {
|
||||
if (props.reloadEffect) {
|
||||
data.reloadEffectRowKeysMap[row.id] = true;
|
||||
}
|
||||
Object.assign(tableRow, row, { id: tableRow.id });
|
||||
methods.getXTable().reloadRow(tableRow);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
} as {
|
||||
ws: Nullable<WebSocket>;
|
||||
} & Recordable;
|
||||
|
||||
type VmArgs = {
|
||||
props: JVxeTableProps;
|
||||
data: JVxeDataProps;
|
||||
methods: JVxeTableMethods;
|
||||
};
|
||||
|
||||
export function useWebSocket(props: JVxeTableProps, data: JVxeDataProps, methods) {
|
||||
const args: VmArgs = { props, data, methods };
|
||||
watch(
|
||||
() => props.socketReload,
|
||||
(socketReload: boolean) => {
|
||||
if (socketReload) {
|
||||
vs.initialWebSocket();
|
||||
vs.addBind(vs.tableMap, props.socketKey, args);
|
||||
} else {
|
||||
vs.removeBind(vs.tableMap, props.socketKey, args);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/** 发送socket消息更新行 */
|
||||
function socketSendUpdateRow(row) {
|
||||
vs.sendMessage(vs.constants.TYPE_UVT, {
|
||||
socketKey: props.socketKey,
|
||||
args: [row, data.caseId],
|
||||
});
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
vs.removeBind(vs.tableMap, props.socketKey, args);
|
||||
});
|
||||
|
||||
return {
|
||||
socketSendUpdateRow,
|
||||
};
|
||||
}
|
||||
75
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/install.ts
Normal file
75
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/install.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import type { App } from 'vue';
|
||||
// 引入 vxe-table
|
||||
import 'xe-utils';
|
||||
import VXETable /*Grid*/ from 'vxe-table';
|
||||
import VXETablePluginAntd from 'vxe-table-plugin-antd';
|
||||
import 'vxe-table/lib/style.css';
|
||||
|
||||
import JVxeTable from './JVxeTable';
|
||||
import { getEventPath } from '/@/utils/common/compUtils';
|
||||
import { registerAllComponent } from './utils/registerUtils';
|
||||
import { getEnhanced } from './utils/enhancedUtils';
|
||||
|
||||
export function registerJVxeTable(app: App) {
|
||||
// VXETable 全局配置
|
||||
const VXETableSettings = {
|
||||
// z-index 起始值
|
||||
zIndex: 1000,
|
||||
table: {},
|
||||
};
|
||||
|
||||
// 添加事件拦截器 event.clearActived
|
||||
// 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
|
||||
VXETable.interceptor.add('event.clearActived', preventClosingPopUp);
|
||||
VXETable.interceptor.add('event.clearEdit', preventClosingPopUp);
|
||||
// 注册插件
|
||||
VXETable.use(VXETablePluginAntd);
|
||||
// 注册自定义组件
|
||||
registerAllComponent();
|
||||
// 执行注册方法
|
||||
app.use(VXETable, VXETableSettings);
|
||||
app.component('JVxeTable', JVxeTable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 阻止行编辑中关闭弹窗
|
||||
* @param params
|
||||
*/
|
||||
function preventClosingPopUp(this: any, params) {
|
||||
// 获取组件增强
|
||||
let col = params.column.params;
|
||||
let { $event } = params;
|
||||
const interceptor = getEnhanced(col.type).interceptor;
|
||||
// 执行增强
|
||||
let flag = interceptor['event.clearActived']?.call(this, ...arguments);
|
||||
if (flag === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let path = getEventPath($event);
|
||||
for (let p of path) {
|
||||
let className: any = 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;
|
||||
}
|
||||
// 点击的是日期选择器
|
||||
if (className.includes('j-vxe-date-picker')) {
|
||||
return false;
|
||||
}
|
||||
// 执行增强
|
||||
let flag = interceptor['event.clearActived.className']?.call(this, className, ...arguments);
|
||||
if (flag === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
@import 'vxe.const';
|
||||
@import 'vxe.dark';
|
||||
|
||||
.@{prefix-cls} {
|
||||
// 编辑按钮样式
|
||||
.vxe-cell--edit-icon {
|
||||
border-color: #606266;
|
||||
}
|
||||
|
||||
.sort--active {
|
||||
border-color: @primary-color;
|
||||
}
|
||||
|
||||
// toolbar 样式
|
||||
&-toolbar {
|
||||
&-collapsed {
|
||||
[data-collapse] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-button.div .ant-btn {
|
||||
margin-right: 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-body--row.sortable-ghost,
|
||||
.vxe-body--row.sortable-chosen {
|
||||
background-color: #dfecfb;
|
||||
}
|
||||
|
||||
// ----------- 【VUEN-1691】默认隐藏滚动条,鼠标放上去才显示 -------------------------------------------
|
||||
.vxe-table {
|
||||
//.vxe-table--footer-wrapper.body--wrapper,
|
||||
.vxe-table--body-wrapper.body--wrapper {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
//.vxe-table--footer-wrapper.body--wrapper,
|
||||
.vxe-table--body-wrapper.body--wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ----------- 【VUEN-1691】默认隐藏滚动条,鼠标放上去才显示 -------------------------------------------
|
||||
|
||||
// 调整展开/收起图标样式
|
||||
.vxe-table--render-default .vxe-table--expanded .vxe-table--expand-btn {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
/*【美化表单】行编辑table的title字体改小一号*/
|
||||
.vxe-header--column.col--ellipsis>.vxe-cell .vxe-cell--title{
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
.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,2 @@
|
||||
//noinspection LessUnresolvedVariable
|
||||
@prefix-cls: ~'@{namespace}-j-vxe-table';
|
||||
@ -0,0 +1,124 @@
|
||||
@import 'vxe.const';
|
||||
// update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确
|
||||
html[data-theme='dark'] {
|
||||
--vxe-table-body-background-color: #151515;
|
||||
--vxe-table-footer-background-color: #151515;
|
||||
--vxe-table-border-color: #606060;
|
||||
--vxe-table-popup-border-color:#606060;
|
||||
--vxe-table-row-hover-background-color:#1e1e1e;
|
||||
--vxe-input-border-color: #606266;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确
|
||||
[data-theme='dark'] .@{prefix-cls} {
|
||||
@fontColor: #c9d1d9;
|
||||
@bgColor: #151515;
|
||||
@borderColor: #606060;
|
||||
|
||||
.vxe-cell--item,
|
||||
.vxe-cell--title,
|
||||
.vxe-cell,
|
||||
.vxe-body--expanded-cell {
|
||||
color: @fontColor;
|
||||
}
|
||||
|
||||
.vxe-toolbar {
|
||||
// update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确
|
||||
background-color: #1f1f1f;
|
||||
// update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8493】修正暗黑模式online表单Erp和编辑页面显示不正确
|
||||
}
|
||||
|
||||
.vxe-table--render-default .vxe-table--body-wrapper,
|
||||
.vxe-table--render-default .vxe-table--footer-wrapper {
|
||||
background-color: @bgColor;
|
||||
}
|
||||
|
||||
// 外边框
|
||||
.vxe-table--render-default .vxe-table--border-line {
|
||||
border-color: @borderColor;
|
||||
}
|
||||
|
||||
// header 下边框
|
||||
.vxe-table .vxe-table--header-wrapper .vxe-table--header-border-line {
|
||||
border-bottom-color: @borderColor;
|
||||
}
|
||||
|
||||
// footer 上边框
|
||||
.vxe-table--render-default .vxe-table--footer-wrapper {
|
||||
border-top-color: @borderColor;
|
||||
}
|
||||
|
||||
// 展开行 边框
|
||||
.vxe-table--render-default .vxe-body--expanded-column {
|
||||
border-bottom-color: @borderColor;
|
||||
}
|
||||
|
||||
// 行斑马纹
|
||||
.vxe-table--render-default .vxe-body--row.row--stripe {
|
||||
background-color: #1e1e1e;
|
||||
}
|
||||
|
||||
// 行hover
|
||||
.vxe-table--render-default .vxe-body--row.row--hover {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
// 选中行
|
||||
.vxe-table--render-default .vxe-body--row.row--checked {
|
||||
background-color: #44403a;
|
||||
|
||||
&.row--hover {
|
||||
background-color: #59524b;
|
||||
}
|
||||
}
|
||||
|
||||
.vxe-table--render-default.border--default .vxe-table--header-wrapper,
|
||||
.vxe-table--render-default.border--full .vxe-table--header-wrapper,
|
||||
.vxe-table--render-default.border--outer .vxe-table--header-wrapper {
|
||||
background-color: #1d1d1d;
|
||||
}
|
||||
|
||||
.vxe-table--render-default.border--default .vxe-body--column,
|
||||
.vxe-table--render-default.border--default .vxe-footer--column,
|
||||
.vxe-table--render-default.border--default .vxe-header--column,
|
||||
.vxe-table--render-default.border--inner .vxe-body--column,
|
||||
.vxe-table--render-default.border--inner .vxe-footer--column,
|
||||
.vxe-table--render-default.border--inner .vxe-header--column {
|
||||
background-image: linear-gradient(#1d1d1d, #1d1d1d);
|
||||
}
|
||||
|
||||
// 列宽拖动
|
||||
.vxe-header--column .vxe-resizable.is--line:before {
|
||||
background-color: #505050;
|
||||
}
|
||||
|
||||
// checkbox
|
||||
.vxe-custom--option .vxe-checkbox--icon:before,
|
||||
.vxe-export--panel-column-option .vxe-checkbox--icon:before,
|
||||
.vxe-table--filter-option .vxe-checkbox--icon:before,
|
||||
.vxe-table--render-default .vxe-cell--checkbox .vxe-checkbox--icon:before {
|
||||
background-color: @bgColor;
|
||||
border-color: @borderColor;
|
||||
}
|
||||
|
||||
.vxe-toolbar .vxe-custom--option-wrapper {
|
||||
background-color: @bgColor;
|
||||
}
|
||||
|
||||
.vxe-button {
|
||||
background-color: @bgColor;
|
||||
border-color: @borderColor;
|
||||
}
|
||||
|
||||
.vxe-button.type--button:not(.is--disabled):active {
|
||||
background-color: @bgColor;
|
||||
}
|
||||
|
||||
.vxe-toolbar .vxe-custom--wrapper.is--active > .vxe-button {
|
||||
background-color: @bgColor;
|
||||
}
|
||||
|
||||
.vxe-toolbar .vxe-custom--option-wrapper .vxe-custom--footer button {
|
||||
color: @fontColor;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
import { ComponentInternalInstance, ExtractPropTypes } from 'vue';
|
||||
import { useJVxeCompProps } from '/@/components/jeecg/JVxeTable/hooks';
|
||||
|
||||
export namespace JVxeComponent {
|
||||
export type Props = ExtractPropTypes<ReturnType<typeof useJVxeCompProps>>;
|
||||
|
||||
interface EnhancedCtx {
|
||||
props?: JVxeComponent.Props;
|
||||
context?: any;
|
||||
}
|
||||
|
||||
/** 组件增强类型 */
|
||||
export interface Enhanced {
|
||||
// 注册参数(详见:https://xuliangzhan_admin.gitee.io/vxe-table/v4/table/renderer/edit)
|
||||
installOptions: {
|
||||
// 自动聚焦的 class 类名
|
||||
autofocus?: string;
|
||||
} & Recordable;
|
||||
// 事件拦截器(用于兼容)
|
||||
interceptor: {
|
||||
// 已实现:event.clearActived
|
||||
// 说明:比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
|
||||
'event.clearActived'?: (params, event, target, ctx?: EnhancedCtx) => boolean;
|
||||
// 自定义:event.clearActived.className
|
||||
// 说明:比原生的多了一个参数:className,用于判断点击的元素的样式名(递归到顶层)
|
||||
'event.clearActived.className'?: (params, event, target, ctx?: EnhancedCtx) => boolean;
|
||||
};
|
||||
// 【功能开关】
|
||||
switches: {
|
||||
// 是否使用 editRender 模式(仅当前组件,并非全局)
|
||||
// 如果设为true,则表头上方会出现一个可编辑的图标
|
||||
editRender?: boolean;
|
||||
// false = 组件触发后可视);true = 组件一直可视
|
||||
visible?: boolean;
|
||||
};
|
||||
// 【切面增强】切面事件处理,一般在某些方法执行后同步执行
|
||||
aopEvents: {
|
||||
// 单元格被激活编辑时会触发该事件
|
||||
editActived?: (this: ComponentInternalInstance, ...args) => any;
|
||||
// 单元格编辑状态下被关闭时会触发该事件
|
||||
editClosed?: (this: ComponentInternalInstance, ...args) => any;
|
||||
// 返回值决定单元格是否可以编辑
|
||||
activeMethod?: (this: ComponentInternalInstance, ...args) => boolean;
|
||||
};
|
||||
// 【翻译增强】可以实现例如select组件保存的value,但是span模式下需要显示成text
|
||||
translate: {
|
||||
// 是否启用翻译
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* 【翻译处理方法】如果handler留空,则使用默认的翻译方法
|
||||
*
|
||||
* @param value 需要翻译的值
|
||||
* @returns{*} 返回翻译后的数据
|
||||
*/
|
||||
handler?: (value, ctx?: EnhancedCtx) => any;
|
||||
};
|
||||
/**
|
||||
* 【获取值增强】组件抛出的值
|
||||
*
|
||||
* @param value 保存到数据库里的值
|
||||
* @returns{*} 返回处理后的值
|
||||
*/
|
||||
getValue: (value, ctx?: EnhancedCtx) => any;
|
||||
/**
|
||||
* 【设置值增强】设置给组件的值
|
||||
*
|
||||
* @param value 组件触发的值
|
||||
* @returns{*} 返回处理后的值
|
||||
*/
|
||||
setValue: (value, ctx?: EnhancedCtx) => any;
|
||||
/**
|
||||
* 【新增行增强】在用户点击新增时触发的事件,返回新行的默认值
|
||||
*
|
||||
* @param defaultValue 默认值
|
||||
* @param row 行数据
|
||||
* @param column 列配置,.params 是用户配置的参数
|
||||
* @param $table vxe 实例
|
||||
* @param renderOptions 渲染选项
|
||||
* @param params 可以在这里获取 $table
|
||||
*
|
||||
* @returns 返回新值
|
||||
*/
|
||||
createValue: (defaultValue: any, ctx?: EnhancedCtx) => any;
|
||||
}
|
||||
|
||||
export type EnhancedPartial = Partial<Enhanced>;
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/** 组件类型 */
|
||||
export enum JVxeTypes {
|
||||
// 行号列
|
||||
rowNumber = 'row-number',
|
||||
// 选择列
|
||||
rowCheckbox = 'row-checkbox',
|
||||
// 单选列
|
||||
rowRadio = 'row-radio',
|
||||
// 展开列
|
||||
rowExpand = 'row-expand',
|
||||
// 上下排序
|
||||
rowDragSort = 'row-drag-sort',
|
||||
|
||||
input = 'input',
|
||||
inputNumber = 'input-number',
|
||||
textarea = 'textarea',
|
||||
select = 'select',
|
||||
date = 'date',
|
||||
datetime = 'datetime',
|
||||
time = 'time',
|
||||
checkbox = 'checkbox',
|
||||
upload = 'upload',
|
||||
// 下拉搜索
|
||||
selectSearch = 'select-search',
|
||||
// 下拉多选
|
||||
selectMultiple = 'select-multiple',
|
||||
// 进度条
|
||||
progress = 'progress',
|
||||
//部门选择
|
||||
departSelect = 'depart-select',
|
||||
//用户选择
|
||||
userSelect = 'user-select',
|
||||
|
||||
// 拖轮Tags(暂无用)
|
||||
tags = 'tags', // TODO 待实现
|
||||
|
||||
slot = 'slot',
|
||||
normal = 'normal',
|
||||
hidden = 'hidden',
|
||||
|
||||
// 以下为自定义组件
|
||||
popup = 'popup',
|
||||
selectDictSearch = 'selectDictSearch',
|
||||
radio = 'radio',
|
||||
image = 'image',
|
||||
file = 'file',
|
||||
// 省市区
|
||||
pca = 'pca',
|
||||
}
|
||||
|
||||
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
|
||||
// 前缀是自动加的,代码中直接用就行(JVxeTypes.input)
|
||||
export const JVxeTypePrefix = 'j-';
|
||||
|
||||
/** VxeTable 渲染类型 */
|
||||
export enum JVxeRenderType {
|
||||
editer = 'editer',
|
||||
spaner = 'spaner',
|
||||
default = 'default',
|
||||
}
|
||||
120
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/types/index.ts
Normal file
120
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/types/index.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import type { Component, Ref, ComputedRef, ExtractPropTypes } from 'vue';
|
||||
import type { VxeColumnProps } from 'vxe-table/types/column';
|
||||
import type { JVxeComponent } from './JVxeComponent';
|
||||
import type { VxeGridInstance, VxeTablePropTypes } from 'vxe-table';
|
||||
import { JVxeTypes } from './JVxeTypes';
|
||||
import { vxeProps } from '../vxe.data';
|
||||
import { useMethods } from '../hooks/useMethods';
|
||||
import { getJVxeAuths } from '../utils/authUtils';
|
||||
|
||||
export type JVxeTableProps = Partial<ExtractPropTypes<ReturnType<typeof vxeProps>>>;
|
||||
export type JVxeTableMethods = ReturnType<typeof useMethods>['methods'];
|
||||
|
||||
export type JVxeVueComponent = {
|
||||
enhanced?: JVxeComponent.EnhancedPartial;
|
||||
} & Component;
|
||||
|
||||
type statisticsTypes = 'sum' | 'average';
|
||||
|
||||
export type JVxeColumn = IJVxeColumn & Recordable;
|
||||
|
||||
/**
|
||||
* JVxe 列配置项
|
||||
*/
|
||||
export interface IJVxeColumn extends VxeColumnProps {
|
||||
type?: any;
|
||||
// 行唯一标识
|
||||
key: string;
|
||||
// 表单预期值的提示信息,可以使用${...}变量替换文本
|
||||
placeholder?: string;
|
||||
// 默认值
|
||||
defaultValue?: any;
|
||||
// 是否禁用当前列,默认false
|
||||
disabled?: boolean;
|
||||
// 校验规则 TODO 类型待定义
|
||||
validateRules?: any;
|
||||
// 联动下一级的字段key
|
||||
linkageKey?: string;
|
||||
// 自定义传入组件的其他属性
|
||||
props?: Recordable;
|
||||
allowClear?: boolean; // 允许清除
|
||||
// 【inputNumber】是否是统计列,只有 inputNumber 才能设置统计列。统计列:sum 求和;average 平均值
|
||||
statistics?: boolean | [statisticsTypes, statisticsTypes?];
|
||||
// 【select】
|
||||
dictCode?: string; // 字典 code
|
||||
options?: { title?: string; label?: string; text?: string; value: any; disabled?: boolean }[]; // 下拉选项列表
|
||||
allowInput?: boolean; // 允许输入
|
||||
allowSearch?: boolean; // 允许搜索
|
||||
// 【slot】
|
||||
slotName?: string; // 插槽名
|
||||
// 【checkbox】
|
||||
customValue?: [any, any]; // 自定义值
|
||||
defaultChecked?: boolean; // 默认选中
|
||||
// 【upload】 upload
|
||||
btnText?: string; // 上传按钮文字
|
||||
token?: boolean; // 是否传递 token
|
||||
responseName?: string; // 返回取值名称
|
||||
action?: string; // 上传地址
|
||||
allowRemove?: boolean; // 是否允许删除
|
||||
allowDownload?: boolean; // 是否允许下载
|
||||
// 【下拉字典搜索】
|
||||
dict?: string; // 字典表配置信息:数据库表名,显示字段名,存储字段名
|
||||
async?: boolean; // 是否同步模式
|
||||
tipsContent?: string;
|
||||
// 【popup】
|
||||
popupCode?: string;
|
||||
field?: string;
|
||||
orgFields?: string;
|
||||
destFields?: string;
|
||||
}
|
||||
|
||||
export interface JVxeRefs {
|
||||
gridRef: Ref<VxeGridInstance | undefined>;
|
||||
subPopoverRef: Ref<any>;
|
||||
detailsModalRef: Ref<any>;
|
||||
}
|
||||
|
||||
export interface JVxeDataProps {
|
||||
prefixCls: string;
|
||||
// vxe 实例ID
|
||||
caseId: string;
|
||||
// vxe 最终 columns
|
||||
vxeColumns?: ComputedRef;
|
||||
// vxe 最终 dataSource
|
||||
vxeDataSource: Ref<Recordable[]>;
|
||||
// 记录滚动条位置
|
||||
scroll: { top: number; left: number };
|
||||
// 当前是否正在滚动
|
||||
scrolling: Ref<boolean>;
|
||||
// vxe 默认配置
|
||||
defaultVxeProps: object;
|
||||
// 绑定左侧选择框
|
||||
selectedRows: Ref<any[]>;
|
||||
// 绑定左侧选择框已选择的id
|
||||
selectedRowIds: Ref<string[]>;
|
||||
disabledRowIds: string[];
|
||||
// 统计列配置
|
||||
statistics: {
|
||||
has: boolean;
|
||||
sum: string[];
|
||||
average: string[];
|
||||
};
|
||||
// 所有和当前表格相关的授权信息
|
||||
authsMap: Ref<Nullable<ReturnType<typeof getJVxeAuths>>>;
|
||||
// 内置 EditRules
|
||||
innerEditRules: Recordable<VxeTablePropTypes.EditRules[]>;
|
||||
// 联动下拉选项(用于隔离不同的下拉选项)
|
||||
// 内部联动配置,map
|
||||
innerLinkageConfig: Map<string, any>;
|
||||
// 开启了数据刷新效果的行
|
||||
reloadEffectRowKeysMap: Recordable;
|
||||
}
|
||||
|
||||
export interface JVxeLinkageConfig {
|
||||
// 联动第一级的 key
|
||||
key: string;
|
||||
// 获取数据的方法
|
||||
requestData: (parent: string) => Promise<any>;
|
||||
}
|
||||
|
||||
export { JVxeTypes };
|
||||
@ -0,0 +1,50 @@
|
||||
/* JVxeTable 行编辑 权限 */
|
||||
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
|
||||
|
||||
const permissionStore = usePermissionStoreWithOut();
|
||||
|
||||
/**
|
||||
* JVxe 专用,获取权限
|
||||
* @param prefix
|
||||
*/
|
||||
export function getJVxeAuths(prefix) {
|
||||
prefix = getPrefix(prefix);
|
||||
let { authList, allAuthList } = permissionStore;
|
||||
let authsMap = new Map<string, typeof allAuthList[0]>();
|
||||
if (!prefix || prefix.length == 0) {
|
||||
return authsMap;
|
||||
}
|
||||
// 将所有vxe用到的权限取出来
|
||||
for (let auth of allAuthList) {
|
||||
if (auth.status == '1' && (auth.action || '').startsWith(prefix)) {
|
||||
authsMap.set(auth.action, { ...auth, isAuth: false });
|
||||
}
|
||||
}
|
||||
// 设置是否已授权
|
||||
for (let auth of authList) {
|
||||
let getAuth = authsMap.get(auth.action);
|
||||
if (getAuth != null) {
|
||||
getAuth.isAuth = true;
|
||||
}
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制
|
||||
let onlineButtonAuths = permissionStore.getOnlineSubTableAuth(prefix);
|
||||
if (onlineButtonAuths && onlineButtonAuths.length > 0) {
|
||||
for (let auth of onlineButtonAuths) {
|
||||
authsMap.set(prefix + 'btn:' + auth, { action: auth, type: 1, status: 1, isAuth: false });
|
||||
}
|
||||
}
|
||||
//update-end-author:taoyan date:2022-6-1 for: VUEN-1162 子表按钮没控制
|
||||
return authsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前缀
|
||||
* @param prefix
|
||||
*/
|
||||
export function getPrefix(prefix: string) {
|
||||
if (prefix && !prefix.endsWith(':')) {
|
||||
return prefix + ':';
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
import { useDefaultEnhanced } from '../hooks/useJVxeComponent';
|
||||
import { isFunction, isObject, isString } from '/@/utils/is';
|
||||
import { JVxeTypes } from '../types';
|
||||
import { JVxeComponent } from '../types/JVxeComponent';
|
||||
import { componentMap } from '../componentMap';
|
||||
|
||||
// 已注册的组件增强
|
||||
const enhancedMap = new Map<JVxeTypes, JVxeComponent.Enhanced>();
|
||||
|
||||
/**
|
||||
* 获取某个组件的增强
|
||||
* @param type JVxeTypes
|
||||
*/
|
||||
export function getEnhanced(type: JVxeTypes | string): JVxeComponent.Enhanced {
|
||||
let $type: JVxeTypes = <JVxeTypes>type;
|
||||
if (!enhancedMap.has($type)) {
|
||||
let defaultEnhanced = useDefaultEnhanced();
|
||||
if (componentMap.has($type)) {
|
||||
let enhanced = componentMap.get($type)?.enhanced ?? {};
|
||||
if (isObject(enhanced)) {
|
||||
Object.keys(defaultEnhanced).forEach((key) => {
|
||||
let def = defaultEnhanced[key];
|
||||
if (enhanced.hasOwnProperty(key)) {
|
||||
// 方法如果存在就不覆盖
|
||||
if (!isFunction(def) && !isString(def)) {
|
||||
enhanced[key] = Object.assign({}, def, enhanced[key]);
|
||||
}
|
||||
} else {
|
||||
enhanced[key] = def;
|
||||
}
|
||||
});
|
||||
enhancedMap.set($type, <JVxeComponent.Enhanced>enhanced);
|
||||
return <JVxeComponent.Enhanced>enhanced;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`[JVxeTable] ${$type} 组件尚未注册,获取增强失败`);
|
||||
}
|
||||
enhancedMap.set($type, <JVxeComponent.Enhanced>defaultEnhanced);
|
||||
}
|
||||
return <JVxeComponent.Enhanced>enhancedMap.get($type);
|
||||
}
|
||||
|
||||
/** 辅助方法:替换${...}变量 */
|
||||
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,143 @@
|
||||
import type { Component } from 'vue';
|
||||
import { h } from 'vue';
|
||||
import VXETable from 'vxe-table';
|
||||
import { definedComponent, addComponent, componentMap, spanEnds, excludeKeywords } from '../componentMap';
|
||||
import { JVxeRenderType, JVxeTypePrefix, JVxeTypes } from '../types/JVxeTypes';
|
||||
import { getEnhanced } from './enhancedUtils';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
/**
|
||||
* 判断某个组件是否已注册
|
||||
* @param type
|
||||
*/
|
||||
export function isRegistered(type: JVxeTypes | string) {
|
||||
if (excludeKeywords.includes(<JVxeTypes>type)) {
|
||||
return true;
|
||||
}
|
||||
return componentMap.has(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册vxe自定义组件
|
||||
*
|
||||
* @param type
|
||||
* @param component 编辑状态显示的组件
|
||||
* @param spanComponent 非编辑状态显示的组件,可以为空
|
||||
*/
|
||||
export function registerComponent(type: JVxeTypes, component: Component, spanComponent?: Component) {
|
||||
addComponent(type, component, spanComponent);
|
||||
registerOneComponent(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步注册vxe自定义组件
|
||||
*
|
||||
* @param type
|
||||
* @param promise
|
||||
*/
|
||||
export async function registerAsyncComponent(type: JVxeTypes, promise: Promise<any>) {
|
||||
const result = await promise;
|
||||
if (isFunction(result.installJVxe)) {
|
||||
result.install((component: Component, spanComponent?: Component) => {
|
||||
addComponent(type, component, spanComponent);
|
||||
registerOneComponent(type);
|
||||
});
|
||||
} else {
|
||||
addComponent(type, result.default);
|
||||
registerOneComponent(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 2024-03-08
|
||||
* liaozhiyang
|
||||
* 异步注册vxe自定义组件
|
||||
* 【QQYUN-8241】
|
||||
* @param type
|
||||
* @param promise
|
||||
*/
|
||||
export function registerASyncComponentReal(type: JVxeTypes, component) {
|
||||
addComponent(type, component);
|
||||
registerOneComponent(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装所有vxe组件
|
||||
*/
|
||||
export function registerAllComponent() {
|
||||
definedComponent();
|
||||
// 遍历所有组件批量注册
|
||||
const components = [...componentMap.keys()];
|
||||
components.forEach((type) => {
|
||||
if (!type.endsWith(spanEnds)) {
|
||||
registerOneComponent(<JVxeTypes>type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册单个vxe组件
|
||||
*
|
||||
* @param type 组件 type
|
||||
*/
|
||||
export function registerOneComponent(type: JVxeTypes) {
|
||||
const component = componentMap.get(type);
|
||||
if (component) {
|
||||
const switches = getEnhanced(type).switches;
|
||||
if (switches.editRender && !switches.visible) {
|
||||
createEditRender(type, component);
|
||||
} else {
|
||||
createCellRender(type, component);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`【registerOneComponent】"${type}"不存在于componentMap中`);
|
||||
}
|
||||
}
|
||||
|
||||
/** 注册可编辑组件 */
|
||||
function createEditRender(type: JVxeTypes, component: Component, spanComponent?: Component) {
|
||||
// 获取当前组件的增强
|
||||
const enhanced = getEnhanced(type);
|
||||
if (!spanComponent) {
|
||||
if (componentMap.has(type + spanEnds)) {
|
||||
spanComponent = componentMap.get(type + spanEnds);
|
||||
} else {
|
||||
// 默认的 span 组件为 normal
|
||||
spanComponent = componentMap.get(JVxeTypes.normal);
|
||||
}
|
||||
}
|
||||
// 添加渲染
|
||||
VXETable.renderer.add(JVxeTypePrefix + type, {
|
||||
// 可编辑模板
|
||||
renderEdit: createRender(type, component, JVxeRenderType.editer),
|
||||
// 显示模板
|
||||
renderCell: createRender(type, spanComponent, JVxeRenderType.spaner),
|
||||
// 增强注册
|
||||
...enhanced.installOptions,
|
||||
});
|
||||
}
|
||||
|
||||
/** 注册普通组件 */
|
||||
function createCellRender(type: JVxeTypes, component: Component = <Component>componentMap.get(JVxeTypes.normal)) {
|
||||
// 获取当前组件的增强
|
||||
const enhanced = getEnhanced(type);
|
||||
VXETable.renderer.add(JVxeTypePrefix + type, {
|
||||
// 默认显示模板
|
||||
renderDefault: createRender(type, component, JVxeRenderType.default),
|
||||
// 增强注册
|
||||
...enhanced.installOptions,
|
||||
});
|
||||
}
|
||||
|
||||
function createRender(type, component, renderType) {
|
||||
return function (renderOptions, params) {
|
||||
return [
|
||||
h(component, {
|
||||
type: type,
|
||||
params: params,
|
||||
renderOptions: renderOptions,
|
||||
renderType: renderType,
|
||||
}),
|
||||
];
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
*
|
||||
* 根据 tagName 获取父级节点
|
||||
*
|
||||
* @param dom 一级dom节点
|
||||
* @param tagName 标签名,不区分大小写
|
||||
*/
|
||||
export function getParentNodeByTagName(dom: HTMLElement, tagName: string = 'body'): HTMLElement | null {
|
||||
if (tagName === 'body') {
|
||||
return document.body;
|
||||
}
|
||||
if (dom.parentElement) {
|
||||
if (dom.parentElement.tagName.toLowerCase() === tagName.trim().toLowerCase()) {
|
||||
return dom.parentElement;
|
||||
} else {
|
||||
return getParentNodeByTagName(dom.parentElement, tagName);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
118
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/vxe.data.ts
Normal file
118
jeecgboot-vue3/src/components/jeecg/JVxeTable/src/vxe.data.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
|
||||
export const vxeProps = () => ({
|
||||
rowKey: propTypes.string.def('id'),
|
||||
// 列信息
|
||||
columns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
// 数据源
|
||||
dataSource: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
authPre: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
// 是否显示工具栏
|
||||
toolbar: propTypes.bool.def(false),
|
||||
// 工具栏配置
|
||||
toolbarConfig: propTypes.object.def(() => ({
|
||||
// prefix 前缀;suffix 后缀;
|
||||
slots: ['prefix', 'suffix'],
|
||||
// add 新增按钮;remove 删除按钮;clearSelection 清空选择按钮;collapse 展开收起
|
||||
btns: ['add', 'remove', 'clearSelection'],
|
||||
})),
|
||||
// 是否显示行号
|
||||
rowNumber: propTypes.bool.def(false),
|
||||
// 固定行号位置或者不固定 【QQYUN-8405】
|
||||
rowNumberFixed: propTypes.oneOf(['left', 'none']).def('left'),
|
||||
// update-begin--author:liaozhiyang---date:20240509---for:【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序
|
||||
dragSortFixed: propTypes.oneOf(['left', 'none']).def('left'),
|
||||
rowSelectionFixed: propTypes.oneOf(['left', 'none']).def('left'),
|
||||
// update-end--author:liaozhiyang---date:20240509---for:【issues/1162】JVxeTable列过长(出现横向滚动条)时无法拖拽排序
|
||||
// 是否可选择行
|
||||
rowSelection: propTypes.bool.def(false),
|
||||
// 选择行类型
|
||||
rowSelectionType: propTypes.oneOf(['checkbox', 'radio']).def('checkbox'),
|
||||
// 是否可展开行
|
||||
rowExpand: propTypes.bool.def(false),
|
||||
// 展开行配置
|
||||
expandConfig: propTypes.object.def(() => ({})),
|
||||
// 页面是否在加载中
|
||||
loading: propTypes.bool.def(false),
|
||||
// 表格高度
|
||||
height: propTypes.oneOfType([propTypes.number, propTypes.string]).def('auto'),
|
||||
// 最大高度
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: () => null,
|
||||
},
|
||||
// 要禁用的行
|
||||
disabledRows: propTypes.object.def(() => ({})),
|
||||
// 是否禁用全部组件
|
||||
disabled: propTypes.bool.def(false),
|
||||
// 是否可拖拽排序(有固定列的情况下无法拖拽排序,仅可上下排序)
|
||||
dragSort: propTypes.bool.def(false),
|
||||
// 排序字段保存的Key
|
||||
sortKey: propTypes.string.def('orderNum'),
|
||||
// 排序序号开始值,默认为 0
|
||||
sortBegin: propTypes.number.def(0),
|
||||
// 大小,可选值有:medium(中)、small(小)、mini(微)
|
||||
size: propTypes.oneOf(['medium', 'small', 'mini']).def('medium'),
|
||||
// 是否显示边框线
|
||||
bordered: propTypes.bool.def(false),
|
||||
// 自定义列配置 默认继承 setup.toolbar.custom
|
||||
custom: propTypes.bool.def(false),
|
||||
// 分页器参数,设置了即可显示分页器
|
||||
pagination: propTypes.object.def(() => ({})),
|
||||
// 点击行时是否显示子表单
|
||||
clickRowShowSubForm: propTypes.bool.def(false),
|
||||
// 点击行时是否显示主表单
|
||||
clickRowShowMainForm: propTypes.bool.def(false),
|
||||
// 是否点击选中行,优先级最低
|
||||
clickSelectRow: propTypes.bool.def(false),
|
||||
// 是否开启 reload 数据效果
|
||||
reloadEffect: propTypes.bool.def(false),
|
||||
// 校验规则
|
||||
editRules: propTypes.object.def(() => ({})),
|
||||
// 是否异步删除行,如果你要实现异步删除,那么需要把这个选项开启,
|
||||
// 在remove事件里调用confirmRemove方法才会真正删除(除非删除的全是新增的行)
|
||||
asyncRemove: propTypes.bool.def(false),
|
||||
// 是否一直显示组件,如果为false则只有点击的时候才出现组件
|
||||
// 注:该参数不能动态修改;如果行、列字段多的情况下,会根据机器性能造成不同程度的卡顿。
|
||||
// TODO 新版vxe-table取消了 visible 参数,导致无法实现该功能
|
||||
alwaysEdit: propTypes.bool.def(false),
|
||||
// 联动配置,数组,详情配置见文档
|
||||
linkageConfig: propTypes.array.def(() => []),
|
||||
// 是否开启使用 webSocket 无痕刷新
|
||||
socketReload: propTypes.bool.def(false),
|
||||
// 相同的socketKey更改时会互相刷新
|
||||
socketKey: propTypes.string.def('vxe-default'),
|
||||
// 新增行时切换行的激活状态
|
||||
addSetActive: propTypes.bool.def(true),
|
||||
// 是否开启键盘编辑
|
||||
keyboardEdit: propTypes.bool.def(false),
|
||||
// update-begin--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
// 横向虚拟滚动配置(不支持展开行)
|
||||
// 【QQYUN-7676】x滚动条滚动时字典变成了id
|
||||
scrollX: propTypes.object.def(() => ({ enabled: false })),
|
||||
// 纵向虚拟滚动配置(不支持展开行)
|
||||
scrollY: propTypes.object.def(() => ({ enabled: true })),
|
||||
// update-end--author:liaozhiyang---date:20231013---for:【QQYUN-5133】JVxeTable 行编辑升级
|
||||
//【QQYUN-8566】缓存列设置的key(路由页面内唯一)
|
||||
cacheColumnsKey: propTypes.string.def(''),
|
||||
// update-begin--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
rowClassName: {
|
||||
type: [String, Function],
|
||||
default: null,
|
||||
},
|
||||
// 不允许拖拽的行 [{'key':field,'value':value}]
|
||||
notAllowDrag: propTypes.array.def(() => []),
|
||||
// update-end--author:liaozhiyang---date:20240417---for:【QQYUN-8785】online表单列位置的id未做限制,拖动其他列到id列上面,同步数据库时报错
|
||||
});
|
||||
|
||||
export const vxeEmits = ['save', 'added', 'removed', 'inserted', 'dragged', 'selectRowChange', 'pageChange', 'valueChange', 'blur'];
|
||||
6
jeecgboot-vue3/src/components/jeecg/JVxeTable/types.ts
Normal file
6
jeecgboot-vue3/src/components/jeecg/JVxeTable/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import JVxeTable from './src/JVxeTable';
|
||||
|
||||
export type { JVxeComponent } from './src/types/JVxeComponent';
|
||||
export type { JVxeColumn, JVxeLinkageConfig } from './src/types';
|
||||
export { JVxeTypes } from './src/types/JVxeTypes';
|
||||
export type JVxeTableInstance = InstanceType<typeof JVxeTable>;
|
||||
109
jeecgboot-vue3/src/components/jeecg/JVxeTable/utils.ts
Normal file
109
jeecgboot-vue3/src/components/jeecg/JVxeTable/utils.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import type { Ref, ComponentInternalInstance } from 'vue';
|
||||
import { unref, isRef } from 'vue';
|
||||
import { isFunction } from '/@/utils/is';
|
||||
|
||||
type dispatchEventOptions = {
|
||||
// JVxeTable 的 props
|
||||
props;
|
||||
// 触发的 event 事件对象
|
||||
$event;
|
||||
// 行、列
|
||||
row?;
|
||||
column?;
|
||||
// JVxeTable的vue3实例
|
||||
instance?: ComponentInternalInstance | any;
|
||||
// 要寻找的className
|
||||
className: string;
|
||||
// 重写找到dom后的处理方法
|
||||
handler?: Fn;
|
||||
// 是否直接执行click方法而不是模拟click事件
|
||||
isClick?: boolean;
|
||||
};
|
||||
|
||||
/** 模拟触发事件 */
|
||||
export function dispatchEvent(options: dispatchEventOptions) {
|
||||
const { props, $event, row, column, instance, className, handler, isClick } = options;
|
||||
if ((!$event || !$event.path) && !instance) {
|
||||
return;
|
||||
}
|
||||
// alwaysEdit 下不模拟触发事件,否者会导致触发两次
|
||||
if (props && props.alwaysEdit) {
|
||||
return;
|
||||
}
|
||||
let getCell = () => {
|
||||
let paths: HTMLElement[] = [...($event?.path ?? [])];
|
||||
// 通过 instance 获取 cell dom对象
|
||||
if (row && column) {
|
||||
let selector = `table.vxe-table--body tbody tr[rowid='${row.id}'] td[colid='${column.id}']`;
|
||||
let cellDom = instance!.vnode?.el?.querySelector(selector);
|
||||
// -update-begin--author:liaozhiyang---date:20230830---for:【QQYUN-6390】解决online新增字段警告(兼容下)
|
||||
if (!cellDom) {
|
||||
cellDom = instance!.$el?.querySelector(selector);
|
||||
}
|
||||
// -update-begin--author:liaozhiyang---date:20230830---for:【QQYUN-6390】解决online新增字段警告(兼容下)
|
||||
if (cellDom) {
|
||||
paths.unshift(cellDom);
|
||||
}
|
||||
}
|
||||
for (const el of paths) {
|
||||
if (el.classList?.contains('vxe-body--column')) {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
let cell = getCell();
|
||||
if (cell) {
|
||||
window.setTimeout(() => {
|
||||
let getElement = () => {
|
||||
let classList = className.split(' ');
|
||||
if (classList.length > 0) {
|
||||
const getClassName = (cls: string) => {
|
||||
if (cls.startsWith('.')) {
|
||||
return cls.substring(1, cls.length);
|
||||
}
|
||||
return cls;
|
||||
};
|
||||
let get = (target, className, idx = 0) => {
|
||||
let elements = target.getElementsByClassName(getClassName(className));
|
||||
if (elements && elements.length > 0) {
|
||||
return elements[idx];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
let element: HTMLElement = get(cell, classList[0]);
|
||||
for (let i = 1; i < classList.length; i++) {
|
||||
if (!element) {
|
||||
break;
|
||||
}
|
||||
element = get(element, classList[i]);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
let element = getElement();
|
||||
if (element) {
|
||||
if (isFunction(handler)) {
|
||||
handler(element);
|
||||
} else {
|
||||
// 模拟触发点击事件
|
||||
if (isClick) {
|
||||
element.click();
|
||||
} else {
|
||||
element.dispatchEvent($event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
} else {
|
||||
console.warn('【JVxeTable】dispatchEvent 获取 cell 失败');
|
||||
}
|
||||
}
|
||||
|
||||
/** 绑定 VxeTable 数据 */
|
||||
export function vModel(value, row, column: Ref<any> | string) {
|
||||
// @ts-ignore
|
||||
let property = isRef(column) ? column.value.property : column;
|
||||
unref(row)[property] = value;
|
||||
}
|
||||
Reference in New Issue
Block a user