mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-04-30 00:42:39 +08:00
248 lines
6.0 KiB
TypeScript
248 lines
6.0 KiB
TypeScript
import type { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/core';
|
||
import { type CascaderOption, defineFormItem, type FormConfig } from '@tmagic/form';
|
||
import { dataSourceTemplateRegExp, getKeysArray, isNumber } from '@tmagic/utils';
|
||
|
||
import BaseFormConfig from './formConfigs/base';
|
||
import HttpFormConfig from './formConfigs/http';
|
||
|
||
const dataSourceFormConfig = defineFormItem({
|
||
type: 'tab',
|
||
items: [
|
||
{
|
||
title: '数据定义',
|
||
items: [
|
||
{
|
||
name: 'fields',
|
||
type: 'data-source-fields',
|
||
defaultValue: () => [],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: '方法定义',
|
||
items: [
|
||
{
|
||
name: 'methods',
|
||
type: 'data-source-methods',
|
||
defaultValue: () => [],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: '事件配置',
|
||
items: [
|
||
{
|
||
name: 'events',
|
||
src: 'datasource',
|
||
type: 'event-select',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: 'mock数据',
|
||
items: [
|
||
{
|
||
name: 'mocks',
|
||
type: 'data-source-mocks',
|
||
defaultValue: () => [],
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: '请求参数裁剪',
|
||
display: (_formState, { model }) => model.type === 'http',
|
||
items: [
|
||
{
|
||
name: 'beforeRequest',
|
||
type: 'vs-code',
|
||
parse: true,
|
||
autosize: { minRows: 10, maxRows: 30 },
|
||
},
|
||
],
|
||
},
|
||
{
|
||
title: '响应数据裁剪',
|
||
display: (_formStat, { model }) => model.type === 'http',
|
||
items: [
|
||
{
|
||
name: 'afterResponse',
|
||
type: 'vs-code',
|
||
parse: true,
|
||
autosize: { minRows: 10, maxRows: 30 },
|
||
},
|
||
],
|
||
},
|
||
],
|
||
});
|
||
|
||
const fillConfig = (config: FormConfig): FormConfig => [...BaseFormConfig(), ...config, dataSourceFormConfig];
|
||
|
||
export const getFormConfig = (type: string, configs: Record<string, FormConfig>): FormConfig => {
|
||
switch (type) {
|
||
case 'base':
|
||
return fillConfig([]);
|
||
case 'http':
|
||
return fillConfig(HttpFormConfig);
|
||
default:
|
||
return fillConfig(configs[type] || []);
|
||
}
|
||
};
|
||
|
||
export const getFormValue = (type: string, values: Partial<DataSourceSchema>): Partial<DataSourceSchema> => {
|
||
if (type !== 'http') {
|
||
return values;
|
||
}
|
||
|
||
return {
|
||
beforeRequest: `(options, context) => {
|
||
/**
|
||
* 用户可以直接编写函数,在原始接口调用之前,会运行该函数,将这个函数的返回值作为该数据源接口的入参
|
||
*
|
||
* options: HttpOptions
|
||
*
|
||
* interface HttpOptions {
|
||
* // 请求链接
|
||
* url: string;
|
||
* // query参数
|
||
* params?: Record<string, string>;
|
||
* // body数据
|
||
* data?: Record<string, any>;
|
||
* // 请求头
|
||
* headers?: Record<string, string>;
|
||
* // 请求方法 GET/POST
|
||
* method?: Method;
|
||
* }
|
||
*
|
||
* context:上下文对象
|
||
*
|
||
* interface Content {
|
||
* app: TMagicApp;
|
||
* dataSource: HttpDataSource;
|
||
* }
|
||
*
|
||
* return: HttpOptions
|
||
*/
|
||
|
||
// 此处的返回值会作为这个接口的入参
|
||
return options;
|
||
}`,
|
||
afterResponse: `(response, context) => {
|
||
/**
|
||
* 用户可以直接编写函数,在原始接口返回之后,会运行该函数,将这个函数的返回值作为该数据源接口的返回
|
||
|
||
* context:上下文对象
|
||
*
|
||
* interface Content {
|
||
* app: TMagicApp;
|
||
* dataSource: HttpDataSource;
|
||
* }
|
||
*
|
||
*/
|
||
|
||
// 此处的返回值会作为这个接口的返回值
|
||
return response;
|
||
}`,
|
||
...values,
|
||
};
|
||
};
|
||
|
||
export const getDisplayField = (dataSources: DataSourceSchema[], key: string) => {
|
||
const displayState: { value: string; type: 'var' | 'text' }[] = [];
|
||
|
||
// 匹配es6字符串模块
|
||
const matches = key.matchAll(dataSourceTemplateRegExp);
|
||
let index = 0;
|
||
for (const match of matches) {
|
||
if (typeof match.index === 'undefined') break;
|
||
|
||
// 字符串常量
|
||
displayState.push({
|
||
type: 'text',
|
||
value: key.substring(index, match.index),
|
||
});
|
||
|
||
let dsText = '';
|
||
let ds: DataSourceSchema | undefined;
|
||
let fields: DataSchema[] | undefined;
|
||
// 将模块解析成数据源对应的值
|
||
getKeysArray(match[1]).forEach((item, index) => {
|
||
if (index === 0) {
|
||
ds = dataSources.find((ds) => ds.id === item);
|
||
dsText += ds?.title || item;
|
||
fields = ds?.fields;
|
||
return;
|
||
}
|
||
|
||
if (isNumber(item)) {
|
||
dsText += `[${item}]`;
|
||
} else {
|
||
const field = fields?.find((field) => field.name === item);
|
||
fields = field?.fields;
|
||
dsText += `.${field?.title || item}`;
|
||
}
|
||
});
|
||
|
||
displayState.push({
|
||
type: 'var',
|
||
value: dsText,
|
||
});
|
||
|
||
index = match.index + match[0].length;
|
||
}
|
||
|
||
if (index < key.length) {
|
||
displayState.push({
|
||
type: 'text',
|
||
value: key.substring(index),
|
||
});
|
||
}
|
||
|
||
return displayState;
|
||
};
|
||
|
||
export const getCascaderOptionsFromFields = (
|
||
fields: DataSchema[] = [],
|
||
dataSourceFieldType: DataSourceFieldType[] = ['any'],
|
||
): CascaderOption[] => {
|
||
const typeSet = new Set(dataSourceFieldType.length ? dataSourceFieldType : ['any']);
|
||
const includesAny = typeSet.has('any');
|
||
|
||
const result: CascaderOption[] = [];
|
||
|
||
for (const field of fields) {
|
||
const fieldType = field.type || 'any';
|
||
const isContainerType = fieldType === 'any' || fieldType === 'array' || fieldType === 'object';
|
||
|
||
const children = isContainerType ? getCascaderOptionsFromFields(field.fields, dataSourceFieldType) : [];
|
||
|
||
const matchesType = includesAny || typeSet.has(fieldType);
|
||
|
||
if (matchesType || (isContainerType && children.length)) {
|
||
result.push({
|
||
label: `${field.title || field.name}(${field.type})`,
|
||
value: field.name,
|
||
children,
|
||
});
|
||
}
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
export const getFieldType = (ds: DataSourceSchema | undefined, fieldNames: string[]) => {
|
||
let fields = ds?.fields;
|
||
let type = '';
|
||
|
||
for (const fieldName of fieldNames) {
|
||
if (!fields?.length) return '';
|
||
|
||
const field = fields.find((f) => f.name === fieldName);
|
||
if (!field) return '';
|
||
|
||
type = field.type || '';
|
||
fields = field.fields;
|
||
}
|
||
|
||
return type;
|
||
};
|