mirror of
https://github.com/jeecgboot/JeecgBoot.git
synced 2025-12-26 08:16:41 +08:00
v3.8.1发布,上传前端代码
This commit is contained in:
@ -367,6 +367,13 @@
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240620---for:【TV360X-1420】校验时闪动
|
||||
|
||||
// 表单组件中间件样式
|
||||
.j-form-item-middleware {
|
||||
flex: 1;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
&.suffix-item {
|
||||
.ant-form-item-children {
|
||||
display: flex;
|
||||
@ -376,6 +383,12 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
// 【QQYUN-12876】当紧凑型 suffix 时,表单组件中间件的宽度不占满
|
||||
&.suffix-compact .j-form-item-middleware {
|
||||
flex: unset;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
display: inline-flex;
|
||||
padding-left: 6px;
|
||||
|
||||
@ -65,6 +65,7 @@ import JTreeSelect from './jeecg/components/JTreeSelect.vue';
|
||||
import JEllipsis from './jeecg/components/JEllipsis.vue';
|
||||
import JSelectUserByDept from './jeecg/components/JSelectUserByDept.vue';
|
||||
import JSelectUserByDepartment from './jeecg/components/JSelectUserByDepartment.vue';
|
||||
import JLinkTableCard from './jeecg/components/JLinkTableCard/JLinkTableCard.vue';
|
||||
import JUpload from './jeecg/components/JUpload/JUpload.vue';
|
||||
import JSearchSelect from './jeecg/components/JSearchSelect.vue';
|
||||
import JAddInput from './jeecg/components/JAddInput.vue';
|
||||
@ -128,6 +129,7 @@ componentMap.set('JImageUpload', JImageUpload);
|
||||
componentMap.set('JDictSelectTag', JDictSelectTag);
|
||||
componentMap.set('JSelectDept', JSelectDept);
|
||||
componentMap.set('JAreaSelect', JAreaSelect);
|
||||
componentMap.set('JLinkTableCard', JLinkTableCard);
|
||||
// componentMap.set(
|
||||
// 'JEditor',
|
||||
// createAsyncComponent(() => import('./jeecg/components/JEditor.vue'))
|
||||
|
||||
@ -464,10 +464,17 @@
|
||||
}
|
||||
|
||||
function renderItem() {
|
||||
const { itemProps, slot, render, field, suffix, component } = props.schema;
|
||||
const { itemProps, slot, render, field, suffix, suffixCompact, component } = props.schema;
|
||||
const { labelCol, wrapperCol } = unref(itemLabelWidthProp);
|
||||
const { colon } = props.formProps;
|
||||
|
||||
// update-begin--author:sunjianlei---date:20250613---for:itemProps 属性支持函数形式
|
||||
let getItemProps = itemProps;
|
||||
if (typeof getItemProps === 'function') {
|
||||
getItemProps = getItemProps(unref(getValues));
|
||||
}
|
||||
// update-end--author:sunjianlei---date:20250613---for:itemProps 属性支持函数形式
|
||||
|
||||
if (component === 'Divider') {
|
||||
return (
|
||||
<Col span={24}>
|
||||
@ -486,8 +493,8 @@
|
||||
<Form.Item
|
||||
name={field}
|
||||
colon={colon}
|
||||
class={{ 'suffix-item': showSuffix }}
|
||||
{...(itemProps as Recordable)}
|
||||
class={{ 'suffix-item': showSuffix, 'suffix-compact': showSuffix && suffixCompact }}
|
||||
{...(getItemProps as Recordable)}
|
||||
label={renderLabelHelpMessage()}
|
||||
rules={handleRules()}
|
||||
// update-begin--author:liaozhiyang---date:20240514---for:【issues/1244】标识了必填,但是必填标识没显示
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :id="formItemId" style="flex: 1; width: 100%">
|
||||
<div :id="formItemId" class="j-form-item-middleware">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -89,6 +89,8 @@
|
||||
required: false,
|
||||
},
|
||||
style: propTypes.any,
|
||||
// 搜索时是否只搜索label
|
||||
onlySearchByLabel: propTypes.bool.def(false),
|
||||
},
|
||||
emits: ['options-change', 'change','update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
@ -202,6 +204,10 @@
|
||||
}
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20230914---for:【QQYUN-6514】 配置的时候,Y轴不能输入多个字段了,控制台报错
|
||||
if (props.onlySearchByLabel) {
|
||||
// 如果开启了只在 label 中搜索,就不继续往下搜索value了
|
||||
return false;
|
||||
}
|
||||
// 在 value 中搜索
|
||||
return (option.value || '').toString().toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
</a-button>
|
||||
</div>
|
||||
</a-upload>
|
||||
<a-modal :open="previewVisible" :footer="null" @cancel="handleCancel()">
|
||||
<a-modal :width="previewWidth" :open="previewVisible" :footer="null" @cancel="handleCancel()">
|
||||
<img alt="example" style="width: 100%" :src="previewImage" />
|
||||
</a-modal>
|
||||
</div>
|
||||
@ -38,7 +38,7 @@
|
||||
import { useAttrs } from '/@/hooks/core/useAttrs';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getFileAccessHttpUrl, getHeaders, getRandom } from '/@/utils/common/compUtils';
|
||||
import { uploadUrl } from '/@/api/common/api';
|
||||
import { uploadUrl as systemUploadUrl } from '/@/api/common/api';
|
||||
import { getToken } from '/@/utils/auth';
|
||||
|
||||
const { createMessage, createErrorModal } = useMessage();
|
||||
@ -79,6 +79,15 @@
|
||||
required: false,
|
||||
default: 1,
|
||||
},
|
||||
uploadUrl: {
|
||||
type: String,
|
||||
default: systemUploadUrl,
|
||||
},
|
||||
previewWidth: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 520,
|
||||
},
|
||||
},
|
||||
emits: ['options-change', 'change', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
@ -256,7 +265,6 @@
|
||||
multiple,
|
||||
headers,
|
||||
loading,
|
||||
uploadUrl,
|
||||
beforeUpload,
|
||||
uploadVisible,
|
||||
handlePreview,
|
||||
|
||||
@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<div ref="tableLinkCardRef">
|
||||
<div class="table-link-card">
|
||||
<div style="width: 100%; height: 100%">
|
||||
<div class="card-button" v-if="showButton">
|
||||
<a-button @click="handleAddRecord"><PlusOutlined />记 录</a-button>
|
||||
</div>
|
||||
|
||||
<a-row>
|
||||
<a-col :span="fixedSpan ? fixedSpan : itemSpan" v-for="(record, index) in selectRecords" :key="index">
|
||||
<div class="card-item" :class="{ 'disabled-chunk': detail == true }">
|
||||
<!-- -->
|
||||
<div class="card-item-left" :class="{ 'show-right-image': getImageSrc(record) }">
|
||||
<span class="card-delete" v-if="disabled == false">
|
||||
<minus-circle-filled @click="(e) => handleDeleteRecord(e, index)" />
|
||||
</span>
|
||||
<div class="card-inner">
|
||||
<div class="card-main-content">{{ getMainContent(record) }}</div>
|
||||
<div class="other-content">
|
||||
<a-row>
|
||||
<a-col :span="columnSpan" v-for="(col, cIndex) in realShowColumns" :key="cIndex">
|
||||
<span class="label ellipsis">{{ col.title }}</span>
|
||||
<span class="text ellipsis">{{ record[col.dataIndex] }}</span>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-item-image" v-if="getImageSrc(record)">
|
||||
<img v-if="getImageSrc(record)" :src="getImageSrc(record)" @error="handleImageError" />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<LinkTableListModal @register="registerListModal" :multi="multi" :id="popTableName" @success="addCard" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { propTypes } from '/@/utils/propTypes';
|
||||
import { PlusOutlined, MinusCircleFilled } from '@ant-design/icons-vue';
|
||||
import { computed, ref, watch, onMounted } from 'vue';
|
||||
import { useLinkTable } from './hooks/useLinkTable';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import placeholderImage from '/@/assets/images/placeholderImage.png';
|
||||
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
|
||||
|
||||
export default {
|
||||
name: 'JLinkTableCard',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
// value字段
|
||||
valueField: propTypes.string.def(''),
|
||||
// 文本字段
|
||||
textField: propTypes.string.def(''),
|
||||
// 关联表名
|
||||
tableName: propTypes.string.def(''),
|
||||
// 是否多选
|
||||
multi: propTypes.bool.def(false),
|
||||
value: propTypes.oneOfType([propTypes.string, propTypes.number]),
|
||||
// ["表单字段,表字典字段","表单字段,表字典字段"]
|
||||
linkFields: propTypes.array.def([]),
|
||||
//是否是禁用页面
|
||||
disabled: propTypes.bool.def(false),
|
||||
// 是否是detail页面
|
||||
detail: propTypes.bool.def(false),
|
||||
// 图片字段
|
||||
imageField: propTypes.string.def(''),
|
||||
},
|
||||
components: {
|
||||
PlusOutlined,
|
||||
MinusCircleFilled,
|
||||
LinkTableListModal: createAsyncComponent(() => import('./components/LinkTableListModal.vue'), { loading: true }),
|
||||
},
|
||||
emits: ['change', 'update:value'],
|
||||
setup(props, { emit }) {
|
||||
const popTableName = computed(() => {
|
||||
return props.tableName;
|
||||
});
|
||||
//注册model
|
||||
const [registerListModal, { openModal: openListModal }] = useModal();
|
||||
const selectValue = ref([]);
|
||||
const selectRecords = ref([]);
|
||||
const tableLinkCardRef = ref(null);
|
||||
const fixedSpan = ref(0);
|
||||
|
||||
const showButton = computed(() => {
|
||||
if (props.disabled == true) {
|
||||
return false;
|
||||
}
|
||||
if (props.multi === false) {
|
||||
//单选且有值
|
||||
if (selectRecords.value.length > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const {
|
||||
auths,
|
||||
otherColumns,
|
||||
realShowColumns,
|
||||
tableColumns,
|
||||
textFieldArray,
|
||||
transData,
|
||||
loadOne,
|
||||
compareData,
|
||||
formatData,
|
||||
initFormData,
|
||||
getImageSrc,
|
||||
showImage,
|
||||
} = useLinkTable(props);
|
||||
|
||||
const itemSpan = computed(() => {
|
||||
if (props.multi === true) {
|
||||
return 12;
|
||||
}
|
||||
return 24;
|
||||
});
|
||||
|
||||
const columnSpan = computed(() => {
|
||||
if (props.multi === true) {
|
||||
return 24;
|
||||
}
|
||||
return 12;
|
||||
});
|
||||
|
||||
function getMainContent(record) {
|
||||
if (record) {
|
||||
if (textFieldArray.value.length > 0) {
|
||||
let field = textFieldArray.value[0];
|
||||
return record[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prevent(e) {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
}
|
||||
|
||||
|
||||
function handleAddRecord(e) {
|
||||
prevent(e);
|
||||
openListModal(true, {
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
selectedRowKeys: selectRecords.value.map((item) => item.id),
|
||||
selectedRows: [...selectRecords.value],
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
});
|
||||
}
|
||||
|
||||
function addCard(data) {
|
||||
// update-begin--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
let arr = [];
|
||||
// update-end--author:liaozhiyang---date:20240517---for:【TV360X-43】修复关联记录可以添加重复数据
|
||||
for (let item of data) {
|
||||
let temp = { ...item };
|
||||
transData(temp);
|
||||
arr.push(temp);
|
||||
}
|
||||
selectRecords.value = arr;
|
||||
emitValue();
|
||||
}
|
||||
|
||||
function updateCardData(formData) {
|
||||
let arr = selectRecords.value;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].id === formData.id) {
|
||||
let temp = { ...formData };
|
||||
transData(temp);
|
||||
arr.splice(i, 1, temp);
|
||||
}
|
||||
}
|
||||
selectRecords.value = arr;
|
||||
emitValue();
|
||||
}
|
||||
|
||||
function handleDeleteRecord(e, index) {
|
||||
prevent(e);
|
||||
let temp = selectRecords.value;
|
||||
if (temp && temp.length > index) {
|
||||
temp.splice(index, 1);
|
||||
selectRecords.value = temp;
|
||||
}
|
||||
emitValue();
|
||||
}
|
||||
|
||||
function emitValue() {
|
||||
let arr = selectRecords.value;
|
||||
let values = [];
|
||||
let formData = {};
|
||||
let linkFieldArray = props.linkFields;
|
||||
if (arr.length > 0) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
values.push(arr[i][props.valueField]);
|
||||
initFormData(formData, linkFieldArray, arr[i]);
|
||||
}
|
||||
} else {
|
||||
initFormData(formData, linkFieldArray);
|
||||
}
|
||||
let text = values.join(',');
|
||||
formatData(formData);
|
||||
emit('change', text, formData);
|
||||
emit('update:value', text);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
async (val) => {
|
||||
if (val) {
|
||||
let flag = compareData(selectRecords.value, val);
|
||||
if (flag === false) {
|
||||
let arr = await loadOne(val);
|
||||
selectRecords.value = arr;
|
||||
}
|
||||
//保证表单其他值回显成功
|
||||
if (props.linkFields && props.linkFields.length > 0) {
|
||||
emitValue();
|
||||
}
|
||||
} else {
|
||||
selectRecords.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// update-begin--author:liaozhiyang---date:20240522---for:【TV360X-281】分辨率小时关联记录文字被图片挤没了
|
||||
if (tableLinkCardRef.value.offsetWidth < 250) {
|
||||
fixedSpan.value = 24;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20240522---for:【TV360X-281】分辨率小时关联记录文字被图片挤没了
|
||||
});
|
||||
// update-begin--author:liaozhiyang---date:20240529---for:【TV360X-389】下拉和卡片关联记录图裂开给个默认图片
|
||||
const handleImageError = (event) => {
|
||||
event.target.src = placeholderImage;
|
||||
};
|
||||
// update-end--author:liaozhiyang---date:20240529---for:【TV360X-389】下拉和卡片关联记录图裂开给个默认图片
|
||||
|
||||
return {
|
||||
popTableName,
|
||||
selectRecords,
|
||||
otherColumns,
|
||||
realShowColumns,
|
||||
showButton,
|
||||
selectValue,
|
||||
handleAddRecord,
|
||||
handleDeleteRecord,
|
||||
getMainContent,
|
||||
itemSpan,
|
||||
columnSpan,
|
||||
tableColumns,
|
||||
addCard,
|
||||
registerListModal,
|
||||
updateCardData,
|
||||
getImageSrc,
|
||||
showImage,
|
||||
auths,
|
||||
tableLinkCardRef,
|
||||
fixedSpan,
|
||||
placeholderImage,
|
||||
handleImageError,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.table-link-card {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.card-button {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
.card-item {
|
||||
width: calc(100% - 10px);
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin: 0px 10px 10px 0px;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
box-shadow:
|
||||
rgb(0 0 0 / 12%) 0px 1px 4px 0px,
|
||||
rgb(0 0 0 / 12%) 0px 0px 2px 0px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
/* box-shadow: rgb(0 0 0 / 12%) 0px 4px 12px 0px, rgb(0 0 0 / 12%) 0px 0px 2px 0px;*/
|
||||
.card-item-left {
|
||||
.card-delete {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled-chunk {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card-item-image {
|
||||
width: 100px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.card-item-left {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
&.show-right-image {
|
||||
width: calc(100% - 100px);
|
||||
}
|
||||
.card-delete {
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
font-size: 16px;
|
||||
color: #757575;
|
||||
line-height: 1em;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-inner {
|
||||
flex: 1 1 0%;
|
||||
padding: 12px 16px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.card-main-content {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
.other-content {
|
||||
.text {
|
||||
font-size: 12px !important;
|
||||
display: inline-block;
|
||||
width: 80%;
|
||||
}
|
||||
.label {
|
||||
max-width: 160px;
|
||||
color: rgb(158, 158, 158);
|
||||
padding-right: 0.7em;
|
||||
display: inline-block;
|
||||
}
|
||||
.ellipsis {
|
||||
overflow: hidden;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
/* vertical-align: top;*/
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<BasicModal
|
||||
@register="registerModal"
|
||||
:width="popModalFixedWidth"
|
||||
:dialogStyle="{ top: '70px' }"
|
||||
:bodyStyle="popBodyStyle"
|
||||
:title="modalTitle"
|
||||
wrapClassName="jeecg-online-pop-list-modal"
|
||||
>
|
||||
<template #footer>
|
||||
<a-button key="back" @click="handleCancel">关闭</a-button>
|
||||
<a-button :disabled="submitDisabled" key="submit" type="primary" @click="handleSubmit" :loading="submitLoading">确定</a-button>
|
||||
</template>
|
||||
|
||||
<BasicTable ref="tableRef" @register="registerTable" :rowSelection="rowSelection">
|
||||
<!-- update-begin-author:taoyan date:2023-7-11 for: issues/4992 online表单开发 字段控件类型是关联记录 新增的时候选择列表可以添加查询么 -->
|
||||
<template #tableTitle>
|
||||
<a-input-search v-model:value="searchText" @search="onSearch" placeholder="请输入关键词,按回车搜索" style="width: 240px" />
|
||||
</template>
|
||||
<!-- update-end-author:taoyan date:2023-7-11 for: issues/4992 online表单开发 字段控件类型是关联记录 新增的时候选择列表可以添加查询么 -->
|
||||
|
||||
<!--操作栏-->
|
||||
<template #action="{ record }">
|
||||
<TableAction :actions="getTableAction(record)"> </TableAction>
|
||||
</template>
|
||||
|
||||
<template #fileSlot="{ text }">
|
||||
<span v-if="!text" style="font-size: 12px; font-style: italic">无文件</span>
|
||||
<a-button v-else :ghost="true" type="primary" preIcon="ant-design:download" size="small" @click="downloadRowFile(text)"> 下载 </a-button>
|
||||
</template>
|
||||
|
||||
<template #imgSlot="{ text }">
|
||||
<span v-if="!text" style="font-size: 12px; font-style: italic">无图片</span>
|
||||
<img v-else :src="getImgView(text)" alt="图片不存在" class="online-cell-image" @click="viewOnlineCellImage(text)" />
|
||||
</template>
|
||||
|
||||
<template #htmlSlot="{ text }">
|
||||
<div v-html="text"></div>
|
||||
</template>
|
||||
|
||||
<template #pcaSlot="{ text }">
|
||||
<div :title="getPcaText(text)">{{ getPcaText(text) }}</div>
|
||||
</template>
|
||||
|
||||
<template #dateSlot="{ text, column }">
|
||||
<span>{{ getFormatDate(text, column) }}</span>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, watch, ref, toRaw, computed, nextTick } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useListPage } from '/@/hooks/system/useListPage';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useTableColumns } from '@/views/super/online/cgform/hooks/auto/useTableColumns';
|
||||
import { createAsyncComponent } from '@/utils/factory/createAsyncComponent';
|
||||
import { useFixedHeightModal } from '../hooks/useLinkTable';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LinkTableListModal',
|
||||
props: {
|
||||
/**可以是表名 可以是ID*/
|
||||
id: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
multi: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
addAuth: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
BasicModal,
|
||||
BasicTable: createAsyncComponent(() => import('/@/components/Table/src/BasicTable.vue'), { loading: true, delay: 1000 }),
|
||||
TableAction: createAsyncComponent(() => import('/@/components/Table/src/components/TableAction.vue'), { loading: true, delay: 1000 }),
|
||||
},
|
||||
emits: ['success', 'register'],
|
||||
setup(props, { emit }) {
|
||||
const { createMessage: $message } = useMessage();
|
||||
const tableRef = ref(null);
|
||||
// 弹窗高度控制
|
||||
const { popModalFixedWidth, resetBodyStyle, popBodyStyle } = useFixedHeightModal();
|
||||
const searchText = ref('');
|
||||
const modalWidth = ref(800);
|
||||
//useModalInner
|
||||
const [registerModal, { closeModal }] = useModalInner((data) => {
|
||||
searchText.value = '';
|
||||
selectedRowKeys.value = data.selectedRowKeys;
|
||||
selectedRows.value = data.selectedRows;
|
||||
setTimeout(async () => {
|
||||
await setPagination({ current: 1 });
|
||||
await reload(); // 等待表格加载
|
||||
resetBodyStyle();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
function handleCancel() {
|
||||
closeModal();
|
||||
}
|
||||
const submitDisabled = computed(() => {
|
||||
const arr = selectedRowKeys.value;
|
||||
if (arr && arr.length > 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const submitLoading = ref(false);
|
||||
function handleSubmit() {
|
||||
submitLoading.value = true;
|
||||
let arr = toRaw(selectedRows.value);
|
||||
if (arr && arr.length > 0) {
|
||||
emit('success', arr);
|
||||
closeModal();
|
||||
}
|
||||
setTimeout(() => {
|
||||
submitLoading.value = false;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
//---------------------列表------------------------
|
||||
function queryTableData(params) {
|
||||
const url = '/online/cgform/api/getData/' + props.id;
|
||||
return defHttp.get({ url, params });
|
||||
}
|
||||
|
||||
function list(params) {
|
||||
params['column'] = 'id';
|
||||
return new Promise(async (resolve, _reject) => {
|
||||
const aa = await queryTableData(params);
|
||||
resolve(aa);
|
||||
});
|
||||
}
|
||||
|
||||
const onlineTableContext = {
|
||||
isPopList: true,
|
||||
reloadTable() {
|
||||
console.log('reloadTable');
|
||||
},
|
||||
isTree() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
const extConfigJson = ref<any>({});
|
||||
|
||||
// 处理 BasicTable 的配置
|
||||
const { columns, downloadRowFile, getImgView, getPcaText, getFormatDate, handleColumnResult, hrefComponent, viewOnlineCellImage } =
|
||||
useTableColumns(onlineTableContext, extConfigJson);
|
||||
|
||||
/**
|
||||
* 查询table列信息 及其他配置
|
||||
*/
|
||||
function getColumnList() {
|
||||
const url = '/online/cgform/api/getColumns/' + props.id;
|
||||
return new Promise((resolve, reject) => {
|
||||
defHttp.get({ url }, { isTransformResponse: false }).then((res) => {
|
||||
if (res.success) {
|
||||
resolve(res.result);
|
||||
} else {
|
||||
$message.warning(res.message);
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const modalTitle = ref('');
|
||||
watch(
|
||||
() => props.id,
|
||||
async () => {
|
||||
let columnResult: any = await getColumnList();
|
||||
handleColumnResult(columnResult);
|
||||
modalTitle.value = columnResult.description;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const { tableContext } = useListPage({
|
||||
designScope: 'process-design',
|
||||
pagination: true,
|
||||
tableProps: {
|
||||
title: '',
|
||||
api: list,
|
||||
clickToRowSelect: true,
|
||||
columns: columns,
|
||||
showTableSetting: false,
|
||||
immediate: false,
|
||||
//showIndexColumn: true,
|
||||
canResize: false,
|
||||
showActionColumn: false,
|
||||
actionColumn: {
|
||||
dataIndex: 'action',
|
||||
slots: { customRender: 'action' },
|
||||
},
|
||||
useSearchForm: false,
|
||||
beforeFetch: (params) => {
|
||||
return addQueryParams(params);
|
||||
},
|
||||
},
|
||||
});
|
||||
const [registerTable, { reload, setPagination }, { rowSelection, selectedRowKeys, selectedRows }] = tableContext;
|
||||
watch(
|
||||
() => props.multi,
|
||||
(val) => {
|
||||
if (val == true) {
|
||||
rowSelection.type = 'checkbox';
|
||||
} else {
|
||||
rowSelection.type = 'radio';
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
/**
|
||||
* 操作栏
|
||||
*/
|
||||
function getTableAction(record) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleUpdate.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function handleUpdate(record) {
|
||||
console.log('handleUpdate', record);
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
reload();
|
||||
}
|
||||
const eqConditonTypes = ['int', 'double', 'Date', 'Datetime', 'BigDecimal'];
|
||||
function addQueryParams(params) {
|
||||
let text = searchText.value;
|
||||
if (!text) {
|
||||
params['superQueryMatchType'] = 'or';
|
||||
params['superQueryParams'] = '';
|
||||
return params;
|
||||
}
|
||||
let arr = columns.value;
|
||||
let conditions: any[] = [];
|
||||
if (arr && arr.length > 0) {
|
||||
for (let item of arr) {
|
||||
if (item.dbType) {
|
||||
if (item.dbType == 'string') {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'like', val: text });
|
||||
} else if (item.dbType == 'Date') {
|
||||
if (text.length == '2020-10-10'.length) {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'eq', val: text });
|
||||
}
|
||||
} else if (item.dbType == 'Datetime') {
|
||||
if (text.length == '2020-10-10 10:10:10'.length) {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'eq', val: text });
|
||||
}
|
||||
} else if (eqConditonTypes.indexOf(item.dbType)) {
|
||||
conditions.push({ field: item.dataIndex, type: item.dbType.toLowerCase(), rule: 'eq', val: text });
|
||||
} else {
|
||||
//text blob不做处理
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
params['superQueryMatchType'] = 'or';
|
||||
params['superQueryParams'] = encodeURI(JSON.stringify(conditions));
|
||||
return params;
|
||||
}
|
||||
|
||||
// modal数据新增完成 直接关闭list,将新增的数据带回表单
|
||||
function handleDataSave(data) {
|
||||
console.log('handleDateSave', data);
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录新增丢失
|
||||
let arr = [data, ...selectedRows.value];
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录新增丢失
|
||||
emit('success', arr);
|
||||
closeModal();
|
||||
//reload();
|
||||
}
|
||||
|
||||
return {
|
||||
registerModal,
|
||||
modalWidth,
|
||||
handleCancel,
|
||||
submitDisabled,
|
||||
submitLoading,
|
||||
handleSubmit,
|
||||
|
||||
registerTable,
|
||||
getTableAction,
|
||||
searchText,
|
||||
onSearch,
|
||||
|
||||
downloadRowFile,
|
||||
getImgView,
|
||||
getPcaText,
|
||||
getFormatDate,
|
||||
hrefComponent,
|
||||
viewOnlineCellImage,
|
||||
rowSelection,
|
||||
modalTitle,
|
||||
|
||||
reload,
|
||||
|
||||
popModalFixedWidth,
|
||||
popBodyStyle,
|
||||
handleDataSave,
|
||||
|
||||
tableRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -0,0 +1,358 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { ref, watchEffect, computed, reactive } from 'vue';
|
||||
import { pick } from 'lodash-es';
|
||||
import { filterMultiDictText } from '/@/utils/dict/JDictSelectUtil';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
|
||||
function queryTableData(tableName, params) {
|
||||
const url = '/online/cgform/api/getData/' + tableName;
|
||||
return defHttp.get({ url, params });
|
||||
}
|
||||
|
||||
function queryTableColumns(tableName, params) {
|
||||
const url = '/online/cgform/api/getColumns/' + tableName;
|
||||
return defHttp.get({ url, params });
|
||||
}
|
||||
|
||||
export function useLinkTable(props) {
|
||||
//TODO 目前只支持查询第一页的数据,可以输入关键字搜索
|
||||
const pageNo = ref('1');
|
||||
// 查询列
|
||||
const baseParam = ref<any>({});
|
||||
// 搜素条件
|
||||
const searchParam = ref<any>({});
|
||||
// 第一个文本列
|
||||
const mainContentField = ref('');
|
||||
//权限数据
|
||||
const auths = reactive({
|
||||
add: true,
|
||||
update: true,
|
||||
});
|
||||
|
||||
//显示列
|
||||
const textFieldArray = computed(() => {
|
||||
if (props.textField) {
|
||||
return props.textField.split(',');
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const otherColumns = ref<any[]>([]);
|
||||
// 展示的列 配置的很多列,但是只展示三行
|
||||
const realShowColumns = computed(() => {
|
||||
const columns = otherColumns.value;
|
||||
if (props.multi == true) {
|
||||
return columns.slice(0, 3);
|
||||
} else {
|
||||
return columns.slice(0, 6);
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(async () => {
|
||||
const table = props.tableName;
|
||||
if (table) {
|
||||
const valueField = props.valueField || '';
|
||||
const textField = props.textField || '';
|
||||
const arr: any[] = [];
|
||||
if (valueField) {
|
||||
arr.push(valueField);
|
||||
}
|
||||
if (textField) {
|
||||
const temp = textField.split(',');
|
||||
mainContentField.value = temp[0];
|
||||
for (const field of temp) {
|
||||
arr.push(field);
|
||||
}
|
||||
}
|
||||
const imageField = props.imageField || '';
|
||||
if (imageField) {
|
||||
arr.push(imageField);
|
||||
}
|
||||
baseParam.value = {
|
||||
linkTableSelectFields: arr.join(','),
|
||||
};
|
||||
await resetTableColumns();
|
||||
await reloadTableLinkOptions();
|
||||
}
|
||||
});
|
||||
|
||||
const otherFields = computed(() => {
|
||||
const textField = props.textField || '';
|
||||
const others: any[] = [];
|
||||
let labelField = '';
|
||||
if (textField) {
|
||||
const temp = textField.split(',');
|
||||
labelField = temp[0];
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
if (i > 0) {
|
||||
others.push(temp[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
others,
|
||||
labelField,
|
||||
};
|
||||
});
|
||||
|
||||
// 选项
|
||||
const selectOptions = ref<any[]>([]);
|
||||
const tableColumns = ref<any[]>([]);
|
||||
const dictOptions = ref<any>({});
|
||||
|
||||
async function resetTableColumns() {
|
||||
const params = baseParam.value;
|
||||
const data = await queryTableColumns(props.tableName, params);
|
||||
tableColumns.value = data.columns;
|
||||
if (data.columns) {
|
||||
const imageField = props.imageField;
|
||||
const arr = data.columns.filter((c) => c.dataIndex != mainContentField.value && c.dataIndex != imageField);
|
||||
otherColumns.value = arr;
|
||||
}
|
||||
dictOptions.value = data.dictOptions;
|
||||
// 权限数据
|
||||
console.log('隐藏的按钮', data.hideColumns);
|
||||
if (data.hideColumns) {
|
||||
const hideCols = data.hideColumns;
|
||||
if (hideCols.indexOf('add') >= 0) {
|
||||
auths.add = false;
|
||||
} else {
|
||||
auths.add = true;
|
||||
}
|
||||
if (hideCols.indexOf('update') >= 0) {
|
||||
auths.update = false;
|
||||
} else {
|
||||
auths.update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reloadTableLinkOptions() {
|
||||
const params = getLoadDataParams();
|
||||
const data = await queryTableData(props.tableName, params);
|
||||
const records = data.records;
|
||||
//tableTitle.value = data.head.tableTxt;
|
||||
const dataList: any[] = [];
|
||||
const { others, labelField } = otherFields.value;
|
||||
const imageField = props.imageField;
|
||||
if (records && records.length > 0) {
|
||||
for (const rd of records) {
|
||||
const temp = { ...rd };
|
||||
transData(temp);
|
||||
const result = Object.assign({}, pick(temp, others), { id: temp.id, label: temp[labelField], value: temp[props.valueField] });
|
||||
if (imageField) {
|
||||
result[imageField] = temp[imageField];
|
||||
}
|
||||
dataList.push(result);
|
||||
}
|
||||
}
|
||||
//添加一个空对象 为add操作占位
|
||||
// update-begin--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮
|
||||
props.editBtnShow && dataList.push({});
|
||||
// update-end--author:liaozhiyang---date:20240607---for:【TV360X-1095】高级查询关联记录去掉编辑按钮及去掉记录按钮
|
||||
selectOptions.value = dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据简单翻译-字典
|
||||
* @param data
|
||||
*/
|
||||
function transData(data) {
|
||||
const columns = tableColumns.value;
|
||||
const dictInfo = dictOptions.value;
|
||||
for (const c of columns) {
|
||||
const { dataIndex, customRender } = c;
|
||||
if (data[dataIndex] || data[dataIndex] === 0) {
|
||||
if (customRender && customRender == dataIndex) {
|
||||
//这样的就是 字典数据了 可以直接翻译
|
||||
if (dictInfo[customRender]) {
|
||||
data[dataIndex] = filterMultiDictText(dictInfo[customRender], data[dataIndex]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 兼容后台翻译字段
|
||||
const dictText = data[dataIndex + '_dictText'];
|
||||
if (dictText) {
|
||||
data[dataIndex] = dictText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取加载数据的查询条件
|
||||
function getLoadDataParams() {
|
||||
const params = Object.assign({ pageSize: 100, pageNo: pageNo.value }, baseParam.value, searchParam.value);
|
||||
return params;
|
||||
}
|
||||
|
||||
//设置查询条件
|
||||
function addQueryParams(text) {
|
||||
if (!text) {
|
||||
searchParam.value = {};
|
||||
} else {
|
||||
const arr = textFieldArray.value;
|
||||
const params: any[] = [];
|
||||
const fields: any[] = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (i <= 1) {
|
||||
fields.push(arr[i]);
|
||||
params.push({ field: arr[i], rule: 'like', val: text });
|
||||
}
|
||||
}
|
||||
// params[arr[i]] = `*${text}*`
|
||||
// params['selectConditionFields'] = fields.join(',')
|
||||
// searchParam.value = params;
|
||||
params['superQueryMatchType'] = 'or';
|
||||
params['superQueryParams'] = encodeURI(JSON.stringify(params));
|
||||
searchParam.value = params;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOne(value) {
|
||||
if (!value) {
|
||||
return [];
|
||||
}
|
||||
let valueFieldName = props.valueField;
|
||||
let params = {
|
||||
...baseParam.value,
|
||||
pageSize: 100,
|
||||
pageNo: pageNo.value,
|
||||
};
|
||||
params['superQueryMatchType'] = 'and';
|
||||
let valueCondition = [{ field: valueFieldName, rule: 'in', val: value }];
|
||||
params['superQueryParams'] = encodeURI(JSON.stringify(valueCondition));
|
||||
const data = await queryTableData(props.tableName, params);
|
||||
let records = data.records;
|
||||
//tableTitle.value = data.head.tableTxt;
|
||||
let dataList: any[] = [];
|
||||
if (records && records.length > 0) {
|
||||
for (let item of records) {
|
||||
let temp = { ...item };
|
||||
transData(temp);
|
||||
dataList.push(temp);
|
||||
}
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* true:数据一致;false:数据不一致
|
||||
* @param arr
|
||||
* @param value
|
||||
*/
|
||||
function compareData(arr, value) {
|
||||
if (!arr || arr.length == 0) {
|
||||
return false;
|
||||
}
|
||||
const valueArray = value.split(',');
|
||||
if (valueArray.length != arr.length) {
|
||||
return false;
|
||||
}
|
||||
let flag = true;
|
||||
for (const item of arr) {
|
||||
const temp = item[props.valueField];
|
||||
if (valueArray.indexOf(temp) < 0) {
|
||||
flag = false;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
function formatData(formData) {
|
||||
Object.keys(formData).map((k) => {
|
||||
if (formData[k] instanceof Array) {
|
||||
formData[k] = formData[k].join(',');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initFormData(formData, linkFieldArray, record) {
|
||||
if (!record) {
|
||||
record = {};
|
||||
}
|
||||
if (linkFieldArray && linkFieldArray.length > 0) {
|
||||
for (const str of linkFieldArray) {
|
||||
const arr = str.split(',');
|
||||
//["表单字段,表字典字段"]
|
||||
const field = arr[0];
|
||||
const dictField = arr[1];
|
||||
if (!formData[field]) {
|
||||
const value = record[dictField] || '';
|
||||
formData[field] = [value];
|
||||
} else {
|
||||
formData[field].push(record[dictField]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片地址
|
||||
function getImageSrc(item) {
|
||||
if (props.imageField) {
|
||||
let url = item[props.imageField];
|
||||
// update-begin--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示
|
||||
if (typeof url === 'string') {
|
||||
// 有多张图时默认取第一张
|
||||
url = url.split(',')[0];
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250517---for:【TV360X-38】关联记录空间,被关联数据优多个图片时,封面图片不展示
|
||||
return getFileAccessHttpUrl(url);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
const showImage = computed(() => {
|
||||
if (props.imageField) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
pageNo,
|
||||
otherColumns,
|
||||
realShowColumns,
|
||||
selectOptions,
|
||||
reloadTableLinkOptions,
|
||||
textFieldArray,
|
||||
addQueryParams,
|
||||
tableColumns,
|
||||
transData,
|
||||
mainContentField,
|
||||
loadOne,
|
||||
compareData,
|
||||
formatData,
|
||||
initFormData,
|
||||
getImageSrc,
|
||||
showImage,
|
||||
auths,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用固定高度的modal
|
||||
*/
|
||||
export function useFixedHeightModal() {
|
||||
const minWidth = 800;
|
||||
const popModalFixedWidth = ref(800);
|
||||
let tempWidth = window.innerWidth - 300;
|
||||
if (tempWidth < minWidth) {
|
||||
tempWidth = minWidth;
|
||||
}
|
||||
popModalFixedWidth.value = tempWidth;
|
||||
|
||||
// 弹窗高度控制
|
||||
const popBodyStyle = ref({});
|
||||
function resetBodyStyle() {
|
||||
const height = window.innerHeight - 210;
|
||||
popBodyStyle.value = {
|
||||
height: height + 'px',
|
||||
overflowY: 'auto',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
popModalFixedWidth,
|
||||
popBodyStyle,
|
||||
resetBodyStyle,
|
||||
};
|
||||
}
|
||||
@ -15,6 +15,9 @@
|
||||
@search="loadData"
|
||||
@change="handleAsyncChange"
|
||||
@popupScroll="handlePopupScroll"
|
||||
:mode="multiple?'multiple':''"
|
||||
@select="handleSelect"
|
||||
@deselect="handleDeSelect"
|
||||
>
|
||||
<template #notFoundContent>
|
||||
<a-spin size="small" />
|
||||
@ -33,6 +36,9 @@
|
||||
:notFoundContent="loading ? undefined : null"
|
||||
:dropdownAlign="{overflow: {adjustY: adjustY }}"
|
||||
@change="handleChange"
|
||||
:mode="multiple?'multiple':''"
|
||||
@select="handleSelect"
|
||||
@deselect="handleDeSelect"
|
||||
>
|
||||
<template #notFoundContent>
|
||||
<a-spin v-if="loading" size="small" />
|
||||
@ -83,6 +89,13 @@
|
||||
default: ()=>{}
|
||||
},
|
||||
//update-end-author:taoyan date:2022-8-15 for: VUEN-1971 【online 专项测试】关联记录和他表字段 1
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//是否为多选
|
||||
multiple:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
},
|
||||
emits: ['change', 'update:value'],
|
||||
setup(props, { emit, refs }) {
|
||||
@ -210,28 +223,55 @@
|
||||
if (!selectedAsyncValue || !selectedAsyncValue.key || selectedAsyncValue.key !== value) {
|
||||
defHttp.get({ url: `/sys/dict/loadDictItem/${dict}`, params: { key: value } }).then((res) => {
|
||||
if (res && res.length > 0) {
|
||||
let obj = {
|
||||
key: value,
|
||||
label: res,
|
||||
};
|
||||
if (props.value == value) {
|
||||
selectedAsyncValue.value = { ...obj };
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//判断组件是否为多选
|
||||
if(props.multiple){
|
||||
if(value){
|
||||
let arr: any = [];
|
||||
//多选返回的是以逗号拼接的方式
|
||||
let values = value.toString().split(',');
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
let obj = {
|
||||
key: values[i],
|
||||
label: res[i],
|
||||
};
|
||||
arr.push(obj);
|
||||
selectedValue.value.push(obj.key);
|
||||
}
|
||||
selectedAsyncValue.value = arr;
|
||||
}
|
||||
} else {
|
||||
let obj = {
|
||||
key: value,
|
||||
label: res,
|
||||
};
|
||||
if (props.value == value) {
|
||||
selectedAsyncValue.value = { ...obj };
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', props.value);
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', props.value);
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online关联记录配置页面
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
selectedValue.value = value.toString();
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', value.toString());
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
if(!props.multiple){
|
||||
selectedValue.value = value.toString();
|
||||
//update-begin-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
if(props.immediateChange == true){
|
||||
emit('change', value.toString());
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
}else{
|
||||
//多选的情况下需要转成数组
|
||||
selectedValue.value = value.toString().split(',');
|
||||
}
|
||||
//update-end-author:taoyan date:2022-8-11 for: 值改变触发change事件--用于online他表字段配置界面
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,35 +346,100 @@
|
||||
* 同步改变事件
|
||||
* */
|
||||
function handleChange(value) {
|
||||
selectedValue.value = value;
|
||||
callback();
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//多选也会触发change事件,需要判断如果时多选不需要赋值
|
||||
if(!props.multiple){
|
||||
selectedValue.value = value;
|
||||
callback();
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
/**
|
||||
* 异步改变事件
|
||||
* */
|
||||
function handleAsyncChange(selectedObj) {
|
||||
if (selectedObj) {
|
||||
selectedAsyncValue.value = selectedObj;
|
||||
selectedValue.value = selectedObj.key;
|
||||
} else {
|
||||
selectedAsyncValue.value = null;
|
||||
selectedValue.value = null;
|
||||
options.value = null;
|
||||
loadData('');
|
||||
}
|
||||
callback();
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
// 点x清空时需要把loadSelectText设置true
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
// update-end--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
// 单选情况下使用change事件
|
||||
if(!props.multiple){
|
||||
if (selectedObj) {
|
||||
selectedAsyncValue.value = selectedObj;
|
||||
selectedValue.value = selectedObj.key;
|
||||
} else {
|
||||
selectedAsyncValue.value = null;
|
||||
selectedValue.value = null;
|
||||
options.value = null;
|
||||
loadData('');
|
||||
}
|
||||
callback();
|
||||
// update-begin--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
// 点x清空时需要把loadSelectText设置true
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
// update-end--author:liaozhiyang---date:20240524---for:【TV360X-426】下拉搜索设置了默认值,把查询条件删掉,再点击重置,没附上值
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
/**
|
||||
* 异步值选中事件
|
||||
* @param selectedObj
|
||||
*/
|
||||
function handleSelect(selectedObj){
|
||||
let key = selectedObj;
|
||||
if(props.async){
|
||||
key = selectedObj.key;
|
||||
}
|
||||
//多选情况下使用select事件
|
||||
if(props.multiple && key){
|
||||
//异步的时候才需要在selectedValue数组中添加值操作,同步的情况下直接走更新值操作
|
||||
if(props.async){
|
||||
selectedValue.value.push(key);
|
||||
}
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步值取消选中事件
|
||||
* @param selectedObj
|
||||
*/
|
||||
function handleDeSelect(selectedObj){
|
||||
let key = selectedObj;
|
||||
if(props.async){
|
||||
key = selectedObj.key;
|
||||
}
|
||||
//多选情况下使用select事件
|
||||
if(props.multiple){
|
||||
//异步的时候才需要在selectedValue数组中删除值操作,同步的情况下直接走更新值操作
|
||||
if(props.async){
|
||||
let findIndex = selectedValue.value.findIndex(item => item === key);
|
||||
if(findIndex != -1){
|
||||
selectedValue.value.splice(findIndex,1);
|
||||
}
|
||||
}
|
||||
selectedObj ?? (loadSelectText.value = true);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
|
||||
/**
|
||||
*回调方法
|
||||
* */
|
||||
function callback() {
|
||||
loadSelectText.value = false;
|
||||
emit('change', unref(selectedValue));
|
||||
emit('update:value', unref(selectedValue));
|
||||
//update-begin---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
//单选直接走更新值操作
|
||||
if(!props.multiple){
|
||||
emit('change', unref(selectedValue));
|
||||
emit('update:value', unref(selectedValue));
|
||||
} else {
|
||||
//多选需要把数组转成字符串
|
||||
emit('change', unref(selectedValue).join(","));
|
||||
emit('update:value', unref(selectedValue).join(","));
|
||||
}
|
||||
//update-end---author:wangshuai---date:2025-04-17---for:【issues/8101】前端dict组件导致内存溢出问题:搜索组件支持多选---
|
||||
}
|
||||
/**
|
||||
* 过滤选中option
|
||||
@ -461,6 +566,8 @@
|
||||
handleAsyncChange,
|
||||
handleAsyncFocus,
|
||||
handlePopupScroll,
|
||||
handleSelect,
|
||||
handleDeSelect,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -71,6 +71,11 @@
|
||||
*/
|
||||
watch(selectValues, () => {
|
||||
if (selectValues) {
|
||||
// update-begin--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
if (props.value === undefined && selectValues.value?.length == 0) {
|
||||
return;
|
||||
}
|
||||
// update-end--author:liaozhiyang---date:20250616---for:【QQYUN-12869】通过部门选择用户组件,必填状态下选择用户后,点击重置后,会出校验信息
|
||||
state.value = selectValues.value;
|
||||
}
|
||||
});
|
||||
|
||||
@ -28,8 +28,12 @@
|
||||
></a-select>
|
||||
</a-col>
|
||||
<a-col v-if="showButton" class="right">
|
||||
<a-button v-if="buttonIcon" :preIcon="buttonIcon" type="primary" @click="openModal(true)" :disabled="disabled">选择</a-button>
|
||||
<a-button v-else type="primary" @click="openModal(true)" :disabled="disabled">选择</a-button>
|
||||
<a-button v-if="buttonIcon" :preIcon="buttonIcon" type="primary" @click="openModal(true)" :disabled="disabled">
|
||||
{{ buttonText }}
|
||||
</a-button>
|
||||
<a-button v-else type="primary" @click="openModal(true)" :disabled="disabled">
|
||||
{{ buttonText }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
@ -46,6 +50,7 @@
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
showButton: propTypes.bool.def(true),
|
||||
buttonText: propTypes.string.def('选择'),
|
||||
disabled: propTypes.bool.def(false),
|
||||
placeholder: {
|
||||
type: String,
|
||||
|
||||
@ -156,7 +156,9 @@ export interface FormSchema {
|
||||
// Required
|
||||
required?: boolean | ((renderCallbackParams: RenderCallbackParams) => boolean);
|
||||
|
||||
suffix?: string | number | ((values: RenderCallbackParams) => string | number);
|
||||
suffix?: string | number | VueNode | ((values: RenderCallbackParams) => string | number | VueNode);
|
||||
// 【QQYUN-12876】是否是紧凑型 suffix(当组件宽度未占满时,可紧挨着组件右侧)
|
||||
suffixCompact?: boolean;
|
||||
|
||||
// Validation rules
|
||||
rules?: Rule[];
|
||||
@ -164,7 +166,7 @@ export interface FormSchema {
|
||||
rulesMessageJoinLabel?: boolean;
|
||||
|
||||
// Reference formModelItem
|
||||
itemProps?: Partial<FormItem>;
|
||||
itemProps?: Partial<FormItem> | ((renderCallbackParams: RenderCallbackParams) => Partial<FormItem>);
|
||||
|
||||
// col configuration outside formModelItem
|
||||
colProps?: Partial<ColEx>;
|
||||
|
||||
@ -157,5 +157,6 @@ export type ComponentType =
|
||||
| 'linkRecordSelect'
|
||||
| 'RangeTime'
|
||||
| 'JRangeNumber'
|
||||
| 'JLinkTableCard'
|
||||
| 'JInputSelect';
|
||||
|
||||
|
||||
|
||||
@ -8,6 +8,9 @@
|
||||
:style="{ width }"
|
||||
@click="currentSelectClick"
|
||||
>
|
||||
<template #suffix v-if="allowClear && currentSelect">
|
||||
<CloseCircleFilled class="menu-current-close" @click.stop="clearCurrentSelect" />
|
||||
</template>
|
||||
<template #addonAfter>
|
||||
<span class="cursor-pointer px-2 py-1 flex items-center" v-if="isSvgMode && currentSelect">
|
||||
<SvgIcon :name="currentSelect" @click="currentSelectClick"/>
|
||||
@ -77,7 +80,7 @@
|
||||
import { useI18n } from '/@/hooks/web/useI18n';
|
||||
import svgIcons from 'virtual:svg-icons-names';
|
||||
import IconList from "./IconList.vue";
|
||||
|
||||
import { CloseCircleFilled } from '@ant-design/icons-vue';
|
||||
// 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告
|
||||
const AInput = Input;
|
||||
|
||||
@ -104,6 +107,7 @@
|
||||
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
|
||||
disabled: propTypes.bool.def(false),
|
||||
clearSelect: propTypes.bool.def(false),
|
||||
allowClear: propTypes.bool.def(false),
|
||||
iconPrefixSave: propTypes.bool.def(true),
|
||||
});
|
||||
|
||||
@ -196,6 +200,12 @@
|
||||
iconOpen.value = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前选择图标
|
||||
*/
|
||||
function clearCurrentSelect(){
|
||||
currentSelect.value = '';
|
||||
}
|
||||
onMounted(()=>{
|
||||
//初始化加载图标
|
||||
initOtherIcon();
|
||||
@ -227,5 +237,16 @@
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
//图标样式
|
||||
.menu-current-close {
|
||||
color: #cccccc;
|
||||
}
|
||||
}
|
||||
//图标样式兼容暗黑模式
|
||||
[data-theme='dark'] .@{prefix-cls} {
|
||||
.menu-current-close {
|
||||
color: #4f4f4f;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
54
jeecgboot-vue3/src/components/JDragNotice/JDragNotice.vue
Normal file
54
jeecgboot-vue3/src/components/JDragNotice/JDragNotice.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<keep-alive>
|
||||
<component
|
||||
v-if="currentModal"
|
||||
v-bind="bindParams"
|
||||
:key="currentModal"
|
||||
:is="currentModal"
|
||||
@register="modalRegCache[currentModal].register"
|
||||
@reply="handReply"
|
||||
@selected="reloadPage"
|
||||
/>
|
||||
</keep-alive>
|
||||
<!-- 系统公告弹窗 -->
|
||||
<DynamicNotice ref="showDynamNotice" v-bind="bindParams" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { useDragNotice } from '/@/hooks/web/useDragNotice';
|
||||
import DynamicNotice from '@/views/monitor/mynews/DynamicNotice.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JDragNotice',
|
||||
components: {
|
||||
DynamicNotice,
|
||||
},
|
||||
setup() {
|
||||
const {
|
||||
initDragWebSocket,
|
||||
currentModal,
|
||||
modalParams,
|
||||
modalRegCache,
|
||||
bindParams,
|
||||
reloadPage,
|
||||
} = useDragNotice();
|
||||
|
||||
onMounted(() => {
|
||||
initDragWebSocket();
|
||||
});
|
||||
|
||||
return {
|
||||
currentModal,
|
||||
modalParams,
|
||||
modalRegCache,
|
||||
bindParams,
|
||||
reloadPage,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@ -13,6 +13,7 @@
|
||||
import { getTenantId, getToken } from '/@/utils/auth';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { uploadFile } from '@/api/common/api';
|
||||
import {$electron} from "@/electron";
|
||||
|
||||
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
|
||||
|
||||
@ -109,6 +110,13 @@
|
||||
function init() {
|
||||
const wrapEl = unref(wrapRef) as HTMLElement;
|
||||
if (!wrapEl) return;
|
||||
|
||||
// vditor组件本地化的路径配置【QQYUN-12053】
|
||||
let localCdn = '/resource/vditor@3.9.4';
|
||||
if ($electron.isElectron()) {
|
||||
localCdn = '.' + localCdn;
|
||||
}
|
||||
|
||||
const bindValue = { ...attrs, ...props };
|
||||
const insEditor = new Vditor(wrapEl, {
|
||||
theme: getDarkMode.value === 'dark' ? 'dark' : 'classic',
|
||||
@ -151,8 +159,8 @@
|
||||
],
|
||||
// update-end--author:liaozhiyang---date:20240520---for:【TV360X-146】Markdown组件去掉录音选项
|
||||
mode: 'sv',
|
||||
// cdn: 'https://cdn.jsdelivr.net/npm/vditor@3.9.6',
|
||||
cdn: 'https://unpkg.com/vditor@3.10.1',
|
||||
cdn: 'https://unpkg.com/vditor@3.10.8',
|
||||
//cdn: localCdn,
|
||||
fullscreen: {
|
||||
index: 520,
|
||||
},
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
{{ t('component.table.settingColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox v-model:checked="checkIndex" @change="handleIndexCheckChange">
|
||||
<Checkbox :disabled="isTreeTable" v-model:checked="checkIndex" @change="handleIndexCheckChange">
|
||||
{{ t('component.table.settingIndexColumnShow') }}
|
||||
</Checkbox>
|
||||
|
||||
@ -143,6 +143,9 @@
|
||||
setup(props, { emit, attrs }) {
|
||||
const { t } = useI18n();
|
||||
const table = useTableContext();
|
||||
// update-begin--author:liaozhiyang---date:20250526---for:【issues/8301】树形表格序号列禁用
|
||||
const isTreeTable = computed(() => table.getBindValues.value.isTreeTable);
|
||||
// update-end--author:liaozhiyang---date:20250526---for:【issues/8301】树形表格序号列禁用
|
||||
const popoverVisible = ref(false);
|
||||
// update-begin--author:sunjianlei---date:20221101---for: 修复第一次进入时列表配置不能拖拽
|
||||
// nextTick(() => popoverVisible.value = false);
|
||||
@ -479,6 +482,7 @@
|
||||
defaultRowSelection,
|
||||
handleColumnFixed,
|
||||
getPopupContainer,
|
||||
isTreeTable,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -140,7 +140,12 @@ export function useCustomSelection(
|
||||
// 解决selectedRowKeys在页面调用处使用ref失效
|
||||
const value = unref(val);
|
||||
if (Array.isArray(value) && !sameArray(value, selectedKeys.value)) {
|
||||
setSelectedRowKeys(value);
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
// 延迟是为了等watch selectedRows
|
||||
setTimeout(() => {
|
||||
setSelectedRowKeys(value);
|
||||
}, 0);
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -149,7 +154,22 @@ export function useCustomSelection(
|
||||
}
|
||||
);
|
||||
// update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8390】部门人员组件点击重置未清空(selectedRowKeys.value=[],watch没监听到加deep)
|
||||
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
// 编辑时selectedRows可能会回填
|
||||
watch(
|
||||
() => unref(propsRef)?.rowSelection?.selectedRows,
|
||||
(val: string[]) => {
|
||||
const value: any = unref(val);
|
||||
if (Array.isArray(value) && !sameArray(value, selectedRows.value)) {
|
||||
selectedRows.value = value;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
/**
|
||||
* 2024-03-06
|
||||
* liaozhiyang
|
||||
@ -581,7 +601,12 @@ export function useCustomSelection(
|
||||
// 通过 selectedKeys 同步 selectedRows
|
||||
function syncSelectedRows() {
|
||||
if (selectedKeys.value.length !== selectedRows.value.length) {
|
||||
setSelectedRowKeys(selectedKeys.value);
|
||||
// update-begin--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
// 延迟是为了等watch selectedRows
|
||||
setTimeout(() => {
|
||||
setSelectedRowKeys(selectedKeys.value);
|
||||
}, 0);
|
||||
// update-end--author:liaozhiyang---date:20250429---for:【issues/8163】关联记录夸页数据丢失
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -146,25 +146,27 @@ export function useTableScroll(
|
||||
|
||||
bodyEl!.style.height = `${height}px`;
|
||||
// update-begin--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||||
if (maxHeight === undefined) {
|
||||
if (unref(getPaginationInfo) && unref(getDataSourceRef).length) {
|
||||
const pageSize = unref(getPaginationInfo)?.pageSize;
|
||||
const current = unref(getPaginationInfo)?.current;
|
||||
const total = unref(getPaginationInfo)?.total;
|
||||
const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement;
|
||||
const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? [];
|
||||
const lastrEl = tr[tr.length - 1] as HTMLElement;
|
||||
const trHeight = lastrEl.offsetHeight;
|
||||
const dataHeight = trHeight * pageSize;
|
||||
if (tableBody && lastrEl) {
|
||||
if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) {
|
||||
tableBody.style.height = `${height}px`;
|
||||
} else {
|
||||
tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`;
|
||||
nextTick(() => {
|
||||
if (maxHeight === undefined) {
|
||||
if (unref(getPaginationInfo) && unref(getDataSourceRef).length) {
|
||||
const pageSize = unref(getPaginationInfo)?.pageSize;
|
||||
const current = unref(getPaginationInfo)?.current;
|
||||
const total = unref(getPaginationInfo)?.total;
|
||||
const tableBody = tableEl.querySelector('.ant-table-body') as HTMLElement;
|
||||
const tr = tableEl.querySelector('.ant-table-tbody')?.children ?? [];
|
||||
const lastrEl = tr[tr.length - 1] as HTMLElement;
|
||||
const trHeight = lastrEl.offsetHeight;
|
||||
const dataHeight = trHeight * pageSize;
|
||||
if (tableBody && lastrEl) {
|
||||
if (current === 1 && pageSize > unref(getDataSourceRef).length && total <= pageSize) {
|
||||
tableBody.style.height = `${height}px`;
|
||||
} else {
|
||||
tableBody.style.height = `${dataHeight < height ? dataHeight : height}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// update-end--author:liaozhiyang---date:20240609---for【issues/8374】分页始终显示在底部
|
||||
}
|
||||
useWindowSizeFn(calcTableHeight, 280);
|
||||
|
||||
@ -38,6 +38,8 @@ export interface TableRowSelection<T = any> extends ITableRowSelection {
|
||||
* @type Function
|
||||
*/
|
||||
onSelectInvert?: (selectedRows: string[] | number[]) => any;
|
||||
//【issues/8163】关联记录新增丢失
|
||||
selectedRows?: any[];
|
||||
}
|
||||
|
||||
export interface TableCustomRecord<T> {
|
||||
|
||||
Reference in New Issue
Block a user