feat(data-source,editor,schema): 数据源mock新增在编辑器中使用的配置

This commit is contained in:
roymondchen 2023-10-17 17:05:36 +08:00
parent 588ec68b21
commit 83ab94fcad
7 changed files with 132 additions and 58 deletions

View File

@ -333,7 +333,7 @@ class App extends EventEmitter implements AppCore {
if (!dataSource) return; if (!dataSource) return;
const methods = dataSource.getMethods() || []; const methods = dataSource.methods || [];
const method = methods.find((item) => item.name === methodName); const method = methods.find((item) => item.name === methodName);

View File

@ -42,14 +42,16 @@ class DataSourceManager extends EventEmitter {
public dataSourceMap = new Map<string, DataSource>(); public dataSourceMap = new Map<string, DataSource>();
public data: DataSourceManagerData = {}; public data: DataSourceManagerData = {};
public useMock?: boolean = false;
constructor({ app, useMock }: DataSourceManagerOptions) { constructor({ app, useMock }: DataSourceManagerOptions) {
super(); super();
this.app = app; this.app = app;
this.useMock = useMock;
app.dsl?.dataSources?.forEach((config) => { 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); return this.dataSourceMap.get(id);
} }
public async addDataSource(config?: DataSourceSchema, useMock?: boolean) { public async addDataSource(config?: DataSourceSchema) {
if (!config) return; if (!config) return;
let ds: DataSource; let ds: DataSource;
@ -66,7 +68,7 @@ class DataSourceManager extends EventEmitter {
app: this.app, app: this.app,
schema: config as HttpDataSourceSchema, schema: config as HttpDataSourceSchema,
request: this.app.request, request: this.app.request,
useMock, useMock: this.useMock,
}); });
} else { } else {
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
@ -75,7 +77,7 @@ class DataSourceManager extends EventEmitter {
ds = new DataSourceClass({ ds = new DataSourceClass({
app: this.app, app: this.app,
schema: config, schema: config,
useMock, useMock: this.useMock,
}); });
} }
@ -90,7 +92,7 @@ class DataSourceManager extends EventEmitter {
const beforeInit: ((...args: any[]) => any)[] = []; const beforeInit: ((...args: any[]) => any)[] = [];
const afterInit: ((...args: any[]) => any)[] = []; const afterInit: ((...args: any[]) => any)[] = [];
ds.getMethods().forEach((method) => { ds.methods.forEach((method) => {
if (typeof method.content !== 'function') return; if (typeof method.content !== 'function') return;
if (method.timing === 'beforeInit') { if (method.timing === 'beforeInit') {
beforeInit.push(method.content); beforeInit.push(method.content);
@ -128,9 +130,10 @@ class DataSourceManager extends EventEmitter {
if (!ds) { if (!ds) {
return; return;
} }
ds.setFields(schema.fields);
ds.updateDefaultData(); this.removeDataSource(schema.id);
this.data[ds.id] = ds.data;
this.addDataSource(schema);
}); });
} }
@ -191,7 +194,7 @@ class DataSourceManager extends EventEmitter {
this.dataSourceMap.forEach((ds) => { this.dataSourceMap.forEach((ds) => {
ds.destroy(); ds.destroy();
}); });
this.dataSourceMap = new Map(); this.dataSourceMap.clear();
} }
} }

View File

@ -17,7 +17,7 @@
*/ */
import EventEmitter from 'events'; 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 { getDefaultValueFromFields } from '@tmagic/utils';
import type { DataSourceOptions } from '@data-source/types'; import type { DataSourceOptions } from '@data-source/types';
@ -26,50 +26,64 @@ import type { DataSourceOptions } from '@data-source/types';
* *
*/ */
export default class DataSource extends EventEmitter { export default class DataSource extends EventEmitter {
public type = 'base';
public id: string;
public isInit = false; public isInit = false;
public data: Record<string, any> = {}; public data: Record<string, any> = {};
/** @tmagic/core 实例 */
public app: AppCore; public app: AppCore;
protected mockData?: MockSchema; protected mockData?: Record<string | number, any>;
private fields: DataSchema[] = []; #type = 'base';
private methods: CodeBlockContent[] = []; #id: string;
/** 数据源自定义字段配置 */
#fields: DataSchema[] = [];
/** 数据源自定义方法配置 */
#methods: CodeBlockContent[] = [];
constructor(options: DataSourceOptions) { constructor(options: DataSourceOptions) {
super(); super();
this.app = options.app; this.app = options.app;
this.id = options.schema.id; this.#id = options.schema.id;
this.setFields(options.schema.fields); this.setFields(options.schema.fields);
this.setMethods(options.schema.methods || []); this.setMethods(options.schema.methods || []);
if (typeof options.useMock === 'boolean' && options.useMock) { const defaultData = this.getDefaultData();
this.mockData = options.schema.mocks?.find((mock) => mock.enable);
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(); this.setData(this.mockData || defaultData);
if (this.mockData) {
this.setData(this.mockData.data);
} }
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[]) { public setFields(fields: DataSchema[]) {
this.fields = fields; this.#fields = fields;
} }
public setMethods(methods: CodeBlockContent[]) { public setMethods(methods: CodeBlockContent[]) {
this.methods = methods; this.#methods = methods;
}
public getMethods() {
return this.methods;
} }
public setData(data: Record<string, any>) { public setData(data: Record<string, any>) {
@ -79,11 +93,7 @@ export default class DataSource extends EventEmitter {
} }
public getDefaultData() { public getDefaultData() {
return getDefaultValueFromFields(this.fields); return getDefaultValueFromFields(this.#fields);
}
public updateDefaultData() {
this.setData(this.getDefaultData());
} }
public async init() { public async init() {
@ -92,7 +102,7 @@ export default class DataSource extends EventEmitter {
public destroy() { public destroy() {
this.data = {}; this.data = {};
this.fields = []; this.#fields = [];
this.removeAllListeners(); this.removeAllListeners();
} }
} }

View File

@ -67,19 +67,24 @@ const webRequest = async (options: HttpOptions) => {
* @description http * @description http
*/ */
export default class HttpDataSource extends DataSource { export default class HttpDataSource extends DataSource {
public type = 'http'; /** 是否正在发起请求 */
public isLoading = false; public isLoading = false;
public error?: { public error?: {
msg?: string; msg?: string;
code?: string | number; code?: string | number;
}; };
public schema: HttpDataSourceSchema; public schema: HttpDataSourceSchema;
/** 请求配置 */
public httpOptions: HttpOptions; public httpOptions: HttpOptions;
private fetch?: RequestFunction; /** 请求函数 */
private beforeRequest: ((...args: any[]) => any)[] = []; #fetch?: RequestFunction;
private afterRequest: ((...args: any[]) => any)[] = []; /** 请求前需要执行的函数队列 */
#beforeRequest: ((...args: any[]) => any)[] = [];
/** 请求后需要执行的函数队列 */
#afterRequest: ((...args: any[]) => any)[] = [];
#type = 'http';
constructor(options: HttpDataSourceOptions) { constructor(options: HttpDataSourceOptions) {
const { options: httpOptions, ...dataSourceOptions } = options.schema; const { options: httpOptions, ...dataSourceOptions } = options.schema;
@ -93,22 +98,26 @@ export default class HttpDataSource extends DataSource {
this.httpOptions = httpOptions; this.httpOptions = httpOptions;
if (typeof options.request === 'function') { if (typeof options.request === 'function') {
this.fetch = options.request; this.#fetch = options.request;
} else if (typeof globalThis.fetch === 'function') { } 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 (typeof method.content !== 'function') return;
if (method.timing === 'beforeRequest') { if (method.timing === 'beforeRequest') {
this.beforeRequest.push(method.content); this.#beforeRequest.push(method.content);
} }
if (method.timing === 'afterRequest') { if (method.timing === 'afterRequest') {
this.afterRequest.push(method.content); this.#afterRequest.push(method.content);
} }
}); });
} }
public get type() {
return this.#type;
}
public async init() { public async init() {
if (this.schema.autoFetch) { if (this.schema.autoFetch) {
await this.request(this.httpOptions); await this.request(this.httpOptions);
@ -117,20 +126,23 @@ export default class HttpDataSource extends DataSource {
super.init(); super.init();
} }
public async request(options: HttpOptions) { public async request(options: Partial<HttpOptions> = {}) {
this.isLoading = true;
try { try {
for (const method of this.beforeRequest) { for (const method of this.#beforeRequest) {
await method({ options, params: {}, dataSource: this, app: this.app }); await method({ options, params: {}, dataSource: this, app: this.app });
} }
// 注意在编辑器中mockData不会为空至少是默认值不会发起请求
const res = this.mockData const res = this.mockData
? this.mockData.data ? this.mockData
: await this.fetch?.({ : await this.#fetch?.({
...this.httpOptions, ...this.httpOptions,
...options, ...options,
}); });
for (const method of this.afterRequest) { for (const method of this.#afterRequest) {
await method({ res, options, params: {}, dataSource: this, app: this.app }); 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.emit('error', error);
} }
this.isLoading = false;
} }
public get(options: Partial<HttpOptions> & { url: string }) { public get(options: Partial<HttpOptions> & { url: string }) {

View File

@ -9,7 +9,9 @@ describe('DataSource', () => {
type: 'base', type: 'base',
id: '1', id: '1',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [],
}, },
app: {},
}); });
expect(ds).toBeInstanceOf(DataSource); expect(ds).toBeInstanceOf(DataSource);
@ -22,7 +24,9 @@ describe('DataSource', () => {
type: 'base', type: 'base',
id: '1', id: '1',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [],
}, },
app: {},
}); });
ds.init(); ds.init();

View File

@ -8,7 +8,7 @@
<MFormDrawer <MFormDrawer
ref="addDialog" ref="addDialog"
label-width="80px" label-width="120px"
:title="drawerTitle" :title="drawerTitle"
:config="formConfig" :config="formConfig"
:values="formValues" :values="formValues"
@ -29,6 +29,7 @@ import type { MockSchema } from '@tmagic/schema';
import { MagicTable } from '@tmagic/table'; import { MagicTable } from '@tmagic/table';
import { getDefaultValueFromFields } from '@tmagic/utils'; import { getDefaultValueFromFields } from '@tmagic/utils';
import CodeEditor from '@editor/layouts/CodeEditor.vue';
import { Services } from '@editor/type'; import { Services } from '@editor/type';
defineOptions({ defineOptions({
@ -80,6 +81,11 @@ const formConfig: FormConfig = [
text: '启用', text: '启用',
type: 'switch', type: 'switch',
}, },
{
name: 'useInEditor',
text: '编辑器中使用',
type: 'switch',
},
{ {
name: 'data', name: 'data',
text: 'mock数据', text: 'mock数据',
@ -112,6 +118,18 @@ const formConfig: FormConfig = [
]; ];
const columns = [ const columns = [
{
type: 'expand',
component: CodeEditor,
props: (row: MockSchema) => ({
initValues: row.data,
language: 'json',
height: '150px',
options: {
readOnly: true,
},
}),
},
{ {
label: '名称', label: '名称',
prop: 'title', prop: 'title',
@ -132,7 +150,23 @@ const columns = [
}), }),
listeners: (row: MockSchema, index: number) => ({ listeners: (row: MockSchema, index: number) => ({
'update:modelValue': (v: boolean) => { '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 newHandler = () => {
const isFirstRow = props.model[props.name].length === 0;
formValues.value = { formValues.value = {
data: getDefaultValueFromFields(props.model.fields || []), data: getDefaultValueFromFields(props.model.fields || []),
useInEditor: isFirstRow,
enable: isFirstRow,
}; };
drawerTitle.value = '新增Mock'; drawerTitle.value = '新增Mock';
addDialog.value?.show(); addDialog.value?.show();
@ -184,16 +221,16 @@ const formChangeHandler = ({ index, ...value }: Record<string, any>) => {
emit('change', props.model[props.name]); emit('change', props.model[props.name]);
}; };
const toggleEnable = (row: MockSchema, enable: boolean, index: number) => { const toggleValue = (row: MockSchema, key: 'enable' | 'useInEditor', value: boolean, index: number) => {
if (enable) { if (value) {
props.model[props.name].forEach((item: MockSchema) => { props.model[props.name].forEach((item: MockSchema) => {
item.enable = false; item[key] = false;
}); });
} }
formChangeHandler({ formChangeHandler({
...row, ...row,
enable, [key]: value,
index, index,
}); });
}; };

View File

@ -201,9 +201,15 @@ export interface DataSchema {
} }
export interface MockSchema { export interface MockSchema {
/** 名称 */
title: string; title: string;
/** 详细描述 */
description?: string; description?: string;
/** 是否启用用于编辑器以外的runtime */
enable: boolean; enable: boolean;
/** 编辑器中使用使用此条数据仅用于编辑器runtime中 */
useInEditor: boolean;
/** mock数据 */
data: Record<string | number, any>; data: Record<string | number, any>;
} }