diff --git a/packages/ac-item-plugin/CHANGELOG.md b/packages/ac-item-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/ac-item-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/ac-item-plugin/README.md b/packages/ac-item-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..388f56f821efee35e41134370c29a56d0a6998d8 --- /dev/null +++ b/packages/ac-item-plugin/README.md @@ -0,0 +1,165 @@ +# 自填列表项插件 + +## 新建自填列表项绘制插件 + +新建自填列表项绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的实体自填模式上绑定插件 + + + +## 在对应的编辑器上绑定上一步配置的实体自填模式 + + + +## 插件机制 + +编辑器在绘制具体的项时,会先通过编辑器模型拿到对应的自填列表项适配器,若适配器存在,则绘制适配器component属性绑定的组件,否则根据编辑器内部逻辑进行绘制。详情如下: + +```tsx +// 绘制自填列表项 +const itemContent = (item: IData) => { + const panel = this.c.deACMode?.itemLayoutPanel; + const { context, params } = this.c; + let selected = item[this.c.textName] || item.srfmajortext === this.curValue; + if (this.c.valueItem) { + selected = + (item[this.c.keyName] || item.srfkey) === this.data[this.c.valueItem]; + } + const className = [ + this.ns.is('active', selected), + this.ns.e('transfer-item'), + ]; + if (this.c.acItemProvider) { + const component = resolveComponent(this.c.acItemProvider.component); + return h(component, { + item, + controller: this.c, + class: className, + onClick: () => { + this.onACSelect(item); + }, + }); + } + return panel ? ( + { + this.onACSelect(item); + }} + > + ) : ( +
{ + this.onACSelect(item); + }} + > + {item[this.c.textName]} +
+ ); +}; +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ ac-item-plugin 自填列表项插件顶层目录,可根据实际业务命名 + |─ ─ src 自填列表项插件源代码目录 +​ |─ ─ ac-item-plugin.provider.ts 自填列表项插件适配器 +​ |─ ─ ac-item-plugin.scss 自填列表项插件样式 +​ |─ ─ ac-item-plugin.tsx 自填列表项插件组件 +​ |─ ─ index.ts 自填列表项插件入口文件 +``` + +### 自填列表项插件入口文件 + +自填列表项插件入口文件会全局注册自填列表项插件适配器和自填列表项插件组件,供外部使用。 + +```typescript +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { App } from 'vue'; +import { registerAcItemProvider } from '@ibiz-template/runtime'; +import { AcItemPlugin } from './ac-item-plugin'; +import { AcItemPluginProvider } from './ac-item-plugin.provider'; + +export default { + install(app: App) { + // 全局注册自填列表项插件组件 + app.component(AcItemPlugin.name!, AcItemPlugin); + // 全局注册自填列表项插件适配器,AC_ITEM是插件类型,R9AcItemPluginId是插件标识 + registerAcItemProvider( + 'AC_ITEM_R9AcItemPluginId', + () => new AcItemPluginProvider(), + ); + }, +}; +``` + +### 自填列表项插件组件 + +自填列表项插件组件使用tsx的书写方式,承载自填列表项绘制的内容,可根据需求自定义内容呈现。 + +### 自填列表项插件适配器 + +自填列表项插件适配器主要通过component属性指定自填列表项实际要绘制的组件。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/ac-item-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import AcItemPlugin from '@ibiz-plugin-template/ac-item-plugin'; + +export default { + install(app: App) { + app.use(AcItemPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/ac-item-plugin/); + }, +}; +``` diff --git a/packages/ac-item-plugin/RUNTIME-PLUGIN-MODE.md b/packages/ac-item-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/ac-item-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/ac-item-plugin/package.json b/packages/ac-item-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..61390edc447c5ab9129f396a15e03d6f89f3c50c --- /dev/null +++ b/packages/ac-item-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/ac-item-plugin", + "version": "0.0.1", + "description": "自填列表项插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/ac-item-plugin/public/docs/image.png b/packages/ac-item-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..0710aec7bafe1a8d7abe9f95a84e05b129776514 Binary files /dev/null and b/packages/ac-item-plugin/public/docs/image.png differ diff --git a/packages/ac-item-plugin/public/docs/image2.png b/packages/ac-item-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d3b6c9316c2dad14ddc2cb03a129b1941e1040 Binary files /dev/null and b/packages/ac-item-plugin/public/docs/image2.png differ diff --git a/packages/ac-item-plugin/public/docs/image3.png b/packages/ac-item-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..aab12d3f846a82bf071e517717708dba1a62020c Binary files /dev/null and b/packages/ac-item-plugin/public/docs/image3.png differ diff --git a/packages/ac-item-plugin/public/docs/image4.png b/packages/ac-item-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..c21f56e54effcb2d7a8829e3b2d8d05ba99dd711 Binary files /dev/null and b/packages/ac-item-plugin/public/docs/image4.png differ diff --git a/packages/ac-item-plugin/public/docs/image5.png b/packages/ac-item-plugin/public/docs/image5.png new file mode 100644 index 0000000000000000000000000000000000000000..82ce9451c383e3ab3924c1b8e08e057f7e92bcad Binary files /dev/null and b/packages/ac-item-plugin/public/docs/image5.png differ diff --git a/packages/ac-item-plugin/scripts/link.sh b/packages/ac-item-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..bb3e1983d7d048eaafe404532c04f25267098927 --- /dev/null +++ b/packages/ac-item-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" \ No newline at end of file diff --git a/packages/ac-item-plugin/src/ac-item-plugin.provider.ts b/packages/ac-item-plugin/src/ac-item-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..e96dd0ee8f9537270cb541d1c37e71aa360995cd --- /dev/null +++ b/packages/ac-item-plugin/src/ac-item-plugin.provider.ts @@ -0,0 +1,5 @@ +import { IAcItemProvider } from '@ibiz-template/runtime'; + +export class AcItemPluginProvider implements IAcItemProvider { + component: string = 'IBizAcItemPlugin'; +} diff --git a/packages/ac-item-plugin/src/ac-item-plugin.scss b/packages/ac-item-plugin/src/ac-item-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..bc33e012b105020bd0b0089caebadce0f8957e45 --- /dev/null +++ b/packages/ac-item-plugin/src/ac-item-plugin.scss @@ -0,0 +1,3 @@ +@include b(ac-item-plugin) { + // 样式 +} \ No newline at end of file diff --git a/packages/ac-item-plugin/src/ac-item-plugin.tsx b/packages/ac-item-plugin/src/ac-item-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d93ea62fa5aa89314b491d2e041fdb035eb89b59 --- /dev/null +++ b/packages/ac-item-plugin/src/ac-item-plugin.tsx @@ -0,0 +1,44 @@ +import { PropType, defineComponent } from 'vue'; +import { EditorController } from '@ibiz-template/runtime'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import './ac-item-plugin.scss'; + +export const AcItemPlugin = defineComponent({ + name: 'IBizAcItemPlugin', + props: { + item: { + type: Object as PropType, + required: true, + }, + controller: { + type: EditorController, + required: true, + }, + }, + + setup() { + const ns = useNamespace('ac-item-plugin'); + return { + ns, + }; + }, + render() { + const quantity = Number(this.item.quantity); + return ( +
= 200 + ? 'green' + : '', + }} + > + {this.item.srfmajortext || this.item.text || ''}[ + {this.item.quantity || ''}] +
+ ); + }, +}); diff --git a/packages/ac-item-plugin/src/index.ts b/packages/ac-item-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5cd3e70bc176de9c3edae0da5aa767ff8ed66a5 --- /dev/null +++ b/packages/ac-item-plugin/src/index.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { App } from 'vue'; +import { registerAcItemProvider } from '@ibiz-template/runtime'; +import { AcItemPlugin } from './ac-item-plugin'; +import { AcItemPluginProvider } from './ac-item-plugin.provider'; + +export default { + install(app: App) { + // 全局注册自填列表项插件组件 + app.component(AcItemPlugin.name!, AcItemPlugin); + // 全局注册自填列表项插件适配器,AC_ITEM是插件类型,R9AcItemPluginId是插件标识 + registerAcItemProvider('AC_ITEM_R9AcItemPluginId', () => new AcItemPluginProvider()); + }, +}; diff --git a/packages/ac-item-plugin/tsconfig.json b/packages/ac-item-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/ac-item-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/ac-item-plugin/tsconfig.node.json b/packages/ac-item-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/ac-item-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/ac-item-plugin/vite.config.ts b/packages/ac-item-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/ac-item-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/control-plugin/CHANGELOG.md b/packages/control-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/control-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/control-plugin/README.md b/packages/control-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..36d32263a3fad52de38233541bb55724929c0f44 --- /dev/null +++ b/packages/control-plugin/README.md @@ -0,0 +1,243 @@ +# 部件插件 + +## 新建自定义部件插件 + +新建自定义部件插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的部件上绑定插件 + +这儿以表单部件为例: + + + +## 插件机制 + +视图在绘制部件时,是通过部件适配器上component属性绑定的组件进行绘制的,详情如下: + +```tsx +// 绘制部件 +const renderControl = (ctrl: IControl, slotProps: IData = {}): VNode => { + const slotKey = ctrl.name! || ctrl.id!; + const ctrlProps = getCtrlProps(ctrl, slotProps); + if (slots[slotKey]) { + return renderSlot(slots, slotKey, ctrlProps); + } + + const provider = c.providers[slotKey]; + const comp = resolveComponent( + provider?.component || 'IBizControlShell', + ) as string; + if (provider) { + ctrlProps.provider = provider; + } + return h(comp, ctrlProps); +}; +``` + +如果部件适配器配置了createController方法,则使用createController方法返回的控制器,否则使用传入的控制器。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 获取部件控制器 +function useControlController( + fn: (...args: ConstructorParameters) => T, + opts?: Partial, +): T { + // 获取上层组件的ctx + const ctx = useCtx(); + // 获取 props + const props = useProps(); + // 上下文里提前预告部件 + ctx.evt.emit('onForecast', props.modelData.name!); + + // 实例化部件控制器 + const provider = props.provider as IControlProvider | undefined; + let c: T; + if (provider?.createController) { + // 如果适配器给了创建方法使用适配器的方法 + c = provider.createController( + props.modelData, + props.context, + props.params, + ctx, + ) as T; + } else { + c = fn(props.modelData, props.context, props.params, ctx); + } + + // 多数据部件类型往记录导航工具添加当前部件 + if (MDControlTypes.indexOf(c.model.controlType!) !== -1) { + ibiz.util.record.add(c.ctrlId, c as unknown as IMDControlController); + } + + watchAndUpdateContextParams(props, c); + watchAndUpdateState(props, c, opts?.excludePropsKeys); + + c.state = reactive(c.state); + + onActivated(() => c.onActivated()); + + onDeactivated(() => c.onDeactivated()); + + // 挂载强制更新方法 + c.force = useForce(); + + const vue = getCurrentInstance()!.proxy!; + c.evt.onAll((eventName: string, event: unknown) => { + vue.$emit(eventName.slice(2), event); + }); + + // vue组件级事件最早的抛出controller + vue.$emit('controllerAppear', c); + + c.created(); + + // 卸载时销毁 + onBeforeUnmount(() => { + c.destroyed(); + // 多数据部件类型从记录导航工具删除当前部件 + if (MDControlTypes.indexOf(c.model.controlType!) !== -1) { + ibiz.util.record.remove(c.ctrlId); + } + }); + return c; +} +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ control-plugin 部件插件顶层目录,可根据实际业务命名 + |─ ─ src 部件插件源代码目录 +​ |─ ─ control-plugin.controller.ts 部件插件控制器 +​ |─ ─ control-plugin.provider.ts 部件插件适配器 +​ |─ ─ control-plugin.scss 部件插件样式 +​ |─ ─ control-plugin.tsx 部件插件组件 +​ |─ ─ index.ts 部件插件入口文件 +``` + +### 部件插件入口文件 + +部件插件入口文件会全局注册部件插件适配器和部件插件组件,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerControlProvider } from '@ibiz-template/runtime'; +import { ControlPlugin } from './control-plugin'; +import { ControlPluginProvider } from './control-plugin.provider'; + +export default { + install(app: App) { + // 全局注册部件插件组件 + app.component(ControlPlugin.name!, ControlPlugin); + // 全局注册部件插件适配器,CUSTOM是插件类型,R9ControlPluginId是插件标识 + registerControlProvider( + 'CUSTOM_R9ControlPluginId', + () => new ControlPluginProvider(), + ); + }, +}; +``` + +### 部件插件组件 + +部件插件组件使用tsx的书写方式,承载部件绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 部件插件适配器 + +部件插件适配器主要通过component属性指定部件实际要绘制的组件,并且通过createController方法返回传递给部件的控制器。 + +### 部件插件控制器 + +部件插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/control-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import ControlPlugin from '@ibiz-plugin-template/control-plugin'; + +export default { + install(app: App) { + app.use(ControlPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/control-plugin/); + }, +}; +``` + +## 附录 + +| 部件类型 | UI呈现 | 控制器 | +| :----------------: | :--------------------: | :-----------------------: | +| 应用菜单 | AppMenuControl | AppMenuController | +| 应用菜单(图标视图) | AppMenuIconViewControl | AppMenuIconViewController | +| 日历 | CalendarControl | CalendarController | +| 标题栏 | CaptionBarControl | CaptionBarController | +| 图表 | ChartControl | ChartController | +| 上下文菜单 | ContextMenuControl | ContextMenuController | +| 数据看板 | DashboardControl | DashboardController | +| 卡片 | DataViewControl | DataViewControlController | +| 数据关系栏 | DRBarControl | DRBarController | +| 数据关系分页 | DRTabControl | DRTabController | +| 日历导航栏 | CalendarExpBarControl | CalendarExpBarController | +| 图表导航栏 | ChartExpBarControl | ChartExpBarController | +| 卡片导航栏 | DataViewExpBarControl | ExpBarControlController | +| 表格导航栏 | GridExpBarControl | ExpBarControlController | +| 列表导航栏 | ListExpBarControl | ExpBarControlController | +| 树导航栏 | TreeExpBarControl | TreeExpBarController | +| 编辑表单 | EditFormControl | EditFormController | +| 搜索表单 | SearchFormControl | SearchFormController | +| 甘特图 | GanttControl | GanttController | +| 表格 | GridControl | GridController | +| 看板 | KanbanControl | KanbanController | +| 列表 | ListControl | ListController | +| 地图 | MapControl | MapController | +| 多编辑视图面板 | MEditViewPanelControl | MEditViewPanelController | +| 选择视图面板 | PickupViewPanelControl | PickupViewPanelController | +| 报表面板 | ReportPanelControl | ReportPanelController | +| 搜索栏 | SearchBarControl | SearchBarController | +| 分页导航面板 | TabExpPanelControl | TabExpPanelController | +| 工具栏 | ToolbarControl | ToolbarController | +| 树 | TreeControl | TreeController | +| 树表格 | TreeGridControl | TreeGridController | +| 树表格(增强) | TreeGridExControl | TreeGridExController | +| 向导面板 | WizardPanelControl | WizardPanelController | diff --git a/packages/control-plugin/RUNTIME-PLUGIN-MODE.md b/packages/control-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/control-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/control-plugin/package.json b/packages/control-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..9d478bd9d5c2a5af471a8bb61456d395ccb36c2b --- /dev/null +++ b/packages/control-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/control-plugin", + "version": "0.0.1", + "description": "部件插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/control-plugin/public/docs/image.png b/packages/control-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..c718a3292d3735d12a05f37e847488fe9f041fc5 Binary files /dev/null and b/packages/control-plugin/public/docs/image.png differ diff --git a/packages/control-plugin/public/docs/image2.png b/packages/control-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..0d4043d81e249503809ac9e733b07fb734dcf830 Binary files /dev/null and b/packages/control-plugin/public/docs/image2.png differ diff --git a/packages/control-plugin/public/docs/image3.png b/packages/control-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..ee8de864d05d196af76a57cbd3ece93e36cfbafa Binary files /dev/null and b/packages/control-plugin/public/docs/image3.png differ diff --git a/packages/control-plugin/public/docs/image4.png b/packages/control-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1c5532d991fb6fc906b92b4b7ca3205c9aac46 Binary files /dev/null and b/packages/control-plugin/public/docs/image4.png differ diff --git a/packages/control-plugin/scripts/link.sh b/packages/control-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/control-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/control-plugin/src/control-plugin.controller.ts b/packages/control-plugin/src/control-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..cbb76e22524bc9dcfb962fcd28707fce33768191 --- /dev/null +++ b/packages/control-plugin/src/control-plugin.controller.ts @@ -0,0 +1,8 @@ +import { EditFormController } from '@ibiz-template/runtime'; + +export class ControlPluginController extends EditFormController { + protected async onCreated(): Promise { + ibiz.log.info('部件控制器初始化'); + await super.onCreated(); + } +} diff --git a/packages/control-plugin/src/control-plugin.provider.ts b/packages/control-plugin/src/control-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..72c284075b4f820fedea864b42ca0ca1a42b8550 --- /dev/null +++ b/packages/control-plugin/src/control-plugin.provider.ts @@ -0,0 +1,20 @@ +import { + CTX, + IControlController, + IControlProvider, +} from '@ibiz-template/runtime'; +import { IControl } from '@ibiz/model-core'; +import { ControlPluginController } from './control-plugin.controller'; + +export class ControlPluginProvider implements IControlProvider { + component: string = 'IBizControlPlugin'; + + createController( + model: IControl, + context: IContext, + params: IParams, + ctx: CTX, + ): IControlController { + return new ControlPluginController(model, context, params, ctx); + } +} diff --git a/packages/control-plugin/src/control-plugin.scss b/packages/control-plugin/src/control-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..de0d518f673db508dd2ee1fc7fb0764337c63d34 --- /dev/null +++ b/packages/control-plugin/src/control-plugin.scss @@ -0,0 +1,5 @@ +@include b(control-plugin){ + width: 100%; + height: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/control-plugin/src/control-plugin.tsx b/packages/control-plugin/src/control-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9da6d98c48fcc5a95ef8ba33b3172541704af77f --- /dev/null +++ b/packages/control-plugin/src/control-plugin.tsx @@ -0,0 +1,40 @@ +import { useControlController, useNamespace } from '@ibiz-template/vue3-util'; +import { IDEEditForm } from '@ibiz/model-core'; +import { defineComponent, PropType } from 'vue'; +import { IControlProvider } from '@ibiz-template/runtime'; +import { ControlPluginController } from './control-plugin.controller'; +import './control-plugin.scss'; + +export const ControlPlugin = defineComponent({ + name: 'IBizControlPlugin', + props: { + modelData: { + type: Object as PropType, + required: true, + }, + context: { type: Object as PropType, required: true }, + params: { type: Object as PropType, default: () => ({}) }, + provider: { type: Object as PropType }, + isSimple: { type: Boolean, required: false }, + data: { type: Object as PropType, required: false }, + loadDefault: { type: Boolean, default: true }, + }, + setup() { + const c = useControlController( + (...args) => new ControlPluginController(...args), + ); + const ns = useNamespace('control-plugin'); + + return { + c, + ns, + }; + }, + render() { + return ( + + {{ ...this.$slots }} + + ); + }, +}); diff --git a/packages/control-plugin/src/index.ts b/packages/control-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cebcaf9e7db6b7096ec8731f3e3c0ed75d69b77 --- /dev/null +++ b/packages/control-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue'; +import { registerControlProvider } from '@ibiz-template/runtime'; +import { ControlPlugin } from './control-plugin'; +import { ControlPluginProvider } from './control-plugin.provider'; + +export default { + install(app: App) { + // 全局注册部件插件组件 + app.component(ControlPlugin.name!, ControlPlugin); + // 全局注册部件插件适配器,CUSTOM是插件类型,R9ControlPluginId是插件标识 + registerControlProvider( + 'CUSTOM_R9ControlPluginId', + () => new ControlPluginProvider(), + ); + }, +}; diff --git a/packages/control-plugin/tsconfig.json b/packages/control-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/control-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/control-plugin/tsconfig.node.json b/packages/control-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/control-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/control-plugin/vite.config.ts b/packages/control-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/control-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/counter-plugin/CHANGELOG.md b/packages/counter-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/counter-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/counter-plugin/README.md b/packages/counter-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f8634671c8742ece2e422f172b076e6630012db5 --- /dev/null +++ b/packages/counter-plugin/README.md @@ -0,0 +1,187 @@ +# 计数器插件 + +## 新建应用计数器插件 + +新建应用计数器插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的计数器上绑定插件 + + + +## 插件机制 + +控制器通过计数器服务获取计数器实例,然后通过监听计数器实例数据变更事件获取变更后的数据。计数器服务内部会通过模型获取对应的计数器适配器,然后调用计数器适配器的createCounter方法获取计数器实例。详情如下: + +```typescript +// 获取计数器实例 +async initCounter(): Promise { + this.counters = {}; + const { appCounterRefs } = this.model; + if (appCounterRefs && appCounterRefs.length > 0) { + const dataKey: string = + this.context[calcDeCodeNameById(this.model.appDataEntityId!)]; + try { + await Promise.all( + appCounterRefs.map(async counterRef => { + const counter = await CounterService.getCounterByRef( + counterRef, + this.context, + dataKey + ? { srfcustomtag: dataKey, ...this.params } + : { ...this.params }, + ); + this.counters[counterRef.id!] = counter; + }), + ); + } catch (error) { + console.error(error); + } + } +} + +// 监听计数器数据变更事件获取变更后的数据 +let counter: AppCounter | null = null; +const counterData = reactive({}); +const counterRefId = ref(''); +const fn = (data: IData) => { + counterData.value = data; +}; +onMounted(() => { + const defaultSlots: VNode[] = slots.default?.() || []; + for (let i = 0; i < defaultSlots.length; i++) { + const slot: VNode = defaultSlots[i]; + const pagePropsC = slot.props?.controller as + | FormTabPageController + | undefined; + if ( + pagePropsC && + pagePropsC.model && + pagePropsC.model.appCounterRefId + ) { + counterRefId.value = pagePropsC.model.appCounterRefId; + break; + } + } + if (counterRefId.value) { + counter = props.controller.getCounter(counterRefId.value); + if (counter) { + counter.onChange(fn); + } + } +}); +onUnmounted(() => { + counter?.offChange(fn); +}); + +// 获取对应的计数器适配器,然后调用计数器适配器的createCounter方法获取计数器实例 +async async getCounter( + model: IAppCounter, + context?: IContext, + params?: IParams, +): Promise { + let id = model.id!; + if (params && params.srfcustomtag) { + id = params.srfcustomtag; + } + if (this.counterMap.has(id)) { + const counter = this.counterMap.get(id)!; + if (counter.isDestroyed === false) { + return counter; + } + this.counterMap.delete(id); + } + const provider = await getAppCounterProvider(model); + const counter = provider.createCounter(model); + await counter.init(context, params); + this.counterMap.set(id, counter); + return counter; +} +``` + +## 插件示例 + +### 插件效果 + +给每个表单分页返回其对应的固定计数值 + + + +### 插件结构 + +``` +|─ ─ counter-plugin 计数器插件顶层目录,可根据实际业务命名 + |─ ─ src 计数器插件源代码目录 +​ |─ ─ counter-plugin.provider.ts 计数器插件适配器 +​ |─ ─ counter-plugin.ts 计数器插件实现类 +​ |─ ─ index.ts 计数器插件入口文件 +``` + +### 计数器插件入口文件 + +计数器插件入口文件会全局注册计数器插件适配器,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerAppCounterProvider } from '@ibiz-template/runtime'; +import { CounterPluginProvider } from './counter-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册计数器插件适配器, APPCOUNTER是插件类型,R9CounterPluginId是插件标识 + registerAppCounterProvider( + 'APPCOUNTER_R9CounterPluginId', + () => new CounterPluginProvider(), + ); + }, +}; +``` + +### 计数器插件实现类 + +计数器插件实现类可继承基础应用计数器AppCounter,重写部分逻辑。 + +### 计数器插件适配器 + +计数器插件适配器的createCounter方法返回计数器插件实现类的实例。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/counter-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import CounterPlugin from '@ibiz-plugin-template/counter-plugin'; + +export default { + install(app: App) { + app.use(CounterPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/counter-plugin/); + }, +}; +``` diff --git a/packages/counter-plugin/RUNTIME-PLUGIN-MODE.md b/packages/counter-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/counter-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/counter-plugin/package.json b/packages/counter-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a888e4ae2b4caf036941dfb19b922614a51ce4df --- /dev/null +++ b/packages/counter-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/counter-plugin", + "version": "0.0.1", + "description": "计数器插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/counter-plugin/public/docs/image.png b/packages/counter-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..4b5d953ecf40a2d187afb74b0470e1b1c4d06945 Binary files /dev/null and b/packages/counter-plugin/public/docs/image.png differ diff --git a/packages/counter-plugin/public/docs/image2.png b/packages/counter-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f801c2100b1140a6a077294e1d3bd2d3c8c594 Binary files /dev/null and b/packages/counter-plugin/public/docs/image2.png differ diff --git a/packages/counter-plugin/public/docs/image3.png b/packages/counter-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..8666b56499ff9c21808658df5d8239e48b6c1d94 Binary files /dev/null and b/packages/counter-plugin/public/docs/image3.png differ diff --git a/packages/counter-plugin/scripts/link.sh b/packages/counter-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/counter-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/counter-plugin/src/counter-plugin.provider.ts b/packages/counter-plugin/src/counter-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..afec9211245d2c57e78707bc8fff539a321a4dc3 --- /dev/null +++ b/packages/counter-plugin/src/counter-plugin.provider.ts @@ -0,0 +1,9 @@ +import { IAppCounter } from '@ibiz/model-core'; +import { AppCounter, IAppCounterProvider } from '@ibiz-template/runtime'; +import { CounterPlugin } from './counter-plugin'; + +export class CounterPluginProvider implements IAppCounterProvider { + createCounter(model: IAppCounter): AppCounter { + return new CounterPlugin(model); + } +} diff --git a/packages/counter-plugin/src/counter-plugin.ts b/packages/counter-plugin/src/counter-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..3cff40969fc3742b401aa0943fda89f6dbc9121d --- /dev/null +++ b/packages/counter-plugin/src/counter-plugin.ts @@ -0,0 +1,12 @@ +import { AppCounter } from '@ibiz-template/runtime'; + +export class CounterPlugin extends AppCounter { + protected async load(): Promise { + this.data = { + count1: 11, + count2: 22, + }; + this.evt.emit('change', this.data); + return this.data; + } +} diff --git a/packages/counter-plugin/src/index.ts b/packages/counter-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9b22b63878fca4257fd5ee78b678b9de1e439d2 --- /dev/null +++ b/packages/counter-plugin/src/index.ts @@ -0,0 +1,13 @@ +import { App } from 'vue'; +import { registerAppCounterProvider } from '@ibiz-template/runtime'; +import { CounterPluginProvider } from './counter-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册计数器插件适配器, APPCOUNTER是插件类型,R9CounterPluginId是插件标识 + registerAppCounterProvider( + 'APPCOUNTER_R9CounterPluginId', + () => new CounterPluginProvider(), + ); + }, +}; diff --git a/packages/counter-plugin/tsconfig.json b/packages/counter-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/counter-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/counter-plugin/tsconfig.node.json b/packages/counter-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/counter-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/counter-plugin/vite.config.ts b/packages/counter-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/counter-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/de-action-plugin/CHANGELOG.md b/packages/de-action-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/de-action-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/de-action-plugin/README.md b/packages/de-action-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ec2ccfadf74a80d4e67f5e9d4e50dca7014ee7e9 --- /dev/null +++ b/packages/de-action-plugin/README.md @@ -0,0 +1,174 @@ +# 实体行为插件 + +## 新建应用实体方法插件 + +新建应用实体方法插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的实体行为上绑定插件 + + + +## 插件机制 + +执行服务方法时,会通过实体获取对应的实体服务,然后再执行实体服务的exec方法。实体服务内部会通过模型获取对应的实体行为适配器,然后调用实体行为适配器的create方法获取实体服务方法实例。详情如下: + +```tsx +// 获取对应的实体服务,然后再执行实体服务的exec方法 +async exec( + appDataEntityId: string, + methodName: string, + context: IContext, + params?: IData | IData[] | undefined, + params2?: IParams | undefined, + header?: IData, +): Promise> { + // 优先处理开启应用实体服务映射情况 + const { targetApp, targetAppDataEntityId } = + await this.computeAppDEMappingParam(context, appDataEntityId); + if (targetApp && targetAppDataEntityId) { + const result = await targetApp.deService.exec( + targetAppDataEntityId, + methodName, + context, + params, + params2, + header, + ); + return result; + } + // 标准逻辑 + const service = await this.getService(context, appDataEntityId); + const result = await service.exec( + methodName, + context, + params, + params2, + header, + ); + return result; +} + +// 获取对应的实体行为适配器,然后调用实体行为适配器的create方法获取实体服务方法实例 +async getMethod( + id: string, + acMode: boolean = false, +): Promise { + const cacheId = acMode ? `ac-${id}` : id; + if (this.methodMap.has(cacheId)) { + return this.methodMap.get(cacheId)!; + } + const model = findAppDEMethod(this.model, id) as IAppDEMethod; + + if (!model) { + throw new RuntimeModelError( + this.model, + ibiz.i18n.t('runtime.service.noFoundServiceMethod', { id }), + ); + } + + // 获取适配器 + const provider = await getDEMethodProvider(model); + if (!provider) { + throw new ModelError( + model, + ibiz.i18n.t('runtime.service.UnsupportedServiceMethod', { + methodType: model.methodType, + }), + ); + } + const method: Method = provider.create(this, this.model, model, { + acMode, + localMode: this.isLocalMode, + }); + this.methodMap.set(cacheId, method); + return method; +} +``` + +## 插件示例 + +### 插件效果 + +表单获取草稿数据时会给返回的数据附加主键值,并将当前系统用户名称和当前系统时间赋值给名称和开始时间属性 + + + +### 插件结构 + +``` +|─ ─ de-action-plugin 实体行为插件顶层目录,可根据实际业务命名 + |─ ─ src 实体行为插件源代码目录 +​ |─ ─ de-action-plugin.provider.ts 实体行为插件适配器 +​ |─ ─ de-action-plugin.ts 实体行为插件实现类 +​ |─ ─ index.ts 实体行为插件入口文件 +``` + +### 实体行为插件入口文件 + +实体行为插件入口文件会全局注册实体行为插件适配器,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerDEMethodProvider } from '@ibiz-template/runtime'; +import { DeActionPluginProvider } from './de-action-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册实体行为插件适配器, DEMETHOD是插件类型,R9DeActionPluginId是插件标识 + registerDEMethodProvider( + 'DEMETHOD_R9DeActionPluginId', + () => new DeActionPluginProvider(), + ); + }, +}; +``` + +### 实体行为插件实现类 + +实体行为插件实现类可继承基础应用方法Method,重写部分逻辑。 + +### 实体行为插件适配器 + +实体行为插件适配器的create方法返回实体行为插件实现类的实例。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/de-action-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import DeActionPlugin from '@ibiz-plugin-template/de-action-plugin'; + +export default { + install(app: App) { + app.use(DeActionPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/de-action-plugin/); + }, +}; +``` diff --git a/packages/de-action-plugin/RUNTIME-PLUGIN-MODE.md b/packages/de-action-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/de-action-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/de-action-plugin/package.json b/packages/de-action-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..81b37134d9a1e143b6f983095119a3b7c93dcd7f --- /dev/null +++ b/packages/de-action-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/de-action-plugin", + "version": "0.0.1", + "description": "实体行为插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/de-action-plugin/public/docs/image.png b/packages/de-action-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..fa93f8bf0933c652cb0dc52c3f315eb88a9ec73c Binary files /dev/null and b/packages/de-action-plugin/public/docs/image.png differ diff --git a/packages/de-action-plugin/public/docs/image2.png b/packages/de-action-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..1b5d660f0654c246c9487ff4ae6892bcd0508a57 Binary files /dev/null and b/packages/de-action-plugin/public/docs/image2.png differ diff --git a/packages/de-action-plugin/public/docs/image3.png b/packages/de-action-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..83428c6484ca8760fc07dd7e9c61e942ae778a04 Binary files /dev/null and b/packages/de-action-plugin/public/docs/image3.png differ diff --git a/packages/de-action-plugin/scripts/link.sh b/packages/de-action-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/de-action-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/de-action-plugin/src/de-action-plugin.provider.ts b/packages/de-action-plugin/src/de-action-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..e84886b38f205c57430ee3bdfb641aaf4a01926e --- /dev/null +++ b/packages/de-action-plugin/src/de-action-plugin.provider.ts @@ -0,0 +1,19 @@ +import { + IAppDEService, + IDEMethodCreateOptions, + IDEMethodProvider, + Method, +} from '@ibiz-template/runtime'; +import { IAppDataEntity, IAppDEMethod } from '@ibiz/model-core'; +import { DeActionPlugin } from './de-action-plugin'; + +export class DeActionPluginProvider implements IDEMethodProvider { + create( + service: IAppDEService, + entity: IAppDataEntity, + method: IAppDEMethod, + opts: IDEMethodCreateOptions, + ): Method { + return new DeActionPlugin(service, entity, method, opts.localMode); + } +} diff --git a/packages/de-action-plugin/src/de-action-plugin.ts b/packages/de-action-plugin/src/de-action-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..036502cb6090992eb31933f3c736d697de55f764 --- /dev/null +++ b/packages/de-action-plugin/src/de-action-plugin.ts @@ -0,0 +1,113 @@ +import { HttpResponse, IHttpResponse, RuntimeError } from '@ibiz-template/core'; +import { IDataEntity, Method } from '@ibiz-template/runtime'; +import { IAppDEAction } from '@ibiz/model-core'; +import dayjs from 'dayjs'; +import { createUUID } from 'qx-util'; + +export class DeActionPlugin extends Method { + declare method: IAppDEAction; + + async exec( + context: IContext, + data?: IData | IData[], + params?: IParams, + ): Promise> { + let result: IHttpResponse; + switch (this.method.codeName) { + case 'Create': + result = await this.create(context, data!, params || {}); + break; + case 'Get': + result = await this.get(context, params); + break; + case 'GetDraft': + result = await this.getDraft(context, params); + break; + case 'Remove': + result = await this.remove(context, params); + break; + case 'Update': + result = await this.update(context, data!, params); + break; + default: + result = await this.getDraft(context, params); + break; + } + + return result; + } + + async create( + context: IContext, + data?: IData | IData[], + params?: IParams, + ): Promise> { + if (!data) { + throw new RuntimeError('create行为没有传data'); + } + const path = this.calcPath(context); + const res = await this.app.net.post(path, data, params); + res.data = await this.result.handle(context, res.data); + return res as IHttpResponse; + } + + async remove( + context: IContext, + params?: IParams, + ): Promise> { + const path = this.calcPath(context); + const res = await this.app.net.delete( + `${path}/${context[this.entity.codeName!.toLowerCase()]}`, + params, + ); + return res as IHttpResponse; + } + + async update( + context: IContext, + data?: IData | IData[], + params?: IParams, + ): Promise> { + if (!data) { + throw new RuntimeError('update行为没有传data'); + } + const path = this.calcPath(context); + const res = await this.app.net.put( + `${path}/${context[this.entity.codeName!.toLowerCase()]}`, + data, + params, + ); + res.data = await this.result.handle(context, res.data); + return res as IHttpResponse; + } + + async get( + context: IContext, + params: IParams = {}, + ): Promise> { + const path = this.calcPath(context); + const res = await this.app.net.get( + `${path}/${context[this.entity.codeName!.toLowerCase()]}`, + params, + ); + res.data = await this.result.handle(context, res.data); + return res as IHttpResponse; + } + + async getDraft( + context: IContext, + params?: IParams, + ): Promise> { + const path = this.calcPath(context); + const res = await this.app.net.get(`${path}/get_draft`, params); + res.data = await this.result.handle(context, res.data); + if (res.data) { + res.data.srfkey = createUUID(); + if (context.plugin_type === 'de_action') { + res.data.name = context.srfusername; + res.data.start_time = dayjs().format('YYYY-MM-DD HH:mm:ss'); + } + } + return res; + } +} diff --git a/packages/de-action-plugin/src/index.ts b/packages/de-action-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd04e48dd06c690353627e39815ce2ff97ec7c0d --- /dev/null +++ b/packages/de-action-plugin/src/index.ts @@ -0,0 +1,13 @@ +import { App } from 'vue'; +import { registerDEMethodProvider } from '@ibiz-template/runtime'; +import { DeActionPluginProvider } from './de-action-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册实体行为插件适配器, DEMETHOD是插件类型,R9DeActionPluginId是插件标识 + registerDEMethodProvider( + 'DEMETHOD_R9DeActionPluginId', + () => new DeActionPluginProvider(), + ); + }, +}; diff --git a/packages/de-action-plugin/tsconfig.json b/packages/de-action-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/de-action-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/de-action-plugin/tsconfig.node.json b/packages/de-action-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/de-action-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/de-action-plugin/vite.config.ts b/packages/de-action-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..12525a15abc9d9376f392d67e5f2633e52863640 --- /dev/null +++ b/packages/de-action-plugin/vite.config.ts @@ -0,0 +1,58 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + sourcemap: true + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/editor-plugin/CHANGELOG.md b/packages/editor-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/editor-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/editor-plugin/README.md b/packages/editor-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e6bd05e90d6010fab8a5d00beb21d2090dae3f84 --- /dev/null +++ b/packages/editor-plugin/README.md @@ -0,0 +1,292 @@ +# 编辑器插件 + +## 新建编辑器自定义绘制插件 + +新建编辑器自定义绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 新建编辑器样式 + +新建编辑器样式,并且绑定上一步新建的编辑器插件。 + + + +## 在对应的编辑器上绑定编辑器样式 + +这儿以span标签编辑器为例: + + + +## 插件机制 + +表单项在绘制编辑器时,是通过编辑器适配器的formEditor属性去获取组件名称,然后进行绘制的,并且渲染时会将createController方法返回的控制器传给对应的编辑器组件。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 表单项绘制编辑器 +render() { + if (!this.c.state.visible || this.c.model.editor?.editorType === 'HIDDEN') { + return null; + } + // 编辑器内容 + let editor = null; + const compositeItem = this.c.model.compositeItem; + // 复合表单项 + if (compositeItem) { + const { editorItems = [] } = this.c.model.editor || {}; + editor = editorItems.map((item: IData) => { + const controller = this.c.form.details[item.id] as FormItemController; + return ( + + ); + }); + } else { + const editMode = this.c.editor?.model?.editorParams?.editMode; + const editorProps = { + style: this.c.editor?.style, + value: this.c.value, + data: this.c.data, + controller: this.c.editor, + disabled: this.c.state.disabled, + readonly: this.c.state.readonly, + onChange: this.onValueChange, + extraParams: this.extraParams, + controlParams: editMode + ? { ...this.c.form.controlParams, editmode: editMode } + : this.c.form.controlParams, + onFocus: (event: MouseEvent) => this.c.onFocus(event), + onBlur: (event: MouseEvent) => this.c.onBlur(event), + onEnter: (event: MouseEvent) => this.c.onEnter(event), + onClick: (event: MouseEvent, params: IParams) => + this.c.onClick(event, params), + ...this.attrs, + }; + if (this.$slots.default) { + editor = this.$slots.default(editorProps); + } else if (this.c.editorProvider) { + const component = resolveComponent(this.c.editorProvider.formEditor); + editor = h(component, { + ...editorProps, + }); + } else { + editor = ( + + ); + } + } + + return ( + this.c.onClick(event)} + > + {editor} + + ); +} +``` + +表格在绘制编辑器时,是通过编辑器适配器的gridEditor属性去获取组件名称,然后进行绘制的,并且渲染时会将createController方法返回的控制器传给对应的编辑器组件。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 表格绘制编辑器 +render() { + const val = this.row.data[this.c.fieldName]; + + return ( + + {this.c.editorProvider && + h(resolveComponent(this.c.editorProvider.gridEditor), { + class: this.ns.e('editor'), + value: val, + data: this.row.data, + controller: this.c.editor, + overflowMode: this.c.grid.overflowMode, + onChange: this.rowDataChange, + onInfoTextChange: this.onInfoTextChange, + title: this.tooltip, + ...this.editorProps, + ...this.attrs, + })} + + ); +} +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ editor-plugin 编辑器插件顶层目录,可根据实际业务命名 + |─ ─ src 编辑器插件源代码目录 +​ |─ ─ editor-plugin.controller.ts 编辑器插件控制器 +​ |─ ─ editor-plugin.provider.ts 编辑器插件适配器 +​ |─ ─ editor-plugin.scss 编辑器插件样式 +​ |─ ─ editor-plugin.tsx 编辑器插件组件 +​ |─ ─ index.ts 编辑器插件入口文件 +``` + +### 编辑器插件入口文件 + +编辑器插件入口文件会全局注册编辑器插件适配器和编辑器插件组件,供外部使用。 + +```typescript +import { registerEditorProvider } from '@ibiz-template/runtime'; +import { App } from 'vue'; +import { EditorPlugin } from './editor-plugin'; +import { EditorPluginProvider } from './editor-plugin.provider'; + +export default { + install(app: App) { + // 全局注册编辑器插件组件 + app.component(EditorPlugin.name!, EditorPlugin); + // 全局注册编辑器插件适配器,EDITOR_CUSTOMSTYLE是插件类型,R9EditorPluginId是插件标识 + registerEditorProvider( + 'EDITOR_CUSTOMSTYLE_R9EditorPluginId', + () => new EditorPluginProvider(), + ); + }, +}; +``` + +### 编辑器插件组件 + +编辑器插件组件使用tsx的书写方式,承载编辑器绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 编辑器插件适配器 + +编辑器插件适配器主要通过formEditor属性和gridEditor属性指定编辑器实际要绘制的组件,并且通过createController方法返回需传递给编辑器的控制器。 + +### 编辑器插件控制器 + +编辑器插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/editor-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import EditorPlugin from '@ibiz-plugin-template/editor-plugin'; + +export default { + install(app: App) { + app.use(EditorPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/editor-plugin/); + }, +}; +``` + +## 附录 + +| 编辑器类型 | UI呈现 | 控制器 | +| :----------------------------: | :-------------------: | :-----------------------------: | +| 数组编辑器 | IBizArray | ArrayEditorController | +| 自动填充 | IBizAutoComplete | AutoCompleteEditorController | +| 自动填充(只能选择) | IBizAutoComplete | AutoCompleteEditorController | +| 自动填充(无按钮) | IBizAutoComplete | AutoCompleteEditorController | +| 自动填充(只能选择、无按钮) | IBizAutoComplete | AutoCompleteEditorController | +| 级联选择器 | IBizCascader | CascaderEditorController | +| 选项框 | IBizCheckbox | CheckBoxEditorController | +| 选项框列表 | IBizCheckboxList | CheckBoxListEditorController | +| 代码编辑框 | IBizCode | CodeEditorController | +| 颜色选择器 | IBizColorPicker | ColorPickerEditorController | +| 地址框(选择) | IBizMPicker | PickerEditorController | +| 地址框(支持选择、AC) | IBizMPicker | PickerEditorController | +| 数据选择 | IBizPicker | PickerEditorController | +| 数据选择(无AC) | IBizPicker | PickerEditorController | +| 数据选择(无AC、数据链接) | IBizPicker | PickerEditorController | +| 数据选择(下拉、数据链接) | IBizPicker | PickerEditorController | +| 数据选择(无按钮) | IBizPicker | PickerEditorController | +| 数据选择(下拉) | IBizPickerDropDown | PickerEditorController | +| 数据选择(嵌入选择视图) | IBizPickerEmbedView | PickerEditorController | +| 数据选择(数据链接) | IBizPickerLink | PickerEditorController | +| 数据链接 | IBizPickerLink | PickerEditorController | +| 数据选择(下拉视图) | IBizPickerSelectView | PickerEditorController | +| 数据选择(下拉视图、数据链接) | IBizPickerSelectView | PickerEditorController | +| 时间选择器 | IBizDatePicker | DatePickerEditorController | +| 时间范围选择器 | IBizDateRangePicker | DateRangeEditorController | +| 下拉列表框 | IBizDropdown | DropDownListEditorController | +| HTML编辑框 | IBizHtml | HtmlEditorController | +| 列表框(选择) | IBizListBox | ListBoxEditorController | +| Markdown编辑框 | IBizMarkDown | MarkDownEditorController | +| 数值范围编辑框 | IBizNumberRangePicker | NumberRangeEditorController | +| 单选项列表 | IBizRadio | RadioButtonListEditorController | +| 评分器 | IBizRate | RateEditorController | +| 直接内容 | IBizRaw | RawEditorController | +| 滑动输入条 | IBizSlider | SliderEditorController | +| 标签 | IBizSpan | SpanEditorController | +| 标签(数据链接) | IBizSpanLink | SpanEditorController | +| 步进器 | IBizStepper | StepperEditorController | +| 开关部件 | IBizSwitch | SwitchEditorController | +| IP地址输入框 | IBizInputIP | TextBoxEditorController | +| 数值框 | IBizInputNumber | TextBoxEditorController | +| 文本框 | IBizInput | TextBoxEditorController | +| 密码框 | IBizInput | TextBoxEditorController | +| 多行输入框 | IBizInput | TextBoxEditorController | +| 文件控件 | IBizFileUpload | UploadEditorController | +| 图片控件(单项、直接内容) | IBizImagePreview | UploadEditorController | +| 图片控件 | IBizImageUpload | UploadEditorController | diff --git a/packages/editor-plugin/RUNTIME-PLUGIN-MODE.md b/packages/editor-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/editor-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/editor-plugin/package.json b/packages/editor-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..7417202880ee0a9c4a3ae0e42e1f9de2f3a9633b --- /dev/null +++ b/packages/editor-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/editor-plugin", + "version": "0.0.1", + "description": "编辑器插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/editor-plugin/public/docs/image.png b/packages/editor-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..bec7031974931ad96f4b72d56876d3d442653704 Binary files /dev/null and b/packages/editor-plugin/public/docs/image.png differ diff --git a/packages/editor-plugin/public/docs/image2.png b/packages/editor-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..04e3c1caa304852637a1dcdcfecdd40147298432 Binary files /dev/null and b/packages/editor-plugin/public/docs/image2.png differ diff --git a/packages/editor-plugin/public/docs/image3.png b/packages/editor-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..ae315e7be1483cef61f70dfafb8de4462ce1ef51 Binary files /dev/null and b/packages/editor-plugin/public/docs/image3.png differ diff --git a/packages/editor-plugin/public/docs/image4.png b/packages/editor-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..f779fba03d398dc662a5217ea0a86edaef80e789 Binary files /dev/null and b/packages/editor-plugin/public/docs/image4.png differ diff --git a/packages/editor-plugin/public/docs/image5.png b/packages/editor-plugin/public/docs/image5.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4067e551850e0bae9be4220b08f32b530ccf3a Binary files /dev/null and b/packages/editor-plugin/public/docs/image5.png differ diff --git a/packages/editor-plugin/scripts/link.sh b/packages/editor-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/editor-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/editor-plugin/src/editor-plugin.controller.ts b/packages/editor-plugin/src/editor-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f1511c4faa040f3cf57d87b0bb34648ed929de4 --- /dev/null +++ b/packages/editor-plugin/src/editor-plugin.controller.ts @@ -0,0 +1,9 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { IEditor } from '@ibiz/model-core'; + +export class EditorPluginController extends EditorController { + protected async onInit(): Promise { + ibiz.log.info('编辑器控制器初始化'); + await super.onInit(); + } +} diff --git a/packages/editor-plugin/src/editor-plugin.provider.ts b/packages/editor-plugin/src/editor-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..f8c62b160ac6826e5d0d7dcb1e2f847014a34040 --- /dev/null +++ b/packages/editor-plugin/src/editor-plugin.provider.ts @@ -0,0 +1,21 @@ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IEditor } from '@ibiz/model-core'; +import { EditorPluginController } from './editor-plugin.controller'; + +export class EditorPluginProvider implements IEditorProvider { + formEditor: string = 'IBizEditorPlugin'; + + gridEditor: string = 'IBizEditorPlugin'; + + async createController( + editorModel: IEditor, + parentController: IEditorContainerController, + ): Promise { + const c = new EditorPluginController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/packages/editor-plugin/src/editor-plugin.scss b/packages/editor-plugin/src/editor-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..30e6163e0d928f7764b919a62a0d70220a69826e --- /dev/null +++ b/packages/editor-plugin/src/editor-plugin.scss @@ -0,0 +1,4 @@ +@include b(editor-plugin){ + width: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/editor-plugin/src/editor-plugin.tsx b/packages/editor-plugin/src/editor-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fddd7e0684174654c3a0f5f9c90a1746381befed --- /dev/null +++ b/packages/editor-plugin/src/editor-plugin.tsx @@ -0,0 +1,36 @@ +import { defineComponent } from 'vue'; +import { + getEditorEmits, + getSpanProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { EditorPluginController } from './editor-plugin.controller'; +import './editor-plugin.scss'; + +export const EditorPlugin = defineComponent({ + name: 'IBizEditorPlugin', + props: getSpanProps(), + emits: getEditorEmits(), + setup(props) { + const ns = useNamespace('editor-plugin'); + const c = props.controller; + + return { + c, + ns, + }; + }, + render() { + return ( +
+ 编辑器插件内容 +
+ ); + }, +}); diff --git a/packages/editor-plugin/src/index.ts b/packages/editor-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..668d4bbc0852774a23e30478d5f17f5a7cb09cb6 --- /dev/null +++ b/packages/editor-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { registerEditorProvider } from '@ibiz-template/runtime'; +import { App } from 'vue'; +import { EditorPlugin } from './editor-plugin'; +import { EditorPluginProvider } from './editor-plugin.provider'; + +export default { + install(app: App) { + // 全局注册编辑器插件组件 + app.component(EditorPlugin.name!, EditorPlugin); + // 全局注册编辑器插件适配器,EDITOR_CUSTOMSTYLE是插件类型,R9EditorPluginId是插件标识 + registerEditorProvider( + 'EDITOR_CUSTOMSTYLE_R9EditorPluginId', + () => new EditorPluginProvider(), + ); + }, +}; diff --git a/packages/editor-plugin/tsconfig.json b/packages/editor-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/editor-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/editor-plugin/tsconfig.node.json b/packages/editor-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/editor-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/editor-plugin/vite.config.ts b/packages/editor-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/editor-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/form-user-control-plugin/CHANGELOG.md b/packages/form-user-control-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/form-user-control-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/form-user-control-plugin/README.md b/packages/form-user-control-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3f49e52b5e0ca8ebb18038863704e001fc3873d6 --- /dev/null +++ b/packages/form-user-control-plugin/README.md @@ -0,0 +1,194 @@ +# 表单成员插件示例 + +## 新建表单自定义控件绘制插件 + +新建表单自定义控件绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的表单成员上绑定插件 + +这儿以表单项为例: + + + +## 插件机制 + +表单在绘制表单成员时,是通过表单成员适配器上的component属性去获取组件名称,然后进行绘制的,并且渲染时会将createController方法返回的控制器传给对应的表单成员组件。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```ts +// 绘制表单成员 +const renderByDetailType = ( + detail: IDEFormDetail, +): VNode | VNode[] | undefined => { + if ((detail as IDEFormItem).hidden) { + return; + } + const detailId = detail.id!; + + // 有插槽走插槽 + if (slots[detailId]) { + return renderSlot(slots, detailId, { + model: detail, + data: c.state.data, + value: c.state.data[detailId], + }); + } + + // 子插槽 + const childSlots: IData = {}; + // 表单项如果有编辑器插槽的时候,调用插槽绘制表单项的默认插槽。 + if (detail.detailType === 'FORMITEM' && slots[`${detailId}_editor`]) { + childSlots.default = (...args: IData[]): VNode[] => { + return slots[`${detailId}_editor`]!(...args); + }; + } + const childDetails = findChildFormDetails(detail); + if (childDetails.length) { + // 容器成员绘制子成员 + childSlots.default = (): (VNode[] | VNode | undefined)[] => + childDetails.map(child => { + return renderByDetailType(child); + }); + } + + // 根据适配器绘制表单成员 + const provider = c.providers[detailId]; + if (!provider) { + return ( +
+ {ibiz.i18n.t('control.form.noSupportDetailType', { + detailType: detail.detailType, + })} +
+ ); + } + const component = resolveComponent(provider.component) as string; + return h( + component, + { + modelData: detail, + controller: c.details[detailId], + key: detail.id, + attrs: renderAttrs(detail), + }, + childSlots, + ); +}; +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ form-user-control-plugin 表单成员插件顶层目录,可根据实际业务命名 + |─ ─ src 表单成员插件源代码目录 +​ |─ ─ form-user-control-plugin.controller.ts 表单成员插件控制器 +​ |─ ─ form-user-control-plugin.provider.ts 表单成员插件适配器 +​ |─ ─ form-user-control-plugin.scss 表单成员插件样式 +​ |─ ─ form-user-control-plugin.tsx 表单成员插件组件 +​ |─ ─ index.ts 表单成员插件入口文件 +``` + +### 表单成员插件入口文件 + +表单成员插件入口文件会全局注册表单成员插件适配器和表单成员插件组件,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerFormDetailProvider } from '@ibiz-template/runtime'; +import { FormUserControlPlugin } from './form-user-control-plugin'; +import { FormUserControlPluginProvider } from './form-user-control-plugin.provider'; + +export default { + install(app: App) { + // 全局注册表单成员插件组件 + app.component(FormUserControlPlugin.name!, FormUserControlPlugin); + // 全局注册表单成员插件适配器,FORM_USERCONTROL是插件类型,R9FormDetailPluginId是插件标识 + registerFormDetailProvider( + 'FORM_USERCONTROL_R9FormDetailPluginId', + () => new FormUserControlPluginProvider(), + ); + }, +}; +``` + +### 表单成员插件组件 + +表单成员插件组件使用tsx的书写方式,承载表单成员绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 表单成员插件适配器 + +表单成员插件适配器主要通过component属性指定表单成员实际要绘制的组件,并且通过createController方法返回需传递给表单成员的控制器。 + +### 表单成员插件控制器 + +表单成员插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/form-user-control-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import FormUserControlPlugin from '@ibiz-plugin-template/form-user-control-plugin'; + +export default { + install(app: App) { + app.use(FormUserControlPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore( + /^@ibiz-plugin-template\/form-user-control-plugin/, + ); + }, +}; +``` + +## 附录 + +| 表单成员类型 | UI呈现 | 控制器 | +| :------------: | :------------: | :----------------------: | +| 表单按钮 | FormButton | FormButtonController | +| 表单按钮组 | FormButtonList | FormButtonListController | +| 表单关系界面 | FormDRUIPart | FormDRUIPartController | +| 表单分组面板 | FormGroupPanel | FormGroupPanelController | +| 表单项 | FormItem | FormItemController | +| 表单多数据部件 | FormMDCtrl | FormMDCtrlController | +| 表单分页 | FormPageItem | FormPageController | +| 表单直接内容 | FormRawItem | FormRawItemController | +| 表单tab分页 | FormTabPage | FormTabPageController | +| 表单tab面板 | FormTabPanel | FormTabPanelController | diff --git a/packages/form-user-control-plugin/RUNTIME-PLUGIN-MODE.md b/packages/form-user-control-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/form-user-control-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/form-user-control-plugin/package.json b/packages/form-user-control-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a5915dc7c8a5c2bca6487a02ee7989f2fc3d9afc --- /dev/null +++ b/packages/form-user-control-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/form-user-control-plugin", + "version": "0.0.1", + "description": "表单成员插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/form-user-control-plugin/public/docs/image.png b/packages/form-user-control-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..cd8a44685d9f9f37d710400eb4fb47923477bf35 Binary files /dev/null and b/packages/form-user-control-plugin/public/docs/image.png differ diff --git a/packages/form-user-control-plugin/public/docs/image2.png b/packages/form-user-control-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..ba1f7075171d6afccd953c34eb87ca255abef30c Binary files /dev/null and b/packages/form-user-control-plugin/public/docs/image2.png differ diff --git a/packages/form-user-control-plugin/public/docs/image3.png b/packages/form-user-control-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..32ffab6444f0edaaa13faf988fe78cab3b37a911 Binary files /dev/null and b/packages/form-user-control-plugin/public/docs/image3.png differ diff --git a/packages/form-user-control-plugin/public/docs/image4.png b/packages/form-user-control-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..f9c95abfcab6a6f71da334089cb56c6b1fa17539 Binary files /dev/null and b/packages/form-user-control-plugin/public/docs/image4.png differ diff --git a/packages/form-user-control-plugin/scripts/link.sh b/packages/form-user-control-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/form-user-control-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/form-user-control-plugin/src/form-user-control-plugin.controller.ts b/packages/form-user-control-plugin/src/form-user-control-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..10b5ad858a8f8a67e5f7ca7b37b3f4c10a1b995b --- /dev/null +++ b/packages/form-user-control-plugin/src/form-user-control-plugin.controller.ts @@ -0,0 +1,8 @@ +import { FormItemController } from '@ibiz-template/runtime'; + +export class FormUserControlPluginController extends FormItemController { + protected async onInit(): Promise { + ibiz.log.info('表单自定义控件控制器初始化'); + await super.onInit(); + } +} diff --git a/packages/form-user-control-plugin/src/form-user-control-plugin.provider.ts b/packages/form-user-control-plugin/src/form-user-control-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a38e53f4c7d7d236d70fb1eca564b76a3a17390 --- /dev/null +++ b/packages/form-user-control-plugin/src/form-user-control-plugin.provider.ts @@ -0,0 +1,21 @@ +import { + FormController, + IFormDetailContainerController, + IFormDetailProvider, +} from '@ibiz-template/runtime'; +import { IDEFormDetail } from '@ibiz/model-core'; +import { FormUserControlPluginController } from './form-user-control-plugin.controller'; + +export class FormUserControlPluginProvider implements IFormDetailProvider { + component: string = 'IBizFormUserControlPlugin'; + + async createController( + detailModel: IDEFormDetail, + form: FormController, + parent: IFormDetailContainerController | undefined, + ): Promise { + const c = new FormUserControlPluginController(detailModel, form, parent); + await c.init(); + return c; + } +} diff --git a/packages/form-user-control-plugin/src/form-user-control-plugin.scss b/packages/form-user-control-plugin/src/form-user-control-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..31a65b4373548d89177b6805292593c5d9878709 --- /dev/null +++ b/packages/form-user-control-plugin/src/form-user-control-plugin.scss @@ -0,0 +1,4 @@ +@include b(form-user-control-plugin){ + width: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/form-user-control-plugin/src/form-user-control-plugin.tsx b/packages/form-user-control-plugin/src/form-user-control-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8dd20b1ff903011a2129a42ce100e65d23e5e056 --- /dev/null +++ b/packages/form-user-control-plugin/src/form-user-control-plugin.tsx @@ -0,0 +1,37 @@ +import { defineComponent, PropType } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IDEFormDetail } from '@ibiz/model-core'; +import { FormUserControlPluginController } from './form-user-control-plugin.controller'; +import './form-user-control-plugin.scss'; + +export const FormUserControlPlugin = defineComponent({ + name: 'IBizFormUserControlPlugin', + props: { + modelData: { + type: Object as PropType, + required: true, + }, + controller: { + type: FormUserControlPluginController, + required: true, + }, + }, + setup() { + const ns = useNamespace('form-user-control-plugin'); + + return { ns }; + }, + render() { + return ( +
+ 表单成员插件内容 +
+ ); + }, +}); diff --git a/packages/form-user-control-plugin/src/index.ts b/packages/form-user-control-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..271caefcf1adb5ad768d1213c4daa526b92c76d9 --- /dev/null +++ b/packages/form-user-control-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue'; +import { registerFormDetailProvider } from '@ibiz-template/runtime'; +import { FormUserControlPlugin } from './form-user-control-plugin'; +import { FormUserControlPluginProvider } from './form-user-control-plugin.provider'; + +export default { + install(app: App) { + // 全局注册表单成员插件组件 + app.component(FormUserControlPlugin.name!, FormUserControlPlugin); + // 全局注册表单成员插件适配器,FORM_USERCONTROL是插件类型,R9FormDetailPluginId是插件标识 + registerFormDetailProvider( + 'FORM_USERCONTROL_R9FormDetailPluginId', + () => new FormUserControlPluginProvider(), + ); + }, +}; diff --git a/packages/form-user-control-plugin/tsconfig.json b/packages/form-user-control-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/form-user-control-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/form-user-control-plugin/tsconfig.node.json b/packages/form-user-control-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/form-user-control-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/form-user-control-plugin/vite.config.ts b/packages/form-user-control-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/form-user-control-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/global-plugin/CHANGELOG.md b/packages/global-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/global-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/global-plugin/README.md b/packages/global-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..29e99e442a47900db6ac9202028c0d356d60df97 --- /dev/null +++ b/packages/global-plugin/README.md @@ -0,0 +1,114 @@ +# 全局插件 + +当前全局插件处于关闭状态。若要应用全局插件,需先将全局插件入口文件中被注释的逻辑代码还原,随后在配置平台里启用应用插件。 + +## 新建自定义部件绘制插件 + +新建自定义部件绘制插件,需要注意的是插件标识需设置为GLOBAL_APP_UTIL,除此之外,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 新建应用插件,并绑定上一步创建的自定义部件绘制插件 + +需要注意的是代码标识需设置为GLOBAL_APP_UTIL。 + + + +## 插件机制 + +应用初始化的时候,如果应用配置了应用插件,则会去加载应用插件,然后替换标准的组件和功能,详情如下: + +```tsx +// 加载应用插件 +async loadGlobalAppUtil(): Promise { + const appUtilTag: string = 'GLOBAL_APP_UTIL'; + const globalAppUtilPlugin = ibiz.hub.getPlugin( + appUtilTag.toLowerCase(), + this.appId, + ); + if (!globalAppUtilPlugin) return; + if ( + globalAppUtilPlugin.refMode !== 'APP' || + globalAppUtilPlugin.refTag !== appUtilTag + ) + return; + await ibiz.plugin.loadPlugin(globalAppUtilPlugin as ISysPFPlugin); +} +``` + +## 插件示例 + +### 插件效果 + +表格加载数据后会弹出自定义提示 + + + +### 插件结构 + +``` +|─ ─ global-plugin 全局插件顶层目录,可根据实际业务命名 + |─ ─ src 全局插件源代码目录 +​ |─ ─ grid-view-engine.ts 表格视图引擎 +​ |─ ─ index.ts 全局插件入口文件 +``` + +### 全局插件入口文件 + +全局插件入口文件会换标准的组件和功能,供外部使用。 + +```typescript +import { App } from 'vue'; +import { IViewController } from '@ibiz-template/runtime'; +import { GridViewEngine } from './grid-view-engine'; + +export default { + install(_app: App) { + // 替换标准的表格视图引擎 + ibiz.engine.register( + 'VIEW_GridView', + (c: IViewController) => new GridViewEngine(c), + ); + }, +}; +``` + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/global-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import GlobalPlugin from '@ibiz-plugin-template/global-plugin'; + +export default { + install(app: App) { + app.use(GlobalPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/global-plugin/); + }, +}; +``` diff --git a/packages/global-plugin/RUNTIME-PLUGIN-MODE.md b/packages/global-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/global-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/global-plugin/package.json b/packages/global-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..2bb7a84d559876dfae60f9852d023345addbd908 --- /dev/null +++ b/packages/global-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/global-plugin", + "version": "0.0.1", + "description": "全局插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/global-plugin/public/docs/image.png b/packages/global-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..243dea4d49c0b90a6ac3a71ab139f383de9da864 Binary files /dev/null and b/packages/global-plugin/public/docs/image.png differ diff --git a/packages/global-plugin/public/docs/image2.png b/packages/global-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..d9871efb80cb09c5396fc1e3cd4e772769963379 Binary files /dev/null and b/packages/global-plugin/public/docs/image2.png differ diff --git a/packages/global-plugin/public/docs/image3.png b/packages/global-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..7b8b15983914faf0c4a046692bebb15241714b9a Binary files /dev/null and b/packages/global-plugin/public/docs/image3.png differ diff --git a/packages/global-plugin/scripts/link.sh b/packages/global-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/global-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/global-plugin/src/grid-view-engine.ts b/packages/global-plugin/src/grid-view-engine.ts new file mode 100644 index 0000000000000000000000000000000000000000..07d94da2316d40cbad4c91e16fb9998d0e5c1e54 --- /dev/null +++ b/packages/global-plugin/src/grid-view-engine.ts @@ -0,0 +1,97 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { RuntimeError } from '@ibiz-template/core'; +import { + ViewController, + IGridViewEvent, + IGridViewState, + MDViewEngine, + IGridController, + SysUIActionTag, + MDCtrlLoadParams, + IApiGridViewCall, +} from '@ibiz-template/runtime'; +import { IAppDEGridView } from '@ibiz/model-core'; + +export class GridViewEngine extends MDViewEngine { + declare protected view: ViewController< + IAppDEGridView, + IGridViewState, + IGridViewEvent + >; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + async call(key: keyof IApiGridViewCall, args: any): Promise { + if (key === SysUIActionTag.NEW_ROW) { + this.grid.newRow(); + return null; + } + if (key === SysUIActionTag.TOGGLE_ROW_EDIT) { + this.grid.toggleRowEdit(); + return null; + } + if (key === SysUIActionTag.SAVE || key === SysUIActionTag.SAVE_ROW) { + this.grid.saveAll(); + return null; + } + if (key === SysUIActionTag.EXPAND) { + const { srfcollapsetag } = args.params || {}; + let tag = srfcollapsetag || ''; + if (!tag) { + const { selectedData } = this.grid.state; + const selectedGroup = selectedData.filter(x => x.isGroupData); + if (selectedGroup.length > 0) { + tag = selectedGroup[0].srfkey; + } + } + if (!tag) { + throw new RuntimeError(ibiz.i18n.t('viewEngine.noExpandTag')); + } + this.grid.changeCollapse({ tag, expand: true }); + return null; + } + if (key === SysUIActionTag.COLLAPSE) { + const { srfcollapsetag } = args.params || {}; + let tag = srfcollapsetag || ''; + if (!tag) { + const { selectedData } = this.grid.state; + const selectedGroup = selectedData.filter(x => x.isGroupData); + if (selectedGroup.length > 0) { + tag = selectedGroup[0].srfkey; + } + } + if (!tag) { + throw new RuntimeError(ibiz.i18n.t('viewEngine.noCollapseTag')); + } + this.grid.changeCollapse({ tag, expand: false }); + return null; + } + if (key === SysUIActionTag.EXPANDALL) { + this.grid.changeCollapse({ expand: true } as any); + return null; + } + if (key === SysUIActionTag.COLLAPSEALL) { + this.grid.changeCollapse({ expand: false } as any); + return null; + } + return super.call(key, args); + } + + get grid(): IGridController { + return this.view.getController('grid') as IGridController; + } + + async onCreated(): Promise { + super.onCreated(); + const { model } = this.view; + if (!this.view.slotProps.grid) { + this.view.slotProps.grid = {}; + } + this.view.slotProps.grid.mdctrlActiveMode = model.gridRowActiveMode!; + this.view.slotProps.grid.rowEditOpen = model.rowEditDefault!; + } + + protected async load(args: MDCtrlLoadParams = {}): Promise { + await this.xdataControl.load({ isInitialLoad: true, ...args }); + ibiz.message.success('全局插件触发成功'); + } +} diff --git a/packages/global-plugin/src/index.ts b/packages/global-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f24dee537261907b9b83051fe74afe6741ec58d --- /dev/null +++ b/packages/global-plugin/src/index.ts @@ -0,0 +1,13 @@ +import { App } from 'vue'; +// import { IViewController } from '@ibiz-template/runtime'; +// import { GridViewEngine } from './grid-view-engine'; + +export default { + install(_app: App) { + // 替换标准的表格视图引擎 + // ibiz.engine.register( + // 'VIEW_GridView', + // (c: IViewController) => new GridViewEngine(c), + // ); + }, +}; diff --git a/packages/global-plugin/tsconfig.json b/packages/global-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/global-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/global-plugin/tsconfig.node.json b/packages/global-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/global-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/global-plugin/vite.config.ts b/packages/global-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/global-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/grid-column-plugin/CHANGELOG.md b/packages/grid-column-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/grid-column-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/grid-column-plugin/README.md b/packages/grid-column-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..91a7f4687ca372f1e63fab2ce507289dc6b38763 --- /dev/null +++ b/packages/grid-column-plugin/README.md @@ -0,0 +1,216 @@ +## 表格列插件 + +## 新建数据表格列绘制插件 + +新建数据表格列绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的表格列上绑定插件 + + + +## 插件机制 + +表格在绘制表格列时,是通过表格列适配器上的component属性去获取组件名称,然后进行绘制的,并且渲染时会将createController方法返回的控制器传给对应的表格列组件。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 绘制表格列 +function renderColumn( + c: GridController, + model: IDEGridColumn, + renderColumns: IDEGridColumn[], + index: number, +): VNode | null { + const { codeName: columnName, width } = model; + + // 查缓存,有缓存用缓存,没缓存的用模型 + const columnC = c.columns[columnName!]; + const columnState = c.state.columnStates.find( + item => item.key === columnName, + )!; + + // 如果没有配置自适应列,则最后一列变为自适应列 + const widthFlexGrow = + columnC.isAdaptiveColumn || + (!c.hasAdaptiveColumn && index === renderColumns.length - 1); + + const widthName = widthFlexGrow ? 'min-width' : 'width'; + + const tempWidth = columnState?.columnWidth || width; + // 表格列自定义 + return ( + { + const fieldName = model.id!.toLowerCase(); + if (a[fieldName] < b[fieldName] || !a[fieldName]) return -1; + if (a[fieldName] > b[fieldName] || !b[fieldName]) return 1; + return 0; + }} + align={model.align?.toLowerCase() || 'center'} + > + {{ + header: ({ column }: IData) => { + return ( + + ); + }, + default: ({ row }: IData): VNode | null => { + let elRow = row; // element表格数据 + if (row.isGroupData) { + // 有第一条数据时,分组那一行绘制第一条数据 + elRow = row.first; + } + + const rowState = c.findRowState(elRow); + if (rowState) { + // 常规非业务单元格由表格绘制(性能优化) + if ( + model.columnType === 'DEFGRIDCOLUMN' || + model.columnType === 'DEFTREEGRIDCOLUMN' + ) { + if ( + c.providers[columnName!].component === 'IBizGridFieldColumn' && + !columnC.isCustomCode && + !(columnC as GridFieldColumnController).codeList + ) { + return renderFieldColumn( + columnC as GridFieldColumnController, + rowState, + ); + } + } + const comp = resolveComponent(c.providers[columnName!].component); + return h(comp, { + controller: columnC, + row: rowState, + key: elRow.tempsrfkey + columnName, + attrs: renderAttrs(model, { + ...c.getEventArgs(), + data: rowState.data, + }), + }); + } + return null; + }, + }} + + ); +} +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ grid-column-plugin 表格列插件顶层目录,可根据实际业务命名 + |─ ─ src 表格列插件源代码目录 +​ |─ ─ grid-column-plugin.controller.ts 表格列插件控制器 +​ |─ ─ grid-column-plugin.provider.ts 表格列插件适配器 +​ |─ ─ grid-column-plugin.scss 表格列插件样式 +​ |─ ─ grid-column-plugin.tsx 表格列插件组件 +​ |─ ─ index.ts 表格列插件入口文件 +``` + +### 表格列插件入口文件 + +表格列插件入口文件会全局注册表格列插件适配器和表格列插件组件,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerGridColumnProvider } from '@ibiz-template/runtime'; +import { GridColumnPlugin } from './grid-column-plugin'; +import { GridColumnPluginProvider } from './grid-column-plugin.provider'; + +export default { + install(app: App) { + // 全局注册表格列插件组件 + app.component(GridColumnPlugin.name!, GridColumnPlugin); + // 全局注册表格列插件适配器,GRID_COLRENDER是插件类型,R9GridColumnPluginId是插件标识 + registerGridColumnProvider( + 'GRID_COLRENDER_R9GridColumnPluginId', + () => new GridColumnPluginProvider(), + ); + }, +}; +``` + +### 表格列插件组件 + +表格列插件组件使用tsx的书写方式,承载表格列绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 表格列插件适配器 + +表格列插件适配器主要通过component属性指定表格列单元格实际要绘制的组件,并且通过createController方法返回需传递给表格列的控制器。 + +### 表格列插件控制器 + +表格列插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/grid-column-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import GridColumnPlugin from '@ibiz-plugin-template/grid-column-plugin'; + +export default { + install(app: App) { + app.use(GridColumnPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/grid-column-plugin/); + }, +}; +``` + +## 附录 + +| 表格列类型 | UI呈现 | 控制器 | +| :----------------: | :-----------------: | :---------------------------: | +| 属性列 | GridFieldColumn | GridFieldColumnController | +| 属性列(开启行编辑) | GridFieldEditColumn | GridFieldEditColumnController | +| 分组列 | GridGroupColumn | GridGroupColumnController | +| 操作列 | GridUAColumn | GridUAColumnController | diff --git a/packages/grid-column-plugin/RUNTIME-PLUGIN-MODE.md b/packages/grid-column-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/grid-column-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/grid-column-plugin/package.json b/packages/grid-column-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..41ae4d540c14dbeecafb54ee474dd2fc486c3b7c --- /dev/null +++ b/packages/grid-column-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/grid-column-plugin", + "version": "0.0.1", + "description": "表格列插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/grid-column-plugin/public/docs/image.png b/packages/grid-column-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..7ecc6c7869b625b4517d92ec0d961fe87a521d71 Binary files /dev/null and b/packages/grid-column-plugin/public/docs/image.png differ diff --git a/packages/grid-column-plugin/public/docs/image2.png b/packages/grid-column-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..8aee6e6f33b4bf921c83824a667db8b68c58f7fb Binary files /dev/null and b/packages/grid-column-plugin/public/docs/image2.png differ diff --git a/packages/grid-column-plugin/public/docs/image3.png b/packages/grid-column-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..a45ba4854a96edfbd804d679dfef188687a7c36b Binary files /dev/null and b/packages/grid-column-plugin/public/docs/image3.png differ diff --git a/packages/grid-column-plugin/public/docs/image4.png b/packages/grid-column-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..3e8e93766d6525a035269123881d5a0c5ab6a888 Binary files /dev/null and b/packages/grid-column-plugin/public/docs/image4.png differ diff --git a/packages/grid-column-plugin/scripts/link.sh b/packages/grid-column-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/grid-column-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/grid-column-plugin/src/grid-column-plugin.controller.ts b/packages/grid-column-plugin/src/grid-column-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..1dd11a649e685c9deff4efcc9877a63302f80dd7 --- /dev/null +++ b/packages/grid-column-plugin/src/grid-column-plugin.controller.ts @@ -0,0 +1,8 @@ +import { GridFieldColumnController } from '@ibiz-template/runtime'; + +export class GridColumnPluginController extends GridFieldColumnController { + protected async onInit(): Promise { + ibiz.log.info('表格列控制器初始化'); + await super.onInit(); + } +} diff --git a/packages/grid-column-plugin/src/grid-column-plugin.provider.ts b/packages/grid-column-plugin/src/grid-column-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..b377d55b24f3348c1dcbb5bb7813c65efd78e41f --- /dev/null +++ b/packages/grid-column-plugin/src/grid-column-plugin.provider.ts @@ -0,0 +1,35 @@ +import { + GridColumnController, + GridController, + GridFieldEditColumnController, + GridUAColumnController, + IGridColumnProvider, +} from '@ibiz-template/runtime'; +import { IDEGridFieldColumn } from '@ibiz/model-core'; +import { GridColumnPluginController } from './grid-column-plugin.controller'; + +function newController( + columnModel: IDEGridFieldColumn, + grid: GridController, +): GridColumnController { + if (columnModel.columnType === 'UAGRIDCOLUMN') { + return new GridUAColumnController(columnModel, grid); + } + if (columnModel.enableRowEdit) { + return new GridFieldEditColumnController(columnModel, grid); + } + return new GridColumnPluginController(columnModel, grid); +} + +export class GridColumnPluginProvider implements IGridColumnProvider { + component: string = 'IBizGridColumnPlugin'; + + async createController( + columnModel: IDEGridFieldColumn, + grid: GridController, + ): Promise { + const c = newController(columnModel, grid); + await c.init(); + return c; + } +} diff --git a/packages/grid-column-plugin/src/grid-column-plugin.scss b/packages/grid-column-plugin/src/grid-column-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..a34d246b6488e440eaba655b16c3aec5405b4d1e --- /dev/null +++ b/packages/grid-column-plugin/src/grid-column-plugin.scss @@ -0,0 +1,5 @@ +@include b(grid-column-plugin) { + width: 100%; + height: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/grid-column-plugin/src/grid-column-plugin.tsx b/packages/grid-column-plugin/src/grid-column-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..bfbe1d1f2d83803485ef44eaf171d9dee5c3ac15 --- /dev/null +++ b/packages/grid-column-plugin/src/grid-column-plugin.tsx @@ -0,0 +1,59 @@ +import { defineComponent } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { GridRowState } from '@ibiz-template/runtime'; +import { GridColumnPluginController } from './grid-column-plugin.controller'; +import './grid-column-plugin.scss'; + +export const GridColumnPlugin = defineComponent({ + name: 'IBizGridColumnPlugin', + props: { + controller: { + type: GridColumnPluginController, + required: true, + }, + row: { + type: GridRowState, + required: true, + }, + }, + setup() { + const ns = useNamespace('grid-column-plugin'); + + return { + ns, + }; + }, + render() { + const content = () => { + if (this.controller.model.columnType === 'UAGRIDCOLUMN') { + return ( + + ); + } + if (this.controller.model.enableRowEdit) { + return ( + + ); + } + return ( + + ); + }; + + return ( +
+ 表格列插件内容 + {content()} +
+ ); + }, +}); diff --git a/packages/grid-column-plugin/src/index.ts b/packages/grid-column-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ad1544a13721675307b3f65f5a070f5606d9114 --- /dev/null +++ b/packages/grid-column-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue'; +import { registerGridColumnProvider } from '@ibiz-template/runtime'; +import { GridColumnPlugin } from './grid-column-plugin'; +import { GridColumnPluginProvider } from './grid-column-plugin.provider'; + +export default { + install(app: App) { + // 全局注册表格列插件组件 + app.component(GridColumnPlugin.name!, GridColumnPlugin); + // 全局注册表格列插件适配器,GRID_COLRENDER是插件类型,R9GridColumnPluginId是插件标识 + registerGridColumnProvider( + 'GRID_COLRENDER_R9GridColumnPluginId', + () => new GridColumnPluginProvider(), + ); + }, +}; diff --git a/packages/grid-column-plugin/tsconfig.json b/packages/grid-column-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/grid-column-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/grid-column-plugin/tsconfig.node.json b/packages/grid-column-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/grid-column-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/grid-column-plugin/vite.config.ts b/packages/grid-column-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/grid-column-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/panel-item-plugin/CHANGELOG.md b/packages/panel-item-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/panel-item-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/panel-item-plugin/README.md b/packages/panel-item-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a8672045fbabc73f7c22174438b9963919ce271e --- /dev/null +++ b/packages/panel-item-plugin/README.md @@ -0,0 +1,242 @@ +# 面板项插件 + +## 新建自定义部件绘制插件 + +新建自定义部件绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的面板项上绑定插件 + + + +## 插件机制 + +视图在绘制面板项时,是通过面板项适配器上的component属性去获取组件名称,然后进行绘制的,并且渲染时会将createController方法返回的控制器传给对应的面板项组件。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 绘制面板项 +const renderPanelItem = ( + panelItem: IPanelItem, + options?: { + providers: { + [key: string]: IPanelItemProvider; + }; + panelItems: { + [key: string]: IPanelItemController; + }; + }, +): VNode | null => { + if ((panelItem as IPanelField).hidden) { + return null; + } + const { providers, panelItems } = options || c; + const provider = providers[panelItem.id!]; + if (!provider) { + return ( +
+ {ibiz.i18n.t('vue3Util.control.unsupportedPanel', { + id: panelItem.id, + itemType: panelItem.itemType, + })} +
+ ); + } + // 面板项插槽,排除部件占位 + if (panelItem.itemType !== 'CTRLPOS' && slots[panelItem.id!]) { + return renderSlot(slots, panelItem.id!, { + model: panelItem, + data: c.data, + value: c.data[panelItem.id!], + }); + } + const component = resolveComponent(provider.component); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let children: any; + // 占位类型成员填充外部对应的插槽。 + if (panelItem.itemType === 'CTRLPOS' && slots[panelItem.id!]) { + const panelItemC = panelItems[panelItem.id!]!; + if (panelItemC.parent && isSimpleDataContainer(panelItemC.parent.model)) { + children = () => { + return slots[panelItem.id!]!({ + isSimple: true, + data: panelItemC.data, + }); + }; + } else { + children = () => slots[panelItem.id!]!(); + } + } else if ( + panelItem.itemType === 'TABPANEL' && + (panelItem as IPanelTabPanel).panelTabPages?.length + ) { + children = () => { + return (panelItem as IPanelTabPanel).panelTabPages!.map(child => { + return renderPanelItem(child, options); + }); + }; + } else if (isDataContainer(panelItem)) { + // 单项数据容器,多项数据容器不给子 + children = undefined; + } else if ((panelItem as IPanelContainer).panelItems?.length) { + children = () => { + return (panelItem as IPanelContainer).panelItems!.map(child => { + return renderPanelItem(child, options); + }); + }; + } + + // 直接样式 + let tempStyle = ''; + if (panelItem.cssStyle) { + tempStyle = panelItem.cssStyle; + } + const panelItemC = panelItems[panelItem.id!]!; + return h( + component, + { + modelData: panelItem, + controller: panelItemC, + key: panelItem.id, + style: tempStyle, + attrs: renderAttrs(panelItem, panelItemC), + }, + children, + ); +}; +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ panel-item-plugin 面板项插件顶层目录,可根据实际业务命名 + |─ ─ src 面板项插件源代码目录 +​ |─ ─ panel-item-plugin.controller.ts 面板项插件控制器 +​ |─ ─ panel-item-plugin.provider.ts 面板项插件适配器 +​ |─ ─ panel-item-plugin.scss 面板项插件样式 +​ |─ ─ panel-item-plugin.tsx 面板项插件组件 +​ |─ ─ index.ts 面板项插件入口文件 +``` + +### 面板项插件入口文件 + +面板项插件入口文件会全局注册面板项插件适配器和面板项插件组件,供外部使用。 + +```typescript +import { registerPanelItemProvider } from '@ibiz-template/runtime'; +import { App } from 'vue'; +import { PanelItemPlugin } from './panel-item-plugin'; +import { PanelItemPluginProvider } from './panel-item-plugin.provider'; + +export default { + install(app: App) { + // 全局注册面板项插件组件 + app.component(PanelItemPlugin.name!, PanelItemPlugin); + // 全局注册面板项插件适配器,CUSTOM是插件类型,R9PanelItemPluginId是插件标识 + registerPanelItemProvider( + 'CUSTOM_R9PanelItemPluginId', + () => new PanelItemPluginProvider(), + ); + }, +}; +``` + +### 面板项插件组件 + +面板项插件组件使用tsx的书写方式,承载面板项绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 面板项插件适配器 + +面板项插件适配器主要通过component属性指定面板项实际要绘制的组件,并且通过createController方法返回需传递给面板项的控制器。 + +### 面板项插件控制器 + +面板项插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/panel-item-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import PanelItemPlugin from '@ibiz-plugin-template/panel-item-plugin'; + +export default { + install(app: App) { + app.use(PanelItemPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/panel-item-plugin/); + }, +}; +``` + +## 附录 + +| 面板项 | UI呈现 | 控制器 | +| :----------------------: | :-------------------: | :-----------------------------: | +| 应用切换器 | AppSwitch | AppSwitchController | +| 人机识别控件 | AuthCaptcha | AuthCaptchaController | +| 第三方登录 | AuthSso | PanelItemController | +| 用户信息 | AuthUserinfo | PanelItemController | +| 协同编辑消息占位 | CoopPos | CoopPosController | +| 数据导入 | DataImportShell | PanelItemController | +| 全局搜索 | GlobalSearch | GlobalSearchController | +| 首页行为容器 | IndexActions | PanelContainerController | +| 首页空白占位 | IndexBlankPlaceholder | IndexBlankPlaceholderController | +| 面包屑导航 | NavBreadcrumb | NavBreadcrumbController | +| 导航区占位 | NavPosIndex | NavPosIndexController | +| 标签页导航栏 | NavTabs | NavTabsController | +| 面板容器(应用头部) | PanelAppHeader | PanelItemController | +| 面板容器(应用登录视图) | PanelAppLoginView | PanelAppLoginViewController | +| 应用标题 | PanelAppTitle | PanelAppTitleController | +| 面板按钮 | PanelButton | PanelButtonController | +| 面板按钮组 | PanelButtonList | PanelButtonListController | +| 面板容器(导航部件头部) | PanelExpHeader | PanelItemController | +| 首页搜索 | PanelIndexViewSearch | PanelItemController | +| 记住登陆 | PanelRememberMe | PanelRememberMeController | +| 轮播图 | PanelStaticCarousel | PanelItemController | +| 分页面板 | PanelTabPanel | PanelTabPanelController | +| 面板容器(视图内容) | PanelViewContent | PanelItemController | +| 面板容器(视图头部) | PanelViewHeader | PanelItemController | +| 搜索表单按钮 | SearchFormButtons | SearchFormButtonsController | +| 快捷操作 | ShortCut | PanelItemController | +| 分割容器 | SplitContainer | SplitContainerController | +| 用户操作 | UserAction | PanelItemController | +| 用户消息 | UserMessage | PanelItemController | +| 视图消息 | ViewMessage | PanelItemController | +| 视图消息占位 | ViewMsgPos | ViewMsgPosController | diff --git a/packages/panel-item-plugin/RUNTIME-PLUGIN-MODE.md b/packages/panel-item-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/panel-item-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/panel-item-plugin/package.json b/packages/panel-item-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..938aab7fae8204070dd506066d1fe9cc8a51beac --- /dev/null +++ b/packages/panel-item-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/panel-item-plugin", + "version": "0.0.1", + "description": "面板项插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/panel-item-plugin/public/docs/image.png b/packages/panel-item-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..5b554a41027b36eac5653ad0af42fdc5f2f35a67 Binary files /dev/null and b/packages/panel-item-plugin/public/docs/image.png differ diff --git a/packages/panel-item-plugin/public/docs/image2.png b/packages/panel-item-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..e1db15cf0fe1e3064bca43f1733dbbab15c272e8 Binary files /dev/null and b/packages/panel-item-plugin/public/docs/image2.png differ diff --git a/packages/panel-item-plugin/public/docs/image3.png b/packages/panel-item-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0aa246fb2971bedfddbaa76fd1cf58775d93f8 Binary files /dev/null and b/packages/panel-item-plugin/public/docs/image3.png differ diff --git a/packages/panel-item-plugin/public/docs/image4.png b/packages/panel-item-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..fa1b8b2a55e9ec21c7c187bc38d13947dfe18cd1 Binary files /dev/null and b/packages/panel-item-plugin/public/docs/image4.png differ diff --git a/packages/panel-item-plugin/scripts/link.sh b/packages/panel-item-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/panel-item-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/panel-item-plugin/src/index.ts b/packages/panel-item-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4bc097a0b101dc15a98e769f90089a4333ce0509 --- /dev/null +++ b/packages/panel-item-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { registerPanelItemProvider } from '@ibiz-template/runtime'; +import { App } from 'vue'; +import { PanelItemPlugin } from './panel-item-plugin'; +import { PanelItemPluginProvider } from './panel-item-plugin.provider'; + +export default { + install(app: App) { + // 全局注册面板项插件组件 + app.component(PanelItemPlugin.name!, PanelItemPlugin); + // 全局注册面板项插件适配器,CUSTOM是插件类型,R9PanelItemPluginId是插件标识 + registerPanelItemProvider( + 'CUSTOM_R9PanelItemPluginId', + () => new PanelItemPluginProvider(), + ); + }, +}; diff --git a/packages/panel-item-plugin/src/panel-item-plugin.controller.ts b/packages/panel-item-plugin/src/panel-item-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..6947f6ea0faa663ba7357d83bed3b3c5310d8340 --- /dev/null +++ b/packages/panel-item-plugin/src/panel-item-plugin.controller.ts @@ -0,0 +1,8 @@ +import { IPanelItem } from '@ibiz/model-core'; +import { PanelItemController } from '@ibiz-template/runtime'; + +export class PanelItemPluginController extends PanelItemController { + get data(): IData { + return this.dataParent.data!; + } +} diff --git a/packages/panel-item-plugin/src/panel-item-plugin.provider.ts b/packages/panel-item-plugin/src/panel-item-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..cea66b93125d6382e66c2abe53ce098a62a8983a --- /dev/null +++ b/packages/panel-item-plugin/src/panel-item-plugin.provider.ts @@ -0,0 +1,21 @@ +import { + IPanelItemProvider, + PanelController, + PanelItemController, +} from '@ibiz-template/runtime'; +import { IPanelItem } from '@ibiz/model-core'; +import { PanelItemPluginController } from './panel-item-plugin.controller'; + +export class PanelItemPluginProvider implements IPanelItemProvider { + component: string = 'IBizPanelItemPlugin'; + + async createController( + panelItem: IPanelItem, + panel: PanelController, + parent: PanelItemController | undefined, + ): Promise { + const c = new PanelItemPluginController(panelItem, panel, parent); + await c.init(); + return c; + } +} diff --git a/packages/panel-item-plugin/src/panel-item-plugin.scss b/packages/panel-item-plugin/src/panel-item-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..a85811f98c2cbc1428052b5a81a2960d06562af2 --- /dev/null +++ b/packages/panel-item-plugin/src/panel-item-plugin.scss @@ -0,0 +1,5 @@ +@include b(panel-item-plugin) { + width: 100%; + height: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/panel-item-plugin/src/panel-item-plugin.tsx b/packages/panel-item-plugin/src/panel-item-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0c649864b80e54bad75630a38746e15801b06e04 --- /dev/null +++ b/packages/panel-item-plugin/src/panel-item-plugin.tsx @@ -0,0 +1,38 @@ +import { IPanelItem } from '@ibiz/model-core'; +import { computed, defineComponent, PropType } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { PanelItemPluginController } from './panel-item-plugin.controller'; +import './panel-item-plugin.scss'; + +export const PanelItemPlugin = defineComponent({ + name: 'IBizPanelItemPlugin', + props: { + modelData: { + type: Object as PropType, + required: true, + }, + controller: { + type: PanelItemPluginController, + required: true, + }, + }, + setup(props) { + const ns = useNamespace('panel-item-plugin'); + + // 类名控制 + const classArr = computed(() => { + const { id } = props.modelData; + const result: Array = [ns.b(), ns.m(id)]; + result.push(...props.controller.containerClass); + return result; + }); + + return { + ns, + classArr, + }; + }, + render() { + return
面板项插件内容
; + }, +}); diff --git a/packages/panel-item-plugin/tsconfig.json b/packages/panel-item-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/panel-item-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/panel-item-plugin/tsconfig.node.json b/packages/panel-item-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/panel-item-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/panel-item-plugin/vite.config.ts b/packages/panel-item-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/panel-item-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/portlet-plugin/CHANGELOG.md b/packages/portlet-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/portlet-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/portlet-plugin/README.md b/packages/portlet-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8fd490d75c98aba049aa8a7f6696c5d8184576e7 --- /dev/null +++ b/packages/portlet-plugin/README.md @@ -0,0 +1,183 @@ +# 门户部件插件 + +## 新建自定义门户部件绘制插件 + +新建自定义门户部件绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的门户部件上绑定插件 + + + +## 插件机制 + +看板在绘制门户部件时,是通过门户部件适配器上的component属性去获取组件名称,然后进行绘制的,并且渲染时会将createController方法返回的控制器传给对应的门户部件组件。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 绘制门户部件 +function renderPortletByType( + model: IDBPortletPart, + c: DashboardController, + opts?: IData, +): VNode { + const provider = c.providers[model.id!]; + const controller = c.portlets[model.id!]; + const commonProps = { + modelData: model, + controller, + }; + + if (!provider) { + return ( +
+ {model.portletType} + {ibiz.i18n.t('app.noSupport')} +
+ ); + } + + const providerComp = resolveComponent( + provider.component, + ) as ConcreteComponent; + // 绘制容器 + if (model.portletType === 'CONTAINER') { + const container = model as IDBContainerPortletPart; + return h( + providerComp, + { + ...commonProps, + key: model.id, + id: model.id, + }, + { + default: () => + container.controls?.map(child => renderPortletByType(child, c, opts)), + }, + ); + } + + // 绘制门户部件 + return h(providerComp, { + ...commonProps, + key: refreshTagObj[model.id!]?.refreshtag + ? refreshTagObj[model.id!].refreshtag + : model.id, + id: model.id, + }); +} +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ portlet-plugin 门户部件插件顶层目录,可根据实际业务命名 + |─ ─ src 门户部件插件源代码目录 +​ |─ ─ portlet-plugin.controller.ts 门户部件插件控制器 +​ |─ ─ portlet-plugin.provider.ts 门户部件插件适配器 +​ |─ ─ portlet-plugin.scss 门户部件插件样式 +​ |─ ─ portlet-plugin.tsx 门户部件插件组件 +​ |─ ─ index.ts 门户部件插件入口文件 +``` + +### 门户部件插件入口文件 + +门户部件插件入口文件会全局注册门户部件插件适配器和门户部件插件组件,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerPortletProvider } from '@ibiz-template/runtime'; +import { PortletPlugin } from './portlet-plugin'; +import { PortletPluginProvider } from './portlet-plugin.provider'; + +export default { + install(app: App) { + // 全局注册门户部件插件组件 + app.component(PortletPlugin.name!, PortletPlugin); + // 全局注册门户部件插件适配器,PORTLET_CUSTOM是插件类型,R9PortletPluginId是插件标识 + registerPortletProvider( + 'PORTLET_CUSTOM_R9PortletPluginId', + () => new PortletPluginProvider(), + ); + }, +}; +``` + +### 门户部件插件组件 + +门户部件插件组件使用tsx的书写方式,承载门户部件绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 门户部件插件适配器 + +门户部件插件适配器主要通过component属性指定门户部件实际要绘制的组件,并且通过createController方法返回需传递给门户部件的控制器。 + +### 门户部件插件控制器 + +门户部件插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/portlet-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import PortletPlugin from '@ibiz-plugin-template/portlet-plugin'; + +export default { + install(app: App) { + app.use(PortletPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/portlet-plugin/); + }, +}; +``` + +## 附录 + +| 门户部件类型 | UI呈现 | 控制器 | +| :----------: | :--------------: | :------------------------: | +| 操作栏 | ActionBarPortlet | ActionBarPortletController | +| 实体图表 | ChartPortlet | ChartPortletController | +| 容器 | ContainerPortlet | ContainerPortletController | +| 过滤器 | FilterPortlet | FilterPortletController | +| 网页部件 | HtmlPortlet | HtmlPortletController | +| 实体列表 | ListPortlet | ListPortletController | +| 菜单栏 | MenuPortlet | MenuPortletController | +| 直接内容 | RawItemPortlet | RawItemPortletController | +| 报表 | ReportPortlet | ReportPortletController | +| 系统视图 | ViewPortlet | ViewPortletController | diff --git a/packages/portlet-plugin/RUNTIME-PLUGIN-MODE.md b/packages/portlet-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/portlet-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/portlet-plugin/package.json b/packages/portlet-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..d23a3e51cab55797a386d976fae8d1cb6a292ddf --- /dev/null +++ b/packages/portlet-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/portlet-plugin", + "version": "0.0.1", + "description": "门户部件插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/portlet-plugin/public/docs/image.png b/packages/portlet-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..d6154872f8b8b6c9f4aded5d88e050c8f0492791 Binary files /dev/null and b/packages/portlet-plugin/public/docs/image.png differ diff --git a/packages/portlet-plugin/public/docs/image2.png b/packages/portlet-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..cbbf9437f03a8501a5b515ad984b7d210de0d282 Binary files /dev/null and b/packages/portlet-plugin/public/docs/image2.png differ diff --git a/packages/portlet-plugin/public/docs/image3.png b/packages/portlet-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..3791f40c703daba5555657f28c1805350dfae583 Binary files /dev/null and b/packages/portlet-plugin/public/docs/image3.png differ diff --git a/packages/portlet-plugin/public/docs/image4.png b/packages/portlet-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..588ec6da6eeb18814d0a89edcf7c758536c6aeb7 Binary files /dev/null and b/packages/portlet-plugin/public/docs/image4.png differ diff --git a/packages/portlet-plugin/scripts/link.sh b/packages/portlet-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/portlet-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/portlet-plugin/src/index.ts b/packages/portlet-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..75e4bb4aa5cc7f25deba5e452d19ea6acd82bef9 --- /dev/null +++ b/packages/portlet-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue'; +import { registerPortletProvider } from '@ibiz-template/runtime'; +import { PortletPlugin } from './portlet-plugin'; +import { PortletPluginProvider } from './portlet-plugin.provider'; + +export default { + install(app: App) { + // 全局注册门户部件插件组件 + app.component(PortletPlugin.name!, PortletPlugin); + // 全局注册门户部件插件适配器,PORTLET_CUSTOM是插件类型,R9PortletPluginId是插件标识 + registerPortletProvider( + 'PORTLET_CUSTOM_R9PortletPluginId', + () => new PortletPluginProvider(), + ); + }, +}; diff --git a/packages/portlet-plugin/src/portlet-plugin.controller.ts b/packages/portlet-plugin/src/portlet-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..391d8392ea05e60c917b4163ddef989fea00ca2d --- /dev/null +++ b/packages/portlet-plugin/src/portlet-plugin.controller.ts @@ -0,0 +1,8 @@ +import { PortletPartController } from '@ibiz-template/runtime'; + +export class PortletPluginController extends PortletPartController { + protected async onInit(): Promise { + ibiz.log.info('门户部件控制器初始化'); + await super.onInit(); + } +} diff --git a/packages/portlet-plugin/src/portlet-plugin.provider.ts b/packages/portlet-plugin/src/portlet-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..e558c88757ac7ec35e9aedfd0f71d002a0e47ed5 --- /dev/null +++ b/packages/portlet-plugin/src/portlet-plugin.provider.ts @@ -0,0 +1,21 @@ +import { + IDashboardController, + IPortletContainerController, + IPortletProvider, +} from '@ibiz-template/runtime'; +import { IDBPortletPart } from '@ibiz/model-core'; +import { PortletPluginController } from './portlet-plugin.controller'; + +export class PortletPluginProvider implements IPortletProvider { + component: string = 'IBizPortletPlugin'; + + async createController( + portletModel: IDBPortletPart, + dashboard: IDashboardController, + parent?: IPortletContainerController, + ): Promise { + const c = new PortletPluginController(portletModel, dashboard, parent); + await c.init(); + return c; + } +} diff --git a/packages/portlet-plugin/src/portlet-plugin.scss b/packages/portlet-plugin/src/portlet-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..8ca6fcd969dd0ebe29d6a53edd828d0b077f243d --- /dev/null +++ b/packages/portlet-plugin/src/portlet-plugin.scss @@ -0,0 +1,5 @@ +@include b(portlet-plugin){ + width: 100%; + height: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/portlet-plugin/src/portlet-plugin.tsx b/packages/portlet-plugin/src/portlet-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..393e8c6a1a62244cce54b1d03afcd874ffac5a78 --- /dev/null +++ b/packages/portlet-plugin/src/portlet-plugin.tsx @@ -0,0 +1,33 @@ +import { useNamespace } from '@ibiz-template/vue3-util'; +import { defineComponent, PropType } from 'vue'; +import { IDBPortletPart } from '@ibiz/model-core'; +import { PortletPluginController } from './portlet-plugin.controller'; +import './portlet-plugin.scss'; + +export const PortletPlugin = defineComponent({ + name: 'IBizPortletPlugin', + props: { + modelData: { + type: Object as PropType, + required: true, + }, + controller: { + type: PortletPluginController, + required: true, + }, + }, + setup() { + const ns = useNamespace(`portlet-plugin`); + + return { ns }; + }, + + render() { + const classArr: string[] = [ + this.ns.b(), + this.ns.m(this.modelData.codeName), + ...this.controller.containerClass, + ]; + return
门户部件插件内容
; + }, +}); diff --git a/packages/portlet-plugin/tsconfig.json b/packages/portlet-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/portlet-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/portlet-plugin/tsconfig.node.json b/packages/portlet-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/portlet-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/portlet-plugin/vite.config.ts b/packages/portlet-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/portlet-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/theme-plugin/CHANGELOG.md b/packages/theme-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/theme-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/theme-plugin/README.md b/packages/theme-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d832e7d5948dcc26b3039fd8dbbe533dc550b2fa --- /dev/null +++ b/packages/theme-plugin/README.md @@ -0,0 +1,168 @@ +# 主题插件 + +当前主题插件处于关闭状态。若要应用主题插件,需先将主题插件入口文件中被注释的逻辑代码还原,随后在配置平台里启用主题。 + +## 新建主题 + +新建界面主题,需要注意的是主题标识需要和主题插件包中的主题class类名一致,并且配置主题参数theme-package-path,主题参数的值是主题插件包信息。 + + + +## 插件机制 + +应用初始化的时候,如果应用配置了主题,则会加载其对应的主题包插件,从而应用自定义主题样式和覆盖指定的默认视图布局,详情如下: + +```typescript +async initModel(context: IParams, permission: boolean = true): Promise { + // 没初始化或者初始化了但是切换模型 + if ( + !this.hasModelInit || + (this.hasModelInit && this.noPermissionModel !== permission) + ) { + // 清空重置基座 + ibiz.hub.reset(); + const helper = new ModelHelper( + async (url: string, params?: IParams) => { + const res = await ibiz.net.get( + `${ibiz.env.remoteModelUrl}${url}`, + params, + permission ? {} : { srfdcsystem: ibiz.env.dcSystem }, + ); + return res.data; + }, + ibiz.env.appId, + context, + permission, + ); + const tempApp = await helper.getAppModel(); + await this.initEnvironment(tempApp); + const app = await ibiz.hub.getAppAsync(ibiz.env.appId); + await AppHooks.initedApp.call({ context, app }); + const appModel = app.model; + ibiz.env.isMob = appModel.mobileApp === true; + if (ibiz.env.isEnableMultiLan) { + const lang = ibiz.i18n.getLang(); + const m = await helper.getPSAppLang( + lang.replace('-', '_').toUpperCase(), + ); + const items = m.languageItems || []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data: any = {}; + items.forEach(item => { + data[item.lanResTag!] = item.content; + }); + i18n.global.mergeLocaleMessage(lang, data); + } + const module = await import('@ibiz-template/web-theme'); + const theme = module.default || module; + AppHooks.useComponent.callSync(null, theme); + if (ibiz.config.theme) ibiz.util.theme.setTheme(ibiz.config.theme); + if (appModel.appUIThemes) { + await this.loadTheme(); + } + + // 设置浏览器标题 + if (app.model.title) { + ibiz.util.setBrowserTitle(''); + } + } + + this.noPermissionModel = permission; + this.hasModelInit = true; + } +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ theme-plugin 主题插件顶层目录,可根据实际业务命名 + |─ ─ src 主题插件源代码目录 +​ |─ ─ layout 布局目录 + |─ ─ de-grid-view-layout.ts 默认表格视图布局json + |─ ─ index.ts 布局入口文件 + |─ ─ theme 主题目录 + |─ ─ custom-theme.scss 自定义主题 + |─ ─ index.ts 主题入口文件 +​ |─ ─ index.ts 主题插件入口文件 +``` + +### 主题插件入口文件 + +主题插件入口文件会加载自定义主题样式,并且覆盖指定的默认视图布局,供外部使用。 + +```typescript +import WebTheme from '@ibiz-template/web-theme'; +import { install } from './layout'; +import './theme/index.scss'; + +export default { + install(): void { + // 安装默认主题 + WebTheme.install(); + // 覆盖默认视图布局 + install((key: string, model: any) => { + ibiz.util.layoutPanel.register(key, model); + }); + }, +}; +``` + +### 布局入口文件 + +布局入口文件会覆盖指定的默认视图布局,可以重新定义默认视图布局的json结构。 + +### 主题入口文件 + +主题入口文件会加载自定义主题样式。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/theme-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import ThemePlugin from '@ibiz-plugin-template/theme-plugin'; + +export default { + install(app: App) { + app.use(ThemePlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/theme-plugin/); + }, +}; +``` diff --git a/packages/theme-plugin/package.json b/packages/theme-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..0ac998b1b09c846533732b1d5179c799a9f2e6a3 --- /dev/null +++ b/packages/theme-plugin/package.json @@ -0,0 +1,108 @@ +{ + "name": "@ibiz-plugin-template/theme-plugin", + "version": "0.0.1", + "description": "主题插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz-template/web-theme": "^3.4.0", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz-template/web-theme": "^3.4.0", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/theme-plugin/public/docs/image.png b/packages/theme-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..4fe527ca25c9a24fac2bc5315f60e91d0dd40dce Binary files /dev/null and b/packages/theme-plugin/public/docs/image.png differ diff --git a/packages/theme-plugin/public/docs/image2.png b/packages/theme-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..3375d33d0d6060ebe8797af9413e16385e656718 Binary files /dev/null and b/packages/theme-plugin/public/docs/image2.png differ diff --git a/packages/theme-plugin/public/docs/image3.png b/packages/theme-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..39dc57287e66f32a9a964e328d93d9f5a289bd0d Binary files /dev/null and b/packages/theme-plugin/public/docs/image3.png differ diff --git a/packages/theme-plugin/scripts/link.sh b/packages/theme-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/theme-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/theme-plugin/src/index.ts b/packages/theme-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..21767cc1891732d82740f2154a2912769915190b --- /dev/null +++ b/packages/theme-plugin/src/index.ts @@ -0,0 +1,14 @@ +import WebTheme from '@ibiz-template/web-theme'; +// import { install } from './layout'; +// import './theme/index.scss'; + +export default { + install(): void { + // 安装默认主题 + WebTheme.install(); + // 覆盖默认视图布局 + // install((key: string, model: any) => { + // ibiz.util.layoutPanel.register(key, model); + // }); + }, +}; diff --git a/packages/theme-plugin/src/layout/de-grid-view-layout.ts b/packages/theme-plugin/src/layout/de-grid-view-layout.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1cdb33b5ccf5225c4e3eec5106b08d586af9720 --- /dev/null +++ b/packages/theme-plugin/src/layout/de-grid-view-layout.ts @@ -0,0 +1,350 @@ +export default { + layoutMode: 'FLEX', + layout: { + layout: 'FLEX', + }, + rootPanelItems: [ + { + rawItem: { + rawItemParams: [ + { + key: 'POSITION', + value: 'TOP', + }, + ], + predefinedType: 'VIEWMSG_POS', + id: 'viewmsg_pos_top', + }, + caption: '视图消息占位', + itemStyle: 'DEFAULT', + itemType: 'RAWITEM', + layoutPos: { + shrink: 0, + layout: 'FLEX', + }, + showCaption: true, + id: 'viewmsg_pos_top', + }, + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + caption: '页面标题', + itemStyle: 'DEFAULT', + itemType: 'CTRLPOS', + layoutPos: { + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'captionbar', + }, + ], + layout: { + align: 'center', + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + heightMode: 'FULL', + layout: 'FLEX', + }, + id: 'view_captionbar', + }, + ], + layout: { + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + heightMode: 'FULL', + layout: 'FLEX', + }, + id: 'view_header_left', + }, + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + caption: '搜索栏', + itemStyle: 'DEFAULT', + itemType: 'CTRLPOS', + layoutPos: { + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'searchbar', + }, + ], + layout: { + align: 'center', + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + heightMode: 'FULL', + layout: 'FLEX', + }, + id: 'view_searchbar', + }, + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + caption: '工具栏', + itemStyle: 'DEFAULT', + itemType: 'CTRLPOS', + layoutPos: { + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'toolbar', + }, + ], + layout: { + align: 'center', + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + heightMode: 'FULL', + layout: 'FLEX', + }, + id: 'view_toolbar', + }, + ], + layout: { + dir: 'row', + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + heightMode: 'FULL', + layout: 'FLEX', + }, + id: 'view_header_right', + }, + ], + predefinedType: 'VIEWHEADER', + layout: { + align: 'space-between', + dir: 'row', + layout: 'FLEX', + valign: 'center', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 0, + layout: 'FLEX', + }, + id: 'view_header', + }, + ], + predefinedType: 'PANELPART', + layout: { + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '引用布局面板', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'panelpart', + }, + { + rawItem: { + rawItemParams: [ + { + key: 'POSITION', + value: 'BODY', + }, + ], + predefinedType: 'VIEWMSG_POS', + id: 'viewmsg_pos_body', + }, + caption: '视图消息占位', + itemStyle: 'DEFAULT', + itemType: 'RAWITEM', + layoutPos: { + shrink: 0, + layout: 'FLEX', + }, + showCaption: true, + id: 'viewmsg_pos_body', + }, + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + caption: '搜索表单', + itemStyle: 'DEFAULT', + itemType: 'CTRLPOS', + layoutPos: { + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'searchform', + }, + ], + layout: { + dir: 'column', + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 0, + layout: 'FLEX', + }, + id: 'view_searchform', + }, + ], + predefinedType: 'PANELPART', + layout: { + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '引用布局面板', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'panelpart1', + }, + { + actionGroupExtractMode: 'ITEM', + panelItems: [ + { + caption: '表格', + itemStyle: 'DEFAULT', + itemType: 'CTRLPOS', + layoutPos: { + grow: 1, + shrink: 1, + layout: 'FLEX', + }, + showCaption: true, + id: 'grid', + }, + ], + predefinedType: 'VIEWCONTENT', + layout: { + layout: 'FLEX', + }, + dataRegionType: 'INHERIT', + caption: '容器', + itemStyle: 'DEFAULT', + itemType: 'CONTAINER', + layoutPos: { + grow: 1, + shrink: 1, + layout: 'FLEX', + }, + id: 'view_content', + }, + { + rawItem: { + caption: '主题插件自定义布局', + halign: 'LEFT', + renderMode: 'PARAGRAPH', + valign: 'MIDDLE', + wrapMode: 'NOWRAP', + contentType: 'RAW', + predefinedType: 'STATIC_LABEL', + id: 'static_label', + appId: 'sztrainsys__web', + }, + caption: '标签', + itemStyle: 'DEFAULT', + itemType: 'RAWITEM', + layoutPos: { + shrink: 1, + layout: 'FLEX', + appId: 'sztrainsys__web', + }, + showCaption: true, + id: 'static_label', + appId: 'sztrainsys__web', + }, + { + rawItem: { + rawItemParams: [ + { + key: 'POSITION', + value: 'BOTTOM', + }, + ], + predefinedType: 'VIEWMSG_POS', + id: 'viewmsg_pos_bottom', + }, + caption: '视图消息占位', + itemStyle: 'DEFAULT', + itemType: 'RAWITEM', + layoutPos: { + shrink: 0, + layout: 'FLEX', + }, + showCaption: true, + id: 'viewmsg_pos_bottom', + }, + ], + layoutPanel: true, + codeName: 'GridViewLayout', + controlType: 'VIEWLAYOUTPANEL', + logicName: '表格视图布局(预置模型)', + appDataEntityId: 'frontmodel.viewlayoutmodelrepository', + controlParam: {}, + modelId: 'd7c15227abc2b1d2198b5b99cb78b41d', + modelType: 'PSSYSVIEWLAYOUTPANEL', + name: 'layoutpanel', + id: 'gridviewlayout', +}; diff --git a/packages/theme-plugin/src/layout/index.ts b/packages/theme-plugin/src/layout/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5bf34417b48bcf43463f9b65576b89bf4a50984f --- /dev/null +++ b/packages/theme-plugin/src/layout/index.ts @@ -0,0 +1,6 @@ +import DEGridView from './de-grid-view-layout'; + +export function install(callBack: (key: string, model: any) => void): void { + // 实体表格视图 + callBack('DEGRIDVIEW_DEFAULT', DEGridView); +} diff --git a/packages/theme-plugin/src/theme/custom-theme.scss b/packages/theme-plugin/src/theme/custom-theme.scss new file mode 100644 index 0000000000000000000000000000000000000000..347a3a41fec915f33f54cfc163da868682e23231 --- /dev/null +++ b/packages/theme-plugin/src/theme/custom-theme.scss @@ -0,0 +1,421 @@ +/* stylelint-disable selector-class-pattern */ +:root.custom_theme { +// 红色 +#{getCssVarName(red, 0)}: 254, 242, 237; +#{getCssVarName(red, 1)}: 254, 221, 210; +#{getCssVarName(red, 2)}: 253, 183, 164; +#{getCssVarName(red, 3)}: 251, 144, 120; +#{getCssVarName(red, 4)}: 250, 102, 75; +#{getCssVarName(red, 5)}: 249, 57, 31; +#{getCssVarName(red, 6)}: 213, 37, 20; +#{getCssVarName(red, 7)}: 177, 20, 13; +#{getCssVarName(red, 8)}: 142, 7, 5; +#{getCssVarName(red, 9)}: 106, 2, 3; + +// 粉色 +#{getCssVarName(pink, 0)}: 253, 236, 239; +#{getCssVarName(pink, 1)}: 251, 207, 216; +#{getCssVarName(pink, 2)}: 246, 160, 181; +#{getCssVarName(pink, 3)}: 242, 115, 150; +#{getCssVarName(pink, 4)}: 238, 72, 122; +#{getCssVarName(pink, 5)}: 233, 31, 99; +#{getCssVarName(pink, 6)}: 197, 18, 86; +#{getCssVarName(pink, 7)}: 162, 11, 72; +#{getCssVarName(pink, 8)}: 126, 4, 58; +#{getCssVarName(pink, 9)}: 90, 1, 44; + +// 紫色 +#{getCssVarName(purple, 0)}: 247, 233, 247; +#{getCssVarName(purple, 1)}: 239, 202, 239; +#{getCssVarName(purple, 2)}: 221, 155, 224; +#{getCssVarName(purple, 3)}: 201, 112, 209; +#{getCssVarName(purple, 4)}: 180, 73, 194; +#{getCssVarName(purple, 5)}: 158, 40, 179; +#{getCssVarName(purple, 6)}: 135, 30, 159; +#{getCssVarName(purple, 7)}: 112, 23, 138; +#{getCssVarName(purple, 8)}: 92, 14, 117; +#{getCssVarName(purple, 9)}: 72, 11, 97; + +// 紫罗兰色 +#{getCssVarName(violet, 0)}: 243, 237, 249; +#{getCssVarName(violet, 1)}: 226, 209, 244; +#{getCssVarName(violet, 2)}: 196, 167, 233; +#{getCssVarName(violet, 3)}: 166, 126, 221; +#{getCssVarName(violet, 4)}: 136, 91, 210; +#{getCssVarName(violet, 5)}: 106, 58, 199; +#{getCssVarName(violet, 6)}: 87, 47, 179; +#{getCssVarName(violet, 7)}: 70, 36, 158; +#{getCssVarName(violet, 8)}: 54, 28, 138; +#{getCssVarName(violet, 9)}: 40, 20, 117; + +// 靛蓝 +#{getCssVarName(indigo, 0)}: 237, 239, 248; +#{getCssVarName(indigo, 1)}: 209, 216, 240; +#{getCssVarName(indigo, 2)}: 167, 179, 225; +#{getCssVarName(indigo, 3)}: 128, 144, 211; +#{getCssVarName(indigo, 4)}: 94, 111, 196; +#{getCssVarName(indigo, 5)}: 63, 81, 181; +#{getCssVarName(indigo, 6)}: 51, 66, 161; +#{getCssVarName(indigo, 7)}: 41, 52, 140; +#{getCssVarName(indigo, 8)}: 31, 40, 120; +#{getCssVarName(indigo, 9)}: 23, 29, 99; + +// 蓝色 +#{getCssVarName(blue, 0)}: 234, 245, 255; +#{getCssVarName(blue, 1)}: 203, 231, 254; +#{getCssVarName(blue, 2)}: 152, 205, 253; +#{getCssVarName(blue, 3)}: 101, 179, 252; +#{getCssVarName(blue, 4)}: 51, 149, 252; +#{getCssVarName(blue, 5)}: 3, 118, 250; +#{getCssVarName(blue, 6)}: 2, 97, 214; +#{getCssVarName(blue, 7)}: 2, 79, 179; +#{getCssVarName(blue, 8)}: 0, 61, 143; +#{getCssVarName(blue, 9)}: 0, 44, 107; + +// 浅蓝色 +#{getCssVarName(light, blue, 0)}: 234, 247, 253; +#{getCssVarName(light, blue, 1)}: 202, 236, 252; +#{getCssVarName(light, blue, 2)}: 149, 216, 248; +#{getCssVarName(light, blue, 3)}: 97, 195, 245; +#{getCssVarName(light, blue, 4)}: 48, 172, 241; +#{getCssVarName(light, blue, 5)}: 0, 149, 238; +#{getCssVarName(light, blue, 6)}: 0, 123, 202; +#{getCssVarName(light, blue, 7)}: 0, 99, 167; +#{getCssVarName(light, blue, 8)}: 0, 75, 131; +#{getCssVarName(light, blue, 9)}: 0, 53, 95; + +// 青色 +#{getCssVarName(cyan, 0)}: 229, 247, 248; +#{getCssVarName(cyan, 1)}: 194, 239, 240; +#{getCssVarName(cyan, 2)}: 138, 221, 225; +#{getCssVarName(cyan, 3)}: 87, 203, 211; +#{getCssVarName(cyan, 4)}: 44, 184, 197; +#{getCssVarName(cyan, 5)}: 6, 164, 182; +#{getCssVarName(cyan, 6)}: 3, 134, 152; +#{getCssVarName(cyan, 7)}: 2, 105, 121; +#{getCssVarName(cyan, 8)}: 0, 77, 91; +#{getCssVarName(cyan, 9)}: 1, 50, 61; + +// 蓝绿色 +#{getCssVarName(teal, 0)}: 227, 247, 244; +#{getCssVarName(teal, 1)}: 192, 240, 232; +#{getCssVarName(teal, 2)}: 135, 224, 211; +#{getCssVarName(teal, 3)}: 84, 209, 193; +#{getCssVarName(teal, 4)}: 39, 194, 176; +#{getCssVarName(teal, 5)}: 5, 179, 161; +#{getCssVarName(teal, 6)}: 0, 149, 137; +#{getCssVarName(teal, 7)}: 0, 119, 111; +#{getCssVarName(teal, 8)}: 0, 90, 85; +#{getCssVarName(teal, 9)}: 1, 60, 58; + +// 绿色 +#{getCssVarName(green, 0)}: 236, 246, 236; +#{getCssVarName(green, 1)}: 208, 240, 208; +#{getCssVarName(green, 2)}: 164, 224, 167; +#{getCssVarName(green, 3)}: 126, 209, 130; +#{getCssVarName(green, 4)}: 90, 194, 98; +#{getCssVarName(green, 5)}: 59, 179, 70; +#{getCssVarName(green, 6)}: 49, 149, 58; +#{getCssVarName(green, 7)}: 37, 119, 47; +#{getCssVarName(green, 8)}: 26, 89, 36; +#{getCssVarName(green, 9)}: 18, 60, 24; + +// 浅绿色 +#{getCssVarName(light, green, 0)}: 243, 248, 236; +#{getCssVarName(light, green, 1)}: 226, 240, 208; +#{getCssVarName(light, green, 2)}: 200, 225, 165; +#{getCssVarName(light, green, 3)}: 173, 211, 127; +#{getCssVarName(light, green, 4)}: 147, 197, 91; +#{getCssVarName(light, green, 5)}: 123, 182, 60; +#{getCssVarName(light, green, 6)}: 100, 152, 47; +#{getCssVarName(light, green, 7)}: 79, 121, 38; +#{getCssVarName(light, green, 8)}: 57, 91, 27; +#{getCssVarName(light, green, 9)}: 36, 61, 18; + +// 清柠色 +#{getCssVarName(lime, 0)}: 241, 250, 230; +#{getCssVarName(lime, 1)}: 227, 246, 197; +#{getCssVarName(lime, 2)}: 203, 237, 142; +#{getCssVarName(lime, 3)}: 183, 226, 91; +#{getCssVarName(lime, 4)}: 168, 218, 44; +#{getCssVarName(lime, 5)}: 154, 209, 0; +#{getCssVarName(lime, 6)}: 125, 174, 0; +#{getCssVarName(lime, 7)}: 99, 139, 2; +#{getCssVarName(lime, 8)}: 72, 104, 0; +#{getCssVarName(lime, 9)}: 47, 70, 0; + +// 黄色 +#{getCssVarName(yellow, 0)}: 255, 253, 234; +#{getCssVarName(yellow, 1)}: 255, 250, 203; +#{getCssVarName(yellow, 2)}: 253, 243, 152; +#{getCssVarName(yellow, 3)}: 252, 231, 101; +#{getCssVarName(yellow, 4)}: 251, 218, 49; +#{getCssVarName(yellow, 5)}: 249, 200, 0; +#{getCssVarName(yellow, 6)}: 208, 170, 0; +#{getCssVarName(yellow, 7)}: 167, 139, 0; +#{getCssVarName(yellow, 8)}: 125, 106, 0; +#{getCssVarName(yellow, 9)}: 83, 72, 0; + +// 琥珀色 +#{getCssVarName(amber, 0)}: 255, 251, 236; +#{getCssVarName(amber, 1)}: 252, 245, 206; +#{getCssVarName(amber, 2)}: 249, 232, 158; +#{getCssVarName(amber, 3)}: 246, 216, 110; +#{getCssVarName(amber, 4)}: 243, 198, 65; +#{getCssVarName(amber, 5)}: 240, 177, 20; +#{getCssVarName(amber, 6)}: 200, 138, 16; +#{getCssVarName(amber, 7)}: 160, 102, 10; +#{getCssVarName(amber, 8)}: 120, 70, 5; +#{getCssVarName(amber, 9)}: 80, 43, 3; + +// 橙色 +#{getCssVarName(orange, 0)}: 255, 243, 224; +#{getCssVarName(orange, 1)}: 255, 223, 177; +#{getCssVarName(orange, 2)}: 255, 204, 128; +#{getCssVarName(orange, 3)}: 254, 183, 77; +#{getCssVarName(orange, 4)}: 255, 167, 38; +#{getCssVarName(orange, 5)}: 255, 152, 0; +#{getCssVarName(orange, 6)}: 250, 141, 0; +#{getCssVarName(orange, 7)}: 245, 124, 2; +#{getCssVarName(orange, 8)}: 239, 108, 0; +#{getCssVarName(orange, 9)}: 230, 81, 0; + +// 灰色 +#{getCssVarName(grey, 0)}: 249, 249, 249; +#{getCssVarName(grey, 1)}: 230, 232, 234; +#{getCssVarName(grey, 2)}: 198, 202, 205; +#{getCssVarName(grey, 3)}: 167, 171, 175; +#{getCssVarName(grey, 4)}: 136, 141, 145; +#{getCssVarName(grey, 5)}: 107, 102, 116; +#{getCssVarName(grey, 6)}: 85, 91, 97; +#{getCssVarName(grey, 7)}: 65, 70, 76; +#{getCssVarName(grey, 8)}: 46, 50, 55; +#{getCssVarName(grey, 9)}: 29, 31, 35; + + +// 蓝青色 +#{getCssVarName(blue, cyan, 0)}: 217, 236, 255; +#{getCssVarName(blue, cyan, 1)}: 164, 201, 238; +#{getCssVarName(blue, cyan, 2)}: 131, 171, 212; +#{getCssVarName(blue, cyan, 3)}: 105, 148, 190; +#{getCssVarName(blue, cyan, 4)}: 85, 125, 165; +#{getCssVarName(blue, cyan, 5)}: 70, 107, 144; +#{getCssVarName(blue, cyan, 6)}: 48, 81, 115; +#{getCssVarName(blue, cyan, 7)}: 36, 65, 96; +#{getCssVarName(blue, cyan, 8)}: 26, 52, 79; +#{getCssVarName(blue, cyan, 9)}: 16, 41, 68; + +// 天蓝色 +#{getCssVarName(sky, blue, 0)}: 232, 249, 255; +#{getCssVarName(sky, blue, 1)}: 183, 232, 251; +#{getCssVarName(sky, blue, 2)}: 135, 212, 248; +#{getCssVarName(sky, blue, 3)}: 89, 190, 244; +#{getCssVarName(sky, blue, 4)}: 44, 165, 241; +#{getCssVarName(sky, blue, 5)}: 0, 137, 237; +#{getCssVarName(sky, blue, 6)}: 0, 108, 197; +#{getCssVarName(sky, blue, 7)}: 0, 81, 157; +#{getCssVarName(sky, blue, 8)}: 0, 56, 117; +#{getCssVarName(sky, blue, 9)}: 0, 34, 77; + +// 黑、白色 +#{getCssVarName(white)}: 255, 255, 255; +#{getCssVarName(black)}: 0, 0, 0; + +// 功能色 +#{getCssVarName(color, white)}: rgba(var(#{getCssVarName(white)}),1); // 浅色模式下深色背景内容Inverse +#{getCssVarName(color, black)}: rgba(var(#{getCssVarName(black)}),1); // 深色模式下浅色背景内容Inverse + +// 主要颜色 +#{getCssVarName(color, primary)}: rgba(var(#{getCssVarName(sky, blue, 5)}),1); // 主要颜色。仅在需要非常强调的情况下使用。 +#{getCssVarName(color, primary, text)}: rgba(var(#{getCssVarName(white)}), 1); // 主要颜色文字色,和背景色形成对比 +#{getCssVarName(color, primary, hover)}: rgba(var(#{getCssVarName(sky, blue, 4)}),1); // 主要颜色悬浮态 +#{getCssVarName(color, primary, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 主要颜色文字色,和背景色形成对比 +#{getCssVarName(color, primary, active)}: rgba(var(#{getCssVarName(sky, blue, 6)}),1); // 主要颜色激活态 +#{getCssVarName(color, primary, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 主要颜色文字色,和背景色形成对比 +#{getCssVarName(color, primary, disabled)}: rgba(var(#{getCssVarName(sky, blue, 2)}),1); // 主要颜色禁用态 +#{getCssVarName(color, primary, disabled, text)}: rgba(var(#{getCssVarName(white)}), 1); // 主要颜色文字色,和背景色形成对比 +#{getCssVarName(color, primary, light, default)}: rgba(var(#{getCssVarName(sky, blue, 0)}),1); // 浅版主要颜色(多用于背景)。仅在需要非常强调的情况下使用。 +#{getCssVarName(color, primary, light, hover)}: rgba(var(#{getCssVarName(sky, blue, 1)}),1); // 浅版主要颜色悬浮态 +#{getCssVarName(color, primary, light, active)}: rgba(var(#{getCssVarName(sky, blue, 2)}),1); // 浅版主要颜色激活态 + +// 次要颜色 +#{getCssVarName(color, secondary)}: rgba(var(#{getCssVarName(light, blue, 5)}),1); // 次要颜色。强调作用次于主要颜色,但仍然具有强调作用。 +#{getCssVarName(color, secondary, text)}: rgba(var(#{getCssVarName(white)}), 1); // 次要颜色文字色,和背景色形成对比 +#{getCssVarName(color, secondary, hover)}: rgba(var(#{getCssVarName(light, blue, 6)}),1); // 次要颜色悬浮态 +#{getCssVarName(color, secondary, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 次要颜色文字色,和背景色形成对比 +#{getCssVarName(color, secondary, active)}: rgba(var(#{getCssVarName(light, blue, 7)}),1); // 次要颜色激活态 +#{getCssVarName(color, secondary, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 次要颜色文字色,和背景色形成对比 +#{getCssVarName(color, secondary, disabled)}: rgba(var(#{getCssVarName(light, blue, 2)}),1); // 次要颜色禁用态 +#{getCssVarName(color, secondary, disabled, text)}: rgba(var(#{getCssVarName(white)}), 1); // 次要颜色文字色,和背景色形成对比 +#{getCssVarName(color, secondary, light, default)}: rgba(var(#{getCssVarName(light, blue, 0)}),1); // 浅版次要颜色(多用于背景)。强调作用次于主要颜色,但仍然具有强调作用。 +#{getCssVarName(color, secondary, light, hover)}: rgba(var(#{getCssVarName(light, blue, 1)}),1); // 浅版次要颜色悬浮态 +#{getCssVarName(color, secondary, light, active)}: rgba(var(#{getCssVarName(light, blue, 2)}),1); // 浅版次要颜色激活态 + +// 第三颜色 +#{getCssVarName(color, tertiary)}: rgba(var(#{getCssVarName(grey, 5)}),1); // 第三颜色,可以在页面上多次使用 +#{getCssVarName(color, tertiary, text)}: rgba(var(#{getCssVarName(white)}), 1); // 第三颜色文字色,和背景色形成对比 +#{getCssVarName(color, tertiary, hover)}: rgba(var(#{getCssVarName(grey, 4)}),1); // 第三颜色悬浮态 +#{getCssVarName(color, tertiary, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 第三颜色文字色,和背景色形成对比 +#{getCssVarName(color, tertiary, active)}: rgba(var(#{getCssVarName(grey, 6)}),1); // 第三颜色激活态 +#{getCssVarName(color, tertiary, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 第三颜色文字色,和背景色形成对比 +#{getCssVarName(color, tertiary, light, default)}: rgba(var(#{getCssVarName(grey, 0)}),1); // 浅版第三颜色(多用于背景),可以在页面上多次使用 +#{getCssVarName(color, tertiary, light, hover)}: rgba(var(#{getCssVarName(grey, 1)}),1); // 浅版第三颜色悬浮态 +#{getCssVarName(color, tertiary, light, active)}: rgba(var(#{getCssVarName(grey, 2)}),1); // 浅版第三颜色激活态 + +// 信息色 +#{getCssVarName(color, info)}: rgba(var(#{getCssVarName(sky, blue, 5)}),1); // 信息色, 通常用于表达客观、中立信息 +#{getCssVarName(color, info, text)}: rgba(var(#{getCssVarName(white)}), 1); // 信息颜色文字色,和背景色形成对比 +#{getCssVarName(color, info, hover)}: rgba(var(#{getCssVarName(sky, blue, 4)}),1); // 信息色悬浮态 +#{getCssVarName(color, info, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 信息颜色文字色,和背景色形成对比 +#{getCssVarName(color, info, active)}: rgba(var(#{getCssVarName(sky, blue, 6)}),1); // 信息色激活态 +#{getCssVarName(color, info, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 信息颜色文字色,和背景色形成对比 +#{getCssVarName(color, info, disabled)}: rgba(var(#{getCssVarName(sky, blue, 2)}),1); // 信息色禁用态 +#{getCssVarName(color, info, disabled, text)}: rgba(var(#{getCssVarName(white)}), 1); // 信息颜色文字色,和背景色形成对比 +#{getCssVarName(color, info, light, default)}: rgba(var(#{getCssVarName(sky, blue, 0)}),1); // 浅版信息色(多用于背景),通常用于表达客观、中立信息 +#{getCssVarName(color, info, light, hover)}: rgba(var(#{getCssVarName(sky, blue, 1)}),1); // 浅版信息色悬浮态 +#{getCssVarName(color, info, light, active)}: rgba(var(#{getCssVarName(sky, blue, 2)}),1); // 浅版信息色激活态 + +// 成功色 +#{getCssVarName(color, success)}: rgba(var(#{getCssVarName(green, 5)}),1); // 成功色,表示安全、成功、开启的状态 +#{getCssVarName(color, success, text)}: rgba(var(#{getCssVarName(white)}), 1); // 成功颜色文字色,和背景色形成对比 +#{getCssVarName(color, success, hover)}: rgba(var(#{getCssVarName(green, 4)}),1); // 成功色悬浮态 +#{getCssVarName(color, success, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 成功颜色文字色,和背景色形成对比 +#{getCssVarName(color, success, active)}: rgba(var(#{getCssVarName(green, 6)}),1); // 成功色激活态 +#{getCssVarName(color, success, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 成功颜色文字色,和背景色形成对比 +#{getCssVarName(color, success, disabled)}: rgba(var(#{getCssVarName(green, 2)}),1); // 成功色禁用态 +#{getCssVarName(color, success, disabled, text)}: rgba(var(#{getCssVarName(white)}), 1); // 成功颜色文字色,和背景色形成对比 +#{getCssVarName(color, success, light, default)}: rgba(var(#{getCssVarName(green, 0)}),1); // 浅版成功色(多用于背景),表示安全、成功、开启的状态 +#{getCssVarName(color, success, light, hover)}: rgba(var(#{getCssVarName(green, 1)}),1); // 浅版成功色悬浮态 +#{getCssVarName(color, success, light, active)}: rgba(var(#{getCssVarName(green, 2)}),1); // 浅版成功色激活态 + +// 警示色 +#{getCssVarName(color, warning)}: rgba(var(#{getCssVarName(orange, 5)}),1); // 警示色,表示警告、不安全的状态 +#{getCssVarName(color, warning, text)}: rgba(var(#{getCssVarName(white)}), 1); // 警告颜色文字色,和背景色形成对比 +#{getCssVarName(color, warning, hover)}: rgba(var(#{getCssVarName(orange, 4)}),1); // 警示色悬浮态 +#{getCssVarName(color, warning, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 警告颜色文字色,和背景色形成对比 +#{getCssVarName(color, warning, active)}: rgba(var(#{getCssVarName(orange, 6)}),1); // 警示色激活态 +#{getCssVarName(color, warning, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 警告颜色文字色,和背景色形成对比 +#{getCssVarName(color, warning, light, default)}: rgba(var(#{getCssVarName(orange, 0)}),1); // 浅版警示色(多用于背景),表示警告、不安全的状态 +#{getCssVarName(color, warning, light, hover)}: rgba(var(#{getCssVarName(orange, 1)}),1); // 浅版警示色悬浮态 +#{getCssVarName(color, warning, light, active)}: rgba(var(#{getCssVarName(orange, 2)}),1); // 浅版警示色激活态 + +// 危险色 +#{getCssVarName(color, danger)}: rgba(var(#{getCssVarName(red, 5)}),1); // 危险色,表示危险的操作、或需要特别注意的危险信息 +#{getCssVarName(color, danger, text)}: rgba(var(#{getCssVarName(white)}), 1); // 危险颜色文字色,和背景色形成对比 +#{getCssVarName(color, danger, hover)}: rgba(var(#{getCssVarName(red, 4)}),1); // 危险色悬浮态 +#{getCssVarName(color, danger, hover, text)}: rgba(var(#{getCssVarName(white)}), 1); // 危险颜色文字色,和背景色形成对比 +#{getCssVarName(color, danger, active)}: rgba(var(#{getCssVarName(red, 6)}),1); // 危险色激活态 +#{getCssVarName(color, danger, active, text)}: rgba(var(#{getCssVarName(white)}), 1); // 危险颜色文字色,和背景色形成对比 +#{getCssVarName(color, danger, light, default)}: rgba(var(#{getCssVarName(red, 0)}),1); // 浅版危险色(多用于背景),表示危险的操作、或需要特别注意的危险信息 +#{getCssVarName(color, danger, light, hover)}: rgba(var(#{getCssVarName(red, 1)}),1); // 浅版危险色悬浮态 +#{getCssVarName(color, danger, light, active)}: rgba(var(#{getCssVarName(red, 2)}),1); // 浅版危险色激活态 + +// 文本/图标颜色 +#{getCssVarName(color, text, 0)}: rgba(var(#{getCssVarName(grey, 9)}),1); // 文本/图标颜色 - 最主要 +#{getCssVarName(color, text, 1)}: rgba(var(#{getCssVarName(grey, 7)}),1); // 文本/图标颜色 - 稍次要 +#{getCssVarName(color, text, 2)}: rgba(var(#{getCssVarName(grey, 6)}),1); // 文本/图标颜色 - 次要 +#{getCssVarName(color, text, 3)}: rgba(var(#{getCssVarName(grey, 4)}),1); // 文本/图标颜色 - 最次要 +#{getCssVarName(color, text, menu)}: rgba(var(#{getCssVarName(grey, 9)}),1); // 文本 - 特殊-菜单颜色 + +// 链接颜色 +#{getCssVarName(color, link)}: rgba(var(#{getCssVarName(sky, blue, 5)}),1); // 链接颜色 +#{getCssVarName(color, link, hover)}: rgba(var(#{getCssVarName(sky, blue, 4)}),1); // 链接颜色 - 悬浮态 +#{getCssVarName(color, link, active)}: rgba(var(#{getCssVarName(sky, blue, 6)}),1); // 链接颜色 - 激活态 +#{getCssVarName(color, link, visited)}: rgba(var(#{getCssVarName(sky, blue, 5)}),1); // 链接颜色 - 已访问 + +// 背景色 +#{getCssVarName(color, bg, 0)}: rgba(var(#{getCssVarName(grey, 0)}),1); // 背景色 - 最下层(底部页面) +#{getCssVarName(color, bg, 1)}: rgba(var(#{getCssVarName(white)}),1); // 背景色 - 次下层(页面中需要提升的内容) +#{getCssVarName(color, bg, 2)}: rgba(var(#{getCssVarName(white)}),1); // 背景色 - 中间层(模态等容器) +#{getCssVarName(color, bg, 3)}: rgba(var(#{getCssVarName(white)}),1); // 背景色 - 次上层(通知,Toast等) +#{getCssVarName(color, bg, 4)}: rgba(var(#{getCssVarName(white)}),1); // 背景色 - 最上层(特殊) +#{getCssVarName(color, bg, 5)}: rgba(var(#{getCssVarName(sky, blue, 4)}),1); // 背景色(特殊) - 应用搜索框使用 +#{getCssVarName(color, bg, overlay)}: rgba(22 22 26 / 60%); // 蒙层背景色 + +// 填充色 +#{getCssVarName(color, fill, 0)}: rgba(var(#{getCssVarName(grey, 8)}),0.05); // 填充色 - 默认态 +#{getCssVarName(color, fill, 1)}: rgba(var(#{getCssVarName(grey, 8)}),0.1); // 填充色 - 悬浮态 +#{getCssVarName(color, fill, 2)}: rgba(var(#{getCssVarName(grey, 8), }),0.13); // 填充色 - 激活态 +#{getCssVarName(color, scroll, menu)}: rgba(var(#{getCssVarName(grey, 8), }),0.13); // 填充色 - 特殊-菜单滚动条颜色 + + +// 边框 +#{getCssVarName(color, border)}: rgba(var(#{getCssVarName(grey, 9)}),0.1); // 默认描边颜色 + +// 禁用态 +#{getCssVarName(color, disabled, text)}: rgba(var(#{getCssVarName(grey, 9)}), 0.35); // 禁用态 - 文字 +#{getCssVarName(color, disabled, border)}: rgba(var(#{getCssVarName(grey, 1)}),1); // 禁用态 - 描边 +#{getCssVarName(color, disabled, bg)}: rgba(var(#{getCssVarName(grey, 1)}),1); // 禁用态 - 背景 +#{getCssVarName(color, disabled, fill)}: rgba(var(#{getCssVarName(grey, 8)}),0.04); // 禁用态 - 填充 + +// 阴影 +#{getCssVarName(color, shadow)}: rgba(var(#{getCssVarName(black)}),0.04); // 用于模拟描边的阴影颜色 +#{getCssVarName(shadow, elevated)}: + 0 0 1px rgba(0 0 0 / 30%), + 0 4px 14px rgba(0 0 0 / 10%); // 用于toast, modal, popover等需要提升层级的界面元素 + +// 字号 +#{getCssVarName('font-size', 'small')}: 12px; +#{getCssVarName('font-size', 'regular')}: 14px; +#{getCssVarName('font-size', 'header-6')}: 16px; +#{getCssVarName('font-size', 'header-5')}: 18px; +#{getCssVarName('font-size', 'header-4')}: 20px; +#{getCssVarName('font-size', 'header-3')}: 24px; +#{getCssVarName('font-size', 'header-2')}: 28px; +#{getCssVarName('font-size', 'header-1')}: 32px; + +// 字重 +#{getCssVarName('font-weight', 'light')}: 200; +#{getCssVarName('font-weight', 'regular')}: 400; +#{getCssVarName('font-weight', 'bold')}: 800; + +// 圆角 +#{getCssVarName(border, radius, extra, small)}: 2px; // 超小圆角,用于 checkbox 内圆角 +#{getCssVarName(border, radius, small)}: 4px; // 小圆角, 用于 button、tag、tabs 等大多数组件, 比较常用 +#{getCssVarName(border, radius, medium)}: 8px; // 中圆角, 用于 dropdown、scrollist、transfer 等菜单类组件 +#{getCssVarName(border, radius, large)}: 12px; // 大圆角, 用于 modal +#{getCssVarName(border, radius, circle)}: 50%; // 全圆角, 用于 avatar, badge 等组件 +#{getCssVarName(border, radius, full)}: 9999px; // 用于创建全尺寸圆角,如胶囊标签等 + +// 高度 +#{getCssVarName('height-control', 'small')}: 24px; +#{getCssVarName('height-control', 'default')}: 32px; +#{getCssVarName('height-control', 'large')}: 40px; + +// 描边尺寸 +#{getCssVarName('border-thickness')}: 0; +#{getCssVarName('border-thickness', 'control')}: 1px; +#{getCssVarName('border-thickness', 'control-focus')}: 1px; + +// 图标尺寸 +#{getCssVarName('width-icon', 'extra-small')}: 8px; +#{getCssVarName('width-icon', 'small')}: 12px; +#{getCssVarName('width-icon', 'medium')}: 16px; +#{getCssVarName('width-icon', 'large')}: 20px; +#{getCssVarName('width-icon', 'extra-large')}: 24px; + +// 间距 +#{getCssVarName('spacing', 'none')}: 0; +#{getCssVarName('spacing', 'super-tight')}: 2px; +#{getCssVarName('spacing', 'extra-tight')}: 4px; +#{getCssVarName('spacing', 'tight')}: 8px; +#{getCssVarName('spacing', 'base-tight')}: 12px; +#{getCssVarName('spacing', 'base')}: 16px; +#{getCssVarName('spacing', 'base-loose')}: 20px; +#{getCssVarName('spacing', 'loose')}: 24px; +#{getCssVarName('spacing', 'extra-loose')}: 32px; +#{getCssVarName('spacing', 'super-loose')}: 40px; +} + +// 主题下的区域性样式覆盖 + +:root.custom_theme { + @include b(panel-app-header) { + #{getCssVarName(grey, 9)}: 255, 255, 255; + #{getCssVarName(color, text, 0)}: rgba(var(#{getCssVarName(grey, 9)}), 1); // 文本/图标颜色 - 最主要 + #{getCssVarName(color, text, 1)}: rgba(var(#{getCssVarName(grey, 9)}), .8); // 文本/图标颜色 - 稍次要 + #{getCssVarName(color, text, 2)}: rgba(var(#{getCssVarName(grey, 9)}), .6); // 文本/图标颜色 - 次要 + #{getCssVarName(color, text, 3)}: rgba(var(#{getCssVarName(grey, 9)}), .35); // 文本/图标颜色 - 最次要 + #{getCssVarName(color, text, menu)}: rgba(var(#{getCssVarName(grey, 9)}), 1); + } +} \ No newline at end of file diff --git a/packages/theme-plugin/src/theme/index.scss b/packages/theme-plugin/src/theme/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..001af8433552fc3e8651dc36ee8596558ae0328d --- /dev/null +++ b/packages/theme-plugin/src/theme/index.scss @@ -0,0 +1,2 @@ +/* 自定义主题 */ +@import './custom-theme'; diff --git a/packages/theme-plugin/src/types.d.ts b/packages/theme-plugin/src/types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..aaa62d9dece1730c44752857aeb93de8b8da3f0f --- /dev/null +++ b/packages/theme-plugin/src/types.d.ts @@ -0,0 +1 @@ +import '@ibiz-template/runtime'; diff --git a/packages/theme-plugin/tsconfig.json b/packages/theme-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/theme-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/theme-plugin/tsconfig.node.json b/packages/theme-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/theme-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/theme-plugin/vite.config.ts b/packages/theme-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/theme-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/toolbar-item-plugin/CHANGELOG.md b/packages/toolbar-item-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/toolbar-item-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/toolbar-item-plugin/README.md b/packages/toolbar-item-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a5a82b95d1f0af24a2334c731b026be359293d11 --- /dev/null +++ b/packages/toolbar-item-plugin/README.md @@ -0,0 +1,251 @@ +# 工具栏项插件 + +## 新建工具栏项绘制插件 + +新建工具栏项绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的工具栏项上绑定插件 + + + +## 插件机制 + +工具栏在绘制工具栏项时,会先通过工具栏项模型拿到对应的适配器,若适配器存在,则绘制适配器component属性绑定的组件,否则根据工具栏内部逻辑进行绘制。详情如下: + +```tsx +// 绘制工具栏项 +const renderToolbarItem = (item: IDEToolbarItem): VNode | null => { + const itemId = item.id!; + const visible = c.state.buttonsState[itemId]?.visible; + const provider = c.itemProviders[itemId]; + if (!visible) { + return null; + } + if (provider) { + const component = resolveComponent(provider.component); + return h(component, { + key: itemId, + class: [ns.e('item')], + item, + controller: c, + }); + } + if (item.itemType === 'SEPERATOR') { + if (c.state.hideSeparator.includes(itemId)) { + return null; + } + return ( +
+ | +
+ ); + } + if (item.itemType === 'RAWITEM') { + return ( +
+ => handleClick(item, e)} + > +
+ ); + } + if (item.itemType === 'DEUIACTION') { + const actionId = (item as IDETBUIActionItem).uiactionId; + const buttonStyle = (item as IDETBUIActionItem).buttonStyle; + if (actionId === 'exportexcel' || actionId === 'gridview_exportaction') { + return ( + btnContent(barItem, c.state)} + size={btnSize.value} + controller={c} + onExportExcel={(e: MouseEvent, data: IData): void => { + handleClick(item, e, data); + }} + > + ); + } + if (actionId === 'shortcut') { + return ( + => handleClick(item, e)} + /> + ); + } + return ( +
+ => handleClick(item, e)} + > + {btnContent(item, c.state)} + +
+ ); + } + if (item.itemType === 'ITEMS') { + const groupItem = item as IDETBGroupItem; + const groupButtonStyle = groupItem.buttonStyle || ''; + + if (groupItem.groupExtractMode && groupItem.uiactionGroup) { + const extractName = `extract-mode-${ + groupItem.groupExtractMode?.toLowerCase() || 'item' + }`; + return ( +
+ {renderActionGroup(item)} +
+ ); + } + return ( + + {renderSubmenu(item)} + + ); + } + return null; +}; +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ toolbar-item-plugin 工具栏项插件顶层目录,可根据实际业务命名 + |─ ─ src 工具栏项插件源代码目录 +​ |─ ─ toolbar-item-plugin.provider.ts 工具栏项插件适配器 +​ |─ ─ toolbar-item-plugin.scss 工具栏项插件样式 +​ |─ ─ toolbar-item-plugin.tsx 工具栏项插件组件 +​ |─ ─ index.ts 工具栏项插件入口文件 +``` + +### 工具栏项插件入口文件 + +工具栏项插件入口文件会全局注册工具栏项插件适配器和工具栏项插件组件,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerToolbarItemProvider } from '@ibiz-template/runtime'; +import { ToolbarItemPlugin } from './toolbar-item-plugin'; +import { ToolbarItemPluginProvider } from './toolbar-item-plugin.provider'; + +export default { + install(app: App) { + // 全局注册工具栏项插件组件 + app.component(ToolbarItemPlugin.name!, ToolbarItemPlugin); + // 全局注册工具栏项插件适配器,TOOLBAR_ITEM是插件类型,R9ToolbarItemPluginId是插件标识 + registerToolbarItemProvider( + 'TOOLBAR_ITEM_R9ToolbarItemPluginId', + () => new ToolbarItemPluginProvider(), + ); + }, +}; +``` + +### 工具栏项插件组件 + +工具栏项插件组件使用tsx的书写方式,承载工具栏项绘制的内容,可根据需求自定义内容呈现。 + +### 工具栏项插件适配器 + +工具栏项插件适配器主要通过component属性指定工具栏项实际要绘制的组件。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/toolbar-item-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import ToolbarItemPlugin from '@ibiz-plugin-template/toolbar-item-plugin'; + +export default { + install(app: App) { + app.use(ToolbarItemPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/toolbar-item-plugin/); + }, +}; +``` diff --git a/packages/toolbar-item-plugin/RUNTIME-PLUGIN-MODE.md b/packages/toolbar-item-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/toolbar-item-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/toolbar-item-plugin/package.json b/packages/toolbar-item-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..62bb3f34e21bd5e25a8c6e04255ba1f7122062a3 --- /dev/null +++ b/packages/toolbar-item-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/toolbar-item-plugin", + "version": "0.0.1", + "description": "工具栏项插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/toolbar-item-plugin/public/docs/image.png b/packages/toolbar-item-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..b044af2330746c9489c568642f8944a53e7a7f79 Binary files /dev/null and b/packages/toolbar-item-plugin/public/docs/image.png differ diff --git a/packages/toolbar-item-plugin/public/docs/image2.png b/packages/toolbar-item-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..7f9e6b74bb384de96579b721596abe662f0bd3f9 Binary files /dev/null and b/packages/toolbar-item-plugin/public/docs/image2.png differ diff --git a/packages/toolbar-item-plugin/public/docs/image3.png b/packages/toolbar-item-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..86a69793cddf619a220cb43435fccdf325b9ba3a Binary files /dev/null and b/packages/toolbar-item-plugin/public/docs/image3.png differ diff --git a/packages/toolbar-item-plugin/public/docs/image4.png b/packages/toolbar-item-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..d7c2fe2183d243c0a9945af1ede1cc6404166231 Binary files /dev/null and b/packages/toolbar-item-plugin/public/docs/image4.png differ diff --git a/packages/toolbar-item-plugin/scripts/link.sh b/packages/toolbar-item-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/toolbar-item-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/toolbar-item-plugin/src/index.ts b/packages/toolbar-item-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f65e185b3e1a7b3a63cecc5fd207e0726f654c36 --- /dev/null +++ b/packages/toolbar-item-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue'; +import { registerToolbarItemProvider } from '@ibiz-template/runtime'; +import { ToolbarItemPlugin } from './toolbar-item-plugin'; +import { ToolbarItemPluginProvider } from './toolbar-item-plugin.provider'; + +export default { + install(app: App) { + // 全局注册工具栏项插件组件 + app.component(ToolbarItemPlugin.name!, ToolbarItemPlugin); + // 全局注册工具栏项插件适配器,TOOLBAR_ITEM是插件类型,R9ToolbarItemPluginId是插件标识 + registerToolbarItemProvider( + 'TOOLBAR_ITEM_R9ToolbarItemPluginId', + () => new ToolbarItemPluginProvider(), + ); + }, +}; diff --git a/packages/toolbar-item-plugin/src/toolbar-item-plugin.provider.ts b/packages/toolbar-item-plugin/src/toolbar-item-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c96368957a93842420144a2b418d124eddb6c28 --- /dev/null +++ b/packages/toolbar-item-plugin/src/toolbar-item-plugin.provider.ts @@ -0,0 +1,5 @@ +import { IToolbarItemProvider } from '@ibiz-template/runtime'; + +export class ToolbarItemPluginProvider implements IToolbarItemProvider { + component: string = 'IBizToolbarItemPlugin'; +} diff --git a/packages/toolbar-item-plugin/src/toolbar-item-plugin.scss b/packages/toolbar-item-plugin/src/toolbar-item-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..e22ff98c29828d9ba99168251721a9e65aab3121 --- /dev/null +++ b/packages/toolbar-item-plugin/src/toolbar-item-plugin.scss @@ -0,0 +1,10 @@ +@include b(toolbar-item-plugin) { + display: flex; + align-items: center; + cursor: pointer; + + > ion-icon { + margin-right: 4px; + font-size: 20px; + } +} diff --git a/packages/toolbar-item-plugin/src/toolbar-item-plugin.tsx b/packages/toolbar-item-plugin/src/toolbar-item-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0402d06d577764a6669d8314213dff3363f68763 --- /dev/null +++ b/packages/toolbar-item-plugin/src/toolbar-item-plugin.tsx @@ -0,0 +1,44 @@ +import { PropType, defineComponent } from 'vue'; +import { IDEToolbarItem } from '@ibiz/model-core'; +import { ToolbarController } from '@ibiz-template/runtime'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import './toolbar-item-plugin.scss'; + +export const ToolbarItemPlugin = defineComponent({ + name: 'IBizToolbarItemPlugin', + props: { + item: { + type: Object as PropType, + required: true, + }, + controller: { + type: ToolbarController, + required: true, + }, + }, + setup(props) { + const ns = useNamespace('toolbar-item-plugin'); + + // 处理点击事件 + const handleClick = async ( + item: IDEToolbarItem, + event: MouseEvent, + params?: IData, + ): Promise => { + await props.controller.onItemClick(item, event, params); + }; + + return { ns, handleClick }; + }, + render() { + return ( +
this.handleClick(this.item, e)} + > + + {this.item.caption} +
+ ); + }, +}); diff --git a/packages/toolbar-item-plugin/tsconfig.json b/packages/toolbar-item-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/toolbar-item-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/toolbar-item-plugin/tsconfig.node.json b/packages/toolbar-item-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/toolbar-item-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/toolbar-item-plugin/vite.config.ts b/packages/toolbar-item-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/toolbar-item-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/ui-action-plugin/CHANGELOG.md b/packages/ui-action-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/ui-action-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/ui-action-plugin/README.md b/packages/ui-action-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1bafb3f33b66695d348a75196edbe8071cd72949 --- /dev/null +++ b/packages/ui-action-plugin/README.md @@ -0,0 +1,121 @@ +# 界面行为插件 + +## 新建应用实体界面行为插件 + +新建应用实体界面行为插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的界面行为上绑定插件 + + + +## 插件机制 + +执行界面行为时,会通过模型获取对应的界面行为适配器,然后再执行界面行为适配器的exec方法。详情如下: + +```tsx +// 执行界面行为 +async exec( + actionId: string, + params: IUILogicParams, + appId: string, +): Promise { + const action = await getUIActionById(actionId, appId); + if (!action) { + throw new RuntimeError( + ibiz.i18n.t('runtime.uiAction.noFoundBehaviorModel', { actionId }), + ); + } + // 单项数据的界面行为执行前校验表单的数据,不通过则拦截 + if (action.actionTarget === 'SINGLEDATA') { + const validateResult = await params.view.call(ViewCallTag.VALIDATE); + if (validateResult === false) { + return { cancel: true }; + } + } + const provider = await getUIActionProvider(action); + return provider.exec(action, params); +} +``` + +## 插件示例 + +### 插件效果 + +点击按钮时会弹出自定义提示 + + + +### 插件结构 + +``` +|─ ─ ui-action-plugin 界面行为插件顶层目录,可根据实际业务命名 + |─ ─ src 界面行为插件源代码目录 +​ |─ ─ ui-action-plugin.provider.ts 界面行为插件适配器 +​ |─ ─ index.ts 界面行为插件入口文件 +``` + +### 界面行为插件入口文件 + +界面行为插件入口文件会全局注册界面行为插件适配器,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerUIActionProvider } from '@ibiz-template/runtime'; +import { UiActionPluginProvider } from './ui-action-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册界面行为插件适配器,DEUIACTION是插件类型,R9ActionPluginId是插件标识 + registerUIActionProvider( + 'DEUIACTION_R9ActionPluginId', + () => new UiActionPluginProvider(), + ); + }, +}; +``` + +### 界面行为插件适配器 + +界面行为插件适配器主要通过重写execAction方法去自定义界面行为逻辑。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/ui-action-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import UiActionPlugin from '@ibiz-plugin-template/ui-action-plugin'; + +export default { + install(app: App) { + app.use(UiActionPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/ui-action-plugin/); + }, +}; +``` diff --git a/packages/ui-action-plugin/RUNTIME-PLUGIN-MODE.md b/packages/ui-action-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/ui-action-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/ui-action-plugin/package.json b/packages/ui-action-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..ad428362cb8b1df1fbf82f2522b7fe24bf8c134e --- /dev/null +++ b/packages/ui-action-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/ui-action-plugin", + "version": "0.0.1", + "description": "界面行为插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/ui-action-plugin/public/docs/image.png b/packages/ui-action-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..57bf9b2dc178a0b06725f40c28048094d1989f8d Binary files /dev/null and b/packages/ui-action-plugin/public/docs/image.png differ diff --git a/packages/ui-action-plugin/public/docs/image2.png b/packages/ui-action-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..cc96f27ea65edd18774c54ad58f64c7bdbcfe83e Binary files /dev/null and b/packages/ui-action-plugin/public/docs/image2.png differ diff --git a/packages/ui-action-plugin/public/docs/image3.png b/packages/ui-action-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..872884ec86909d81a226a80b7584c44cc82a2b55 Binary files /dev/null and b/packages/ui-action-plugin/public/docs/image3.png differ diff --git a/packages/ui-action-plugin/scripts/link.sh b/packages/ui-action-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/ui-action-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/ui-action-plugin/src/index.ts b/packages/ui-action-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b71e8bf303bd9b95aac42d1747f57876fe922582 --- /dev/null +++ b/packages/ui-action-plugin/src/index.ts @@ -0,0 +1,13 @@ +import { App } from 'vue'; +import { registerUIActionProvider } from '@ibiz-template/runtime'; +import { UiActionPluginProvider } from './ui-action-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册界面行为插件适配器,DEUIACTION是插件类型,R9ActionPluginId是插件标识 + registerUIActionProvider( + 'DEUIACTION_R9ActionPluginId', + () => new UiActionPluginProvider(), + ); + }, +}; diff --git a/packages/ui-action-plugin/src/ui-action-plugin.provider.ts b/packages/ui-action-plugin/src/ui-action-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..8cef7a04c50734196f8230606e8d53aad5dbf76c --- /dev/null +++ b/packages/ui-action-plugin/src/ui-action-plugin.provider.ts @@ -0,0 +1,18 @@ +import { IAppDEUIAction } from '@ibiz/model-core'; +import { + IUIActionResult, + IUILogicParams, + UIActionProviderBase, +} from '@ibiz-template/runtime'; + +export class UiActionPluginProvider extends UIActionProviderBase { + async execAction( + action: IAppDEUIAction, + args: IUILogicParams, + ): Promise { + ibiz.log.info(action, args); + ibiz.message.success('界面行为插件触发成功!'); + const actionResult: IUIActionResult = {}; + return actionResult; + } +} diff --git a/packages/ui-action-plugin/tsconfig.json b/packages/ui-action-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/ui-action-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/ui-action-plugin/tsconfig.node.json b/packages/ui-action-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/ui-action-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/ui-action-plugin/vite.config.ts b/packages/ui-action-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/ui-action-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/ui-logic-node-plugin/CHANGELOG.md b/packages/ui-logic-node-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/ui-logic-node-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/ui-logic-node-plugin/README.md b/packages/ui-logic-node-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..77db052c5eab76402b62620d5158b47d97f3844b --- /dev/null +++ b/packages/ui-logic-node-plugin/README.md @@ -0,0 +1,112 @@ +# 界面逻辑节点插件 + +## 新建界面逻辑节点插件 + +新建界面逻辑节点插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 在对应的前端插件节点上绑定插件 + + + +## 插件机制 + +界面逻辑节点执行时,会通过模型获取对应的界面逻辑节点适配器,然后再执行界面逻辑节点的exec方法。详情如下: + +```tsx +// 界面逻辑节点执行 +async exec(ctx: UILogicContext): Promise { + ibiz.log.debug( + ibiz.i18n.t('runtime.uiLogic.interfaceLogicNodeFrontendPlugin', { + id: this.model.id, + sysPFPluginId: this.model.sysPFPluginId, + }), + ); + const provider = await getUILogicNodeProvider(this.model); + if (provider) { + await provider.exec(this.model, ctx); + } +} +``` + +## 插件示例 + +### 插件效果 + +表单加载草稿数据成功后会弹出自定义提示 + + + +### 插件结构 + +``` +|─ ─ ui-logic-node-plugin 界面逻辑节点插件顶层目录,可根据实际业务命名 + |─ ─ src 界面逻辑节点插件源代码目录 +​ |─ ─ ui-logic-node-plugin.provider.ts 界面逻辑节点插件适配器 +​ |─ ─ index.ts 界面逻辑节点插件入口文件 +``` + +### 界面逻辑节点插件入口文件 + +界面逻辑节点插件入口文件会全局注册界面逻辑节点插件适配器,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerUILogicNodeProvider } from '@ibiz-template/runtime'; +import { UiLogicNodePluginProvider } from './ui-logic-node-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册界面逻辑节点插件适配器,UILOGICNODE是插件类型,R9UILogicPluginId是插件标识 + registerUILogicNodeProvider( + 'UILOGICNODE_R9UILogicPluginId', + () => new UiLogicNodePluginProvider(), + ); + }, +}; +``` + +### 界面逻辑节点插件适配器 + +界面逻辑节点插件适配器主要通过实现exec方法去自定义界面逻辑节点执行逻辑。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/ui-logic-node-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import UiLogicNodePlugin from '@ibiz-plugin-template/ui-logic-node-plugin'; + +export default { + install(app: App) { + app.use(UiLogicNodePlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/ui-logic-node-plugin/); + }, +}; +``` diff --git a/packages/ui-logic-node-plugin/RUNTIME-PLUGIN-MODE.md b/packages/ui-logic-node-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/ui-logic-node-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/ui-logic-node-plugin/package.json b/packages/ui-logic-node-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..b7f6b8981a0c56e663d7170da9f7002509b2a1ee --- /dev/null +++ b/packages/ui-logic-node-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/ui-logic-node-plugin", + "version": "0.0.1", + "description": "界面逻辑节点插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/ui-logic-node-plugin/public/docs/image.png b/packages/ui-logic-node-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..47046acfccaf28c7959412f7526da36e3269d3bc Binary files /dev/null and b/packages/ui-logic-node-plugin/public/docs/image.png differ diff --git a/packages/ui-logic-node-plugin/public/docs/image2.png b/packages/ui-logic-node-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..4ed7a81045ea40c5e40c589e0adcca0d1b925bcb Binary files /dev/null and b/packages/ui-logic-node-plugin/public/docs/image2.png differ diff --git a/packages/ui-logic-node-plugin/public/docs/image3.png b/packages/ui-logic-node-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..01b3c2d35a57c4e14aa8454f11e39fd4954c9075 Binary files /dev/null and b/packages/ui-logic-node-plugin/public/docs/image3.png differ diff --git a/packages/ui-logic-node-plugin/scripts/link.sh b/packages/ui-logic-node-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/ui-logic-node-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/ui-logic-node-plugin/src/index.ts b/packages/ui-logic-node-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8473eb0b63124f174e4280723423d7f6fe09620b --- /dev/null +++ b/packages/ui-logic-node-plugin/src/index.ts @@ -0,0 +1,13 @@ +import { App } from 'vue'; +import { registerUILogicNodeProvider } from '@ibiz-template/runtime'; +import { UiLogicNodePluginProvider } from './ui-logic-node-plugin.provider'; + +export default { + install(_app: App) { + // 全局注册界面逻辑节点插件适配器,UILOGICNODE是插件类型,R9UILogicPluginId是插件标识 + registerUILogicNodeProvider( + 'UILOGICNODE_R9UILogicPluginId', + () => new UiLogicNodePluginProvider(), + ); + }, +}; diff --git a/packages/ui-logic-node-plugin/src/ui-logic-node-plugin.provider.ts b/packages/ui-logic-node-plugin/src/ui-logic-node-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..78a95976f9d72ccc9a93a056dccfedde928a8120 --- /dev/null +++ b/packages/ui-logic-node-plugin/src/ui-logic-node-plugin.provider.ts @@ -0,0 +1,11 @@ +import { IUILogicContext, IUILogicNodeProvider } from '@ibiz-template/runtime'; +import { IDEUIPFPluginLogic } from '@ibiz/model-core'; + +export class UiLogicNodePluginProvider implements IUILogicNodeProvider { + declare model: IDEUIPFPluginLogic; + + async exec(model: IDEUIPFPluginLogic, ctx: IUILogicContext): Promise { + ibiz.log.info(model, ctx); + ibiz.message.success('界面逻辑节点插件触发成功!'); + } +} diff --git a/packages/ui-logic-node-plugin/tsconfig.json b/packages/ui-logic-node-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/ui-logic-node-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/ui-logic-node-plugin/tsconfig.node.json b/packages/ui-logic-node-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/ui-logic-node-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/ui-logic-node-plugin/vite.config.ts b/packages/ui-logic-node-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/ui-logic-node-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +}); diff --git a/packages/view-plugin/CHANGELOG.md b/packages/view-plugin/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..8ac8e7f05d173977a1ad060d18e7f0b6a5f23cf5 --- /dev/null +++ b/packages/view-plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +# 版本变更日志 + +添加变更日志 \ No newline at end of file diff --git a/packages/view-plugin/README.md b/packages/view-plugin/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c7f308090c904c9a543135597982f0ba8760b0e7 --- /dev/null +++ b/packages/view-plugin/README.md @@ -0,0 +1,291 @@ +# 视图插件 + +## 新建实体视图绘制插件 + +新建实体视图绘制插件,这儿唯一需要注意一点的是,运行时插件模式同时支持远程运行时插件,在实际运用的时候需根据当前项目所选择的插件模式来定义。详情参见 [运行时插件模式](./RUNTIME-PLUGIN-MODE.md) 篇。 + + + +## 新建视图样式 + +新建视图样式,并且绑定上一步新建的视图插件。 + + + +## 在对应的视图上绑定视图样式 + +这儿以编辑视图为例: + + + +## 插件机制 + +view-shell视图层在绘制具体视图时,是通过视图适配器上的component属性去获取组件,然后进行绘制的。详情如下: + +```tsx +// 绘制视图 +render() { + if (this.isComplete && this.provider && this.hasAuthority) { + return h( + resolveComponent(this.provider.component) as string, + { + context: this.curContext, + params: this.curParams, + modelData: clone(this.viewModelData), + ...this.$attrs, + provider: this.provider, + onRedrawView: this.redrawView, + }, + this.$slots, + ); + } + // 无权限访问绘制403界面 + if (!this.hasAuthority) { + const provider = getErrorViewProvider('403'); + if (provider) { + if (typeof provider.component === 'string') { + return h(resolveComponent(provider.component) as string); + } + return h(provider.component); + } + } + return ( +
+ {this.isComplete ? this.errMsg : null} +
+ ); +}, +``` + +如果视图适配器配置了createController方法,则使用createController方法返回的控制器,否则使用传入的控制器。如只需修改控制器逻辑但不改变UI呈现,可直接通过createController方法返回新的控制器即可。详情如下: + +```tsx +// 获取视图控制器 +function useViewController( + fn: (...args: ConstructorParameters) => T, +): T { + // 获取 props + const props = useProps(); + // 获取上层组件的ctx + const ctx = inject('ctx', undefined); + // 上下文里提前预告部件 + ctx?.evt.emit('onForecast', props.modelData.name!); + + // 实例化视图控制器 + const provider = props.provider as IViewProvider | undefined; + let c: IViewController; + if (provider?.createController) { + // 如果适配器给了创建方法使用适配器的方法 + c = provider.createController( + props.modelData, + props.context, + props.params, + ctx, + ) as T; + } else { + c = fn(props.modelData, props.context, props.params, ctx) as T; + } + + ibiz.util.viewStack.add(c.id, c); + + watchAndUpdateContextParams(props, c); + watchAndUpdateState(props, c); + + // 提供自身的ctx给下层组件 + provide('ctx', (c as any).ctx); + // 让state 响应式 + c.state = reactive(c.state) as any; + + // 让state 响应式 + c.slotProps = reactive(c.slotProps) as any; + + // 从props赋值modal,如果存在的话。 + if (props.modal) { + c.modal = props.modal; + } + + onActivated(() => { + c.onActivated(); + ibiz.util.viewStack.active(c.id); + }); + + onDeactivated(() => { + c.onDeactivated(); + ibiz.util.viewStack.deactivate(c.id); + }); + + c.force = useForce(); + + const vue = getCurrentInstance()!.proxy!; + c.evt.onAll((eventName, event) => { + vue.$emit(eventName.slice(2), event); + }); + + c.created(); + + // 卸载时销毁 + onBeforeUnmount(() => { + c.destroyed(); + ibiz.util.viewStack.remove(c.id); + }); + return c as T; +} +``` + +## 插件示例 + +### 插件效果 + +#### 使用插件前 + + + +#### 使用插件后 + + + +### 插件结构 + +``` +|─ ─ view-plugin 视图插件顶层目录,可根据实际业务命名 + |─ ─ src 视图插件源代码目录 +​ |─ ─ view-plugin.controller.ts 视图插件控制器 +​ |─ ─ view-plugin.engine.ts 视图插件引擎 +​ |─ ─ view-plugin.provider.ts 视图插件适配器 +​ |─ ─ view-plugin.scss 视图插件样式 +​ |─ ─ view-plugin.tsx 视图插件组件 +​ |─ ─ index.ts 视图插件入口文件 +``` + +### 视图插件入口文件 + +视图插件入口文件会全局注册视图插件适配器和视图插件组件,供外部使用。 + +```typescript +import { App } from 'vue'; +import { registerViewProvider } from '@ibiz-template/runtime'; +import { ViewPlugin } from './view-plugin'; +import { ViewPluginProvider } from './view-plugin.provider'; + +export default { + install(app: App) { + // 全局注册视图插件组件 + app.component(ViewPlugin.name!, ViewPlugin); + // 全局注册视图插件适配器,VIEW_CUSTOM是插件类型,R9ViewPluginId是插件标识 + registerViewProvider( + 'VIEW_CUSTOM_R9ViewPluginId', + () => new ViewPluginProvider(), + ); + }, +}; +``` + +### 视图插件组件 + +视图插件组件使用tsx的书写方式,承载视图绘制的内容,可拷贝基础UI绘制,然后根据需求进行调整。其中,基础UI绘制可参考附录中UI呈现部分。 + +### 视图插件适配器 + +视图插件适配器主要通过component属性指定视图实际要绘制的组件,并且通过createController方法返回传递给视图的控制器。 + +### 视图插件控制器 + +视图插件控制器可继承基础控制器,然后根据需求进行调整。其中,基础控制器可参考附录中控制器部分。 + +### 视图插件引擎 + +视图插件引擎可继承基础引擎,然后根据需求进行调整。其中,基础引擎可参考附录中引擎部分。 + +## 本地开发 + +1. 安装依赖并link至全局 + +```sh +// 安装依赖 +pnpm i + +// link底包 +./scripts/link.sh + +// 启动 +pnpm dev + +// link到全局 +pnpm link --global +``` + +2. 主项目包中引用插件 + +```sh +// link插件 +pnpm link --global '@ibiz-plugin-template/view-plugin' +``` + +3. 主项目包中注册插件 + +```ts +import { App } from 'vue'; +import ViewPlugin from '@ibiz-plugin-template/view-plugin'; + +export default { + install(app: App) { + app.use(ViewPlugin); + // 设置本地开发需要忽略加载的插件,可填写正则或全匹配字符串。匹配插件在modeling建模平台配置的[运行时插件仓库配置]内容 + ibiz.plugin.setDevIgnore(/^@ibiz-plugin-template\/view-plugin/); + }, +}; +``` + +## 附录 + +| 视图类型 | UI呈现 | 控制器 | 引擎 | +| :----------------------------: | :-----------: | :-------------------------: | :----------------------: | +| 应用数据导入视图 | View | AppDataUploadViewController | AppDataUploadViewEngine | +| 日历导航视图 | View | ViewController | CalendarExpViewEngine | +| 日历视图 | View | ViewController | CalendarViewEngine | +| 图表导航视图 | View | ViewController | ChartExpViewEngine | +| 图表视图 | View | ViewController | ChartViewEngine | +| 自定义视图 | View | ViewController | CustomViewEngine | +| 卡片导航视图 | View | ViewController | DataViewExpViewEngine | +| 卡片视图 | View | ViewController | DataViewEngine | +| 实体首页视图 | View | ViewController | DEIndexViewEngine | +| 编辑视图 | View | ViewController | EditViewEngine | +| 编辑视图2(左右关系) | View | ViewController | EditView2Engine | +| 编辑视图3(分页关系) | View | ViewController | EditView3Engine | +| 编辑视图4(上下关系) | View | ViewController | EditView4Engine | +| 表单选择数据视图 | View | ViewController | FormPickupDataViewEngine | +| 甘特图视图 | View | ViewController | GanttViewEngine | +| 表格导航视图 | View | ViewController | GridExpViewEngine | +| 表格视图 | View | ViewController | GridViewEngine | +| 首页视图 | View | ViewController | IndexViewEngine | +| 看板视图 | View | ViewController | KanbanViewEngine | +| 列表导航视图 | View | ViewController | ListExpViewEngine | +| 列表视图 | View | ViewController | ListViewEngine | +| 登录视图 | View | ViewController | LoginViewEngine | +| 地图视图 | View | ViewController | MapViewEngine | +| 多数据自定义视图 | View | ViewController | MDCustomViewEngine | +| 多表单编辑视图 | View | ViewController | MEditView9Engine | +| 多数据选择视图 | View | ViewController | MPickupViewEngine | +| 多数据选择视图(左右关系) | View | ViewController | MPickupView2Engine | +| 选项操作视图 | View | ViewController | OptViewEngine | +| 面板视图 | View | ViewController | PanelViewEngine | +| 选择数据视图 | View | ViewController | PickupDataViewEngine | +| 选择表格视图 | View | ViewController | PickupGridViewEngine | +| 选择树视图 | View | ViewController | PickupTreeViewEngine | +| 数据选择视图 | View | ViewController | PickupViewEngine | +| 数据选择视图(左右关系) | View | ViewController | PickupView2Engine | +| 数据看板视图 | PortalView | ViewController | PortalViewEngine | +| 报表视图 | View | ViewController | ReportViewEngine | +| 子系统引用视图 | SubAppRefView | ViewController | SubAppRefViewEngine | +| 分页导航视图 | View | ViewController | TabExpViewEngine | +| 分页搜索视图 | View | ViewController | TabSearchViewEngine | +| 树导航视图 | View | ViewController | TreeExpViewEngine | +| 树表格视图(增强) | View | ViewController | TreeGridExViewEngine | +| 树表格视图 | View | ViewController | TreeGridViewEngine | +| 树视图 | View | ViewController | TreeViewEngine | +| 工作流动态操作视图 | View | ViewController | WFDynaActionViewEngine | +| 工作流动态编辑视图 | View | ViewController | WFDynaEditViewEngine | +| 工作流动态编辑视图(分页关系) | View | ViewController | WFDynaEditView3Engine | +| 工作流动态启动视图 | View | ViewController | WFDynaStartViewEngine | +| 应用流程处理记录视图 | View | ViewController | WFStepDataViewEngine | +| 向导视图 | View | ViewController | WizardViewEngine | diff --git a/packages/view-plugin/RUNTIME-PLUGIN-MODE.md b/packages/view-plugin/RUNTIME-PLUGIN-MODE.md new file mode 100644 index 0000000000000000000000000000000000000000..dbe8a2b8aeeecb3d044d6a44a2ba70f24afe1ed1 --- /dev/null +++ b/packages/view-plugin/RUNTIME-PLUGIN-MODE.md @@ -0,0 +1,74 @@ +# 运行时插件模式 + +| 插件模式 | 是否已支持 | +| :------------: | :--------: | +| 非运行时插件 | 是 | +| 本地运行时插件 | 否 | +| 远程运行时插件 | 是 | + +## 非运行时插件 + +非运行时插件与主项目一起编译打包,主项目中可以直接引用该插件,并且通过vue插件的形式挂载。 + +```typescript +import { App } from 'vue'; +import plugin from '@ibiz-template-plugin-example/example-one'; + +// 挂载插件 +export default { + install(app: App): void { + app.use(plugin); + }, +}; +``` + +## 远程运行时插件 + +远程运行时插件单独部署在cdn上。主项目中不能直接引用该插件,在使用到时才会去远程加载该插件。cdn请求基础路径pluginBaseUrl可在environment.js中配置。运行时插件仓库配置填写需参照[unpkg规范](https://unpkg.com); + +```typescript +// 加载远程插件 +async loadPluginRef( + rtObjectName: string, + rtObjectRepo: string, + ): Promise { + if (this.isIgnore(rtObjectRepo)) { + return true; + } + if (this.pluginCache.has(rtObjectName)) { + return true; + } + let configData: unknown = null; + { + const pluginPath: string = rtObjectRepo; + const configUrl = this.urlReg.test(pluginPath) + ? `${pluginPath}/package.json` + : `${ibiz.env.pluginBaseUrl}/${join(pluginPath, 'package.json')}`; + const res = await ibiz.net.axios({ + method: 'get', + headers: { 'Access-Control-Allow-Origin': '*' }, + url: configUrl, + }); + if (res.status !== 200) { + throw new Error(`配置加载失败`); + } + configData = res.data; + } + const remotePlugin = new RemotePluginItem( + rtObjectName, + rtObjectRepo, + configData as RemotePluginConfig, + ); + if (remotePlugin) { + await this.loadPluginExternal(remotePlugin.config); + try { + await this.loadScript(remotePlugin); + this.pluginCache.set(rtObjectName, remotePlugin); + return true; + } catch (error) { + ibiz.log.error(error); + } + } + return false; + } +``` diff --git a/packages/view-plugin/package.json b/packages/view-plugin/package.json new file mode 100644 index 0000000000000000000000000000000000000000..5126a902865348c08c5365d6087a13a5cc9be9f3 --- /dev/null +++ b/packages/view-plugin/package.json @@ -0,0 +1,107 @@ +{ + "name": "@ibiz-plugin-template/view-plugin", + "version": "0.0.1", + "description": "视图插件", + "author": "iBiz", + "license": "MIT", + "type": "module", + "main": "dist/index.es.js", + "module": "dist/index.es.js", + "types": "dist/types/index.d.ts", + "system": "dist/index.legacy.js", + "styles": [ + "dist/style.css" + ], + "files": [ + "dist", + "public", + "CHANGELOG.md", + "README.md", + "RUNTIME-PLUGIN-MODE.md" + ], + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "dev": "vite build --watch", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@floating-ui/dom": "^1.5.3", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "@qx-chitanda/vite-plugin-lib-legacy": "^4.1.1", + "@types/lodash-es": "^4.17.12", + "@types/ramda": "^0.29.6", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "@vitejs/plugin-vue": "^4.4.1", + "@vitejs/plugin-vue-jsx": "^3.0.2", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "eslint": "^8.53.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-unused-imports": "^3.0.0", + "eslint-plugin-vue": "^9.18.1", + "husky": "^8.0.3", + "lerna": "^8.0.0", + "lint-staged": "^15.1.0", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "postcss": "^8.4.31", + "prettier": "^3.1.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "rollup-plugin-visualizer": "^5.9.2", + "sass": "1.69.5", + "stylelint": "15.11.0", + "stylelint-config-prettier": "^9.0.5", + "stylelint-config-recess-order": "^4.3.0", + "stylelint-config-standard": "^34.0.0", + "stylelint-config-standard-scss": "^11.1.0", + "stylelint-scss": "5.3.1", + "terser": "^5.24.0", + "typescript": "5.2.2", + "vite": "^4.5.0", + "vite-plugin-dts": "^3.6.3", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-libcss": "^1.1.1", + "vue": "^3.3.8", + "vue-eslint-parser": "^9.3.2", + "vue-router": "^4.2.5", + "vue-tsc": "1.8.22", + "vuedraggable": "^4.1.0" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.5.1", + "@ibiz-template/core": "^0.7.40-alpha.7", + "@ibiz-template/model-helper": "^0.7.40-alpha.8", + "@ibiz-template/runtime": "^0.7.40-alpha.8", + "@ibiz-template/theme": "^0.7.39", + "@ibiz-template/vue3-util": "^0.7.40-alpha.8", + "@ibiz/model-core": "^0.1.72", + "async-validator": "^4.2.5", + "axios": "^1.6.2", + "dayjs": "^1.11.10", + "element-plus": "^2.4.2", + "lodash-es": "^4.17.21", + "pluralize": "^8.0.0", + "qs": "^6.11.2", + "qx-util": "^0.4.8", + "ramda": "^0.29.1", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vuedraggable": "^4.1.0" + } +} diff --git a/packages/view-plugin/public/docs/image.png b/packages/view-plugin/public/docs/image.png new file mode 100644 index 0000000000000000000000000000000000000000..234239060e6bc452fccfb2f1998e971048c7dd18 Binary files /dev/null and b/packages/view-plugin/public/docs/image.png differ diff --git a/packages/view-plugin/public/docs/image2.png b/packages/view-plugin/public/docs/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..10cb7b8290e242f758fe92509d6cc744037892ea Binary files /dev/null and b/packages/view-plugin/public/docs/image2.png differ diff --git a/packages/view-plugin/public/docs/image3.png b/packages/view-plugin/public/docs/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..e38c6b91c8a519a71eebe904bc5e6217e3db4f49 Binary files /dev/null and b/packages/view-plugin/public/docs/image3.png differ diff --git a/packages/view-plugin/public/docs/image4.png b/packages/view-plugin/public/docs/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..7f01c8869728a9b121ae6c728347908252907601 Binary files /dev/null and b/packages/view-plugin/public/docs/image4.png differ diff --git a/packages/view-plugin/public/docs/image5.png b/packages/view-plugin/public/docs/image5.png new file mode 100644 index 0000000000000000000000000000000000000000..3045263f1bf78a005953772678e1b8d53ddb94e5 Binary files /dev/null and b/packages/view-plugin/public/docs/image5.png differ diff --git a/packages/view-plugin/scripts/link.sh b/packages/view-plugin/scripts/link.sh new file mode 100644 index 0000000000000000000000000000000000000000..d4edc1556f72a02300c491cda3c13a7ff34795a7 --- /dev/null +++ b/packages/view-plugin/scripts/link.sh @@ -0,0 +1,6 @@ +pnpm link --global "@ibiz-template/vue3-util" +pnpm link --global "@ibiz-template/model-helper" +pnpm link --global "@ibiz-template/runtime" +pnpm link --global "@ibiz-template/core" +pnpm link --global "@ibiz-template/theme" +pnpm link --global "@ibiz-template/vue3-components" diff --git a/packages/view-plugin/src/index.ts b/packages/view-plugin/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..7ed2e42c5450ac96b4088d478b615e9f045e65b6 --- /dev/null +++ b/packages/view-plugin/src/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue'; +import { registerViewProvider } from '@ibiz-template/runtime'; +import { ViewPlugin } from './view-plugin'; +import { ViewPluginProvider } from './view-plugin.provider'; + +export default { + install(app: App) { + // 全局注册视图插件组件 + app.component(ViewPlugin.name!, ViewPlugin); + // 全局注册视图插件适配器,VIEW_CUSTOM是插件类型,R9ViewPluginId是插件标识 + registerViewProvider( + 'VIEW_CUSTOM_R9ViewPluginId', + () => new ViewPluginProvider(), + ); + }, +}; diff --git a/packages/view-plugin/src/view-plugin.controller.ts b/packages/view-plugin/src/view-plugin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd90cf6ee3fc91a7aad38108771b031ef187c32f --- /dev/null +++ b/packages/view-plugin/src/view-plugin.controller.ts @@ -0,0 +1,8 @@ +import { ViewController } from '@ibiz-template/runtime'; +import { ViewPluginEngine } from './view-plugin.engine'; + +export class ViewPluginController extends ViewController { + protected initEngines(): void { + this.engines.push(new ViewPluginEngine(this)); + } +} diff --git a/packages/view-plugin/src/view-plugin.engine.ts b/packages/view-plugin/src/view-plugin.engine.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d02e59c036a9134fd42a6fbb6823ea2ee0e8a76 --- /dev/null +++ b/packages/view-plugin/src/view-plugin.engine.ts @@ -0,0 +1,3 @@ +import { ViewEngineBase } from '@ibiz-template/runtime'; + +export class ViewPluginEngine extends ViewEngineBase {} diff --git a/packages/view-plugin/src/view-plugin.provider.ts b/packages/view-plugin/src/view-plugin.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..39bfd0fcf0c61f29f04470ccb74a047417ced172 --- /dev/null +++ b/packages/view-plugin/src/view-plugin.provider.ts @@ -0,0 +1,16 @@ +import { CTX, IViewController, IViewProvider } from '@ibiz-template/runtime'; +import { IAppView } from '@ibiz/model-core'; +import { ViewPluginController } from './view-plugin.controller'; + +export class ViewPluginProvider implements IViewProvider { + component: string = 'IBizViewPlugin'; + + createController( + model: IAppView, + context: IContext, + params?: IParams, + ctx?: CTX, + ): IViewController { + return new ViewPluginController(model, context, params, ctx); + } +} diff --git a/packages/view-plugin/src/view-plugin.scss b/packages/view-plugin/src/view-plugin.scss new file mode 100644 index 0000000000000000000000000000000000000000..938fa08f916a26facbcd32fa1282849304f5ceb9 --- /dev/null +++ b/packages/view-plugin/src/view-plugin.scss @@ -0,0 +1,5 @@ +@include b(view-plugin-footer){ + width: 100%; + height: 100%; + border: 1px solid red; +} \ No newline at end of file diff --git a/packages/view-plugin/src/view-plugin.tsx b/packages/view-plugin/src/view-plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1e94a5cb8f2f5b1d40eb32aecae78f219f30c859 --- /dev/null +++ b/packages/view-plugin/src/view-plugin.tsx @@ -0,0 +1,251 @@ +import { + IModal, + IViewLayoutPanelController, + IViewProvider, + getControlsByView, + getCtrlTeleportParams, + getErrorViewProvider, +} from '@ibiz-template/runtime'; +import { + PropType, + Teleport, + VNode, + computed, + defineComponent, + h, + renderSlot, + resolveComponent, +} from 'vue'; +import { + useNamespace, + useViewController, + useViewOperation, +} from '@ibiz-template/vue3-util'; +import { IAppView, IControl } from '@ibiz/model-core'; +import { ViewPluginController } from './view-plugin.controller'; +import './view-plugin.scss'; + +export const ViewPlugin = defineComponent({ + name: 'IBizViewPlugin', + props: { + /** + * @description 应用上下文 + */ + context: { type: Object as PropType }, + /** + * @description 视图参数 + * @default {} + */ + params: { type: Object as PropType, default: () => ({}) }, + /** + * @description 视图模型 + */ + modelData: { type: Object as PropType, required: true }, + /** + * @description 视图模态操作对象,在模态等形式打开视图时,需给视图注入此对象 + */ + modal: { type: Object as PropType }, + /** + * @description 视图状态 + */ + state: { type: Object as PropType }, + /** + * @description 视图适配器 + */ + provider: { type: Object as PropType }, + }, + setup(_props, { slots }) { + const ns = useNamespace('view'); + const ns2 = useNamespace('view-plugin'); + const c = useViewController((...args) => new ViewPluginController(...args)); + // 监听视图用户操作 + useViewOperation(c); + // 视图部件模型在viewlayoutPanel里面。 + const allControls = getControlsByView(c.model); + const teleportControls: IControl[] = []; + const teleportTags = new Map(); + const controls: IControl[] = []; + + allControls.forEach(ctrl => { + const { teleportFlag, teleportTag } = getCtrlTeleportParams(ctrl); + // 配置了tag或者flag,则认为是teleport部件 + if (!!teleportTag || teleportFlag) { + teleportControls.push(ctrl); + teleportTags.set(ctrl.id!, teleportTag || ''); + } else { + controls.push(ctrl); + } + }); + + const getCtrlTeleportTag = (ctrl: IControl): string | undefined => { + const tag = teleportTags.get(ctrl.id!); + if (tag) { + return tag; + } + const placeholderC = c.parentView?.layoutPanel?.panelItems[ctrl.name!]; + if (placeholderC) { + return `#${(placeholderC.state as IData).teleportTag}`; + } + }; + + const { viewType, sysCss, codeName } = c.model; + const typeClass = viewType!.toLowerCase(); + const sysCssName = sysCss?.cssName; + const viewClassNames = computed(() => [ + ns.b(), + true && ns.b(typeClass), + true && ns.m(codeName), + true && sysCssName, + c.state.viewMessages.TOP ? 'has-top-message' : '', + c.state.viewMessages.BOTTOM ? 'has-bottom-message' : '', + c.state.presetClassList, + ]); + + const onLayoutPanelCreated = ( + controller: IViewLayoutPanelController, + ): void => { + c.setLayoutPanel(controller as IViewLayoutPanelController); + }; + + const getControlStyle = () => { + const result = {}; + Object.assign(result, { + display: c.state.hasError ? 'none' : 'initial', + }); + return result; + }; + + const getCtrlProps = (ctrl: IControl, slotProps: IData = {}): IParams => { + const slotKey = ctrl.name! || ctrl.id!; + return { + context: c.context, + params: c.params, + modelData: ctrl, + ...(c.slotProps[slotKey] || {}), + ...slotProps, + }; + }; + + const renderControl = (ctrl: IControl, slotProps: IData = {}): VNode => { + const slotKey = ctrl.name! || ctrl.id!; + const ctrlProps = getCtrlProps(ctrl, slotProps); + if (slots[slotKey]) { + return renderSlot(slots, slotKey, ctrlProps); + } + + const provider = c.providers[slotKey]; + const comp = resolveComponent( + provider?.component || 'IBizControlShell', + ) as string; + if (provider) { + ctrlProps.provider = provider; + } + return h(comp, ctrlProps); + }; + + return { + c, + ns, + ns2, + controls, + teleportControls, + viewClassNames, + onLayoutPanelCreated, + getCtrlProps, + renderControl, + getCtrlTeleportTag, + getControlStyle, + }; + }, + render() { + let layoutPanel = null; + if (this.c.state.isCreated) { + if (this.c.engines.length === 0) { + layoutPanel = ( + + {ibiz.i18n.t('vue3Util.view.viewType', { + viewType: this.modelData.viewType, + })} + + ); + } else { + // 绘制部件插槽,外部插槽优先 + const slots: IData = { + ...this.$slots, + }; + if (this.controls?.length) { + this.controls.forEach(ctrl => { + const slotKey = ctrl.name! || ctrl.id!; + slots[slotKey] = (slotProps: IData): VNode => { + return this.renderControl(ctrl, slotProps); + }; + }); + } + + // 绘制视图布局面板 + const viewLayoutPanel = this.c.model.viewLayoutPanel!; + const provider = this.c.providers[viewLayoutPanel.name!]; + layoutPanel = h( + resolveComponent(provider.component) as string, + { + modelData: viewLayoutPanel, + context: this.c.context, + params: this.c.params, + provider, + container: this.c, + style: this.getControlStyle(), + onControllerAppear: this.onLayoutPanelCreated, + }, + slots, + ); + } + } + + // 绘制teleport部件 + let teleportContent = null; + if (this.c.state.isCreated && this.teleportControls?.length) { + teleportContent = this.teleportControls.map(ctrl => { + const tag = this.getCtrlTeleportTag(ctrl); + if (!tag) { + ibiz.log.error( + ibiz.i18n.t('vue3Util.view.noTeleportTag', { + name: ctrl.name, + }), + ); + return null; + } + return ( +
+ + {this.renderControl(ctrl)} + +
+ ); + }); + } + + // 绘制错误页面 + let errorContent = null; + if (this.c.state.hasError && this.c.error.status) { + const provider = getErrorViewProvider(this.c.error.status); + if (provider) { + if (typeof provider.component === 'string') { + errorContent = h(resolveComponent(provider.component) as string); + } + errorContent = h(provider.component); + } + } + return ( +
+ {layoutPanel} +
视图底部自定义内容区域
+ {teleportContent} + {errorContent} +
+ ); + }, +}); diff --git a/packages/view-plugin/tsconfig.json b/packages/view-plugin/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..ba8f48be2a612227b3b2cd44668c26316fa3dd4b --- /dev/null +++ b/packages/view-plugin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "jsxImportSource": "vue", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/view-plugin/tsconfig.node.json b/packages/view-plugin/tsconfig.node.json new file mode 100644 index 0000000000000000000000000000000000000000..9d31e2aed93c876bc048cf2f863cb2a847c901e8 --- /dev/null +++ b/packages/view-plugin/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/view-plugin/vite.config.ts b/packages/view-plugin/vite.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc3449a5a42babd06efbe73c42acd8989ffb91fc --- /dev/null +++ b/packages/view-plugin/vite.config.ts @@ -0,0 +1,57 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import libLegacy from '@qx-chitanda/vite-plugin-lib-legacy'; +import dts from 'vite-plugin-dts'; +import libCss from 'vite-plugin-libcss'; +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: './src/index.ts', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: [ + '@ibiz-template/core', + '@ibiz-template/model-helper', + '@ibiz-template/runtime', + '@ibiz-template/theme', + '@ibiz-template/vue3-util', + '@ibiz/dynamic-model-api', + '@floating-ui/dom', + 'async-validator', + 'axios', + 'dayjs', + 'element-plus', + 'lodash-es', + 'pluralize', + 'qs', + 'qx-util', + 'ramda', + 'vue', + 'vue-router', + 'vuedraggable', + ], + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: '@import "@ibiz-template/theme/style/global.scss";', + }, + }, + }, + plugins: [ + // eslint({ + // include: 'src/**/*.{ts,tsx,js,jsx}', + // }), + vue(), + vueJsx(), + libLegacy(), + libCss(), + dts({ + outDir: 'dist/types', + }), + ], +});