mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-23 07:59:59 +08:00
feat(data-source,editor,schema): 数据源mock新增在编辑器中使用的配置
This commit is contained in:
parent
588ec68b21
commit
83ab94fcad
@ -333,7 +333,7 @@ class App extends EventEmitter implements AppCore {
|
||||
|
||||
if (!dataSource) return;
|
||||
|
||||
const methods = dataSource.getMethods() || [];
|
||||
const methods = dataSource.methods || [];
|
||||
|
||||
const method = methods.find((item) => item.name === methodName);
|
||||
|
||||
|
@ -42,14 +42,16 @@ class DataSourceManager extends EventEmitter {
|
||||
public dataSourceMap = new Map<string, DataSource>();
|
||||
|
||||
public data: DataSourceManagerData = {};
|
||||
public useMock?: boolean = false;
|
||||
|
||||
constructor({ app, useMock }: DataSourceManagerOptions) {
|
||||
super();
|
||||
|
||||
this.app = app;
|
||||
this.useMock = useMock;
|
||||
|
||||
app.dsl?.dataSources?.forEach((config) => {
|
||||
this.addDataSource(config, useMock);
|
||||
this.addDataSource(config);
|
||||
});
|
||||
}
|
||||
|
||||
@ -57,7 +59,7 @@ class DataSourceManager extends EventEmitter {
|
||||
return this.dataSourceMap.get(id);
|
||||
}
|
||||
|
||||
public async addDataSource(config?: DataSourceSchema, useMock?: boolean) {
|
||||
public async addDataSource(config?: DataSourceSchema) {
|
||||
if (!config) return;
|
||||
|
||||
let ds: DataSource;
|
||||
@ -66,7 +68,7 @@ class DataSourceManager extends EventEmitter {
|
||||
app: this.app,
|
||||
schema: config as HttpDataSourceSchema,
|
||||
request: this.app.request,
|
||||
useMock,
|
||||
useMock: this.useMock,
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@ -75,7 +77,7 @@ class DataSourceManager extends EventEmitter {
|
||||
ds = new DataSourceClass({
|
||||
app: this.app,
|
||||
schema: config,
|
||||
useMock,
|
||||
useMock: this.useMock,
|
||||
});
|
||||
}
|
||||
|
||||
@ -90,7 +92,7 @@ class DataSourceManager extends EventEmitter {
|
||||
const beforeInit: ((...args: any[]) => any)[] = [];
|
||||
const afterInit: ((...args: any[]) => any)[] = [];
|
||||
|
||||
ds.getMethods().forEach((method) => {
|
||||
ds.methods.forEach((method) => {
|
||||
if (typeof method.content !== 'function') return;
|
||||
if (method.timing === 'beforeInit') {
|
||||
beforeInit.push(method.content);
|
||||
@ -128,9 +130,10 @@ class DataSourceManager extends EventEmitter {
|
||||
if (!ds) {
|
||||
return;
|
||||
}
|
||||
ds.setFields(schema.fields);
|
||||
ds.updateDefaultData();
|
||||
this.data[ds.id] = ds.data;
|
||||
|
||||
this.removeDataSource(schema.id);
|
||||
|
||||
this.addDataSource(schema);
|
||||
});
|
||||
}
|
||||
|
||||
@ -191,7 +194,7 @@ class DataSourceManager extends EventEmitter {
|
||||
this.dataSourceMap.forEach((ds) => {
|
||||
ds.destroy();
|
||||
});
|
||||
this.dataSourceMap = new Map();
|
||||
this.dataSourceMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
*/
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import type { AppCore, CodeBlockContent, DataSchema, MockSchema } from '@tmagic/schema';
|
||||
import type { AppCore, CodeBlockContent, DataSchema } from '@tmagic/schema';
|
||||
import { getDefaultValueFromFields } from '@tmagic/utils';
|
||||
|
||||
import type { DataSourceOptions } from '@data-source/types';
|
||||
@ -26,50 +26,64 @@ import type { DataSourceOptions } from '@data-source/types';
|
||||
* 静态数据源
|
||||
*/
|
||||
export default class DataSource extends EventEmitter {
|
||||
public type = 'base';
|
||||
|
||||
public id: string;
|
||||
|
||||
public isInit = false;
|
||||
|
||||
public data: Record<string, any> = {};
|
||||
|
||||
/** @tmagic/core 实例 */
|
||||
public app: AppCore;
|
||||
|
||||
protected mockData?: MockSchema;
|
||||
protected mockData?: Record<string | number, any>;
|
||||
|
||||
private fields: DataSchema[] = [];
|
||||
private methods: CodeBlockContent[] = [];
|
||||
#type = 'base';
|
||||
#id: string;
|
||||
|
||||
/** 数据源自定义字段配置 */
|
||||
#fields: DataSchema[] = [];
|
||||
/** 数据源自定义方法配置 */
|
||||
#methods: CodeBlockContent[] = [];
|
||||
|
||||
constructor(options: DataSourceOptions) {
|
||||
super();
|
||||
|
||||
this.app = options.app;
|
||||
this.id = options.schema.id;
|
||||
this.#id = options.schema.id;
|
||||
this.setFields(options.schema.fields);
|
||||
this.setMethods(options.schema.methods || []);
|
||||
|
||||
if (typeof options.useMock === 'boolean' && options.useMock) {
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.enable);
|
||||
const defaultData = this.getDefaultData();
|
||||
|
||||
if (this.app.platform === 'editor') {
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.useInEditor)?.data || defaultData;
|
||||
} else if (typeof options.useMock === 'boolean' && options.useMock) {
|
||||
this.mockData = options.schema.mocks?.find((mock) => mock.enable)?.data;
|
||||
}
|
||||
|
||||
this.updateDefaultData();
|
||||
|
||||
if (this.mockData) {
|
||||
this.setData(this.mockData.data);
|
||||
this.setData(this.mockData || defaultData);
|
||||
}
|
||||
|
||||
public get id() {
|
||||
return this.#id;
|
||||
}
|
||||
|
||||
public get type() {
|
||||
return this.#type;
|
||||
}
|
||||
|
||||
public get fields() {
|
||||
return this.#fields;
|
||||
}
|
||||
|
||||
public get methods() {
|
||||
return this.#methods;
|
||||
}
|
||||
|
||||
public setFields(fields: DataSchema[]) {
|
||||
this.fields = fields;
|
||||
this.#fields = fields;
|
||||
}
|
||||
|
||||
public setMethods(methods: CodeBlockContent[]) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public getMethods() {
|
||||
return this.methods;
|
||||
this.#methods = methods;
|
||||
}
|
||||
|
||||
public setData(data: Record<string, any>) {
|
||||
@ -79,11 +93,7 @@ export default class DataSource extends EventEmitter {
|
||||
}
|
||||
|
||||
public getDefaultData() {
|
||||
return getDefaultValueFromFields(this.fields);
|
||||
}
|
||||
|
||||
public updateDefaultData() {
|
||||
this.setData(this.getDefaultData());
|
||||
return getDefaultValueFromFields(this.#fields);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
@ -92,7 +102,7 @@ export default class DataSource extends EventEmitter {
|
||||
|
||||
public destroy() {
|
||||
this.data = {};
|
||||
this.fields = [];
|
||||
this.#fields = [];
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
@ -67,19 +67,24 @@ const webRequest = async (options: HttpOptions) => {
|
||||
* @description 通过 http 请求获取数据
|
||||
*/
|
||||
export default class HttpDataSource extends DataSource {
|
||||
public type = 'http';
|
||||
|
||||
/** 是否正在发起请求 */
|
||||
public isLoading = false;
|
||||
public error?: {
|
||||
msg?: string;
|
||||
code?: string | number;
|
||||
};
|
||||
public schema: HttpDataSourceSchema;
|
||||
/** 请求配置 */
|
||||
public httpOptions: HttpOptions;
|
||||
|
||||
private fetch?: RequestFunction;
|
||||
private beforeRequest: ((...args: any[]) => any)[] = [];
|
||||
private afterRequest: ((...args: any[]) => any)[] = [];
|
||||
/** 请求函数 */
|
||||
#fetch?: RequestFunction;
|
||||
/** 请求前需要执行的函数队列 */
|
||||
#beforeRequest: ((...args: any[]) => any)[] = [];
|
||||
/** 请求后需要执行的函数队列 */
|
||||
#afterRequest: ((...args: any[]) => any)[] = [];
|
||||
|
||||
#type = 'http';
|
||||
|
||||
constructor(options: HttpDataSourceOptions) {
|
||||
const { options: httpOptions, ...dataSourceOptions } = options.schema;
|
||||
@ -93,22 +98,26 @@ export default class HttpDataSource extends DataSource {
|
||||
this.httpOptions = httpOptions;
|
||||
|
||||
if (typeof options.request === 'function') {
|
||||
this.fetch = options.request;
|
||||
this.#fetch = options.request;
|
||||
} else if (typeof globalThis.fetch === 'function') {
|
||||
this.fetch = webRequest;
|
||||
this.#fetch = webRequest;
|
||||
}
|
||||
|
||||
this.getMethods().forEach((method) => {
|
||||
this.methods.forEach((method) => {
|
||||
if (typeof method.content !== 'function') return;
|
||||
if (method.timing === 'beforeRequest') {
|
||||
this.beforeRequest.push(method.content);
|
||||
this.#beforeRequest.push(method.content);
|
||||
}
|
||||
if (method.timing === 'afterRequest') {
|
||||
this.afterRequest.push(method.content);
|
||||
this.#afterRequest.push(method.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get type() {
|
||||
return this.#type;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
if (this.schema.autoFetch) {
|
||||
await this.request(this.httpOptions);
|
||||
@ -117,20 +126,23 @@ export default class HttpDataSource extends DataSource {
|
||||
super.init();
|
||||
}
|
||||
|
||||
public async request(options: HttpOptions) {
|
||||
public async request(options: Partial<HttpOptions> = {}) {
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
for (const method of this.beforeRequest) {
|
||||
for (const method of this.#beforeRequest) {
|
||||
await method({ options, params: {}, dataSource: this, app: this.app });
|
||||
}
|
||||
|
||||
// 注意:在编辑器中mockData不会为空,至少是默认值,不会发起请求
|
||||
const res = this.mockData
|
||||
? this.mockData.data
|
||||
: await this.fetch?.({
|
||||
? this.mockData
|
||||
: await this.#fetch?.({
|
||||
...this.httpOptions,
|
||||
...options,
|
||||
});
|
||||
|
||||
for (const method of this.afterRequest) {
|
||||
for (const method of this.#afterRequest) {
|
||||
await method({ res, options, params: {}, dataSource: this, app: this.app });
|
||||
}
|
||||
|
||||
@ -149,6 +161,8 @@ export default class HttpDataSource extends DataSource {
|
||||
|
||||
this.emit('error', error);
|
||||
}
|
||||
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
public get(options: Partial<HttpOptions> & { url: string }) {
|
||||
|
@ -9,7 +9,9 @@ describe('DataSource', () => {
|
||||
type: 'base',
|
||||
id: '1',
|
||||
fields: [{ name: 'name' }],
|
||||
methods: [],
|
||||
},
|
||||
app: {},
|
||||
});
|
||||
|
||||
expect(ds).toBeInstanceOf(DataSource);
|
||||
@ -22,7 +24,9 @@ describe('DataSource', () => {
|
||||
type: 'base',
|
||||
id: '1',
|
||||
fields: [{ name: 'name' }],
|
||||
methods: [],
|
||||
},
|
||||
app: {},
|
||||
});
|
||||
|
||||
ds.init();
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
<MFormDrawer
|
||||
ref="addDialog"
|
||||
label-width="80px"
|
||||
label-width="120px"
|
||||
:title="drawerTitle"
|
||||
:config="formConfig"
|
||||
:values="formValues"
|
||||
@ -29,6 +29,7 @@ import type { MockSchema } from '@tmagic/schema';
|
||||
import { MagicTable } from '@tmagic/table';
|
||||
import { getDefaultValueFromFields } from '@tmagic/utils';
|
||||
|
||||
import CodeEditor from '@editor/layouts/CodeEditor.vue';
|
||||
import { Services } from '@editor/type';
|
||||
|
||||
defineOptions({
|
||||
@ -80,6 +81,11 @@ const formConfig: FormConfig = [
|
||||
text: '启用',
|
||||
type: 'switch',
|
||||
},
|
||||
{
|
||||
name: 'useInEditor',
|
||||
text: '编辑器中使用',
|
||||
type: 'switch',
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
text: 'mock数据',
|
||||
@ -112,6 +118,18 @@ const formConfig: FormConfig = [
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
type: 'expand',
|
||||
component: CodeEditor,
|
||||
props: (row: MockSchema) => ({
|
||||
initValues: row.data,
|
||||
language: 'json',
|
||||
height: '150px',
|
||||
options: {
|
||||
readOnly: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: '名称',
|
||||
prop: 'title',
|
||||
@ -132,7 +150,23 @@ const columns = [
|
||||
}),
|
||||
listeners: (row: MockSchema, index: number) => ({
|
||||
'update:modelValue': (v: boolean) => {
|
||||
toggleEnable(row, v, index);
|
||||
toggleValue(row, 'enable', v, index);
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: '编辑器中使用',
|
||||
prop: 'useInEditor',
|
||||
type: 'component',
|
||||
component: TMagicSwitch,
|
||||
props: (row: MockSchema) => ({
|
||||
modelValue: row.useInEditor,
|
||||
activeValue: true,
|
||||
inactiveValue: false,
|
||||
}),
|
||||
listeners: (row: MockSchema, index: number) => ({
|
||||
'update:modelValue': (v: boolean) => {
|
||||
toggleValue(row, 'useInEditor', v, index);
|
||||
},
|
||||
}),
|
||||
},
|
||||
@ -165,8 +199,11 @@ const columns = [
|
||||
];
|
||||
|
||||
const newHandler = () => {
|
||||
const isFirstRow = props.model[props.name].length === 0;
|
||||
formValues.value = {
|
||||
data: getDefaultValueFromFields(props.model.fields || []),
|
||||
useInEditor: isFirstRow,
|
||||
enable: isFirstRow,
|
||||
};
|
||||
drawerTitle.value = '新增Mock';
|
||||
addDialog.value?.show();
|
||||
@ -184,16 +221,16 @@ const formChangeHandler = ({ index, ...value }: Record<string, any>) => {
|
||||
emit('change', props.model[props.name]);
|
||||
};
|
||||
|
||||
const toggleEnable = (row: MockSchema, enable: boolean, index: number) => {
|
||||
if (enable) {
|
||||
const toggleValue = (row: MockSchema, key: 'enable' | 'useInEditor', value: boolean, index: number) => {
|
||||
if (value) {
|
||||
props.model[props.name].forEach((item: MockSchema) => {
|
||||
item.enable = false;
|
||||
item[key] = false;
|
||||
});
|
||||
}
|
||||
|
||||
formChangeHandler({
|
||||
...row,
|
||||
enable,
|
||||
[key]: value,
|
||||
index,
|
||||
});
|
||||
};
|
||||
|
@ -201,9 +201,15 @@ export interface DataSchema {
|
||||
}
|
||||
|
||||
export interface MockSchema {
|
||||
/** 名称 */
|
||||
title: string;
|
||||
/** 详细描述 */
|
||||
description?: string;
|
||||
/** 是否启用,用于编辑器以外的runtime */
|
||||
enable: boolean;
|
||||
/** 编辑器中使用使用此条数据,仅用于编辑器runtime中 */
|
||||
useInEditor: boolean;
|
||||
/** mock数据 */
|
||||
data: Record<string | number, any>;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user