feat: 明细表中的组织架构选择器支持将文本表示存储在独立字段,以改善显示性能

This commit is contained in:
gaoyunqi
2024-05-16 15:49:49 +08:00
parent 2e07279924
commit 92fd90f35d
5 changed files with 249 additions and 208 deletions

View File

@ -422,6 +422,13 @@
</template>
</a-input>
</a-form-item>
<template v-if="data.isSubFormChild">
<a-form-item v-if="hasKey('sepTextField')" label="文本存储字段">
<a-select v-model:value="data.options.sepTextField" :allow-clear="true" :placeholder="t('请选择文本字段')" size="mini">
<a-select-option v-for="fItem in findTableFields(data)" :key="fItem.key" :value="fItem.bindField">{{ fItem.label }}</a-select-option>
</a-select>
</a-form-item>
</template>
<!-- 响应式布局只对主表字段生效 -->
<template v-if="!data.isSubFormChild">
<a-form-item v-if="hasKey('responsive')" label="响应式">
@ -1560,6 +1567,18 @@
});
}
function findTableFields(obj) {
if (!props.widgetForm?.list) {
return [];
}
const bTable = obj.bindTable;
const pTable = props.widgetForm.list.find((item) => item.type === 'form' && item.bindTable === bTable);
if (!pTable) {
return [];
}
return pTable.children;
}
return {
data,
hasKey,
@ -1617,7 +1636,8 @@
handleIsSave,
buttonTableOptions,
t,
imageUrl
imageUrl,
findTableFields
};
}
});

View File

@ -1164,6 +1164,7 @@ export const infoComponents = [
labelFixWidth: 120,
parentNode: '',
responsive: false,
sepTextField: '',
span: '',
width: '100%',
defaultValue: undefined,
@ -1184,6 +1185,7 @@ export const infoComponents = [
labelWidthMode: 'fix',
labelFixWidth: 120,
responsive: false,
sepTextField: '',
span: '',
width: '100%',
defaultValue: '',

View File

@ -1,205 +1,226 @@
<template>
<div style="width: 100%" @click="show" class="depart-select">
<a-input v-model:value="departNames" :bordered="bordered" :disabled="disabled" :placeholder="placeholder"
:size="size" readonly>
<template v-if="prefix" #prefix>
<Icon :icon="prefix" />
</template>
<template v-if="suffix" #suffix>
<Icon :icon="suffix" />
</template>
</a-input>
<ModalPanel :visible="visible" :width="800" :title="t('选择部门')" @submit="submit" @close="close" class="depart-select-dialog">
<div class="choose-dep-box">
<div class="choose-dep">
<a-spin class="loading-box" :spinning="loading" />
<SelectDepartmentTreeV2 v-if="visible" @changeValue="departChange" :value="props.value"
@queryCompleted="queryCompleted" :parentNode="parentNode"></SelectDepartmentTreeV2>
</div>
<div class="choosen-dep">
<div class="choosen-item" v-for="item in selectedNodes">
<div class="choosen-label">{{ item.name }}</div>
<close-outlined class="close" @click="deleteItem" />
</div>
</div>
</div>
</ModalPanel>
</div>
<div class="depart-select" style="width: 100%" @click="show">
<a-input v-model:value="departNames" :bordered="bordered" :disabled="disabled" :placeholder="placeholder" :size="size" readonly>
<template v-if="prefix" #prefix>
<Icon :icon="prefix" />
</template>
<template v-if="suffix" #suffix>
<Icon :icon="suffix" />
</template>
</a-input>
<ModalPanel :title="t('选择部门')" :visible="visible" :width="800" class="depart-select-dialog" @close="close" @submit="submit">
<div class="choose-dep-box">
<div class="choose-dep">
<a-spin :spinning="loading" class="loading-box" />
<SelectDepartmentTreeV2 v-if="visible" :parentNode="parentNode" :value="props.value" @changeValue="departChange" @queryCompleted="queryCompleted"></SelectDepartmentTreeV2>
</div>
<div class="choosen-dep">
<div v-for="item in selectedNodes" class="choosen-item">
<div class="choosen-label">{{ item.name }}</div>
<close-outlined class="close" @click="deleteItem" />
</div>
</div>
</div>
</ModalPanel>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue';
import SelectDepartmentTreeV2 from './SelectDepartmentTreeV2.vue'
import { useI18n } from '/@/hooks/web/useI18n';
import { ModalPanel } from '/@/components/ModalPanel/index';
const { t } = useI18n();
const emits = defineEmits(['change', 'changeNames', 'close', 'options-change', 'update:value']);
import { getDepartmentTrees } from '/@/api/system/department';
import { CloseOutlined } from '@ant-design/icons-vue';
const visible = ref(false)
const departNames = ref<string>();
<script setup>
import { onMounted, ref, watch } from 'vue';
import SelectDepartmentTreeV2 from './SelectDepartmentTreeV2.vue';
import { useI18n } from '/@/hooks/web/useI18n';
import { ModalPanel } from '/@/components/ModalPanel/index';
const props = defineProps({
value: String,
prefix: String,
suffix: String,
placeholder: String,
readonly: Boolean,
disabled: Boolean,
parentNode: String,
size: String,
bordered: {
type: Boolean,
default: true
},
multiple: {
type: Boolean,
default: true
},
componentProps: {
type: Object,
default: {}
}
});
const selectedNodes = ref([])
const loading = ref(true)
const { t } = useI18n();
const emits = defineEmits(['change', 'changeNames', 'close', 'options-change', 'update:value']);
import { getDepartmentTrees } from '/@/api/system/department';
import { CloseOutlined } from '@ant-design/icons-vue';
import { camelCaseString } from '/@/utils/event/design';
// Embedded in the form, just use the hook binding to perform form verification
const visible = ref(false);
const departNames = ref();
const valChanged = ref(false);
onMounted(() => {
});
const props = defineProps({
value: String,
prefix: String,
suffix: String,
placeholder: String,
readonly: Boolean,
disabled: Boolean,
parentNode: String,
size: String,
bordered: {
type: Boolean,
default: true
},
multiple: {
type: Boolean,
default: true
},
componentProps: {
type: Object,
default: {}
},
sepTextField: '', // 将文字描述存在独立字段,增加首次渲染速度
row: Object
});
const selectedNodes = ref([]);
const loading = ref(true);
watch(
() => props.value,
(val: any) => {
if (val) {
getDefaultList(val)
// Embedded in the form, just use the hook binding to perform form verification
onMounted(() => {});
watch(
() => props.value,
(val) => {
if (val) {
if (props.sepTextField && !valChanged.value) {
departNames.value = props.row[camelCaseString(props.sepTextField, true)];
} else {
getDefaultList(val);
}
}
},
{
immediate: true
}
);
async function getDefaultList(id) {
let param = {
id: id,
code: '',
name: '',
parentNode: false
};
let list = resetTreeList(await getDepartmentTrees(param));
selectedNodes.value = list;
let nameList = selectedNodes.value.map((item) => item.name);
const names = nameList.join(',');
departNames.value = names;
if (props.sepTextField) {
props.row[camelCaseString(props.sepTextField, true)] = names;
}
}
},
{
immediate: true,
},
);
async function getDefaultList(id) {
let param = {
id: id,
code: '',
name: '',
parentNode: false
}
let list = resetTreeList(await getDepartmentTrees(param))
selectedNodes.value = list
let nameList = selectedNodes.value.map(item => item.name)
const names = nameList.join(',')
departNames.value = names;
// handleSelectedNodes()
}
function resetTreeList(list) {
const result = list.map(item => {
return {
...item,
...{
key: item.id,
children: resetTreeList(item.children),
title: item.name
}
function resetTreeList(list) {
const result = list.map((item) => {
return {
...item,
...{
key: item.id,
children: resetTreeList(item.children),
title: item.name
}
};
});
return result;
}
function queryCompleted() {
loading.value = false;
}
function departChange(nodes) {
selectedNodes.value = nodes;
valChanged.value = true;
}
function show() {
visible.value = true;
loading.value = true;
if (props.value) {
getDefaultList(props.value);
}
}
function handleSelectedNodes() {
let nameList = selectedNodes.value.map((item) => item.name);
let idList = selectedNodes.value.map((item) => item.id);
const names = nameList.join(',');
const ids = idList.join(',');
emits('update:value', ids);
departNames.value = names;
if (props.sepTextField) {
props.row[camelCaseString(props.sepTextField, true)] = names;
}
}
function deleteItem() {
selectedNodes.value = [];
}
function submit() {
handleSelectedNodes();
close();
}
function close() {
visible.value = false;
selectedNodes.value = [];
emits('close');
}
})
return result
}
function queryCompleted() {
loading.value = false
}
function departChange(nodes) {
selectedNodes.value = nodes
}
function show() {
visible.value = true
loading.value = true
if (props.value) {
getDefaultList(props.value)
}
}
function handleSelectedNodes() {
let nameList = selectedNodes.value.map(item => item.name)
let idList = selectedNodes.value.map(item => item.id)
const names = nameList.join(',')
const ids = idList.join(',')
emits('update:value', ids);
departNames.value = names;
}
function deleteItem() {
selectedNodes.value = []
}
function submit() {
handleSelectedNodes()
close();
}
function close() {
visible.value = false;
selectedNodes.value = []
emits('close');
}
</script>
<style lang="less">
.depart-select-dialog {
.content {
margin-bottom: 0;
}
}
.depart-select-dialog {
.content {
margin-bottom: 0;
}
}
</style>
<style lang="less" scoped>
.choose-dep-box {
display: flex;
height: 95%;
.choose-dep-box {
display: flex;
height: 95%;
.choose-dep {
border: 1px solid #eaeaea;
margin: 8px;
width: 60%;
height: 100%;
position: relative;
overflow: scroll;
.choose-dep {
border: 1px solid #eaeaea;
margin: 8px;
width: 60%;
height: 100%;
position: relative;
overflow: scroll;
.loading-box {
position: absolute;
left: 49%;
top: 49%;
.loading-box {
position: absolute;
left: 49%;
top: 49%;
}
}
.choosen-dep {
border: 1px solid #eaeaea;
margin: 8px;
width: 40%;
height: 100%;
padding: 4px;
.choosen-item {
padding: 2px 4px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.choosen-label {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.close {
visibility: hidden;
}
}
.choosen-item:hover {
background-color: #eaeaea;
.close {
visibility: visible;
}
}
}
}
}
.choosen-dep {
border: 1px solid #eaeaea;
margin: 8px;
width: 40%;
height: 100%;
padding: 4px;
.choosen-item {
padding: 2px 4px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
.choosen-label {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.close {
visibility: hidden
}
}
.choosen-item:hover {
background-color: #eaeaea;
.close {
visibility: visible
}
}
}
}
</style>

View File

@ -9,7 +9,7 @@
{{ t('新增') }}
</a-button>
</div>
<a-table :bordered="showFormBorder" :columns="headColums.length > 0 ? headColums : columns" :data-source="data" :pagination="showPagination ? { defaultPageSize: 10 } : false" :scroll="{ x: 'max-content' }">
<a-table :bordered="showFormBorder" :columns="headColums.length > 0 ? headColums : columns" :data-source="addDataKey(data)" :pagination="showPagination ? { defaultPageSize: 10 } : false" :scroll="{ x: 'max-content' }">
<template #summary>
<a-table-summary-row v-if="columns.some((x) => x.componentProps?.subTotal)">
<a-table-summary-cell v-for="(column, idx) in columns" :key="idx">
@ -66,6 +66,8 @@
:bordered="showComponentBorder"
:index="index"
:mainKey="mainKey"
:key="column.dataIndex + record['_key_']"
:row="record"
v-bind="getComponentsProps(column.componentProps, column.dataIndex, record, index)"
@blur="onFieldBlur(column, record, index)"
@change="onFieldChange(column, record, index)"
@ -225,6 +227,15 @@
});
}
function addDataKey(rows) {
rows.forEach((row) => {
if (!row['_key_']) {
row['_key_'] = Math.random();
}
});
return rows;
}
onMounted(() => {
data.value = cloneDeep(props.value);
@ -261,23 +272,7 @@
watch(
() => props.value,
(v) => {
// console.log('watch props.value', props.value);
data.value = v;
// const rangeComponents = props.columns.filter((column) =>
// column.componentType?.includes('Range'),
// );
// if (rangeComponents.length && formModel) {
// rangeComponents.forEach((item) => {
// data.value.forEach((x) => {
// if (x[(item.dataIndex as string)?.split(',')[0]]) {
// x[item.dataIndex as string] = [
// x[(item.dataIndex as string)?.split(',')[0]],
// x[(item.dataIndex as string)?.split(',')[1]],
// ];
// }
// });
// });
// }
//要保证在预加载之后在emit 不然预加载数据不会绑定到表单数据中
emit('change', unref(data));

View File

@ -27,8 +27,11 @@ export function changeToPinyin(label: string, isUpper?: boolean) {
/* 如果没有下划线,不需要处理
如果有下划线,用下划线切割,第一个下划线左边的全部小写,后面的首字母大写,首字母后面的全部小写 */
export function camelCaseString(string: string) {
export function camelCaseString(string: string, skipNoUnderline = false) {
if (!string) return;
if (skipNoUnderline && string.indexOf('_') < 0) {
return string;
}
const stringLower = string.toLowerCase();
const len = stringLower.length;
let str = '';