From 90e8780879ae5d1902a28c011c0ff9aca67c7a25 Mon Sep 17 00:00:00 2001 From: chilingling Date: Wed, 28 Aug 2024 03:02:30 -0700 Subject: [PATCH 1/5] feat: generatecode support import element style --- packages/vue-generator/package.json | 1 + .../src/generator/generateApp.js | 8 +- .../src/plugins/appendElePlusStylePlugin.js | 79 +++++++++++++++++++ packages/vue-generator/src/plugins/index.js | 1 + 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 packages/vue-generator/src/plugins/appendElePlusStylePlugin.js diff --git a/packages/vue-generator/package.json b/packages/vue-generator/package.json index b7a94ecdcc..edffb26153 100644 --- a/packages/vue-generator/package.json +++ b/packages/vue-generator/package.json @@ -53,6 +53,7 @@ }, "peerDependencies": { "@babel/parser": "^7.18.13", + "@babel/generator": "^7.18.13", "@babel/traverse": "^7.18.13" } } diff --git a/packages/vue-generator/src/generator/generateApp.js b/packages/vue-generator/src/generator/generateApp.js index b602a1c9e7..ad8bb84647 100644 --- a/packages/vue-generator/src/generator/generateApp.js +++ b/packages/vue-generator/src/generator/generateApp.js @@ -9,7 +9,8 @@ import { genUtilsPlugin, formatCodePlugin, parseSchemaPlugin, - genGlobalState + genGlobalState, + appendElePlusStylePlugin } from '../plugins' import CodeGenerator from './codeGenerator' @@ -63,6 +64,11 @@ export function generateApp(config = {}) { globalState: globalState || defaultPlugins.globalState } + // 默认支持 element-plus 注入样式 + if (config?.customContext?.injectElementPlusStyle !== false) { + transformEnd.push(appendElePlusStylePlugin(config?.customContext?.injectElementPlusStyle || {})) + } + const codeGenInstance = new CodeGenerator({ plugins: { transformStart: [parseSchema || defaultPlugins.parseSchema, ...transformStart], diff --git a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js new file mode 100644 index 0000000000..01ed97341f --- /dev/null +++ b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js @@ -0,0 +1,79 @@ +import prettier from 'prettier' +import { parse } from '@babel/parser' +import traverse from '@babel/traverse' +import generate from '@babel/generator' +import parserBabel from 'prettier/parser-babel' +import { mergeOptions } from '../utils/mergeOptions' + +const defaultOption = { + fileName: 'package.json', + path: '.', + prettierOption: { + singleQuote: true, + printWidth: 120, + semi: false, + trailingComma: 'none' + } +} + +function genElementPlusStyleDeps(options = {}) { + const realOptions = mergeOptions(defaultOption, options) + + const { prettierOption, fileName, path } = realOptions + + return { + name: 'tinyEngine-generateCode-plugin-element-plus-style', + description: 'import element-plus style', + /** + * 注入 element-plus 全局样式依赖 + * @param {tinyEngineDslVue.IAppSchema} schema + * @returns + */ + run() { + const originPackageItem = this.getFile(path, fileName) + + if (!originPackageItem) { + return + } + + let originPackageJSON = JSON.parse(originPackageItem.fileContent) + const hasElementPlusDeps = Object.keys(originPackageJSON.dependencies).includes('element-plus') + + if (!hasElementPlusDeps) { + return + } + + const mainJsFile = this.getFile('./src', 'main.js') || {} + + if (!mainJsFile.fileContent) { + return + } + + const ast = parse(mainJsFile.fileContent, { sourceType: 'module' }) + let lastImport = null + + traverse(ast, { + ImportDeclaration(path) { + lastImport = path + } + }) + + // 引入 element-plus 样式依赖 + if (lastImport) { + lastImport.insertAfter(parse("import 'elemetn-plus/dist/index.css'", { sourceType: 'module' }).program.body[0]) + } + + const newFileContent = generate(ast).code + + const formattedContent = prettier.format(newFileContent, { + parser: 'babel', + plugins: [parserBabel], + ...prettierOption + }) + + this.replaceFile({ ...mainJsFile, fileContent: formattedContent }) + } + } +} + +export default genElementPlusStyleDeps diff --git a/packages/vue-generator/src/plugins/index.js b/packages/vue-generator/src/plugins/index.js index 1980ba6025..cfdfeab5a4 100644 --- a/packages/vue-generator/src/plugins/index.js +++ b/packages/vue-generator/src/plugins/index.js @@ -9,3 +9,4 @@ export { default as genTemplatePlugin } from './genTemplatePlugin' export { default as formatCodePlugin } from './formatCodePlugin' export { default as genGlobalState } from './genGlobalState' export { default as parseSchemaPlugin } from './parseSchemaPlugin' +export { default as appendElePlusStylePlugin } from './appendElePlusStylePlugin' From 8343d71698eb2584ef5308ed366d442c4c634a63 Mon Sep 17 00:00:00 2001 From: chilingling Date: Wed, 28 Aug 2024 04:13:10 -0700 Subject: [PATCH 2/5] fix: element word spell issue --- packages/vue-generator/src/plugins/appendElePlusStylePlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js index 01ed97341f..1c8b1045c3 100644 --- a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js +++ b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js @@ -60,7 +60,7 @@ function genElementPlusStyleDeps(options = {}) { // 引入 element-plus 样式依赖 if (lastImport) { - lastImport.insertAfter(parse("import 'elemetn-plus/dist/index.css'", { sourceType: 'module' }).program.body[0]) + lastImport.insertAfter(parse("import 'element-plus/dist/index.css'", { sourceType: 'module' }).program.body[0]) } const newFileContent = generate(ast).code From d31e2618b455343551a695aec147c0bf3f67e8dc Mon Sep 17 00:00:00 2001 From: chilingling Date: Wed, 25 Sep 2024 04:06:20 -0700 Subject: [PATCH 3/5] feat: add ele style import test case --- .../element-plus-case.test.js | 43 ++++++ .../expected/appdemo01/.gitignore | 13 ++ .../expected/appdemo01/README.md | 19 +++ .../expected/appdemo01/index.html | 13 ++ .../expected/appdemo01/package.json | 28 ++++ .../expected/appdemo01/src/App.vue | 11 ++ .../expected/appdemo01/src/http/axios.js | 139 ++++++++++++++++++ .../expected/appdemo01/src/http/config.js | 15 ++ .../expected/appdemo01/src/http/index.js | 27 ++++ .../expected/appdemo01/src/i18n/index.js | 7 + .../expected/appdemo01/src/i18n/locale.js | 1 + .../appdemo01/src/lowcodeConfig/bridge.js | 13 ++ .../appdemo01/src/lowcodeConfig/dataSource.js | 102 +++++++++++++ .../src/lowcodeConfig/dataSource.json | 3 + .../appdemo01/src/lowcodeConfig/lowcode.js | 86 +++++++++++ .../appdemo01/src/lowcodeConfig/store.js | 13 ++ .../expected/appdemo01/src/main.js | 19 +++ .../expected/appdemo01/src/router/index.js | 10 ++ .../expected/appdemo01/src/stores/index.js | 0 .../expected/appdemo01/src/utils.js | 1 + .../expected/appdemo01/src/views/DemoPage.vue | 25 ++++ .../expected/appdemo01/vite.config.js | 23 +++ .../testcases/element-plus-case/mockData.js | 87 +++++++++++ 23 files changed, 698 insertions(+) create mode 100644 packages/vue-generator/test/testcases/element-plus-case/element-plus-case.test.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/.gitignore create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/README.md create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/index.html create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/package.json create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/App.vue create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/axios.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/config.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/index.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/index.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/locale.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/bridge.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.json create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/lowcode.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/store.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/main.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/router/index.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/stores/index.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/utils.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/views/DemoPage.vue create mode 100644 packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/vite.config.js create mode 100644 packages/vue-generator/test/testcases/element-plus-case/mockData.js diff --git a/packages/vue-generator/test/testcases/element-plus-case/element-plus-case.test.js b/packages/vue-generator/test/testcases/element-plus-case/element-plus-case.test.js new file mode 100644 index 0000000000..b86f2672c0 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/element-plus-case.test.js @@ -0,0 +1,43 @@ +import { expect, test, describe } from 'vitest' +import path from 'path' +import fs from 'fs' +import dirCompare from 'dir-compare' +import { generateApp } from '@/generator/generateApp' +import { appSchemaDemo01 } from './mockData' +import { logDiffResult } from '../../utils/logDiffResult' + +describe('generate element-plus material project correctly', () => { + test('should generate element-plus css import statement 预期生成 element-plus 样式依赖引入', async () => { + const instance = generateApp() + + const res = await instance.generate(appSchemaDemo01) + const { genResult } = res + + // 写入文件 + genResult.forEach(({ fileName, path: filePath, fileContent }) => { + fs.mkdirSync(path.resolve(__dirname, `./result/appdemo01/${filePath}`), { recursive: true }) + fs.writeFileSync( + path.resolve(__dirname, `./result/appdemo01/${filePath}/${fileName}`), + // 这里需要将换行符替换成 CRLF 格式的 + fileContent.replace(/\r?\n/g, '\r\n') + ) + }) + + const compareOptions = { + compareContent: true, + ignoreLineEnding: true, + ignoreAllWhiteSpaces: true, + ignoreEmptyLines: true + } + + const path1 = path.resolve(__dirname, './expected/appdemo01') + const path2 = path.resolve(__dirname, './result/appdemo01') + + // 对比文件差异 + const diffResult = dirCompare.compareSync(path1, path2, compareOptions) + + logDiffResult(diffResult) + + expect(diffResult.same).toBe(true) + }) +}) diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/.gitignore b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/.gitignore new file mode 100644 index 0000000000..9961aac9d4 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/.gitignore @@ -0,0 +1,13 @@ +node_modules +dist/ + +# local env files +.env.local +.env.*.local + +# Editor directories and files +.vscode +.idea + +yarn.lock +package-lock.json \ No newline at end of file diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/README.md b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/README.md new file mode 100644 index 0000000000..275a5e79cd --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/README.md @@ -0,0 +1,19 @@ +## portal-app + +本工程是使用 TinyEngine 低代码引擎搭建之后得到的出码工程。 + +## 使用 + +安装依赖: + +```bash +npm install +``` + +本地启动项目: + +```bash +npm run dev +``` + + diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/index.html b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/index.html new file mode 100644 index 0000000000..2f10f362d1 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/index.html @@ -0,0 +1,13 @@ + + + + + + + portal-app + + +
+ + + diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/package.json b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/package.json new file mode 100644 index 0000000000..4d9b7824d9 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/package.json @@ -0,0 +1,28 @@ +{ + "name": "portal-app", + "version": "1.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "main": "dist/index.js", + "module": "dist/index.js", + "dependencies": { + "@opentiny/tiny-engine-i18n-host": "^1.0.0", + "@opentiny/vue": "^3.10.0", + "@opentiny/vue-icon": "^3.10.0", + "axios": "^0.21.1", + "axios-mock-adapter": "^1.19.0", + "vue": "^3.3.9", + "vue-i18n": "^9.2.0-beta.3", + "vue-router": "^4.2.5", + "pinia": "^2.1.7", + "element-plus": "^2.4.2" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.5.1", + "@vitejs/plugin-vue-jsx": "^3.1.0", + "vite": "^4.3.7" + } +} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/App.vue b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/App.vue new file mode 100644 index 0000000000..72b6032dea --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/axios.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/axios.js new file mode 100644 index 0000000000..4b2d6e4208 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/axios.js @@ -0,0 +1,139 @@ +/** + * 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 axios from 'axios' +import MockAdapter from 'axios-mock-adapter' + +export default (config) => { + const instance = axios.create(config) + const defaults = {} + let mock + + if (typeof MockAdapter.prototype.proxy === 'undefined') { + MockAdapter.prototype.proxy = function ({ url, config = {}, proxy, response, handleData } = {}) { + let stream = this + const request = (proxy, any) => { + return (setting) => { + return new Promise((resolve) => { + config.responseType = 'json' + axios + .get(any ? proxy + setting.url + '.json' : proxy, config) + .then(({ data }) => { + /* eslint-disable no-useless-call */ + typeof handleData === 'function' && (data = handleData.call(null, data, setting)) + resolve([200, data]) + }) + .catch((error) => { + resolve([error.response.status, error.response.data]) + }) + }) + } + } + + if (url === '*' && proxy && typeof proxy === 'string') { + stream = proxy === '*' ? this.onAny().passThrough() : this.onAny().reply(request(proxy, true)) + } else { + if (proxy && typeof proxy === 'string') { + stream = this.onAny(url).reply(request(proxy)) + } else if (typeof response === 'function') { + stream = this.onAny(url).reply(response) + } + } + + return stream + } + } + + return { + request(config) { + return instance(config) + }, + get(url, config) { + return instance.get(url, config) + }, + delete(url, config) { + return instance.delete(url, config) + }, + head(url, config) { + return instance.head(url, config) + }, + post(url, data, config) { + return instance.post(url, data, config) + }, + put(url, data, config) { + return instance.put(url, data, config) + }, + patch(url, data, config) { + return instance.patch(url, data, config) + }, + all(iterable) { + return axios.all(iterable) + }, + spread(callback) { + return axios.spread(callback) + }, + defaults(key, value) { + if (key && typeof key === 'string') { + if (typeof value === 'undefined') { + return instance.defaults[key] + } + instance.defaults[key] = value + defaults[key] = value + } else { + return instance.defaults + } + }, + defaultSettings() { + return defaults + }, + interceptors: { + request: { + use(fnHandle, fnError) { + return instance.interceptors.request.use(fnHandle, fnError) + }, + eject(id) { + return instance.interceptors.request.eject(id) + } + }, + response: { + use(fnHandle, fnError) { + return instance.interceptors.response.use(fnHandle, fnError) + }, + eject(id) { + return instance.interceptors.response.eject(id) + } + } + }, + mock(config) { + if (!mock) { + mock = new MockAdapter(instance) + } + + if (Array.isArray(config)) { + config.forEach((item) => { + mock.proxy(item) + }) + } + + return mock + }, + disableMock() { + mock && mock.restore() + mock = undefined + }, + isMock() { + return typeof mock !== 'undefined' + }, + CancelToken: axios.CancelToken, + isCancel: axios.isCancel + } +} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/config.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/config.js new file mode 100644 index 0000000000..cfa3714e17 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/config.js @@ -0,0 +1,15 @@ +/** + * 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. + * + */ + +export default { + withCredentials: false +} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/index.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/index.js new file mode 100644 index 0000000000..b0a08546a6 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/http/index.js @@ -0,0 +1,27 @@ +/** + * 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 axios from './axios' +import config from './config' + +export default (dataHandler) => { + const http = axios(config) + + http.interceptors.response.use(dataHandler, (error) => { + const response = error.response + if (response.status === 403 && response.headers && response.headers['x-login-url']) { + // TODO 处理无权限时,重新登录再发送请求 + } + }) + + return http +} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/index.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/index.js new file mode 100644 index 0000000000..043eac53ea --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/index.js @@ -0,0 +1,7 @@ +import i18n from '@opentiny/tiny-engine-i18n-host' +import lowcode from '../lowcodeConfig/lowcode' +import locale from './locale.js' + +i18n.lowcode = lowcode + +export default i18n diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/locale.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/locale.js new file mode 100644 index 0000000000..b1c6ea436a --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/i18n/locale.js @@ -0,0 +1 @@ +export default {} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/bridge.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/bridge.js new file mode 100644 index 0000000000..7a19e4a116 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/bridge.js @@ -0,0 +1,13 @@ +/** + * 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. + * + */ + +export default () => {} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.js new file mode 100644 index 0000000000..80e1e88e7b --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.js @@ -0,0 +1,102 @@ +/** + * 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 useHttp from '../http' +import dataSources from './dataSource.json' + +const dataSourceMap = {} + +// 暂时使用 eval 解析 JSON 数据里的函数 +const createFn = (fnContent) => { + return (...args) => { + // eslint-disable-next-line no-eval + window.eval('var fn = ' + fnContent) + // eslint-disable-next-line no-undef + return fn.apply(this, args) + } +} + +const globalDataHandle = dataSources.dataHandler ? createFn(dataSources.dataHandler.value) : (res) => res + +const load = (http, options, dataSource, shouldFetch) => (params, customUrl) => { + // 如果没有配置远程请求,则直接返回静态数据,返回前可能会有全局数据处理 + if (!options) { + return globalDataHandle(dataSource.config.data) + } + + if (!shouldFetch()) { + return + } + + dataSource.status = 'loading' + + const { method, uri: url, params: defaultParams, timeout, headers } = options + const config = { method, url, headers, timeout } + + const data = params || defaultParams + + config.url = customUrl || config.url + + if (method.toLowerCase() === 'get') { + config.params = data + } else { + config.data = data + } + + return http.request(config) +} + +dataSources.list.forEach((config) => { + const http = useHttp(globalDataHandle) + const dataSource = { config } + + dataSourceMap[config.name] = dataSource + + const shouldFetch = config.shouldFetch?.value ? createFn(config.shouldFetch.value) : () => true + const willFetch = config.willFetch?.value ? createFn(config.willFetch.value) : (options) => options + + const dataHandler = (res) => { + const data = config.dataHandler?.value ? createFn(config.dataHandler.value)(res) : res + dataSource.status = 'loaded' + dataSource.data = data + return data + } + + const errorHandler = (error) => { + config.errorHandler?.value && createFn(config.errorHandler.value)(error) + dataSource.status = 'error' + dataSource.error = error + } + + http.interceptors.request.use(willFetch, errorHandler) + http.interceptors.response.use(dataHandler, errorHandler) + + if (import.meta.env.VITE_APP_MOCK === 'mock') { + http.mock([ + { + url: config.options?.uri, + response() { + return Promise.resolve([200, { data: config.data }]) + } + }, + { + url: '*', + proxy: '*' + } + ]) + } + + dataSource.status = 'init' + dataSource.load = load(http, config.options, dataSource, shouldFetch) +}) + +export default dataSourceMap diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.json b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.json new file mode 100644 index 0000000000..2546e9ed93 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/dataSource.json @@ -0,0 +1,3 @@ +{ + "list": [] +} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/lowcode.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/lowcode.js new file mode 100644 index 0000000000..29da8186b5 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/lowcode.js @@ -0,0 +1,86 @@ +/** + * 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 { getCurrentInstance, nextTick, provide, inject } from 'vue' +import { useRouter, useRoute } from 'vue-router' +import { I18nInjectionKey } from 'vue-i18n' +import dataSourceMap from './dataSource' +import * as utils from '../utils' +import * as bridge from './bridge' +import { useStores } from './store' + +export const lowcodeWrap = (props, context) => { + const global = {} + const instance = getCurrentInstance() + const router = useRouter() + const route = useRoute() + const { t, locale } = inject(I18nInjectionKey).global + const emit = context.emit + const ref = (ref) => instance.refs[ref] + + const setState = (newState, callback) => { + Object.assign(global.state, newState) + nextTick(() => callback.apply(global)) + } + + const getLocale = () => locale.value + const setLocale = (val) => { + locale.value = val + } + + const location = () => window.location + const history = () => window.history + + Object.defineProperties(global, { + props: { get: () => props }, + emit: { get: () => emit }, + setState: { get: () => setState }, + router: { get: () => router }, + route: { get: () => route }, + i18n: { get: () => t }, + getLocale: { get: () => getLocale }, + setLocale: { get: () => setLocale }, + location: { get: location }, + history: { get: history }, + utils: { get: () => utils }, + bridge: { get: () => bridge }, + dataSourceMap: { get: () => dataSourceMap }, + $: { get: () => ref } + }) + + const wrap = (fn) => { + if (typeof fn === 'function') { + return (...args) => fn.apply(global, args) + } + + Object.entries(fn).forEach(([name, value]) => { + Object.defineProperty(global, name, { + get: () => value + }) + }) + + fn.t = t + + return fn + } + + return wrap +} + +export default () => { + const i18n = inject(I18nInjectionKey) + provide(I18nInjectionKey, i18n) + + const stores = useStores() + + return { t: i18n.global.t, lowcodeWrap, stores } +} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/store.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/store.js new file mode 100644 index 0000000000..f7f39c7a84 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/lowcodeConfig/store.js @@ -0,0 +1,13 @@ +import * as useDefinedStores from '@/stores' + +const useStores = () => { + const stores = {} + + Object.values({ ...useDefinedStores }).forEach((store) => { + stores[store.$id] = store() + }) + + return stores +} + +export { useStores } diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/main.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/main.js new file mode 100644 index 0000000000..311640fed6 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/main.js @@ -0,0 +1,19 @@ +/** + * 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 { createApp } from 'vue' +import router from './router' +import { createPinia } from 'pinia' +import App from './App.vue' +import 'element-plus/dist/index.css' +const pinia = createPinia() +createApp(App).use(pinia).use(router).mount('#app') diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/router/index.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/router/index.js new file mode 100644 index 0000000000..c76f722207 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/router/index.js @@ -0,0 +1,10 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +const routes = [ + { path: '/', redirect: '/demopage' }, + { path: '/demopage', component: () => import('@/views/DemoPage.vue') } +] + +export default createRouter({ + history: createWebHashHistory(), + routes +}) diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/stores/index.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/stores/index.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/utils.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/utils.js new file mode 100644 index 0000000000..336ce12bb9 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/utils.js @@ -0,0 +1 @@ +export {} diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/views/DemoPage.vue b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/views/DemoPage.vue new file mode 100644 index 0000000000..274e305a42 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/src/views/DemoPage.vue @@ -0,0 +1,25 @@ + + + + diff --git a/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/vite.config.js b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/vite.config.js new file mode 100644 index 0000000000..e1e57978b1 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/expected/appdemo01/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import path from 'path' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +export default defineConfig({ + resolve: { + alias: { + '@': path.resolve(__dirname, 'src') + } + }, + plugins: [vue(), vueJsx()], + define: { + 'process.env': { ...process.env } + }, + build: { + minify: true, + commonjsOptions: { + transformMixedEsModules: true + }, + cssCodeSplit: false + } +}) diff --git a/packages/vue-generator/test/testcases/element-plus-case/mockData.js b/packages/vue-generator/test/testcases/element-plus-case/mockData.js new file mode 100644 index 0000000000..8cb3ac8af3 --- /dev/null +++ b/packages/vue-generator/test/testcases/element-plus-case/mockData.js @@ -0,0 +1,87 @@ +export const appSchemaDemo01 = { + dataSource: { + list: [] + }, + globalState: [], + utils: [], + i18n: {}, + pageSchema: [ + { + state: {}, + methods: {}, + componentName: 'Page', + css: '', + props: {}, + lifeCycles: {}, + children: [ + { + componentName: 'div', + props: {}, + id: '85375559', + children: [ + { + componentName: 'ElInput', + props: {}, + id: '33433546' + } + ] + } + ], + dataSource: { + list: [] + }, + utils: [], + bridge: [], + inputs: [], + outputs: [], + fileName: 'DemoPage', + meta: { + name: 'DemoPage', + id: '5bhD7p5FUsUOTFRN', + app: '918', + router: 'demopage', + tenant: 1, + isBody: false, + parentId: '0', + group: 'staticPages', + depth: 0, + isPage: true, + isDefault: false, + occupier: { + id: 86, + username: '开发者', + email: 'developer@lowcode.com', + resetPasswordToken: 'developer', + confirmationToken: 'dfb2c162-351f-4f44-ad5f-8998', + is_admin: true + }, + isHome: false, + message: 'Page auto save', + _id: '5bhD7p5FUsUOTFRN' + } + } + ], + componentsMap: [ + { + componentName: 'ElInput', + package: 'element-plus', + exportName: 'ElInput', + destructuring: true, + version: '^2.4.2' + } + ], + meta: { + name: 'portal-app', + tenant: 1, + git_group: '', + project_name: '', + description: 'demo应用', + branch: 'develop', + is_demo: null, + global_state: [], + appId: '918', + creator: '', + gmt_create: '2022-06-08 03:19:01', + gmt_modified: '2023-08-23 10:22:28' + } +} From 3e4bca811ce26dc88f096104f611136cb1aee2a3 Mon Sep 17 00:00:00 2001 From: chilingling Date: Wed, 25 Sep 2024 04:41:21 -0700 Subject: [PATCH 4/5] fix: check if already exist element plus style import --- .../src/plugins/appendElePlusStylePlugin.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js index 1c8b1045c3..15448468a5 100644 --- a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js +++ b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js @@ -16,6 +16,32 @@ const defaultOption = { } } +// const checkHasElementStyleImport = (code) => { +// try { +// const ast = parse(code, { sourceType: 'module', plugins: ['jsx'] }) +// let res = false + +// traverse(ast, { +// ImportDeclaration(path) { + +// } +// JSXElement(path) { +// res = true +// path.stop() +// }, +// JSXFragment(path) { +// res = true +// path.stop() +// } +// }) + +// return res +// } catch (error) { +// // 解析失败则认为不存在 jsx +// return false +// } +// } + function genElementPlusStyleDeps(options = {}) { const realOptions = mergeOptions(defaultOption, options) @@ -51,13 +77,23 @@ function genElementPlusStyleDeps(options = {}) { const ast = parse(mainJsFile.fileContent, { sourceType: 'module' }) let lastImport = null + let hasElementPlusStyleImport = false traverse(ast, { ImportDeclaration(path) { lastImport = path + + if (path.node.source.value === 'element-plus/dist/index.css') { + hasElementPlusStyleImport = true + } } }) + // 已经存在 element-plus 的 import,不再插入 + if (hasElementPlusStyleImport) { + return + } + // 引入 element-plus 样式依赖 if (lastImport) { lastImport.insertAfter(parse("import 'element-plus/dist/index.css'", { sourceType: 'module' }).program.body[0]) From 3c4e7bbfa457ce8d566ba297f2e181100418235e Mon Sep 17 00:00:00 2001 From: chilingling Date: Sat, 7 Dec 2024 02:25:13 -0800 Subject: [PATCH 5/5] fix: del useless comment code --- .../src/plugins/appendElePlusStylePlugin.js | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js index 15448468a5..1d77975df5 100644 --- a/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js +++ b/packages/vue-generator/src/plugins/appendElePlusStylePlugin.js @@ -16,32 +16,6 @@ const defaultOption = { } } -// const checkHasElementStyleImport = (code) => { -// try { -// const ast = parse(code, { sourceType: 'module', plugins: ['jsx'] }) -// let res = false - -// traverse(ast, { -// ImportDeclaration(path) { - -// } -// JSXElement(path) { -// res = true -// path.stop() -// }, -// JSXFragment(path) { -// res = true -// path.stop() -// } -// }) - -// return res -// } catch (error) { -// // 解析失败则认为不存在 jsx -// return false -// } -// } - function genElementPlusStyleDeps(options = {}) { const realOptions = mergeOptions(defaultOption, options)