mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-02 22:38:16 +08:00
feat(editor): 查看/编辑时自动切换 sidebar 并定位到对应配置
点击代码块、数据源字段或方法的选择器编辑/查看按钮时,自动切换到对应 sidebar tab,并打开详情中的目标字段或方法配置。
This commit is contained in:
parent
b35132e93e
commit
ffcc734102
@ -107,10 +107,12 @@ const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues
|
||||
|
||||
const notEditable = computed(() => filterFunction(mForm, props.config.notEditable, props));
|
||||
|
||||
const hasCodeBlockSidePanel = computed(() =>
|
||||
const codeBlockSidePanel = computed(() =>
|
||||
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.CODE_BLOCK),
|
||||
);
|
||||
|
||||
const hasCodeBlockSidePanel = computed(() => codeBlockSidePanel.value);
|
||||
|
||||
/**
|
||||
* 根据代码块id获取代码块参数配置
|
||||
* @param codeId 代码块ID
|
||||
@ -191,6 +193,10 @@ const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
||||
};
|
||||
|
||||
const editCode = (id: string) => {
|
||||
const sideBarItem = codeBlockSidePanel.value;
|
||||
if (sideBarItem) {
|
||||
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.CODE_BLOCK);
|
||||
}
|
||||
eventBus?.emit('edit-code', id);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -211,11 +211,25 @@ const onChangeHandler = (v: string[] = []) => {
|
||||
emit('change', v);
|
||||
};
|
||||
|
||||
const hasDataSourceSidePanel = computed(() =>
|
||||
const dataSourceSidePanel = computed(() =>
|
||||
uiService.get('sideBarItems').find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
||||
);
|
||||
|
||||
const hasDataSourceSidePanel = computed(() => dataSourceSidePanel.value);
|
||||
|
||||
const editHandler = (id: string) => {
|
||||
eventBus?.emit('edit-data-source', removeDataSourceFieldPrefix(id));
|
||||
const sideBarItem = dataSourceSidePanel.value;
|
||||
if (sideBarItem) {
|
||||
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
|
||||
}
|
||||
|
||||
const dataSourceId = removeDataSourceFieldPrefix(id);
|
||||
const fieldPath = selectFieldsId.value;
|
||||
|
||||
if (fieldPath.length) {
|
||||
eventBus?.emit('edit-data-source-field', dataSourceId, [...fieldPath]);
|
||||
} else {
|
||||
eventBus?.emit('edit-data-source', dataSourceId);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, Ref, ref } from 'vue';
|
||||
import { computed, type ComputedRef, inject, onMounted, provide, Ref, ref } from 'vue';
|
||||
|
||||
import type { DataSchema } from '@tmagic/core';
|
||||
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||
@ -101,6 +101,16 @@ const newHandler = () => {
|
||||
addDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const editField = (row: Record<string, any>, index: number) => {
|
||||
fieldValues.value = {
|
||||
...row,
|
||||
index,
|
||||
};
|
||||
fieldTitle.value = `编辑${row.title}`;
|
||||
calcBoxPosition();
|
||||
addDialogVisible.value = true;
|
||||
};
|
||||
|
||||
const fieldChange = ({ index, ...value }: Record<string, any>, data: ContainerChangeEventData) => {
|
||||
addDialogVisible.value = false;
|
||||
|
||||
@ -158,13 +168,7 @@ const fieldColumns: ColumnConfig[] = [
|
||||
{
|
||||
text: '编辑',
|
||||
handler: (row: Record<string, any>, index: number) => {
|
||||
fieldValues.value = {
|
||||
...row,
|
||||
index,
|
||||
};
|
||||
fieldTitle.value = `编辑${row.title}`;
|
||||
calcBoxPosition();
|
||||
addDialogVisible.value = true;
|
||||
editField(row, index);
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -354,4 +358,29 @@ const { height: editorHeight } = useEditorContentHeight();
|
||||
|
||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||
|
||||
/**
|
||||
* 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
|
||||
* 当前层消费 path[0],并把剩余路径下发给嵌套字段,实现逐层打开。
|
||||
*/
|
||||
const editingFieldPath = inject<ComputedRef<string[]>>(
|
||||
'editingDataSourceFieldPath',
|
||||
computed(() => []),
|
||||
);
|
||||
|
||||
provide(
|
||||
'editingDataSourceFieldPath',
|
||||
computed(() => editingFieldPath.value.slice(1)),
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
const path = editingFieldPath.value;
|
||||
if (!path.length) return;
|
||||
|
||||
const fields: Record<string, any>[] = props.model[props.name] || [];
|
||||
const index = fields.findIndex((field) => field.name === path[0]);
|
||||
if (index === -1) return;
|
||||
|
||||
editField(fields[index], index);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
></MCascader>
|
||||
|
||||
<TMagicTooltip
|
||||
v-if="model[name] && isCustomMethod && hasDataSourceSidePanel && !isCompare"
|
||||
v-if="model[name] && isCustomMethod && dataSourceSidePanel && !isCompare"
|
||||
:content="notEditable ? '查看' : '编辑'"
|
||||
>
|
||||
<TMagicButton class="m-fields-select-action-button" :size="size" @click="editCodeHandler">
|
||||
@ -75,7 +75,7 @@ const props = withDefaults(defineProps<FieldProps<DataSourceMethodSelectConfig>>
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const hasDataSourceSidePanel = computed(() =>
|
||||
const dataSourceSidePanel = computed(() =>
|
||||
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
||||
);
|
||||
|
||||
@ -208,12 +208,17 @@ const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
||||
};
|
||||
|
||||
const editCodeHandler = () => {
|
||||
const [id] = props.model[props.name];
|
||||
const [id, methodName] = props.model[props.name];
|
||||
|
||||
const dataSource = dataSourceService.getDataSourceById(id);
|
||||
|
||||
if (!dataSource) return;
|
||||
|
||||
eventBus?.emit('edit-data-source', id);
|
||||
const sideBarItem = dataSourceSidePanel.value;
|
||||
if (sideBarItem) {
|
||||
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
|
||||
}
|
||||
|
||||
eventBus?.emit('edit-data-source-method', id, methodName);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, nextTick, ref, useTemplateRef } from 'vue';
|
||||
import { computed, type ComputedRef, inject, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type { CodeBlockContent } from '@tmagic/core';
|
||||
@ -52,6 +52,29 @@ const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>(
|
||||
|
||||
let editIndex = -1;
|
||||
|
||||
const editMethod = (method: CodeBlockContent, index: number) => {
|
||||
let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
|
||||
|
||||
if (method.content) {
|
||||
if (typeof method.content !== 'string') {
|
||||
codeContent = method.content.toString();
|
||||
} else {
|
||||
codeContent = method.content;
|
||||
}
|
||||
}
|
||||
|
||||
codeConfig.value = {
|
||||
...cloneDeep(method),
|
||||
content: codeContent,
|
||||
};
|
||||
|
||||
editIndex = index;
|
||||
|
||||
nextTick(() => {
|
||||
codeBlockEditorRef.value?.show();
|
||||
});
|
||||
};
|
||||
|
||||
const methodColumns: ColumnConfig[] = [
|
||||
{
|
||||
label: '名称',
|
||||
@ -77,26 +100,7 @@ const methodColumns: ColumnConfig[] = [
|
||||
{
|
||||
text: '编辑',
|
||||
handler: (method: CodeBlockContent, index: number) => {
|
||||
let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
|
||||
|
||||
if (method.content) {
|
||||
if (typeof method.content !== 'string') {
|
||||
codeContent = method.content.toString();
|
||||
} else {
|
||||
codeContent = method.content;
|
||||
}
|
||||
}
|
||||
|
||||
codeConfig.value = {
|
||||
...cloneDeep(method),
|
||||
content: codeContent,
|
||||
};
|
||||
|
||||
editIndex = index;
|
||||
|
||||
nextTick(() => {
|
||||
codeBlockEditorRef.value?.show();
|
||||
});
|
||||
editMethod(method, index);
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -158,4 +162,21 @@ const submitCodeHandler = (value: CodeBlockContent, data: ContainerChangeEventDa
|
||||
|
||||
codeBlockEditorRef.value?.hide();
|
||||
};
|
||||
|
||||
/** 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的方法名 */
|
||||
const editingMethodName = inject<ComputedRef<string | undefined>>(
|
||||
'editingDataSourceMethodName',
|
||||
computed(() => ''),
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
const methodName = editingMethodName.value;
|
||||
if (!methodName) return;
|
||||
|
||||
const methods: CodeBlockContent[] = props.model[props.name] || [];
|
||||
const index = methods.findIndex((method) => method.name === methodName);
|
||||
if (index === -1) return;
|
||||
|
||||
editMethod(methods[index], index);
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { inject, nextTick, Ref, ref, watch, watchEffect } from 'vue';
|
||||
import { computed, inject, nextTick, provide, Ref, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
import type { DataSourceSchema } from '@tmagic/core';
|
||||
import { tMagicMessage } from '@tmagic/design';
|
||||
@ -41,6 +41,10 @@ const props = defineProps<{
|
||||
title?: string;
|
||||
values: any;
|
||||
disabled: boolean;
|
||||
/** 打开后需要直接定位并打开的方法名,传入时默认激活「方法定义」tab */
|
||||
editMethodName?: string;
|
||||
/** 打开后需要直接定位并打开的字段路径,传入时默认激活「数据定义」tab */
|
||||
editFieldPath?: string[];
|
||||
}>();
|
||||
|
||||
const boxVisible = defineModel<boolean>('visible', { default: false });
|
||||
@ -62,9 +66,33 @@ const { height: editorHeight } = useEditorContentHeight();
|
||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||
|
||||
/** 供「方法定义」tab 内的字段消费,用于打开数据源详情后自动打开指定方法 */
|
||||
provide(
|
||||
'editingDataSourceMethodName',
|
||||
computed(() => props.editMethodName),
|
||||
);
|
||||
|
||||
/** 供「数据定义」tab 内的字段消费,用于打开数据源详情后自动打开指定字段 */
|
||||
provide(
|
||||
'editingDataSourceFieldPath',
|
||||
computed(() => props.editFieldPath || []),
|
||||
);
|
||||
|
||||
watchEffect(() => {
|
||||
initValues.value = props.values;
|
||||
dataSourceConfig.value = dataSourceService.getFormConfig(initValues.value.type);
|
||||
const config = dataSourceService.getFormConfig(initValues.value.type);
|
||||
|
||||
// 传入方法名/字段路径时,将外层 tab 容器默认激活到对应 tab(status: methods / fields)
|
||||
let activeTab = '';
|
||||
if (props.editMethodName) {
|
||||
activeTab = 'methods';
|
||||
} else if (props.editFieldPath?.length) {
|
||||
activeTab = 'fields';
|
||||
}
|
||||
|
||||
dataSourceConfig.value = activeTab
|
||||
? config.map((item) => ((item as { type?: string }).type === 'tab' ? { ...item, active: activeTab } : item))
|
||||
: config;
|
||||
});
|
||||
|
||||
const submitHandler = (values: any, data: ContainerChangeEventData) => {
|
||||
|
||||
@ -29,6 +29,8 @@
|
||||
:disabled="!editable"
|
||||
:values="dataSourceValues"
|
||||
:title="dialogTitle"
|
||||
:edit-method-name="editMethodName"
|
||||
:edit-field-path="editFieldPath"
|
||||
@submit="submitDataSourceHandler"
|
||||
@close="editDialogCloseHandler"
|
||||
></DataSourceConfigPanel>
|
||||
@ -45,7 +47,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, useTemplateRef, watch } from 'vue';
|
||||
import { computed, inject, ref, useTemplateRef, watch } from 'vue';
|
||||
import { mergeWith } from 'lodash-es';
|
||||
|
||||
import { tMagicMessageBox, TMagicScrollbar } from '@tmagic/design';
|
||||
@ -79,7 +81,16 @@ const { dataSourceService } = useServices();
|
||||
const { editDialog, dataSourceValues, dialogTitle, editable, editHandler, submitDataSourceHandler } =
|
||||
useDataSourceEdit(dataSourceService);
|
||||
|
||||
/** 打开数据源详情时需要直接定位并打开的方法名,为空则正常展示「数据定义」tab */
|
||||
const editMethodName = ref('');
|
||||
|
||||
/** 打开数据源详情时需要直接定位并打开的字段路径,为空则不自动打开字段配置 */
|
||||
const editFieldPath = ref<string[]>([]);
|
||||
|
||||
const editDialogCloseHandler = () => {
|
||||
editMethodName.value = '';
|
||||
editFieldPath.value = [];
|
||||
|
||||
if (dataSourceListRef.value) {
|
||||
for (const [, status] of dataSourceListRef.value.nodeStatusMap.entries()) {
|
||||
status.selected = false;
|
||||
@ -139,6 +150,20 @@ const filterTextChangeHandler = (val: string) => {
|
||||
};
|
||||
|
||||
eventBus?.on('edit-data-source', (id: string) => {
|
||||
editMethodName.value = '';
|
||||
editFieldPath.value = [];
|
||||
editHandler(id);
|
||||
});
|
||||
|
||||
eventBus?.on('edit-data-source-method', (id: string, methodName: string) => {
|
||||
editMethodName.value = methodName;
|
||||
editFieldPath.value = [];
|
||||
editHandler(id);
|
||||
});
|
||||
|
||||
eventBus?.on('edit-data-source-field', (id: string, fieldPath: string[]) => {
|
||||
editMethodName.value = '';
|
||||
editFieldPath.value = fieldPath;
|
||||
editHandler(id);
|
||||
});
|
||||
|
||||
|
||||
@ -1156,6 +1156,8 @@ export type SyncHookPlugin<
|
||||
|
||||
export interface EventBusEvent {
|
||||
'edit-data-source': [id: string];
|
||||
'edit-data-source-method': [id: string, methodName: string];
|
||||
'edit-data-source-field': [id: string, fieldPath: string[]];
|
||||
'remove-data-source': [id: string];
|
||||
'edit-code': [id: string];
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ const dataSourceFormConfig: TabConfig = {
|
||||
items: [
|
||||
{
|
||||
title: '数据定义',
|
||||
status: 'fields',
|
||||
items: [
|
||||
{
|
||||
name: 'fields',
|
||||
@ -20,6 +21,7 @@ const dataSourceFormConfig: TabConfig = {
|
||||
},
|
||||
{
|
||||
title: '方法定义',
|
||||
status: 'methods',
|
||||
items: [
|
||||
{
|
||||
name: 'methods',
|
||||
|
||||
@ -14,7 +14,7 @@ const { messageError } = vi.hoisted(() => ({ messageError: vi.fn() }));
|
||||
|
||||
const dataSourceService = { get: vi.fn() };
|
||||
const propsService = { getDisabledDataSource: vi.fn() };
|
||||
const uiService = { get: vi.fn() };
|
||||
const uiService = { get: vi.fn(), set: vi.fn() };
|
||||
|
||||
vi.mock('@editor/hooks/use-services', () => ({
|
||||
useServices: () => ({ dataSourceService, propsService, uiService }),
|
||||
@ -158,13 +158,25 @@ describe('FieldSelect', () => {
|
||||
expect(wrapper.emitted('change')?.[0]?.[0]).toEqual(['ds1', 'a']);
|
||||
});
|
||||
|
||||
test('editHandler emit edit-data-source 到 eventBus', () => {
|
||||
test('editHandler 无字段时 emit edit-data-source 并切换数据源 tab', async () => {
|
||||
const eventBusEmit = vi.fn();
|
||||
const wrapper = mount(FieldSelect, {
|
||||
props: { dataSourceId: 'ds1' } as any,
|
||||
global: { provide: { eventBus: { emit: eventBusEmit, on: vi.fn() } } },
|
||||
});
|
||||
expect(wrapper).toBeTruthy();
|
||||
await wrapper.find('.m-fields-select-action-button').trigger('click');
|
||||
expect(uiService.set).toHaveBeenCalledWith('sideBarActiveTabName', 'data-source');
|
||||
expect(eventBusEmit).toHaveBeenCalledWith('edit-data-source', 'ds1');
|
||||
});
|
||||
|
||||
test('editHandler 有字段时 emit edit-data-source-field 带字段路径', async () => {
|
||||
const eventBusEmit = vi.fn();
|
||||
const wrapper = mount(FieldSelect, {
|
||||
props: { dataSourceId: 'ds1', modelValue: ['a'] } as any,
|
||||
global: { provide: { eventBus: { emit: eventBusEmit, on: vi.fn() } } },
|
||||
});
|
||||
await wrapper.find('.m-fields-select-action-button').trigger('click');
|
||||
expect(eventBusEmit).toHaveBeenCalledWith('edit-data-source-field', 'ds1', ['a']);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ const dataSourceService = {
|
||||
getFormMethod: vi.fn(() => []),
|
||||
};
|
||||
|
||||
const uiService = { get: vi.fn(() => [{ $key: 'data-source' }]) };
|
||||
const uiService = { get: vi.fn(() => [{ $key: 'data-source' }]), set: vi.fn() };
|
||||
|
||||
vi.mock('@editor/hooks/use-services', () => ({
|
||||
useServices: () => ({ dataSourceService, uiService }),
|
||||
@ -158,7 +158,7 @@ describe('DataSourceMethodSelect', () => {
|
||||
expect(evts).toBeTruthy();
|
||||
});
|
||||
|
||||
test('编辑按钮 emit edit-data-source', async () => {
|
||||
test('编辑按钮 emit edit-data-source-method 并切换到数据源 tab', async () => {
|
||||
dataSourceService.getDataSourceById.mockReturnValue({ id: 'ds1', methods: [{ name: 'doFetch' }] });
|
||||
const eventBus = { emit: vi.fn() };
|
||||
const wrapper = mount(DataSourceMethodSelect, {
|
||||
@ -166,7 +166,8 @@ describe('DataSourceMethodSelect', () => {
|
||||
global: { provide: { eventBus } },
|
||||
});
|
||||
await wrapper.find('button').trigger('click');
|
||||
expect(eventBus.emit).toHaveBeenCalledWith('edit-data-source', 'ds1');
|
||||
expect(uiService.set).toHaveBeenCalledWith('sideBarActiveTabName', 'data-source');
|
||||
expect(eventBus.emit).toHaveBeenCalledWith('edit-data-source-method', 'ds1', 'doFetch');
|
||||
});
|
||||
|
||||
test('编辑按钮: 找不到 dataSource 时不触发', async () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user