feat: 明细表中的组织架构选择器支持将文本表示存储在独立字段,以改善显示性能
This commit is contained in:
@ -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
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@ -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: '',
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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 = '';
|
||||
|
||||
Reference in New Issue
Block a user