mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-06-17 18:51:33 +08:00
feat(editor): 优化数据源字段选择器交互
This commit is contained in:
parent
77f13fa898
commit
0ffc223459
152
packages/editor/src/fields/DataSourceFieldSelect/FieldSelect.vue
Normal file
152
packages/editor/src/fields/DataSourceFieldSelect/FieldSelect.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div class="m-editor-data-source-field-select">
|
||||
<TMagicSelect
|
||||
:model-value="selectDataSourceId"
|
||||
clearable
|
||||
filterable
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@change="dsChangeHandler"
|
||||
>
|
||||
<component
|
||||
v-for="option in dataSourcesOptions"
|
||||
class="tmagic-design-option"
|
||||
:key="option.value"
|
||||
:is="optionComponent?.component || 'el-option'"
|
||||
v-bind="
|
||||
optionComponent?.props({
|
||||
label: option.text,
|
||||
value: option.value,
|
||||
disabled: option.disabled,
|
||||
}) || {
|
||||
label: option.text,
|
||||
value: option.value,
|
||||
disabled: option.disabled,
|
||||
}
|
||||
"
|
||||
>
|
||||
</component>
|
||||
</TMagicSelect>
|
||||
|
||||
<TMagicCascader
|
||||
:model-value="selectFieldsId"
|
||||
clearable
|
||||
filterable
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:options="fieldsOptions"
|
||||
:props="{
|
||||
checkStrictly,
|
||||
}"
|
||||
@change="fieldChangeHandler"
|
||||
></TMagicCascader>
|
||||
|
||||
<TMagicButton
|
||||
v-if="selectDataSourceId && hasDataSourceSidePanel"
|
||||
class="m-fields-select-action-button"
|
||||
:size="size"
|
||||
@click="editHandler(selectDataSourceId)"
|
||||
><MIcon :icon="!notEditable ? Edit : View"></MIcon
|
||||
></TMagicButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
import { Edit, View } from '@element-plus/icons-vue';
|
||||
|
||||
import { getConfig as getDesignConfig, TMagicButton, TMagicCascader, TMagicSelect } from '@tmagic/design';
|
||||
import { type FilterFunction, filterFunction, type FormState, type SelectOption } from '@tmagic/form';
|
||||
import { DataSourceFieldType } from '@tmagic/schema';
|
||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
||||
|
||||
import MIcon from '@editor/components/Icon.vue';
|
||||
import { type EventBus, type Services, SideItemKey } from '@editor/type';
|
||||
import { getCascaderOptionsFromFields, removeDataSourceFieldPrefix } from '@editor/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
/**
|
||||
* 是否要编译成数据源的data。
|
||||
* key: 不编译,就是要数据源id和field name;
|
||||
* value: 要编译(数据源data[`${filed}`])
|
||||
* */
|
||||
value?: 'key' | 'value';
|
||||
disabled?: boolean;
|
||||
checkStrictly?: boolean;
|
||||
size?: 'large' | 'default' | 'small';
|
||||
dataSourceFieldType?: DataSourceFieldType[];
|
||||
/** 是否可以编辑数据源,disable表示的是是否可以选择数据源 */
|
||||
notEditable?: boolean | FilterFunction;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [v: string[]];
|
||||
}>();
|
||||
|
||||
const modelValue = defineModel<string[] | any>('modelValue', { default: [] });
|
||||
|
||||
const optionComponent = getDesignConfig('components')?.option;
|
||||
|
||||
const services = inject<Services>('services');
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
const eventBus = inject<EventBus>('eventBus');
|
||||
|
||||
const dataSources = computed(() => services?.dataSourceService.get('dataSources') || []);
|
||||
|
||||
const valueIsKey = computed(() => props.value === 'key');
|
||||
const notEditable = computed(() => filterFunction(mForm, props.notEditable, props));
|
||||
|
||||
const dataSourcesOptions = computed<SelectOption[]>(() =>
|
||||
dataSources.value.map((ds) => ({
|
||||
text: ds.title || ds.id,
|
||||
value: valueIsKey.value ? ds.id : `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${ds.id}`,
|
||||
})),
|
||||
);
|
||||
|
||||
const selectDataSourceId = ref('');
|
||||
|
||||
const selectFieldsId = ref<string[]>([]);
|
||||
|
||||
watch(
|
||||
modelValue,
|
||||
(value) => {
|
||||
if (Array.isArray(value)) {
|
||||
const [dsId, ...fields] = value;
|
||||
selectDataSourceId.value = dsId;
|
||||
selectFieldsId.value = fields;
|
||||
} else {
|
||||
selectDataSourceId.value = '';
|
||||
selectFieldsId.value = [];
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
const fieldsOptions = computed(() => {
|
||||
const ds = dataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(selectDataSourceId.value));
|
||||
|
||||
if (!ds) return [];
|
||||
|
||||
return getCascaderOptionsFromFields(ds.fields, props.dataSourceFieldType);
|
||||
});
|
||||
|
||||
const dsChangeHandler = (v: string) => {
|
||||
modelValue.value = [v];
|
||||
emit('change', modelValue.value);
|
||||
};
|
||||
|
||||
const fieldChangeHandler = (v: string[]) => {
|
||||
modelValue.value = [selectDataSourceId.value, ...v];
|
||||
emit('change', modelValue.value);
|
||||
};
|
||||
|
||||
const hasDataSourceSidePanel = computed(() =>
|
||||
(services?.uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
||||
);
|
||||
|
||||
const editHandler = (id: string) => {
|
||||
eventBus?.emit('edit-data-source', removeDataSourceFieldPrefix(id));
|
||||
};
|
||||
</script>
|
@ -1,8 +1,20 @@
|
||||
<template>
|
||||
<div class="m-fields-data-source-field-select">
|
||||
<FieldSelect
|
||||
v-if="showDataSourceFieldSelect || !config.fieldConfig"
|
||||
:model-value="model[name]"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:value="config.value"
|
||||
:checkStrictly="checkStrictly"
|
||||
:dataSourceFieldType="config.dataSourceFieldType"
|
||||
@change="onChangeHandler"
|
||||
></FieldSelect>
|
||||
|
||||
<component
|
||||
v-else
|
||||
:is="tagName"
|
||||
:config="showDataSourceFieldSelect || !config.fieldConfig ? cascaderConfig : config.fieldConfig"
|
||||
:config="config.fieldConfig"
|
||||
:model="model"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
@ -14,14 +26,6 @@
|
||||
@change="onChangeHandler"
|
||||
></component>
|
||||
|
||||
<TMagicButton
|
||||
v-if="(showDataSourceFieldSelect || !config.fieldConfig) && selectedDataSourceId && hasDataSourceSidePanel"
|
||||
class="m-fields-select-action-button"
|
||||
:size="size"
|
||||
@click="editHandler(selectedDataSourceId)"
|
||||
><MIcon :icon="!notEditable ? Edit : View"></MIcon
|
||||
></TMagicButton>
|
||||
|
||||
<TMagicButton
|
||||
v-if="config.fieldConfig"
|
||||
style="margin-left: 5px"
|
||||
@ -35,66 +39,29 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
||||
import { Coin, Edit, View } from '@element-plus/icons-vue';
|
||||
import { Coin } from '@element-plus/icons-vue';
|
||||
|
||||
import { TMagicButton } from '@tmagic/design';
|
||||
import type { CascaderConfig, FieldProps, FormState } from '@tmagic/form';
|
||||
import { filterFunction, MCascader } from '@tmagic/form';
|
||||
import { TMagicButton, tMagicMessage } from '@tmagic/design';
|
||||
import type { FieldProps, FormState } from '@tmagic/form';
|
||||
import { DataSchema } from '@tmagic/schema';
|
||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
||||
|
||||
import MIcon from '@editor/components/Icon.vue';
|
||||
import type { DataSourceFieldSelectConfig, EventBus, Services } from '@editor/type';
|
||||
import { SideItemKey } from '@editor/type';
|
||||
import { getCascaderOptionsFromFields } from '@editor/utils';
|
||||
import type { DataSourceFieldSelectConfig, Services } from '@editor/type';
|
||||
import { removeDataSourceFieldPrefix } from '@editor/utils';
|
||||
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'MFieldsDataSourceFieldSelect',
|
||||
});
|
||||
|
||||
const services = inject<Services>('services');
|
||||
const eventBus = inject<EventBus>('eventBus');
|
||||
const emit = defineEmits(['change']);
|
||||
|
||||
const props = withDefaults(defineProps<FieldProps<DataSourceFieldSelectConfig>>(), {
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const notEditable = computed(() => filterFunction(mForm, props.config.notEditable, props));
|
||||
|
||||
const hasDataSourceSidePanel = computed(() =>
|
||||
(services?.uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
||||
);
|
||||
|
||||
const selectedDataSourceId = computed(() => {
|
||||
const value = props.model[props.name];
|
||||
if (!Array.isArray(value) || !value.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return value[0].replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, '');
|
||||
});
|
||||
|
||||
const dataSources = computed(() => services?.dataSourceService.get('dataSources'));
|
||||
|
||||
const cascaderConfig = computed<CascaderConfig>(() => {
|
||||
const valueIsKey = props.config.value === 'key';
|
||||
|
||||
return {
|
||||
type: 'cascader',
|
||||
checkStrictly: props.config.checkStrictly ?? !valueIsKey,
|
||||
popperClass: 'm-editor-data-source-field-select-popper',
|
||||
options: () => {
|
||||
const options =
|
||||
dataSources.value?.map((ds) => ({
|
||||
label: ds.title || ds.id,
|
||||
value: valueIsKey ? ds.id : `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${ds.id}`,
|
||||
children: getCascaderOptionsFromFields(ds.fields, props.config.dataSourceFieldType),
|
||||
})) || [];
|
||||
return options.filter((option) => option.children.length);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const showDataSourceFieldSelect = ref(false);
|
||||
|
||||
watch(
|
||||
@ -115,8 +82,11 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
const services = inject<Services>('services');
|
||||
const mForm = inject<FormState | undefined>('mForm');
|
||||
|
||||
const dataSources = computed(() => services?.dataSourceService.get('dataSources') || []);
|
||||
|
||||
const type = computed((): string => {
|
||||
let type = props.config.fieldConfig?.type;
|
||||
if (typeof type === 'function') {
|
||||
@ -130,20 +100,60 @@ const type = computed((): string => {
|
||||
});
|
||||
|
||||
const tagName = computed(() => {
|
||||
if (showDataSourceFieldSelect.value || !props.config.fieldConfig) {
|
||||
return MCascader;
|
||||
}
|
||||
|
||||
const component = resolveComponent(`m-${props.config.items ? 'form' : 'fields'}-${type.value}`);
|
||||
if (typeof component !== 'string') return component;
|
||||
return 'm-fields-text';
|
||||
});
|
||||
|
||||
const onChangeHandler = (value: any) => {
|
||||
emit('change', value);
|
||||
};
|
||||
const checkStrictly = computed(() => {
|
||||
let value: boolean | undefined;
|
||||
|
||||
const editHandler = (id: string) => {
|
||||
eventBus?.emit('edit-data-source', id);
|
||||
if (typeof props.config.checkStrictly !== 'function') {
|
||||
value = props.config.checkStrictly;
|
||||
} else {
|
||||
const dsId = removeDataSourceFieldPrefix(props.model[0]);
|
||||
const dataSource = dataSources.value.find((ds) => ds.id === dsId);
|
||||
|
||||
value = props.config.checkStrictly(mForm, {
|
||||
values: mForm?.initValues || {},
|
||||
model: props.model,
|
||||
parent: mForm?.parentValues || {},
|
||||
formValue: mForm?.values || props.model,
|
||||
prop: props.prop,
|
||||
config: props.config,
|
||||
dataSource,
|
||||
});
|
||||
}
|
||||
|
||||
return value ?? props.config.value === 'key';
|
||||
});
|
||||
|
||||
const onChangeHandler = (value: string[]) => {
|
||||
const [dsId, ...keys] = value;
|
||||
const dataSource = dataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(dsId));
|
||||
|
||||
let fields = dataSource?.fields || [];
|
||||
let field: DataSchema | undefined;
|
||||
(keys || []).forEach((key) => {
|
||||
field = fields.find((f) => f.name === key);
|
||||
fields = field?.fields || [];
|
||||
});
|
||||
|
||||
const dataSourceFieldType = props.config.dataSourceFieldType || ['any'];
|
||||
if (!dataSourceFieldType.length) {
|
||||
dataSourceFieldType.push('any');
|
||||
}
|
||||
|
||||
if (
|
||||
!dsId ||
|
||||
!keys.length ||
|
||||
(field?.type &&
|
||||
(field.type === 'any' || dataSourceFieldType.includes('any') || dataSourceFieldType.includes(field.type)))
|
||||
) {
|
||||
emit('change', value);
|
||||
} else {
|
||||
tMagicMessage.error(`请选择类型为${dataSourceFieldType.join('或')}的字段`);
|
||||
emit('change', [dsId]);
|
||||
}
|
||||
};
|
||||
</script>
|
@ -22,7 +22,7 @@ import CodeLink from './fields/CodeLink.vue';
|
||||
import CodeSelect from './fields/CodeSelect.vue';
|
||||
import CodeSelectCol from './fields/CodeSelectCol.vue';
|
||||
import DataSourceFields from './fields/DataSourceFields.vue';
|
||||
import DataSourceFieldSelect from './fields/DataSourceFieldSelect.vue';
|
||||
import DataSourceFieldSelect from './fields/DataSourceFieldSelect/Index.vue';
|
||||
import DataSourceInput from './fields/DataSourceInput.vue';
|
||||
import DataSourceMethods from './fields/DataSourceMethods.vue';
|
||||
import DataSourceMethodSelect from './fields/DataSourceMethodSelect.vue';
|
||||
@ -68,7 +68,7 @@ export { default as DataSourceMethods } from './fields/DataSourceMethods.vue';
|
||||
export { default as DataSourceInput } from './fields/DataSourceInput.vue';
|
||||
export { default as DataSourceSelect } from './fields/DataSourceSelect.vue';
|
||||
export { default as DataSourceMethodSelect } from './fields/DataSourceMethodSelect.vue';
|
||||
export { default as DataSourceFieldSelect } from './fields/DataSourceFieldSelect.vue';
|
||||
export { default as DataSourceFieldSelect } from './fields/DataSourceFieldSelect/Index.vue';
|
||||
export { default as EventSelect } from './fields/EventSelect.vue';
|
||||
export { default as KeyValue } from './fields/KeyValue.vue';
|
||||
export { default as CodeBlockList } from './layouts/sidebar/code-block/CodeBlockList.vue';
|
||||
|
16
packages/editor/src/theme/data-source-field-select.scss
Normal file
16
packages/editor/src/theme/data-source-field-select.scss
Normal file
@ -0,0 +1,16 @@
|
||||
.m-fields-data-source-field-select {
|
||||
width: 100%;
|
||||
.m-editor-data-source-field-select {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.tmagic-design-select {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tmagic-design-cascader {
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,3 +25,4 @@
|
||||
@import "./floating-box.scss";
|
||||
@import "./page-fragment-select.scss";
|
||||
@import "./data-source-field.scss";
|
||||
@import "./data-source-field-select.scss";
|
||||
|
@ -20,11 +20,12 @@ import type { Component } from 'vue';
|
||||
import type EventEmitter from 'events';
|
||||
import type { PascalCasedProperties } from 'type-fest';
|
||||
|
||||
import type { ChildConfig, ColumnConfig, FilterFunction, FormConfig, FormItem, Input } from '@tmagic/form';
|
||||
import type { ChildConfig, ColumnConfig, FilterFunction, FormConfig, FormItem, FormState, Input } from '@tmagic/form';
|
||||
import type {
|
||||
CodeBlockContent,
|
||||
CodeBlockDSL,
|
||||
DataSourceFieldType,
|
||||
DataSourceSchema,
|
||||
Id,
|
||||
MApp,
|
||||
MContainer,
|
||||
@ -646,13 +647,27 @@ export interface DataSourceMethodSelectConfig extends FormItem {
|
||||
|
||||
export interface DataSourceFieldSelectConfig extends FormItem {
|
||||
type: 'data-source-field-select';
|
||||
/** 是否要编译成数据源的data。
|
||||
/**
|
||||
* 是否要编译成数据源的data。
|
||||
* key: 不编译,就是要数据源id和field name;
|
||||
* value: 要编译(数据源data[`${filed}`])
|
||||
* */
|
||||
value?: 'key' | 'value';
|
||||
/** 是否严格的遵守父子节点不互相关联 */
|
||||
checkStrictly?: boolean;
|
||||
checkStrictly?:
|
||||
| boolean
|
||||
| ((
|
||||
mForm: FormState | undefined,
|
||||
data: {
|
||||
model: Record<any, any>;
|
||||
values: Record<any, any>;
|
||||
parent?: Record<any, any>;
|
||||
formValue: Record<any, any>;
|
||||
prop: string;
|
||||
config: DataSourceFieldSelectConfig;
|
||||
dataSource?: DataSourceSchema;
|
||||
},
|
||||
) => boolean);
|
||||
dataSourceFieldType?: DataSourceFieldType[];
|
||||
fieldConfig?: ChildConfig;
|
||||
/** 是否可以编辑数据源,disable表示的是是否可以选择数据源 */
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { CascaderOption, FormConfig, FormState } from '@tmagic/form';
|
||||
import { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/schema';
|
||||
import { isNumber } from '@tmagic/utils';
|
||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, isNumber } from '@tmagic/utils';
|
||||
|
||||
import BaseFormConfig from './formConfigs/base';
|
||||
import HttpFormConfig from './formConfigs/http';
|
||||
@ -218,7 +218,7 @@ export const getCascaderOptionsFromFields = (
|
||||
const children = getCascaderOptionsFromFields(field.fields, dataSourceFieldType);
|
||||
|
||||
const item = {
|
||||
label: field.title || field.name,
|
||||
label: `${field.title || field.name}(${field.type})`,
|
||||
value: field.name,
|
||||
children,
|
||||
};
|
||||
@ -241,3 +241,6 @@ export const getCascaderOptionsFromFields = (
|
||||
});
|
||||
return child;
|
||||
};
|
||||
|
||||
export const removeDataSourceFieldPrefix = (id?: string) =>
|
||||
id?.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, '') || '';
|
||||
|
@ -23,17 +23,15 @@ export default [
|
||||
text: '数据源数据',
|
||||
value: 'value',
|
||||
dataSourceFieldType: ['array'],
|
||||
checkStrictly: false,
|
||||
checkStrictly: true,
|
||||
type: 'data-source-field-select',
|
||||
onChange: (vm: any, v: string[] = [], { model }: any) => {
|
||||
if (Array.isArray(v)) {
|
||||
if (Array.isArray(v) && v.length > 1) {
|
||||
const [dsId, ...keys] = v;
|
||||
model.dsField = [dsId.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, ''), ...keys];
|
||||
} else {
|
||||
model.dsField = [];
|
||||
}
|
||||
|
||||
return v;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -23,10 +23,10 @@ export default [
|
||||
text: '数据源数据',
|
||||
value: 'value',
|
||||
dataSourceFieldType: ['array'],
|
||||
checkStrictly: false,
|
||||
checkStrictly: true,
|
||||
type: 'data-source-field-select',
|
||||
onChange: (vm: any, v: string[] = [], { model }: any) => {
|
||||
if (Array.isArray(v)) {
|
||||
if (Array.isArray(v) && v.length > 1) {
|
||||
const [dsId, ...keys] = v;
|
||||
model.dsField = [dsId.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, ''), ...keys];
|
||||
} else {
|
||||
|
@ -23,10 +23,10 @@ export default [
|
||||
text: '数据源数据',
|
||||
value: 'value',
|
||||
dataSourceFieldType: ['array'],
|
||||
checkStrictly: false,
|
||||
checkStrictly: true,
|
||||
type: 'data-source-field-select',
|
||||
onChange: (vm: any, v: string[] = [], { model }: any) => {
|
||||
if (Array.isArray(v)) {
|
||||
if (Array.isArray(v) && v.length > 1) {
|
||||
const [dsId, ...keys] = v;
|
||||
model.dsField = [dsId.replace(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, ''), ...keys];
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user