diff --git a/packages/common/composable/http/index.js b/packages/common/composable/http/index.js deleted file mode 100644 index 89adfa8fd4..0000000000 --- a/packages/common/composable/http/index.js +++ /dev/null @@ -1,72 +0,0 @@ -import { defineService, META_SERVICE } from '@opentiny/tiny-engine-meta-register' -import axios from 'axios' - -let http = null - -const createInterceptorHandler = - (http) => - ({ data, type }) => { - if (typeof data === 'function') { - http.interceptors[type].use(data) - - return - } - - if (Array.isArray(data)) { - data.forEach((item) => { - if (!item) return - - if (Array.isArray(item)) { - http.interceptors[type].use(...item) - - return - } - - if (typeof item === 'function') { - http.interceptors[type].use(item) - } - }) - } - } - -export default defineService({ - id: META_SERVICE.Http, - type: 'MetaService', - options: { - axiosConfig: { - // axios 配置 - baseURL: '', - withCredentials: false, // 跨域请求时是否需要使用凭证 - headers: {} // 请求头 - }, - interceptors: { - // 拦截器 - request: [], // 支持配置多个请求拦截器,先注册后执行 - response: [] // 支持配置多个响应拦截器,先注册先执行 - } - }, - init: ({ options = {} }) => { - const { axiosConfig = {}, interceptors = {} } = options - const { request = [], response = [] } = interceptors - - http = axios.create(axiosConfig) - const addInterceptors = createInterceptorHandler(http) - addInterceptors({ data: request, type: 'request' }) - addInterceptors({ data: response, type: 'response' }) - }, - apis: () => ({ - getHttp: () => http, - get: (...args) => http?.get(...args), - post: (...args) => http?.post(...args), - request: (...args) => http?.request(...args), - put: (...args) => http?.put(...args), - delete: (...args) => http?.delete(...args), - stream: (config) => { - const streamConfig = { - responseType: 'stream', - ...config - } - return http?.request(streamConfig) - } - }) -}) diff --git a/packages/common/composable/http/index.ts b/packages/common/composable/http/index.ts new file mode 100644 index 0000000000..88d965a0d1 --- /dev/null +++ b/packages/common/composable/http/index.ts @@ -0,0 +1,113 @@ +import { defineService, META_SERVICE } from '@opentiny/tiny-engine-meta-register' +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type CreateAxiosDefaults } from 'axios' + +// 请求拦截器函数类型 +type RequestInterceptorFunction = + | ((config: AxiosRequestConfig) => AxiosRequestConfig | Promise) + | [(config: AxiosRequestConfig) => AxiosRequestConfig | Promise, (error: any) => any] + +// 响应拦截器函数类型 +type ResponseInterceptorFunction = + | ((response: AxiosResponse) => AxiosResponse | Promise) + | [(response: AxiosResponse) => AxiosResponse | Promise, (error: any) => any] + +// 请求拦截器配置类型(支持单个函数、数组、嵌套数组) +type RequestInterceptorConfig = RequestInterceptorFunction | (RequestInterceptorFunction | undefined)[] + +// 响应拦截器配置类型(支持单个函数、数组、嵌套数组) +type ResponseInterceptorConfig = ResponseInterceptorFunction | (ResponseInterceptorFunction | undefined)[] + +// 拦截器配置接口 +interface InterceptorsConfig { + request?: RequestInterceptorConfig + response?: ResponseInterceptorConfig +} + +// HTTP 服务选项接口 +interface HttpServiceOptions { + axiosConfig?: CreateAxiosDefaults + interceptors?: InterceptorsConfig +} + +let http: AxiosInstance | null = null + +/** + * 创建拦截器处理器 + * @param http - axios 实例 + * @returns 拦截器添加函数 + */ +const createInterceptorHandler = + (http: AxiosInstance) => + ({ data, type }: { data: any; type: 'request' | 'response' }): void => { + if (typeof data === 'function') { + http.interceptors[type].use(data as any) + + return + } + + if (Array.isArray(data)) { + data.forEach((item) => { + if (!item) return + + if (Array.isArray(item)) { + http.interceptors[type].use(...(item as any)) + + return + } + + if (typeof item === 'function') { + http.interceptors[type].use(item as any) + } + }) + } + } + +export default defineService({ + id: META_SERVICE.Http, + type: 'MetaService', + initialState: {}, + options: { + axiosConfig: { + // axios 配置 + baseURL: '', + withCredentials: false, // 跨域请求时是否需要使用凭证 + headers: {} // 请求头 + }, + interceptors: { + // 拦截器 + request: [], // 支持配置多个请求拦截器,先注册后执行 + response: [] // 支持配置多个响应拦截器,先注册先执行 + } + } as HttpServiceOptions, + init: ({ options = {} }: { options?: HttpServiceOptions }) => { + const { axiosConfig = {}, interceptors = {} } = options + const { request = [], response = [] } = interceptors + + http = axios.create(axiosConfig) + const addInterceptors = createInterceptorHandler(http) + addInterceptors({ data: request, type: 'request' }) + addInterceptors({ data: response, type: 'response' }) + }, + apis: () => ({ + /** 获取 axios 实例 */ + getHttp: (): AxiosInstance | null => http, + /** GET 请求 */ + get: (...args: any[]) => (http?.get as any)?.(...args), + /** POST 请求 */ + post: (...args: any[]) => (http?.post as any)?.(...args), + /** 通用请求方法 */ + request: (...args: any[]) => (http?.request as any)?.(...args), + /** PUT 请求 */ + put: (...args: any[]) => (http?.put as any)?.(...args), + /** DELETE 请求 */ + delete: (...args: any[]) => (http?.delete as any)?.(...args), + /** 流式请求 */ + stream: (config: AxiosRequestConfig): Promise> | undefined => { + const streamConfig: AxiosRequestConfig = { + responseType: 'stream', + ...config + } + return http?.request(streamConfig) + } + }) +}) diff --git a/packages/common/i18n/index.js b/packages/common/i18n/index.ts similarity index 100% rename from packages/common/i18n/index.js rename to packages/common/i18n/index.ts diff --git a/packages/common/js/app.js b/packages/common/js/app.ts similarity index 100% rename from packages/common/js/app.js rename to packages/common/js/app.ts diff --git a/packages/common/js/canvas.js b/packages/common/js/canvas.ts similarity index 79% rename from packages/common/js/canvas.js rename to packages/common/js/canvas.ts index 9214d8504f..4fb1ddd19f 100644 --- a/packages/common/js/canvas.js +++ b/packages/common/js/canvas.ts @@ -13,7 +13,19 @@ import { PAGE_STATUS } from './constants' import { useResource, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' -export const getCanvasStatus = (data) => { +// 占用者信息接口 +interface Occupier { + id: number + username: string +} + +// 画布状态返回值接口 +interface CanvasStatus { + state: string + data: Occupier | undefined +} + +export const getCanvasStatus = (data: Occupier | undefined): CanvasStatus => { const globalState = getMetaApi(META_SERVICE.GlobalService).getState() const isDemo = useResource().appSchemaState.isDemo let state = '' diff --git a/packages/common/js/comment.js b/packages/common/js/comment.ts similarity index 87% rename from packages/common/js/comment.js rename to packages/common/js/comment.ts index 335d8b640b..edba16d60f 100644 --- a/packages/common/js/comment.js +++ b/packages/common/js/comment.ts @@ -10,7 +10,7 @@ * */ -export const getCommentByKey = (key) => ({ +export const getCommentByKey = (key: string): { start: string; end: string } => ({ start: `start-${key} 设计器生成的代码,为了避免出现问题,请勿修改`, end: `end-${key}` }) diff --git a/packages/common/js/config-files/eslint-rule.js b/packages/common/js/config-files/eslint-rule.ts similarity index 94% rename from packages/common/js/config-files/eslint-rule.js rename to packages/common/js/config-files/eslint-rule.ts index ad64125826..07c2ff8e83 100644 --- a/packages/common/js/config-files/eslint-rule.js +++ b/packages/common/js/config-files/eslint-rule.ts @@ -1,4 +1,5 @@ -import eslintRecommended from '@eslint/js/src/configs/eslint-recommended.js' +import eslintRecommended from '@eslint/js/src/configs/eslint-recommended' + export default { ...eslintRecommended.rules, 'no-console': 'error', diff --git a/packages/common/js/config-files/prettierrc.js b/packages/common/js/config-files/prettierrc.ts similarity index 100% rename from packages/common/js/config-files/prettierrc.js rename to packages/common/js/config-files/prettierrc.ts diff --git a/packages/common/js/constants.js b/packages/common/js/constants.ts similarity index 100% rename from packages/common/js/constants.js rename to packages/common/js/constants.ts diff --git a/packages/common/js/css.js b/packages/common/js/css.ts similarity index 79% rename from packages/common/js/css.js rename to packages/common/js/css.ts index 3dfd04f35f..c8e0035e48 100644 --- a/packages/common/js/css.js +++ b/packages/common/js/css.ts @@ -11,6 +11,7 @@ */ import * as cssTree from 'css-tree' +import type { StyleSheet, Rule } from 'css-tree' import { utils } from '@opentiny/tiny-engine-utils' const { hyphenate } = utils @@ -22,13 +23,13 @@ const { hyphenate } = utils * @param {string} styleStr css 字符串 * @returns object { [string]: string } */ -export const getCssObjectFromStyleStr = (styleStr) => { - const ast = cssTree.parse(styleStr) - const cssObject = {} +export const getCssObjectFromStyleStr = (styleStr: string): Record => { + const ast = cssTree.parse(styleStr) as StyleSheet + const cssObject: Record = {} ast.children - .filter(({ type }) => type === 'Rule') - .forEach((item) => { + .filter((node): node is Rule => node.type === 'Rule') + .forEach((item: Rule) => { const matchCode = cssTree.generate(item).match(/^(.+){(.+)}$/) if (!matchCode) { @@ -47,7 +48,7 @@ export const styleStrAddRoot = (str = '') => { return `:root { ${str}\n}` } -export const obj2StyleStr = (obj = {}, addRoot = true) => { +export const obj2StyleStr = (obj: Record = {}, addRoot = true) => { const list = Object.entries(obj).map(([key, value]) => (value ? `${hyphenate(key)}: ${value};` : '')) return addRoot ? styleStrAddRoot(list.join('\n ')) : ` { \n ${list.join('\n ')} \n}` diff --git a/packages/common/js/environments.js b/packages/common/js/environments.ts similarity index 93% rename from packages/common/js/environments.js rename to packages/common/js/environments.ts index 9f5d82ac2b..dc769913d0 100644 --- a/packages/common/js/environments.js +++ b/packages/common/js/environments.ts @@ -20,7 +20,7 @@ export const VITE_CDN_TYPE = import.meta.env.VITE_CDN_TYPE export const isMock = VITE_API_MOCK === 'mock' -export const isVsCodeEnv = window.vscodeBridge +export const isVsCodeEnv = (window as any).vscodeBridge as boolean export const isDevelopEnv = MODE?.includes('dev') diff --git a/packages/common/js/example.js b/packages/common/js/example.ts similarity index 94% rename from packages/common/js/example.js rename to packages/common/js/example.ts index 3ceeef4543..29caa6ad01 100644 --- a/packages/common/js/example.js +++ b/packages/common/js/example.ts @@ -93,7 +93,7 @@ const exampleMap = { ] ` } -export const getExample = (name) => { +export const getExample = (name: string): string => { const resetName = `${name || ''}`.toLocaleLowerCase() - return exampleMap[resetName] + return exampleMap[resetName as keyof typeof exampleMap] || '' } diff --git a/packages/common/js/http.js b/packages/common/js/http.js deleted file mode 100644 index c733345fe8..0000000000 --- a/packages/common/js/http.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2023 - present TinyEngine Authors. - * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. - * - * Use of this source code is governed by an MIT-style license. - * - * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, - * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR - * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. - * - */ - -import { isVsCodeEnv } from './environments' -import { generateRouter, generatePage } from './vscodeGenerateFile' -import { usePage, useNotify, useBreadcrumb, useMessage } from '@opentiny/tiny-engine-meta-register' -import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' - -/** - * 异常情况埋点上传 - * @param { json } params {"event_type": design_error,"url": "elit in reprehenderit enim incididunt" } - * @returns { Promise } - */ -export const requestEvent = (url, params) => { - if (!url) { - return - } - - return getMetaApi(META_SERVICE.Http) - .post(url, params) - .catch(() => {}) -} - -/** - * 页面更新 - * @param { string } id 页面ID - * @param { json } params 页面信息 - * @returns { Promise } - * - */ -export const handlePageUpdate = (updateParams) => { - const { id, params, routerChange = false, isCurEditPage = true, isUpdateTree = true } = updateParams - - return getMetaApi(META_SERVICE.Http) - .post(`/app-center/api/pages/update/${id}`, params) - .then((res) => { - if (isVsCodeEnv) { - generatePage({ - id, - name: params.name, - page_content: params.page_content - }) - - if (routerChange) { - generateRouter({ - id, - componentsTree: params - }) - } - } - - if (isUpdateTree) { - useNotify({ message: '保存成功!', type: 'success' }) - } - - // 发布 Schema 变动通知 - useMessage().publish({ - topic: 'pageOrBlockInit', - data: params.page_content - }) - - if (isCurEditPage) { - const { setBreadcrumbPage } = useBreadcrumb() - setBreadcrumbPage([params.name]) - } - - return res - }) - .catch((err) => { - useNotify({ title: '保存失败', message: `${err?.message || ''}`, type: 'error' }) - }) - .finally(() => { - const { pageSettingState } = usePage() - // 更新页面管理的列表,如果不存在,说明还没有打开过页面管理面板 - if (isUpdateTree) { - pageSettingState.updateTreeData?.() - } - pageSettingState.isNew = false - }) -} diff --git a/packages/common/js/http.ts b/packages/common/js/http.ts new file mode 100644 index 0000000000..e5ad9c2212 --- /dev/null +++ b/packages/common/js/http.ts @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { isVsCodeEnv } from './environments' +import { generateRouter, generatePage } from './vscodeGenerateFile' +import { usePage, useNotify, useBreadcrumb, useMessage } from '@opentiny/tiny-engine-meta-register' +import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' + +// ============ 类型定义 ============ + +/** + * 埋点事件参数接口 + */ +interface EventParams { + event_type: string // 事件类型,如 'design_error', 'design_JSError' 等 + url: string // 当前页面URL + unit?: string // URL参数信息 + content?: string // 事件详细内容 + [key: string]: any // 支持其他自定义字段 +} + +/** + * 页面内容接口 + */ +interface PageContent { + fileName?: string // 文件名 + lifeCycles?: any // 生命周期 + [key: string]: any // 页面 schema 的其他属性 +} + +/** + * 页面参数接口 + */ +interface PageParams { + name?: string // 页面名称 + page_content?: PageContent // 页面内容 + route?: string // 路由信息 + message?: string // 更新消息 + [key: string]: any // 其他页面属性 +} + +/** + * 页面更新参数接口 + */ +interface UpdateParams { + id: number | string // 页面ID + params: PageParams // 页面参数 + routerChange?: boolean // 路由是否改变,默认 false + isCurEditPage?: boolean // 是否是当前编辑页面,默认 true + isUpdateTree?: boolean // 是否更新树结构,默认 true +} + +// ============ 函数实现 ============ + +/** + * 异常情况埋点上传 + * @param { string } url 埋点上传地址 + * @param { EventParams } params 埋点事件参数 {"event_type": "design_error","url": "elit in reprehenderit enim incididunt" } + * @returns { Promise | undefined } 返回 Promise 或 undefined + */ +export const requestEvent = (url: string, params: EventParams): Promise | undefined => { + if (!url) { + return + } + + return getMetaApi(META_SERVICE.Http) + .post(url, params) + .catch(() => {}) +} + +/** + * 页面更新 + * @param { UpdateParams } updateParams 页面更新参数,包含 id、params、routerChange、isCurEditPage、isUpdateTree + * @returns { Promise } + * + */ +export const handlePageUpdate = (updateParams: UpdateParams): Promise => { + const { id, params, routerChange = false, isCurEditPage = true, isUpdateTree = true } = updateParams + + return ( + getMetaApi(META_SERVICE.Http) + .post(`/app-center/api/pages/update/${id}`, params) + // TODO: 优化返回类型 + .then((res: any) => { + if (isVsCodeEnv) { + generatePage({ + id: Number(id), + name: params.name || '', + page_content: params.page_content + }) + + if (routerChange) { + generateRouter({ + pageId: String(id), + componentsTree: params + }) + } + } + + if (isUpdateTree) { + useNotify({ message: '保存成功!', type: 'success' }) + } + + // 发布 Schema 变动通知 + useMessage().publish({ + topic: 'pageOrBlockInit', + data: params.page_content + }) + + if (isCurEditPage) { + const { setBreadcrumbPage } = useBreadcrumb() + setBreadcrumbPage([params.name || '']) + } + + return res + }) + .catch((err: any) => { + useNotify({ title: '保存失败', message: `${err?.message || ''}`, type: 'error' }) + }) + .finally(() => { + const { pageSettingState } = usePage() + // 更新页面管理的列表,如果不存在,说明还没有打开过页面管理面板 + if (isUpdateTree) { + pageSettingState.updateTreeData?.() + } + pageSettingState.isNew = false + }) + ) +} diff --git a/packages/common/js/i18n.js b/packages/common/js/i18n.ts similarity index 66% rename from packages/common/js/i18n.js rename to packages/common/js/i18n.ts index cd7163024c..3abad639a4 100644 --- a/packages/common/js/i18n.js +++ b/packages/common/js/i18n.ts @@ -15,11 +15,20 @@ import i18n, { defineCustomI18n } from '@opentiny/tiny-engine-i18n-host' import tinyLocale from '@opentiny/vue-locale' import { i18nKeyMaps } from './constants' +// 类型定义 +type LocaleKeyMap = keyof typeof i18nKeyMaps +type LocaleMessages = Record + +interface CreateI18nParams { + locale: string + messages: Record +} + // 此处处理TinyVue组件库的国际化zhCN --> zh_CN -const customCreateI18n = ({ locale, messages }) => { - const newMessages = {} +const customCreateI18n = ({ locale, messages }: CreateI18nParams) => { + const newMessages: Record = {} Object.keys(messages).forEach((key) => { - const lang = i18nKeyMaps[key] + const lang = i18nKeyMaps[key as LocaleKeyMap] newMessages[lang] = messages[key] }) @@ -30,7 +39,9 @@ const customCreateI18n = ({ locale, messages }) => { }) } -const customI18n = tinyLocale.initI18n({ +// 初始化 TinyVue 组件库的 i18n 实例 +// 注意:@opentiny/vue-locale 可能缺少完整的类型定义,此处暂时使用 any 类型 +const customI18n: any = tinyLocale.initI18n({ i18n: { locale: i18nKeyMaps.zhCN }, createI18n: customCreateI18n, messages: {} @@ -39,6 +50,9 @@ const customI18n = tinyLocale.initI18n({ // 合并组件库的i18n配置 defineCustomI18n(customI18n) +// 导出类型 +export type { LocaleKeyMap, LocaleMessages, CreateI18nParams } + export { I18nInjectionKey, i18nKeyMaps } // i18n对象可以多处使用。模板中直接使用$t,setup环境或普通环境中可以引入后使用i18n.global.t diff --git a/packages/common/js/importMap.js b/packages/common/js/importMap.ts similarity index 100% rename from packages/common/js/importMap.js rename to packages/common/js/importMap.ts diff --git a/packages/common/js/linter.js b/packages/common/js/linter.ts similarity index 58% rename from packages/common/js/linter.js rename to packages/common/js/linter.ts index edfb3f1a61..7b8465e101 100644 --- a/packages/common/js/linter.js +++ b/packages/common/js/linter.ts @@ -11,9 +11,44 @@ */ import eslintWorkerUrl from './worker-files/eslint.worker?worker&url' +import type * as monaco from 'monaco-editor' -export const initLinter = (editor, monacoInstance, state) => { - let workerUrl = new URL(eslintWorkerUrl, import.meta.url) +// Linter 状态接口 +interface LinterState { + hasError: boolean + [key: string]: any +} + +// ESLint 检查结果标记接口 +interface ESLintMarker { + severity: monaco.MarkerSeverity | 'Error' | 'Warning' | 'Info' | 'Hint' + message: string + startLineNumber: number + startColumn: number + endLineNumber: number + endColumn: number + source?: string + code?: string +} + +// Worker 响应消息接口 +interface WorkerResponseMessage { + markers: ESLintMarker[] + version: number +} + +// Worker 请求消息接口 +interface WorkerRequestMessage { + code: string + version: number +} + +export const initLinter = ( + editor: monaco.editor.IStandaloneCodeEditor, + monacoInstance: typeof monaco, + state: LinterState +): Worker => { + let workerUrl: URL | string = new URL(eslintWorkerUrl, import.meta.url) // 线上环境,存在 worker 资源跨域的情况 if (workerUrl.origin !== location.origin) { @@ -26,7 +61,7 @@ export const initLinter = (editor, monacoInstance, state) => { const worker = new Worker(workerUrl, { type: 'module' }) // 监听 ESLint web worker 的返回 - worker.onmessage = function (event) { + worker.onmessage = function (event: MessageEvent) { const { markers, version } = event.data const model = editor.getModel() @@ -34,16 +69,16 @@ export const initLinter = (editor, monacoInstance, state) => { // 判断当前 model 的 versionId 与请求时是否一致 if (model && model.getVersionId() === version) { - monacoInstance.editor.setModelMarkers(model, 'ESLint', markers) + monacoInstance.editor.setModelMarkers(model, 'ESLint', markers as monaco.editor.IMarkerData[]) } } return worker } -let timer = null +let timer: ReturnType | null = null -export const lint = (model, worker) => { +export const lint = (model: monaco.editor.ITextModel, worker: Worker): void => { if (timer) { clearTimeout(timer) } @@ -55,6 +90,6 @@ export const lint = (model, worker) => { code: model.getValue(), // 发起 ESLint 静态检查时,携带 versionId version: model.getVersionId() - }) + } as WorkerRequestMessage) }, 500) } diff --git a/packages/common/js/monitor.js b/packages/common/js/monitor.ts similarity index 90% rename from packages/common/js/monitor.js rename to packages/common/js/monitor.ts index 7f3152588e..8e9efca459 100644 --- a/packages/common/js/monitor.js +++ b/packages/common/js/monitor.ts @@ -10,7 +10,7 @@ * */ -import { requestEvent } from './http.js' +import { requestEvent } from './http' let monitorUrl = '' @@ -24,10 +24,10 @@ let monitorUrl = '' */ const getUrlUnit = () => { const urlUnit = window.location?.search?.substring(1)?.split('&') - const unit = {} + const unit: Record = {} if (urlUnit.length) { urlUnit.forEach((item) => { - let unitItem = item.split('=') + const unitItem = item.split('=') unit[unitItem[0]] = unitItem[1] }) } @@ -36,7 +36,7 @@ const getUrlUnit = () => { } const globalMonitoring = () => { - window.onerror = function (errorMessage, scriptURI, lineNo, columnNo, error) { + window.onerror = function (errorMessage, scriptURI, _lineNo, columnNo, error) { requestEvent(monitorUrl, { event_type: 'design_JSError', url: window.location.href, @@ -59,7 +59,7 @@ const promiseMonitoring = () => { event.preventDefault() let message let matchResult = '' - let reason = event.reason + const reason = event.reason if (typeof reason === 'string') { message = reason } else if (typeof reason === 'object') { @@ -97,7 +97,7 @@ export const iframeMonitoring = () => { return false } - window.frames[0].onerror = function (errorMessage, scriptURI, lineNo, columnNo, error) { + window.frames[0].onerror = function (errorMessage, scriptURI, _lineNo, columnNo, error) { requestEvent(monitorUrl, { event_type: 'design_iframeError', url: window.location.href, @@ -112,7 +112,7 @@ export const iframeMonitoring = () => { } } -export const initMonitor = (url) => { +export const initMonitor = (url: string) => { monitorUrl = url globalMonitoring() promiseMonitoring() diff --git a/packages/common/js/preview.js b/packages/common/js/preview.ts similarity index 82% rename from packages/common/js/preview.js rename to packages/common/js/preview.ts index 52f842c405..9be6251801 100644 --- a/packages/common/js/preview.js +++ b/packages/common/js/preview.ts @@ -27,14 +27,44 @@ import { isDevelopEnv } from './environments' const { deepClone } = utils +// 消息事件数据接口 +interface PreviewMessageEventData { + source?: 'preview' | 'designer' + event?: string + type?: string + data?: any +} + +// 预览参数接口 +interface PreviewParams { + previewType?: string + history?: string + [key: string]: any +} + +interface PackageDep { + name: string + package: string + version: string + script: string + css?: string +} +// 脚本依赖项接口 +interface ScriptDep { + [key: string]: PackageDep +} + // 保存预览窗口引用 -let previewWindow = null +let previewWindow: Window | null = null -const getScriptAndStyleDeps = () => { +const getScriptAndStyleDeps = (): { + scripts: ScriptDep + styles: string[] +} => { const { scripts, styles } = useMaterial().getCanvasDeps() const utilsDeps = useResource().getUtilsDeps() - const scriptsDeps = [...scripts, ...utilsDeps].reduce((res, item) => { + const scriptsDeps = [...scripts, ...utilsDeps].reduce((res, item: PackageDep) => { res[item.package] = res[item.package] || item.script return res @@ -47,7 +77,12 @@ const getScriptAndStyleDeps = () => { } } -const getSchemaParams = async () => { +const getSchemaParams = async (): Promise<{ + currentPage: any + ancestors: any[] + scripts: Record + styles: string[] +}> => { const { isBlock, getPageSchema, getCurrentPage, getSchema } = useCanvas() const isBlockPreview = isBlock() const { scripts, styles } = getScriptAndStyleDeps() @@ -88,14 +123,14 @@ const getSchemaParams = async () => { } // 当 schema 变化时发送更新 -const sendSchemaUpdate = (data) => { - previewWindow.postMessage( +const sendSchemaUpdate = (data: Awaited>) => { + previewWindow!.postMessage( { source: 'designer', type: 'schema', data }, - previewWindow.origin || window.location.origin + previewWindow!.origin || window.location.origin ) } @@ -163,7 +198,7 @@ export const setupSchemaChangeListener = () => { // 监听来自预览页面的消息 const setupMessageListener = () => { - window.addEventListener('message', async (event) => { + window.addEventListener('message', async (event: MessageEvent) => { const parsedOrigin = new URL(event.origin) const parsedHost = new URL(window.location.href) // 确保消息来源安全 @@ -171,7 +206,7 @@ const setupMessageListener = () => { const { event: eventType, source } = event.data || {} // 通过 heartbeat 消息来重新建立连接,避免刷新页面后 previewWindow 为 null if (source === 'preview' && eventType === 'connect' && !previewWindow) { - previewWindow = event.source + previewWindow = event.source as Window setupSchemaChangeListener() } @@ -197,9 +232,12 @@ const setupMessageListener = () => { // 初始化消息监听 setupMessageListener() -const handleHistoryPreview = (params, url) => { - let historyPreviewWindow = null - const handlePreviewReady = (event) => { +const handleHistoryPreview = ( + params: PreviewParams & Partial>>, + url: string +) => { + let historyPreviewWindow: Window | null = null + const handlePreviewReady = (event: MessageEvent): void => { if (event.origin === window.location.origin || event.origin.includes(window.location.hostname)) { const { event: eventType, source } = event.data || {} if (source === 'preview' && eventType === 'onMounted' && historyPreviewWindow) { @@ -230,7 +268,7 @@ const handleHistoryPreview = (params, url) => { historyPreviewWindow = window.open(url, '_blank') } -const getQueryParams = (params = {}, isHistory = false) => { +const getQueryParams = (params: PreviewParams = {}, isHistory: boolean = false): string => { const paramsMap = new URLSearchParams(location.search) const tenant = paramsMap.get('tenant') || '' const pageId = paramsMap.get('pageid') @@ -260,7 +298,7 @@ const getQueryParams = (params = {}, isHistory = false) => { return query } -const open = (params = {}, isHistory = false) => { +const open = (params: PreviewParams = {}, isHistory: boolean = false) => { const href = window.location.href.split('?')[0] || './' const { scripts, styles } = getScriptAndStyleDeps() const query = getQueryParams(params, isHistory) @@ -300,6 +338,6 @@ const open = (params = {}, isHistory = false) => { setupSchemaChangeListener() } -export const previewPage = (params = {}, isHistory = false) => { +export const previewPage = (params: PreviewParams = {}, isHistory: boolean = false) => { open(params, isHistory) } diff --git a/packages/common/js/verification.js b/packages/common/js/verification.ts similarity index 74% rename from packages/common/js/verification.js rename to packages/common/js/verification.ts index e217f3d6bd..32264ac2be 100644 --- a/packages/common/js/verification.js +++ b/packages/common/js/verification.ts @@ -12,19 +12,19 @@ export const REGEXP_EVENT_NAME = /^[a-z]+([A-Z][a-z]*)*$/ -export const verifyEventName = (name) => REGEXP_EVENT_NAME.test(name) +export const verifyEventName = (name: string) => REGEXP_EVENT_NAME.test(name) export const REGEXP_BLOCK_NAME = /^([A-Z][a-z0-9]*){2,}$/ -export const verifyBlockName = (string) => REGEXP_BLOCK_NAME.test(string) +export const verifyBlockName = (string: string) => REGEXP_BLOCK_NAME.test(string) export const REGEXP_BLOCK_ID = /^[A-Za-z]+$/ -export const verifyBlockId = (string) => REGEXP_BLOCK_ID.test(string) +export const verifyBlockId = (string: string) => REGEXP_BLOCK_ID.test(string) export const REGEXP_BLOCK_PATH = /^[\w-][/\w-]*?[\w-]*?$/ -export const verifyBlockPath = (string) => !string || REGEXP_BLOCK_PATH.test(string) +export const verifyBlockPath = (string: string) => !string || REGEXP_BLOCK_PATH.test(string) export const REGEXP_GROUP_NAME = /^[\u4e00-\u9fa5a-zA-Z0-9_-]+$/ @@ -42,6 +42,6 @@ export const REGEXP_JS_VAR = /^[a-zA-Z_]\w*$/ export const REGEXP_JS_VAR_SYMBOL = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/ -export const verifyJsVarName = (name) => REGEXP_JS_VAR.test(name) +export const verifyJsVarName = (name: string) => REGEXP_JS_VAR.test(name) -export const verifyJsVarSymbolName = (name) => REGEXP_JS_VAR_SYMBOL.test(name) +export const verifyJsVarSymbolName = (name: string) => REGEXP_JS_VAR_SYMBOL.test(name) diff --git a/packages/common/js/vscodeGenerateFile.js b/packages/common/js/vscodeGenerateFile.js deleted file mode 100644 index 357f6effc9..0000000000 --- a/packages/common/js/vscodeGenerateFile.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2023 - present TinyEngine Authors. - * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. - * - * Use of this source code is governed by an MIT-style license. - * - * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, - * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR - * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. - * - */ - -import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' - -/** - * vscode生成路由文件 - * - * - * @param { json } params - { - pageId:"123", // 当前页面ID - componentsTree:{} // 整个应用的路由对象 - } - * @returns { string } - */ - -const generateRouter = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateRouter', params) - -/** - * vscode生成本地国际化词条 - * - * @param { json } params - { - - key:'lowcode.preview' // 词条的唯一key值 - contents: { - en_US: "preview", // 英文 - zh_CN: "预览" // 中文 - } - } - * @returns { string } - */ - -const generateI18n = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateI18n', params) - -/** - * vscode生成区块 - * - * @param { json } params - { - - schema: '', // 区块的schema - blockPath: ''// 区块的分类ID,或者说传保存路径 -} - * @returns { string } - */ - -const generateBlock = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateBlock', params) - -/** - * vscode生成页面 - * - * @param { json } params - { - id: 2645, // 页面ID - name: 'xh-test', // 页面名称 - page_content:{} //页面的schema - } - * @returns { string } - */ -const generatePage = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generatePage', params) - -/** - * vscode生成数据源 - * - * @param { json } params - { - list:[], // 新的数据源合集 - dataHanlder:{ - //全局的处理函数,可以从apps/schema/:id 接口返回中的dataSource中获取 - type: "JSFunction", - value: "" - } -} - * @returns { string } - */ -const generateDataSource = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateDataSource', params) - -/** - * vscode生成桥接源 - * - * @param { json } params - { - //桥接源合集,可以从apps/schema/:id 接口返回中的bridge中获取 - bridge:[] - } - * @returns { string } - */ -const generateBridge = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateBridge', params) - -/** - * vscode生成工具类 - * - * @param { json } params - { - //桥接源合集,可以从apps/schema/:id 接口返回中的utils中获取 - utils:[] - } - * @returns { string } - */ -const generateUtil = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateUtil', params) - -export { generateRouter, generateI18n, generateBlock, generatePage, generateDataSource, generateBridge, generateUtil } diff --git a/packages/common/js/vscodeGenerateFile.ts b/packages/common/js/vscodeGenerateFile.ts new file mode 100644 index 0000000000..1f048cc9ad --- /dev/null +++ b/packages/common/js/vscodeGenerateFile.ts @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' + +// ============ 类型定义 ============ + +/** + * 路由生成参数接口 + */ +interface GenerateRouterParams { + pageId: string + componentsTree: any +} + +/** + * 国际化词条参数接口 + */ +interface GenerateI18nParams { + key: string + contents: { + en_US: string + zh_CN: string + [key: string]: string // 支持其他语言 + } +} + +/** + * 区块生成参数接口 + */ +interface GenerateBlockParams { + schema: any + blockPath: string +} + +/** + * 页面生成参数接口 + */ +interface GeneratePageParams { + id: number + name: string + page_content: any +} + +/** + * 数据源生成参数接口 + */ +interface GenerateDataSourceParams { + list: any[] + dataHandler: { + type: string + value: string + } +} + +/** + * 桥接源生成参数接口 + */ +interface GenerateBridgeParams { + bridge: any[] +} + +/** + * 工具类生成参数接口 + */ +interface GenerateUtilParams { + utils: any[] +} + +// ============ 函数实现 ============ + +/** + * vscode生成路由文件 + * + * + * @param { json } params + { + pageId:"123", // 当前页面ID + componentsTree:{} // 整个应用的路由对象 + } + * @returns { string } + */ + +const generateRouter = (params: GenerateRouterParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateRouter', params) + +/** + * vscode生成本地国际化词条 + * + * @param { json } params + { + + key:'lowcode.preview' // 词条的唯一key值 + contents: { + en_US: "preview", // 英文 + zh_CN: "预览" // 中文 + } + } + * @returns { string } + */ + +const generateI18n = (params: GenerateI18nParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateI18n', params) + +/** + * vscode生成区块 + * + * @param { json } params + { + + schema: '', // 区块的schema + blockPath: ''// 区块的分类ID,或者说传保存路径 + } + * @returns { string } + */ + +const generateBlock = (params: GenerateBlockParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateBlock', params) + +/** + * vscode生成页面 + * + * @param { json } params + { + id: 2645, // 页面ID + name: 'xh-test', // 页面名称 + page_content:{} //页面的schema + } + * @returns { string } + */ +const generatePage = (params: GeneratePageParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generatePage', params) + +/** + * vscode生成数据源 + * + * @param { json } params + { + list:[], // 新的数据源合集 + dataHandler:{ + //全局的处理函数,可以从apps/schema/:id 接口返回中的dataSource中获取 + type: "JSFunction", + value: "" + } +} + * @returns { string } + */ +const generateDataSource = (params: GenerateDataSourceParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateDataSource', params) + +/** + * vscode生成桥接源 + * + * @param { json } params + { + //桥接源合集,可以从apps/schema/:id 接口返回中的bridge中获取 + bridge:[] + } + * @returns { string } + */ +const generateBridge = (params: GenerateBridgeParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateBridge', params) + +/** + * vscode生成工具类 + * + * @param { json } params + { + //桥接源合集,可以从apps/schema/:id 接口返回中的utils中获取 + utils:[] + } + * @returns { string } + */ +const generateUtil = (params: GenerateUtilParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateUtil', params) + +export { generateRouter, generateI18n, generateBlock, generatePage, generateDataSource, generateBridge, generateUtil } diff --git a/packages/common/package.json b/packages/common/package.json index d57ff66641..5adae66e13 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*", + "@types/css-tree": "2.3.11", "@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue-jsx": "^4.0.1", "glob": "^10.3.4",