diff --git a/packages/core/src/App.ts b/packages/core/src/App.ts index e692fae1..d29cce5f 100644 --- a/packages/core/src/App.ts +++ b/packages/core/src/App.ts @@ -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); diff --git a/packages/data-source/src/DataSourceManager.ts b/packages/data-source/src/DataSourceManager.ts index aea01579..3ac12877 100644 --- a/packages/data-source/src/DataSourceManager.ts +++ b/packages/data-source/src/DataSourceManager.ts @@ -42,14 +42,16 @@ class DataSourceManager extends EventEmitter { public dataSourceMap = new Map(); 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(); } } diff --git a/packages/data-source/src/data-sources/Base.ts b/packages/data-source/src/data-sources/Base.ts index 5e536301..a9ae910f 100644 --- a/packages/data-source/src/data-sources/Base.ts +++ b/packages/data-source/src/data-sources/Base.ts @@ -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 = {}; + /** @tmagic/core 实例 */ public app: AppCore; - protected mockData?: MockSchema; + protected mockData?: Record; - 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(); + 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[]) { - 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) { @@ -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(); } } diff --git a/packages/data-source/src/data-sources/Http.ts b/packages/data-source/src/data-sources/Http.ts index 29426a95..1be8c9b1 100644 --- a/packages/data-source/src/data-sources/Http.ts +++ b/packages/data-source/src/data-sources/Http.ts @@ -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 = {}) { + 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 & { url: string }) { diff --git a/packages/data-source/tests/DataSource.spec.ts b/packages/data-source/tests/DataSource.spec.ts index c852c414..7bcc2cb6 100644 --- a/packages/data-source/tests/DataSource.spec.ts +++ b/packages/data-source/tests/DataSource.spec.ts @@ -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(); diff --git a/packages/editor/src/fields/DataSourceMocks.vue b/packages/editor/src/fields/DataSourceMocks.vue index d9c280ec..4e005a4b 100644 --- a/packages/editor/src/fields/DataSourceMocks.vue +++ b/packages/editor/src/fields/DataSourceMocks.vue @@ -8,7 +8,7 @@ ({ + 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) => { 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, }); }; diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 3c030bb3..4a064c4f 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -201,9 +201,15 @@ export interface DataSchema { } export interface MockSchema { + /** 名称 */ title: string; + /** 详细描述 */ description?: string; + /** 是否启用,用于编辑器以外的runtime */ enable: boolean; + /** 编辑器中使用使用此条数据,仅用于编辑器runtime中 */ + useInEditor: boolean; + /** mock数据 */ data: Record; }