Repository: lowcode-scaffold/lowcode-materials Branch: master Commit: 4c2e99a0c8b4 Files: 628 Total size: 756.5 KB Directory structure: gitextract_0an204y0/ ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── README.md ├── buildMaterials.js ├── llm/ │ └── index.js ├── lowcode-context.d.ts ├── materials/ │ ├── blocks/ │ │ ├── antdv2 增删改查列表页/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── schema.ts │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── ModifyModal/ │ │ │ │ ├── index.vue.ejs │ │ │ │ ├── model.ts.ejs │ │ │ │ ├── presenter.ts.ejs │ │ │ │ └── service.ts.ejs │ │ │ ├── api.ts.ejs │ │ │ ├── index.vue.ejs │ │ │ ├── model.ts.ejs │ │ │ ├── presenter.ts.ejs │ │ │ ├── service.ts.ejs │ │ │ ├── temp.mock.script.ejs │ │ │ └── temp.mock.type.ejs │ │ ├── react-mvp 模块/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ └── schema.json │ │ │ └── src/ │ │ │ ├── index.tsx.ejs │ │ │ ├── model.ts.ejs │ │ │ ├── presenter.tsx.ejs │ │ │ └── service.ts.ejs │ │ ├── taro-request/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ └── schema.json │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── config.ts.ejs │ │ │ ├── index.ts.ejs │ │ │ └── interceptors.ts.ejs │ │ ├── vant 表单/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── viewPrompt.ejs │ │ │ ├── script/ │ │ │ │ └── index.js │ │ │ └── src/ │ │ │ ├── index.vue.ejs │ │ │ ├── model.ts.ejs │ │ │ ├── presenter.tsx.ejs │ │ │ └── service.ts.ejs │ │ ├── vue-mvp 模块/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ └── schema.json │ │ │ └── src/ │ │ │ ├── index.tsx.ejs │ │ │ ├── model.ts.ejs │ │ │ ├── presenter.ts.ejs │ │ │ └── service.ts.ejs │ │ ├── vue2-mvp 模块/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ └── schema.json │ │ │ └── src/ │ │ │ ├── index.tsx.ejs │ │ │ ├── model.ts.ejs │ │ │ ├── presenter.ts.ejs │ │ │ └── service.ts.ejs │ │ ├── 测试使用 jsx 作为模版引擎/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ ├── schema.ts │ │ │ │ └── viewPrompt.ejs │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── ModifyModal/ │ │ │ │ ├── index.vue.ejs │ │ │ │ ├── model.ts.ejs │ │ │ │ ├── presenter.tsx.ejs │ │ │ │ └── service.ts.ejs │ │ │ ├── api.ts.ejs │ │ │ ├── api.ts.template.tsx │ │ │ ├── index.vue.ejs │ │ │ ├── model.ts.template.tsx │ │ │ ├── presenter.tsx.ejs │ │ │ ├── service.ts.ejs │ │ │ ├── temp.mock.script.ejs │ │ │ └── temp.mock.type.ejs │ │ ├── 测试脚本/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── viewPrompt.ejs │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ └── README.md │ │ ├── 现有模块中添加 antdv Descriptions 描述列表/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── schema.ts │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── temp.api.ts.ejs │ │ │ ├── temp.index.vue.ejs │ │ │ ├── temp.mock.script │ │ │ ├── temp.mock.type.ejs │ │ │ ├── temp.model.ts.ejs │ │ │ └── temp.service.ts.ejs │ │ ├── 现有模块中添加 antdv Form 垂直布局列表/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── schema.ts │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── temp.api.ts.ejs │ │ │ ├── temp.index.vue.ejs │ │ │ ├── temp.mock.script │ │ │ ├── temp.mock.type.ejs │ │ │ ├── temp.model.ts.ejs │ │ │ └── temp.service.ts.ejs │ │ ├── 现有模块中添加 antdv Form 表单/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── schema.ts │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── temp.index.vue.ejs │ │ │ ├── temp.model.ts.ejs │ │ │ ├── temp.presenter.ts.ejs │ │ │ └── temp.service.ts.ejs │ │ ├── 现有模块中添加 antdv Modal 弹框/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ └── schema.json │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── temp.index.vue.ejs │ │ │ ├── temp.model.ts.ejs │ │ │ └── temp.presenter.ts.ejs │ │ ├── 现有模块中添加 antdv Table 表格/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── schema.ts │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ ├── temp.api.ts.ejs │ │ │ ├── temp.index.vue.ejs │ │ │ ├── temp.mock.script.ejs │ │ │ ├── temp.mock.type.ejs │ │ │ ├── temp.model.ts.ejs │ │ │ ├── temp.presenter.ts.ejs │ │ │ └── temp.service.ts.ejs │ │ ├── 通过 ast 给 antdv Descriptions 描述列表添加字段/ │ │ │ ├── config/ │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ ├── schema.json │ │ │ │ └── viewPrompt.ejs │ │ │ ├── script/ │ │ │ │ ├── index.js │ │ │ │ └── src/ │ │ │ │ ├── context.ts │ │ │ │ └── main.ts │ │ │ └── src/ │ │ │ └── README.md │ │ └── 通过脚本启动一个 nest api 服务/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── README.md │ └── snippets/ │ ├── OCR/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── OCR + ChatGPT 翻译/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── schema.ts │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── Pro Chat/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ ├── controller.ts │ │ ├── main.ts │ │ └── routes.ts │ ├── Pro Chat + Tldraw/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ ├── controller.ts │ │ ├── main.ts │ │ └── routes.ts │ ├── Pro Chat + TypeChat/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── schema.ts │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ └── main.ts │ ├── amis/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── axios-request/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── axios-request-api/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── axios-request-api-外挂脚本/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ ├── genCode/ │ │ │ │ └── genCodeByYapi.ts │ │ │ ├── main.ts │ │ │ └── utils/ │ │ │ ├── config.ts │ │ │ ├── editor.ts │ │ │ ├── ejs.ts │ │ │ ├── file.ts │ │ │ ├── json.ts │ │ │ ├── material.ts │ │ │ └── request.ts │ │ └── src/ │ │ └── template.ejs │ ├── form-render/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── formily/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── share ChatGPT 测试/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── schema.ts │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── start nest api server/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── app.controller.ts │ │ │ ├── app.module.ts │ │ │ ├── app.service.ts │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── taro-request-api/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── umi-request-api/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 动态表单 demo/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ ├── controller.ts │ │ │ ├── main.ts │ │ │ └── routes.ts │ │ └── src/ │ │ └── template.ejs │ ├── 当前目录翻译成英文/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ └── main.ts │ ├── 快速创建区块/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ ├── lowcode/ │ │ │ └── 代码片段/ │ │ │ ├── config/ │ │ │ │ ├── commandPrompt.ejs.ejs │ │ │ │ ├── model.json │ │ │ │ ├── preview.json │ │ │ │ └── schema.json │ │ │ ├── script/ │ │ │ │ ├── index.js.ejs │ │ │ │ └── src/ │ │ │ │ ├── context.ts.ejs │ │ │ │ └── main.ts.ejs │ │ │ └── src/ │ │ │ └── template.ejs.ejs │ │ ├── uTools askChatGPT/ │ │ │ └── script/ │ │ │ ├── index.ts.ejs │ │ │ └── src/ │ │ │ └── main.ts.ejs │ │ ├── uTools 动态表单/ │ │ │ ├── config/ │ │ │ │ ├── config.json │ │ │ │ ├── model.json │ │ │ │ ├── schema.json │ │ │ │ └── schema.ts │ │ │ ├── script/ │ │ │ │ ├── index.ts.ejs │ │ │ │ └── src/ │ │ │ │ ├── controller.ts.ejs │ │ │ │ └── main.ts.ejs │ │ │ └── src/ │ │ │ ├── api.ts.keep.ejs │ │ │ ├── index.vue.keep.ejs │ │ │ ├── model.ts.keep.ejs │ │ │ ├── presenter.ts.keep.ejs │ │ │ ├── service.ts.keep.ejs │ │ │ ├── temp.mock.script │ │ │ └── temp.mock.type.keep.ejs │ │ ├── uTools 自动化脚本/ │ │ │ └── script/ │ │ │ ├── index.ts.ejs │ │ │ └── src/ │ │ │ └── main.ts.ejs │ │ └── uniapp/ │ │ ├── vue3-mvp/ │ │ │ ├── index.scss.ejs │ │ │ ├── index.vue.ejs │ │ │ ├── model.ts │ │ │ ├── presenter.ts │ │ │ └── service.ts │ │ ├── vue3-mvp emit/ │ │ │ ├── index.scss.ejs │ │ │ ├── index.vue.ejs │ │ │ ├── model.ts │ │ │ ├── presenter.ts │ │ │ └── service.ts │ │ ├── vue3-mvp props/ │ │ │ ├── index.scss.ejs │ │ │ ├── index.vue.ejs │ │ │ ├── model.ts │ │ │ ├── presenter.ts │ │ │ └── service.ts │ │ └── vue3-mvp props emit/ │ │ ├── index.scss.ejs │ │ ├── index.vue.ejs │ │ ├── model.ts │ │ ├── presenter.ts │ │ └── service.ts │ ├── 打开webview/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ ├── controller.ts │ │ ├── main.ts │ │ └── routes.ts │ ├── 根据 DevOps 需求标题创建 GIT commit - 截图/ │ │ ├── config/ │ │ │ └── preview.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ └── main.ts │ ├── 根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/ │ │ ├── config/ │ │ │ ├── preview.json │ │ │ └── schema.ts │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ └── main.ts │ ├── 根据JSON生成API请求方法/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 根据JSON生成MOCK方法/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 根据JSON生成TS类型/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 根据JSON生成TS类型-去除接口名称/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 根据JSON生成TS类型-将中文字段翻译成英文/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── viewPrompt.ejs │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 根据TS类型生成API请求方法/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 根据TS类型生成MOCK方法/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 根据TS类型生成markdown表格/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 根据YAPI接口定义生成高级Mock脚本/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── src/ │ │ └── template.ejs │ ├── 测试 JSONSchemaChat/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── validSchema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 测试脚本/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 生成 value-label 格式 JSON/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ ├── schema.json │ │ │ └── schema.ts │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 翻译成驼峰格式/ │ │ ├── config/ │ │ │ ├── commandPrompt.ejs │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ └── main.ts │ │ └── src/ │ │ └── template.ejs │ ├── 自动保存活动窗口/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ └── main.ts │ ├── 获取当前用户最近一次 Git Commit 信息/ │ │ ├── config/ │ │ │ └── preview.json │ │ └── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ └── main.ts │ ├── 设置配置信息/ │ │ ├── config/ │ │ │ ├── model.json │ │ │ ├── preview.json │ │ │ └── schema.json │ │ ├── script/ │ │ │ ├── index.js │ │ │ └── src/ │ │ │ ├── context.ts │ │ │ ├── controller.ts │ │ │ ├── main.ts │ │ │ └── routes.ts │ │ └── src/ │ │ └── template.ejs │ └── 通过 TS 类型做字段映射/ │ ├── config/ │ │ ├── model.json │ │ ├── preview.json │ │ └── schema.json │ ├── script/ │ │ ├── index.js │ │ └── src/ │ │ ├── context.ts │ │ ├── controller.ts │ │ ├── main.ts │ │ └── routes.ts │ └── src/ │ └── template.ejs ├── package.json ├── scripts/ │ └── ClipboardImage/ │ ├── linux.sh │ ├── mac.applescript │ └── pc.ps1 ├── share/ │ ├── BaiduOCR/ │ │ ├── index.ts │ │ └── request.ts │ ├── JSONSchemaChat/ │ │ ├── index.ts │ │ └── result.ts │ ├── LLM/ │ │ ├── gemini.ts │ │ ├── geminiProxy.ts │ │ ├── index.ts │ │ ├── openai.ts │ │ └── openaiV2.ts │ ├── TypeChatSlim/ │ │ ├── index.ts │ │ ├── result.ts │ │ └── utools.ts │ ├── WebView/ │ │ ├── callback.ts │ │ ├── controllers/ │ │ │ ├── alert.ts │ │ │ ├── dynamicForm.ts │ │ │ ├── llm.ts │ │ │ ├── script.ts │ │ │ └── task.ts │ │ ├── index.ts │ │ ├── routes/ │ │ │ └── index.ts │ │ └── type.ts │ ├── clearCache.ts │ ├── uTools/ │ │ └── webviewBaseController.ts │ └── utils/ │ ├── clipboardImage.ts │ ├── config.ts │ ├── dynamicForm.ts │ ├── editor.ts │ ├── ejs.ts │ ├── emitter.ts │ ├── file.ts │ ├── json.ts │ ├── lint.ts │ ├── material.ts │ ├── platformIndependent/ │ │ └── json.ts │ ├── shareData.ts │ ├── tsx.ts │ └── uTools.ts ├── tsconfig.compiler.json ├── tsconfig.json ├── uTools/ │ ├── Ask ChatGPT/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask ChatGPT-生成 value-label 格式 JSON/ │ │ ├── config/ │ │ │ ├── schema.ts │ │ │ └── template.ejs │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask ChatGPT-生成 value-label 格式 JSON - Form/ │ │ ├── config/ │ │ │ ├── schema.ts │ │ │ └── template.ejs │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask ChatGPT-生成 value-label 格式 JSON - Prompt/ │ │ ├── config/ │ │ │ └── schema.ts │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask Gemini/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask Groq/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask Kimi/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Ask Perplexity/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Chat With Form Demo/ │ │ ├── config/ │ │ │ ├── config.json │ │ │ ├── model.json │ │ │ └── schema.json │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ ├── controller.ts │ │ └── main.ts │ ├── Git 获取当前用户最近一次 Commit 信息/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Open ChatGPT/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Open ChatGPT-获取命令行命令/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── Open Tldraw/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── TS 类型新增字段 - 根据 YAPI 文档字段格式/ │ │ ├── prompt.md │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── TS 类型新增字段 - 根据 YAPI 文档字段格式 - 截图/ │ │ ├── README.md │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── vscode 选中的文件夹/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 中文翻译英文/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 动态表单 demo/ │ │ ├── config/ │ │ │ ├── config.json │ │ │ ├── model.json │ │ │ ├── schema.json │ │ │ └── schema.ts │ │ ├── script/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── controller.ts │ │ │ └── main.ts │ │ └── src/ │ │ ├── api.ts.ejs │ │ ├── index.vue.ejs │ │ ├── model.ts.ejs │ │ ├── presenter.ts.ejs │ │ ├── service.ts.ejs │ │ ├── temp.mock.script │ │ └── temp.mock.type.ejs │ ├── 截屏并转base64/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 根据 DevOps 需求标题创建 GIT commit - 截图/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 根据 DevOps 需求标题创建 GIT 分支名 - 截图/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/ │ │ ├── config/ │ │ │ └── schema.ts │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 根据当前分支名称创建 GIT commit/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 翻译为驼峰格式-首字母大写/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 翻译为驼峰格式-首字母小写/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 英文翻译中文/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 英文:中文格式的描述转 TS 类型/ │ │ ├── README.md │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ ├── 获取命令行命令/ │ │ └── script/ │ │ ├── index.ts │ │ └── src/ │ │ └── main.ts │ └── 设置配置信息/ │ └── script/ │ ├── index.ts │ └── src/ │ └── main.ts ├── uTools.js └── uToolsUpload.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintignore ================================================ dist gemini.js geminiProxy.js ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, }, extends: [ 'airbnb-base', 'prettier', 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], parser: '@typescript-eslint/parser', plugins: ['prettier', '@typescript-eslint'], parserOptions: { ecmaVersion: 12, }, rules: { 'prettier/prettier': 'error', 'func-names': 'off', 'import/prefer-default-export': 'off', 'no-plusplus': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-unused-vars': 'off', 'max-classes-per-file': 'off', '@typescript-eslint/no-explicit-any': 'off', 'class-methods-use-this': 'off', '@typescript-eslint/ban-ts-comment': 'off', 'import/no-unresolved': 'off', 'no-useless-constructor': 'off', 'no-empty-function': 'off', 'import/extensions': 'off', 'consistent-return': 'off', 'no-use-before-define': 'off', 'array-callback-return': 'off', 'no-param-reassign': 'off', 'no-new-func': 'off', 'no-empty': 'off', 'import/no-dynamic-require': 'off', 'global-require': 'off', 'import/no-extraneous-dependencies': 'off', 'no-template-curly-in-string': 'off', 'arrow-body-style': 'off', 'prefer-promise-reject-errors': 'off', }, }; ================================================ FILE: .gitattributes ================================================ *.ejs linguist-language=TypeScript ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ .ts-node .lowcoderc dist ================================================ FILE: .npmrc ================================================ electron_mirror=https://npmmirror.com/mirrors/electron/ ================================================ FILE: .prettierignore ================================================ .ejs dist ================================================ FILE: .prettierrc.js ================================================ module.exports = { trailingComma: 'all', tabWidth: 2, semi: true, singleQuote: true, endOfLine: 'auto', }; ================================================ FILE: README.md ================================================ 将项目 clone 到本地,安装依赖,执行 yarn build ## 使用 VSCODE 安装 [lowcode](https://marketplace.visualstudio.com/items?itemName=wjkang.lowcode) 插件 设置同步目录为 clone 项目的目录 ![](https://github.com/user-attachments/assets/979aefca-445b-4805-98e8-5224f9a06067) ![](https://github.com/user-attachments/assets/01f4684d-3cc5-496d-adce-91a345c8a4c8) vscode 执行如下命令 ![](https://github.com/user-attachments/assets/3563be5f-322d-429a-b3d7-44be3d380fbe) 出现如下选项,配置成功 ![](https://github.com/user-attachments/assets/266b876a-bae9-409a-bbb9-f01dbd8c0637) 快速创建区块 ![](https://github.com/user-attachments/assets/cacc892a-0834-4899-a766-f6e090abe302) 以同步目录设置的代码模版和区块在所有项目里都可见,代码逻辑可以自由修改,不过分依赖 lowcode 插件内部,比如上面快速创建区块的代码: https://github.com/lowcode-scaffold/lowcode-materials/blob/master/materials/snippets/%E5%BF%AB%E9%80%9F%E5%88%9B%E5%BB%BA%E5%8C%BA%E5%9D%97/script/src/main.ts ### 支持其它 LLM 只要实现了 `llm/index.js` 中的 `createChatCompletion` 的方法,lowcode 插件内部的 ChatGPT 请求将会转为使用这个方法。不存在这个文件或者没有 `createChatCompletion` 方法会继续使用内部 ChatGPT 请求。https://github.com/lowcode-scaffold/lowcode-materials/blob/master/llm/index.js 如果使用的 LLM 兼容 openai 的数据格式,直接通过可视化界面进行配置 ![image](https://github.com/user-attachments/assets/c115b70c-68f8-4479-96f4-495d4d0a1275) ![image](https://github.com/user-attachments/assets/cfa2b61b-e462-40a5-aadd-710adec15b8a) ![image](https://github.com/user-attachments/assets/5eb13ef1-150b-450c-9b53-018cbbdf59a1) ## 使用 uTools ### uTools 自动化脚本 安装依赖后执行 yarn build,uTools 中安装自动化脚本插件,新建脚本,把 dist/utools 目录下的各个 index.js 内容复制过去就行。 ![image](https://github.com/user-attachments/assets/ee3cd944-850b-478b-b1e7-03fa8a79d3a2) ![image](https://github.com/user-attachments/assets/3b307be1-70ab-4524-9a2d-9b19419965a4) 部分脚本需要配合插件使用,插件下载:[lowcode-1.0.0.upxs](https://github.com/lowcode-scaffold/lowcode-materials/releases) 一个生成特定代码的例子: ![](https://github.com/user-attachments/assets/ebd19d40-1f92-49d3-ab13-aa7272ff04f9) ![](https://github.com/user-attachments/assets/b0a4e635-08cf-4e09-8469-6d73c76c17d3) ================================================ FILE: buildMaterials.js ================================================ const path = require('path'); const fs = require('fs-extra'); /** * @description * @param {string} dirPath * @return {string[]} */ function getAllFiles(dirPath) { const files = fs.readdirSync(dirPath); let result = []; // eslint-disable-next-line no-restricted-syntax for (const file of files) { const filePath = `${dirPath}/${file}`; // eslint-disable-next-line no-await-in-loop const stats = fs.statSync(filePath); if (stats.isDirectory()) { result = result.concat(getAllFiles(filePath)); } else { result.push(filePath); } } return result; } const devContent = ` require('ts-node').register({ transpileOnly: true, typeCheck: false, emit: false, compilerHost: false, // 和 emit 一起设置为 true,会在 .ts-node 文件夹输出编译后的代码 cwd: __dirname, // 要输出编译后代码必须配置,否则会报错 EROFS: read-only file system, mkdir '/.ts-node'。不输出也要配置不然会出现各种奇奇怪怪的报错 }); // 清除缓存,保证每次修改代码后实时生效,否则要重新打开 vscode const { clearCache } = require('../../../../share/clearCache.ts'); clearCache(__dirname); // 调试的时候才打开,不然会很慢 const main = require('./src/main.ts'); const { context } = require('./src/context.ts'); `; const mode = process.argv[2] || 'prod'; fs.removeSync(path.join(__dirname, 'dist')); getAllFiles(path.join(__dirname, 'materials')) .filter((s) => s.includes('script/index.js') && !s.includes('.ejs')) .forEach((file) => { const materialName = file .replace(/\\/g, '/') .replace(`${__dirname.replace(/\\/g, '/')}/materials/`, '') .replace('/script/index.js', ''); const prodContent = ` const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/${materialName}/script/src/main'); const { context, } = require('../../../../dist/materials/${materialName}/script/src/context'); `; const content = fs.readFileSync(file).toString(); const exportContent = `module.exports${content.split('module.exports')[1]}`; if (mode === 'prod') { fs.writeFileSync(file, prodContent.trimStart() + exportContent); } else { fs.writeFileSync(file, devContent.trimStart() + exportContent); } }); ================================================ FILE: llm/index.js ================================================ const vscode = require('vscode'); const gemini = require('../dist/share/LLM/gemini'); const geminiProxy = require('../dist/share/LLM/geminiProxy'); const openai = require('../dist/share/LLM/openai'); const share = require('../dist/share/utils/shareData'); const GeminiKey = 'lowcode.GeminiKey'; const OpenaiKey = 'lowcode.OpenaiKey'; module.exports = { /** * @description 替换 lowcode 插件内部的 ChatGPT 请求,修改后需要重启对应项目 vscode * @param {({ * messages: { role: 'system' | 'user' | 'assistant'; content: string }[]; * handleChunk?: (data: { text?: string; }) => void; * lowcodeContext: { env: { extensionContext: any }}; * })} options * @returns {Promise} */ createChatCompletion: async (options) => { const context = options.lowcodeContext.env.extensionContext; // await context.secrets.delete(GeminiKey); // 需要更新 API KEY 的时候打开 // let apiKey = await context.secrets.get(GeminiKey); // if (!apiKey) { // vscode.window.showWarningMessage( // 'Enter your API KEY to save it securely.', // ); // apiKey = await setApiKey(context, GeminiKey); // if (!apiKey) { // if (options.handleChunk) { // options.handleChunk({ text: 'Please enter your api key' }); // } // return 'Please enter your api key'; // } // } // const res = await gemini.createChatCompletion({ // messages: options.messages, // model: 'gemini-pro', // apiKey, // handleChunk(data) { // if (options.handleChunk) { // options.handleChunk(data); // } // }, // proxyUrl: 'http://127.0.0.1:7890', // }); // return res; // const res = await geminiProxy.createChatCompletion({ // messages: options.messages, // model: 'gemini-pro', // maxTokens: '4096', // handleChunk(data) { // if (options.handleChunk) { // options.handleChunk(data); // } // }, // }); // return res; // await context.secrets.delete(OpenaiKey); // 需要更新 API KEY 的时候打开 // let apiKey = await context.secrets.get(OpenaiKey); // if (!apiKey) { // vscode.window.showWarningMessage( // 'Enter your API KEY to save it securely.', // ); // apiKey = await setApiKey(context, OpenaiKey); // if (!apiKey) { // if (options.handleChunk) { // options.handleChunk({ text: 'Please enter your api key' }); // } // return 'Please enter your api key'; // } // } const config = share.oneAPIConfig() || {}; const res = await openai.createChatCompletion({ messages: options.messages, hostname: config.hostname, model: config.model, apiKey: config.apiKey, notHttps: config.notHttps, apiPath: config.apiPath, port: config.port, handleChunk(data) { if (options.handleChunk) { options.handleChunk(data); } }, }); return res; }, }; ================================================ FILE: lowcode-context.d.ts ================================================ import type vscode from 'vscode'; interface Context { /** * @description 模版数据 * @type {object} */ model: object; /** * @description vscode 对象,能调用 vscode 提供的 api * @type {typeof vscode} */ vscode: typeof vscode; /** * @description 调用脚本的工作目录,不一定是脚本所在的项目目录 * @type {string} */ workspaceRootPath: string; /** * @description 区块生成目录 * @type {string} */ createBlockPath?: string; /** * @description OutputChannel * @type {vscode.OutputChannel} */ outputChannel: vscode.OutputChannel; /** * @description log * @type {vscode.OutputChannel} */ log: vscode.OutputChannel; /** * @description 调用 ChatGPT */ createChatCompletion: (options: { messages: { role: 'system' | 'user' | 'assistant'; content: string; }[]; handleChunk?: ((data: { text?: string }) => void) | undefined; showWebview?: boolean; }) => Promise; /** * @description 当前选择的物料路径(加上物料名称) * @type {string} */ materialPath: string; /** * @description 一些环境变量 */ env: { /** * @description 等于 workspaceRootPath * @type {string} */ rootPath: string; /** * @description 临时工作目录 * @type {string} */ tempWorkPath: string; /** * @description 物料路径 * @type {string} */ materialsPath: string; /** * @description 区块路径 * @type {string} */ blockMaterialsPath: string; /** * @description 代码片段路径 * @type {string} */ snippetMaterialsPath: string; /** * @description 私有物料路径 * @type {string} */ privateMaterialsPath: string; /** * @description ExtensionContext * @type {vscode.ExtensionContext} */ extensionContext: vscode.ExtensionContext; }; /** * @description lwocode 插件内部使用的一些库,暴露出来避免重复安装 */ libs: { /** * @description axios * @type {*} */ axios: any; /** * @description copy-paste * @type {*} */ copyPaste: any; /** * @description directory-tree * @type {*} */ dirTree: any; /** * @description ejs * @type {*} */ ejs: any; /** * @description fs-extra * @type {*} */ fsExtra: any; /** * @description execa * @type {*} */ execa: any; /** * @description glob * @type {*} */ glob: any; /** * @description prettier * @type {*} */ prettier: any; /** * @description strip-comments * @type {*} */ stripComments: any; /** * @description strip-json-comments * @type {*} */ stripJsonComments: any; /** * @description generate-schema * @type {*} */ generateSchema: any; /** * @description json-schema-to-typescript * @type {*} */ jsonSchemaToTypescript: any; /** * @description typescript-json-schema * @type {*} */ typescriptJsonSchema: any; /** * @description axios * @type {*} */ tar: any; }; /** * 剪贴板的图片,执行脚本弹框里点击确定按钮的时候获取的,不通过 webview 获取不到 */ clipboardImage?: string; /** * 打开 webview 获取剪贴板里的图片,base64 格式 */ getClipboardImage: () => Promise; /** * @description 最后一次激活的 TextEditor */ activeTextEditor?: vscode.TextEditor; } export interface CompileContext extends Context { /** * @description 代码片段编译后的代码 * @type {string} */ code: string; /** * @description 执行右键菜单时选中的文件夹 * @type {string} */ explorerSelectedPath: string; /** 脚本方法名,runScript 方法才有 */ method: string; /** 脚本方法名,runScript 方法才有 */ script: string; /** 脚本方法参数,runScript 方法才有 */ params: string; } export interface ViewCallContext extends Context { /** * @description 传入的方法参数 * @type {string} */ params: string; } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/config/model.json ================================================ { "filters": [], "columns": [], "pagination": { "show": true, "page": "page", "size": "size", "total": "result.total" }, "includeModifyModal": false, "fetchName": "fetchTableList", "result": "[\"result\"][\"records\"]", "serviceName": "getTableList" } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/config/preview.json ================================================ { "title": "", "description": "", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [], "schema": "amis", "scripts": [ { "method": "OCR", "remark": "OCR 识别剪贴版截图" }, { "method": "initFiltersFromImage", "remark": "使用截图初始化查询条件" }, { "method": "initFiltersFromText", "remark": "使用文本初始化查询条件" }, { "method": "initColumnsFromText", "remark": "使用文本初始化表格" }, { "method": "initColumnsFromImage", "remark": "使用截图初始化表格" }, { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的指定中文字段" } ] } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "combo", "label": "查询条件", "name": "filters", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "" }, { "type": "select", "name": "component", "placeholder": "选项", "options": [ { "label": "input", "value": "input" }, { "label": "select", "value": "select" }, { "label": "range-picker", "value": "range-picker" } ], "id": "u:995915eabcca", "multiple": false, "label": "组件", "value": "" }, { "type": "switch", "label": "后端接口查询", "option": "", "name": "remoteFetch", "falseValue": false, "trueValue": true, "id": "u:3a46d16f89c9", "value": false, "visibleOn": "${filters[index].component==='select'}" }, { "type": "input-text", "label": "placeholder", "name": "placeholder", "id": "u:d7f1a8a39449", "description": "", "visibleOn": "${filters[index].component==='select'||filters[index].component==='input'}" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false }, { "type": "combo", "label": "表格", "name": "columns", "multiple": true, "addable": true, "removable": true, "removableMode": "button", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:1e8070edc3d3" }, "items": [ { "type": "input-text", "name": "title", "id": "u:152dd82b82f9", "label": "title" }, { "type": "input-text", "label": "dataIndex", "name": "dataIndex", "id": "u:ecc7298e0550", "description": "" }, { "type": "input-text", "label": "key", "name": "key", "id": "u:fbaa95c3f15d", "description": "" }, { "type": "input-text", "label": "width", "name": "width", "id": "u:b143127e097b", "description": "" }, { "type": "switch", "label": "自定义插槽", "option": "", "name": "slot", "falseValue": false, "trueValue": true, "id": "u:ee1ce1faee0b", "value": false } ], "id": "u:9b9fb0cf38f9", "strictMode": true, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "deleteBtn": { "label": "删除", "level": "default" }, "tabsLabelTpl": "列${index+1}" }, { "type": "fieldset", "title": "分页参数", "collapsable": true, "body": [ { "type": "switch", "label": "是否分页", "option": "", "name": "pagination.show", "falseValue": false, "trueValue": true, "id": "u:6c70041d5143", "value": true, "className": "" }, { "type": "input-text", "label": "查询接口页数参数字段名", "name": "pagination.page", "id": "u:cbbf6853cf64", "value": "page" }, { "type": "input-text", "label": "查询接口每页数据行数参数字段名", "name": "pagination.size", "id": "u:a8fae66fa927", "value": "size" }, { "type": "input-text", "label": "接口返回总数据量字段 PATH", "name": "pagination.total", "id": "u:e1cd979c7ee8", "value": "result.total", "themeCss": { "inputControlClassName": { "padding-and-margin:default": { "marginBottom": "", "marginTop": "", "marginRight": "", "marginLeft": "" } } } } ], "id": "u:0f1bd8fc2f2b", "collapsed": true, "headingClassName": "", "bodyClassName": "p" }, { "type": "fieldset", "title": "请求方法", "collapsable": true, "body": [ { "type": "input-text", "label": "请求名称", "name": "fetchName", "id": "u:a3e712484fae", "value": "fetchTableList", "description": "追加了YAPI数据则不使用此参数", "themeCss": { "labelClassName": { "padding-and-margin:default": { "marginTop": "", "marginRight": "", "marginBottom": "", "marginLeft": "" } } }, "labelClassName": "labelClassName-a3e712484fae" }, { "type": "input-text", "label": "接口数据字段 PATH", "name": "result", "id": "u:8c082acf7db2", "value": "[\"result\"][\"records\"]", "description": "" }, { "type": "input-text", "label": "service方法名", "name": "serviceName", "id": "u:cfbbdd07366b", "value": "getTableList", "description": "" } ], "id": "u:382f8cdf59a6", "collapsed": true, "className": "", "headingClassName": "", "bodyClassName": "p-r p-l p-b" }, { "type": "fieldset", "title": "新增/编辑弹框", "collapsable": true, "body": [ { "type": "switch", "label": "是否包含弹框", "option": "", "name": "includeModifyModal", "falseValue": false, "trueValue": true, "id": "u:03957070af9e", "value": false }, { "type": "combo", "label": "表单项", "name": "modifyModal.formItems", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "strictMode": false, "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:86cc27b6a663" }, "items": [ { "type": "input-text", "name": "key", "id": "u:62cc1cf36c73", "label": "字段名(key)" }, { "type": "select", "name": "type", "options": [ { "label": "string", "value": "string" }, { "label": "number", "value": "number" }, { "label": "boolean", "value": "boolean" }, { "label": "Dayjs", "value": "Dayjs" }, { "label": "string[]", "value": "string[]" }, { "label": "number[]", "value": "number[]" }, { "label": "boolean[]", "value": "boolean[]" }, { "label": "[Dayjs,Dayjs]", "value": "[Dayjs,Dayjs]" } ], "id": "u:b165c75e5e1a", "multiple": false, "label": "字段类型", "value": "" }, { "type": "switch", "label": "字段可选", "option": "", "name": "optional", "falseValue": false, "trueValue": true, "id": "u:68fc4c85fb03", "value": false, "description": "字段名字后加?" }, { "type": "select", "name": "defaultValue", "options": [ { "label": "\"\"", "value": "\"\"" }, { "label": "false", "value": "false" }, { "label": "true", "value": "true" }, { "label": "0", "value": "0" }, { "label": "undefined", "value": "undefined" }, { "label": "[]", "value": "[]" } ], "id": "u:379ea92fb3c6", "multiple": false, "label": "默认值", "value": "" }, { "type": "select", "name": "component", "options": [ { "label": "input", "value": "input" }, { "label": "input-password", "value": "input-password" }, { "label": "input-number", "value": "input-number" }, { "label": "textarea", "value": "textarea" }, { "label": "select", "value": "select" }, { "label": "radio-group", "value": "radio-group" }, { "label": "checkbox-group", "value": "checkbox-group" }, { "label": "switch", "value": "switch" }, { "label": "date-picker", "value": "date-picker" }, { "label": "time-ticker", "value": "time-picker" }, { "label": "range-picker", "value": "range-picker" }, { "label": "transfer", "value": "transfer" } ], "id": "u:7932ea3b05da", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "name": "label", "id": "u:5bb237f20098", "label": "label" }, { "type": "input-text", "name": "placeholder", "id": "u:580898257491", "label": "placeholder" }, { "type": "switch", "label": "required", "option": "", "name": "required", "falseValue": false, "trueValue": true, "id": "u:559dbdbb01da", "value": false, "description": "验证规则加required" }, { "type": "input-text", "name": "message", "id": "u:55013279d659", "label": "校验失败 message", "value": "不能为空" }, { "type": "switch", "label": "更多组件配置", "option": "", "name": "showMore", "falseValue": false, "trueValue": true, "id": "u:67e0cb5b7496", "value": false, "description": "" }, { "type": "switch", "label": "labelInValue", "option": "", "name": "labelInValue", "falseValue": false, "trueValue": true, "id": "u:7fd6f1b233d9", "value": false, "description": "是否把每个选项的 label 包装到 value 中", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "select", "name": "mode", "options": [ { "label": "multiple", "value": "multiple" }, { "label": "tags", "value": "tags" } ], "multiple": false, "label": "mode", "value": "", "description": "设置 Select 的模式为多选或标签", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "optionFilterProp", "label": "optionFilterProp", "description": "搜索时过滤对应的 option 属性", "value": "label", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "showSearch", "option": "", "name": "showSearch", "falseValue": false, "trueValue": true, "value": false, "description": "使单选模式可搜索", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "hideArrow", "option": "", "name": "hideArrow", "falseValue": false, "trueValue": true, "value": false, "description": "是否隐藏下拉小箭头", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "maxlength", "label": "maxlength", "description": "最大长度", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "switch", "label": "showCount", "option": "", "name": "showCount", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示字数", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "input-text", "name": "max", "label": "max", "description": "最大值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "min", "label": "min", "description": "最小值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "step", "label": "step", "description": "每次改变步数,可以为小数", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "checkedChildren", "label": "checkedChildren", "description": "选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedChildren", "label": "unCheckedChildren", "description": "非选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "checkedValue", "label": "checkedValue", "description": "选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedValue", "label": "unCheckedValue", "description": "非选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "select", "name": "picker", "options": [ { "label": "date", "value": "date" }, { "label": "week", "value": "week" }, { "label": "month", "value": "month" }, { "label": "quarter", "value": "quarter" }, { "label": "year", "value": "year" } ], "multiple": false, "label": "picker", "description": "设置选择器类型", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker')}" }, { "type": "switch", "label": "showTime", "name": "showTime", "falseValue": false, "trueValue": true, "value": false, "description": "增加时间选择功能", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showNow", "name": "showNow", "falseValue": false, "trueValue": true, "value": false, "description": "当设定了 showTime 的时候,面板是否显示“此刻”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showToday", "name": "showToday", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示“今天”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" } ], "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false, "hiddenOn": "${!includeModifyModal}" } ], "bodyClassName": "p", "collapsed": true } ], "submitText": "" } ], "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false } }, "conditionFiles": { "includeModifyModal": { "value": false, "exclude": [ "/ModifyModal/index.vue.ejs", "/ModifyModal/model.ts.ejs", "/ModifyModal/presenter.tsx.ejs", "/ModifyModal/presenter.ts.ejs", "/ModifyModal/service.ts.ejs" ] } }, "excludeCompile": [ "temp.mock.script.ejs" ] } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/config/schema.ts ================================================ export type PageConfig = { filters: { component: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ label: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ placeholder: string; }[]; columns: { slot: boolean; /** * @description 保持原始内容,不要翻译 * @type {string} */ title: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ dataIndex: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; }[]; pagination: { show: boolean; page: string; size: string; total: string; }; includeModifyModal: boolean; fetchName: string; result: string; serviceName: string; }; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/antdv2 增删改查列表页/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/antdv2 增删改查列表页/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => { lowcodeContext.outputChannel.appendLine(__dirname); lowcodeContext.outputChannel.appendLine(__filename); lowcodeContext.outputChannel.appendLine(process.cwd()); lowcodeContext.outputChannel.appendLine( JSON.stringify(lowcodeContext.model), ); if (!lowcodeContext.model.includeModifyModal) { // lowcodeContext.libs.fsExtra.removeSync( // path.join( // path.join(lowcodeContext.env.tempWorkPath, 'src', 'ModifyModal'), // ), // ); } }, complete: (lowcodeContext) => { context.lowcodeContext = lowcodeContext; try { main.handleComplete(); } catch (ex) {} }, initFiltersFromImage: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleInitFiltersFromImage(); return res; }, initFiltersFromText: (lowcodeContext) => { let filters = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); filters = filters.map((item) => { const s = item.replace(/:|:/g, ':').split(':'); return { component: (s[1] || '').indexOf('选择') > -1 ? 'select' : 'input', key: s[0].trim(), label: s[0].trim(), placeholder: s[1] || '', }; }); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...lowcodeContext.model, filters }, }; }, initColumnsFromImage: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleInitColumnsFromImage(); return res; }, initColumnsFromText: (lowcodeContext) => { let columns = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); columns = columns.map((s) => ({ slot: false, title: s, dataIndex: s, key: s, })); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...lowcodeContext.model, columns }, }; }, askChatGPT: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleAskChatGPT(); return res; }, OCR: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleOCR(); return res; }, }; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/script/src/main.ts ================================================ import * as path from 'path'; import { window, workspace } from 'vscode'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { translate } from '@share/TypeChatSlim/index'; import { generalBasic } from '@share/BaiduOCR/index'; import { typescriptToMock } from '@share/utils/json'; import { context } from './context'; import { PageConfig } from '../../config/schema'; export async function handleOCR() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return { updateModelImmediately: false, onlyUpdateParams: true, params: '', model: lowcodeContext?.model, }; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); return { updateModelImmediately: false, onlyUpdateParams: true, params: ocrRes.words_result.map((s) => s.words).join('\r\n'), model: lowcodeContext?.model, }; } export async function handleInitFiltersFromImage() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return lowcodeContext?.model; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); const filters = ocrRes.words_result.map((s) => s.words); const formatedFilters = filters.map((item) => { const s = item.replace(/:|:/g, ':').split(':'); return { component: (s[1] || '').indexOf('选择') > -1 ? 'select' : 'input', key: s[0].replace(/:|:/g, '').trim(), label: s[0].replace(/:|:/g, '').trim(), placeholder: s[1], }; }); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...lowcodeContext.model, filters: formatedFilters }, }; } export async function handleInitColumnsFromImage() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return lowcodeContext?.model; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); const columns = ocrRes.words_result.map((s) => ({ slot: false, title: s.words, dataIndex: s.words, key: s.words, })); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...lowcodeContext.model, columns }, }; } export async function handleAskChatGPT() { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'PageConfig'; const res = await translate({ schema, typeName, request: JSON.stringify(lowcodeContext!.model as PageConfig), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify(lowcodeContext!.model as PageConfig)}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2)); if (res.success) { return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...res.data }, }; } return lowcodeContext!.model; } export async function handleComplete() { const { lowcodeContext } = context; const createBlockPath = context.lowcodeContext?.createBlockPath; if (createBlockPath) { // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(createBlockPath, 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync( path.join(createBlockPath, 'temp.mock.script.ejs').toString(), ) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.script.ejs')); // @ts-ignore if (!lowcodeContext?.model.includeModifyModal) { fs.removeSync(path.join(path.join(createBlockPath, 'ModifyModal'))); } const mockScript = ejs.render(mockTemplate, { ...lowcodeContext!.model, mockCode, mockData, createBlockPath: createBlockPath.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showInformationMessage( // '获取 mock 项目路径失败,跳过更新 mock 服务', // ); }); if (mockProjectPathRes?.data.result) { const projectName = workspace.rootPath ?.replace(/\\/g, '/') .split('/') .pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } // #endregion } } } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/ModifyModal/index.vue.ejs ================================================ ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/ModifyModal/model.ts.ejs ================================================ import { reactive, ref } from "vue"; <% if(modifyModal.formItems.some(s => (s.type || "").indexOf("Dayjs") > -1)) { _%> import type { Dayjs } from "dayjs"; <% } _%> export interface IFormData { id?:number; <% modifyModal.formItems.map(item => { _%> <%= item.key %><% if(item.optional){ _%>?<% } _%>: <%= item.type %>; <% }) _%> } const defaultFormData: IFormData = { <% modifyModal.formItems.map(item => { _%> <%= item.key %>: <%- item.defaultValue || '\"\"' %>, <% }) _%> }; <% if(modifyModal.formItems.some(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group")){ %> interface IOptionItem { label: string; value: string; } interface IOptions { <% modifyModal.formItems.filter(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group").map(item => { _%> <%= item.key %>: IOptionItem[]; <% }) _%> } const defaultOptions: IOptions = { <% modifyModal.formItems.filter(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group").map(item => { _%> <%= item.key %>: [], <% }) _%> }; <% } %> export const useModel = () => { const formData = reactive({ ...defaultFormData }); <% if(modifyModal.formItems.some(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group")){ _%> const options = reactive({ ...defaultOptions }); <% } _%> const loading = ref(false); return { formData, <% if(modifyModal.formItems.some(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group")){ _%> options, <% } _%> loading }; }; export type Model = ReturnType; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/ModifyModal/presenter.ts.ejs ================================================ import Service from "./service"; import { useModel } from "./model"; import { Form, message } from "ant-design-vue"; import { watch, reactive } from "vue"; import { Rule } from 'ant-design-vue/es/form/interface' interface IProps { title: string visible: boolean action: 'add' | 'edit' | 'view' id?: number } interface IEmit { (event: 'cancel'): void (event: 'ok'): void } const { useForm } = Form; export const usePresenter = (props: IProps, emit: IEmit) => { const model = useModel(); const service = new Service(model); const rules: Record = reactive({ <% modifyModal.formItems.map(item => { _%> <%= item.key %>: [{ required: <%= item.required || false %>, message: "<%= item.message %>" }], <% }) _%> }); const { resetFields, validate, validateInfos } = useForm( model.formData, rules, ); watch( () => props.visible, () => { if (props.visible && props.id) { service.getDetail(props.id as number); } }, ); const handleSubmit = () => { validate().then(() => { if (props.action === "add") { service.create().then(() => { message.success("新建成功"); resetFields(); emit("ok"); }); } else { service.edit().then(() => { message.success("提交成功"); resetFields(); emit("ok"); }); } }); }; const handleCancel = () => { resetFields(); emit("cancel"); }; return { model, service, handleSubmit, handleCancel, validateInfos, }; }; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/ModifyModal/service.ts.ejs ================================================ import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async getDetail(id: number) { // const res = await fetchDetail({ id }); // this.model.formData.id = id; <% modifyModal.formItems.map(item => { _%> // this.model.formData.<%= item.key %> = res.result<%= item.key %>; <% }) _%> } async create() { // this.model.loading.value = true; // await create({ <% modifyModal.formItems.map(item => { _%> // <%= item.key %>: this.model.formData.<%= item.key %>, <% }) _%> // }).finally(() => { // this.model.loading.value = false; // }); } async edit() { // this.model.loading.value = true; // await edit( // { id: this.model.formData.id! }, // { <% modifyModal.formItems.map(item => { _%> // <%= item.key %>: this.model.formData.<%= item.key %>, <% }) _%> // }, // ).finally(() => { // this.model.loading.value = false; // }); } } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/api.ts.ejs ================================================ import request from "@/utils/request"; <% if (locals.api) { %> // #region <%= api.title %> <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { _%> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.filter(query => query.name !== pagination.page && query.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.req_params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.query_path.params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}') < 0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType) { %> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } %> ) { return request<<%= typeName %>["result"]>({ url: `http://127.0.0.1:3000<%= api.query_path.path.replace(/\{/g,"${params.") %>`, method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } // #endregion <% } else { %> // #region export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params { <% filters.map(item => { _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>?: string; <% } else { _%> <%= item.key %>Start?: string; <%= item.key %>End?: string; <% } _%> <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } export function <%= fetchName %>( params: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params ) { return requestResult["result"]>({ url: `http://127.0.0.1:3000/<%= createBlockPath %>/<%= fetchName %>`, method: 'GET', params, }); } // #endregion <% } %> ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/index.vue.ejs ================================================ ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/model.ts.ejs ================================================ import { reactive, ref } from "vue"; <% if(locals.api){ %> import { I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Result } from "./api"; <% } else { %> import { I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result } from "./api"; <% } %> <% if(!locals.api){ %> interface ITableListItem { <% columns.map((item, index) => { _%> /** <%= item.title %> */ <%= item.key || `column${index+1}` %>: string; <% }) _%> /** * 接口返回的数据,新增字段不需要改 ITableListItem 直接从这里取 */ apiResult: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result<%- result %>[0] } <% } %> interface IFormData { <% filters.map(item => { _%> /** <%= item.label %> */ <% if(item.component === "range-picker") { _%> <%= item.key %>?: [string,string]; <% } _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>?: string; <% } _%> <% }) _%> } <% if(filters.some(s => s.component === "select" )){ %> interface IOptionItem { label: string; value: string; } interface IOptions { <% filters.filter(s => s.component === "select").map(item => { _%> <%= item.key %>: IOptionItem[], <% }) _%> } const defaultOptions: IOptions = { <% filters.filter(s => s.component === "select").map(item => { _%> <%= item.key %>: [], <% }) _%> }; <% } %> export const defaultFormData: IFormData = { <% filters.map(item => { _%> <%= item.key %>: undefined, <% }) _%> }; export const useModel = () => { const filterForm = reactive({ ...defaultFormData }); <% if(filters.some(s => s.component === "select" )){ %> const options = reactive({ ...defaultOptions }); <% } %> <% if(locals.api){ _%> const tableList = ref<(I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Result<%- result %>[0] & { _?: unknown })[]>( [], ); <% } else { _%> const tableList = ref<(ITableListItem & { _?: unknown })[]>( [], ); <% } _%> <% if(pagination.show) { %> const pagination = reactive<{ page: number; pageSize: number; total: number; }>({ page: 1, pageSize: 10, total: 0, }); <% } %> const loading = reactive<{ list: boolean }>({ list: false, }); <% if(includeModifyModal) { %> const modalInfo = reactive<{ visible: boolean; title: string; id?: number; action: "add" | "edit" | "view"; }>({ visible: false, title: "", action: "add", }); <% } %> return { filterForm, <% if(filters.some(s => s.component === "select" )){ _%> options, <% } _%> tableList, <% if(pagination.show) { _%> pagination, <% } _%> loading, <% if(includeModifyModal) { _%> modalInfo, <% } _%> }; }; export type Model = ReturnType; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/presenter.ts.ejs ================================================ import Service from "./service"; import { defaultFormData, useModel } from "./model"; import { createVNode, onMounted } from "vue"; import { message, Modal } from "ant-design-vue"; import { ExclamationCircleOutlined } from "@ant-design/icons-vue"; <% if(filters.some(item => item.component === "select" && item.remoteFetch)) { _%> import { useDebounceFn } from "@vueuse/core"; <% } _%> export const usePresenter = () => { const model = useModel(); const service = new Service(model); onMounted(() => { service.<%= serviceName %>(); }); const handleClear = () => { Object.assign(model.filterForm, defaultFormData) <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; const handleSearch = () => { <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; <% if(pagination.show) { _%> const handlePageChange = (page: number, pageSize: number) => { if (pageSize !== model.pagination.pageSize) { model.pagination.pageSize = pageSize; model.pagination.page = 1; } else { model.pagination.page = page; } service.<%= serviceName %>(); }; <% } _%> <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> const handleSearch<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %> = useDebounceFn((value: string) => { if (!value) { return; } service.search<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>(value); }, 400); <% }) _%> const handleDel = (record: typeof model.tableList.value[0]) => { Modal.confirm({ title: "此操作将删除该选项,是否继续?", icon: createVNode(ExclamationCircleOutlined), okText: "确定", cancelText: "取消", onOk() { message.success("删除成功"); }, }); }; const handleCreate = () => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "新建"; model.modalInfo.action = "add"; model.modalInfo.id = undefined; <% } _%> }; const handleEdit = (record: typeof model.tableList.value[0]) => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "编辑"; model.modalInfo.action = "edit"; model.modalInfo.id = record.id; <% } _%> }; <% if(includeModifyModal) { _%> const handleView = (record: typeof model.tableList.value[0]) => { model.modalInfo.visible = true; model.modalInfo.title = "查看"; model.modalInfo.action = "view"; model.modalInfo.id = record.id; }; const handleModalOk = () => { model.modalInfo.visible = false; service.<%= serviceName %>(); }; const handleModalCancel = () => { model.modalInfo.visible = false; }; <% } _%> return { model, service, handleClear, handleSearch, <% if(pagination.show) { _%> handlePageChange, <% } _%> <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> handleSearch<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>, <% }) _%> handleDel, handleCreate, handleEdit, <% if(includeModifyModal) { _%> handleView, handleModalOk, handleModalCancel <% } _%> }; }; ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/service.ts.ejs ================================================ import { <%= locals.api ? funcName : fetchName %> } from "./api"; import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async <%= serviceName %>() { this.model.loading.list = true; <% filters.map(item => { _%> <% if(item.component === "range-picker") { _%> let <%= item.key %>Start: string | undefined = undefined; let <%= item.key %>End: string | undefined = undefined; if ( this.model.filterForm.<%= item.key %> && this.model.filterForm.<%= item.key %>[0] ) { <%= item.key %>Start = `${this.model.filterForm.<%= item.key %>[0]} 00:00:00`; } if ( this.model.filterForm.<%= item.key %> && this.model.filterForm.<%= item.key %>[1] ) { <%= item.key %>End = `${this.model.filterForm.<%= item.key %>[1]} 23:59:59`; } <% } _%> <% }) _%> const res = await <%= locals.api ? funcName : fetchName %>({ <% filters.map(item => { _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>: this.model.filterForm.<%= item.key %>, <% } else { _%> <%= item.key %>Start: <%= item.key %>Start, <%= item.key %>End: <%= item.key %>End, <% } _%> <% }) _%> <% if(pagination.show) { _%> <%= pagination.page %>: this.model.pagination.page, <%= pagination.size %>: this.model.pagination.pageSize, <% } _%> }).finally(() => { this.model.loading.list = false; }); this.model.tableList.value = res<%- result %>.map((s) => { return { ...s, <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: s.<%= item.key || `column${index+1}` %>, <% }) _%> apiResult: s }; }); <% if(pagination.show) { _%> this.model.pagination.total = res.<%- pagination.total %>; <% } _%> } <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> async search<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>(value: string) { const res = await Promise.resolve([{ label: "1", value: "1" }]); this.model.options.<%= item.key %> = res; } <% }) _%> } ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/temp.mock.script.ejs ================================================ .get(`<%= createBlockPath %>/<%= fetchName %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: materials/blocks/antdv2 增删改查列表页/src/temp.mock.type.ejs ================================================ { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } ================================================ FILE: materials/blocks/react-mvp 模块/config/model.json ================================================ {} ================================================ FILE: materials/blocks/react-mvp 模块/config/preview.json ================================================ { "title": "react-mvp 模块", "description": "react-mvp 模块", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [ "react", "mvp" ] } ================================================ FILE: materials/blocks/react-mvp 模块/config/schema.json ================================================ {} ================================================ FILE: materials/blocks/react-mvp 模块/src/index.tsx.ejs ================================================ import React from "react"; import usePresenter from "./presenter"; export default () => { const presenter = usePresenter(); const { model } = presenter; return
react mvp
; }; ================================================ FILE: materials/blocks/react-mvp 模块/src/model.ts.ejs ================================================ export const useModel = () => {}; export type Model = ReturnType; ================================================ FILE: materials/blocks/react-mvp 模块/src/presenter.tsx.ejs ================================================ import { useModel } from "./model"; import Service from "./service"; const usePresenter = () => { const model = useModel(); const service = new Service(model); return { model, service, }; }; export default usePresenter; ================================================ FILE: materials/blocks/react-mvp 模块/src/service.ts.ejs ================================================ import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/blocks/taro-request/config/model.json ================================================ {} ================================================ FILE: materials/blocks/taro-request/config/preview.json ================================================ { "title": "taro-request", "description": "taro-request通用封装", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [ "taro", "request" ] } ================================================ FILE: materials/blocks/taro-request/config/schema.json ================================================ {} ================================================ FILE: materials/blocks/taro-request/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/taro-request/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/taro-request/script/src/context'); module.exports = { beforeCompile: (context) => {}, afterCompile: (constext) => {}, }; ================================================ FILE: materials/blocks/taro-request/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/taro-request/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/blocks/taro-request/src/config.ts.ejs ================================================ // 请求连接前缀 export const baseUrl = process.env.NODE_ENV === "production" ? "https://xxx.com" : "http://localhost:8360"; // 输出日志信息 export const noConsole = false; ================================================ FILE: materials/blocks/taro-request/src/index.ts.ejs ================================================ import * as Taro from "@tarojs/taro"; import * as queryString from "query-string"; import { baseUrl } from "./config"; import interceptors from "./interceptors"; interceptors.forEach((interceptorItem) => Taro.addInterceptor(interceptorItem)); interface OptionsType { method: "GET" | "POST" | "PUT" | "DELETE"; params?: Object; data?: Object; noLoading?: boolean; } export const request = ( url: string, options: OptionsType = { method: "GET", params: {}, data: {}, noLoading: false, } ) => { if (!options.noLoading) { Taro.showLoading({ title: "加载中", }); } for (const key in options.data) { if ( options.data.hasOwnProperty(key) && (options.data[key] === undefined || options.data[key] == null) ) { delete options.data[key]; } } const urlWithParms = url.indexOf("?") > -1 ? `${url}&${queryString.stringify(options.params)}` : `${url}?${queryString.stringify(options.params)}`; return Taro.request({ url: urlWithParms.indexOf("http") === -1 ? `${baseUrl}${urlWithParms}` : urlWithParms, data: { ...options.data, }, header: { // "X-Token": Taro.getStorageSync("token"), "Content-Type": "application/json", }, method: options.method.toUpperCase() as any, }) .then((res) => { return res.data; }) .finally(() => { setTimeout(() => { Taro.hideLoading(); }, 100); }); }; ================================================ FILE: materials/blocks/taro-request/src/interceptors.ts.ejs ================================================ import * as Taro from "@tarojs/taro"; const HTTP_STATUS = { SUCCESS: 200, CREATED: 201, ACCEPTED: 202, CLIENT_ERROR: 400, AUTHENTICATE: 401, FORBIDDEN: 403, NOT_FOUND: 404, SERVER_ERROR: 500, BAD_GATEWAY: 502, SERVICE_UNAVAILABLE: 503, GATEWAY_TIMEOUT: 504, }; const rspInterceptor: Taro.interceptor = (chain) => { const { requestParams } = chain; return chain.proceed(requestParams).then((res) => { if (res.statusCode === HTTP_STATUS.BAD_GATEWAY) { return Promise.reject("服务端出现了问题"); } if (res.statusCode === HTTP_STATUS.FORBIDDEN) { return Promise.reject("没有权限访问"); } if (res.statusCode === HTTP_STATUS.AUTHENTICATE) { return Promise.reject("需要鉴权"); } if (res.statusCode === HTTP_STATUS.SUCCESS) { return res; } }); }; const interceptors = [rspInterceptor]; export default interceptors; ================================================ FILE: materials/blocks/vant 表单/config/model.json ================================================ { "formItems": [] } ================================================ FILE: materials/blocks/vant 表单/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "amis", "scripts": [ { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的指定中文字段" } ] } ================================================ FILE: materials/blocks/vant 表单/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "switch", "label": "defineProps", "option": "", "name": "defineProps", "falseValue": false, "trueValue": true, "id": "u:9e7d1ee83373", "value": false }, { "type": "switch", "label": "defineEmits", "option": "", "name": "defineEmits", "falseValue": false, "trueValue": true, "id": "u:4ef4be234efc", "value": false }, { "type": "combo", "label": "表单项", "name": "formItems", "multiple": true, "addable": true, "removable": true, "removableMode": "button", "tabsLabelTpl": "表单项${index+1}", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "select", "name": "type", "placeholder": "选项", "options": [ { "label": "string", "value": "string" }, { "label": "number", "value": "number" }, { "label": "boolean", "value": "boolean" }, { "label": "string[]", "value": "string[]" }, { "label": "number[]", "value": "number[]" }, { "label": "boolean[]", "value": "boolean[]" } ], "id": "u:5bb823bc0afb", "multiple": false, "label": "字段类型", "value": "" }, { "type": "switch", "label": "字段可选(字段名字后加?)", "option": "", "name": "optional", "falseValue": false, "trueValue": true, "id": "u:f22b76a31198", "value": false, "description": "" }, { "type": "select", "label": "默认值", "name": "defaultValue", "options": [ { "label": "\"\"", "value": "\"\"" }, { "label": "false", "value": "false" }, { "label": "true", "value": "true" }, { "label": "0", "value": "0" }, { "label": "undefined", "value": "undefined" }, { "label": "[]", "value": "[]" } ], "id": "u:d721f505457e", "multiple": false, "value": "", "creatable": true }, { "type": "flex", "items": [ { "type": "container", "body": [ { "type": "select", "name": "component", "placeholder": "选项", "options": [ { "label": "input(van-field)", "value": "input" }, { "label": "van-cell(常规表单组件布局无法满足时使用)", "value": "van-cell" }, { "label": "van-switch", "value": "van-switch" }, { "label": "van-checkbox", "value": "van-checkbox" }, { "label": "van-checkbox-group", "value": "van-checkbox-group" }, { "label": "van-radio-group", "value": "van-radio-group" }, { "label": "van-stepper", "value": "van-stepper" }, { "label": "van-uploader", "value": "van-uploader" }, { "label": "van-picker", "value": "van-picker" }, { "label": "van-datetime-picker", "value": "van-datetime-picker" } ], "id": "u:995915eabcca", "multiple": false, "label": "组件", "value": "" } ], "size": "xs", "style": { "position": "static", "display": "block", "flex": "1 1 auto", "flexGrow": 1, "flexBasis": "auto", "paddingLeft": "0px" }, "wrapperBody": false, "isFixedHeight": false, "isFixedWidth": false, "id": "u:63cbd01838cb" }, { "type": "container", "body": [ { "type": "button", "label": "预览图", "id": "u:421835b9eb42", "actionType": "dialog", "dialog": { "type": "dialog", "title": "", "body": [ { "type": "carousel", "auto": false, "thumbMode": "contain", "animation": "fade", "options": [ { "image": "https://black-pearl.oss-cn-shenzhen.aliyuncs.com/2023/08/14/df767ebd-1a56-40ee-bb47-f7acbda5a85f.png", "href": "https://black-pearl.oss-cn-shenzhen.aliyuncs.com/2023/08/14/df767ebd-1a56-40ee-bb47-f7acbda5a85f.png" }, { "image": "https://black-pearl.oss-cn-shenzhen.aliyuncs.com/2023/08/14/deb8c82a-7b03-415d-bfd0-372f8a5d9e31.png", "href": "https://black-pearl.oss-cn-shenzhen.aliyuncs.com/2023/08/14/deb8c82a-7b03-415d-bfd0-372f8a5d9e31.png" } ], "height": "300", "id": "u:679ce934b491", "interval": 5000, "duration": 500, "multiple": false, "alwaysShowArrow": false, "controls": "arrows", "controlsTheme": "dark" } ], "actions": [], "id": "u:92a20653a80f" }, "hiddenOn": "${formItems[index].component==='666'}" } ], "size": "xs", "style": { "position": "static", "display": "block", "flex": "0 0 auto" }, "wrapperBody": false, "isFixedHeight": false, "isFixedWidth": false, "id": "u:e1a77fc17ac2" } ], "style": { "position": "relative", "inset": "auto", "flexWrap": "nowrap", "alignItems": "flex-end", "marginBottom": "1.5rem", "paddingLeft": "0px", "paddingRight": "0px" }, "id": "u:e109a539ee8f", "isFixedHeight": false, "isFixedWidth": false }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "placeholder": "", "description": "输入框左侧文本" }, { "type": "input-text", "label": "placeholder", "name": "placeholder", "id": "u:d7f1a8a39449", "placeholder": "", "description": "输入框占位提示文字" }, { "type": "switch", "label": "required", "option": "", "name": "required", "falseValue": false, "trueValue": true, "id": "u:032dae2bdd71", "value": false, "description": "是否显示表单必填星号" }, { "type": "switch", "label": "Props (更多组件配置)", "option": "", "name": "showMore", "falseValue": false, "trueValue": true, "id": "u:bb465f530390", "value": false, "description": "以下组件 props 不修改默认值,对应 props 不会显式出现" }, { "type": "input-text", "label": "name", "name": "name", "id": "u:8f64b1631d4b", "description": "名称,作为提交表单时的标识符", "hiddenOn": "${!showMore}", "clearValueOnHidden": false, "static": false }, { "type": "select", "label": "type", "name": "prop-type", "options": [ { "label": "text", "value": "text" } ], "id": "u:2c327c726ef1", "multiple": false, "description": "输入框类型, 支持原生 input 标签的所有 type 属性,额外支持了 digit 类型,默认 text", "value": "", "creatable": true, "hiddenOn": "${!showMore}" }, { "type": "select", "label": "size", "name": "size", "options": [ { "label": "large", "value": "large" } ], "id": "u:8226dd30543a", "multiple": false, "description": "大小,可选值为 large", "value": "", "creatable": false, "hiddenOn": "${!showMore}" }, { "type": "input-text", "label": "maxlength", "name": "maxlength", "id": "u:07d46929d8bd", "description": "输入的最大字符数", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "border", "option": "", "name": "border", "falseValue": false, "trueValue": true, "id": "u:c8ffc4fb3136", "value": true, "description": "是否显示内边框", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "disabled", "option": "", "name": "disabled", "falseValue": false, "trueValue": true, "id": "u:8e7682ebbdf7", "value": false, "description": "是否禁用输入框", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "readonly", "option": "", "name": "readonly", "falseValue": false, "trueValue": true, "id": "u:51f1dc68ba31", "value": false, "description": "是否为只读状态,只读状态下无法输入内容", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "colon", "option": "", "name": "colon", "falseValue": false, "trueValue": true, "id": "u:d9d4478d4916", "value": false, "description": "是否在 label 后面添加冒号", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "center", "option": "", "name": "center", "falseValue": false, "trueValue": true, "id": "u:15bf10ee0f38", "value": false, "description": "是否使内容垂直居中", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "clearable", "option": "", "name": "clearable", "falseValue": false, "trueValue": true, "id": "u:6c6cb9913893", "value": false, "description": "是否启用清除图标,点击清除图标后会清空输入框", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "clickable", "option": "", "name": "clickable", "falseValue": false, "trueValue": true, "id": "u:a477d44b7d8b", "value": false, "description": "是否开启点击反馈", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "is-link", "option": "", "name": "is-link", "falseValue": false, "trueValue": true, "id": "u:7dec0bd94494", "value": false, "description": "是否展示右侧箭头并开启点击反馈", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "autofocus", "option": "", "name": "autofocus", "falseValue": false, "trueValue": true, "id": "u:e4ed85c736e6", "value": false, "description": "是否自动聚焦,iOS 系统不支持该属性", "hiddenOn": "${!showMore}" }, { "type": "switch", "label": "show-word-limit", "option": "", "name": "show-word-limit", "falseValue": false, "trueValue": true, "id": "u:09de98d0b14f", "value": false, "description": "是否显示字数统计,需要设置 maxlength 属性", "hiddenOn": "${!showMore}" }, { "type": "select", "label": "arrow-direction", "name": "arrow-direction", "options": [ { "label": "left", "value": "left" }, { "label": "right", "value": "right" }, { "label": "up", "value": "up" }, { "label": "down", "value": "down" } ], "id": "u:861c9d5d39f9", "value": "", "creatable": true, "description": "箭头方向,可选值为 left up down,默认 right", "hiddenOn": "${!showMore}" }, { "type": "select", "label": "label-align", "name": "label-align", "options": [ { "label": "left", "value": "left" }, { "label": "center", "value": "center" }, { "label": "right", "value": "right" } ], "id": "u:29bb77848154", "value": "", "creatable": true, "description": "左侧文本对齐方式,可选值为 center right,默认 left", "hiddenOn": "${!showMore}", "multiple": false }, { "type": "select", "label": "input-align", "name": "input-align", "options": [ { "label": "left", "value": "left" }, { "label": "center", "value": "center" }, { "label": "right", "value": "right" } ], "id": "u:5a64d2c0eb4d", "value": "", "creatable": true, "description": "输入框对齐方式,可选值为 center right", "hiddenOn": "${!showMore}", "multiple": false }, { "type": "switch", "label": "Slots", "option": "", "name": "slots", "falseValue": false, "trueValue": true, "id": "u:678ac90795de", "value": false, "hidden": false }, { "type": "switch", "label": "label slot", "option": "", "name": "label-slot", "falseValue": false, "trueValue": true, "id": "u:549ef5521c55", "value": false, "description": "自定义输入框左侧文本", "hiddenOn": "${!slots}" }, { "type": "switch", "label": "left-icon slot", "option": "", "name": "left-icon", "falseValue": false, "trueValue": true, "id": "u:02a1b49564b8", "value": false, "description": "自定义输入框头部图标", "hiddenOn": "${!slots}" }, { "type": "switch", "label": "right-icon slot", "option": "", "name": "right-icon", "falseValue": false, "trueValue": true, "id": "u:5ce280c3acfe", "value": false, "description": "自定义输入框尾部图标", "hiddenOn": "${!slots}" }, { "type": "switch", "label": "button slot", "option": "", "name": "button", "falseValue": false, "trueValue": true, "id": "u:90291d1eca81", "value": false, "description": "自定义输入框尾部按钮", "hiddenOn": "${!slots}" }, { "type": "switch", "label": "extra slot", "option": "", "name": "extra", "falseValue": false, "trueValue": true, "id": "u:db41c5de7416", "value": false, "description": "自定义输入框最右侧的额外内容", "hiddenOn": "${!slots}" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "deleteBtn": { "label": "删除", "level": "default" } } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "不需要编译的文件,不会被删除.ejs" ] } } ================================================ FILE: materials/blocks/vant 表单/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文,使用驼峰语法, 返回翻译后的markdown语法的代码块 ================================================ FILE: materials/blocks/vant 表单/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/vant 表单/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/vant 表单/script/src/context'); module.exports = { beforeCompile: (context) => {}, afterCompile: (context) => { context.outputChannel.appendLine(__dirname); context.outputChannel.appendLine(__filename); context.outputChannel.appendLine(process.cwd()); context.outputChannel.appendLine(JSON.stringify(context.model)); }, askChatGPT: async (context) => { const statusBarItem = context.vscode.window.createStatusBarItem( context.vscode.StatusBarAlignment.Left, ); statusBarItem.text = '$(sync~spin) Ask ChatGPT...'; statusBarItem.show(); const res = await context.createChatCompletion({ messages: [ { role: 'system', content: `你是一个严谨的代码机器人,严格按照输入的要求处理问题`, }, { role: 'user', content: `${JSON.stringify( context.model, )} 将这段 json formItems 字段中的 key 字段的值翻译为英文,使用驼峰语法,label、placeholder 保留中文。 返回翻译后的JSON,不要带其他无关的内容,并且返回的结果使用 JSON.parse 不会报错`, }, ], handleChunk: (data) => { // context.outputChannel.append(data.text || '') }, }); statusBarItem.hide(); statusBarItem.dispose(); context.outputChannel.appendLine(res); return { ...context.model, ...JSON.parse(res) }; }, }; ================================================ FILE: materials/blocks/vant 表单/src/index.vue.ejs ================================================ ================================================ FILE: materials/blocks/vant 表单/src/model.ts.ejs ================================================ import { reactive } from 'vue' export interface IFormData { <% formItems.map(item => { _%> <%= item.key %><% if(item.optional){ _%>?<% } _%>: <%= item.type %>; <% }) _%> } const defaultFormData: IFormData = { <% formItems.map(item => { _%> <%= item.key %>: <%- item.defaultValue || '\'\'' %>, <% }) _%> }; <% if(formItems.some(s => s.component === "van-picker" || s.component === "van-datetime-picker")){ %> interface IPicker { <% formItems.filter(s => s.component === "van-picker" || s.component === "van-datetime-picker").map(item => { _%> <% if(item.component === "van-picker"){ %> <%= item.key %>: { visible: boolean; columns: string[] } <% } _%> <% if(item.component === "van-datetime-picker"){ %> <%= item.key %>: { visible: boolean } <% } _%> <% }) _%> } <% } %> <% if(formItems.some(s => s.component === "van-checkbox-group" || s.component === "van-radio-group")){ %> interface IOptionItem { label: string; value: string; } interface IOptions { <% formItems.filter(s => s.component === "van-checkbox-group" || s.component === "van-radio-group").map(item => { _%> <%= item.key %>: IOptionItem[]; <% }) _%> } const defaultOptions: IOptions = { <% formItems.filter(s => s.component === "van-checkbox-group" || s.component === "van-radio-group").map(item => { _%> <%= item.key %>: [], <% }) _%> }; <% } %> export const useModel = () => { const formData = reactive({ ...defaultFormData }); <% if(formItems.some(s => s.component === "van-checkbox-group" || s.component === "van-radio-group")){ _%> const options = reactive({ ...defaultOptions }); <% } _%> <% if(formItems.some(s => s.component === "van-picker" || s.component === "van-datetime-picker")){ %> const picker = reactive({ <% formItems.filter(s => s.component === "van-picker" || s.component === "van-datetime-picker").map(item => { _%> <% if(item.component === "van-picker"){ %> <%= item.key %>: { visible: false, columns: [] }, <% } _%> <% if(item.component === "van-datetime-picker"){ %> <%= item.key %>: { visible: false } <% } _%> <% }) _%> }) <% } %> return { formData, <% if(formItems.some(s => s.component === "van-checkbox-group" || s.component === "van-radio-group")){ _%> options, <% } _%> <% if(formItems.some(s => s.component === "van-picker" || s.component === "van-datetime-picker")){ _%> picker <% } %> }; }; export type Model = ReturnType; ================================================ FILE: materials/blocks/vant 表单/src/presenter.tsx.ejs ================================================ import Service from './service' import { useModel } from './model' <% if(defineProps){ %> interface IProps { visible: boolean } <% } %> <% if(defineEmits){ %> interface IEmit { (event: 'update', id: number): void } <% } %> export const usePresenter = (<% if(defineProps){ %>props: IProps,<% } %> <% if(defineEmits){ %>emit: IEmit<% } %>) => { const model = useModel() const service = new Service(model) <% formItems.filter(s => s.component === "van-picker" || s.component === "van-datetime-picker").map(item => { _%> const handle<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>PickerOpen = () => { model.picker.<%= item.key %>.visible = true } const handle<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>PickerCancel = () => { model.picker.<%= item.key %>.visible = false } const handle<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>PickerConfirm = (value: string) => { model.picker.<%= item.key %>.visible = false model.formData.<%= item.key %> = value } <% }) _%> const handleSubmit = () => {} const handleCancel = () => {} return { model, service, handleSubmit, handleCancel, <% formItems.filter(s => s.component === "van-picker" || s.component === "van-datetime-picker").map(item => { _%> handle<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>PickerOpen, handle<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>PickerCancel, handle<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>PickerConfirm, <% }) _%> } } ================================================ FILE: materials/blocks/vant 表单/src/service.ts.ejs ================================================ import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async submit() { const data = { <% formItems.map(item => { _%> <%= item.key %>: this.model.formData.<%= item.key %>, <% }) _%> }; await Promise.resolve(data); } } ================================================ FILE: materials/blocks/vue-mvp 模块/config/model.json ================================================ {} ================================================ FILE: materials/blocks/vue-mvp 模块/config/preview.json ================================================ { "title": "vue-mvp 模块", "description": "vue-mvp 模块", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [ "vue", "vue3", "mvp" ] } ================================================ FILE: materials/blocks/vue-mvp 模块/config/schema.json ================================================ {} ================================================ FILE: materials/blocks/vue-mvp 模块/src/index.tsx.ejs ================================================ import { defineComponent } from "vue"; import { usePresenter } from "./presenter"; const Page = defineComponent({ setup() { const presenter = usePresenter(); const { model } = presenter; return { model, presenter }; }, render() { return
vue mvp
; }, }); export default Page; ================================================ FILE: materials/blocks/vue-mvp 模块/src/model.ts.ejs ================================================ import { reactive } from "vue"; export const useModel = () => { return {}; }; export type Model = ReturnType; ================================================ FILE: materials/blocks/vue-mvp 模块/src/presenter.ts.ejs ================================================ import Service from "./service"; import { useModel } from "./model"; export const usePresenter = () => { const model = useModel(); const service = new Service(model); return { model, service, }; }; ================================================ FILE: materials/blocks/vue-mvp 模块/src/service.ts.ejs ================================================ import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/blocks/vue2-mvp 模块/config/model.json ================================================ {} ================================================ FILE: materials/blocks/vue2-mvp 模块/config/preview.json ================================================ { "title": "vue2-mvp 模块", "description": "vue2-mvp 模块", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [ "vue", "vue2", "mvp" ] } ================================================ FILE: materials/blocks/vue2-mvp 模块/config/schema.json ================================================ {} ================================================ FILE: materials/blocks/vue2-mvp 模块/src/index.tsx.ejs ================================================ import { defineComponent } from "@vue/composition-api"; import { usePresenter } from "./presenter"; const Index = defineComponent({ setup() { const presenter = usePresenter(); const { model } = presenter; return { model, presenter }; }, render() { return
vue2 mvp
; } }); export default Index; ================================================ FILE: materials/blocks/vue2-mvp 模块/src/model.ts.ejs ================================================ import { reactive } from "@vue/composition-api"; export const useModel = () => { return {}; }; export type Model = ReturnType; ================================================ FILE: materials/blocks/vue2-mvp 模块/src/presenter.ts.ejs ================================================ import Service from "./service"; import { useModel } from "./model"; export const usePresenter = () => { const model = useModel(); const service = new Service(model); return { model }; }; ================================================ FILE: materials/blocks/vue2-mvp 模块/src/service.ts.ejs ================================================ import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/config/model.json ================================================ { "filters": [], "columns": [], "pagination": { "show": true, "page": "page", "size": "size", "total": "result.total" }, "includeModifyModal": false, "fetchName": "fetchTableList", "result": "[\"result\"][\"records\"]", "serviceName": "getTableList" } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/config/preview.json ================================================ { "title": "", "description": "", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [], "schema": "amis", "scripts": [ { "method": "initFiltersFromImage", "remark": "使用截图初始化查询条件" }, { "method": "initFiltersFromText", "remark": "使用文本初始化查询条件" }, { "method": "initColumnsFromText", "remark": "使用文本初始化表格" }, { "method": "initColumnsFromImage", "remark": "使用截图初始化表格" }, { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的指定中文字段" } ] } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "combo", "label": "查询条件", "name": "filters", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "" }, { "type": "select", "name": "component", "placeholder": "选项", "options": [ { "label": "input", "value": "input" }, { "label": "select", "value": "select" } ], "id": "u:995915eabcca", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "label": "placeholder", "name": "placeholder", "id": "u:d7f1a8a39449", "description": "" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false }, { "type": "combo", "label": "表格", "name": "columns", "multiple": true, "addable": true, "removable": true, "removableMode": "button", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:1e8070edc3d3" }, "items": [ { "type": "input-text", "name": "title", "id": "u:152dd82b82f9", "label": "title" }, { "type": "input-text", "label": "dataIndex", "name": "dataIndex", "id": "u:ecc7298e0550", "description": "" }, { "type": "input-text", "label": "key", "name": "key", "id": "u:fbaa95c3f15d", "description": "" }, { "type": "input-text", "label": "width", "name": "width", "id": "u:b143127e097b", "description": "" }, { "type": "switch", "label": "自定义插槽", "option": "", "name": "slot", "falseValue": false, "trueValue": true, "id": "u:ee1ce1faee0b", "value": false } ], "id": "u:9b9fb0cf38f9", "strictMode": true, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "deleteBtn": { "label": "删除", "level": "default" }, "tabsLabelTpl": "列${index+1}" }, { "type": "fieldset", "title": "分页参数", "collapsable": true, "body": [ { "type": "switch", "label": "是否分页", "option": "", "name": "pagination.show", "falseValue": false, "trueValue": true, "id": "u:6c70041d5143", "value": true, "className": "" }, { "type": "input-text", "label": "查询接口页数参数字段名", "name": "pagination.page", "id": "u:cbbf6853cf64", "value": "page" }, { "type": "input-text", "label": "查询接口每页数据行数参数字段名", "name": "pagination.size", "id": "u:a8fae66fa927", "value": "size" }, { "type": "input-text", "label": "接口返回总数据量字段 PATH", "name": "pagination.total", "id": "u:e1cd979c7ee8", "value": "result.total", "themeCss": { "inputControlClassName": { "padding-and-margin:default": { "marginBottom": "", "marginTop": "", "marginRight": "", "marginLeft": "" } } } } ], "id": "u:0f1bd8fc2f2b", "collapsed": true, "headingClassName": "", "bodyClassName": "p" }, { "type": "fieldset", "title": "请求方法", "collapsable": true, "body": [ { "type": "input-text", "label": "请求名称", "name": "fetchName", "id": "u:a3e712484fae", "value": "fetchTableList", "description": "追加了YAPI数据则不使用此参数", "themeCss": { "labelClassName": { "padding-and-margin:default": { "marginTop": "", "marginRight": "", "marginBottom": "", "marginLeft": "" } } }, "labelClassName": "labelClassName-a3e712484fae" }, { "type": "input-text", "label": "接口数据字段 PATH", "name": "result", "id": "u:8c082acf7db2", "value": "[\"result\"][\"records\"]", "description": "" }, { "type": "input-text", "label": "service方法名", "name": "serviceName", "id": "u:cfbbdd07366b", "value": "getTableList", "description": "" } ], "id": "u:382f8cdf59a6", "collapsed": true, "className": "", "headingClassName": "", "bodyClassName": "p-r p-l p-b" }, { "type": "fieldset", "title": "新增/编辑弹框", "collapsable": true, "body": [ { "type": "switch", "label": "是否包含弹框", "option": "", "name": "includeModifyModal", "falseValue": false, "trueValue": true, "id": "u:03957070af9e", "value": false }, { "type": "combo", "label": "表单项", "name": "modifyModal.formItems", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "strictMode": false, "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:86cc27b6a663" }, "items": [ { "type": "input-text", "name": "key", "id": "u:62cc1cf36c73", "label": "字段名(key)" }, { "type": "select", "name": "type", "options": [ { "label": "string", "value": "string" }, { "label": "number", "value": "number" }, { "label": "boolean", "value": "boolean" }, { "label": "Dayjs", "value": "Dayjs" }, { "label": "string[]", "value": "string[]" }, { "label": "number[]", "value": "number[]" }, { "label": "boolean[]", "value": "boolean[]" }, { "label": "[Dayjs,Dayjs]", "value": "[Dayjs,Dayjs]" } ], "id": "u:b165c75e5e1a", "multiple": false, "label": "字段类型", "value": "" }, { "type": "switch", "label": "字段可选", "option": "", "name": "optional", "falseValue": false, "trueValue": true, "id": "u:68fc4c85fb03", "value": false, "description": "字段名字后加?" }, { "type": "select", "name": "defaultValue", "options": [ { "label": "\"\"", "value": "\"\"" }, { "label": "false", "value": "false" }, { "label": "true", "value": "true" }, { "label": "0", "value": "0" }, { "label": "undefined", "value": "undefined" }, { "label": "[]", "value": "[]" } ], "id": "u:379ea92fb3c6", "multiple": false, "label": "默认值", "value": "" }, { "type": "select", "name": "component", "options": [ { "label": "input", "value": "input" }, { "label": "input-password", "value": "input-password" }, { "label": "input-number", "value": "input-number" }, { "label": "textarea", "value": "textarea" }, { "label": "select", "value": "select" }, { "label": "radio-group", "value": "radio-group" }, { "label": "checkbox-group", "value": "checkbox-group" }, { "label": "switch", "value": "switch" }, { "label": "date-picker", "value": "date-picker" }, { "label": "time-ticker", "value": "time-picker" }, { "label": "range-picker", "value": "range-picker" }, { "label": "transfer", "value": "transfer" } ], "id": "u:7932ea3b05da", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "name": "label", "id": "u:5bb237f20098", "label": "label" }, { "type": "input-text", "name": "placeholder", "id": "u:580898257491", "label": "placeholder" }, { "type": "switch", "label": "required", "option": "", "name": "required", "falseValue": false, "trueValue": true, "id": "u:559dbdbb01da", "value": false, "description": "验证规则加required" }, { "type": "input-text", "name": "message", "id": "u:55013279d659", "label": "校验失败 message", "value": "不能为空" }, { "type": "switch", "label": "更多组件配置", "option": "", "name": "showMore", "falseValue": false, "trueValue": true, "id": "u:67e0cb5b7496", "value": false, "description": "" }, { "type": "switch", "label": "labelInValue", "option": "", "name": "labelInValue", "falseValue": false, "trueValue": true, "id": "u:7fd6f1b233d9", "value": false, "description": "是否把每个选项的 label 包装到 value 中", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "select", "name": "mode", "options": [ { "label": "multiple", "value": "multiple" }, { "label": "tags", "value": "tags" } ], "multiple": false, "label": "mode", "value": "", "description": "设置 Select 的模式为多选或标签", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "optionFilterProp", "label": "optionFilterProp", "description": "搜索时过滤对应的 option 属性", "value": "label", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "showSearch", "option": "", "name": "showSearch", "falseValue": false, "trueValue": true, "value": false, "description": "使单选模式可搜索", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "hideArrow", "option": "", "name": "hideArrow", "falseValue": false, "trueValue": true, "value": false, "description": "是否隐藏下拉小箭头", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "maxlength", "label": "maxlength", "description": "最大长度", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "switch", "label": "showCount", "option": "", "name": "showCount", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示字数", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "input-text", "name": "max", "label": "max", "description": "最大值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "min", "label": "min", "description": "最小值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "step", "label": "step", "description": "每次改变步数,可以为小数", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "checkedChildren", "label": "checkedChildren", "description": "选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedChildren", "label": "unCheckedChildren", "description": "非选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "checkedValue", "label": "checkedValue", "description": "选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedValue", "label": "unCheckedValue", "description": "非选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "select", "name": "picker", "options": [ { "label": "date", "value": "date" }, { "label": "week", "value": "week" }, { "label": "month", "value": "month" }, { "label": "quarter", "value": "quarter" }, { "label": "year", "value": "year" } ], "multiple": false, "label": "picker", "description": "设置选择器类型", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker')}" }, { "type": "switch", "label": "showTime", "name": "showTime", "falseValue": false, "trueValue": true, "value": false, "description": "增加时间选择功能", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showNow", "name": "showNow", "falseValue": false, "trueValue": true, "value": false, "description": "当设定了 showTime 的时候,面板是否显示“此刻”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showToday", "name": "showToday", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示“今天”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" } ], "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false, "hiddenOn": "${!includeModifyModal}" } ], "bodyClassName": "p", "collapsed": true } ], "submitText": "" } ], "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false } }, "conditionFiles": { "includeModifyModal": { "value": false, "exclude": [ "/ModifyModal/index.vue.ejs", "/ModifyModal/model.ts.ejs", "/ModifyModal/presenter.tsx.ejs", "/ModifyModal/presenter.ts.ejs", "/ModifyModal/service.ts.ejs" ] } }, "excludeCompile": [ "temp.mock.script.ejs" ] } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/config/schema.ts ================================================ export type PageConfig = { filters: { component: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ label: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ placeholder: string; }[]; columns: { slot: boolean; /** * @description 保持原始内容,不要翻译 * @type {string} */ title: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ dataIndex: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; }[]; pagination: { show: boolean; page: string; size: string; total: string; }; includeModifyModal: boolean; fetchName: string; result: string; serviceName: string; }; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,filters 字段中的 key 字段的值翻译为英文,使用驼峰语法,label、placeholder 字段的值保留中文。columns 字段中的 key、dataIndex 字段的值翻译为英文,使用驼峰语法,title 字段的值保留中文。 返回翻译后的 markdown 语法的代码块 ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/测试使用 jsx 作为模版引擎/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/测试使用 jsx 作为模版引擎/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; try { await main.handleAfterCompile(); } catch (ex) { console.log(ex); } }, complete: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; try { await main.handleComplete(); } catch (ex) { console.log(ex); } }, initFiltersFromImage: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleInitFiltersFromImage(); return res; }, initFiltersFromText: (lowcodeContext) => { let filters = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); filters = filters.map((item) => { const s = item.replace(/:|:/g, ':').split(':'); return { component: (s[1] || '').indexOf('选择') > -1 ? 'select' : 'input', key: s[0].trim(), label: s[0].trim(), placeholder: s[1] || '', }; }); lowcodeContext.outputChannel.appendLine(JSON.stringify(filters)); return { ...lowcodeContext.model, filters }; }, initColumnsFromImage: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleInitColumnsFromImage(); return res; }, initColumnsFromText: (lowcodeContext) => { let columns = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); columns = columns.map((s) => ({ slot: false, title: s, dataIndex: s, key: s, })); lowcodeContext.outputChannel.appendLine(JSON.stringify(columns)); return { ...lowcodeContext.model, columns }; }, askChatGPT: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleAskChatGPT(); return res; }, }; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/script/src/main.ts ================================================ import * as path from 'path'; import { window, env, workspace } from 'vscode'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { lint } from '@share/utils/lint'; import { renderTemplates } from '@share/utils/tsx'; import { translate } from '@share/TypeChatSlim/index'; import { generalBasic } from '@share/BaiduOCR/index'; import { typescriptToMock } from '@share/utils/json'; import { context } from './context'; import { PageConfig } from '../../config/schema'; export async function handleInitFiltersFromImage() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return lowcodeContext?.model; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); env.clipboard.writeText(ocrRes.words_result.map((s) => s.words).join('\r\n')); window.showInformationMessage('内容已经复制到剪贴板'); const filters = ocrRes.words_result .map((s) => s.words) .reduce((result, value, index, array) => { if (index % 2 === 0) { result.push(array.slice(index, index + 2)); } return result; }, [] as string[][]); const formatedFilters = filters.map((s) => ({ component: s[1].indexOf('选择') > -1 ? 'select' : 'input', key: s[0].replace(/:|:/g, '').trim(), label: s[0].replace(/:|:/g, '').trim(), placeholder: s[1], })); return { ...lowcodeContext.model, filters: formatedFilters }; } export async function handleInitColumnsFromImage() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return lowcodeContext?.model; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); env.clipboard.writeText(ocrRes.words_result.map((s) => s.words).join('\r\n')); window.showInformationMessage('内容已经复制到剪贴板'); const columns = ocrRes.words_result.map((s) => ({ slot: false, title: s.words, dataIndex: s.words, key: s.words, })); return { ...lowcodeContext.model, columns }; } export async function handleAskChatGPT() { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'PageConfig'; const res = await translate({ schema, typeName, request: JSON.stringify(lowcodeContext!.model as PageConfig), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify(lowcodeContext!.model as PageConfig)}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2)); if (res.success) { return { ...res.data }; } return lowcodeContext!.model; } export async function handleAfterCompile() { const { lowcodeContext } = context; const tempWorkPath = path.join( lowcodeContext?.env.privateMaterialsPath || '', '.lowcode', ); await renderTemplates( { ...lowcodeContext?.model, title: '12121', }, path.join(tempWorkPath, 'src'), ); } export async function handleComplete() { const { lowcodeContext } = context; const createBlockPath = context.lowcodeContext?.createBlockPath; if (createBlockPath) { // #region lint lint({ createBlockPath, rootPath: lowcodeContext!.env.rootPath, }); // #endregion // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(createBlockPath, 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync( path.join(createBlockPath, 'temp.mock.script.ejs').toString(), ) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.script.ejs')); // @ts-ignore if (!lowcodeContext?.model.includeModifyModal) { fs.removeSync(path.join(path.join(createBlockPath, 'ModifyModal'))); } const mockScript = ejs.render(mockTemplate, { ...lowcodeContext!.model, mockCode, mockData, createBlockPath: createBlockPath.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showInformationMessage( // '获取 mock 项目路径失败,跳过更新 mock 服务', // ); }); if (mockProjectPathRes?.data.result) { const projectName = workspace.rootPath ?.replace(/\\/g, '/') .split('/') .pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } // #endregion } } } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/ModifyModal/index.vue.ejs ================================================ ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/ModifyModal/model.ts.ejs ================================================ import { reactive, ref } from "vue"; <% if(modifyModal.formItems.some(s => (s.type || "").indexOf("Dayjs") > -1)) { _%> import type { Dayjs } from "dayjs"; <% } _%> export interface IFormData { id?:number; <% modifyModal.formItems.map(item => { _%> <%= item.key %><% if(item.optional){ _%>?<% } _%>: <%= item.type %>; <% }) _%> } const defaultFormData: IFormData = { <% modifyModal.formItems.map(item => { _%> <%= item.key %>: <%- item.defaultValue || '\"\"' %>, <% }) _%> }; <% if(modifyModal.formItems.some(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group")){ %> interface IOptionItem { label: string; value: string; } interface IOptions { <% modifyModal.formItems.filter(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group").map(item => { _%> <%= item.key %>: IOptionItem[]; <% }) _%> } const defaultOptions: IOptions = { <% modifyModal.formItems.filter(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group").map(item => { _%> <%= item.key %>: [], <% }) _%> }; <% } %> export const useModel = () => { const formData = reactive({ ...defaultFormData }); <% if(modifyModal.formItems.some(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group")){ _%> const options = reactive({ ...defaultOptions }); <% } _%> const loading = ref(false); return { formData, <% if(modifyModal.formItems.some(s => s.component === "select" || s.component === "radio-group" || s.component === "checkbox-group")){ _%> options, <% } _%> loading }; }; export type Model = ReturnType; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/ModifyModal/presenter.tsx.ejs ================================================ import Service from "./service"; import { useModel } from "./model"; import { Form, message } from "ant-design-vue"; import { watch, reactive } from "vue"; import { Rule } from 'ant-design-vue/es/form/interface' interface IProps { title: string visible: boolean action: 'add' | 'edit' | 'view' id?: number } interface IEmit { (event: 'cancel'): void (event: 'ok'): void } const { useForm } = Form; export const usePresenter = (props: IProps, emit: IEmit) => { const model = useModel(); const service = new Service(model); const rules: Record = reactive({ <% modifyModal.formItems.map(item => { _%> <%= item.key %>: [{ required: <%= item.required || false %>, message: "<%= item.message %>" }], <% }) _%> }); const { resetFields, validate, validateInfos } = useForm( model.formData, rules, ); watch( () => props.visible, () => { if (props.visible && props.id) { service.getDetail(props.id as number); } }, ); const handleSubmit = () => { validate().then(() => { if (props.action === "add") { service.create().then(() => { message.success("新建成功"); resetFields(); emit("ok"); }); } else { service.edit().then(() => { message.success("提交成功"); resetFields(); emit("ok"); }); } }); }; const handleCancel = () => { resetFields(); emit("cancel"); }; return { model, service, handleSubmit, handleCancel, validateInfos, }; }; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/ModifyModal/service.ts.ejs ================================================ import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async getDetail(id: number) { // const res = await fetchDetail({ id }); // this.model.formData.id = id; <% modifyModal.formItems.map(item => { _%> // this.model.formData.<%= item.key %> = res.result<%= item.key %>; <% }) _%> } async create() { // this.model.loading.value = true; // await create({ <% modifyModal.formItems.map(item => { _%> // <%= item.key %>: this.model.formData.<%= item.key %>, <% }) _%> // }).finally(() => { // this.model.loading.value = false; // }); } async edit() { // this.model.loading.value = true; // await edit( // { id: this.model.formData.id! }, // { <% modifyModal.formItems.map(item => { _%> // <%= item.key %>: this.model.formData.<%= item.key %>, <% }) _%> // }, // ).finally(() => { // this.model.loading.value = false; // }); } } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/api.ts.ejs ================================================ import request from "@/utils/request"; <% if (locals.api) { %> // #region <%= api.title %> <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { _%> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.filter(query => query.name !== pagination.page && query.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.req_params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.query_path.params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}') < 0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType) { %> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } %> ) { return request<<%= typeName %>["result"]>({ url: `http://127.0.0.1:3000<%= api.query_path.path.replace(/\{/g,"${params.") %>`, method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } // #endregion <% } else { %> // #region export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params { <% filters.map(item => { _%> <%= item.key %>?: string; <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } export function <%= fetchName %>( params: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params ) { return requestResult["result"]>({ url: `http://127.0.0.1:3000/<%= createBlockPath %>/<%= fetchName %>`, method: 'GET', params, }); } // #endregion <% } %> ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/api.ts.template.tsx ================================================ import React from 'react'; interface IProps { api?: { title: string; req_query: { name: string }[]; req_params: { name: string }[]; query_path: { params: { name: string }[] }; req_body_other: string; }; requestBodyType: string; type: string; funcName: string; fetchName: string; title: string; columns: { title: string; key: string }[]; filters: { key: string; label: string; component: string }[]; result: string; pagination: { show: boolean; page: string; size: string }; } const Content: React.FC = (props) => ( <> {`import request from "@/utils/request";`} {props.api && ( <> // #region {props.api.title} {props.type} {(props.api.req_query.length > 0 || props.api.req_params.length > 0 || props.api.query_path.params.length > 0) && ( <> {`export interface I${props.funcName .slice(0, 1) .toUpperCase()}${props.funcName.slice(1)}Params {`} <> {props.api.req_query .filter( (query) => query.name !== props.pagination.page && query.name !== props.pagination.size, ) .map((query) => ( <>{query.name}?: string; ))} {props.api.req_params .filter( (query) => query.name !== props.pagination.page && query.name !== props.pagination.size, ) .map((query) => ( <>{query.name}?: string; ))} {props.api.query_path.params .filter( (query) => query.name !== props.pagination.page && query.name !== props.pagination.size, ) .map((query) => ( <>{query.name}?: string; ))} {props.pagination.show && ( <> {props.pagination.page}: number; {props.pagination.size}: number; )} {`}`} )} {props.requestBodyType && props.api.req_body_other.indexOf('{}') < 0 && ( <>{props.requestBodyType} )} // #endregion )} ); export default Content; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/index.vue.ejs ================================================ ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/model.ts.template.tsx ================================================ import React from 'react'; interface IProps { api?: object; funcName: string; fetchName: string; title: string; columns: { title: string; key: string }[]; filters: { key: string; label: string; component: string }[]; result: string; pagination: { show: boolean }; } const Content: React.FC = (props) => ( <> {`import { reactive, ref } from "vue";`} {props.api && `import { I${ props.funcName.slice(0, 1).toUpperCase() + props.funcName.slice(1) }Result } from "./api";`} {!props.api && `import { I${ props.fetchName.slice(0, 1).toUpperCase() + props.fetchName.slice(1) }Result } from "./api";`} {!props.api && ( <> {`interface ITableListItem {`} <> {props.columns.map((item, index) => ( <> {`/** * ${item.title} */ `} {item.key || `column${index + 1}`}: string; ))} <> <> {` /** * 接口返回的数据,新增字段不需要改 ITableListItem 直接从这里取 */ `} {`apiResult: I${props.fetchName .slice(0, 1) .toUpperCase()}${props.fetchName.slice(1)}Result${ props.result }[0]`} {`}`} )} {`interface IFormData {`} <> {props.filters.map((item) => ( <> {`/** * ${item.label} */ `} {item.component === 'range-picker' && `${item.key}?: string[];`} {item.component !== 'range-picker' && `${item.key}?: string;`} ))} {`}`} {props.filters.some((s) => s.component === 'select') && ( <> {`interface IOptionItem { label: string; value: string; }`} {`interface IOptions {`} <> {props.filters .filter((s) => s.component === 'select') .map((item) => ( <>{`${item.key}: IOptionItem[],`} ))} {`}`} {`const defaultOptions: IOptions = {`} <> {props.filters .filter((s) => s.component === 'select') .map((item) => ( <>{`${item.key}: [],`} ))} {`};`} )} {`;export const defaultFormData: IFormData = {`} <> {props.filters.map((item) => ( <>{`${item.key}: undefined,`} ))} {`}`} {`;export const useModel = () => {`} <> <> {`const filterForm = reactive({ ...defaultFormData });`} {props.filters.some((s) => s.component === 'select') && ( <>{`const options = reactive({ ...defaultOptions });`} )} {props.api && ( <> {` const tableList = ref<(I${props.funcName .slice(0, 1) .toUpperCase()}${props.funcName.slice(1)}Result${ props.result }[0] & { _?: unknown })[]>( [], ); `} )} {!props.api && ( <> {` const tableList = ref<(ITableListItem & { _?: unknown })[]>( [], ); `} )} {props.pagination.show && ( <> {` const pagination = reactive<{ page: number; pageSize: number; total: number; }>({ page: 1, pageSize: 10, total: 0, }); `} )} {` const loading = reactive<{ list: boolean }>({ list: false, }); `} {`return {`} <> filterForm, {props.filters.some((s) => s.component === 'select') && `options,`} tableList, {props.pagination.show && `pagination,`} loading, {`}`} {`};`} {`export type Model = ReturnType ;`} ); export default Content; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/presenter.tsx.ejs ================================================ import Service from "./service"; import { defaultFormData, useModel } from "./model"; import { createVNode, onMounted } from "vue"; import { message, Modal } from "ant-design-vue"; import { ExclamationCircleOutlined } from "@ant-design/icons-vue"; export const usePresenter = () => { const model = useModel(); const service = new Service(model); onMounted(() => { service.<%= serviceName %>(); }); const handleClear = () => { Object.assign(model.filterForm, defaultFormData) <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; const handleSearch = () => { <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; <% if(pagination.show) { _%> const handlePageChange = (page: number, pageSize: number) => { if (pageSize !== model.pagination.pageSize) { model.pagination.pageSize = pageSize; model.pagination.page = 1; } else { model.pagination.page = page; } service.<%= serviceName %>(); }; <% } _%> const handleDel = (record: typeof model.tableList.value[0]) => { Modal.confirm({ title: "此操作将删除该选项,是否继续?", icon: createVNode(ExclamationCircleOutlined), okText: "确定", cancelText: "取消", onOk() { message.success("删除成功"); }, }); }; const handleCreate = () => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "新建"; model.modalInfo.action = "add"; model.modalInfo.id = undefined; <% } _%> }; const handleEdit = (record: typeof model.tableList.value[0]) => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "编辑"; model.modalInfo.action = "edit"; model.modalInfo.id = record.id; <% } _%> }; <% if(includeModifyModal) { _%> const handleView = (record: typeof model.tableList.value[0]) => { model.modalInfo.visible = true; model.modalInfo.title = "查看"; model.modalInfo.action = "view"; model.modalInfo.id = record.id; }; const handleModalOk = () => { model.modalInfo.visible = false; service.<%= serviceName %>(); }; const handleModalCancel = () => { model.modalInfo.visible = false; }; <% } _%> return { model, service, handleClear, handleSearch, <% if(pagination.show) { _%> handlePageChange, <% } _%> handleDel, handleCreate, handleEdit, <% if(includeModifyModal) { _%> handleView, handleModalOk, handleModalCancel <% } _%> }; }; ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/service.ts.ejs ================================================ import { <%= locals.api ? funcName : fetchName %> } from "./api"; import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async <%= serviceName %>() { this.model.loading.list = true; const res = await <%= locals.api ? funcName : fetchName %>({ <% filters.map(item => { _%> <%= item.key %>: this.model.filterForm.<%= item.key %>, <% }) _%> <% if(pagination.show) { _%> <%= pagination.page %>: this.model.pagination.page, <%= pagination.size %>: this.model.pagination.pageSize, <% } _%> }).finally(() => { this.model.loading.list = false; }); this.model.tableList.value = res<%- result %>.map((s) => { return { ...s, <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: s.<%= item.key || `column${index+1}` %>, <% }) _%> apiResult: s }; }); <% if(pagination.show) { _%> this.model.pagination.total = res.<%- pagination.total %>; <% } _%> } } ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/temp.mock.script.ejs ================================================ .get(`<%= createBlockPath %>/<%= fetchName %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: materials/blocks/测试使用 jsx 作为模版引擎/src/temp.mock.type.ejs ================================================ { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } ================================================ FILE: materials/blocks/测试脚本/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/blocks/测试脚本/config/preview.json ================================================ { "title": "测试脚本", "description": "测试脚本", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "amis", "scripts": [ { "method": "intFromOcrText", "remark": "使用 ocr 结果初始化表单" }, { "method": "askChatGPT", "remark": "askChatGPT" } ] } ================================================ FILE: materials/blocks/测试脚本/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "不需要编译的文件,不会被删除.ejs" ] } } ================================================ FILE: materials/blocks/测试脚本/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文,使用驼峰语法, 返回翻译后的markdown语法的代码块 ================================================ FILE: materials/blocks/测试脚本/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/测试脚本/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/测试脚本/script/src/context'); module.exports = { beforeCompile: (context) => { const compileHandler = new main.CompileHandler3c5a281f3af548fda73cb864dd8f452b(context); compileHandler.log('compile start'); }, afterCompile: (context) => { const compileHandler = new main.CompileHandler3c5a281f3af548fda73cb864dd8f452b(context); compileHandler.log('compile end'); }, complete: (context) => { const compileHandler = new main.CompileHandler3c5a281f3af548fda73cb864dd8f452b(context); compileHandler.log('compile complete'); }, intFromOcrText: (context) => { const viewCallHandler = new main.ViewCallHandler3c5a281f3af548fda73cb864dd8f452b(context); viewCallHandler.log('call method intFromOcrText'); viewCallHandler.showInformationMessage('lowcode'); return viewCallHandler.intFromOcrText(); }, askChatGPT: (context) => { const viewCallHandler = new main.ViewCallHandler3c5a281f3af548fda73cb864dd8f452b(context); viewCallHandler.log('call method askChatGPT'); return viewCallHandler.askChatGPT(); }, }; ================================================ FILE: materials/blocks/测试脚本/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/测试脚本/script/src/main.ts ================================================ import { CompileContext, ViewCallContext } from 'lowcode-context'; export class CompileHandler3c5a281f3af548fda73cb864dd8f452b { private context!: CompileContext; constructor(context: CompileContext) { this.context = context; } log(value: string) { this.context.outputChannel.appendLine(value); } } export class ViewCallHandler3c5a281f3af548fda73cb864dd8f452b { private context!: ViewCallContext; constructor(context: ViewCallContext) { this.context = context; } log(value: string) { this.context.outputChannel.appendLine(value); } showInformationMessage(msg: string) { this.context.vscode.window.showInformationMessage(msg); } intFromOcrText() { return Promise.resolve({ ...this.context.model, name: '测试一下' }); } async askChatGPT() { const res = await this.context.createChatCompletion({ messages: [{ role: 'user', content: this.context.params }], handleChunk: (data) => { this.context.outputChannel.append(data.text || ''); }, }); return { ...this.context.model, name: res }; } } ================================================ FILE: materials/blocks/测试脚本/src/README.md ================================================ 在当前文件夹下放区块模板,并将此文件删除 ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/config/model.json ================================================ { "items": [], "bordered": false, "extra": false, "layout": "horizontal", "variableName": "", "title": "", "column": "" } ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "amis", "scripts": [ { "method": "OCR", "remark": "OCR 识别剪贴版截图" }, { "method": "initFromOcrText", "remark": "初始化列表,参数中粘贴 ocr 识别出的结果,每一行表示一项,也可以手动输入" }, { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的 items.key 字段" }, { "method": "intFromClipboardImage", "remark": "读取剪贴板截图并翻译,初始化列表" } ] } ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "id": "u:11b127c5df46", "label": "变量名", "name": "variableName", "description": "" }, { "type": "input-text", "id": "u:1fdc2ed6321c", "label": "title", "name": "title", "description": "标题" }, { "type": "switch", "id": "u:9cc79716dde1", "label": "bordered", "option": "", "name": "bordered", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示边框" }, { "type": "input-text", "id": "u:43fbc9d670c3", "label": "column", "name": "column", "description": "一行的 DescriptionItems 数量,可以写成像素值或支持响应式的对象写法 { xs: 8, sm: 16, md: 24},默认3" }, { "type": "switch", "id": "u:b8b2cb8217d1", "label": "extra", "option": "", "name": "extra", "falseValue": false, "trueValue": true, "value": false, "description": "描述列表的操作区域,显示在右上方" }, { "type": "select", "id": "u:a8859cde1998", "label": "layout", "name": "layout", "options": [ { "label": "horizontal", "value": "horizontal" }, { "label": "vertical", "value": "vertical" } ], "multiple": false, "description": "描述布局", "value": "horizontal" }, { "type": "combo", "label": "描述列表", "name": "items", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "内容的描述" }, { "type": "input-number", "label": "span", "name": "span", "keyboard": true, "id": "u:9ee4d10df24a", "step": 1, "min": 1, "description": "包含列的数量" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": false, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "value": [ {} ] } ], "submitText": "", "id": "u:67967afb0e69" } ], "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false, "id": "u:d87dbf6bf8df" }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "temp.mock.script.ejs" ] } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/config/schema.ts ================================================ export type IItems = { /** * @description 保持原始内容,不需要处理,不要翻译 * @type {string} */ label: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; span?: number; }[]; ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/现有模块中添加 antdv Descriptions 描述列表/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/现有模块中添加 antdv Descriptions 描述列表/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, complete: (lowcodeContext) => { context.lowcodeContext = lowcodeContext; main.handleComplete(); }, initFromOcrText: (lowcodeContext) => { let items = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); items = items.map((s) => ({ key: s.split(/:|:/g)[0], label: s.split(/:|:/g)[0], })); return { ...lowcodeContext.model, items }; }, askChatGPT: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleAskChatGPT(); return res; }, intFromClipboardImage: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleIntFromClipboardImage(); return res; }, OCR: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleOCR(); return res; }, }; ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/script/src/main.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { workspace, window } from 'vscode'; import { translate } from '@share/TypeChatSlim/index'; import { generalBasic } from '@share/BaiduOCR/index'; import { typescriptToMock } from '@share/utils/json'; import { context } from './context'; import { IItems } from '../../config/schema'; export async function handleOCR() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return { updateModelImmediately: false, onlyUpdateParams: true, params: '', model: lowcodeContext?.model, }; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); return { updateModelImmediately: false, onlyUpdateParams: true, params: ocrRes.words_result.map((s) => s.words).join('\r\n'), model: lowcodeContext?.model, }; } export async function handleAskChatGPT() { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'IItems'; const res = await translate({ schema, typeName, request: JSON.stringify((lowcodeContext!.model as { items: IItems }).items), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify( (lowcodeContext!.model as { items: IItems }).items, )}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2)); if (res.success) { return { ...lowcodeContext!.model, items: res.data }; } return lowcodeContext!.model; } export async function handleIntFromClipboardImage() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里获取不到图片'); return lowcodeContext?.model; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); const items: { key: string; label: string }[] = []; ocrRes.words_result.map((s, index) => { const includeColon = s.words.includes(':') || s.words.includes(':'); if (includeColon) { const work = s.words.split(/:|:/g)[0]; items.push({ key: work, label: work, }); } else if (index % 2 === 0) { const work = s.words.split(/:|:/g)[0]; items.push({ key: work, label: work, }); } }); const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'IItems'; const res = await translate({ schema, typeName, request: JSON.stringify(items), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify(items)}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2)); if (res.success) { return { ...lowcodeContext!.model, items: res.data }; } return { ...lowcodeContext.model }; } export async function handleComplete() { const { lowcodeContext } = context; const createBlockPath = context.lowcodeContext?.createBlockPath; if (createBlockPath) { // #region 更新 api.ts 文件 const apiFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.api.ts')) .toString(); let apiFileContentOld = ''; try { apiFileContentOld = fs .readFileSync(path.join(createBlockPath, 'api.ts').toString()) .toString(); } catch {} fs.writeFileSync( path.join(createBlockPath, 'api.ts'), apiFileContentOld + apiFileContent, ); fs.removeSync(path.join(createBlockPath, 'temp.api.ts')); try { execa.sync('node', [ path.join(workspace.rootPath!, '/node_modules/eslint/bin/eslint.js'), path.join(createBlockPath, 'api.ts'), '--resolve-plugins-relative-to', workspace.rootPath!, '--fix', ]); } catch (err) { console.log(err); } // #endregion // #region 更新 model.ts 文件 const modelFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.model.ts')) .toString(); let modelFileContentOld = fs .readFileSync(path.join(createBlockPath, 'model.ts').toString()) .toString(); const keywords = [ '// lowcode-model-import-api', '// lowcode-model-type', '// lowcode-model-variable', '// lowcode-model-return-variable', ]; const modelSplitArr = modelFileContent.split( new RegExp(keywords.join('|'), 'ig'), ); const modelImportApi = modelSplitArr[1].replace(/\n/g, ''); const modelType = modelSplitArr[2]; const modelVariable = modelSplitArr[3]; const modelReturnVariable = modelSplitArr[4].replace(/\n/g, ''); if (!modelFileContentOld.includes('// lowcode-model-import-api')) { modelFileContentOld = `// lowcode-model-import-api\n${modelFileContentOld}`; } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-import-api', modelImportApi, ); if (!modelFileContentOld.includes('// lowcode-model-type')) { modelFileContentOld = modelFileContentOld.replace( 'export const useModel', '// lowcode-model-type\nexport const useModel', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-type', modelType, ); if (!modelFileContentOld.includes('// lowcode-model-variable')) { modelFileContentOld = modelFileContentOld.replace( 'return {', '// lowcode-model-variable\nreturn {', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-variable', modelVariable, ); if (!modelFileContentOld.includes('// lowcode-model-return-variable')) { modelFileContentOld = modelFileContentOld.replace( 'return {', 'return {\n// lowcode-model-return-variable', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-return-variable', modelReturnVariable, ); fs.writeFileSync( path.join(createBlockPath, 'model.ts'), modelFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.model.ts')); try { execa.sync('node', [ path.join(workspace.rootPath!, '/node_modules/eslint/bin/eslint.js'), path.join(createBlockPath, 'model.ts'), '--resolve-plugins-relative-to', workspace.rootPath!, '--fix', ]); } catch (err) { console.log(err); } // #endregion // #region 更新 service.ts 文件 const serviceFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.service.ts').toString()) .toString(); let serviceFileContentOld = fs .readFileSync(path.join(createBlockPath, 'service.ts').toString()) .toString() .trim(); const serviceSplitArr = serviceFileContent.split( new RegExp( ['// lowcode-service-import-api', '// lowcode-service-method'].join( '|', ), 'ig', ), ); const serviceImportApi = serviceSplitArr[1].replace(/\n/g, ''); const serviceMethod = serviceSplitArr[2]; if (!serviceFileContentOld.includes('// lowcode-service-import-api')) { serviceFileContentOld = `// lowcode-service-import-api\n${serviceFileContentOld}`; } serviceFileContentOld = serviceFileContentOld.replace( '// lowcode-service-import-api', serviceImportApi, ); if (!serviceFileContentOld.includes('// lowcode-service-method')) { serviceFileContentOld = `${serviceFileContentOld.slice( 0, serviceFileContentOld.length - 1, )}// lowcode-service-method\n}`; } serviceFileContentOld = serviceFileContentOld.replace( '// lowcode-service-method', serviceMethod, ); fs.writeFileSync( path.join(createBlockPath, 'service.ts'), serviceFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.service.ts')); try { execa.sync('node', [ path.join(workspace.rootPath!, '/node_modules/eslint/bin/eslint.js'), path.join(createBlockPath, 'service.ts'), '--resolve-plugins-relative-to', workspace.rootPath!, '--fix', ]); } catch (err) { console.log(err); } // #endregion // #region 更新 index.vue 文件 const vueFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.index.vue').toString()) .toString(); let vueFileContentOld = fs .readFileSync(path.join(createBlockPath, 'index.vue').toString()) .toString(); const vueSplitArr = vueFileContent.split( new RegExp([''].join('|'), 'ig'), ); const vueTemplate = vueSplitArr[1]; if (!vueFileContentOld.includes('')) { const index = vueFileContentOld.lastIndexOf(''); vueFileContentOld = `${vueFileContentOld.substring( 0, index, )}${vueFileContentOld.substring(index)}`; } vueFileContentOld = vueFileContentOld.replace( '', vueTemplate, ); fs.writeFileSync( path.join(createBlockPath, 'index.vue'), vueFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.index.vue')); try { execa.sync('node', [ path.join(workspace.rootPath!, '/node_modules/eslint/bin/eslint.js'), path.join(createBlockPath, 'index.vue'), '--resolve-plugins-relative-to', workspace.rootPath!, '--fix', ]); } catch (err) { console.log(err); } // #endregion // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(createBlockPath, 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync(path.join(createBlockPath, 'temp.mock.script').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.script')); const mockScript = ejs.render(mockTemplate, { ...lowcodeContext!.model, mockCode, mockData, createBlockPath: createBlockPath.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showErrorMessage('获取 mock 项目路径失败'); }); if (mockProjectPathRes?.data.result) { const projectName = workspace.rootPath ?.replace(/\\/g, '/') .split('/') .pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } // #endregion } } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/src/temp.api.ts.ejs ================================================ // #region export interface IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result { code: number; msg: string; result: { <% items.map((item, index) => { _%> <%= item.key || `item${index+1}` %>: string; <% }) _%> }; } export interface I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Params { id?: number; } export function fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>( params: I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Params ) { return requestResult>({ url: `http://127.0.0.1:3000<%= createBlockPath %>/fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>`, method: 'GET', params, }); } // #endregion ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/src/temp.index.vue.ejs ================================================ :column="<%= column %>" <% } _%> <% if(layout){ _%> layout="<%= layout %>" <% } _%> > <% if(extra){ _%> <% } _%> <% items.map((item, index) => { _%> :span="<%= item.span %>" <% } _%> > {{ model.<%= variableName %>.value?.<%= item.key || `item${index+1}` %> }} <% }) _%> ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/src/temp.mock.script ================================================ .get(`<%= createBlockPath %>/fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/src/temp.mock.type.ejs ================================================ { code: number; msg: string; result: { <% items.map((item, index) => { _%> <%= item.key || `item${index+1}` %>: string; <% }) _%> }; } ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/src/temp.model.ts.ejs ================================================ // lowcode-model-import-api import { IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result } from "./api"; // lowcode-model-type interface I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %> { <% items.map((item, index) => { _%> /** <%= item.label %> */ <%= item.key || `item${index+1}` %>: string; <% }) _%> /** * 接口返回的数据,新增字段不需要改 I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %> 直接从这里取 */ apiResult: IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result['result'] } // lowcode-model-variable const <%= variableName %> = ref | undefined>( undefined, ); // lowcode-model-return-variable <%= variableName %>, ================================================ FILE: materials/blocks/现有模块中添加 antdv Descriptions 描述列表/src/temp.service.ts.ejs ================================================ // lowcode-service-import-api import {fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>} from './api'; // lowcode-service-method async get<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>() { const res = await fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>({}) this.model.<%= variableName %>.value = { <% items.map((item, index) => { _%> <%= item.key || `item${index+1}` %>: res.result.<%= item.key || `item${index+1}` %>, <% }) _%> apiResult: res.result } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/config/model.json ================================================ { "items": [], "variableName": "" } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "amis", "scripts": [ { "method": "OCR", "remark": "OCR 识别剪贴版截图" }, { "method": "initFromOcrText", "remark": "初始化列表,参数中粘贴 ocr 识别出的结果,每一行表示一项,也可以手动输入" }, { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的 items.key 字段" } ] } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "id": "u:67967afb0e69", "title": "", "body": [ { "type": "input-text", "id": "u:11b127c5df46", "label": "变量名", "name": "variableName", "description": "" }, { "type": "combo", "label": "描述列表", "name": "items", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "内容的描述" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": false, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "value": [ {} ] } ], "submitText": "" } ], "id": "u:d87dbf6bf8df", "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "不需要编译的文件,不会被删除.ejs" ] } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/config/schema.ts ================================================ export type IItems = { /** * @description 保持原始内容,不需要处理,不要翻译 * @type {string} */ label: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; }[]; ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/现有模块中添加 antdv Form 垂直布局列表/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/现有模块中添加 antdv Form 垂直布局列表/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, complete: (lowcodeContext) => { context.lowcodeContext = lowcodeContext; main.handleComplete(); }, initFromOcrText: (lowcodeContext) => { let items = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); items = items.map((s) => ({ key: s.split(/:|:/g)[0], label: s.split(/:|:/g)[0], })); return { ...lowcodeContext.model, items }; }, OCR: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleOCR(); return res; }, runScript: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleRunScript(); return res; }, }; ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/script/src/main.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { workspace, window } from 'vscode'; import { generalBasic } from '@share/BaiduOCR/index'; import { translate } from '@share/TypeChatSlim/index'; import { typescriptToMock } from '@share/utils/json'; import { IItems } from '../../config/schema'; import { context } from './context'; export async function handleOCR() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return { updateModelImmediately: false, onlyUpdateParams: true, params: '', model: lowcodeContext?.model, }; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); return { updateModelImmediately: false, onlyUpdateParams: true, params: ocrRes.words_result.map((s) => s.words).join('\r\n'), model: lowcodeContext?.model, }; } const scriptHandle: { [method: string]: () => Promise<{ updateModelImmediately: boolean; onlyUpdateParams: boolean; params?: string; model: any; }>; } = { askChatGPT: async () => { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'IItems'; const res = await translate({ schema, typeName, request: JSON.stringify( (lowcodeContext!.model as { items: IItems }).items, ), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify( (lowcodeContext!.model as { items: IItems }).items, )}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2)); if (res.success) { return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...lowcodeContext?.model, items: res.data }, }; } return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: lowcodeContext?.model, }; }, }; export async function handleRunScript() { const { lowcodeContext } = context; const res = await scriptHandle[lowcodeContext!.method](); return res; } export async function handleComplete() { const { lowcodeContext } = context; const createBlockPath = context.lowcodeContext?.createBlockPath; if (createBlockPath) { // #region 更新 api.ts 文件 const apiFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.api.ts')) .toString(); let apiFileContentOld = ''; try { apiFileContentOld = fs .readFileSync(path.join(createBlockPath, 'api.ts').toString()) .toString(); } catch {} fs.writeFileSync( path.join(createBlockPath, 'api.ts'), apiFileContentOld + apiFileContent, ); fs.removeSync(path.join(createBlockPath, 'temp.api.ts')); // #endregion // #region 更新 model.ts 文件 const modelFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.model.ts')) .toString(); let modelFileContentOld = fs .readFileSync(path.join(createBlockPath, 'model.ts').toString()) .toString(); const keywords = [ '// lowcode-model-import-api', '// lowcode-model-type', '// lowcode-model-variable', '// lowcode-model-return-variable', ]; const modelSplitArr = modelFileContent.split( new RegExp(keywords.join('|'), 'ig'), ); const modelImportApi = modelSplitArr[1].replace(/\n/g, ''); const modelType = modelSplitArr[2]; const modelVariable = modelSplitArr[3]; const modelReturnVariable = modelSplitArr[4].replace(/\n/g, ''); if (!modelFileContentOld.includes('// lowcode-model-import-api')) { modelFileContentOld = `// lowcode-model-import-api\n${modelFileContentOld}`; } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-import-api', modelImportApi, ); if (!modelFileContentOld.includes('// lowcode-model-type')) { modelFileContentOld = modelFileContentOld.replace( 'export const useModel', '// lowcode-model-type\nexport const useModel', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-type', modelType, ); if (!modelFileContentOld.includes('// lowcode-model-variable')) { modelFileContentOld = modelFileContentOld.replace( 'return {', '// lowcode-model-variable\nreturn {', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-variable', modelVariable, ); if (!modelFileContentOld.includes('// lowcode-model-return-variable')) { modelFileContentOld = modelFileContentOld.replace( 'return {', 'return {\n// lowcode-model-return-variable', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-return-variable', modelReturnVariable, ); fs.writeFileSync( path.join(createBlockPath, 'model.ts'), modelFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.model.ts')); // #endregion // #region 更新 service.ts 文件 const serviceFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.service.ts').toString()) .toString(); let serviceFileContentOld = fs .readFileSync(path.join(createBlockPath, 'service.ts').toString()) .toString() .trim(); const serviceSplitArr = serviceFileContent.split( new RegExp( ['// lowcode-service-import-api', '// lowcode-service-method'].join( '|', ), 'ig', ), ); const serviceImportApi = serviceSplitArr[1].replace(/\n/g, ''); const serviceMethod = serviceSplitArr[2]; if (!serviceFileContentOld.includes('// lowcode-service-import-api')) { serviceFileContentOld = `// lowcode-service-import-api\n${serviceFileContentOld}`; } serviceFileContentOld = serviceFileContentOld.replace( '// lowcode-service-import-api', serviceImportApi, ); if (!serviceFileContentOld.includes('// lowcode-service-method')) { serviceFileContentOld = `${serviceFileContentOld.slice( 0, serviceFileContentOld.length - 1, )}// lowcode-service-method\n}`; } serviceFileContentOld = serviceFileContentOld.replace( '// lowcode-service-method', serviceMethod, ); fs.writeFileSync( path.join(createBlockPath, 'service.ts'), serviceFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.service.ts')); // #endregion // #region 更新 index.vue 文件 const vueFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.index.vue').toString()) .toString(); let vueFileContentOld = fs .readFileSync(path.join(createBlockPath, 'index.vue').toString()) .toString(); const vueSplitArr = vueFileContent.split( new RegExp([''].join('|'), 'ig'), ); const vueTemplate = vueSplitArr[1]; if (!vueFileContentOld.includes('')) { const index = vueFileContentOld.lastIndexOf(''); vueFileContentOld = `${vueFileContentOld.substring( 0, index, )}${vueFileContentOld.substring(index)}`; } vueFileContentOld = vueFileContentOld.replace( '', vueTemplate, ); fs.writeFileSync( path.join(createBlockPath, 'index.vue'), vueFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.index.vue')); // #endregion // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(createBlockPath, 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync(path.join(createBlockPath, 'temp.mock.script').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.script')); const mockScript = ejs.render(mockTemplate, { ...lowcodeContext!.model, mockCode, mockData, createBlockPath: createBlockPath.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showErrorMessage('获取 mock 项目路径失败'); }); if (mockProjectPathRes?.data.result) { const projectName = workspace.rootPath ?.replace(/\\/g, '/') .split('/') .pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } } // #endregion } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/src/temp.api.ts.ejs ================================================ // #region export interface IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result { code: number; msg: string; result: { <% items.map((item, index) => { _%> <%= item.key || `item${index+1}` %>: string; <% }) _%> }; } export interface I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Params { id?: number; } export function fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>( params: I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Params ) { return requestResult>({ url: `http://127.0.0.1:3000<%= createBlockPath %>/fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>`, method: 'GET', params, }); } // #endregion ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/src/temp.index.vue.ejs ================================================ <% items.map((item, index) => { _%> {{ model.<%= variableName %>.value?.<%= item.key || `item${index+1}` %> }} <% }) _%> ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/src/temp.mock.script ================================================ .get(`<%= createBlockPath %>/fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/src/temp.mock.type.ejs ================================================ { code: number; msg: string; result: { <% items.map((item, index) => { _%> <%= item.key || `item${index+1}` %>: string; <% }) _%> }; } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/src/temp.model.ts.ejs ================================================ // lowcode-model-import-api import { IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result } from "./api"; // lowcode-model-type interface I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %> { <% items.map((item, index) => { _%> /** <%= item.label %> */ <%= item.key || `item${index+1}` %>: string; <% }) _%> /** * 接口返回的数据,新增字段不需要改 I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %> 直接从这里取 */ apiResult: IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result['result'] } // lowcode-model-variable const <%= variableName %> = ref | undefined>( undefined, ); // lowcode-model-return-variable <%= variableName %>, ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 垂直布局列表/src/temp.service.ts.ejs ================================================ // lowcode-service-import-api import {fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>} from './api'; // lowcode-service-method async get<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>() { const res = await fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>({}) this.model.<%= variableName %>.value = { <% items.map((item, index) => { _%> <%= item.key || `item${index+1}` %>: res.result.<%= item.key || `item${index+1}` %>, <% }) _%> apiResult: res.result } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/config/model.json ================================================ { "variableName": "", "formItems": [] } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "form-render", "scripts": [ { "method": "OCR", "remark": "OCR 识别剪贴版截图" }, { "method": "initFromOcrText", "remark": "初始化列表,参数中粘贴 ocr 识别出的结果,每一行表示一项,也可以手动输入" }, { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的 formItems.key 字段" } ] } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/config/schema.json ================================================ { "formSchema": { "schema": { "type": "object", "column": 1, "labelWidth": 120, "displayType": "column", "properties": { "variableName": { "title": "变量名", "type": "string", "hidden": false, "props": {} }, "formItems": { "title": "表单项", "type": "array", "items": { "type": "object", "properties": { "key": { "title": "字段名", "type": "string", "props": {} }, "type": { "title": "字段类型", "type": "string", "enum": [ "string", "number", "boolean", "Dayjs", "string[]", "number[]", "boolean[]", "[Dayjs,Dayjs]", "{name?:string;url:string}[]" ], "enumNames": [ "string", "number", "boolean", "Dayjs", "string[]", "number[]", "boolean[]", "[Dayjs,Dayjs]", "图片、文件上传选此项,默认值选空数组" ], "widget": "select", "default": "string" }, "optional": { "title": "字段可选", "type": "boolean", "widget": "switch", "description": "字段名字后加?" }, "defaultValue": { "title": "默认值", "type": "string", "enum": [ "\"\"", "false", "boolean", "true", "0", "undefined", "[]" ], "enumNames": [ "\"\"", "false", "boolean", "true", "0", "undefined", "[]" ], "widget": "select", "default": "\"\"" }, "component": { "title": "组件", "type": "string", "enum": [ "input", "input-password", "input-number", "textarea", "select", "radio-group", "checkbox-group", "switch", "date-picker", "time-picker", "range-picker", "transfer", "uploadFiles" ], "enumNames": [ "input", "input-password", "input-number", "textarea", "select", "radio-group", "checkbox-group", "switch", "date-picker", "time-picker", "range-picker", "transfer", "uploadFiles(图片、文件上传)" ], "widget": "select" }, "label": { "title": "label", "type": "string", "props": {} }, "placeholder": { "title": "placeholder", "type": "string", "props": {} }, "required": { "title": "是否必填", "type": "boolean", "widget": "switch", "description": "验证规则加required", "required": false }, "message": { "title": "校验失败 message", "type": "string", "default": "不能为空", "props": {} }, "showMore": { "title": "更多组件配置", "type": "boolean", "widget": "switch" }, "labelInValue": { "title": "labelInValue", "type": "boolean", "widget": "switch", "description": "是否把每个选项的 label 包装到 value 中", "index": 0, "hidden": "{{rootValue.showMore !== true || rootValue.component !== 'select'}}" }, "mode": { "title": "mode", "type": "string", "enum": [ "multiple", "tags" ], "enumNames": [ "multiple", "tags" ], "widget": "select", "description": "设置 Select 的模式为多选或标签", "index": 1, "hidden": "{{rootValue.showMore !== true || rootValue.component !== 'select'}}" }, "optionFilterProp": { "title": "optionFilterProp", "type": "string", "description": "搜索时过滤对应的 option 属性", "default": "label", "props": {}, "hidden": "{{rootValue.showMore !== true || rootValue.component !== 'select'}}" }, "showSearch": { "title": "showSearch", "type": "boolean", "widget": "switch", "description": "使单选模式可搜索", "hidden": "{{rootValue.showMore !== true || rootValue.component !== 'select'}}" }, "hideArrow": { "title": "hideArrow", "type": "boolean", "widget": "switch", "description": "是否隐藏下拉小箭头", "hidden": "{{rootValue.showMore !== true || rootValue.component !== 'select'}}" }, "maxlength": { "title": "maxlength", "type": "string", "description": "最大长度", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'input' && rootValue.component !== 'input-password' && rootValue.component !== 'textarea')}}" }, "showCount": { "title": "showCount", "type": "boolean", "widget": "switch", "description": "是否展示字数", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'input' && rootValue.component !== 'input-password' && rootValue.component !== 'textarea')}}" }, "max": { "title": "max", "type": "string", "description": "最大值", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'input-number')}}" }, "min": { "title": "min", "type": "string", "description": "最小值", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'input-number')}}" }, "step": { "title": "step", "type": "string", "description": "每次改变步数,可以为小数", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'input-number')}}" }, "checkedChildren": { "title": "checkedChildren", "type": "string", "description": "选中时的内容", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'switch')}}" }, "unCheckedChildren": { "title": "unCheckedChildren", "type": "string", "description": "非选中时的内容", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'switch')}}" }, "checkedValue": { "title": "checkedValue", "type": "string", "description": "选中时的值", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'switch')}}" }, "unCheckedValue": { "title": "unCheckedValue", "type": "string", "description": "非选中时的值", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'switch')}}" }, "picker": { "title": "picker", "type": "string", "enum": [ "date", "week", "month", "quarter", "year" ], "enumNames": [ "date", "week", "month", "quarter", "year" ], "widget": "select", "description": "设置选择器类型", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'date-picker' && rootValue.component !== 'range-picker')}}" }, "showTime": { "title": "showTime", "type": "boolean", "widget": "switch", "description": "增加时间选择功能", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'date-picker' && rootValue.component !== 'range-picker' || rootValue.picker !== 'date')}}" }, "showNow": { "title": "showNow", "type": "boolean", "widget": "switch", "description": "当设定了 showTime 的时候,面板是否显示“此刻”按钮", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'date-picker' && rootValue.component !== 'range-picker' || rootValue.picker !== 'date')}}" }, "showToday": { "title": "showToday", "type": "boolean", "widget": "switch", "description": "是否展示“今天”按钮", "hidden": "{{rootValue.showMore !== true || (rootValue.component !== 'date-picker' && rootValue.component !== 'range-picker' || rootValue.picker !== 'date')}}" } } }, "props": {}, "index": 0, "hidden": false } } }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "不需要编译的文件,不会被删除.ejs" ] } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/config/schema.ts ================================================ export type IFormItems = { /** * @description 保持原始内容,不需要处理,不要翻译 * @type {string} */ label: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; /** * @description 保持原始内容,不需要处理,不要翻译 * @type {string} */ placeholder: string; }[]; ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/现有模块中添加 antdv Form 表单/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/现有模块中添加 antdv Form 表单/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, complete: (lowcodeContext) => { context.lowcodeContext = lowcodeContext; main.handleComplete(); }, initFromOcrText: (lowcodeContext) => { let formItems = lowcodeContext.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); formItems = formItems.map((s) => ({ key: s.split(/:|:/g)[0], label: s.split(/:|:/g)[0], placeholder: s.split(/:|:/g)[1] || '', })); return { ...lowcodeContext.model, formItems }; }, OCR: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleOCR(); return res; }, runScript: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; const res = await main.handleRunScript(); return res; }, }; ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/现有模块中添加 antdv Form 表单/script/src/main.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { window } from 'vscode'; import { generalBasic } from '@share/BaiduOCR/index'; import { translate } from '@share/TypeChatSlim/index'; import { context } from './context'; import { IFormItems } from '../../config/schema'; export async function handleOCR() { const { lowcodeContext } = context; if (!lowcodeContext?.clipboardImage) { window.showInformationMessage('剪贴板里没有截图'); return { updateModelImmediately: false, onlyUpdateParams: true, params: '', model: lowcodeContext?.model, }; } const ocrRes = await generalBasic({ image: lowcodeContext!.clipboardImage! }); return { updateModelImmediately: false, onlyUpdateParams: true, params: ocrRes.words_result.map((s) => s.words).join('\r\n'), model: lowcodeContext?.model, }; } const scriptHandle: { [method: string]: () => Promise<{ updateModelImmediately: boolean; onlyUpdateParams: boolean; params?: string; model: any; }>; } = { askChatGPT: async () => { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'IFormItems'; const res = await translate({ schema, typeName, request: JSON.stringify( (lowcodeContext!.model as { formItems: IFormItems }).formItems, ), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify( (lowcodeContext!.model as { formItems: IFormItems }).formItems, )}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); lowcodeContext!.outputChannel.appendLine(JSON.stringify(res, null, 2)); if (res.success) { return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...lowcodeContext?.model, formItems: res.data }, }; } return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: lowcodeContext?.model, }; }, }; export async function handleRunScript() { const { lowcodeContext } = context; const res = await scriptHandle[lowcodeContext!.method](); return res; } export async function handleComplete() { const createBlockPath = context.lowcodeContext?.createBlockPath; if (createBlockPath) { // #region 更新 model.ts 文件 const modelFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.model.ts')) .toString(); let modelFileContentOld = fs .readFileSync(path.join(createBlockPath, 'model.ts')) .toString(); const keywords = [ '// lowcode-model-type', '// lowcode-model-defalut-data', '// lowcode-model-variable', '// lowcode-model-return-variable', ]; const modelSplitArr = modelFileContent.split( new RegExp(keywords.join('|'), 'ig'), ); const modelType = modelSplitArr[1]; const modelDefaultData = modelSplitArr[2]; const modelVariable = modelSplitArr[3]; const modelReturnVariable = modelSplitArr[4].replace(/\n/g, ''); if (!modelFileContentOld.includes('// lowcode-model-type')) { modelFileContentOld = modelFileContentOld.replace( 'export const useModel', '// lowcode-model-type\nexport const useModel', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-type', modelType, ); if (!modelFileContentOld.includes('// lowcode-model-defalut-data')) { modelFileContentOld = modelFileContentOld.replace( 'export const useModel', '// lowcode-model-defalut-data\nexport const useModel', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-defalut-data', modelDefaultData, ); if (!modelFileContentOld.includes('// lowcode-model-variable')) { modelFileContentOld = modelFileContentOld.replace( 'return {', '// lowcode-model-variable\nreturn {', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-variable', modelVariable, ); if (!modelFileContentOld.includes('// lowcode-model-return-variable')) { modelFileContentOld = modelFileContentOld.replace( 'return {', 'return {\n// lowcode-model-return-variable', ); } modelFileContentOld = modelFileContentOld.replace( '// lowcode-model-return-variable', modelReturnVariable, ); fs.writeFileSync( path.join(createBlockPath, 'model.ts'), modelFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.model.ts')); // #endregion // #region 更新 service.ts 文件 const serviceFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.service.ts')) .toString(); let serviceFileContentOld = fs .readFileSync(path.join(createBlockPath, 'service.ts')) .toString() .trim(); const serviceSplitArr = serviceFileContent.split( new RegExp(['// lowcode-service-method'].join('|'), 'ig'), ); const serviceMethod = serviceSplitArr[1]; if (!serviceFileContentOld.includes('// lowcode-service-method')) { serviceFileContentOld = `${serviceFileContentOld.slice( 0, serviceFileContentOld.length - 1, )}// lowcode-service-method\n}`; } serviceFileContentOld = serviceFileContentOld.replace( '// lowcode-service-method', serviceMethod, ); fs.writeFileSync( path.join(createBlockPath, 'service.ts'), serviceFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.service.ts')); // #endregion // #region 更新 presenter 文件 const presenterFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.presenter.ts')) .toString(); let presenterFile = path.join(createBlockPath, 'presenter.ts'); if (!fs.existsSync(presenterFile)) { presenterFile = path.join(createBlockPath, 'presenter.tsx'); } let presenterFileContentOld = fs.readFileSync(presenterFile).toString(); const presenterSplitArr = presenterFileContent.split( new RegExp( ['// lowcode-presenter-variable', '// lowcode-presenter-return'].join( '|', ), 'ig', ), ); const presenterVariable = presenterSplitArr[1]; const presenterReturn = presenterSplitArr[2]; if (!presenterFileContentOld.includes('// lowcode-presenter-variable')) { presenterFileContentOld = presenterFileContentOld.replace( 'return {', `// lowcode-presenter-variable\nreturn {`, ); } presenterFileContentOld = presenterFileContentOld.replace( '// lowcode-presenter-variable', presenterVariable, ); if (!presenterFileContentOld.includes('// lowcode-presenter-return')) { presenterFileContentOld = presenterFileContentOld.replace( 'return {', 'return {\n// lowcode-presenter-return', ); } presenterFileContentOld = presenterFileContentOld.replace( '// lowcode-presenter-return', presenterReturn, ); fs.writeFileSync(presenterFile, presenterFileContentOld); fs.removeSync(path.join(createBlockPath, 'temp.presenter.ts')); // #endregion // #region 更新 index.vue 文件 const vueFileContent = fs .readFileSync(path.join(createBlockPath, 'temp.index.vue').toString()) .toString(); let vueFileContentOld = fs .readFileSync(path.join(createBlockPath, 'index.vue').toString()) .toString(); const vueSplitArr = vueFileContent.split( new RegExp( ['', '// lowcode-vue-import'].join('|'), 'ig', ), ); const vueTemplate = vueSplitArr[1]; const vueImport = vueSplitArr[2]; if (!vueFileContentOld.includes('')) { const index = vueFileContentOld.lastIndexOf(''); vueFileContentOld = `${vueFileContentOld.substring( 0, index, )}${vueFileContentOld.substring(index)}`; } vueFileContentOld = vueFileContentOld.replace( '', vueTemplate, ); if (!vueFileContentOld.includes('lowcode-vue-import')) { vueFileContentOld = vueFileContentOld.replace( '', `// lowcode-vue-columns\n`, ); } vueFileContentOld = vueFileContentOld.replace( '// lowcode-vue-columns', vueColumns, ); fs.writeFileSync( path.join(createBlockPath, 'index.vue'), vueFileContentOld, ); fs.removeSync(path.join(createBlockPath, 'temp.index.vue')); try { execa.sync('node', [ path.join(workspace.rootPath!, '/node_modules/eslint/bin/eslint.js'), path.join(createBlockPath, 'index.vue'), '--resolve-plugins-relative-to', workspace.rootPath!, '--fix', ]); } catch (err) { console.log(err); } // #endregion // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(createBlockPath, 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync( path.join(createBlockPath, 'temp.mock.script.ejs').toString(), ) .toString(); fs.removeSync(path.join(createBlockPath, 'temp.mock.script.ejs')); const mockScript = ejs.render(mockTemplate, { ...lowcodeContext!.model, mockCode, mockData, createBlockPath: createBlockPath.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showErrorMessage('获取 mock 项目路径失败'); }); if (mockProjectPathRes?.data.result) { const projectName = workspace.rootPath ?.replace(/\\/g, '/') .split('/') .pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } // #endregion } } } ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.api.ts.ejs ================================================ // #region export interface IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } export interface IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Params { <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> id?: number; } export function fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>( params: IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Params ) { return requestResult>({ url: `http://127.0.0.1:3000<%= createBlockPath.replace(':', '') %>/fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>`, method: 'GET', params, }); } // #endregion ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.index.vue.ejs ================================================ <% if(pagination.show) { _%> <% } _%> // lowcode-vue-columns const <%= variableName %>TableColumns = [ <% columns.map((item, index) => { _%> { title: "<%= item.title || `column${index+1}` %>", dataIndex: "<%= item.dataIndex || `column${index+1}` %>", key: "<%= item.key || `column${index+1}` %>", <% if(item.width) {%>width: "<%= item.width %>",<% } _%> }, <% }) _%> { title: "操作", key: "operation", width: 100 } ]; ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.mock.script.ejs ================================================ .get(`<%= createBlockPath %>/fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.mock.type.ejs ================================================ { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.model.ts.ejs ================================================ // lowcode-model-import-api import { IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result } from "./api"; // lowcode-model-type interface I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Item { <% columns.map((item, index) => { _%> /** <%= item.title %> */ <%= item.key || `column${index+1}` %>: string; <% }) _%> /** * 接口返回的数据,新增字段不需要改 I<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Item 直接从这里取 */ apiResult: IFetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>Result<%- result %>[0] } // lowcode-model-variable const <%= variableName %> = refItem[]>( [], ); <% if(pagination.show) { %> const <%= variableName %>Pagination = reactive<{ page: number; pageSize: number; total: number; }>({ page: 1, pageSize: 10, total: 0, }); <% } %> // lowcode-model-return-variable <%= variableName %>, <% if(pagination.show) { _%> <%= variableName %>Pagination, <% } _%> ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.presenter.ts.ejs ================================================ // lowcode-presenter-handlePageChange <% if(pagination.show) { _%> const handle<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>PageChange = (page: number, pageSize: number) => { if (pageSize !== model.<%= variableName %>Pagination.pageSize) { model.<%= variableName %>Pagination.pageSize = pageSize; model.<%= variableName %>Pagination.page = 1; } else { model.<%= variableName %>Pagination.page = page; } service.get<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>(); }; <% } _%> // lowcode-presenter-return-handlePageChange <% if(pagination.show) { _%> handle<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>PageChange, <% } _%> ================================================ FILE: materials/blocks/现有模块中添加 antdv Table 表格/src/temp.service.ts.ejs ================================================ // lowcode-service-import-api import {fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>} from './api'; // lowcode-service-method async get<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>() { const res = await fetch<%= variableName.slice(0, 1).toUpperCase() + variableName.slice(1) %>({}) this.model.<%= variableName %>.value = res<%- result %>.map((s) => { return { ...s, <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: s.<%= item.key || `column${index+1}` %>, <% }) _%> apiResult: s }; }); <% if(pagination.show) { _%> this.model.<%= variableName %>Pagination.total = res.<%- pagination.total %>; <% } _%> } ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/config/preview.json ================================================ { "title": "通过 ast 给 antdv Descriptions 描述列表添加字段", "description": "现有模块必须是遵循整洁架构的目录结构,否则无法使用", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "amis", "scripts": [ { "method": "test", "remark": "测试一下" } ] } ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "不需要编译的文件,不会被删除.ejs" ] } } ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文,使用驼峰语法, 返回翻译后的markdown语法的代码块 ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/script/src/context'); module.exports = { beforeCompile: (context) => { const compileHandler = new main.CompileHandlerb9e78736b4ba410186eabffd9a749388(context); compileHandler.log('compile start'); }, afterCompile: (context) => { const compileHandler = new main.CompileHandlerb9e78736b4ba410186eabffd9a749388(context); compileHandler.log('compile end'); }, complete: (context) => { const compileHandler = new main.CompileHandlerb9e78736b4ba410186eabffd9a749388(context); compileHandler.updateModel(); compileHandler.log('compile complete'); }, intFromOcrText: (context) => { const viewCallHandler = new main.ViewCallHandlerb9e78736b4ba410186eabffd9a749388(context); viewCallHandler.log('call method intFromOcrText'); viewCallHandler.showInformationMessage('lowcode'); return viewCallHandler.intFromOcrText(); }, test: (context) => ({ ...context.model, name: '测试一下' }), }; ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/script/src/main.ts ================================================ import path from 'path'; import * as typescriptParse from 'recast/parsers/typescript'; import * as recast from 'recast'; import fs from 'fs-extra'; import { visit, builders } from 'ast-types'; import { IdentifierKind } from 'ast-types/gen/kinds'; import { CompileContext, ViewCallContext } from 'lowcode-context'; export class CompileHandlerb9e78736b4ba410186eabffd9a749388 { private context!: CompileContext; constructor(context: CompileContext) { this.context = context; } log(value: string) { this.context.outputChannel.appendLine(value); } updateModel() { const code = fs.readFileSync( path.join(this.context.createBlockPath!, 'model.ts'), 'utf-8', ); const ast = recast.parse(code, { parser: typescriptParse }); const fieldName = 'name'; const fieldType = 'string'; visit(ast, { visitTSInterfaceDeclaration: (nodePath) => { const members = nodePath.node.body.body; if ((nodePath.node.id as IdentifierKind).name === 'IDetailInfo') { members.push( builders.tsPropertySignature( builders.identifier(fieldName), builders.tsTypeAnnotation(builders.tsStringKeyword()), ), ); } return false; }, }); visit(ast, { visitVariableDeclaration(nodePath) { const declaration = nodePath.node.declarations[0]; if ( // @ts-ignore declaration.id.name === 'defaultFormData' && // @ts-ignore declaration.init.type === 'ObjectExpression' ) { // @ts-ignore declaration.init.properties.push( builders.objectProperty( builders.identifier(fieldName), builders.stringLiteral(''), ), ); } return false; }, }); const newCode = recast.print(ast).code; fs.writeFileSync( path.join(this.context.createBlockPath!, 'model.ts'), newCode, ); } } export class ViewCallHandlerb9e78736b4ba410186eabffd9a749388 { private context!: ViewCallContext; constructor(context: ViewCallContext) { this.context = context; } log(value: string) { this.context.outputChannel.appendLine(value); } intFromOcrText() { return Promise.resolve({ ...this.context.model, name: '测试一下' }); } } ================================================ FILE: materials/blocks/通过 ast 给 antdv Descriptions 描述列表添加字段/src/README.md ================================================ 在当前文件夹下放区块模板,并将此文件删除 ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/config/model.json ================================================ { "name": "通过脚本启动一个 nest api 服务" } ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/config/preview.json ================================================ { "title": "通过脚本启动一个 nest api 服务", "description": "通过脚本启动一个 nest api 服务", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "schema": "amis", "scripts": [ { "method": "startNestApiServer", "remark": "启动 nest api 服务" } ] } ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] }, "conditionFiles": { "name": { "value": "123", "exclude": [ "当表单name的值为123,删除这个数组里的文件.ejs" ] } }, "excludeCompile": [ "不需要编译的文件,不会被删除.ejs" ] } } ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文,使用驼峰语法, 返回翻译后的markdown语法的代码块 ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/blocks/通过脚本启动一个 nest api 服务/script/src/main'); const { context, } = require('../../../../dist/materials/blocks/通过脚本启动一个 nest api 服务/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, startNestApiServer: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); return { ...lowcodeContext.model }; }, }; ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/script/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getHello(): string { return this.appService.getHello(); } } ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/script/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: `.env.${process.env.NODE_ENV}`, isGlobal: true, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/script/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { context } from './context'; @Injectable() export class AppService { getHello(): string { return context.lowcodeContext?.env.rootPath || 'Hello World!'; } } ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/script/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { context } from './context'; export async function bootstrap() { const lowcodeContext = context.lowcodeContext!; if (!context.statusBarItem) { const statusBarItem = lowcodeContext.vscode.window.createStatusBarItem( lowcodeContext.vscode.StatusBarAlignment.Left, ); context.statusBarItem = statusBarItem; } if (!context.nestApp) { context.statusBarItem.text = '$(loading~spin) Start nest api server...'; context.statusBarItem.show(); const app = await NestFactory.create(AppModule); await app .listen(3000) .then(() => { context.nestApp = app; if (context.statusBarItem) { context.statusBarItem.text = '$(circle-slash) Low Code Server Port : 3000'; context.statusBarItem.tooltip = 'Click to close nest api server'; // vscode 限制命令唯一,但是唯一的话命令回调里的 context 永远是同一个 const command = `lowcode.StopNestApiServer${new Date().getTime()}`; try { const dsisposable = lowcodeContext.vscode.commands.registerCommand( command, () => { context.statusBarItem!.text = '$(loading~spin) close...'; context.statusBarItem!.command = undefined; context.nestApp?.close().then(() => { context.nestApp = undefined; context.statusBarItem?.hide(); context.statusBarItem?.dispose(); context.statusBarItem = undefined; dsisposable.dispose(); }); }, ); } catch (ex) { /* empty */ } context.statusBarItem.command = command; } }) .catch((ex) => { context.statusBarItem?.hide(); context.statusBarItem?.dispose(); context.statusBarItem = undefined; throw ex; }); } } ================================================ FILE: materials/blocks/通过脚本启动一个 nest api 服务/src/README.md ================================================ 在当前文件夹下放区块模板,并将此文件删除 ================================================ FILE: materials/snippets/OCR/config/commandPrompt.ejs ================================================ ================================================ FILE: materials/snippets/OCR/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/OCR/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/OCR/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/OCR/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/OCR/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/OCR/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/OCR/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/OCR/script/src/main.ts ================================================ import { window, Range, env } from 'vscode'; import { generalBasic } from '@share/BaiduOCR/index'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const clipboardImage = await lowcodeContext?.getClipboardImage(); const ocrRes = await generalBasic({ image: clipboardImage! }); const words = ocrRes.words_result.map((s) => s.words).join(','); env.clipboard.writeText(words).then(() => { window.showInformationMessage('内容已经复制到剪贴板'); }); window.activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (window.activeTextEditor?.selection.isEmpty) { editBuilder.insert(window.activeTextEditor.selection.start, words); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), words, ); } }); } ================================================ FILE: materials/snippets/OCR/src/template.ejs ================================================ ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/config/schema.ts ================================================ export type IColumns = { /** * @description 保持原始内容,不需要处理,不要翻译 * @type {string} */ title: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ dataIndex: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; slots: { /** * @description 翻译成英文,驼峰格式 * @type {string} */ customRender: string; }; }[]; ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/OCR + ChatGPT 翻译/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/OCR + ChatGPT 翻译/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/script/src/main.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { window, Range } from 'vscode'; import { generalBasic } from '@share/BaiduOCR/index'; import { translate } from '@share/TypeChatSlim/index'; import { context } from './context'; import { IColumns } from '../../config/schema'; export async function bootstrap() { const { lowcodeContext } = context; const clipboardImage = await lowcodeContext?.getClipboardImage(); const ocrRes = await generalBasic({ image: clipboardImage! }); const columns = ocrRes.words_result.map((s) => ({ title: s.words, dataIndex: s.words, key: s.words, slots: { customRender: s.words, }, })); const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'IColumns'; const res = await translate({ schema, typeName, request: JSON.stringify(columns), completePrompt: `你是一个根据以下 TypeScript 类型定义将用户请求转换为 "${typeName}" 类型的 JSON 对象的服务,并且按照字段的注释进行处理:\n` + `\`\`\`\n${schema}\`\`\`\n` + `以下是用户请求:\n` + `"""\n${JSON.stringify(columns)}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, extendValidate: (jsonObject) => ({ success: true, data: jsonObject }), }); let insertText = ''; if (res.success) { insertText = JSON.stringify(res.data, null, 2); } else { insertText = JSON.stringify(res, null, 2); } window.activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (lowcodeContext?.activeTextEditor?.selection.isEmpty) { editBuilder.insert(window.activeTextEditor!.selection.start, insertText); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), insertText, ); } }); } ================================================ FILE: materials/snippets/OCR + ChatGPT 翻译/src/template.ejs ================================================ ================================================ FILE: materials/snippets/Pro Chat/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/Pro Chat/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/Pro Chat/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/Pro Chat/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/Pro Chat/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/Pro Chat/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/Pro Chat/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/Pro Chat/script/src/controller.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { createChatCompletion } from '@share/LLM/gemini'; import { createChatCompletion as createChatGPTChatCompletion } from '@share/LLM/openai'; import { invokeLLMChunkCallback } from '@share/WebView/callback'; import { IMessage } from '@share/WebView/type'; const API_KEY = 'lowcode.GeminiKey'; type Message = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const askGemini = async ( message: IMessage<{ messages: Message; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const context = lowcodeContext.env.extensionContext; // await context.secrets.delete(API_KEY); // 需要更新 API KEY 的时候打开 let apiKey = await context.secrets.get(API_KEY); if (!apiKey) { vscode.window.showWarningMessage('Enter your API KEY to save it securely.'); apiKey = await setApiKey(context); if (!apiKey) { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: 'Please enter your api key', }); return { content: 'Please enter your api key', }; } } const res = await createChatCompletion({ model: message.data.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gemini-pro-vision' : 'gemini-pro', apiKey, messages: message.data.messages, handleChunk: (data) => { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, proxyUrl: 'http://127.0.0.1:7890', }); return { content: res, }; }; export const askChatGPT = async ( message: IMessage<{ messages: Message; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const res = await createChatGPTChatCompletion({ model: message.data.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gpt-4-vision-preview' : undefined, messages: message.data.messages, handleChunk: (data) => { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, }); return { content: res, }; }; async function setApiKey(context) { const apiKey = await vscode.window.showInputBox({ title: 'Enter your API KEY', password: true, placeHolder: '**************************************', ignoreFocusOut: true, }); if (!apiKey) { vscode.window.showWarningMessage('Empty value'); return; } await context.secrets.store(API_KEY, apiKey); return apiKey; } ================================================ FILE: materials/snippets/Pro Chat/script/src/main.ts ================================================ import { window } from 'vscode'; import { showWebView } from '@share/WebView'; import { routes } from './routes'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; showWebView({ key: 'main', title: 'lowcode chat', viewColumn: 3, task: { task: 'route', data: { path: '/chat', materialPath: lowcodeContext?.materialPath }, }, lowcodeContext: { ...lowcodeContext!, }, htmlForWebview: getHtmlForWebview(false), routes, }); } const getHtmlForWebview = (dev = false) => { if (dev) { return `
`; } return `
`; }; ================================================ FILE: materials/snippets/Pro Chat/script/src/routes.ts ================================================ import * as controller from './controller'; export const routes: Record = { askGemini: controller.askGemini, askChatGPT: controller.askChatGPT, }; ================================================ FILE: materials/snippets/Pro Chat + Tldraw/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/Pro Chat + Tldraw/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/Pro Chat + Tldraw/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/Pro Chat + Tldraw/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/Pro Chat + Tldraw/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/Pro Chat + Tldraw/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/Pro Chat + Tldraw/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/Pro Chat + Tldraw/script/src/controller.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { createChatCompletion } from '@share/LLM/gemini'; import { createChatCompletion as createChatGPTChatCompletion } from '@share/LLM/openai'; import { invokeLLMChunkCallback } from '@share/WebView/callback'; import { IMessage } from '@share/WebView/type'; const API_KEY = 'lowcode.GeminiKey'; export const getMaterialPath = async ( message: IMessage<{ materialPath: string; script: string; params: string; }>, context: { webview: vscode.Webview; task: { task: string; data?: any }; } & CompileContext, ) => context.materialPath; type Message = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const askGemini = async ( message: IMessage<{ messages: Message; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const context = lowcodeContext.env.extensionContext; // await context.secrets.delete(API_KEY); // 需要更新 API KEY 的时候打开 let apiKey = await context.secrets.get(API_KEY); if (!apiKey) { vscode.window.showWarningMessage('Enter your API KEY to save it securely.'); apiKey = await setApiKey(context); if (!apiKey) { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: 'Please enter your api key', }); return { content: 'Please enter your api key', }; } } const res = await createChatCompletion({ model: message.data.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gemini-pro-vision' : 'gemini-pro', apiKey, messages: message.data.messages, handleChunk: (data) => { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, proxyUrl: 'http://127.0.0.1:7890', }); return { content: res, }; }; export const askChatGPT = async ( message: IMessage<{ messages: Message; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const res = await createChatGPTChatCompletion({ model: message.data.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gpt-4-vision-preview' : undefined, messages: message.data.messages, handleChunk: (data) => { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, }); return { content: res, }; }; async function setApiKey(context) { const apiKey = await vscode.window.showInputBox({ title: 'Enter your API KEY', password: true, placeHolder: '**************************************', ignoreFocusOut: true, }); if (!apiKey) { vscode.window.showWarningMessage('Empty value'); return; } await context.secrets.store(API_KEY, apiKey); return apiKey; } ================================================ FILE: materials/snippets/Pro Chat + Tldraw/script/src/main.ts ================================================ import { window } from 'vscode'; import { showWebView } from '@share/WebView'; import { routes } from './routes'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; showWebView({ key: 'main', title: 'tldraw make real', viewColumn: 3, task: { task: 'route', data: { path: '/tldraw', materialPath: lowcodeContext?.materialPath }, }, lowcodeContext: { ...lowcodeContext!, }, htmlForWebview: getHtmlForWebview(false), routes, }); } const getHtmlForWebview = (dev = false) => { if (dev) { return `
`; } return `
`; }; ================================================ FILE: materials/snippets/Pro Chat + Tldraw/script/src/routes.ts ================================================ import * as controller from './controller'; export const routes: Record = { getMaterialPath: controller.getMaterialPath, askGemini: controller.askGemini, askChatGPT: controller.askChatGPT, }; ================================================ FILE: materials/snippets/Pro Chat + TypeChat/config/commandPrompt.ejs ================================================ export const <%- rawSelectedText %>Options = <%- content %> export const <%- rawSelectedText %>Map = <%- rawSelectedText %>Options.reduce((obj, { label, value }) => { obj[value] = label return obj }, {}) ================================================ FILE: materials/snippets/Pro Chat + TypeChat/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/Pro Chat + TypeChat/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/Pro Chat + TypeChat/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/Pro Chat + TypeChat/config/schema.ts ================================================ export type IOption = { value: string; label: string }[]; ================================================ FILE: materials/snippets/Pro Chat + TypeChat/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/Pro Chat + TypeChat/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/Pro Chat + TypeChat/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/Pro Chat + TypeChat/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/Pro Chat + TypeChat/script/src/main.ts ================================================ import fs from 'fs'; import path from 'path'; import { env, window, Range } from 'vscode'; import { createChatCompletionShowWebView } from '@share/LLM'; import { translate } from '@share/TypeChatSlim/index'; import { getMaterial } from '@share/utils/material'; import { compile as compileEjs } from '@share/utils/ejs'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const clipboardText = await env.clipboard.readText(); const { selection, document } = window.activeTextEditor!; const selectText = document.getText(selection).trim(); const template = getMaterial(lowcodeContext!.materialPath); lowcodeContext?.outputChannel.appendLine(lowcodeContext!.materialPath); const res = await translate({ schema, typeName: 'IOption', request: (clipboardText || '').trim() || '客户验收状态:1.无需验收、2.待验收、3已验收', createChatCompletion: (options: { messages: any; handleChunk?: ((data: { text?: string | undefined }) => void) | undefined; }) => createChatCompletionShowWebView({ messages: options.messages, lowcodeContext: lowcodeContext!, htmlForWebview: getHtmlForWebview(false), // llm: 'gemini', }), tryCount: 3, }); if (res.success) { const code = compileEjs(template!.commandPrompt, { rawSelectedText: selectText, content: JSON.stringify(res.data), } as any); window.activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (window.activeTextEditor?.selection.isEmpty) { editBuilder.insert(window.activeTextEditor.selection.start, code); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), code, ); } }); } else { window.showErrorMessage(res.message); } } const getHtmlForWebview = (dev = false) => { if (dev) { return `
`; } return `
`; }; ================================================ FILE: materials/snippets/amis/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/amis/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": false, "notShowInSnippetsList": false, "notShowInintellisense": false, "schema": "amis", "scripts": [ { "method": "test", "remark": "测试一下" } ] } ================================================ FILE: materials/snippets/amis/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/amis/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文, 使用驼峰语法,返回翻译后的 markdown 语法的代码块 ================================================ FILE: materials/snippets/amis/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/amis/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/amis/script/src/context'); module.exports = { beforeCompile: (context) => { context.outputChannel.appendLine('compile amis start'); }, afterCompile: (context) => { context.outputChannel.appendLine('compile amis end'); }, test: (context) => { context.outputChannel.appendLine(Object.keys(context)); context.outputChannel.appendLine(JSON.stringify(context.model)); context.outputChannel.appendLine(context.params); return { ...context.model, name: '测试一下' }; }, }; ================================================ FILE: materials/snippets/amis/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/amis/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/amis/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/axios-request/config/model.json ================================================ {} ================================================ FILE: materials/snippets/axios-request/config/preview.json ================================================ { "title": "axios-request", "description": "axios 通用封装", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [ "请求" ] } ================================================ FILE: materials/snippets/axios-request/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/axios-request/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/axios-request/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/axios-request/script/src/context'); module.exports = { beforeCompile: (context) => {}, afterCompile: (constext) => {}, }; ================================================ FILE: materials/snippets/axios-request/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/axios-request/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/axios-request/src/template.ejs ================================================ import axios, { AxiosRequestConfig } from "axios"; const instance = axios.create({ timeout: 30 * 1000, }); // 请求拦截 instance.interceptors.request.use( (config) => { return config; }, (error) => { return Promise.reject(error); }, ); // 响应拦截 instance.interceptors.response.use( (res) => { return Promise.resolve(res.data); }, (error) => { return Promise.reject(error); }, ); type Request = (config: AxiosRequestConfig) => Promise; export const request = instance.request as Request; ================================================ FILE: materials/snippets/axios-request-api/config/model.json ================================================ {} ================================================ FILE: materials/snippets/axios-request-api/config/preview.json ================================================ { "title": "axios-request-api", "description": "通过 yapi 接口信息生成接口请求方法", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/axios-request-api/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/axios-request-api/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/axios-request-api/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/axios-request-api/script/src/context'); module.exports = { beforeCompile: (context) => {}, afterCompile: (constext) => {}, }; ================================================ FILE: materials/snippets/axios-request-api/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/axios-request-api/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/axios-request-api/src/template.ejs ================================================ <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params { <% api.req_query.map(query => { %><%= query.name %>: string;<% }) %> <% api.req_params.map(query => { %><%= query.name %>: string;<% }) %> <% api.query_path.params.map(query => { %><%= query.name %>: string;<% }) %> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= rawSelectedText %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> params: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params, <% } _%> <% if (requestBodyType) { %> data: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data <% } %> ) { return requestResult>({ url: `<%= api.query_path.path.replace(/\{/g,"${params.") %>`, method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/config/commandPrompt.ejs ================================================ <%- rawSelectedText || rawClipboardText %> 解释这段代码的意思 ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": false, "notShowInSnippetsList": false, "notShowInintellisense": false, "showInRunSnippetScript": true, "schema": "amis", "scripts": [ { "method": "test", "remark": "测试一下" } ] } ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文, 使用驼峰语法,返回翻译后的 markdown 语法的代码块 ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/axios-request-api-外挂脚本/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/axios-request-api-外挂脚本/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, test: (lowcodeContext) => { lowcodeContext.outputChannel.appendLine(Object.keys(lowcodeContext)); lowcodeContext.outputChannel.appendLine( JSON.stringify(lowcodeContext.model), ); lowcodeContext.outputChannel.appendLine(lowcodeContext.params); return { ...lowcodeContext.model, name: '测试一下' }; }, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/genCode/genCodeByYapi.ts ================================================ import { window, env } from 'vscode'; import { compile } from 'json-schema-to-typescript'; import strip from 'strip-comments'; import jsonminify from 'jsonminify'; import * as GenerateSchema from 'generate-schema'; import { compile as compileEjs, Model } from '../utils/ejs'; import { fetchApiDetailInfo } from '../utils/request'; import { getFuncNameAndTypeName, pasteToEditor } from '../utils/editor'; import { mockFromSchema } from '../utils/json'; import { getConfig } from '../utils/config'; import { getMaterial } from '../utils/material'; import { context } from '../context'; export const genCodeByYapi = async () => { const domain = getConfig().yapi?.domain || ''; if (!domain.trim()) { window.showErrorMessage('请配置yapi域名'); return; } const projectList = getConfig().yapi?.projects || []; if (projectList.length === 0) { window.showErrorMessage('请配置项目'); } const rawClipboardText = await env.clipboard.readText(); if (!rawClipboardText) { window.showErrorMessage('请复制 yapi 接口 id'); return; } const selectInfo = getFuncNameAndTypeName(); const result = await window.showQuickPick( projectList.map((s) => s.name), { placeHolder: '请选择项目' }, ); if (!result) { return; } const project = projectList.find((s) => s.name === result); const { lowcodeContext } = context; const template = getMaterial(lowcodeContext!.materialPath); try { const model = await genTemplateModelByYapi( project?.domain || domain, rawClipboardText, project!.token, selectInfo.typeName, selectInfo.funcName, ); if (model) { model.rawSelectedText = selectInfo.rawSelectedText; model.rawClipboardText = rawClipboardText; const code = compileEjs(template!.template, model); pasteToEditor(code); } } catch (e: any) { window.showErrorMessage(e.toString()); } }; const genTemplateModelByYapi = async ( domain: string, yapiId: string, token: string, typeNameOriginal: string, funcNameOriginal: string, ) => { let funcName = funcNameOriginal; let typeName = typeNameOriginal; const res = await fetchApiDetailInfo(domain, yapiId, token); if (!res.data.data) { throw res.data.errmsg; } funcName = await context.lowcodeContext!.createChatCompletion({ messages: [ { role: 'system', content: `你是一个代码专家,按照用户传给你的 api 接口地址,和接口请求方法,根据接口地址里的信息推测出一个生动形象的方法名称,驼峰格式,返回方法名称`, }, { role: 'user', content: `api 地址:${res.data.data.query_path},${res.data.data.method} 方法,作用是${res.data.data.title}`, }, ], }); typeName = `I${funcName.charAt(0).toUpperCase() + funcName.slice(1)}Result`; const requestBodyTypeName = funcName.slice(0, 1).toUpperCase() + funcName.slice(1); if (res.data.data.res_body_type === 'json') { const schema = JSON.parse(jsonminify(res.data.data.res_body)); fixSchema(schema, ['$ref', '$$ref']); delete schema.title; let ts = await compile(schema, typeName, { bannerComment: '', }); ts = ts.replace(/(\[k: string\]: unknown;)|\?/g, ''); const { mockCode, mockData } = mockFromSchema(schema); let requestBodyType = ''; if (res.data.data.req_body_other) { const reqBodyScheme = JSON.parse(res.data.data.req_body_other); fixSchema(reqBodyScheme, ['$ref', '$$ref']); delete reqBodyScheme.title; requestBodyType = await compile( reqBodyScheme, `I${requestBodyTypeName}Data`, { bannerComment: '', }, ); } const model: Model = { type: ts, requestBodyType: requestBodyType.replace(/\[k: string\]: unknown;/g, ''), funcName, typeName, api: res.data.data, yapiDomain: domain, inputValues: [], mockCode, mockData, jsonData: {}, rawSelectedText: '', rawClipboardText: '', }; return model; } // yapi 返回数据直接贴的 json const resBodyJson = JSON.parse(jsonminify(res.data.data.res_body)); const schema = GenerateSchema.json(typeName || 'Schema', resBodyJson); fixSchema(schema, ['$ref', '$$ref']); let ts = await compile(schema, typeName, { bannerComment: '', }); ts = strip(ts.replace(/(\[k: string\]: unknown;)|\?/g, '')); const { mockCode, mockData } = mockFromSchema(schema); let requestBodyType = ''; if (res.data.data.req_body_other) { const reqBodyScheme = JSON.parse(jsonminify(res.data.data.req_body_other)); fixSchema(reqBodyScheme, ['$ref', '$$ref']); delete reqBodyScheme.title; requestBodyType = await compile( reqBodyScheme, `I${requestBodyTypeName}Data`, { bannerComment: '', }, ); } const model: Model = { type: ts, requestBodyType: requestBodyType.replace(/\[k: string\]: unknown;/g, ''), funcName, typeName, api: res.data.data, yapiDomain: domain, inputValues: [], mockCode, mockData, jsonData: resBodyJson, rawClipboardText: '', rawSelectedText: '', }; return model; }; function fixSchema(obj: object, fieldNames: string[]) { // eslint-disable-next-line no-restricted-syntax for (const key in obj) { if (Array.isArray(obj[key])) { obj[key].forEach((item: object) => { if (typeof item === 'object' && item !== null) { fixSchema(item, fieldNames); } else { // eslint-disable-next-line no-restricted-syntax for (const fieldName of fieldNames) { if (item && item[fieldName]) { delete item[fieldName]; } } } }); } else if (typeof obj[key] === 'object' && obj[key] !== null) { if (obj[key].type === 'object' && !obj[key].properties) { delete obj[key]; } fixSchema(obj[key], fieldNames); } else { // eslint-disable-next-line no-restricted-syntax for (const fieldName of fieldNames) { if (key === fieldName) { delete obj[key]; } } } } } ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/main.ts ================================================ import { genCodeByYapi } from './genCode/genCodeByYapi'; export async function bootstrap() { await genCodeByYapi(); } ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/config.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { workspace } from 'vscode'; import { getFileContent } from './file'; const defaultConfig: Config = { yapi: { projects: [] }, mock: { mockKeyWordEqual: [], mockKeyWordLike: [] }, commonlyUsedBlock: [], }; export type Config = { yapi?: { domain?: string; projects?: { name: string; token: string; domain: string; }[]; }; mock?: { mockNumber?: string; mockBoolean?: string; mockString?: string; mockKeyWordEqual?: { key: string; value: string; }[]; mockKeyWordLike?: { key: string; value: string; }[]; }; commonlyUsedBlock?: string[]; }; export const getConfig: () => Config = () => { let config: Config = {}; if (fs.existsSync(path.join(workspace.rootPath || '', '.lowcoderc'))) { config = JSON.parse(getFileContent('.lowcoderc') || '{}'); config.yapi?.projects?.forEach((s) => { s.domain = s.domain || config.yapi?.domain || ''; }); } return { ...defaultConfig, ...config }; }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/editor.ts ================================================ import { Range, SnippetString, window } from 'vscode'; export const getSelectedText = () => { const { selection, document } = window.activeTextEditor!; return document.getText(selection).trim(); }; export const pasteToEditor = (content: string, isInsertSnippet = true) => { // vscode 本身代码片段语法 if (isInsertSnippet) { return insertSnippet(content); } const { activeTextEditor } = window; if (activeTextEditor === undefined) { throw new Error('无打开文件'); } return activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (activeTextEditor.selection.isEmpty) { editBuilder.insert(activeTextEditor.selection.start, content); } else { editBuilder.replace( new Range( activeTextEditor.selection.start, activeTextEditor.selection.end, ), content, ); } }); }; export const insertSnippet = (content: string) => { const { activeTextEditor } = window; if (activeTextEditor === undefined) { throw new Error('无打开文件'); } return activeTextEditor.insertSnippet(new SnippetString(content)); }; export const getFuncNameAndTypeName = () => { // 这部分代码可以写在模版里,暂时保留 const selectedText = getSelectedText() || ''; let funcName = 'fetch'; let typeName = 'IFetchResult'; if (selectedText) { const splitValue = selectedText.split(' '); funcName = splitValue[0] || funcName; if (splitValue.length > 1 && splitValue[1]) { // eslint-disable-next-line prefer-destructuring typeName = splitValue[1]; } else { typeName = `I${ funcName.charAt(0).toUpperCase() + funcName.slice(1) }Result`; } } return { funcName, typeName, rawSelectedText: selectedText, }; }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/ejs.ts ================================================ import * as ejs from 'ejs'; export type YapiInfo = { query_path: { path: string }; method: string; title: string; project_id: number; req_params: { name: string; desc: string; }[]; _id: number; req_query: { required: '0' | '1'; name: string }[]; res_body_type: 'raw' | 'json'; res_body: string; username: string; }; export type Model = { type: string; requestBodyType?: string; funcName: string; typeName: string; inputValues: string[]; api?: YapiInfo; yapiDomain?: string; mockCode: string; mockData: string; jsonData: any; jsonKeys?: string[]; rawSelectedText: string; // 编辑器中选中的原始文本 rawClipboardText: string; // 系统剪切板中的原始文本 activeTextEditorFilePath?: string; // 当前打开文件地址 createBlockPath?: string; // 创建区块的目录 }; export const compile = (templateString: string, model: Model) => ejs.render(templateString, model); ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/file.ts ================================================ import * as path from 'path'; import * as fs from 'fs'; import { workspace } from 'vscode'; export const getFileContent = (filePath: string, fullPath = false) => { let fileContent = ''; const fileFullPath = fullPath ? filePath : path.join(workspace.rootPath || '', filePath); try { const fileBuffer = fs.readFileSync(fileFullPath); fileContent = fileBuffer.toString(); // eslint-disable-next-line no-empty } catch (error) {} return fileContent; }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/json.ts ================================================ import { getConfig } from './config'; export const mockFromSchema = (schema: any) => { let listIndex = 1; const config = getConfig(); const mockConfig = config.mock; const getMockValue = (key: string, defaultValue: string, type = 'number') => { const value = defaultValue; const mockKeyWordEqualConfig = mockConfig?.mockKeyWordEqual || []; for (let i = 0; i < mockKeyWordEqualConfig.length; i++) { if (key.toUpperCase() === mockKeyWordEqualConfig[i].key.toUpperCase()) { if (typeof mockKeyWordEqualConfig[i].value === 'string') { const array = mockKeyWordEqualConfig[i].value.split('&&'); if (array.length > 1) { if (type === array[1]) { return array[0]; } return value; } } return mockKeyWordEqualConfig[i].value; } } const mockKeyWordLikeConfig = mockConfig?.mockKeyWordLike || []; for (let i = 0; i < mockKeyWordLikeConfig.length; i++) { if ( key.toUpperCase().indexOf(mockKeyWordLikeConfig[i].key.toUpperCase()) > -1 ) { if (typeof mockKeyWordLikeConfig[i].value === 'string') { const array = mockKeyWordLikeConfig[i].value.split('&&'); if (array.length > 1) { if (type === array[1]) { return array[0]; } return value; } } return mockKeyWordLikeConfig[i].value; } } return value; }; const formatProperty = (property: any, key: string = '') => { let jsonStr = ''; let listStr: string[] = []; if (property.type === 'object') { jsonStr += `${key ? `${key}: {` : ''}`; Object.keys(property.properties).map((childPropertyKey) => { const childProperty = property.properties[childPropertyKey]; const { jsonStr: childJsonStr, listStr: childListStr } = formatProperty( childProperty, childPropertyKey, ); jsonStr += childJsonStr; listStr = listStr.concat(childListStr); }); jsonStr += `${key ? '},' : ''}`; } else if (property.type === 'array') { if (Object.keys(property.items).length > 0) { const index = listIndex; listIndex++; let itemStr = ` const list${index}=[]; for(let i = 0; i < 10 ; i++){ list${index}.push( `; if (property.items.type === 'object') { itemStr += '{'; Object.keys(property.items.properties).map((itemPropertyKey) => { const itemProperty = property.items.properties[itemPropertyKey]; const { jsonStr: itemJsonStr, listStr: itemListStr } = formatProperty(itemProperty, itemPropertyKey); itemStr += itemJsonStr; listStr = listStr.concat(itemListStr); }); itemStr += `})}`; } else { if (property.items.type === 'string') { itemStr += getMockValue( key, mockConfig?.mockString || '', 'string', ); } else { itemStr += getMockValue( key, mockConfig?.mockNumber || 'Random.natural(1000,1000)', ); } itemStr += `)}`; } listStr.push(itemStr); jsonStr += `${key}: list${index},`; } else { jsonStr += `${key}: [],`; } } else if (property.type === 'number') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockNumber || 'Random.natural(1000,1000)', )},`; } else if (property.type === 'boolean') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockBoolean || 'false', 'boolean', )},`; } else if (property.type === 'string') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockString || 'Random.cword(5, 7)', 'string', )},`; } return { jsonStr, listStr, }; }; const { jsonStr, listStr } = formatProperty(schema); return { mockCode: listStr.join('\n'), mockData: `{${jsonStr}}`, }; }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/material.ts ================================================ import * as path from 'path'; import { getFileContent } from './file'; export const getMaterial = (materialPath: string) => { let material: { model: object; schema: object; preview: { title?: string; description?: string; img?: string | string[]; category?: string[]; notShowInCommand?: boolean; notShowInSnippetsList?: boolean; notShowInintellisense?: boolean; schema?: string; scripts?: [{ method: string; remark: string }]; }; template: string; commandPrompt: string; viewPrompt: string; } = {} as any; try { const fullPath = path.join(materialPath); let model = {} as any; let schema = {} as any; let preview = { img: '', category: [], schema: 'form-render', chatGPT: { commandPrompt: '', viewPrompt: '' }, }; let template = ''; let commandPrompt = ''; let viewPrompt = ''; try { model = JSON.parse( getFileContent(path.join(fullPath, 'config', 'model.json'), true), ); } catch {} try { schema = JSON.parse( getFileContent(path.join(fullPath, 'config', 'schema.json'), true), ); } catch {} try { preview = JSON.parse( getFileContent(path.join(fullPath, 'config', 'preview.json'), true), ); } catch {} try { commandPrompt = getFileContent( path.join(fullPath, 'config', 'commandPrompt.ejs'), true, ); } catch {} try { viewPrompt = getFileContent( path.join(fullPath, 'config', 'viewPrompt.ejs'), true, ); } catch {} if (!preview.img) { preview.img = 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg'; } if (!preview.schema) { preview.schema = 'form-render'; } try { template = getFileContent( path.join(fullPath, 'src', 'template.ejs'), true, ); } catch {} if (schema.formSchema) { if (schema.formSchema.formData) { model = schema.formSchema.formData; } schema = schema.formSchema.schema; } if (Object.keys(schema).length > 0 && preview.schema === 'amis') { // 设置 page 默认 name schema.name = 'page'; if (schema.body && Array.isArray(schema.body)) { schema.body.forEach((s: Record) => { if (s.type === 'form') { s.name = 'form'; if (s.data && Object.keys(model).length === 0) { model = s.data; } else if (!s.data && Object.keys(model).length > 0) { s.data = model; } } }); } } material = { model, schema, preview, template, commandPrompt, viewPrompt, }; } catch {} return material; }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/script/src/utils/request.ts ================================================ import axios from 'axios'; const https = require('https'); const agent = new https.Agent({ rejectUnauthorized: true, }); https.globalAgent.options.rejectUnauthorized = false; interface IApiDetailInfo { data: { query_path: { path: string }; path: string; method: string; title: string; project_id: number; req_params: { name: string; desc: string; }[]; _id: number; req_query: { required: '0' | '1'; name: string }[]; res_body_type: 'raw' | 'json'; res_body: string; req_body_other: string; username: string; }; errmsg?: string; } export const fetchApiDetailInfo = ( domain: string, id: string, token: string, ) => { const url = domain.endsWith('/') ? `${domain}api/interface/get?id=${id}&token=${token}` : `${domain}/api/interface/get?id=${id}&token=${token}`; return axios.get(url, { httpsAgent: agent }); }; ================================================ FILE: materials/snippets/axios-request-api-外挂脚本/src/template.ejs ================================================ // #region <%= api.title %> <%- type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.map(query => { %><%= query.name %>: string;<% }) %> <% api.req_params.map(query => { %><%= query.name %>: string;<% }) %> <% api.query_path.params.map(query => { %><%= query.name %>: string;<% }) %> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { %> <%- requestBodyType %> <% } %> /** * <%= api.title %> * <%= yapiDomain %>/project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType) { %> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } %> ) { return requestResult>({ url: `<%= api.query_path.path.replace(/\{/g,"${params.") %>`, method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } // #endregion ================================================ FILE: materials/snippets/form-render/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/form-render/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": false, "notShowInSnippetsList": false, "notShowInintellisense": false, "schema": "form-render", "scripts": [ { "method": "test", "remark": "测试一下" } ] } ================================================ FILE: materials/snippets/form-render/config/schema.json ================================================ { "formSchema": { "schema": { "type": "object", "column": 1, "displayType": "column", "properties": { "name": { "title": "测试表单", "type": "string", "props": {} } } } } } ================================================ FILE: materials/snippets/form-render/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文, 使用驼峰语法,返回翻译后的 markdown 语法的代码块 ================================================ FILE: materials/snippets/form-render/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/form-render/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/form-render/script/src/context'); module.exports = { beforeCompile: (context) => { context.outputChannel.appendLine('compile form-render start'); }, afterCompile: (context) => { context.outputChannel.appendLine('compile form-render end'); }, test: (context) => { context.outputChannel.appendLine(Object.keys(context)); context.outputChannel.appendLine(JSON.stringify(context.model)); context.outputChannel.appendLine(context.params); return { ...context.model, name: '测试一下' }; }, }; ================================================ FILE: materials/snippets/form-render/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/form-render/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/form-render/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/formily/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/formily/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": false, "notShowInSnippetsList": false, "notShowInintellisense": false, "schema": "formily", "scripts": [ { "method": "test", "remark": "测试一下" } ] } ================================================ FILE: materials/snippets/formily/config/schema.json ================================================ { "formSchema": { "schema": { "form": { "labelCol": 6, "wrapperCol": 12, "layout": "vertical", "labelAlign": "left", "fullness": false, "inset": false }, "schema": { "type": "object", "properties": { "name": { "type": "string", "title": "测试表单", "x-decorator": "FormItem", "x-component": "Input", "x-validator": [], "x-component-props": {}, "x-decorator-props": {}, "x-designable-id": "v3zwx2xtcfx", "x-index": 0, "name": "name" } }, "x-designable-id": "d4ogui2afmr" } } } } ================================================ FILE: materials/snippets/formily/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文, 使用驼峰语法,返回翻译后的 markdown 语法的代码块 ================================================ FILE: materials/snippets/formily/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/formily/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/formily/script/src/context'); module.exports = { beforeCompile: (context) => { context.outputChannel.appendLine('compile formily start'); }, afterCompile: (context) => { context.outputChannel.appendLine('compile formily end'); }, test: (context) => { context.outputChannel.appendLine(Object.keys(context)); context.outputChannel.appendLine(JSON.stringify(context.model)); context.outputChannel.appendLine(context.params); return { ...context.model, name: '测试一下' }; }, }; ================================================ FILE: materials/snippets/formily/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/formily/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/formily/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/share ChatGPT 测试/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/share ChatGPT 测试/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/share ChatGPT 测试/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/share ChatGPT 测试/config/schema.ts ================================================ export type IColumns = { /** * @description 保持原始内容,不需要处理,不要翻译 * @type {string} */ title: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ dataIndex: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; slots: { /** * @description 翻译成英文,驼峰格式 * @type {string} */ customRender: string; }; }[]; ================================================ FILE: materials/snippets/share ChatGPT 测试/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/share ChatGPT 测试/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/share ChatGPT 测试/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/share ChatGPT 测试/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/share ChatGPT 测试/script/src/main.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { window, Range } from 'vscode'; import { generalBasic } from '@share/BaiduOCR/index'; import { translate } from '@share/TypeChatSlim/index'; import { createChatCompletion } from '@share/LLM/openai'; import { context } from './context'; import { IColumns } from '../../config/schema'; const systemPrompt = `# Role: Tailwind CSS Developer ## Task - Input: Screenshot(s) of a reference web page or Low-fidelity - Output: Single HTML page using Tailwind CSS, HTML ## Guidelines - Utilize Tailwind CSS to develop the website based on the provided screenshot or Low-fidelity - Achieve an exact visual match to the provided screenshot or Low-fidelity - Pay close attention to: - Background color - Text color - Font size - Font family - Padding - Margin - Border - Use the precise text from the screenshot - Avoid placeholder comments; write the full code - Repeat elements as shown in the screenshot (e.g., if there are 15 items, include 15 items in the code) - Use placeholder images from \`https://placehold.co\` with descriptive \`alt\` text for future image generation ## Libraries - Include Tailwind CSS via: \`\` ## Deliverable - Respond with the complete HTML code within \`\` tags - Respond with the HTML file content only `; export async function bootstrap() { const { lowcodeContext } = context; const res = await createChatCompletion({ model: 'gpt-4-vision-preview', maxTokens: 4096, messages: [ { role: 'system', content: systemPrompt, }, { role: 'user', content: [ { type: 'image_url', image_url: { url: 'https://i.imgur.com/fHpqvC9.png' }, }, { type: 'text', text: 'Turn this into a single html file using tailwind.', }, ], }, ], handleChunk(data) { lowcodeContext?.log.append(data.text || ''); }, }); } ================================================ FILE: materials/snippets/share ChatGPT 测试/src/template.ejs ================================================ ================================================ FILE: materials/snippets/start nest api server/config/commandPrompt.ejs ================================================ <%- rawSelectedText || rawClipboardText %> 解释这段代码的意思 ================================================ FILE: materials/snippets/start nest api server/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/start nest api server/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": false, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [ { "method": "test", "remark": "测试一下" } ] } ================================================ FILE: materials/snippets/start nest api server/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/start nest api server/config/viewPrompt.ejs ================================================ <%- model %> 将这段 json 中,中文 key 翻译为英文, 使用驼峰语法,返回翻译后的 markdown 语法的代码块 ================================================ FILE: materials/snippets/start nest api server/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/start nest api server/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/start nest api server/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => { lowcodeContext.outputChannel.appendLine( 'compile start nest api srver start', ); }, afterCompile: (lowcodeContext) => { lowcodeContext.outputChannel.appendLine('compile start nest api srver end'); }, test: (lowcodeContext) => { lowcodeContext.outputChannel.appendLine(Object.keys(lowcodeContext)); lowcodeContext.outputChannel.appendLine( JSON.stringify(lowcodeContext.model), ); lowcodeContext.outputChannel.appendLine(lowcodeContext.params); return { ...lowcodeContext.model, name: '测试一下' }; }, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/start nest api server/script/src/app.controller.ts ================================================ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @Get() getMaterialPath() { return this.appService.getMaterialPath(); } } ================================================ FILE: materials/snippets/start nest api server/script/src/app.module.ts ================================================ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ ConfigModule.forRoot({ envFilePath: `.env.${process.env.NODE_ENV}`, isGlobal: true, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {} ================================================ FILE: materials/snippets/start nest api server/script/src/app.service.ts ================================================ import { Injectable } from '@nestjs/common'; import { context } from './context'; @Injectable() export class AppService { getMaterialPath() { return context.lowcodeContext?.materialPath; } } ================================================ FILE: materials/snippets/start nest api server/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/start nest api server/script/src/main.ts ================================================ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { context } from './context'; export async function bootstrap() { const lowcodeContext = context.lowcodeContext!; if (!context.statusBarItem) { const statusBarItem = lowcodeContext.vscode.window.createStatusBarItem( lowcodeContext.vscode.StatusBarAlignment.Left, ); context.statusBarItem = statusBarItem; } if (!context.nestApp) { context.statusBarItem.text = '$(loading~spin) Start nest api server...'; context.statusBarItem.show(); const app = await NestFactory.create(AppModule); await app .listen(3000) .then(() => { context.nestApp = app; if (context.statusBarItem) { context.statusBarItem.text = '$(circle-slash) Low Code Server Port : 3000'; context.statusBarItem.tooltip = 'Click to close nest api server'; // vscode 限制命令唯一,但是唯一的话命令回调里的 context 永远是同一个 const command = `lowcode.StopNestApiServer${new Date().getTime()}`; try { const dsisposable = lowcodeContext.vscode.commands.registerCommand( command, () => { context.statusBarItem!.text = '$(loading~spin) close...'; context.statusBarItem!.command = undefined; context.nestApp?.close().then(() => { context.nestApp = undefined; context.statusBarItem?.hide(); context.statusBarItem?.dispose(); context.statusBarItem = undefined; dsisposable.dispose(); }); }, ); } catch (ex) { /* empty */ } context.statusBarItem.command = command; } }) .catch((ex) => { context.statusBarItem?.hide(); context.statusBarItem?.dispose(); context.statusBarItem = undefined; throw ex; }); } } ================================================ FILE: materials/snippets/start nest api server/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/taro-request-api/config/model.json ================================================ {} ================================================ FILE: materials/snippets/taro-request-api/config/preview.json ================================================ { "title": "taro-request-api", "description": "通过 yapi 接口信息生成接口请求方法", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/taro-request-api/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/taro-request-api/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/taro-request-api/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/taro-request-api/script/src/context'); module.exports = { beforeCompile: (context) => {}, afterCompile: (constext) => {}, }; ================================================ FILE: materials/snippets/taro-request-api/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/taro-request-api/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/taro-request-api/src/template.ejs ================================================ <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.map(query => { %><%= query.name %>: string;<% }) %> <% api.req_params.map(query => { %><%= query.name %>: string;<% }) %> <% api.query_path.params.map(query => { %><%= query.name %>: string;<% }) %> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { _%> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { _%> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } _%> ) { return request<<%= typeName %>>(`<%= api.query_path.path.replace(/\{/g,"${params.") %>`, { method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } ================================================ FILE: materials/snippets/umi-request-api/config/model.json ================================================ {} ================================================ FILE: materials/snippets/umi-request-api/config/preview.json ================================================ { "title": "umi-request-api", "description": "通过 yapi 接口信息生成接口请求方法", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/umi-request-api/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/umi-request-api/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/umi-request-api/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/umi-request-api/script/src/context'); module.exports = { beforeCompile: (context) => {}, afterCompile: (constext) => {}, }; ================================================ FILE: materials/snippets/umi-request-api/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/umi-request-api/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/umi-request-api/src/template.ejs ================================================ <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.map(query => { %><%= query.name %>: string;<% }) %> <% api.req_params.map(query => { %><%= query.name %>: string;<% }) %> <% api.query_path.params.map(query => { %><%= query.name %>: string;<% }) %> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { _%> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { _%> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } _%> ) { return request<<%= typeName %>>(`<%= api.query_path.path.replace(/\{/g,"${params.") %>`, { method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } ================================================ FILE: materials/snippets/动态表单 demo/config/model.json ================================================ { "filters": [], "columns": [], "pagination": { "show": true, "page": "page", "size": "size", "total": "result.total" }, "includeModifyModal": false, "fetchName": "fetchTableList", "result": "[\"result\"][\"records\"]", "serviceName": "getTableList" } ================================================ FILE: materials/snippets/动态表单 demo/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [ { "method": "initFiltersFromImage", "remark": "使用 OCR 识别查询条件截图内容" }, { "method": "initFiltersFromText", "remark": "使用文本初始化查询条件" }, { "method": "initColumnsFromText", "remark": "使用文本初始化表格" }, { "method": "initColumnsFromImage", "remark": "使用截图初始化表格" }, { "method": "askChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的指定中文字段" } ] } ================================================ FILE: materials/snippets/动态表单 demo/config/schema.json ================================================ { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "combo", "label": "查询条件", "name": "filters", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "" }, { "type": "select", "name": "component", "placeholder": "选项", "options": [ { "label": "input", "value": "input" }, { "label": "select", "value": "select" } ], "id": "u:995915eabcca", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "label": "placeholder", "name": "placeholder", "id": "u:d7f1a8a39449", "description": "" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false }, { "type": "combo", "label": "表格", "name": "columns", "multiple": true, "addable": true, "removable": true, "removableMode": "button", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:1e8070edc3d3" }, "items": [ { "type": "input-text", "name": "title", "id": "u:152dd82b82f9", "label": "title" }, { "type": "input-text", "label": "dataIndex", "name": "dataIndex", "id": "u:ecc7298e0550", "description": "" }, { "type": "input-text", "label": "key", "name": "key", "id": "u:fbaa95c3f15d", "description": "" }, { "type": "input-text", "label": "width", "name": "width", "id": "u:b143127e097b", "description": "" }, { "type": "switch", "label": "自定义插槽", "option": "", "name": "slot", "falseValue": false, "trueValue": true, "id": "u:ee1ce1faee0b", "value": false } ], "id": "u:9b9fb0cf38f9", "strictMode": true, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "deleteBtn": { "label": "删除", "level": "default" }, "tabsLabelTpl": "列${index+1}" }, { "type": "fieldset", "title": "分页参数", "collapsable": true, "body": [ { "type": "switch", "label": "是否分页", "option": "", "name": "pagination.show", "falseValue": false, "trueValue": true, "id": "u:6c70041d5143", "value": true, "className": "" }, { "type": "input-text", "label": "查询接口页数参数字段名", "name": "pagination.page", "id": "u:cbbf6853cf64", "value": "page" }, { "type": "input-text", "label": "查询接口每页数据行数参数字段名", "name": "pagination.size", "id": "u:a8fae66fa927", "value": "size" }, { "type": "input-text", "label": "接口返回总数据量字段 PATH", "name": "pagination.total", "id": "u:e1cd979c7ee8", "value": "result.total", "themeCss": { "inputControlClassName": { "padding-and-margin:default": { "marginBottom": "", "marginTop": "", "marginRight": "", "marginLeft": "" } } } } ], "id": "u:0f1bd8fc2f2b", "collapsed": true, "headingClassName": "", "bodyClassName": "p" }, { "type": "fieldset", "title": "请求方法", "collapsable": true, "body": [ { "type": "input-text", "label": "请求名称", "name": "fetchName", "id": "u:a3e712484fae", "value": "fetchTableList", "description": "追加了YAPI数据则不使用此参数", "themeCss": { "labelClassName": { "padding-and-margin:default": { "marginTop": "", "marginRight": "", "marginBottom": "", "marginLeft": "" } } }, "labelClassName": "labelClassName-a3e712484fae" }, { "type": "input-text", "label": "接口数据字段 PATH", "name": "result", "id": "u:8c082acf7db2", "value": "[\"result\"][\"records\"]", "description": "" }, { "type": "input-text", "label": "service方法名", "name": "serviceName", "id": "u:cfbbdd07366b", "value": "getTableList", "description": "" } ], "id": "u:382f8cdf59a6", "collapsed": true, "className": "", "headingClassName": "", "bodyClassName": "p-r p-l p-b" }, { "type": "fieldset", "title": "新增/编辑弹框", "collapsable": true, "body": [ { "type": "switch", "label": "是否包含弹框", "option": "", "name": "includeModifyModal", "falseValue": false, "trueValue": true, "id": "u:03957070af9e", "value": false }, { "type": "combo", "label": "表单项", "name": "modifyModal.formItems", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "strictMode": false, "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:86cc27b6a663" }, "items": [ { "type": "input-text", "name": "key", "id": "u:62cc1cf36c73", "label": "字段名(key)" }, { "type": "select", "name": "type", "options": [ { "label": "string", "value": "string" }, { "label": "number", "value": "number" }, { "label": "boolean", "value": "boolean" }, { "label": "Dayjs", "value": "Dayjs" }, { "label": "string[]", "value": "string[]" }, { "label": "number[]", "value": "number[]" }, { "label": "boolean[]", "value": "boolean[]" }, { "label": "[Dayjs,Dayjs]", "value": "[Dayjs,Dayjs]" } ], "id": "u:b165c75e5e1a", "multiple": false, "label": "字段类型", "value": "" }, { "type": "switch", "label": "字段可选", "option": "", "name": "optional", "falseValue": false, "trueValue": true, "id": "u:68fc4c85fb03", "value": false, "description": "字段名字后加?" }, { "type": "select", "name": "defaultValue", "options": [ { "label": "\"\"", "value": "\"\"" }, { "label": "false", "value": "false" }, { "label": "true", "value": "true" }, { "label": "0", "value": "0" }, { "label": "undefined", "value": "undefined" }, { "label": "[]", "value": "[]" } ], "id": "u:379ea92fb3c6", "multiple": false, "label": "默认值", "value": "" }, { "type": "select", "name": "component", "options": [ { "label": "input", "value": "input" }, { "label": "input-password", "value": "input-password" }, { "label": "input-number", "value": "input-number" }, { "label": "textarea", "value": "textarea" }, { "label": "select", "value": "select" }, { "label": "radio-group", "value": "radio-group" }, { "label": "checkbox-group", "value": "checkbox-group" }, { "label": "switch", "value": "switch" }, { "label": "date-picker", "value": "date-picker" }, { "label": "time-ticker", "value": "time-picker" }, { "label": "range-picker", "value": "range-picker" }, { "label": "transfer", "value": "transfer" } ], "id": "u:7932ea3b05da", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "name": "label", "id": "u:5bb237f20098", "label": "label" }, { "type": "input-text", "name": "placeholder", "id": "u:580898257491", "label": "placeholder" }, { "type": "switch", "label": "required", "option": "", "name": "required", "falseValue": false, "trueValue": true, "id": "u:559dbdbb01da", "value": false, "description": "验证规则加required" }, { "type": "input-text", "name": "message", "id": "u:55013279d659", "label": "校验失败 message", "value": "不能为空" }, { "type": "switch", "label": "更多组件配置", "option": "", "name": "showMore", "falseValue": false, "trueValue": true, "id": "u:67e0cb5b7496", "value": false, "description": "" }, { "type": "switch", "label": "labelInValue", "option": "", "name": "labelInValue", "falseValue": false, "trueValue": true, "id": "u:7fd6f1b233d9", "value": false, "description": "是否把每个选项的 label 包装到 value 中", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "select", "name": "mode", "options": [ { "label": "multiple", "value": "multiple" }, { "label": "tags", "value": "tags" } ], "multiple": false, "label": "mode", "value": "", "description": "设置 Select 的模式为多选或标签", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "optionFilterProp", "label": "optionFilterProp", "description": "搜索时过滤对应的 option 属性", "value": "label", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "showSearch", "option": "", "name": "showSearch", "falseValue": false, "trueValue": true, "value": false, "description": "使单选模式可搜索", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "hideArrow", "option": "", "name": "hideArrow", "falseValue": false, "trueValue": true, "value": false, "description": "是否隐藏下拉小箭头", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "maxlength", "label": "maxlength", "description": "最大长度", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "switch", "label": "showCount", "option": "", "name": "showCount", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示字数", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "input-text", "name": "max", "label": "max", "description": "最大值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "min", "label": "min", "description": "最小值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "step", "label": "step", "description": "每次改变步数,可以为小数", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "checkedChildren", "label": "checkedChildren", "description": "选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedChildren", "label": "unCheckedChildren", "description": "非选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "checkedValue", "label": "checkedValue", "description": "选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedValue", "label": "unCheckedValue", "description": "非选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "select", "name": "picker", "options": [ { "label": "date", "value": "date" }, { "label": "week", "value": "week" }, { "label": "month", "value": "month" }, { "label": "quarter", "value": "quarter" }, { "label": "year", "value": "year" } ], "multiple": false, "label": "picker", "description": "设置选择器类型", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker')}" }, { "type": "switch", "label": "showTime", "name": "showTime", "falseValue": false, "trueValue": true, "value": false, "description": "增加时间选择功能", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showNow", "name": "showNow", "falseValue": false, "trueValue": true, "value": false, "description": "当设定了 showTime 的时候,面板是否显示“此刻”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showToday", "name": "showToday", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示“今天”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" } ], "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false, "hiddenOn": "${!includeModifyModal}" } ], "bodyClassName": "p", "collapsed": true } ], "submitText": "" } ], "pullRefresh": { "disabled": true }, "regions": ["body"], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false } ================================================ FILE: materials/snippets/动态表单 demo/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/动态表单 demo/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/动态表单 demo/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/动态表单 demo/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/动态表单 demo/script/src/controller.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { IMessage } from '@share/WebView/type'; type RunDynamicFormScript = ( message: IMessage<{ method: string; params: string; model: object; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => Promise<{ /** 立即更新 model */ updateModelImmediately: boolean; /** 仅更新参数 */ onlyUpdateParams: boolean; params?: string; model: object; }>; export const runDynamicFormScript: RunDynamicFormScript = async ( message, lowcodeContext, ) => { return Promise.resolve({ model: message.data.model, updateModelImmediately: false, onlyUpdateParams: true, params: `执行了方法:${message.data.method}`, }); }; ================================================ FILE: materials/snippets/动态表单 demo/script/src/main.ts ================================================ import { window } from 'vscode'; import { showWebView } from '@share/WebView'; import { routes } from './routes'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; showWebView({ key: 'main', title: '动态表单', viewColumn: 3, task: { task: 'route', data: { path: '/dynamicForm', materialPath: lowcodeContext?.materialPath, }, }, lowcodeContext: { ...lowcodeContext!, }, htmlForWebview: getHtmlForWebview(false), routes, }); } const getHtmlForWebview = (dev = false) => { if (dev) { return `
`; } return `
`; }; ================================================ FILE: materials/snippets/动态表单 demo/script/src/routes.ts ================================================ import * as controller from './controller'; export const routes: Record = { runDynamicFormScript: controller.runDynamicFormScript, }; ================================================ FILE: materials/snippets/动态表单 demo/src/template.ejs ================================================ 代码片段 ================================================ FILE: materials/snippets/当前目录翻译成英文/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/当前目录翻译成英文/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": false, "showInRunSnippetScriptOnExplorer": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/当前目录翻译成英文/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/当前目录翻译成英文/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/当前目录翻译成英文/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/当前目录翻译成英文/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/当前目录翻译成英文/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/当前目录翻译成英文/script/src/main.ts ================================================ import * as path from 'path'; import * as vscode from 'vscode'; import * as fs from 'fs-extra'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const explorerSelectedPath = path .join(lowcodeContext?.explorerSelectedPath || '') .replace(/\\/g, '/'); const explorerSelectedPathArr = explorerSelectedPath.split('/'); const name = explorerSelectedPathArr.pop(); vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, }, async (progress) => { progress.report({ message: `loading`, }); let content = await context.lowcodeContext!.createChatCompletion({ messages: [ { role: 'system', content: `你是一个翻译家,你的目标是把中文翻译成英文单词,请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面用户输入的内容`, }, { role: 'user', content: name || '', }, ], }); content = content.charAt(0).toLowerCase() + content.slice(1); fs.renameSync( path.join(lowcodeContext?.explorerSelectedPath || ''), path.join(explorerSelectedPathArr.join('/'), content), ); }, ); } ================================================ FILE: materials/snippets/快速创建区块/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/快速创建区块/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": false, "showInRunSnippetScriptOnExplorer": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/快速创建区块/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/快速创建区块/config/viewPrompt.ejs ================================================ ================================================ FILE: materials/snippets/快速创建区块/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/快速创建区块/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/快速创建区块/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/快速创建区块/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/快速创建区块/script/src/main.ts ================================================ import * as path from 'path'; import { window } from 'vscode'; import * as fs from 'fs-extra'; import { renderEjsTemplates } from '@share/utils/ejs'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const result = await window.showQuickPick( [ 'uniapp/vue3-mvp', 'uniapp/vue3-mvp emit', 'uniapp/vue3-mvp props', 'uniapp/vue3-mvp props emit', 'lowcode/代码片段', 'uTools 自动化脚本', 'uTools 动态表单', 'uTools askChatGPT', ].map((s) => s), { placeHolder: '请选择模板' }, ); if (!result) { return; } const tempWorkPath = path.join( lowcodeContext?.env.rootPath || '', '.lowcode', ); fs.copySync(path.join(lowcodeContext?.materialPath || ''), tempWorkPath); await renderEjsTemplates( { createBlockPath: path .join(lowcodeContext?.explorerSelectedPath || '') .replace(/\\/g, '/'), }, path.join(tempWorkPath, 'src'), ); fs.copySync( path.join(tempWorkPath, 'src', result), path.join(lowcodeContext?.explorerSelectedPath || ''), ); fs.removeSync(tempWorkPath); } ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/config/commandPrompt.ejs.ejs ================================================ Ask ChatGPT With Template 内容 可用变量: rawSelectedText rawClipboardText ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/script/index.js.ejs ================================================ require('ts-node').register({ transpileOnly: true, typeCheck: false, emit: false, compilerHost: false, // 和 emit 一起设置为 true,会在 .ts-node 文件夹输出编译后的代码 cwd: __dirname, // 要输出编译后代码必须配置,否则会报错 EROFS: read-only file system, mkdir '/.ts-node'。不输出也要配置不然会出现各种奇奇怪怪的报错 }); // 清除缓存,保证每次修改代码后实时生效,否则要重新打开 vscode const { clearCache } = require('@share/clearCache.ts'); clearCache(__dirname); // 调试的时候才打开,不然会很慢 const main = require('./src/main.ts'); const { context } = require('./src/context.ts'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/script/src/context.ts.ejs ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/script/src/main.ts.ejs ================================================ export function bootstrap() {} ================================================ FILE: materials/snippets/快速创建区块/src/lowcode/代码片段/src/template.ejs.ejs ================================================ 代码片段 ================================================ FILE: materials/snippets/快速创建区块/src/uTools askChatGPT/script/index.ts.ejs ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: materials/snippets/快速创建区块/src/uTools askChatGPT/script/src/main.ts.ejs ================================================ import { LLMMessage } from '@share/uTools/webviewBaseController'; import { askChatGPT as askOpenai } from '@share/utils/uTools'; export const bootstrap = async (scriptFile?: string) => { const requestPrompt = '你好'; const content = await askOpenai({ messages: [{ role: 'user', content: requestPrompt }], handleChunk: () => {}, }); utools.outPlugin(); utools.hideMainWindowPasteText(content.content); }; // 给页面调用的 export const askChatGPT = (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; }) => askOpenai({ ...data }); ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/config/config.json ================================================ { "scripts": [ { "method": "initFiltersFromImage", "remark": "使用 OCR 识别查询条件截图内容" }, { "method": "initFiltersFromText", "remark": "使用文本初始化查询条件" }, { "method": "initColumnsFromText", "remark": "使用文本初始化表格" }, { "method": "openChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的指定中文字段" }, { "method": "generateCode", "remark": "生成代码" } ] } ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/config/model.json ================================================ { "filters": [], "columns": [], "pagination": { "show": true, "page": "page", "size": "size", "total": "result.total" }, "includeModifyModal": false, "fetchName": "fetchTableList", "result": "[\"result\"][\"records\"]", "serviceName": "getTableList" } ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/config/schema.json ================================================ { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "combo", "label": "查询条件", "name": "filters", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "" }, { "type": "select", "name": "component", "placeholder": "选项", "options": [ { "label": "input", "value": "input" }, { "label": "select", "value": "select" } ], "id": "u:995915eabcca", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "label": "placeholder", "name": "placeholder", "id": "u:d7f1a8a39449", "description": "" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false }, { "type": "combo", "label": "表格", "name": "columns", "multiple": true, "addable": true, "removable": true, "removableMode": "button", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:1e8070edc3d3" }, "items": [ { "type": "input-text", "name": "title", "id": "u:152dd82b82f9", "label": "title" }, { "type": "input-text", "label": "dataIndex", "name": "dataIndex", "id": "u:ecc7298e0550", "description": "" }, { "type": "input-text", "label": "key", "name": "key", "id": "u:fbaa95c3f15d", "description": "" }, { "type": "input-text", "label": "width", "name": "width", "id": "u:b143127e097b", "description": "" }, { "type": "switch", "label": "自定义插槽", "option": "", "name": "slot", "falseValue": false, "trueValue": true, "id": "u:ee1ce1faee0b", "value": false } ], "id": "u:9b9fb0cf38f9", "strictMode": true, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "deleteBtn": { "label": "删除", "level": "default" }, "tabsLabelTpl": "列${index+1}" }, { "type": "fieldset", "title": "分页参数", "collapsable": true, "body": [ { "type": "switch", "label": "是否分页", "option": "", "name": "pagination.show", "falseValue": false, "trueValue": true, "id": "u:6c70041d5143", "value": true, "className": "" }, { "type": "input-text", "label": "查询接口页数参数字段名", "name": "pagination.page", "id": "u:cbbf6853cf64", "value": "page" }, { "type": "input-text", "label": "查询接口每页数据行数参数字段名", "name": "pagination.size", "id": "u:a8fae66fa927", "value": "size" }, { "type": "input-text", "label": "接口返回总数据量字段 PATH", "name": "pagination.total", "id": "u:e1cd979c7ee8", "value": "result.total", "themeCss": { "inputControlClassName": { "padding-and-margin:default": { "marginBottom": "", "marginTop": "", "marginRight": "", "marginLeft": "" } } } } ], "id": "u:0f1bd8fc2f2b", "collapsed": true, "headingClassName": "", "bodyClassName": "p" }, { "type": "fieldset", "title": "请求方法", "collapsable": true, "body": [ { "type": "input-text", "label": "请求名称", "name": "fetchName", "id": "u:a3e712484fae", "value": "fetchTableList", "description": "追加了YAPI数据则不使用此参数", "themeCss": { "labelClassName": { "padding-and-margin:default": { "marginTop": "", "marginRight": "", "marginBottom": "", "marginLeft": "" } } }, "labelClassName": "labelClassName-a3e712484fae" }, { "type": "input-text", "label": "接口数据字段 PATH", "name": "result", "id": "u:8c082acf7db2", "value": "[\"result\"][\"records\"]", "description": "" }, { "type": "input-text", "label": "service方法名", "name": "serviceName", "id": "u:cfbbdd07366b", "value": "getTableList", "description": "" } ], "id": "u:382f8cdf59a6", "collapsed": true, "className": "", "headingClassName": "", "bodyClassName": "p-r p-l p-b" }, { "type": "fieldset", "title": "新增/编辑弹框", "collapsable": true, "body": [ { "type": "switch", "label": "是否包含弹框", "option": "", "name": "includeModifyModal", "falseValue": false, "trueValue": true, "id": "u:03957070af9e", "value": false }, { "type": "combo", "label": "表单项", "name": "modifyModal.formItems", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "strictMode": false, "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:86cc27b6a663" }, "items": [ { "type": "input-text", "name": "key", "id": "u:62cc1cf36c73", "label": "字段名(key)" }, { "type": "select", "name": "type", "options": [ { "label": "string", "value": "string" }, { "label": "number", "value": "number" }, { "label": "boolean", "value": "boolean" }, { "label": "Dayjs", "value": "Dayjs" }, { "label": "string[]", "value": "string[]" }, { "label": "number[]", "value": "number[]" }, { "label": "boolean[]", "value": "boolean[]" }, { "label": "[Dayjs,Dayjs]", "value": "[Dayjs,Dayjs]" } ], "id": "u:b165c75e5e1a", "multiple": false, "label": "字段类型", "value": "" }, { "type": "switch", "label": "字段可选", "option": "", "name": "optional", "falseValue": false, "trueValue": true, "id": "u:68fc4c85fb03", "value": false, "description": "字段名字后加?" }, { "type": "select", "name": "defaultValue", "options": [ { "label": "\"\"", "value": "\"\"" }, { "label": "false", "value": "false" }, { "label": "true", "value": "true" }, { "label": "0", "value": "0" }, { "label": "undefined", "value": "undefined" }, { "label": "[]", "value": "[]" } ], "id": "u:379ea92fb3c6", "multiple": false, "label": "默认值", "value": "" }, { "type": "select", "name": "component", "options": [ { "label": "input", "value": "input" }, { "label": "input-password", "value": "input-password" }, { "label": "input-number", "value": "input-number" }, { "label": "textarea", "value": "textarea" }, { "label": "select", "value": "select" }, { "label": "radio-group", "value": "radio-group" }, { "label": "checkbox-group", "value": "checkbox-group" }, { "label": "switch", "value": "switch" }, { "label": "date-picker", "value": "date-picker" }, { "label": "time-ticker", "value": "time-picker" }, { "label": "range-picker", "value": "range-picker" }, { "label": "transfer", "value": "transfer" } ], "id": "u:7932ea3b05da", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "name": "label", "id": "u:5bb237f20098", "label": "label" }, { "type": "input-text", "name": "placeholder", "id": "u:580898257491", "label": "placeholder" }, { "type": "switch", "label": "required", "option": "", "name": "required", "falseValue": false, "trueValue": true, "id": "u:559dbdbb01da", "value": false, "description": "验证规则加required" }, { "type": "input-text", "name": "message", "id": "u:55013279d659", "label": "校验失败 message", "value": "不能为空" }, { "type": "switch", "label": "更多组件配置", "option": "", "name": "showMore", "falseValue": false, "trueValue": true, "id": "u:67e0cb5b7496", "value": false, "description": "" }, { "type": "switch", "label": "labelInValue", "option": "", "name": "labelInValue", "falseValue": false, "trueValue": true, "id": "u:7fd6f1b233d9", "value": false, "description": "是否把每个选项的 label 包装到 value 中", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "select", "name": "mode", "options": [ { "label": "multiple", "value": "multiple" }, { "label": "tags", "value": "tags" } ], "multiple": false, "label": "mode", "value": "", "description": "设置 Select 的模式为多选或标签", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "optionFilterProp", "label": "optionFilterProp", "description": "搜索时过滤对应的 option 属性", "value": "label", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "showSearch", "option": "", "name": "showSearch", "falseValue": false, "trueValue": true, "value": false, "description": "使单选模式可搜索", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "hideArrow", "option": "", "name": "hideArrow", "falseValue": false, "trueValue": true, "value": false, "description": "是否隐藏下拉小箭头", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "maxlength", "label": "maxlength", "description": "最大长度", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "switch", "label": "showCount", "option": "", "name": "showCount", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示字数", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "input-text", "name": "max", "label": "max", "description": "最大值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "min", "label": "min", "description": "最小值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "step", "label": "step", "description": "每次改变步数,可以为小数", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "checkedChildren", "label": "checkedChildren", "description": "选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedChildren", "label": "unCheckedChildren", "description": "非选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "checkedValue", "label": "checkedValue", "description": "选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedValue", "label": "unCheckedValue", "description": "非选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "select", "name": "picker", "options": [ { "label": "date", "value": "date" }, { "label": "week", "value": "week" }, { "label": "month", "value": "month" }, { "label": "quarter", "value": "quarter" }, { "label": "year", "value": "year" } ], "multiple": false, "label": "picker", "description": "设置选择器类型", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker')}" }, { "type": "switch", "label": "showTime", "name": "showTime", "falseValue": false, "trueValue": true, "value": false, "description": "增加时间选择功能", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showNow", "name": "showNow", "falseValue": false, "trueValue": true, "value": false, "description": "当设定了 showTime 的时候,面板是否显示“此刻”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showToday", "name": "showToday", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示“今天”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" } ], "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false, "hiddenOn": "${!includeModifyModal}" } ], "bodyClassName": "p", "collapsed": true } ], "submitText": "" } ], "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false } ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/config/schema.ts ================================================ export type PageConfig = { filters: { component: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ label: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ placeholder: string; }[]; columns: { slot: boolean; /** * @description 保持原始内容,不要翻译 * @type {string} */ title: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ dataIndex: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; }[]; pagination: { show: boolean; page: string; size: string; total: string; }; includeModifyModal: boolean; fetchName: string; result: string; serviceName: string; }; ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/script/index.ts.ejs ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/script/src/controller.ts.ejs ================================================ import path from 'path'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { clipboard } from 'electron'; import { generalBasic } from '@share/BaiduOCR/index'; import { renderEjsTemplates } from '@share/utils/ejs'; import { typescriptToMock } from '@share/utils/platformIndependent/json'; import { getShareData } from '@share/utils/shareData'; import { MethodHandle } from '@share/uTools/webviewBaseController'; import { getBlockJsonValidSchema } from '@share/utils/uTools'; export const initFiltersFromImage: MethodHandle = async (data) => { const availableFormats = clipboard.availableFormats('clipboard'); if (!availableFormats.some((s) => s.includes('image'))) { throw new Error('剪贴板里没有截图'); } const base64 = clipboard.readImage('clipboard').toDataURL(); const ocrRes = await generalBasic({ image: base64 }); return { updateModelImmediately: false, model: data.model, onlyUpdateParams: true, params: ocrRes.words_result.map((s) => s.words).join('\r\n'), }; }; export const initFiltersFromText: MethodHandle = async (data) => { const filters = data.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); const formatedFilters = filters.map((item) => { const s = item.replace(/:|:/g, ':').split(':'); return { component: (s[1] || '').indexOf('选择') > -1 ? 'select' : 'input', key: s[0].trim(), label: s[0].trim(), placeholder: s[1] || '', }; }); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...data.model, filters: formatedFilters }, }; }; export const initColumnsFromText: MethodHandle = async (data) => { const columns = data.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); const formatedColumns = columns.map((s) => ({ slot: false, title: s, dataIndex: s, key: s, })); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...data.model, columns: formatedColumns }, }; }; export const openChatGPT: MethodHandle = async (data) => { const schema = getBlockJsonValidSchema(data.scriptFile); const clipboardText = JSON.stringify(data.model); const typeName = 'PageConfig'; const requestPrompt = `You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; return { updateModelImmediately: true, onlyUpdateParams: false, params: '', showChat: true, chatContent: requestPrompt, model: data.model, }; }; export const generateCode: MethodHandle = async (data) => { const { selectedFolder, activeWindow } = getShareData(); const tempWorkPath = path.join(activeWindow || '', '.lowcode'); const blockPath = path.join( data.scriptFile .replace('/script/src/mainBundle', '') .replace('/script/src/main', ''), ); fs.copySync(blockPath, tempWorkPath); try { await renderEjsTemplates( { ...data.model, createBlockPath: path.join(selectedFolder || '').replace(/\\/g, '/'), }, path.join(tempWorkPath, 'src'), ); // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(tempWorkPath, 'src', 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(tempWorkPath, 'src', 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync( path.join(tempWorkPath, 'src', 'temp.mock.script').toString(), ) .toString(); fs.removeSync(path.join(tempWorkPath, 'src', 'temp.mock.script')); const mockScript = ejs.render(mockTemplate, { ...data.model, mockCode, mockData, createBlockPath: selectedFolder?.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showInformationMessage( // '获取 mock 项目路径失败,跳过更新 mock 服务', // ); }); if (mockProjectPathRes?.data.result) { const projectName = activeWindow?.replace(/\\/g, '/').split('/').pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } // #endregion } } catch (ex) { fs.removeSync(tempWorkPath); throw ex; } fs.copySync(path.join(tempWorkPath, 'src'), path.join(selectedFolder || '')); fs.removeSync(tempWorkPath); return { updateModelImmediately: false, model: data.model, onlyUpdateParams: true, params: `代码生成目录:${selectedFolder}`, }; }; ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/script/src/main.ts.ejs ================================================ import { clipboard } from 'electron'; import { AskChatGPTForDynamicFormPageWebviewData, MethodHandle, baseAskChatGPTForDynamicFormPage, } from '@share/uTools/webviewBaseController'; import * as controller from './controller'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/dynamicForm' }), }); }; // #region 给 webview 调用的 export { getDynamicForm } from '@share/uTools/webviewBaseController'; export const askChatGPTForDynamicFormPage = ( data: AskChatGPTForDynamicFormPageWebviewData, ) => { return baseAskChatGPTForDynamicFormPage({ ...data, validateJsonSchemaTypeName: 'PageConfig', }); }; export const runDynamicFormScript: MethodHandle = (data) => { if (controller[data.method]) { return controller[data.method](data); } return Promise.reject(`方法不存在:${data.method}`); }; // #endregion ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/api.ts.keep.ejs ================================================ import { request } from "@/utils/request"; <% if (locals.api) { %> // #region <%= api.title %> <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { _%> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.filter(query => query.name !== pagination.page && query.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.req_params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.query_path.params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}') < 0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType) { %> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } %> ) { return request<<%= typeName %>>({ url: `http://127.0.0.1:3000<%= api.query_path.path.replace(/\{/g,"${params.") %>`, method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } // #endregion <% } else { %> // #region export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params { <% filters.map(item => { _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>?: string; <% } else { _%> <%= item.key %>Start?: string; <%= item.key %>End?: string; <% } _%> <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } export function <%= fetchName %>( params: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params ) { return requestResult>({ url: `http://127.0.0.1:3000<%= createBlockPath %>/<%= fetchName %>`, method: "GET", params, }); } // #endregion <% } %> ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/index.vue.keep.ejs ================================================ ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/model.ts.keep.ejs ================================================ import { reactive, ref } from "vue"; <% if(locals.api){ %> import { I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Result } from "./api"; <% } else { %> import { I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result } from "./api"; <% } %> <% if(!locals.api){ %> interface ITableListItem { <% columns.map((item, index) => { _%> /** <%= item.title %> */ <%= item.key || `column${index+1}` %>: string; <% }) _%> /** * 接口返回的数据,新增字段不需要改 ITableListItem 直接从这里取 */ apiResult: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result<%- result %>[0] } <% } %> interface IFormData { <% filters.map(item => { _%> /** <%= item.label %> */ <% if(item.component === "range-picker") { _%> <%= item.key %>?: [string,string]; <% } _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>?: string; <% } _%> <% }) _%> } <% if(filters.some(s => s.component === "select" )){ %> interface IOptionItem { label: string; value: string; } interface IOptions { <% filters.filter(s => s.component === "select").map(item => { _%> <%= item.key %>: IOptionItem[], <% }) _%> } const defaultOptions: IOptions = { <% filters.filter(s => s.component === "select").map(item => { _%> <%= item.key %>: [], <% }) _%> }; <% } %> export const defaultFormData: IFormData = { <% filters.map(item => { _%> <%= item.key %>: undefined, <% }) _%> }; export const useModel = () => { const filterForm = reactive({ ...defaultFormData }); <% if(filters.some(s => s.component === "select" )){ %> const options = reactive({ ...defaultOptions }); <% } %> <% if(locals.api){ _%> const tableList = ref<(I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Result<%- result %>[0] & { _?: unknown })[]>( [], ); <% } else { _%> const tableList = ref<(ITableListItem & { _?: unknown })[]>( [], ); <% } _%> <% if(pagination.show) { %> const pagination = reactive<{ page: number; pageSize: number; total: number; }>({ page: 1, pageSize: 10, total: 0, }); <% } %> const loading = reactive<{ list: boolean }>({ list: false, }); <% if(includeModifyModal) { %> const modalInfo = reactive<{ visible: boolean; title: string; id?: number; action: "add" | "edit" | "view"; }>({ visible: false, title: "", action: "add", }); <% } %> return { filterForm, <% if(filters.some(s => s.component === "select" )){ _%> options, <% } _%> tableList, <% if(pagination.show) { _%> pagination, <% } _%> loading, <% if(includeModifyModal) { _%> modalInfo, <% } _%> }; }; export type Model = ReturnType; ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/presenter.ts.keep.ejs ================================================ import Service from "./service"; import { defaultFormData, useModel } from "./model"; import { createVNode, onMounted } from "vue"; import { message, Modal } from "ant-design-vue"; import { ExclamationCircleOutlined } from "@ant-design/icons-vue"; <% if(filters.some(item => item.component === "select" && item.remoteFetch)) { _%> import { useDebounceFn } from "@vueuse/core"; <% } _%> export const usePresenter = () => { const model = useModel(); const service = new Service(model); onMounted(() => { service.<%= serviceName %>(); }); const handleClear = () => { Object.assign(model.filterForm, defaultFormData) <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; const handleSearch = () => { <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; <% if(pagination.show) { _%> const handlePageChange = (page: number, pageSize: number) => { if (pageSize !== model.pagination.pageSize) { model.pagination.pageSize = pageSize; model.pagination.page = 1; } else { model.pagination.page = page; } service.<%= serviceName %>(); }; <% } _%> <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> const handleSearch<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %> = useDebounceFn((value: string) => { if (!value) { return; } service.search<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>(value); }, 400); <% }) _%> const handleDel = (record: typeof model.tableList.value[0]) => { Modal.confirm({ title: "此操作将删除该选项,是否继续?", icon: createVNode(ExclamationCircleOutlined), okText: "确定", cancelText: "取消", onOk() { message.success("删除成功"); }, }); }; const handleCreate = () => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "新建"; model.modalInfo.action = "add"; model.modalInfo.id = undefined; <% } _%> }; const handleEdit = (record: typeof model.tableList.value[0]) => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "编辑"; model.modalInfo.action = "edit"; model.modalInfo.id = record.id; <% } _%> }; <% if(includeModifyModal) { _%> const handleView = (record: typeof model.tableList.value[0]) => { model.modalInfo.visible = true; model.modalInfo.title = "查看"; model.modalInfo.action = "view"; model.modalInfo.id = record.id; }; const handleModalOk = () => { model.modalInfo.visible = false; service.<%= serviceName %>(); }; const handleModalCancel = () => { model.modalInfo.visible = false; }; <% } _%> return { model, service, handleClear, handleSearch, <% if(pagination.show) { _%> handlePageChange, <% } _%> <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> handleSearch<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>, <% }) _%> handleDel, handleCreate, handleEdit, <% if(includeModifyModal) { _%> handleView, handleModalOk, handleModalCancel <% } _%> }; }; ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/service.ts.keep.ejs ================================================ import { <%= locals.api ? funcName : fetchName %> } from "./api"; import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async <%= serviceName %>() { this.model.loading.list = true; <% filters.map(item => { _%> <% if(item.component === "range-picker") { _%> let <%= item.key %>Start: string | undefined = undefined; let <%= item.key %>End: string | undefined = undefined; if ( this.model.filterForm.<%= item.key %> && this.model.filterForm.<%= item.key %>[0] ) { <%= item.key %>Start = `${this.model.filterForm.<%= item.key %>[0]} 00:00:00`; } if ( this.model.filterForm.<%= item.key %> && this.model.filterForm.<%= item.key %>[1] ) { <%= item.key %>End = `${this.model.filterForm.<%= item.key %>[1]} 23:59:59`; } <% } _%> <% }) _%> const res = await <%= locals.api ? funcName : fetchName %>({ <% filters.map(item => { _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>: this.model.filterForm.<%= item.key %>, <% } else { _%> <%= item.key %>Start: <%= item.key %>Start, <%= item.key %>End: <%= item.key %>End, <% } _%> <% }) _%> <% if(pagination.show) { _%> <%= pagination.page %>: this.model.pagination.page, <%= pagination.size %>: this.model.pagination.pageSize, <% } _%> }).finally(() => { this.model.loading.list = false; }); this.model.tableList.value = res<%- result %>.map((s) => { return { ...s, <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: s.<%= item.key || `column${index+1}` %>, <% }) _%> apiResult: s }; }); <% if(pagination.show) { _%> this.model.pagination.total = res.<%- pagination.total %>; <% } _%> } <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> async search<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>(value: string) { const res = await Promise.resolve([{ label: "1", value: "1" }]); this.model.options.<%= item.key %> = res; } <% }) _%> } ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/temp.mock.script ================================================ .get(`<%= createBlockPath %>/<%= fetchName %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: materials/snippets/快速创建区块/src/uTools 动态表单/src/temp.mock.type.keep.ejs ================================================ { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } ================================================ FILE: materials/snippets/快速创建区块/src/uTools 自动化脚本/script/index.ts.ejs ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: materials/snippets/快速创建区块/src/uTools 自动化脚本/script/src/main.ts.ejs ================================================ import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat' }), }); }; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp/index.scss.ejs ================================================ .<%= createBlockPath.split("/").pop().replace(/^\S/, s => s.toLowerCase()) %> { } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp/index.vue.ejs ================================================ ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp/model.ts ================================================ export const useModel = () => {}; export type Model = ReturnType; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp/presenter.ts ================================================ import Service from "./service"; import { useModel } from "./model"; export const usePresenter = () => { const model = useModel(); const service = new Service(model); return { model, service, }; }; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp/service.ts ================================================ import type { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp emit/index.scss.ejs ================================================ .<%= createBlockPath.split("/").pop().replace(/^\S/, s => s.toLowerCase()) %> { } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp emit/index.vue.ejs ================================================ ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp emit/model.ts ================================================ export const useModel = () => {}; export type Model = ReturnType; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp emit/presenter.ts ================================================ import Service from "./service"; import { useModel } from "./model"; interface IEmit { (e: "change", data?: { value: string }): void; } export const usePresenter = (emit: IEmit) => { const model = useModel(); const service = new Service(model); const handleChange = (value: string) => { emit("change", { value }); }; return { model, service, handleChange, }; }; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp emit/service.ts ================================================ import type { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props/index.scss.ejs ================================================ .<%= createBlockPath.split("/").pop().replace(/^\S/, s => s.toLowerCase()) %> { } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props/index.vue.ejs ================================================ ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props/model.ts ================================================ export const useModel = () => {}; export type Model = ReturnType; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props/presenter.ts ================================================ import Service from "./service"; import { useModel } from "./model"; interface IProps { name: string; } export const usePresenter = (props: IProps) => { const model = useModel(); const service = new Service(model); return { model, service, }; }; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props/service.ts ================================================ import type { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props emit/index.scss.ejs ================================================ .<%= createBlockPath.split("/").pop().replace(/^\S/, s => s.toLowerCase()) %> { } ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props emit/index.vue.ejs ================================================ ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props emit/model.ts ================================================ export const useModel = () => {}; export type Model = ReturnType; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props emit/presenter.ts ================================================ import Service from "./service"; import { useModel } from "./model"; interface IProps { name: string; } interface IEmit { (e: "change", data?: { value: string }): void; } export const usePresenter = (props: IProps, emit: IEmit) => { const model = useModel(); const service = new Service(model); const handleChange = () => { emit("change", { value: props.name }); }; return { model, service, handleChange, }; }; ================================================ FILE: materials/snippets/快速创建区块/src/uniapp/vue3-mvp props emit/service.ts ================================================ import type { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } } ================================================ FILE: materials/snippets/打开webview/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/打开webview/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/打开webview/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/打开webview/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/打开webview/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/打开webview/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, testScript: (lowcodeContext) => { context.lowcodeContext = lowcodeContext; return main.testScript(); }, }; ================================================ FILE: materials/snippets/打开webview/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/打开webview/script/src/controller.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { createChatCompletion } from '@share/LLM/gemini'; import { createChatCompletion as createChatGPTChatCompletion } from '@share/LLM/openai'; import { invokeLLMChunkCallback } from '@share/WebView/callback'; import { IMessage } from '@share/WebView/type'; const API_KEY = 'lowcode.GeminiKey'; export const getMaterialPath = async ( message: IMessage<{ materialPath: string; script: string; params: string; }>, context: { webview: vscode.Webview; task: { task: string; data?: any }; } & CompileContext, ) => context.materialPath; type Message = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const askGemini = async ( message: IMessage<{ messages: Message; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const context = lowcodeContext.env.extensionContext; // await context.secrets.delete(API_KEY); // 需要更新 API KEY 的时候打开 let apiKey = await context.secrets.get(API_KEY); if (!apiKey) { vscode.window.showWarningMessage('Enter your API KEY to save it securely.'); apiKey = await setApiKey(context); if (!apiKey) { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: 'Please enter your api key', }); return { content: 'Please enter your api key', }; } } const res = await createChatCompletion({ model: message.data.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gemini-pro-vision' : 'gemini-pro', apiKey, messages: message.data.messages, handleChunk: (data) => { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, proxyUrl: 'http://127.0.0.1:7890', }); return { content: res, }; }; export const askChatGPT = async ( message: IMessage<{ messages: Message; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const res = await createChatGPTChatCompletion({ model: message.data.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gpt-4-vision-preview' : undefined, messages: message.data.messages, handleChunk: (data) => { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, }); return { content: res, }; }; async function setApiKey(context) { const apiKey = await vscode.window.showInputBox({ title: 'Enter your API KEY', password: true, placeHolder: '**************************************', ignoreFocusOut: true, }); if (!apiKey) { vscode.window.showWarningMessage('Empty value'); return; } await context.secrets.store(API_KEY, apiKey); return apiKey; } ================================================ FILE: materials/snippets/打开webview/script/src/main.ts ================================================ import { window } from 'vscode'; import { showWebView } from '@share/WebView'; import { routes } from './routes'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; showWebView({ key: 'main', title: 'lowcode chat', viewColumn: 3, task: { task: 'route', data: { path: '/chat', materialPath: lowcodeContext?.materialPath }, }, lowcodeContext: { ...lowcodeContext!, }, htmlForWebview: getHtmlForWebview(false), routes, }); } export async function testScript() { const { lowcodeContext } = context; window.showInformationMessage('Hello World!'); const res = await lowcodeContext?.createChatCompletion({ messages: [{ role: 'user', content: '现在是什么时间' }], showWebview: true, }); return res; } const getHtmlForWebview = (dev = false) => { if (dev) { // return ` // // // // // // // // // Vite + Vue + TS // // //
// // // // `; return ` Vite + React + TS
`; } return `
`; }; ================================================ FILE: materials/snippets/打开webview/script/src/routes.ts ================================================ import * as controller from './controller'; export const routes: Record = { getMaterialPath: controller.getMaterialPath, askGemini: controller.askGemini, askChatGPT: controller.askChatGPT, }; ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT commit - 截图/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT commit - 截图/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/根据 DevOps 需求标题创建 GIT commit - 截图/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/根据 DevOps 需求标题创建 GIT commit - 截图/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT commit - 截图/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT commit - 截图/script/src/main.ts ================================================ import { window, env } from 'vscode'; import { generalBasic } from '@share/BaiduOCR'; import { getClipboardImage } from '@share/utils/clipboardImage'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const clipboardImage = await getClipboardImage( lowcodeContext?.env.privateMaterialsPath || '', ).catch((ex) => { window.showErrorMessage(ex); }); if (!clipboardImage) { window.showErrorMessage('剪贴板里没有截图'); } const ocrRes = await generalBasic({ image: clipboardImage! }); if (ocrRes.words_result.length > 1) { const task = ocrRes.words_result[0].words.trim(); const title = ocrRes.words_result[1].words.trimStart(); window.showInformationMessage( `内容已复制到剪贴板:feat(T${task}): #${task}/${title}`, ); env.clipboard.writeText(`feat(T${task}): #${task}/${title}`); } else { const result = ocrRes.words_result[0]?.words.trimStart() || ''; const match = result.match(/^(\d+)(.*)/); if (match) { const task = match[1]; // 开头的数字部分 const title = match[2]; // 其余的内容 window.showInformationMessage( `内容已复制到剪贴板:feat(T${task}): #${task}/${title}`, ); env.clipboard.writeText(`feat(T${task}): #${task}/${title}`); } else { window.showInformationMessage( `数据异常:${JSON.stringify(ocrRes, null, 2)}`, ); } } } ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/config/schema.ts ================================================ export type TaskInfo = { // 需求 id,用户发送内容的开头数字部分 taskId: string; // 需求标题,用户发送内容去掉开头数字部分,请翻译成英文,不要直接翻译,要自然、流畅和地道,这部分内容会作为 git 分支名,请用中划线代替翻译后的空格 taskTitle: string; }; ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/src/main.ts ================================================ import path from 'path'; import { window, env } from 'vscode'; import * as fs from 'fs-extra'; import { generalBasic } from '@share/BaiduOCR'; import { validate } from '@share/TypeChatSlim'; import { getClipboardImage } from '@share/utils/clipboardImage'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const clipboardImage = await getClipboardImage( lowcodeContext?.env.privateMaterialsPath || '', ).catch((ex) => { window.showErrorMessage(ex); }); if (!clipboardImage) { window.showErrorMessage('剪贴板里没有截图'); } const ocrRes = await generalBasic({ image: clipboardImage! }); const words = ocrRes.words_result.map((s) => s.words).join(' '); const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const typeName = 'TaskInfo'; const requestPrompt = ` You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions: \`\`\`ts ${schema} \`\`\` The following is a user request: \`\`\` ${words} \`\`\` The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined: `; const content = await context.lowcodeContext!.createChatCompletion({ messages: [ { role: 'user', content: requestPrompt, }, ], showWebview: true, handleChunk: (chunck) => {}, }); const valid = validate<{ taskId: string; taskTitle: string }>( content, schema, typeName, ); if (valid.success) { const branchName = `feat/T${valid.data.taskId}_${valid.data.taskTitle}`; env.clipboard.writeText(branchName); window.showInformationMessage(`分支名已复制到剪贴板:${branchName}`); } else { window.showErrorMessage(`JSON 校验失败:${valid.message}`); } } ================================================ FILE: materials/snippets/根据JSON生成API请求方法/config/commandPrompt.ejs ================================================ <%- rawClipboardText %> 根据这段 json 生成 ts 类型,名字为 I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Result 和下面的代码一起返回 export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params { id: number; } export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data { xx: string; } export function <%= rawSelectedText %>( params: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params, data: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data, ) { return requestResult>({ url: `xxxx`, method: 'GET', params, data, }); } 返回 markdown 代码块 ================================================ FILE: materials/snippets/根据JSON生成API请求方法/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成API请求方法/config/preview.json ================================================ { "title": "根据JSON生成API请求方法", "description": "根据JSON生成API请求方法", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/根据JSON生成API请求方法/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成API请求方法/src/template.ejs ================================================ <%- type %> export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params { id: number; } export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data { xx: string; } export function <%= rawSelectedText %>( params: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params, data: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data, ) { return requestResult>({ url: `xxxx`, method: 'GET', params, data, }); } ================================================ FILE: materials/snippets/根据JSON生成MOCK方法/config/commandPrompt.ejs ================================================ <%- rawClipboardText %> 生成一个 js 方法,方法名为 <%= rawSelectedText || 'getRandomData' %>, 方法内部使用 mock.js 生成跟上面的 json 一样字段的数据,如果有数组则生成10个元素, 最终的数据使用 Promise.resolve 返回 返回markdown代码块 ================================================ FILE: materials/snippets/根据JSON生成MOCK方法/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成MOCK方法/config/preview.json ================================================ { "title": "根据JSON生成MOCK方法", "description": "根据JSON生成MOCK方法", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/根据JSON生成MOCK方法/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成MOCK方法/src/template.ejs ================================================ const Mock = require('mockjs'); const { Random } = Mock; export function <%= rawSelectedText || 'getRandomData' %>() { <%- mockCode %> const res = <%- mockData %> return Promise.resolve(res); } ================================================ FILE: materials/snippets/根据JSON生成TS类型/config/commandPrompt.ejs ================================================ <%- rawClipboardText %> 根据这段 json 生成 ts 类型,名字为 <%= rawSelectedText || 'IResult' %> 返回markdown代码块 ================================================ FILE: materials/snippets/根据JSON生成TS类型/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成TS类型/config/preview.json ================================================ { "title": "根据JSON生成TS类型", "description": "根据JSON生成TS类型", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/根据JSON生成TS类型/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成TS类型/src/template.ejs ================================================ <%- type %> ================================================ FILE: materials/snippets/根据JSON生成TS类型-去除接口名称/config/commandPrompt.ejs ================================================ <%- rawClipboardText %> 根据这段 json 生成 ts 类型,把类型名称去掉 返回markdown代码块 ================================================ FILE: materials/snippets/根据JSON生成TS类型-去除接口名称/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成TS类型-去除接口名称/config/preview.json ================================================ { "title": "根据JSON生成TS类型-去除接口名称", "description": "根据JSON生成TS类型-去除接口名称", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/根据JSON生成TS类型-去除接口名称/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据JSON生成TS类型-去除接口名称/src/template.ejs ================================================ <%- type.replace("export interface IFetchResult","") %> ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/config/commandPrompt.ejs ================================================ 下面我让你来充当翻译家,你的目标是把中文翻译成英文单词,请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面的内容:“<%- rawSelectedText || rawClipboardText %>” ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/config/viewPrompt.ejs ================================================ ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/script/src/main.ts ================================================ import { env, window, Range } from 'vscode'; import { context } from './context'; export async function bootstrap() { const clipboardText = await env.clipboard.readText(); const { selection, document } = window.activeTextEditor!; const selectText = document.getText(selection).trim(); let content = await context.lowcodeContext!.createChatCompletion({ messages: [ { role: 'system', content: `你是一个代码专家,你的目标是把 JSON 数据转换成 TypeScript 类型,严格按照以下要求进行处理:类型名称根据每个字段意思推测出来;类型声明使用 type 关键字;把中文字段翻译成英文;将原来的中文作为字段的注释,注释是中文,而且注释使用 jsdoc 的格式;请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请处理下面用户输入的内容`, }, { role: 'user', content: selectText || clipboardText, }, ], }); content = content.replace('```typescript', '').replace('```', ''); window.activeTextEditor?.edit((editBuilder) => { if (window.activeTextEditor?.selection.isEmpty) { editBuilder.insert(window.activeTextEditor.selection.start, content); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), content, ); } }); } ================================================ FILE: materials/snippets/根据JSON生成TS类型-将中文字段翻译成英文/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/根据TS类型生成API请求方法/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据TS类型生成API请求方法/config/preview.json ================================================ { "title": "", "description": "", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [] } ================================================ FILE: materials/snippets/根据TS类型生成API请求方法/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据TS类型生成API请求方法/src/template.ejs ================================================ export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Result { code: number; msg: string; result: <%- rawClipboardText %> } export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params { id: number; } export interface I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data { xx: string; } export function <%= rawSelectedText %>( params: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Params, data: I<%= rawSelectedText.slice(0, 1).toUpperCase() + rawSelectedText.slice(1) %>Data, ) { return requestResult>({ url: `xxxx`, method: 'GET', params, data, }); } ================================================ FILE: materials/snippets/根据TS类型生成MOCK方法/config/commandPrompt.ejs ================================================ <%- rawClipboardText %> 生成一个 js 方法,方法名为 <%= rawSelectedText || 'getRandomData' %>, 方法内部使用 mock.js 生成跟上面的 ts 类型一样字段的数据,如果有数组则生成10个元素, 最终的数据使用 Promise.resolve 返回 返回markdown代码块 ================================================ FILE: materials/snippets/根据TS类型生成MOCK方法/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据TS类型生成MOCK方法/config/preview.json ================================================ { "title": "根据TS类型生成MOCK方法", "description": "根据TS类型生成MOCK方法", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" } ================================================ FILE: materials/snippets/根据TS类型生成MOCK方法/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据TS类型生成MOCK方法/src/template.ejs ================================================ const Mock = require('mockjs'); const { Random } = Mock; export function <%= rawSelectedText || 'getRandomData' %>() { <%- mockCode %> const res = <%- mockData %> return Promise.resolve(res); } ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/config/commandPrompt.ejs ================================================ <%- rawSelectedText || rawClipboardText %> 根据 ts 类型生成 markdown table, 格式如:|参数|说明|类型|必须|, 其中参数为类型字段名称, 说明为字段注释, 类型为字段类型, 必须为字段是否为 required。 返回 markdown ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "schema": "amis" } ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/根据TS类型生成markdown表格/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/根据TS类型生成markdown表格/script/src/context'); module.exports = { beforeCompile: () => {}, afterCompile: () => { context.outputChannel.appendLine('compile 根据TS类型生成markdown表格 end'); }, }; ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/script/src/main.ts ================================================ export const main = 1; ================================================ FILE: materials/snippets/根据TS类型生成markdown表格/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/根据YAPI接口定义生成高级Mock脚本/config/model.json ================================================ {} ================================================ FILE: materials/snippets/根据YAPI接口定义生成高级Mock脚本/config/preview.json ================================================ { "title": "", "description": "", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "category": [ "YAPI", "脚本" ] } ================================================ FILE: materials/snippets/根据YAPI接口定义生成高级Mock脚本/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/根据YAPI接口定义生成高级Mock脚本/src/template.ejs ================================================ <%- mockCode %> const mockData = <%- mockData %> Object.assign( mockJson, mockData) ================================================ FILE: materials/snippets/测试 JSONSchemaChat/config/commandPrompt.ejs ================================================ <%- rawSelectedText || rawClipboardText %> 解释这段代码的意思 ================================================ FILE: materials/snippets/测试 JSONSchemaChat/config/model.json ================================================ { "filters": [ { "component": "select", "key": "店铺名称", "label": "店铺名称", "placeholder": "请输入" }, { "component": "select", "key": "店铺编码", "label": "店铺编码", "placeholder": "请输入" }, { "component": "select", "key": "店铺门头编码", "label": "店铺门头编码", "placeholder": "请输入" }, { "component": "select", "key": "所在区域", "label": "所在区域", "placeholder": "全部" } ], "columns": [ { "slot": false, "title": "店铺编码", "dataIndex": "店铺编码", "key": "店铺编码" }, { "slot": false, "title": "店铺名称", "dataIndex": "店铺名称", "key": "店铺名称" }, { "slot": false, "title": "店铺业务范围", "dataIndex": "店铺业务范围", "key": "店铺业务范围" }, { "slot": false, "title": "店铺类型", "dataIndex": "店铺类型", "key": "店铺类型" }, { "slot": false, "title": "所在区域", "dataIndex": "所在区域", "key": "所在区域" }, { "slot": false, "title": "详细地址", "dataIndex": "详细地址", "key": "详细地址" } ], "pagination": { "show": true, "page": "page", "size": "size", "total": "result.total" }, "includeModifyModal": false, "fetchName": "fetchTableList", "result": "[\"result\"][\"records\"]", "serviceName": "getTableList" } ================================================ FILE: materials/snippets/测试 JSONSchemaChat/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/测试 JSONSchemaChat/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/测试 JSONSchemaChat/config/validSchema.json ================================================ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "", "type": "object", "properties": { "filters": { "type": "array", "items": { "type": "object", "properties": { "component": { "type": "string" }, "key": { "type": "string", "description": "翻译成英文,驼峰格式" }, "label": { "type": "string", "description": "保持原始内容,不要翻译" }, "placeholder": { "type": "string", "description": "保持原始内容,不要翻译" } }, "required": [ "component", "key", "label", "placeholder" ] } }, "columns": { "type": "array", "items": { "type": "object", "properties": { "slot": { "type": "boolean" }, "title": { "type": "string", "description": "保持原始内容,不要翻译" }, "dataIndex": { "type": "string", "description": "翻译成英文,驼峰格式" }, "key": { "type": "string", "description": "翻译成英文,驼峰格式" } }, "required": [ "slot", "title", "dataIndex", "key" ] } }, "pagination": { "type": "object", "properties": { "show": { "type": "boolean" }, "page": { "type": "string" }, "size": { "type": "string" }, "total": { "type": "string" } }, "required": [ "show", "page", "size", "total" ] }, "includeModifyModal": { "type": "boolean" }, "fetchName": { "type": "string" }, "result": { "type": "string" }, "serviceName": { "type": "string" } }, "required": [ "filters", "columns", "pagination", "includeModifyModal", "fetchName", "result", "serviceName" ] } ================================================ FILE: materials/snippets/测试 JSONSchemaChat/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/测试 JSONSchemaChat/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/测试 JSONSchemaChat/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/测试 JSONSchemaChat/script/src/context.ts ================================================ import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/测试 JSONSchemaChat/script/src/main.ts ================================================ import fs from 'fs'; import path from 'path'; import { env, window, Range } from 'vscode'; import { translate } from '@share/JSONSchemaChat/index'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/validSchema.json'), 'utf8', ); const clipboardText = await env.clipboard.readText(); const { selection, document } = window.activeTextEditor!; const selectText = document.getText(selection).trim(); const res = await translate({ schema, request: selectText || clipboardText, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, }); if (res.success) { window.activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (window.activeTextEditor?.selection.isEmpty) { editBuilder.insert( window.activeTextEditor.selection.start, JSON.stringify(res.data), ); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), JSON.stringify(res.data), ); } }); } else { window.showErrorMessage(res.message); } } ================================================ FILE: materials/snippets/测试 JSONSchemaChat/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/测试脚本/config/model.json ================================================ {} ================================================ FILE: materials/snippets/测试脚本/config/preview.json ================================================ { "title": "", "description": "", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "showInRunSnippetScript": true, "runActivate": true } ================================================ FILE: materials/snippets/测试脚本/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/测试脚本/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/测试脚本/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/测试脚本/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => { context.outputChannel.appendLine(Object.keys(lowcodeContext)); context.vscode.window.showErrorMessage('12134'); return { a: 'lowcode' }; }, afterCompile: (lowcodeContext) => { context.outputChannel.appendLine(Object.keys(lowcodeContext)); }, onSelect: async (lowcodeContext) => { await main.bootstrap(); }, onActivate: () => { main.onActivate(); }, }; ================================================ FILE: materials/snippets/测试脚本/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/测试脚本/script/src/main.ts ================================================ import { window } from 'vscode'; export async function bootstrap() { window.onDidChangeWindowState((state) => { console.log(state.focused, 123); }); } export function onActivate() { // window.showInformationMessage('你好'); } ================================================ FILE: materials/snippets/测试脚本/src/template.ejs ================================================ 测试编译前后脚本 ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/config/commandPrompt.ejs ================================================ export const <%- rawSelectedText %>Options = <%- content %> export const <%- rawSelectedText %>Map = <%- rawSelectedText %>Options.reduce((obj, { label, value }) => { obj[value] = label return obj }, {}) ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": false, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/config/schema.ts ================================================ export type IOption = { value: string; label: string }[]; ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/生成 value-label 格式 JSON/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/生成 value-label 格式 JSON/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/script/src/main.ts ================================================ import fs from 'fs'; import path from 'path'; import { env, window, Range } from 'vscode'; import { translate } from '@share/TypeChatSlim/index'; import { getMaterial } from '@share/utils/material'; import { compile as compileEjs } from '@share/utils/ejs'; import { pasteToEditor } from '@share/utils/editor'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; const schema = fs.readFileSync( path.join(lowcodeContext!.materialPath, 'config/schema.ts'), 'utf8', ); const clipboardText = await env.clipboard.readText(); const { selection, document } = window.activeTextEditor!; const selectText = document.getText(selection).trim(); const template = getMaterial(lowcodeContext!.materialPath); lowcodeContext?.outputChannel.appendLine(lowcodeContext!.materialPath); const res = await translate({ schema, typeName: 'IOption', request: clipboardText, createChatCompletion: lowcodeContext!.createChatCompletion, showWebview: true, }); if (res.success) { const code = compileEjs(template!.commandPrompt, { rawSelectedText: selectText, content: JSON.stringify(res.data), } as any); window.activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (window.activeTextEditor?.selection.isEmpty) { editBuilder.insert(window.activeTextEditor.selection.start, code); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), code, ); } }); } else { window.showErrorMessage(res.message); } } ================================================ FILE: materials/snippets/生成 value-label 格式 JSON/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/翻译成驼峰格式/config/commandPrompt.ejs ================================================ 下面我让你来充当翻译家,你的目标是把中文翻译成英文单词,请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。 请翻译下面的内容:“<%- rawSelectedText || rawClipboardText %>” ================================================ FILE: materials/snippets/翻译成驼峰格式/config/model.json ================================================ { "name": "lowcode" } ================================================ FILE: materials/snippets/翻译成驼峰格式/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/翻译成驼峰格式/config/schema.json ================================================ { "formSchema": { "schema": { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "input-text", "name": "name", "label": "测试表单", "id": "u:4886baa626cf", "value": "" } ], "id": "u:67967afb0e69", "submitText": "" } ], "id": "u:d87dbf6bf8df", "asideResizor": false, "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "pullRefresh": { "disabled": true }, "regions": [ "body" ] } } } ================================================ FILE: materials/snippets/翻译成驼峰格式/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/翻译成驼峰格式/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/翻译成驼峰格式/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/翻译成驼峰格式/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { ViewCallContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: ViewCallContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/翻译成驼峰格式/script/src/main.ts ================================================ import { env, window, Range } from 'vscode'; import { context } from './context'; export async function bootstrap() { const clipboardText = await env.clipboard.readText(); const { selection, document } = window.activeTextEditor!; const selectText = document.getText(selection).trim(); let content = await context.lowcodeContext!.createChatCompletion({ messages: [ { role: 'system', content: `你是一个翻译家,你的目标是把中文翻译成英文单词,请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面用户输入的内容`, }, { role: 'user', content: selectText || clipboardText, }, ], }); content = content.charAt(0).toLowerCase() + content.slice(1); window.activeTextEditor?.edit((editBuilder) => { if (window.activeTextEditor?.selection.isEmpty) { editBuilder.insert(window.activeTextEditor.selection.start, content); } else { editBuilder.replace( new Range( window.activeTextEditor!.selection.start, window.activeTextEditor!.selection.end, ), content, ); } }); } ================================================ FILE: materials/snippets/翻译成驼峰格式/src/template.ejs ================================================ <%= name %> ================================================ FILE: materials/snippets/自动保存活动窗口/config/model.json ================================================ {} ================================================ FILE: materials/snippets/自动保存活动窗口/config/preview.json ================================================ { "title": "", "description": "", "img": "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg", "showInRunSnippetScript": false, "runActivate": true } ================================================ FILE: materials/snippets/自动保存活动窗口/config/schema.json ================================================ {} ================================================ FILE: materials/snippets/自动保存活动窗口/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/自动保存活动窗口/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/自动保存活动窗口/script/src/context'); module.exports = { onActivate: (lowcodeContext) => { context.lowcodeContext = lowcodeContext; main.onActivate(); }, }; ================================================ FILE: materials/snippets/自动保存活动窗口/script/src/context.ts ================================================ import { INestApplication } from '@nestjs/common'; import { CompileContext } from 'lowcode-context'; import { StatusBarItem } from 'vscode'; export const context: { lowcodeContext?: CompileContext; nestApp?: INestApplication; statusBarItem?: StatusBarItem; } = { lowcodeContext: undefined, nestApp: undefined, statusBarItem: undefined, }; ================================================ FILE: materials/snippets/自动保存活动窗口/script/src/main.ts ================================================ import { Uri, commands, env, window, workspace } from 'vscode'; import { saveShareData } from '@share/utils/shareData'; import { context } from './context'; export function onActivate() { const { lowcodeContext } = context; saveShareData({ activeWindow: (workspace.workspaceFolders && workspace.workspaceFolders[0].uri.path) || '', }); window.onDidChangeWindowState((state) => { if (state.focused) { saveShareData({ activeWindow: (workspace.workspaceFolders && workspace.workspaceFolders[0].uri.path) || '', }); } }); // 获取选中的文件夹 commands.getCommands().then((res) => { if (!res.some((s) => s.includes('lowcode.getSelectedFolder'))) { const getSelectedFolder = commands.registerCommand( 'lowcode.getSelectedFolder', async () => { const oriClipboardText = await env.clipboard.readText(); await commands.executeCommand('copyFilePath'); const folder = await env.clipboard.readText(); env.clipboard.writeText(oriClipboardText); const newUri = Uri.file(folder); saveShareData({ selectedFolder: process.platform === 'win32' && newUri.path.startsWith('/') ? newUri.path.substring(1) : newUri.path, }); console.log(newUri.path); }, ); lowcodeContext?.env.extensionContext.subscriptions.push( getSelectedFolder, ); } }); } ================================================ FILE: materials/snippets/获取当前用户最近一次 Git Commit 信息/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [] } ================================================ FILE: materials/snippets/获取当前用户最近一次 Git Commit 信息/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/获取当前用户最近一次 Git Commit 信息/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/获取当前用户最近一次 Git Commit 信息/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/获取当前用户最近一次 Git Commit 信息/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/获取当前用户最近一次 Git Commit 信息/script/src/main.ts ================================================ import { platform } from 'os'; import * as execa from 'execa'; import { window, env } from 'vscode'; import { getShareData } from '@share/utils/shareData'; export function bootstrap() { const { activeWindow } = getShareData(); try { const projectPath = platform() === 'win32' && activeWindow?.startsWith('/') ? activeWindow.substring(1) : activeWindow; const userName = execa.sync('git', [ '-C', projectPath || '.', 'config', 'user.name', ]); const res = execa.sync('git', [ '-C', projectPath || '.', 'log', '-1', '--pretty=format:%s', `--author=${userName.stdout}`, // 'log -1 --pretty=format:"%s" --author="$(git config user.name)"', ]); if (res.stdout) { env.clipboard.writeText(res.stdout); window.showInformationMessage(`内容已复制到剪贴板:${res.stdout}`); return; } window.showInformationMessage(`数据异常${JSON.stringify(res)}`); } catch (ex) { window.showInformationMessage(`数据异常${(ex as object).toString()}`); } } ================================================ FILE: materials/snippets/设置配置信息/config/model.json ================================================ {} ================================================ FILE: materials/snippets/设置配置信息/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [ { "method": "saveConfig", "remark": "保存配置信息" } ] } ================================================ FILE: materials/snippets/设置配置信息/config/schema.json ================================================ { "type": "page", "name": "page", "body": [ { "type": "form", "name": "form", "data": {}, "title": "", "body": [ { "type": "input-text", "id": "u:11b127c5df46", "label": "activeWindow", "name": "activeWindow", "description": "" }, { "type": "combo", "label": "oneAPI", "name": "oneAPI", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "label": "hostname", "name": "hostname", "id": "u:6496cac4f4b8", "description": "" }, { "type": "input-text", "label": "apiPath", "name": "apiPath", "id": "u:a701e97d81a6", "description": "" }, { "type": "switch", "label": "notHttps", "option": "", "name": "notHttps", "falseValue": false, "trueValue": true, "id": "u:a3283c2ac1b6", "value": false }, { "type": "input-number", "label": "port", "name": "port", "keyboard": true, "id": "u:3d3695bd7ab2", "step": 1 }, { "type": "input-text", "name": "apiKey", "placeholder": "", "id": "u:25b0c7b5e5a0", "label": "apiKey" }, { "type": "input-text", "label": "model", "name": "model", "id": "u:ac0737858444", "description": "" }, { "type": "input-number", "label": "maxTokens", "name": "maxTokens", "keyboard": true, "id": "u:a8cfb52c5e43", "step": 1 }, { "type": "input-number", "label": "temperature", "name": "temperature", "keyboard": true, "id": "u:8797d33941aa", "step": 1 }, { "type": "input-text", "label": "proxyUrl", "name": "proxyUrl", "description": "" }, { "type": "switch", "label": "use", "option": "", "name": "use", "falseValue": false, "trueValue": true, "id": "u:1ac806d7ad60", "value": false } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": false, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "value": [{}] } ], "submitText": "" } ], "pullRefresh": { "disabled": true }, "regions": ["body"], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false } ================================================ FILE: materials/snippets/设置配置信息/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/设置配置信息/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/设置配置信息/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/设置配置信息/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/设置配置信息/script/src/controller.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { getShareData, saveShareData } from '@share/utils/shareData'; import { getDynamicFormConfig } from '@share/utils/dynamicForm'; import { IMessage } from '@share/WebView/type'; type RunDynamicFormScript = ( message: IMessage<{ method: string; params: string; model: object; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => Promise<{ /** 立即更新 model */ updateModelImmediately: boolean; /** 仅更新参数 */ onlyUpdateParams: boolean; params?: string; model: object; }>; export const getDynamicForm = ( message: IMessage, context: { webview: vscode.Webview; task: { task: string; data?: any }; } & CompileContext, ) => { const { materialPath } = context; const model = getShareData(); const config = getDynamicFormConfig({ vscodeMaterialPath: materialPath, model, }); return { schema: config.schema, scripts: config.scripts, }; }; export const runDynamicFormScript: RunDynamicFormScript = async ( message, lowcodeContext, ) => { if (handler[message.data.method]) { return handler[message.data.method](message, lowcodeContext); } return Promise.reject(`方法:${message.data.method} 不存在`); }; const handler: { [method: string]: RunDynamicFormScript; } = { saveConfig: (message, lowcodeContext) => { saveShareData(message.data.model); return Promise.resolve({ model: message.data.model, updateModelImmediately: true, onlyUpdateParams: false, params: '', }); }, }; ================================================ FILE: materials/snippets/设置配置信息/script/src/main.ts ================================================ import { showWebView } from '@share/WebView'; import { routes } from './routes'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; showWebView({ key: 'main', title: '设置配置信息', viewColumn: 3, task: { task: 'route', data: { path: '/dynamicForm', materialPath: lowcodeContext?.materialPath, }, }, lowcodeContext: { ...lowcodeContext!, }, // htmlForWebview: getHtmlForWebview(false), routes, }); } const getHtmlForWebview = (dev = false) => { if (dev) { return `
`; } return `
`; }; ================================================ FILE: materials/snippets/设置配置信息/script/src/routes.ts ================================================ import * as controller from './controller'; export const routes: Record = { runDynamicFormScript: controller.runDynamicFormScript, getDynamicForm: controller.getDynamicForm, }; ================================================ FILE: materials/snippets/设置配置信息/src/template.ejs ================================================ 代码片段 ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/config/model.json ================================================ { "originalType": "", "targetType": "", "originalVariable": "", "targetVariable": "" } ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/config/preview.json ================================================ { "title": "", "description": "", "img": [ "https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg" ], "category": [], "notShowInCommand": true, "notShowInSnippetsList": true, "notShowInintellisense": true, "showInRunSnippetScript": true, "showInRunSnippetScriptOnExplorer": false, "runActivate": false, "schema": "amis", "scripts": [ { "method": "askChatGPT", "remark": "ask ChatGPT" } ] } ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/config/schema.json ================================================ { "type": "page", "body": [ { "type": "form", "id": "u:67967afb0e69", "title": "", "body": [ { "type": "textarea", "label": "原始类型", "name": "originalType", "id": "u:1772600049f6", "minRows": 10, "maxRows": 20 }, { "type": "textarea", "label": "目标类型", "name": "targetType", "id": "u:07bd55b0bd60", "minRows": 10, "maxRows": 20 }, { "type": "input-text", "label": "原始变量", "name": "originalVariable", "id": "u:1bc921a6215e" }, { "type": "input-text", "label": "目标变量", "name": "targetVariable", "id": "u:0237afad67b1" } ], "submitText": "", "actions": [], "feat": "Insert" } ], "id": "u:d87dbf6bf8df", "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": {}, "asideResizor": false, "themeCss": { "baseControlClassName": { "boxShadow:default": " 0px 0px 0px 0px transparent" } } } ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/script/index.js ================================================ const path = require('path'); const moduleAlias = require('module-alias'); function splitStringByLastKeyword(inputString, keyword) { const lastIndex = inputString.lastIndexOf(keyword); if (lastIndex === -1) { return [inputString, '']; } const part1 = inputString.slice(0, lastIndex); const part2 = inputString.slice(lastIndex + keyword.length); return [part1, part2]; } moduleAlias.addAlias( '@share', path.join(splitStringByLastKeyword(__dirname, 'materials')[0], 'dist/share'), ); const main = require('../../../../dist/materials/snippets/通过 TS 类型做字段映射/script/src/main'); const { context, } = require('../../../../dist/materials/snippets/通过 TS 类型做字段映射/script/src/context'); module.exports = { beforeCompile: (lowcodeContext) => {}, afterCompile: (lowcodeContext) => {}, onSelect: async (lowcodeContext) => { context.lowcodeContext = lowcodeContext; await main.bootstrap(); }, }; ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/script/src/context.ts ================================================ import { CompileContext } from 'lowcode-context'; export const context: { lowcodeContext?: CompileContext; } = { lowcodeContext: undefined, }; ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/script/src/controller.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { IMessage } from '@share/WebView/type'; type RunDynamicFormScript = ( message: IMessage<{ method: string; params: string; model: any; }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => Promise<{ /** 立即更新 model */ updateModelImmediately: boolean; /** 仅更新参数 */ onlyUpdateParams: boolean; params?: string; model: any; }>; export const runDynamicFormScript: RunDynamicFormScript = async ( message, lowcodeContext, ) => { if (handler[message.data.method]) { return handler[message.data.method](message, lowcodeContext); } return Promise.reject(`方法:${message.data.method} 不存在`); }; const handler: { [method: string]: RunDynamicFormScript; } = { askChatGPT: async (message, lowcodeContext) => { const prompt = `目前有变量 ${message.data.model.originalVariable},类型为: """ ${message.data.model.originalType} """ 以及变量 ${message.data.model.targetVariable || 'model'},类型为: """ ${message.data.model.targetType} """ 请根据注释或者字段名称把变量 ${ message.data.model.originalVariable } 的每个字段赋值给变量 ${ message.data.model.targetVariable || 'model' } 的相应字段,按下面的方式进行赋值: """ { a: ${message.data.model.originalVariable}.a, b: ${message.data.model.originalVariable}.b, c: ${message.data.model.originalVariable}.c, d: ${message.data.model.originalVariable}.d, } """ `; const res = await lowcodeContext.createChatCompletion({ messages: [{ content: prompt, role: 'user' }], showWebview: true, }); return { model: message.data.model, updateModelImmediately: false, onlyUpdateParams: true, params: res, }; }, }; ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/script/src/main.ts ================================================ import { window } from 'vscode'; import { showWebView } from '@share/WebView'; import { routes } from './routes'; import { context } from './context'; export async function bootstrap() { const { lowcodeContext } = context; showWebView({ key: 'main', title: '通过 TS 类型做字段映射', viewColumn: 3, task: { task: 'route', data: { path: '/dynamicForm', materialPath: lowcodeContext?.materialPath, }, }, lowcodeContext: { ...lowcodeContext!, }, htmlForWebview: getHtmlForWebview(false), routes, }); } const getHtmlForWebview = (dev = false) => { if (dev) { return `
`; } return `
`; }; ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/script/src/routes.ts ================================================ import * as controller from './controller'; export const routes: Record = { runDynamicFormScript: controller.runDynamicFormScript, }; ================================================ FILE: materials/snippets/通过 TS 类型做字段映射/src/template.ejs ================================================ 代码片段 ================================================ FILE: package.json ================================================ { "name": "lowcode-materials", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "compile": "tsc --project tsconfig.compiler.json --skipLibCheck", "uTools": "node ./uTools.js", "build": "npm run prod", "prod": "node ./buildMaterials.js prod && npm run compile && npm run uTools", "dev": "node ./buildMaterials.js dev && npm run compile && npm run uTools", "lint": "eslint --ext .ts,.tsx,.js,.jsx materials/", "start:dev": "cross-env NODE_ENV=dev nest start --watch", "tsc": "tsc --noEmit --skipLibCheck" }, "repository": { "type": "git", "url": "git+https://github.com/lowcode-scaffold/lowcode-materials.git" }, "author": "", "license": "ISC", "bugs": { "url": "https://github.com/lowcode-scaffold/lowcode-materials/issues" }, "homepage": "https://github.com/lowcode-scaffold/lowcode-materials#readme", "dependencies": { "@babel/parser": "^7.22.10", "@google/generative-ai": "^0.1.3", "@nestjs/common": "^10.2.2", "@nestjs/config": "^3.0.0", "@nestjs/core": "^10.2.2", "@nestjs/platform-express": "^10.2.2", "ajv": "^8.12.0", "ali-oss": "^6.21.0", "ast-types": "^0.14.2", "axios": "^1.5.0", "ejs": "^3.1.9", "execa": "^4.0.1", "fs-extra": "^11.1.1", "generate-schema": "^2.6.0", "glob": "^7.1.6", "html-entities": "^2.4.0", "json-schema-to-typescript": "^13.1.1", "jsonminify": "^0.4.2", "mitt": "^3.0.1", "mockjs": "^1.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "recast": "^0.23.4", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "strip-comments": "^2.0.1", "tar": "^7.4.3", "ts-node": "^10.9.1", "tunnel": "^0.0.6", "typescript": "^5.1.3", "typescript-json-schema": "^0.60.0", "undici": "^6.4.0" }, "devDependencies": { "@electron/remote": "^2.1.2", "@nestjs/cli": "^10.1.16", "@nestjs/schematics": "^10.0.2", "@types/ejs": "^3.1.3", "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", "@types/glob": "^7.1.1", "@types/node": "^20.3.1", "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", "@types/vscode": "^1.79.1", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "cross-env": "^7.0.3", "dotenv-cli": "^7.3.0", "electron": "^29.1.0", "esbuild": "^0.20.2", "eslint": "^8.47.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.28.0", "eslint-plugin-prettier": "^5.0.0", "module-alias": "^2.2.3", "prettier": "^3.0.1", "tslib": "^2.5.3", "utools-api-types": "^4.0.0" } } ================================================ FILE: scripts/ClipboardImage/linux.sh ================================================ #!/bin/sh # require xclip(see http://stackoverflow.com/questions/592620/check-if-a-program-exists-from-a-bash-script/677212#677212) command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; } # write image in clipboard to file (see http://unix.stackexchange.com/questions/145131/copy-image-from-clipboard-to-file) if xclip -selection clipboard -target image/png -o >/dev/null 2>&1 then xclip -selection clipboard -target image/png -o >$1 2>/dev/null echo $1 else echo "no image" fi ================================================ FILE: scripts/ClipboardImage/mac.applescript ================================================ property fileTypes : {{«class PNGf», ".png"}} on run argv if argv is {} then return "" end if set imagePath to (item 1 of argv) set theType to getType() if theType is not missing value then try set myFile to (open for access imagePath with write permission) set eof myFile to 0 write (the clipboard as (first item of theType)) to myFile close access myFile return (POSIX path of imagePath) on error try close access myFile end try return "" end try else return "no image" end if end run on getType() repeat with aType in fileTypes repeat with theInfo in (clipboard info) if (first item of theInfo) is equal to (first item of aType) then return aType end repeat end repeat return missing value end getType ================================================ FILE: scripts/ClipboardImage/pc.ps1 ================================================ param($imagePath) # Adapted from https://github.com/octan3/img-clipboard-dump/blob/master/dump-clipboard-png.ps1 Add-Type -Assembly PresentationCore $img = [Windows.Clipboard]::GetImage() if ($img -eq $null) { "no image" Exit 1 } if (-not $imagePath) { "no image" Exit 1 } $fcb = new-object Windows.Media.Imaging.FormatConvertedBitmap($img, [Windows.Media.PixelFormats]::Rgb24, $null, 0) $stream = [IO.File]::Open($imagePath, "OpenOrCreate") $encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder $encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($fcb)) | out-null $encoder.Save($stream) | out-null $stream.Dispose() | out-null $imagePath ================================================ FILE: share/BaiduOCR/index.ts ================================================ import { request } from './request'; // #region 通用文字识别(标准版) export interface IGeneralBasicResult { words_result: { words: string; }[]; words_result_num: number; log_id: number; } export interface IGeneralBasicData { image: string; detect_direction?: boolean; paragraph?: boolean; probability?: boolean; } /** * 通用文字识别(标准版) * https://cloud.baidu.com/doc/OCR/s/zk3h7xz52 */ export function generalBasic(data: IGeneralBasicData) { return request({ url: `https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic`, method: 'POST', data, }); } // #endregion // #region 通用文字识别(标准含位置版) export interface IGeneralResult { words_result: { words: string; location: { top: number; left: number; width: number; height: number; }; }[]; words_result_num: number; log_id: number; } export interface IGeneralData { image: string; detect_direction?: boolean; paragraph?: boolean; probability?: boolean; } /** * 通用文字识别(标准含位置版) * https://cloud.baidu.com/doc/OCR/s/vk3h7y58v */ export function general(data: IGeneralData) { return request({ url: `https://aip.baidubce.com/rest/2.0/ocr/v1/general`, method: 'POST', data, }); } // #endregion // #region 通用文字识别(高精度版) export interface IAccurateBasicResult { words_result: { words: string; }[]; words_result_num: number; log_id: number; } export interface IAccurateBasicData { image: string; } /** * 通用文字识别(高精度版) * https://cloud.baidu.com/doc/OCR/s/1k3h7y3db */ export function accurateBasic(data: IAccurateBasicData) { return request({ url: `https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic`, method: 'POST', data, }); } // #endregion // #region 通用文字识别(高精度含位置版) export interface IAccurateResult { words_result: { words: string; location: { top: number; left: number; width: number; height: number; }; }[]; words_result_num: number; log_id: number; } export interface IAccurateData { image: string; } /** * 通用文字识别(高精度含位置版) * https://cloud.baidu.com/doc/OCR/s/tk3h7y2aq */ export function accurate(data: IAccurateData) { return request({ url: `https://aip.baidubce.com/rest/2.0/ocr/v1/accurate`, method: 'POST', data, }); } // #endregion // #region 办公文档识别 export interface IDocAnalysisOfficeResult { results: { words: { word: string; words_location: { left: number; height: number; width: number; top: number; }; }; words_type: string; }[]; results_num: number; log_id: number; } export interface IDocAnalysisOfficeData { image: string; } /** * 办公文档识别 * https://cloud.baidu.com/doc/OCR/s/ykg9c09ji */ export function docAnalysisOffice(data: IDocAnalysisOfficeData) { return request({ url: `https://aip.baidubce.com/rest/2.0/ocr/v1/doc_analysis_office`, method: 'POST', data, }); } // #endregion ================================================ FILE: share/BaiduOCR/request.ts ================================================ import axios, { AxiosRequestConfig } from 'axios'; const instance = axios.create({ timeout: 30 * 1000, }); // 请求拦截 instance.interceptors.request.use( // @ts-ignore async (config) => { let token = ''; if (!config.url?.includes('/oauth/2.0/token')) { token = await getAccessToken(); } return { ...config, headers: { ...config.headers, 'content-type': 'application/x-www-form-urlencoded', }, params: { ...config.params, access_token: token, }, }; }, (error) => Promise.reject(error), ); // 响应拦截 instance.interceptors.response.use( (res) => { if (res.data && res.data.error_msg) { return Promise.reject(res.data.error_msg); } return Promise.resolve(res.data); }, (error) => Promise.reject(error), ); type Request = (config: AxiosRequestConfig) => Promise; export const request = instance.request as Request; const getAccessToken = async () => { const res = await request<{ access_token: string }>({ url: 'https://aip.baidubce.com/oauth/2.0/token', method: 'POST', params: { client_id: 'xxxxx', client_secret: 'xxxxxxxx', grant_type: 'client_credentials', }, }); return res.access_token; }; ================================================ FILE: share/JSONSchemaChat/index.ts ================================================ import Ajv from 'ajv'; import { StatusBarAlignment, window } from 'vscode'; import { Success, Error, error, success } from './result'; export async function translate(option: { schema: string; request: string; createChatCompletion: (options: { messages: { role: 'system' | 'user' | 'assistant'; content: string; }[]; handleChunk?: ((data: { text?: string }) => void) | undefined; showWebview?: boolean; }) => Promise; showWebview?: boolean; /** * @description 完整的 prompt,若提供则内部不再组合 prompt * @type {string} */ completePrompt?: string; extendValidate?: (jsonObject: T) => Error | Success; }) { let requestPrompt = option.completePrompt || `你是一个根据以下 JSON Schema 定义将用户请求转换为相应 JSON 数据的服务,并且按照 JSON Schema 中 description 的描述对字段进行处理:\n` + `\`\`\`\n${option.schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${option.request}\n"""\n` + `The following is the user request translated into a JSON data with 2 spaces of indentation and no properties with the value undefined:\n`; let tryCount = 0; // eslint-disable-next-line no-unreachable-loop, no-constant-condition while (true) { const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); statusBarItem.text = '$(sync~spin) Ask ChatGPT...'; statusBarItem.show(); // eslint-disable-next-line no-await-in-loop const responseText = await option .createChatCompletion({ messages: [{ role: 'user', content: requestPrompt }], handleChunk: undefined, showWebview: option.showWebview, }) .finally(() => { statusBarItem.hide(); statusBarItem.dispose(); }); let validation = validate( responseText.replace(/```/g, ''), option.schema, ); if (validation.success) { // 走额外的校验 if (option.extendValidate) { validation = option.extendValidate(validation.data); if (validation.success) { return validation; } } else { return validation; } } if (tryCount > 3) { return validation; } requestPrompt += `${responseText}\n${createRepairPrompt( validation.message, )}`; tryCount++; } } function createRepairPrompt(validationError: string) { return ( `The JSON object is invalid for the following reason:\n` + `"""\n${validationError}\n"""\n` + `The following is a revised JSON object:\n` ); } function validate(jsonText: string, schema: string) { let jsonObject; try { jsonObject = JSON.parse(jsonText) as object; } catch (e) { return error(e instanceof SyntaxError ? e.message : 'JSON parse error'); } stripNulls(jsonObject); const ajv = new Ajv(); const jsonValidate = ajv.compile(JSON.parse(schema)); const valid = jsonValidate(jsonObject); if (!valid) { console.log(jsonValidate.errors); return error( jsonValidate.errors?.map((s) => s.message || s.keyword).join(',') || '', ); } return success(jsonObject); } function stripNulls(obj: any) { let keysToDelete: string[] | undefined; // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const k in obj) { const value = obj[k]; if (value === null) { (keysToDelete ??= []).push(k); } else { if (Array.isArray(value)) { if (value.some((x) => x === null)) { obj[k] = value.filter((x) => x !== null); } } if (typeof value === 'object') { stripNulls(value); } } } if (keysToDelete) { // eslint-disable-next-line no-restricted-syntax for (const k of keysToDelete) { delete obj[k]; } } } ================================================ FILE: share/JSONSchemaChat/result.ts ================================================ export type Success = { success: true; data: T }; export type Error = { success: false; message: string }; export type Result = Success | Error; export function success(data: T): Success { return { success: true, data }; } export function error(message: string): Error { return { success: false, message }; } export function getData(result: Result) { if (result.success) { return result.data; } throw new Error(result.message); } ================================================ FILE: share/LLM/gemini.ts ================================================ import { Content, GoogleGenerativeAI, Part } from '@google/generative-ai'; import { setGlobalDispatcher, ProxyAgent } from 'undici'; import { oneAPIConfig } from '../utils/shareData'; type Message = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const createChatCompletion = async (options: { apiKey?: string; model?: 'gemini-pro' | 'gemini-pro-vision'; maxTokens?: number; hostname?: string; apiPath?: string; messages: Message; handleChunk?: (data: { text?: string }) => void; topP?: number; temperature?: number; proxyUrl?: string; }) => { const config = oneAPIConfig(); if (options.proxyUrl || config?.proxyUrl) { const dispatcher = new ProxyAgent({ uri: new URL(options.proxyUrl || config?.proxyUrl || '').toString(), }); setGlobalDispatcher(dispatcher); } const genAI = new GoogleGenerativeAI(options.apiKey || config?.apiKey || ''); const model = genAI.getGenerativeModel({ model: options.model || config?.model || '', generationConfig: { maxOutputTokens: options.maxTokens || config?.maxTokens, temperature: options.temperature, topP: options.topP, }, }); try { const result = await model.generateContentStream({ contents: openAiMessageToGeminiMessage(options.messages), }); let combinedResult = ''; // eslint-disable-next-line no-restricted-syntax for await (const chunk of result.stream) { const chunkText = chunk.text(); if (options.handleChunk) { options.handleChunk({ text: chunkText }); } combinedResult += chunkText; } return combinedResult; } catch (ex: any) { if (options.handleChunk) { options.handleChunk({ text: ex.toString() }); } return ex.toString(); } }; const openAiMessageToGeminiMessage = (messages: Message): Content[] => { const result: Content[] = []; const hasImage = messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ); if (hasImage) { result.push({ role: 'user', parts: [] }); const partsText: string[] = []; let imagePart!: Part; for (let i = 0; i < messages.length; i++) { const { role, content } = messages[i]; if (role === 'system') { partsText.push(content); // eslint-disable-next-line no-continue continue; } if (content == null || typeof content === 'string') { partsText.push(content || ''); } else { for (let j = 0; j < content.length; j++) { const item = content[j]; if (item.type === 'text') { partsText.push(item.text || ''); } else { imagePart = parseBase64( (item as { type: 'image_url'; image_url: { url: string } }) .image_url.url, ); } } } } result[0].parts = [{ text: partsText.join('\n') }, imagePart]; } else { for (let i = 0; i < messages.length; i++) { const { role, content } = messages[i]; const parts: Part[] = []; if (role === 'system') { result.push({ role: 'user', parts: [{ text: content as string }] }); result.push({ role: 'model', parts: [{ text: '' }] }); // eslint-disable-next-line no-continue continue; } if (content == null || typeof content === 'string') { parts.push({ text: content?.toString() ?? '' }); } else { for (let j = 0; j < content.length; j++) { const item = content[j]; parts.push( item.type === 'text' ? { text: item.text } : parseBase64( (item as { type: 'image_url'; image_url: { url: string } }) .image_url.url, ), ); } } result.push({ role: role === 'user' ? 'user' : 'model', parts }); if ( i < messages.length - 1 && messages[i + 1].role === 'user' && role === 'user' ) { result.push({ role: 'model', parts: [{ text: '' }] }); } } } return result; }; const parseBase64 = (base64: string): Part => { if (!base64.startsWith('data:')) { return { text: '' }; } const [m, data, ..._arr] = base64.split(','); const mimeType = m.match(/:(?.*?);/)?.groups?.mime ?? 'img/png'; return { inlineData: { mimeType, data, }, }; }; ================================================ FILE: share/LLM/geminiProxy.ts ================================================ /* eslint-disable no-continue */ /* eslint-disable no-shadow */ import * as https from 'https'; import { TextDecoder } from 'util'; import tunnel from 'tunnel'; const agent = tunnel.httpsOverHttp({ proxy: { host: '127.0.0.1', port: 7890, }, }); export const createChatCompletion = (options: { model?: string; maxTokens?: number; messages: { role: 'system' | 'user' | 'assistant'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; }[]; handleChunk?: (data: { text?: string }) => void; }) => new Promise((resolve, reject) => { let combinedResult = ''; const error = '发生错误:'; const request = https.request( { hostname: 'api.gemini-chat.pro', // https://github.com/blacksev/Gemini-Next-Web API port: 443, path: '/v1/chat/completions', method: 'POST', headers: { 'Content-Type': 'application/json', Referer: 'https://gemini-chat.pro/', Origin: 'https://gemini-chat.pro', }, agent, }, (res) => { let preDataLast = ''; res.on('data', async (chunk) => { const text = new TextDecoder('utf-8').decode(chunk); const data = text.split('\n\n').filter((s) => s); for (let i = 0; i < data.length; i++) { try { let element = data[i]; if (i === data.length - 1 && !data[i].endsWith('}')) { preDataLast = data[i]; continue; } if (element.startsWith('data: ')) { if (element.trim() === 'data:') { // 处理只返回了 data: 的情况 continue; } } else { // 处理没有 data 开头 element = preDataLast + element; } if (element.includes('data: ')) { if (element.includes('[DONE]')) { if (options.handleChunk) { options.handleChunk({ text: '' }); } continue; } // remove 'data: ' const data = JSON.parse(element.replace('data: ', '')); if (data.finish_reason === 'stop') { if (options.handleChunk) { options.handleChunk({ text: '' }); } continue; } const openaiRes = data.choices[0].delta.content; console.log(openaiRes); if (openaiRes) { if (options.handleChunk) { options.handleChunk({ text: openaiRes.replaceAll('\\n', '\n'), }); } combinedResult += openaiRes; } } else { console.log('no includes data: ', element); } } catch (e) { console.error({ e: (e as Error).toString(), element: data[i], }); // error = (e as Error).toString(); } } }); res.on('error', (e) => { if (options.handleChunk) { options.handleChunk({ text: e.toString(), }); } reject(e); }); res.on('end', () => { if (error !== '发生错误:') { if (options.handleChunk) { options.handleChunk({ text: error, }); } } resolve(combinedResult || error); }); }, ); const body = { model: options.model, messages: options.messages, stream: true, max_tokens: options.maxTokens, presence_penalty: 0, temperature: 0.5, top_p: 1, frequency_penalty: 0, }; request.on('error', (error) => { // eslint-disable-next-line no-unused-expressions options.handleChunk && options.handleChunk({ text: error.toString() }); resolve(error.toString()); // emitter.emit('chatGPTComplete', error.toString()); }); request.write(JSON.stringify(body)); request.end(); }); ================================================ FILE: share/LLM/index.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { oneAPIConfig } from '../utils/shareData'; import { showWebView } from '../WebView'; import { emitter } from '../utils/emitter'; import * as gemini from './gemini'; import * as geminiProxy from './geminiProxy'; import * as openai from './openai'; const API_KEY = 'lowcode.GeminiKey'; type Message = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const createChatCompletion = async (options: { messages: Message; handleChunk?: (data: { text?: string }) => void; lowcodeContext: CompileContext; llm?: string; }) => { const config = oneAPIConfig(); if (options.llm === 'gemini') { const apiKey = config?.apiKey; if (!apiKey) { if (options.handleChunk) { options.handleChunk({ text: 'Please config your api key' }); } return 'Please config your api key'; } const res = await gemini.createChatCompletion({ messages: options.messages, model: options.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gemini-pro-vision' : 'gemini-pro', apiKey, handleChunk(data) { if (options.handleChunk) { options.handleChunk(data); emitter.emit('chatGPTChunck', data); } }, proxyUrl: config?.proxyUrl || 'http://127.0.0.1:7890', }); emitter.emit('chatGPTComplete', res); return res; } if (options.llm === 'geminiProxy') { const res = await geminiProxy.createChatCompletion({ messages: options.messages, model: options.messages.some( (s) => Array.isArray(s.content) && s.content.some((c) => c.type === 'image_url'), ) ? 'gemini-pro-vision' : 'gemini-pro', maxTokens: 4096, handleChunk(data) { if (options.handleChunk) { options.handleChunk(data); emitter.emit('chatGPTChunck', data); } }, }); emitter.emit('chatGPTComplete', res); return res; } const res = await openai.createChatCompletion({ messages: options.messages, handleChunk(data) { if (options.handleChunk) { options.handleChunk(data); emitter.emit('chatGPTChunck', data); } }, }); emitter.emit('chatGPTComplete', res); return res; }; export const createChatCompletionShowWebView = (options: { messages: Message; handleChunk?: (data: { text?: string }) => void; lowcodeContext: CompileContext; llm?: string; htmlForWebview?: string; }) => { // 打开 webview,使用 emitter 监听结果,把结果回传给 script showWebView({ key: 'main', lowcodeContext: options.lowcodeContext, task: { task: 'askLLM', data: { content: options.messages.map((m) => m.content).join('\n'), llm: options.llm, }, }, htmlForWebview: options.htmlForWebview, }); return new Promise((resolve) => { emitter.on('chatGPTChunck', (data) => { if (options.handleChunk) { options.handleChunk(data); } }); emitter.on('chatGPTComplete', (data) => { resolve(data); emitter.off('chatGPTChunck'); emitter.off('chatGPTComplete'); }); }); }; ================================================ FILE: share/LLM/openai.ts ================================================ /* eslint-disable no-continue */ /* eslint-disable no-shadow */ import * as https from 'https'; import * as http from 'http'; import { TextDecoder } from 'util'; import { workspace } from 'vscode'; import { oneAPIConfig } from '../utils/shareData'; type ChatGPTConfig = { hostname: string; apiPath: string; apiKey: string; model: string; maxTokens: number; temperature: number; }; export const getChatGPTConfig: () => ChatGPTConfig = () => { const hostname = workspace .getConfiguration('lowcode') .get('hostname', 'api.openai.com'); const apiPath = workspace .getConfiguration('lowcode') .get('apiPath', '/v1/chat/completions'); const apiKey = workspace .getConfiguration('lowcode') .get('apiKey', ''); const model = workspace .getConfiguration('lowcode') .get('model', 'gpt-3.5-turbo'); const maxTokens = workspace .getConfiguration('lowcode') .get('maxTokens', 2000); const temperature = workspace .getConfiguration('lowcode') .get('temperature', 0.3); return { hostname, apiPath, apiKey, model, maxTokens, temperature, }; }; export const createChatCompletion = (options: { apiKey?: string; model?: string; maxTokens?: number; hostname?: string; apiPath?: string; port?: number; notHttps?: boolean; messages: { role: 'system' | 'user' | 'assistant'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; }[]; handleChunk?: (data: { text?: string }) => void; }) => new Promise((resolve, reject) => { const config = oneAPIConfig(); let combinedResult = ''; const error = '发生错误:'; const h = config?.notHttps || options.notHttps ? http : https; const request = h.request( { hostname: options.hostname || config?.hostname || getChatGPTConfig().hostname || 'api.openai.com', port: options.port || config?.port || 443, path: options.apiPath || config?.apiPath || getChatGPTConfig().apiPath || '/v1/chat/completions', method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ options.apiKey || config?.apiKey || getChatGPTConfig().apiKey }`, }, }, (res) => { let preDataLast = ''; res.on('data', async (chunk) => { const text = new TextDecoder('utf-8').decode(chunk); const data = text.split('\n\n').filter((s) => s); for (let i = 0; i < data.length; i++) { try { let element = data[i]; if (i === data.length - 1 && !data[i].endsWith('}')) { preDataLast = data[i]; continue; } if (element.startsWith('data: ')) { if (element.trim() === 'data:') { // 处理只返回了 data: 的情况 continue; } } else { // 处理没有 data 开头 element = preDataLast + element; } if (element.includes('data: ')) { if (element.includes('[DONE]')) { if (options.handleChunk) { options.handleChunk({ text: '' }); } continue; } // remove 'data: ' const data = JSON.parse(element.replace('data: ', '')); if (data.finish_reason === 'stop') { if (options.handleChunk) { options.handleChunk({ text: '' }); } continue; } const openaiRes = data.choices[0].delta.content; console.log(openaiRes); if (openaiRes) { if (options.handleChunk) { options.handleChunk({ text: openaiRes.replaceAll('\\n', '\n'), }); } combinedResult += openaiRes; } } else { console.log('no includes data: ', element); } } catch (e) { console.error({ e: (e as Error).toString(), element: data[i], }); // error = (e as Error).toString(); } } }); res.on('error', (e) => { if (options.handleChunk) { options.handleChunk({ text: e.toString(), }); } reject(e); }); res.on('end', () => { if (error !== '发生错误:') { if (options.handleChunk) { options.handleChunk({ text: error, }); } } resolve(combinedResult || error); }); }, ); const body = { model: options.model || config?.model || getChatGPTConfig().model, messages: options.messages, stream: true, max_tokens: options.maxTokens || config?.maxTokens || getChatGPTConfig().maxTokens, }; request.on('error', (error) => { // eslint-disable-next-line no-unused-expressions options.handleChunk && options.handleChunk({ text: error.toString() }); resolve(error.toString()); }); request.write(JSON.stringify(body)); request.end(); }); ================================================ FILE: share/LLM/openaiV2.ts ================================================ /* eslint-disable no-continue */ /* eslint-disable no-shadow */ import * as https from 'https'; import * as http from 'http'; import { TextDecoder } from 'util'; import { oneAPIConfig } from '../utils/shareData'; export const createChatCompletion = (options: { apiKey?: string; hostname?: string; apiPath?: string; port?: number; notHttps?: boolean; model?: string; maxTokens?: number; messages: { role: 'system' | 'user' | 'assistant'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; }[]; handleChunk?: (data: { text?: string }) => void; }) => new Promise((resolve, reject) => { const config = oneAPIConfig(); let combinedResult = ''; const error = '发生错误:'; const h = options.notHttps || config?.notHttps ? http : https; const request = h.request( { hostname: options.hostname || config?.hostname || 'api.openai.com', port: options.port || config?.port || 443, path: options.apiPath || config?.apiPath || '/v1/chat/completions', method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${options.apiKey || config?.apiKey}`, }, }, (res) => { let preDataLast = ''; res.on('data', async (chunk) => { const text = new TextDecoder('utf-8').decode(chunk); const data = text.split('\n\n').filter((s) => s); for (let i = 0; i < data.length; i++) { try { let element = data[i]; if (i === data.length - 1 && !data[i].endsWith('}')) { preDataLast = data[i]; continue; } if (element.startsWith('data: ')) { if (element.trim() === 'data:') { // 处理只返回了 data: 的情况 continue; } } else { // 处理没有 data 开头 element = preDataLast + element; } if (element.includes('data: ')) { if (element.includes('[DONE]')) { if (options.handleChunk) { options.handleChunk({ text: '' }); } continue; } // remove 'data: ' const data = JSON.parse(element.replace('data: ', '')); if (data.finish_reason === 'stop') { if (options.handleChunk) { options.handleChunk({ text: '' }); } continue; } const openaiRes = data.choices[0].delta.content; console.log(openaiRes); if (openaiRes) { if (options.handleChunk) { options.handleChunk({ text: openaiRes.replaceAll('\\n', '\n'), }); } combinedResult += openaiRes; } } else { console.log('no includes data: ', element); } } catch (e) { console.error({ e: (e as Error).toString(), element: data[i], }); // error = (e as Error).toString(); } } }); res.on('error', (e) => { if (options.handleChunk) { options.handleChunk({ text: e.toString(), }); } reject(e); }); res.on('end', () => { if (error !== '发生错误:') { if (options.handleChunk) { options.handleChunk({ text: error, }); } } resolve(combinedResult || error); }); }, ); const body = { model: options.model || config?.model || 'gpt-3.5-turbo', messages: options.messages, stream: true, max_tokens: options.maxTokens || config?.maxTokens || 2000, }; request.on('error', (error) => { // eslint-disable-next-line no-unused-expressions options.handleChunk && options.handleChunk({ text: error.toString() }); resolve(error.toString()); }); request.write(JSON.stringify(body)); request.end(); }); ================================================ FILE: share/TypeChatSlim/index.ts ================================================ import * as ts from 'typescript'; import { StatusBarAlignment, window } from 'vscode'; import { Success, Error, error, success } from './result'; const libText = `interface Array { length: number, [n: number]: T } interface Object { toString(): string } interface Function { prototype: unknown } interface CallableFunction extends Function {} interface NewableFunction extends Function {} interface String { readonly length: number } interface Boolean { valueOf(): boolean } interface Number { valueOf(): number } interface RegExp { test(string: string): boolean }`; export async function translate(option: { schema: string; typeName: string; request: string; createChatCompletion: (options: { messages: { role: 'system' | 'user' | 'assistant'; content: string; }[]; handleChunk?: ((data: { text?: string }) => void) | undefined; showWebview?: boolean; }) => Promise; showWebview?: boolean; /** * @description 完整的 prompt,若提供则内部不再组合 prompt * @type {string} */ completePrompt?: string; extendValidate?: (jsonObject: T) => Error | Success; /** * @description 重试次数,默认为重试 3 次 * @type {number} */ tryCount?: number; }) { let requestPrompt = option.completePrompt || `You are a service that translates user requests into JSON objects of type "${option.typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${option.schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${option.request}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; let tryCount = 1; // eslint-disable-next-line no-unreachable-loop, no-constant-condition while (true) { // eslint-disable-next-line no-await-in-loop let responseText = await option.createChatCompletion({ messages: [{ role: 'user', content: requestPrompt }], handleChunk: undefined, showWebview: option.showWebview, }); let match = /```json([\s\S]*?)```/g.exec(responseText); if (match && match[1]) { // eslint-disable-next-line prefer-destructuring responseText = match[1]; } else { match = /```([\s\S]*?)```/g.exec(responseText); } if (match && match[1]) { // eslint-disable-next-line prefer-destructuring responseText = match[1]; } let validation = validate( responseText.replace(/```json/g, '').replace(/```/g, ''), option.schema, option.typeName, ); if (validation.success) { // 走额外的校验 if (option.extendValidate) { validation = option.extendValidate(validation.data); if (validation.success) { return validation; } } else { return validation; } } if (tryCount > (option.tryCount || 3)) { return validation; } requestPrompt += `${responseText}\n${createRepairPrompt( validation.message, )}`; tryCount++; } } function createRepairPrompt(validationError: string) { return ( `The JSON object is invalid for the following reason:\n` + `"""\n${validationError}\n"""\n` + `The following is a revised JSON object:\n` ); } export function validate( jsonText: string, schema: string, typeName: string, ) { let jsonObject; try { let match = /```json([\s\S]*?)```/g.exec(jsonText); if (match && match[1]) { // eslint-disable-next-line prefer-destructuring jsonText = match[1]; } else { match = /```([\s\S]*?)```/g.exec(jsonText); } if (match && match[1]) { // eslint-disable-next-line prefer-destructuring jsonText = match[1]; } jsonObject = JSON.parse( jsonText.replace(/```json/g, '').replace(/```/g, ''), ) as object; } catch (e) { return error(e instanceof SyntaxError ? e.message : 'JSON parse error'); } stripNulls(jsonObject); const moduleResult = `import { ${typeName} } from './schema';\nconst json: ${typeName} = ${JSON.stringify( jsonObject, undefined, 2, )};\n`; const program = createProgramFromModuleText({ moduleText: moduleResult, schema, }); const syntacticDiagnostics = program.getSyntacticDiagnostics(); const programDiagnostics = syntacticDiagnostics.length ? syntacticDiagnostics : program.getSemanticDiagnostics(); if (programDiagnostics.length) { const diagnostics = programDiagnostics .map((d) => typeof d.messageText === 'string' ? d.messageText : d.messageText.messageText, ) .join('\n'); return error(diagnostics); } return success(jsonObject); } function createProgramFromModuleText(option: { moduleText: string; oldProgram?: ts.Program; schema: string; }) { const fileMap = new Map([ createFileMapEntry('/lib.d.ts', libText), createFileMapEntry('/schema.ts', option.schema), createFileMapEntry('/json.ts', option.moduleText), ]); const host: ts.CompilerHost = { getSourceFile: (fileName) => fileMap.get(fileName), getDefaultLibFileName: () => 'lib.d.ts', writeFile: () => {}, getCurrentDirectory: () => '/', getCanonicalFileName: (fileName) => fileName, useCaseSensitiveFileNames: () => true, getNewLine: () => '\n', fileExists: (fileName) => fileMap.has(fileName), readFile: (fileName) => '', }; const options: ts.CompilerOptions = { ...ts.getDefaultCompilerOptions(), strict: true, skipLibCheck: true, noLib: true, types: [], }; return ts.createProgram( Array.from(fileMap.keys()), options, host, option.oldProgram, ); } function createFileMapEntry( filePath: string, fileText: string, ): [string, ts.SourceFile] { return [ filePath, ts.createSourceFile(filePath, fileText, ts.ScriptTarget.Latest), ]; } function stripNulls(obj: any) { let keysToDelete: string[] | undefined; // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const k in obj) { const value = obj[k]; if (value === null) { (keysToDelete ??= []).push(k); } else { if (Array.isArray(value)) { if (value.some((x) => x === null)) { obj[k] = value.filter((x) => x !== null); } } if (typeof value === 'object') { stripNulls(value); } } } if (keysToDelete) { // eslint-disable-next-line no-restricted-syntax for (const k of keysToDelete) { delete obj[k]; } } } ================================================ FILE: share/TypeChatSlim/result.ts ================================================ export type Success = { success: true; data: T }; export type Error = { success: false; message: string }; export type Result = Success | Error; export function success(data: T): Success { return { success: true, data }; } export function error(message: string): Error { return { success: false, message }; } export function getData(result: Result) { if (result.success) { return result.data; } throw new Error(result.message); } ================================================ FILE: share/TypeChatSlim/utools.ts ================================================ import * as ts from 'typescript'; import { Success, Error, error, success } from './result'; const libText = `interface Array { length: number, [n: number]: T } interface Object { toString(): string } interface Function { prototype: unknown } interface CallableFunction extends Function {} interface NewableFunction extends Function {} interface String { readonly length: number } interface Boolean { valueOf(): boolean } interface Number { valueOf(): number } interface RegExp { test(string: string): boolean }`; export async function translate(option: { schema: string; typeName: string; request: string; createChatCompletion: (options: { messages: { role: 'system' | 'user' | 'assistant'; content: string; }[]; handleChunk?: ((data: { text?: string }) => void) | undefined; }) => Promise; showWebview?: boolean; scriptFile: string; /** * @description 完整的 prompt,若提供则内部不再组合 prompt * @type {string} */ completePrompt?: string; extendValidate?: (jsonObject: T) => Error | Success; /** * @description 重试次数,默认为重试 3 次 * @type {number} */ tryCount?: number; }) { let requestPrompt = option.completePrompt || `You are a service that translates user requests into JSON objects of type "${option.typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${option.schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${option.request}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; let tryCount = 1; // eslint-disable-next-line no-unreachable-loop, no-constant-condition while (true) { // eslint-disable-next-line no-await-in-loop const responseText = await option .createChatCompletion({ messages: [{ role: 'user', content: requestPrompt }], handleChunk: undefined, }) .finally(() => {}); let validation = validate(responseText, option.schema, option.typeName); if (validation.success) { // 走额外的校验 if (option.extendValidate) { validation = option.extendValidate(validation.data); if (validation.success) { return { ...validation, responseText }; } } else { return { ...validation, responseText }; } } requestPrompt += `${responseText}\n${createRepairPrompt( validation.message, )}`; if (option.showWebview) { // eslint-disable-next-line no-loop-func // setTimeout(() => { // utools.redirect(['lowcode', 'lowcode'], { // type: 'text', // data: JSON.stringify({ // scriptFile: option.scriptFile, // route: '/chat', // content: requestPrompt, // }), // }); // }, 1000); return { ...validation, responseText }; } if (tryCount > (option.tryCount || 3)) { return { ...validation, responseText }; } tryCount++; } } function createRepairPrompt(validationError: string) { return ( `The JSON object is invalid for the following reason:\n` + `"""\n${validationError}\n"""\n` + `The following is a revised JSON object:\n` ); } export function validate( jsonText: string, schema: string, typeName: string, ) { let jsonObject; try { let match = /```json([\s\S]*?)```/g.exec(jsonText); if (match && match[1]) { // eslint-disable-next-line prefer-destructuring jsonText = match[1]; } else { match = /```([\s\S]*?)```/g.exec(jsonText); } if (match && match[1]) { // eslint-disable-next-line prefer-destructuring jsonText = match[1]; } jsonObject = JSON.parse( jsonText.replace(/```json/g, '').replace(/```/g, ''), ) as object; } catch (e) { return error(e instanceof SyntaxError ? e.message : 'JSON parse error'); } stripNulls(jsonObject); const moduleResult = `import { ${typeName} } from './schema';\nconst json: ${typeName} = ${JSON.stringify( jsonObject, undefined, 2, )};\n`; const program = createProgramFromModuleText({ moduleText: moduleResult, schema, }); const syntacticDiagnostics = program.getSyntacticDiagnostics(); const programDiagnostics = syntacticDiagnostics.length ? syntacticDiagnostics : program.getSemanticDiagnostics(); if (programDiagnostics.length) { const diagnostics = programDiagnostics .map((d) => typeof d.messageText === 'string' ? d.messageText : d.messageText.messageText, ) .join('\n'); return error(diagnostics); } return success(jsonObject); } function createProgramFromModuleText(option: { moduleText: string; oldProgram?: ts.Program; schema: string; }) { const fileMap = new Map([ createFileMapEntry('/lib.d.ts', libText), createFileMapEntry('/schema.ts', option.schema), createFileMapEntry('/json.ts', option.moduleText), ]); const host: ts.CompilerHost = { getSourceFile: (fileName) => fileMap.get(fileName), getDefaultLibFileName: () => 'lib.d.ts', writeFile: () => {}, getCurrentDirectory: () => '/', getCanonicalFileName: (fileName) => fileName, useCaseSensitiveFileNames: () => true, getNewLine: () => '\n', fileExists: (fileName) => fileMap.has(fileName), readFile: (fileName) => '', }; const options: ts.CompilerOptions = { ...ts.getDefaultCompilerOptions(), strict: true, skipLibCheck: true, noLib: true, types: [], }; return ts.createProgram( Array.from(fileMap.keys()), options, host, option.oldProgram, ); } function createFileMapEntry( filePath: string, fileText: string, ): [string, ts.SourceFile] { return [ filePath, ts.createSourceFile(filePath, fileText, ts.ScriptTarget.Latest), ]; } function stripNulls(obj: any) { let keysToDelete: string[] | undefined; // eslint-disable-next-line no-restricted-syntax, guard-for-in for (const k in obj) { const value = obj[k]; if (value === null) { (keysToDelete ??= []).push(k); } else { if (Array.isArray(value)) { if (value.some((x) => x === null)) { obj[k] = value.filter((x) => x !== null); } } if (typeof value === 'object') { stripNulls(value); } } } if (keysToDelete) { // eslint-disable-next-line no-restricted-syntax for (const k of keysToDelete) { delete obj[k]; } } } ================================================ FILE: share/WebView/callback.ts ================================================ import * as vscode from 'vscode'; export function invokeCallback( webview: vscode.Webview, cbid: string, res: T, ) { webview.postMessage({ cmd: 'vscodeCallback', cbid, data: res, code: 200, }); } export function invokeLLMChunkCallback( webview: vscode.Webview, cbid: string, res: T, ) { webview.postMessage({ cmd: 'vscodeLLMChunkCallback', task: 'handleLLMChunk', cbid, data: res, code: 200, }); } export function invokeErrorCallback( webview: vscode.Webview, cbid: string, res: any, ) { webview.postMessage({ cmd: 'vscodeCallback', cbid, data: res, code: 400, }); } ================================================ FILE: share/WebView/controllers/alert.ts ================================================ import { window } from 'vscode'; import { IMessage } from '../type'; const alert = { alert: (message: IMessage) => { window.showErrorMessage(message.data); return '来自vscode的响应'; }, }; export default alert; ================================================ FILE: share/WebView/controllers/dynamicForm.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { IMessage } from '../type'; import { getDynamicFormConfig } from '../../../share/utils/dynamicForm'; export const getDynamicForm = ( message: IMessage, context: { webview: vscode.Webview; task: { task: string; data?: any }; } & CompileContext, ) => { const { materialPath } = context; const config = getDynamicFormConfig({ vscodeMaterialPath: materialPath }); return { schema: config.schema, scripts: config.scripts, }; }; ================================================ FILE: share/WebView/controllers/llm.ts ================================================ import * as vscode from 'vscode'; import { CompileContext } from 'lowcode-context'; import { createChatCompletion } from '../../LLM'; import { IMessage } from '../type'; import { invokeLLMChunkCallback } from '../callback'; type LLMMessage = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const askLLM = async ( message: IMessage<{ messages: LLMMessage; llm?: 'gemini' | 'geminiProxy' }>, lowcodeContext: { webview: vscode.Webview; } & CompileContext, ) => { const res = await createChatCompletion({ messages: message.data.messages, lowcodeContext, handleChunk(data) { invokeLLMChunkCallback(lowcodeContext.webview, message.cbid, { content: data.text, }); }, llm: message.data.llm, }); return { content: res, }; }; ================================================ FILE: share/WebView/controllers/script.ts ================================================ /* eslint-disable no-eval */ import * as path from 'path'; import * as vscode from 'vscode'; import * as fs from 'fs-extra'; import { CompileContext } from 'lowcode-context'; import { IMessage } from '../type'; export const runScript = async ( message: IMessage<{ materialPath: string; script: string; params: string; }>, context: { webview: vscode.Webview; task: { task: string; data?: any }; } & CompileContext, ) => { const scriptFile = path.join(message.data.materialPath, 'script/index.js'); if (fs.existsSync(scriptFile)) { delete eval('require').cache[eval('require').resolve(scriptFile)]; const script = eval('require')(scriptFile); if (script[message.data.script]) { const c = { ...context, params: message.data.params, materialPath: message.data.materialPath, }; const scriptRes = await script[message.data.script](c); return scriptRes; } throw new Error(`方法: ${message.data.script} 不存在`); } else { throw new Error(`脚本文件不存在`); } }; ================================================ FILE: share/WebView/controllers/task.ts ================================================ import * as vscode from 'vscode'; import { IMessage } from '../type'; export const getTask = async ( message: IMessage, context: { webview: vscode.Webview; task: { task: string; data?: any }; }, ) => context.task; ================================================ FILE: share/WebView/index.ts ================================================ /* eslint-disable no-underscore-dangle */ import * as vscode from 'vscode'; import { window } from 'vscode'; import type { CompileContext } from 'lowcode-context'; import { routes } from './routes'; import { invokeCallback, invokeErrorCallback } from './callback'; type WebViewKeys = 'main' | string; let webviewPanels: { key: WebViewKeys; panel: vscode.WebviewPanel; disposables: vscode.Disposable[]; }[] = []; const getHtmlForWebview = (dev = false) => { if (dev) { return ` Vite + React + TS
`; } return `
`; }; export const showWebView = (options: { key: WebViewKeys; lowcodeContext: CompileContext; title?: string; viewColumn?: vscode.ViewColumn; /** * webview 打开后执行命令,比如转到指定路由 */ task?: { task: string; data?: any }; htmlForWebview?: string; routes?: Record; }) => { const webview = webviewPanels.find((s) => s.key === options.key); if (webview) { webview.panel.reveal(); if (options.task) { webview.panel.webview.postMessage({ cmd: 'vscodePushTask', task: options.task.task, data: options.task.data, }); } } else { // 创建 webview 的时候,设置之前 focus 的 activeTextEditor // if (vscode.window.activeTextEditor) { // setLastActiveTextEditorId((vscode.window.activeTextEditor as any).id); // } const panel = vscode.window.createWebviewPanel( 'lowcode', options.title || 'LOW-CODE可视化', { viewColumn: options.viewColumn || vscode.ViewColumn.Two, preserveFocus: true, }, { enableScripts: true, // localResourceRoots: [ // vscode.Uri.file(path.join(getExtensionPath(), 'webview-dist')), // ], retainContextWhenHidden: true, // webview被隐藏时保持状态,避免被重置 }, ); // panel.iconPath = vscode.Uri.file( // path.join(getExtensionPath(), 'asset', 'icon.png'), // ); panel.webview.html = options.htmlForWebview ? options.htmlForWebview : getHtmlForWebview(); const disposables: vscode.Disposable[] = []; panel.webview.onDidReceiveMessage( async (message: { cmd: string; cbid: string; data: any; skipError?: boolean; }) => { if (options.routes && options.routes[message.cmd]) { try { const res = await options.routes[message.cmd](message, { webview: panel.webview, webviewKey: options.key, task: options.task, ...options.lowcodeContext, }); invokeCallback(panel.webview, message.cbid, res); } catch (ex: any) { if (!message.skipError) { window.showErrorMessage(ex.toString()); } invokeErrorCallback(panel.webview, message.cbid, ex); } } else if (routes[message.cmd]) { try { const res = await routes[message.cmd](message, { webview: panel.webview, webviewKey: options.key, task: options.task, ...options.lowcodeContext, }); invokeCallback(panel.webview, message.cbid, res); } catch (ex: any) { if (!message.skipError) { window.showErrorMessage(ex.toString()); } invokeErrorCallback(panel.webview, message.cbid, ex); } } else { invokeErrorCallback( panel.webview, message.cbid, `未找到名为 ${message.cmd} 回调方法!`, ); vscode.window.showWarningMessage( `未找到名为 ${message.cmd} 回调方法!`, ); } }, null, disposables, ); panel.onDidDispose( () => { panel.dispose(); while (disposables.length) { const x = disposables.pop(); if (x) { x.dispose(); } } webviewPanels = webviewPanels.filter((s) => s.key !== options.key); }, null, disposables, ); webviewPanels.push({ key: options.key, panel, disposables, }); if (options.task) { setTimeout(() => { panel.webview.postMessage({ cmd: 'vscodePushTask', task: options.task!.task, data: options.task!.data, }); }, 500); } } }; export const closeWebView = (key: WebViewKeys) => { const webviewPanel = webviewPanels.find((s) => s.key === key); webviewPanel?.panel.dispose(); webviewPanels = webviewPanels.filter((s) => s.key !== key); }; ================================================ FILE: share/WebView/routes/index.ts ================================================ import alert from '../controllers/alert'; import * as task from '../controllers/task'; import * as script from '../controllers/script'; import * as llm from '../controllers/llm'; import * as dynamicForm from '../controllers/dynamicForm'; export const routes: Record = { alert: alert.alert, getTask: task.getTask, runScript: script.runScript, askLLM: llm.askLLM, getDynamicForm: dynamicForm.getDynamicForm, }; ================================================ FILE: share/WebView/type.ts ================================================ export interface IMessage { cmd: string; cbid: string; data: T; } ================================================ FILE: share/clearCache.ts ================================================ import fs from 'fs-extra'; export const clearCache = (path: string, clearShare = true) => { getAllFiles(path).forEach((file) => { if (!file.includes('script/index.js')) { delete require.cache[require.resolve(file)]; } }); if (clearShare) { getAllFiles(__dirname).forEach((file) => { if (!file.includes('clearCache')) { delete require.cache[require.resolve(file)]; } }); } }; // 递归获取文件夹下的所有文件 function getAllFiles(dirPath: string) { const files = fs.readdirSync(dirPath); let result: string[] = []; // eslint-disable-next-line no-restricted-syntax for (const file of files) { const filePath = `${dirPath}/${file}`; // eslint-disable-next-line no-await-in-loop const stats = fs.statSync(filePath); if (stats.isDirectory()) { result = result.concat(getAllFiles(filePath)); } else { result.push(filePath); } } return result; } ================================================ FILE: share/uTools/webviewBaseController.ts ================================================ import { validate } from '@share/TypeChatSlim/utools'; import { getDynamicFormConfig } from '@share/utils/dynamicForm'; import { askChatGPT as askOpenai, getBlockJsonValidSchema, } from '@share/utils/uTools'; export type MethodHandle = (data: { method: string; params: string; model: object; scriptFile: string; }) => Promise<{ /** 立即更新 model */ updateModelImmediately?: boolean; /** 仅更新参数 */ onlyUpdateParams?: boolean; /** 要更新的参数 */ params?: string; /** 打开 LLM Chat,DynamicForm Page使用 */ showChat?: boolean; /** 关闭表单界面, Chat Page 使用 */ closeForm?: boolean; /** LLM Chat Content */ chatContent?: string; model: object; }>; // #region 获取动态表单配置 type GetDynamicForm = (data: { scriptFile: string }) => Promise<{ schema: object; scripts: { method: string; remark: string }[]; }>; export const getDynamicForm: GetDynamicForm = (data) => { const config = getDynamicFormConfig({ utoolsScriptFile: data.scriptFile }); return Promise.resolve({ schema: config.schema, scripts: config.scripts, }); }; // #endregion export type LLMMessage = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; // #region 动态表单页面 LLM 交互 export type AskChatGPTForDynamicFormPageWebviewData = { params: string; model: object; scriptFile: string; messages: LLMMessage; handleChunk: (chunck: string) => void; }; type AskChatGPTForDynamicFormPage = ( data: AskChatGPTForDynamicFormPageWebviewData & { /** 用于校验 json 数据的 TS 类型名称 */ validateJsonSchemaTypeName?: string; }, ) => Promise<{ /** LLM 返回内容 */ content: string; /** 立即更新 model */ updateModelImmediately: boolean; /** 仅更新参数 */ onlyUpdateParams: boolean; /** 要更新的参数 */ params?: string; /** 关闭 LLM Chat */ closeChat?: boolean; model: object; }>; export const baseAskChatGPTForDynamicFormPage: AskChatGPTForDynamicFormPage = async (data) => { const res = await askOpenai({ messages: data.messages, handleChunk: data.handleChunk, }); if (!data.validateJsonSchemaTypeName) { return { content: res.content, updateModelImmediately: false, onlyUpdateParams: true, params: data.params, closeChat: false, model: data.model, }; } const valid = validate( res.content, getBlockJsonValidSchema(data.scriptFile), data.validateJsonSchemaTypeName, ); if (valid.success) { return { content: res.content, updateModelImmediately: false, onlyUpdateParams: false, params: data.params, closeChat: true, model: valid.data, }; } data.handleChunk(` ${valid.message}`); return { content: res.content, updateModelImmediately: false, onlyUpdateParams: true, params: valid.message, closeChat: false, model: data.model, }; }; // #endregion // #region 处理页面 ChatGPT 请求 export type AskChatGPTData = { params: string; model: object; scriptFile: string; messages: LLMMessage; handleChunk: (chunck: string) => void; }; export type AskChatGPT = ( data: AskChatGPTData & { /** 用于校验 json 数据的 TS 类型名称 */ validateJsonSchemaTypeName?: string; }, ) => Promise<{ /** LLM 返回内容 */ content: string; /** 立即更新 model */ updateModelImmediately?: boolean; /** 仅更新参数 */ onlyUpdateParams?: boolean; /** 要更新的参数 */ params?: string; /** 打开表单界面, Chat Page 使用 */ showForm?: boolean; /** 表单数据 */ model?: object; }>; export const askChatGPT: AskChatGPT = (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; model?: object; }) => askOpenai({ ...data, model: undefined }); // #endregion ================================================ FILE: share/utils/clipboardImage.ts ================================================ import * as path from 'path'; import { spawn } from 'child_process'; import { homedir } from 'os'; import * as fs from 'fs-extra'; const saveClipboardImageToPath = (projectPath: string, imagePath: string) => { return new Promise((resolve, reject) => { const { platform } = process; if (platform === 'win32') { // Windows const scriptPath = path.join( projectPath, '/scripts/ClipboardImage/pc.ps1', ); let command = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; const powershellExisted = fs.existsSync(command); if (!powershellExisted) { command = 'powershell'; } const powershell = spawn(command, [ '-noprofile', '-noninteractive', '-nologo', '-sta', '-executionpolicy', 'unrestricted', '-windowstyle', 'hidden', '-file', scriptPath, imagePath, ]); powershell.on('error', (e: { code: string }) => { if (e.code === 'ENOENT') { reject( `The powershell command is not in you PATH environment variables. Please add it and retry.`, ); } else { reject(e); } }); powershell.on('exit', (code, signal) => { // console.log('exit', code, signal); }); powershell.stdout.on('data', (data: Buffer) => { // cb(imagePath, data.toString().trim()); resolve(imagePath); }); } else if (platform === 'darwin') { // Mac const scriptPath = path.join( projectPath, '/scripts/ClipboardImage/mac.applescript', ); const ascript = spawn('osascript', [scriptPath, imagePath]); ascript.on('error', (e) => { reject(e); }); ascript.on('exit', (code, signal) => { // console.log('exit',code,signal); }); ascript.stdout.on('data', (data: Buffer) => { // cb(imagePath, data.toString().trim()); resolve(imagePath); }); } else { // Linux const scriptPath = path.join( projectPath, '/scripts/ClipboardImage/linux.sh', ); const ascript = spawn('sh', [scriptPath, imagePath]); ascript.on('error', (e) => { reject(e); }); ascript.on('exit', (code, signal) => { // console.log('exit',code,signal); }); ascript.stdout.on('data', (data: Buffer) => { const result = data.toString().trim(); if (result === 'no xclip') { reject('You need to install xclip command first.'); } resolve(imagePath); // cb(imagePath, result); }); } }); }; export const getClipboardImage = async (projectPath: string) => { const imagePath = path.join(homedir(), '.lowcode', 'clipboardImage.png'); if (fs.existsSync(imagePath)) { fs.removeSync(imagePath); } await saveClipboardImageToPath(projectPath, imagePath); if (fs.existsSync(imagePath)) { const base64 = fs.readFileSync(imagePath, 'base64'); return `data:image/png;base64,${base64}`; } return ''; }; ================================================ FILE: share/utils/config.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { workspace } from 'vscode'; import { getFileContent } from './file'; export type Config = { yapi?: { domain?: string; projects?: { name: string; token: string; domain: string; }[]; }; mock?: { mockNumber?: string; mockBoolean?: string; mockString?: string; mockKeyWordEqual?: { key: string; value: string; }[]; mockKeyWordLike?: { key: string; value: string; }[]; }; commonlyUsedBlock?: string[]; }; export const getConfig: () => Config = () => { let config: Config; if (fs.existsSync(path.join(workspace.rootPath || '', '.lowcoderc'))) { config = JSON.parse(getFileContent('.lowcoderc') || '{}'); config.yapi?.projects?.forEach((s) => { s.domain = s.domain || config.yapi?.domain || ''; }); } else { config = {}; } return config; }; ================================================ FILE: share/utils/dynamicForm.ts ================================================ import path from 'path'; import fs from 'fs'; /** utoolsScriptFile 和 vscodeMaterialPath 必须传入一个 */ export const getDynamicFormConfig = (data: { utoolsScriptFile?: string; vscodeMaterialPath?: string; model?: object; }) => { let configPath = ''; if (data.utoolsScriptFile) { configPath = data.utoolsScriptFile .replace('/script/src/mainBundle', '/config') .replace('/script/src/main', '/config'); } if (data.vscodeMaterialPath) { configPath = path.join(data.vscodeMaterialPath, 'config'); } const formConfig: { schema: object; scripts: { method: string; remark: string }[]; } = { schema: {}, scripts: [] }; try { const fullPath = path.join(configPath); let model = {}; let schema = {} as any; let config = { scripts: [] }; try { model = JSON.parse( fs.readFileSync(path.join(fullPath, 'model.json')).toString(), ); } catch {} try { schema = JSON.parse( fs.readFileSync(path.join(fullPath, 'schema.json')).toString(), ); } catch {} try { let configFilePath = path.join(fullPath, 'config.json'); if (!fs.existsSync(configFilePath)) { configFilePath = path.join(fullPath, 'preview.json'); } config = JSON.parse(fs.readFileSync(configFilePath).toString()); } catch {} if (schema.formSchema && schema.formSchema.schema) { schema = schema.formSchema.schema; } if (Object.keys(schema).length > 0) { // 设置 page 默认 name schema.name = 'page'; if (schema.body && Array.isArray(schema.body)) { schema.body.forEach((s: Record) => { if (s.type === 'form') { s.name = 'form'; s.data = data.model || model; } }); } } formConfig.schema = schema; formConfig.scripts = config.scripts; } catch {} return formConfig; }; ================================================ FILE: share/utils/editor.ts ================================================ import { Range, SnippetString, window } from 'vscode'; export const getSelectedText = () => { const { selection, document } = window.activeTextEditor!; return document.getText(selection).trim(); }; export const pasteToEditor = (content: string, isInsertSnippet = true) => { // vscode 本身代码片段语法 if (isInsertSnippet) { return insertSnippet(content); } const { activeTextEditor } = window; if (activeTextEditor === undefined) { throw new Error('无打开文件'); } return activeTextEditor?.edit((editBuilder) => { // editBuilder.replace(activeTextEditor.selection, content); if (activeTextEditor.selection.isEmpty) { editBuilder.insert(activeTextEditor.selection.start, content); } else { editBuilder.replace( new Range( activeTextEditor.selection.start, activeTextEditor.selection.end, ), content, ); } }); }; export const insertSnippet = (content: string) => { const { activeTextEditor } = window; if (activeTextEditor === undefined) { throw new Error('无打开文件'); } return activeTextEditor.insertSnippet(new SnippetString(content)); }; export const getFuncNameAndTypeName = () => { // 这部分代码可以写在模版里,暂时保留 const selectedText = getSelectedText() || ''; let funcName = 'fetch'; let typeName = 'IFetchResult'; if (selectedText) { const splitValue = selectedText.split(' '); funcName = splitValue[0] || funcName; if (splitValue.length > 1 && splitValue[1]) { // eslint-disable-next-line prefer-destructuring typeName = splitValue[1]; } else { typeName = `I${ funcName.charAt(0).toUpperCase() + funcName.slice(1) }Result`; } } return { funcName, typeName, rawSelectedText: selectedText, }; }; ================================================ FILE: share/utils/ejs.ts ================================================ import * as path from 'path'; import * as ejs from 'ejs'; import glob from 'glob'; import * as fse from 'fs-extra'; export type YapiInfo = { query_path: { path: string }; method: string; title: string; project_id: number; req_params: { name: string; desc: string; }[]; _id: number; req_query: { required: '0' | '1'; name: string }[]; res_body_type: 'raw' | 'json'; res_body: string; username: string; }; export type Model = { type: string; requestBodyType?: string; funcName: string; typeName: string; inputValues: string[]; api?: YapiInfo; yapiDomain?: string; mockCode: string; mockData: string; jsonData: any; jsonKeys?: string[]; rawSelectedText: string; // 编辑器中选中的原始文本 rawClipboardText: string; // 系统剪切板中的原始文本 activeTextEditorFilePath?: string; // 当前打开文件地址 createBlockPath?: string; // 创建区块的目录 }; export const compile = (templateString: string, model: Model) => ejs.render(templateString, model); export async function renderEjsTemplates( templateData: object, templateDir: string, ) { return new Promise((resolve, reject) => { glob( '**', { cwd: templateDir, ignore: ['node_modules/**'], nodir: true, dot: true, }, (err, files) => { if (err) { return reject(err); } const templateFiles = files.filter((s) => { let valid = true; if (s.indexOf('.ejs') < 0) { valid = false; } return valid; }); Promise.all( templateFiles.map((file) => { const filepath = path.join(templateDir, file); return renderFile( filepath, templateData, file.includes('.keep.ejs'), ); }), ) .then(() => resolve()) .catch(reject); }, ); }); } async function renderFile( templateFilepath: string, data: ejs.Data, keepRawContent: boolean, ) { if (!keepRawContent) { const content = await ejs.renderFile(templateFilepath, data); const targetFilePath = templateFilepath .replace(/\.ejs$/, '') .replace( /\$\{.+?\}/gi, (match) => data[match.replace(/\$|\{|\}/g, '')] || '', ); await fse.rename(templateFilepath, targetFilePath); await fse.writeFile(targetFilePath, content); } else { const targetFilePath = templateFilepath .replace(/\.keep\.ejs$/, '.ejs') .replace( /\$\{.+?\}/gi, (match) => data[match.replace(/\$|\{|\}/g, '')] || '', ); await fse.rename(templateFilepath, targetFilePath); } } ================================================ FILE: share/utils/emitter.ts ================================================ import mitt from 'mitt'; type Events = { chatGPTChunck: { text?: string }; chatGPTComplete: string; }; export const emitter = mitt(); ================================================ FILE: share/utils/file.ts ================================================ import * as path from 'path'; import * as fs from 'fs'; import { workspace } from 'vscode'; export const getFileContent = (filePath: string, fullPath = false) => { let fileContent = ''; const fileFullPath = fullPath ? filePath : path.join(path.join(workspace.rootPath || ''), filePath); try { const fileBuffer = fs.readFileSync(fileFullPath); fileContent = fileBuffer.toString(); } catch (error) {} return fileContent; }; ================================================ FILE: share/utils/json.ts ================================================ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs-extra'; import * as TJS from 'typescript-json-schema'; import { getConfig } from './config'; export const mockFromSchema = (schema: any) => { let listIndex = 1; const config = getConfig(); const mockConfig = config.mock; const getMockValue = (key: string, defaultValue: string, type = 'number') => { const value = defaultValue; const mockKeyWordEqualConfig = mockConfig?.mockKeyWordEqual || []; for (let i = 0; i < mockKeyWordEqualConfig.length; i++) { if (key.toUpperCase() === mockKeyWordEqualConfig[i].key.toUpperCase()) { if (typeof mockKeyWordEqualConfig[i].value === 'string') { const array = mockKeyWordEqualConfig[i].value.split('&&'); if (array.length > 1) { if (type === array[1]) { return array[0]; } return value; } } return mockKeyWordEqualConfig[i].value; } } const mockKeyWordLikeConfig = mockConfig?.mockKeyWordLike || []; for (let i = 0; i < mockKeyWordLikeConfig.length; i++) { if ( key.toUpperCase().indexOf(mockKeyWordLikeConfig[i].key.toUpperCase()) > -1 ) { if (typeof mockKeyWordLikeConfig[i].value === 'string') { const array = mockKeyWordLikeConfig[i].value.split('&&'); if (array.length > 1) { if (type === array[1]) { return array[0]; } return value; } } return mockKeyWordLikeConfig[i].value; } } return value; }; const formatProperty = (property: any, key: string = '') => { let jsonStr = ''; let listStr: string[] = []; if (property.type === 'object') { jsonStr += `${key ? `${key}: {` : ''}`; Object.keys(property.properties).map((childPropertyKey) => { const childProperty = property.properties[childPropertyKey]; const { jsonStr: childJsonStr, listStr: childListStr } = formatProperty( childProperty, childPropertyKey, ); jsonStr += childJsonStr; listStr = listStr.concat(childListStr); }); jsonStr += `${key ? '},' : ''}`; } else if (property.type === 'array') { if (Object.keys(property.items).length > 0) { const index = listIndex; listIndex++; let itemStr = ` const list${index}=[]; for(let i = 0; i < 10 ; i++){ list${index}.push( `; if (property.items.type === 'object') { itemStr += '{'; Object.keys(property.items.properties).map((itemPropertyKey) => { const itemProperty = property.items.properties[itemPropertyKey]; const { jsonStr: itemJsonStr, listStr: itemListStr } = formatProperty(itemProperty, itemPropertyKey); itemStr += itemJsonStr; listStr = listStr.concat(itemListStr); }); itemStr += `})}`; } else { if (property.items.type === 'string') { itemStr += getMockValue( key, mockConfig?.mockString || '', 'string', ); } else { itemStr += getMockValue( key, mockConfig?.mockNumber || 'Random.natural(1000,1000)', ); } itemStr += `)}`; } listStr.push(itemStr); jsonStr += `${key}: list${index},`; } else { jsonStr += `${key}: [],`; } } else if (property.type === 'number') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockNumber || 'Random.natural(1000,1000)', )},`; } else if (property.type === 'boolean') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockBoolean || 'false', 'boolean', )},`; } else if (property.type === 'string') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockString || 'Random.cword(5, 7)', 'string', )},`; } return { jsonStr, listStr, }; }; const { jsonStr, listStr } = formatProperty(schema); return { mockCode: listStr.join('\n'), mockData: `{${jsonStr}}`, }; }; export const typescriptToMock = (oriType: string) => { let type = oriType; const tempDir = path.join(os.homedir(), '.lowcode/temp'); const filePath = path.join(tempDir, 'ts.ts'); if (!fs.existsSync(filePath)) { fs.createFileSync(filePath); } // 处理最外层是数组类型的场景 if (!type.trim().endsWith('}')) { type = `{ result: ${type} }`; } fs.writeFileSync(filePath, `export interface TempType ${type}`, { encoding: 'utf-8', }); const program = TJS.getProgramFromFiles([filePath]); const schema = TJS.generateSchema(program, 'TempType') as any; if (schema === null) { throw new Error('根据TS类型生成JSON Schema失败'); } const { mockCode, mockData } = mockFromSchema(schema); return { mockCode, mockData: !oriType.trim().endsWith('}') ? 'list1' : mockData, }; }; ================================================ FILE: share/utils/lint.ts ================================================ import * as path from 'path'; import glob from 'glob'; import * as execa from 'execa'; export async function lint(option: { createBlockPath: string; rootPath: string; }) { const { createBlockPath, rootPath } = option; return new Promise((resolve, reject) => { glob( '**', { cwd: createBlockPath, ignore: ['node_modules/**'], nodir: true, dot: true, }, (err, files) => { if (err) { return reject(err); } Promise.all( files.map((file) => { try { execa.sync('node', [ path.join(rootPath, '/node_modules/eslint/bin/eslint.js'), path.join(createBlockPath, file), '--resolve-plugins-relative-to', rootPath, '--fix', ]); } catch (e) { console.log(e); } }), ) .then(() => resolve()) .catch(reject); }, ); }); } ================================================ FILE: share/utils/material.ts ================================================ import * as path from 'path'; import { getFileContent } from './file'; export const getMaterial = (materialPath: string) => { let material: { model: object; schema: object; preview: { title?: string; description?: string; img?: string | string[]; category?: string[]; notShowInCommand?: boolean; notShowInSnippetsList?: boolean; notShowInintellisense?: boolean; schema?: string; scripts?: [{ method: string; remark: string }]; }; template: string; commandPrompt: string; viewPrompt: string; } = {} as any; try { const fullPath = path.join(materialPath); let model = {} as any; let schema = {} as any; let preview = { img: '', category: [], schema: 'form-render', chatGPT: { commandPrompt: '', viewPrompt: '' }, }; let template = ''; let commandPrompt = ''; let viewPrompt = ''; try { model = JSON.parse( getFileContent(path.join(fullPath, 'config', 'model.json'), true), ); } catch {} try { schema = JSON.parse( getFileContent(path.join(fullPath, 'config', 'schema.json'), true), ); } catch {} try { preview = JSON.parse( getFileContent(path.join(fullPath, 'config', 'preview.json'), true), ); } catch {} try { commandPrompt = getFileContent( path.join(fullPath, 'config', 'commandPrompt.ejs'), true, ); } catch {} try { viewPrompt = getFileContent( path.join(fullPath, 'config', 'viewPrompt.ejs'), true, ); } catch {} if (!preview.img) { preview.img = 'https://gitee.com/img-host/img-host/raw/master/2020/11/05/1604587962875.jpg'; } if (!preview.schema) { preview.schema = 'form-render'; } try { template = getFileContent( path.join(fullPath, 'src', 'template.ejs'), true, ); } catch {} if (schema.formSchema) { if (schema.formSchema.formData) { model = schema.formSchema.formData; } schema = schema.formSchema.schema; } if (Object.keys(schema).length > 0 && preview.schema === 'amis') { // 设置 page 默认 name schema.name = 'page'; if (schema.body && Array.isArray(schema.body)) { schema.body.forEach((s: Record) => { if (s.type === 'form') { s.name = 'form'; if (s.data && Object.keys(model).length === 0) { model = s.data; } else if (!s.data && Object.keys(model).length > 0) { s.data = model; } } }); } } material = { model, schema, preview, template, commandPrompt, viewPrompt, }; } catch {} return material; }; ================================================ FILE: share/utils/platformIndependent/json.ts ================================================ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs-extra'; import * as TJS from 'typescript-json-schema'; import { getShareData } from '../shareData'; export type Config = { mock?: { mockNumber?: string; mockBoolean?: string; mockString?: string; mockKeyWordEqual?: { key: string; value: string; }[]; mockKeyWordLike?: { key: string; value: string; }[]; }; commonlyUsedBlock?: string[]; }; export const getMockConfig: () => Config = () => { const { activeWindow } = getShareData(); let config: Config; if (fs.existsSync(path.join(activeWindow || '', '.lowcoderc'))) { config = fs.readJSONSync(path.join(activeWindow || '', '.lowcoderc')); } else { config = {}; } return config; }; export const mockFromSchema = (schema: any) => { let listIndex = 1; const config = getMockConfig(); const mockConfig = config.mock; const getMockValue = (key: string, defaultValue: string, type = 'number') => { const value = defaultValue; const mockKeyWordEqualConfig = mockConfig?.mockKeyWordEqual || []; for (let i = 0; i < mockKeyWordEqualConfig.length; i++) { if (key.toUpperCase() === mockKeyWordEqualConfig[i].key.toUpperCase()) { if (typeof mockKeyWordEqualConfig[i].value === 'string') { const array = mockKeyWordEqualConfig[i].value.split('&&'); if (array.length > 1) { if (type === array[1]) { return array[0]; } return value; } } return mockKeyWordEqualConfig[i].value; } } const mockKeyWordLikeConfig = mockConfig?.mockKeyWordLike || []; for (let i = 0; i < mockKeyWordLikeConfig.length; i++) { if ( key.toUpperCase().indexOf(mockKeyWordLikeConfig[i].key.toUpperCase()) > -1 ) { if (typeof mockKeyWordLikeConfig[i].value === 'string') { const array = mockKeyWordLikeConfig[i].value.split('&&'); if (array.length > 1) { if (type === array[1]) { return array[0]; } return value; } } return mockKeyWordLikeConfig[i].value; } } return value; }; const formatProperty = (property: any, key: string = '') => { let jsonStr = ''; let listStr: string[] = []; if (property.type === 'object') { jsonStr += `${key ? `${key}: {` : ''}`; Object.keys(property.properties).map((childPropertyKey) => { const childProperty = property.properties[childPropertyKey]; const { jsonStr: childJsonStr, listStr: childListStr } = formatProperty( childProperty, childPropertyKey, ); jsonStr += childJsonStr; listStr = listStr.concat(childListStr); }); jsonStr += `${key ? '},' : ''}`; } else if (property.type === 'array') { if (Object.keys(property.items).length > 0) { const index = listIndex; listIndex++; let itemStr = ` const list${index}=[]; for(let i = 0; i < 10 ; i++){ list${index}.push( `; if (property.items.type === 'object') { itemStr += '{'; Object.keys(property.items.properties).map((itemPropertyKey) => { const itemProperty = property.items.properties[itemPropertyKey]; const { jsonStr: itemJsonStr, listStr: itemListStr } = formatProperty(itemProperty, itemPropertyKey); itemStr += itemJsonStr; listStr = listStr.concat(itemListStr); }); itemStr += `})}`; } else { if (property.items.type === 'string') { itemStr += getMockValue( key, mockConfig?.mockString || '', 'string', ); } else { itemStr += getMockValue( key, mockConfig?.mockNumber || 'Random.natural(1000,1000)', ); } itemStr += `)}`; } listStr.push(itemStr); jsonStr += `${key}: list${index},`; } else { jsonStr += `${key}: [],`; } } else if (property.type === 'number') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockNumber || 'Random.natural(1000,1000)', )},`; } else if (property.type === 'boolean') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockBoolean || 'false', 'boolean', )},`; } else if (property.type === 'string') { jsonStr += `${key}: ${getMockValue( key, mockConfig?.mockString || 'Random.cword(5, 7)', 'string', )},`; } return { jsonStr, listStr, }; }; const { jsonStr, listStr } = formatProperty(schema); return { mockCode: listStr.join('\n'), mockData: `{${jsonStr}}`, }; }; export const typescriptToMock = (oriType: string) => { let type = oriType; const tempDir = path.join(os.homedir(), '.lowcode/temp'); const filePath = path.join(tempDir, 'ts.ts'); if (!fs.existsSync(filePath)) { fs.createFileSync(filePath); } // 处理最外层是数组类型的场景 if (!type.trim().endsWith('}')) { type = `{ result: ${type} }`; } fs.writeFileSync(filePath, `export interface TempType ${type}`, { encoding: 'utf-8', }); const program = TJS.getProgramFromFiles([filePath]); const schema = TJS.generateSchema(program, 'TempType') as any; if (schema === null) { throw new Error('根据TS类型生成JSON Schema失败'); } const { mockCode, mockData } = mockFromSchema(schema); return { mockCode, mockData: !oriType.trim().endsWith('}') ? 'list1' : mockData, }; }; ================================================ FILE: share/utils/shareData.ts ================================================ import path from 'path'; import { homedir } from 'os'; import * as fs from 'fs-extra'; const dataFile = path.join(homedir(), '.lowcode', 'data.json'); export type ShareData = { activeWindow?: string; selectedFolder?: string; oneAPI?: { apiKey: string; hostname?: string; apiPath?: string; port?: number; notHttps?: boolean; model?: string; maxTokens?: number; proxyUrl?: string; /** * @description 使用当前配置 * @type {boolean} */ use: boolean; }[]; }; export const getShareData = () => { if (fs.existsSync(dataFile)) { const data = fs.readJSONSync(dataFile) as ShareData; if (data.activeWindow) { data.activeWindow = process.platform === 'win32' && data.activeWindow.startsWith('/') ? data.activeWindow.substring(1) : data.activeWindow; } return data; } return {} as ShareData; }; export const saveShareData = (data: ShareData) => { fs.writeJSONSync(dataFile, { ...getShareData(), ...data }, { spaces: 2 }); }; export const oneAPIConfig = () => { const { oneAPI } = getShareData(); if (oneAPI && oneAPI.length > 0) { return oneAPI.find((s) => s.use) || oneAPI[0]; } return undefined; }; ================================================ FILE: share/utils/tsx.ts ================================================ import * as path from 'path'; import { decode } from 'html-entities'; import * as ReactDOMServer from 'react-dom/server'; import glob from 'glob'; import * as fse from 'fs-extra'; import ts from 'typescript'; export async function renderTemplates(props: object, templateDir: string) { return new Promise((resolve, reject) => { glob( '**', { cwd: templateDir, ignore: ['node_modules/**'], nodir: true, dot: true, }, (err, files) => { if (err) { return reject(err); } const templateFiles = files.filter((s) => { let valid = true; if (s.indexOf('.template.tsx') < 0) { valid = false; } return valid; }); Promise.all( templateFiles.map((file) => { const filepath = path.join(templateDir, file); return renderFile(filepath, props); }), ) .then(() => resolve()) .catch(reject); }, ); }); } async function renderFile(templateFilepath: string, props: object) { const tsxContentStr = fse.readFileSync(templateFilepath).toString(); const transpileResult = ts.transpileModule(tsxContentStr, { compilerOptions: { jsx: ts.JsxEmit.React, module: ts.ModuleKind.ES2015, strict: false, moduleResolution: ts.ModuleResolutionKind.NodeNext, target: ts.ScriptTarget.ES2015, }, }); fse.writeFileSync(path.join(templateFilepath), transpileResult.outputText); const templateJsFilepath = templateFilepath.replace( /\.template.tsx$/, '.template.js', ); await fse.rename(templateFilepath, templateJsFilepath); delete require.cache[require.resolve(templateJsFilepath)]; const script = require(templateJsFilepath); const markup = ReactDOMServer.renderToStaticMarkup(script.default(props)); const targetFilePath = templateJsFilepath .replace(/\.template.js$/, '') .replace( /\$\{.+?\}/gi, (match) => props[match.replace(/\$|\{|\}/g, '')] || '', ); await fse.rename(templateJsFilepath, targetFilePath); await fse.writeFile(targetFilePath, decode(markup)); } ================================================ FILE: share/utils/uTools.ts ================================================ import path from 'path'; import * as fs from 'fs-extra'; import { createChatCompletion } from '../LLM/openaiV2'; export const screenCapture = () => new Promise((resolve, reject) => { utools.screenCapture((res) => { resolve(res); }); }); type LLMMessage = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; export const askChatGPT = async (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; hostname?: string; apiKey?: string; apiPath?: string; port?: number; notHttps?: boolean; model?: string; maxTokens?: number; }) => { const res = await createChatCompletion({ apiKey: data.apiKey, hostname: data.hostname, apiPath: data.apiPath, port: data.port, notHttps: data.notHttps, messages: data.messages, model: data.model, maxTokens: data.maxTokens, handleChunk(chunck) { data.handleChunk(chunck.text || ''); }, }); return { content: res }; }; export const getBlockJsonValidSchema = ( mainScriptFile: string, schemaFileName = 'schema.ts', ) => { const configPath = getBlockConfigPath(mainScriptFile); return fs.readFileSync(path.join(configPath, schemaFileName), 'utf8'); }; /** 获取脚本目录 */ export const getBlockPath = (mainScriptFile: string) => { return path.join( mainScriptFile .replace('/script/src/mainBundle', '') .replace('/script/src/main', ''), ); }; export const getBlockConfigPath = (mainScriptFile: string) => { const configPath = path.join( mainScriptFile .replace('/script/src/mainBundle', '/config') .replace('/script/src/main', '/config'), ); return configPath; }; export const getBlockTemplatePath = (mainScriptFile: string) => { const configPath = path.join( mainScriptFile .replace('/script/src/mainBundle', '/src') .replace('/script/src/main', '/src'), ); return configPath; }; export const ocr: (data: { base64: string; model: 'structure_table' | 'ocr_system'; }) => Promise<{ log_id: string; error_code: number; error_message: string; result?: { texts?: string[]; }; }> = async (data) => { const base64Array = data.base64.match( /^data:(image\/(?:png|jpg|jpeg));base64,(.+)$/, )!; const type = base64Array[1]; const atob = window.atob(base64Array[2]); const uint8Array = new Uint8Array(atob.length); for (let i = 0; i < atob.length; i++) { uint8Array[i] = atob.charCodeAt(i); } const blob = new Blob([uint8Array], { type }); const form = new window.FormData(); form.append('image', blob); const i = { method: 'POST', headers: { Accept: 'application/json' }, body: form, }; try { const tokenRes = await window.utools.fetchUserServerTemporaryToken(); const res = await fetch( `https://ocr.u-tools.cn:7999/ocr/?model=${data.model}&access_token=${tokenRes.token}`, i, ); try { const json = await res.json(); return json; } catch (ex) { return Promise.reject(ex); } } catch (ex) { return Promise.reject(ex); } }; export const icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AACEYUlEQVR42u2deZgcVbn/v+dU9TZ79kASsieEQEISZBVJJIpwEUVBREW57nLR63q9/AAdAfcN9XrdvW644QKCgjDCsG8heyD7npB9Mmt3dVfV+f3R05Oe3rt6marubz1PHp6E6k/VOXXq/Zxzquq8ADdu3Lhx48at7jZRKmD58qUihaM6OjoVeeSRRx555JHnXp5eovy11H/r6Oi0ySOPPPLII488d/N0hwcWALTUngcAizzyyCOPPPLIcz9Pd3hwPdPBnUxlkEceeeSRRx551efpDg7uz3DwWAmFIY888sgjjzzyqsgrqgMwePBAhoNHSygMeeSRRx555JFXRV6CqRe4owQQzHBww2FhyCOPPPLII4+86vMEAAlA6QUePJTh4BEnbzCSRx555JFHHnkjwku8QKiAPI8Akg6e/LmBDSBcQmEaBnsf5JFHHnnkkUdedXgiyfm5OwCDOydPO4jBHw2UUJimDD0Z8sgjjzzyyCOvcrzEC4QqIX9kewSQ44WDvjIXhjzyyCOPPPLIqyzPnzzyB4COjs70DkCWTw3sCvRkePHII4888sgjr7K8YOrIv6Oj0wJSHgHkWGQgzMomjzzyyCOPPE/xQikj/2ErBqbOAGRaXjDCyiaPPPLII488T/EaMsjfTP50UE/6QSb5G6xs8sgjjzzyyPMcD4g/vk/w0hYN0gd/kJoWuJQVi3jxyCOPPPLII29keSplMJ/m88QMQLnWKubFI4888sgjjzz38CLZfK5nGPlblD955JFHHnnkeZqXd9EgmfJ3yp888sgjjzzyvM/L++n+0LKATg7MyiaPPPLII488b/IESthY2eSRRx555JHnTZ7jDgArmzzyyCOPPPK8y3PUAWBlk0ceeeSRR563eYKVQx555JFHHnn1xxOsHPLII4888sirL15RHQBWNnnkkUceeeTVhvyXL18qBCuHPPLII4888uqGJxBfA0jJAg/ewMomjzzyyCOPPM/LX0v8XRZw8FDKfqxs8sgjjzzyyPOe/PVknsyzc5CVTR555JFHHnmel78/lafn2DnAyiaPPPLII488z/P8SRwAQEdHZ3oHIEtPwUYBiQVY2eSRRx555JHnKl5wkJGQv+ro6LSAlHTAmZ4RDP4ozMomjzzyyCOPPE/xQikjfwXASuyTOgOgZTh4hJVNHnnkkUceeZ7iNWSQv9nR0anSOgDLly/NJH+DlU0eeeSRRx55nuMB8cf3CV40Wf5DHYDBqX+kHDxtZ1Y2eeSRRx555HmCp1IG82k+T8wApB48RvmTRx555JFHnud5kWw+1zOM/C3KnzzyyCOPPPI8zbOR5wX+1IWAKH/yyCOPPPLI8z4v76f7iRkAVUJGIVY2eeSRRx555HmMJ1DCxsomjzzyyCOPPG/yHHcAWNnkkUceeeSR512eow4AK5s88sgjjzzyvM0TrBzyyCOPPPLIqz+eYOWQRx555JFHXn3xiuoAsLLJI4888sgjrzbkv3z5UiFYOeSRRx555JFXNzyB+BpAShZ48AZWNnnkkUceeeR5Xv5a4u+ygIOHUvZjZZNHHnnkkUee9+SvJ/Nknp2DrGzyyCOPPPLI87z8/ak8PcfOAVY2eeSRRx555Hme50/iAAA6OjrTOwBZego2CkgswMomjzzyyCOPPFfxgoOMhPxVR0enBaSkA870jGDwR2FWNnnkkUceeeR5ihdKGfkrAFZin9QZAC3DwSOsbPLII4888sjzFK8hg/zNjo7OoccAIukHGuIvBSa+EQSAKCubPPK8z/vgihW+sYHRozXLGgsbY5UQo4RCEIBfCARsW/mFQACQfltZQbO3t0lABQD4FaAkRERvbupXQosAIioUDEg7KhQMpWAAiCrIASHso0rII7rffxSnTT7eLoTN60EeeSPCA+KP7xOjfiNZ/kMdgKSFARIdADEof8XKJo88l/KUEp98YvVocfjgaQg1TpUB/1RN10+GlKP0UGOblGKMghgDYCyA1gKZsKIGoJJufSGg+QOAKDp1iK2UOmZFBo4JpY7ZtjqmlDoK2Id9DS1bIeUO6HKHfkzsal82PcLrSx55ZeWpwQ6Ajfhj/DSfi6QfJY/8TcqfPPJGntf+6KM6mqfNMqU5R0BMg1DTYcf/qxSm21GjpUyyLrf8i+W9IoAdCmqnEGIHlNoBKbZLgQ3tC2cdYnshj7yieYmRf9Z3+FI7AAKARfmTR171ee0rNo01hbZAQCxQ0l4glFgAYD7ib/G6SdbV5h1UtrXO7O/faEfDLxk9XesPdnZuPvLyijDbH3nkZeVZyPP1XmoHwKb8ySOv8rxTr/vgSaPmLT5TaPpiJbAAwAIAJ9WArKvFs6TfvxlCroVQa4TC032R4PPfPn9KmO2PPPIK4w29A+BE/Kxs8sjLz9NGT2+d99arTtNHjzlHD/jOlbr/XC0QnFJHsq4WLwYhVkLhKaXwlO0XT33pjBkH2Z7JIy9HB8Dpxsomj7z0rf3RHUGzzTxXmfarbSNyEYQ4WwjRQlmPCG+rAp6SAk+pgfBjz3z+owfYnskjr8QOACubPPJObLes2Thd2NqlAC6FEq+FUg2Utft4yrJ22Gbs4dhAf8fhp594/JVnOw6zPZNXrzxHHQBWNnn1zvvoli2B1l55oRS4TCn7UkCcSll7jheBEI8pIR7QBR5oXzhjC4RQvD/IqxeeYOWQR15hvPY1W8ebJq4UQvwbgNcCqpFyrSGewHYo/EMJ9edNW2Y9cffbhMX7g7xa5glWDnnk5fg0b8Oe0bYRuVIB1wDiYuRIoU251hTvgADutmzxR9+SGU9zRUPyao1XVAeAlU1evfBefcdPmmw/3qQg3g7gdQB8lGtd8/ZCqT/17t15/+qf3/mi3t+veL+R53X5L1++tLA7gZVNXq3zRi2+KHDqVde+Fj79bVC4DECAMiQvlaeUvdeOxv4aOXL4txt+9NXneb+R50FeYtVfJQo8eAPimQJZ2eTVFG/eBz8+r3HCKe/Rm5rfLoRoowzJK5QnBJ5XAj/R/NHft8+f38f7jTyPyF8bZOTuACTlE05OE8zKJs/TvLYF5zdMv+SyN+kNTf8udP0cypC8Enl9SuF3Qoqf3H7mjBXZviTg/UueC+SvD/7VztkBGNw5hOFZAlnZ5HmWN/8jnz4rOGbie6XP/3YhRCvlRV75eWoNBH4cteRdXz1rZjfvX/JcJH9/YuQ/2AGwRY6dA0kj/8Sbz6xs8jzFa3/0Ud1qPOkqM2b8pxDyXMqLvCrxwgDu0mztm0/+979v5v1L3gjz/Mkjf8Tz/lgiR08heeQP5MkqxMomz0289g0bmsxo8H2wrU/Y0ehUyou8keKZ4YEHwkcP/s/qn3/n6cEvCHj/kldNXjB15N/R0Wki5SDJzwhSR/5hVjZ5XuD9vxU7T9KEeSMEPgKlRlFe5LmFp2xrpRXu+07/ppW///6NH4vy/iWvCrxQysjfAmAmkv+ldgD0lJG/ABBhZZPndt4tL26eJ4T4NCDeBcBPeZHnXp7coaC+HdH6f/6NhQv7ef+SVyFeQxLHHvwTTc78K5J+oCWJPzHyj7KyyXMz79YVm8+BJm6BEpdTNuR5jNelgG/rUfWd9nNn9zAekFdmXurI30iW/1AHIGlhgOSRfzR1Z1Y2eW7htb+4fYEl1B2AeiNlQ563eeqYgvxaROv7n9QZAcYD8krgJY/8w5l8LpJ+lDzyNyl/8tzIa1+9bY5lqy8AeDtlQ16N8Q5BiS9rPfKH7cumRxgPyCuRlxj5Z32HL7UDIABYXpD/Hz587adCmlhYxL0pIratK3WCJwRUUEpTCCgH9zp5VeSZ/lDjwNiTF8QammaqDDkslFIwrbTkbdA1DcJBMK813r6Fr0G0bRxl7Q3eftuyv7jlN9/9w7EN603KkDyHPAt5vt5L7QDYXhn5P/2Jd+w8oxFTC7o3AUQsBTvJKxICQU3Awa1OHnme4/349Lfg+JwzKVcP8ZRt7TH7B7669sc/+J11fLdJGZJXbl5iWUBVQkYhV1cO5UAeeZSrF3lCyCm+pqb/WfyJT31Y6r6PffGc0x6lvMgrJ08CgJNRP+VPHnmUP3mV5+nBhtOlz//IrSu3/bF91Y5plBd55eJJONw8K3+fVlrwlWLo5nYUfEX895QXeZQ/ecXx1NWWsjbeunLr7Z9es6aRMiSvVJ5eD/KXE6cicP5laDxtMbSGJqj+HpibViH22L2wjx3MD2xogX3OJQjNWwLRMhqIRWHv3Qq5ogNq2/oCulkafK+6GL4lF0FOOAW2bWNg91Zoq5+EvbITsC3KizzKn7xCeAEAt4Ssxn//3Kptn73tzBm/Tc0+SBmSV/B41KuFyfYSYGqw9C26CMFL34VgQwOkTJnwiEUR+eP3YG5cmb2CJk4DrvkYEGpKujcFgsEgpJSIPfl3GA/9bvjNm/z7YAOC7/o0tKlzAQC2bSMSiUAN7m/t3YroH76LQCxCeZFXMd7QS4CUa03xBPCskuqG28+cvYoyJK9YnqylwqQGS23qqQhedl1m+QOAz4/gNR+DHHdyZmCwEbj6xqzyBwDfq/8NvrNem/WcAld+KKv8AUCfMhstV32E8iKPI3/yiuYp4FzY4oXPrdzylQ//+YlGypC8inQAvPjMP/DatyIYCmWW/5CBffAve2tGnrlkGdDYklX+ic1/8VWAlv40RZs8E/ppZ2WVf4LnP+0saFNmU17kVY5HudYyT1MKnx01sXXNGR+7+ULKkLwCmULWQmEyBUutoQWNM0/LLf/EvnPPHHbTJXjarIV55Q8AorEF2pRZGbiL88o/wdNOXUx5kVcxnmXGKNca5wkhZobGTrz/rM99886TX/fmZsqQvGziH8z9k78DkJRVyFNv+4dGjy1I/gAg/EGIYGMaT7SMyiv/IUbL6PR/ax1dkPwBQLaOprzIqxhv2NqKlGtN86QvcP0py//tufO//pM3UobkpcofgHYituQ/eChlP2985x/uLxxkW1DRSBpPhfsLkj8AqEj68eyBvoLkDwBqoL+08lKG5BXAo1zrgyeEOFkpcc+tK7f9sX3N1vGUIXmD8teTeTLPzkF4dJEfu/sI7OOHC2JZuzZDWWYaz96zuSD5w4zB3r0l7fz6t6wrSP4AYO14ifIij/Inr8w8dbVlYd0tq7ZdRhnWvfz9qTyZY+eAmyvHsGyZM1gqhdhjfyuIF3nsnozBV3/hX5Aq/+nFnu+AigykBd/Y1nWwD+3LK3/70F6Ym1ZRXuRVUP6gDOuXN14o9fdbV2793iee3hOiDOuSl+pzdHR0pncAsvQUbLdVTvIjzWzBMvbiozBXP5lb/o/+FQOb12UMvurYAUTu+UnWb/zjswebYHT8MXPwtS1E/vJDINyXVf6qvweR338XsC3Ki7yK8TTdRxmSd2NT0FjR/uL2BZRrXfHSZvI7OjotIOllgCT564g/GhBJPwq7rXIuHNd0w3g/WvMFS3Pji1ADfdBOmgoRCJ7o0Rw7iPB9v0Df0//MGXztA7th7XwZ2oQpEM2jThTCCCP21D9g3PMTwIxlDb4i3A9944vQ2sZAjpt04iZVCuZLLyDyuzuhsqxGSHmRVy7eygmnITJmImVI3jgl1Psu+uB/9s0894wVPevWNVKuNc0LDfJU0mDe2r59Z2JicNgPEvJP7gBE3Fg5N502cd0ZjWJKwcFSSshxJ0M0NEP1Hod19AAipl1U8BWtYyBHjYeKRmAf3ANYZlHBXAQbIMdPjl+Fw/ugcryoSHmRV05eznTAlGtd8syB8CNH1q26Ycdf/u8A5VqTvIYkjj34J5qc/E9P+oGW4eCGWytHAMUFS9uGfXBvScFXdR+F1X3UcTBXkQFYuzdTXuRxxUDyRpwnNPHasQsXPd0yZcoH1nzntg7KteZ4iRF/ghdNzfwrB38gMsQUw82VE9CkzWBOHnmUP3nOeUKI0YGx4/90zpd/9LFXP/woyhWfKWvX8YxU+Q91ADLsHM20s5sqh8GcPPIof/LKwhNS0263Vm37y2dXbGulXGuOF8nmcz1DTLHcLv+Ojk77rAnv2PRPqaYWw7SUGr4gGgBNJLDFb+SR5xVe7KyW4W/8UobkpfPe5Jfq+faVW65sXzz7JcrV8zwbeV7gT+0AeEL+ALCqVx0tapiT6VM+4WicRB55nuPN1wNopAzJy8+bY0E897mV266/bfHMP1OunuYN5OMlHgGojo5Oz8ifciCPvDLxKMOa5knbcsJrUlB/unXV1q+2P/qoPhLx/q5zT2qj/CvP04H4ikBOYgflTx55lL9XeCcFdEz3KSilJeEEfIEgRJG8FccH0BezXF3eUw5tQ6j7CF6assAZT+G/rNbJiz67YtvVXz1rZnc1430j5EM9N13z7pYv/2FzOXiUf44OAOVPHnmUf62PrKf7gEUtJ1bClTK+PLcoLCv6sG1vOIpN/QOuLe/JR3bhsqd+g8dPW14iT7zOL9VTtzyx9vJnv/CxY9WK9/a575goNK0jfPM7Lgx98be7KOvK8KST2EH5k0ce5e813mifLIv8lbIxCpZryzuq5zAuf/LX0AZXKC3D+c23NfHsGR/6zJlVjvdTTKHuP/Dp6xop68rwZK1VDuVAHnmUfybeuIBeFvlHIhGM82muLK8ei+Kyp++CLxYp7/kBE4Inn/LAmZ/8wiXVjffi9Mag9VPKujI8WUuFoRzII4/yz8Rr0CQapCyL/G1bYexgZ8Jt5X3t839GW++RYf9cvkWD0OAfNeZ353z5x++qcrx/e+/N7/gY5V9+X8paKgzlQB55lH8m3ji/Xjb5A0CzJhGQ0lXlnbN9BWbv35AU3AV8vrJngZRSk9//3Itbv9auVPX8IfDl4+3vmEH5l8+Xy5cvLexOoPzJI8/bPDsWretP6cYGfWWTPxB/jDChpdE15fX1deHCdQ8Pk39QE0V/3VDo+SmBz1irtv/+o1u2BKoU7xs0Ez+g/MvCE4ncP7LAgzdQ/uSR512eqvPv6E9ubiyr/IPBICYE/a4p74XrOxCMDQyXf8VnitTVbT3yb+0r9jdUKd6/vuuWa6+l/EuTP3BiUVBZwMFDKftR/uSR51VenS6i40TWueQvhMT4gO6K8o49fgDz9q2rsvwT/1+93pIDD7Y/u6Wl0vFeATAsdfsUTdMpf8fy15N5ep6dgy4e+YtyBUshgImjfFBIPw0ppePYa9uF8ZQCDnbFYOeohYAUaNPttLXfRQlyUBnWkq8m74gBmJQ15V9hni4lxvi1ssofiL9X4Ibynrv5CUCpkUwEdaHtx8PtG/Zc2j5/yrFKyT9iKQCYedOCMW+7YdWh31H+RcvfP8hIXGSl59g54OrKUbae1jAdBsuPXz0RF5zROGyaVAiBYEBCOmDaSiFi2EXxVmzqx+d+ui+L/IGvzFAYnTSIKU8WuZHlbR4Abt6mMqfCofzLyhN1vHzuaL9e1H1ciPwTHQApBGylRqy8Y7sPYsaBTSOeBVJBnG0axqPta7a+7slPvf9IJeSfGP40S/mZJtP8XZ+uU/6F8/xJHADxFYBljp5CalYht1VO2YJlW6McUfkDwLhWX9b/54NCs6/2UtJOCVDW1eJJn79u184vZqq+UPkDgC4FRvm0ES3voh0vuCYFtAAWmDH78VnvfP/sSskfADQpZvz81aecTfkXzEubye/o6LSAlEcAmZ4RDB487OLKKTlYfv13r2DhrAZIKQABaFI6vTdh2TZS58Hz8xTWbBnICu2zgM9tA6YF40WNn2bx6RvUYAcl5V4fMd5+I0PiWsq6Mrw6TpwzrsDp/2Lkn9jGB3QcjZojUl7djGHOKy+7Qv4Jnh2Lzh01Z8E/57z7g1ds/tWPd5Vb/kmdnfcAeJjyz8sLpYz8FQBrqBOb8hstw8EjtSx/AOgL23hqXZ+rg/l+I/6HMiSvKrwaWjdgfMBXEfkn2C/3RkakvHNe2YhmZbpG/gmeEGJq68zTH5z73hsu2fTz/93gJN7bCojaOWcW33K0/Z0tY9rv6qH8s/IaMsjfTE7+J5N+UJfyJ4888mpX/gL5HwE4lX/W2YUqlXf+oU2uk/8QTsqTRs1ddO853/jRSU7ifdi2ZZ7HiiG/qZZT/jl5qV/vRVMz/8rBH4gMsy+GqytHSJPBnDzyKP9cvEZdQ0iTFZE/MnUuqlRen+7D1KM7XX09hBDTpS07blq5ZVyx8T4Zl/UdByUupvwL5hmp8k+eARD5egpuqxzAwUNryoE88upG/gAwMcfov1T5A0CTrsEvRdXLe/LxvfAlZ/tz7fUQp2qQD7Wv2tHmJN7nfMFR5p4BoPyHeJFsPpcZRv6m2+XPRYPII4/yL4TXluUFwHLIP7E1aLLq5T3l8A7PXA8BdaalrH+0b9jQVDb5x20wp+emd4+h/LPybOR5gT+1pVuUP3nkUf61smiQL8P/L6f8E0G02uUd233Ia9fjPMsI3NP+6I5gIfFeCBT2dYOMzaP8s/IG8vESrV11dHRS/uSRR/nX1IqBlqqs/JWyEQ6Hq17eMX1HCsS56npcbLXaf/zgihW+fPE+JKVd0NlJnEr5O+dJIL4ikJPYQfmTRx7l72be8ZhZUfn3DYTRb1lVLa9QCm19R/PjAMRiMZddX/XGCdqon0MpkSvey4JPT8yl/J3zdKdxqJbk7/f7MGfWzDzr/itYyTf64KZpqV9PFnxyjnhK2di8dQcMw8haXp8PmD0pCEgBrYRcBtkWNQKA470m9hyKZv1tYFQb/K2jhp+ebaXxhNQcn1+pPKOrC9Hu45R/jcofAA4YZsXkH4lEcCBinjjFKpU3GIvAZ8Xyyj9iuTQXhFLvuuXFTTueBb6RNd6f+46CUFJhDOXvnOeoA1BrI/+bPvUxnHfOkqwo247f7GnL+waDjpIFlcpbs+4lfPbWO7KW9zNvPwmL5zZWdDljpYCbfrwHa7eG034fGjsOp3/kRohEWVwqB9s0se5/vwej6xjlX6O5AnpiFg6Eo2iFWXb527bC9oFI1cvrMyMFyX9Y6jCXXV87Zt66+LNffGXlV2/+bSnxXgm0UP7OebLWKsdJsBQ55pvcJv9E8MpVXilQ8VwGQgBZX9FJPj8Xy8GOxaAsi/Kv8URBLxzuqoj8bShs6jWqXt5AzPC0/BM8vbH5u2d87NbXlBLvbRstlL9znl5LhXEaLL/09e9i3txZGQTsjmn/4cVT2LR5W87yfv0Ph3DqMz2O7k/LsjOcX3qgPN5rYeeBzIEofOgQ1n3/O/C3tUHZdlrwiM8MOKu/svEUMHDgFZgD/ZR/jWcJfCkKLGkNoVXXyiZ/AFjXHUG/rapeXp9lFiz/2fs3Yu/keegJBF14fYUeGjv+N+fd/r0Lbj/vjPWDZRC9t77jPUJhfAG3MAZM291Za13uS72WCuM0WEajUaxZ95K3gnkOnhG1sWbrwIieX+TYMUSOHq0fuVL+ruXZAB4+1IOrJ48um/x7TQvPdg2MSHmjmr8g+UsIzOnahZkd/4MXZ56HZ+ZehKjP77br2wIh7m9/fse5n7zvlpm9St0pFM4qRP4RS8ECuil/Z75cvnyp0GulMJQDeeRl3+xYtC7ln9j2Gxae7Inh9aGGkuVvQuGBQ70wRyjFciQQKkj+ie/odcvEOZufwOm7VuOx0y/GhlMWQeU6TpWvb0u4e+rrVt63QSg1usDJu6Hy2gpdlH/x4kf88b/SCzx4A+VPHnne5ak6ln+Ct7onAgiB5eNaUOhnZqnyN5TC317pxmGljVh5I75QwfJP3hqNXlz24j1YvO15PLLwUuwdM3VEr4fPNnHO5ifxqs1PwmfFipZ//O/qGOVftPy1QUbuRwBJ+YQl5U8eeTXAq1P5J3iru8M4ZJi4etIoBPL0AlLlfyxm4t4DPRiQvhEtr6npiGk+6FasYPknbxOP78c7HvsZNk4+HZ1nXIKeUGvVr8dpe9fhovUPoTncUzguQ2fHVuIY5V+U/PUkTvYOwODOQReP/AXlQB55lH+xvP2RGPotG4Eca0ZkWjfgkGGNuPwT24C/Cf6+Y0XLP3k7de96zHxlE16YfQGenfNqGJZd8esxqf8Ilj/zAE4+tqc4XJaZjlE+7KD8C5a/f5CRqMTMjwAGd3b325XK1tMaJuVAHnkZeYLyLwKXedEgfzAI9MRcUd7dzeMwI2k1wGLln9h8VgznbezErO0r8OSpS7F50unx9wPKfD0ajT4s2/w4Tt+7FkIVt/Bsrsccto6nKf+CeP7kkT8QXwFYz9FTSM0qNOCyyqEcyCOvQJ4coRfWakX+wWAQIsf399Uu7/5RkzHjlY0lyT9Zro3hHlyy6m9YuPNFPH7663F4woyyXA/NNrFk+3O4cNsz8Fsxx+eXeaZDHGhrv2s75Z+XF0wd+Xd0dFpAyiOApGcEqQcPu7hyKAfyyMvHo/xLk7+DTwcrWd79oyeVTf7Jcj25az/e88yv8PKUBXjs9NejL9jsuLyz9m/Ea156BOOj3WU7v2HlFfYzlH9eXihl5K8ADC1GkzoDoGU4eITyz76dHACmh+IFs5VKvdchReabc3cE2JNhRc9QMIizFi+EkGJw0aDhi91rmgbhKLiponm2bWPlqnXoHxjIWE0LZgbRGNSGl1eTjm9227LT689FvB17w9h7OFYfnQnK37XyB4BDrSfB1nQ0wC6/XJXC/N1rMGf/S3h27mvwwuwLYEq94PMb230QF61/GFOO7S5r5yS1syNsUfD0f53KvyGD/M3k5H960g8yyd+g/LNvjRrw9VkCfplorMjaWFO3mAL+Y5PCsRSffPKjH8IF573KNcsPr1i5Brfc9tW0f1+2uAkfedP44bwKLz880jwjauMj39yJnn6b8qf8R7S8tqbj2NipaD6yoyJyBQCfGcOFG/6FhTtW4tHTX49Nk+fnPL9QpA/nb+zE/D1rIBUqKn8AULb4B+WfkwdgSEsKQDQ1868++AOR4RpEnaQJrlrlCGmOdLA0lUC3BTSr4j/F6TcBI0NtHDl6zFW5B1SmulIKx3stKJWYXa59+Sul0Be2EDUpf8rfHeXdMHURphbZAShm3YDE1jLQhTc9/wfs2T4Vjyy4DAfbThre9iIDWLzteZyz+Un4LKPsjyUy8QTEc01fvuslyj8nT6UM5tMChT5Un8MPHnO1/IG+lLnsEQmWhq3w8U0KY3zD/z3+eXHu0zsaAyIZauTHP/817v37A4P5ARIByXnKXNtOzz1QDO/Q4aMZ62/lpgF86Os70BDUHHVMkjso6efnTt7RbhORKOVP+bujvJsmz8fytffDH4tWTP7J25Qju/DuR3+I9VMX4fHTlqM/0Iipu9fjwvUPoXWgq2heKednAz+j/AvmRbL5XM9wDSy3y7+jo9O+5IprXRF8owp4xSgfTwE4cOCw++SQgdfVa6OrT7n2/Mij/HPNbEUihmflDwAxzYeNk8/Agh0vVlz+Q6egFM7YuRJz9mzA3qaxmJj0PX+15C+A/kg08HvKPy/PRp4X+FM7AJ6QP4M5eeQVx1t3z18Af45EMBlmilDCzJPbeVfd+F4ER48qSv6HN2/Gs3ff66ryHtWjWNBSHfkn8+xoZETkDwBPHAof+NiKXT+YvfiCrP2UvV0xPdUelo3jAmLz3EUXPLZp1VMr6mDkn/fT/aFlAUvIKET5k0eey3n7161FNJp5qjjTirh2CRM7XuBF33stMNgBKHTk33f4EHY+87SryrsTwFvOmYBzxgarJv+R5Bm2wpfXH5sJhZm56k8lf/WeVH8K8VnWWYsu2AyIr25d9eQvANj1OpMggfiKQJQ/eeTVH68e5T/s76VM+7ukvN966RjsDO2i1uQPAL/d3oNXwmbJ9SeAOQLqZ7MXXfDk9EXnT61H+Q91ACh/8sij/Cl/b5Z3S28M9+3tr3n5HzMs/GxbT7nr7zwd4rlZCy+YX2/yd9wBoPzJI4/y9zIv8Sms1+Wf2L6/6TgilqpZ+QPADzYfx4BpV6L+JgiJf85bcuFJ9SR/Rx0Ayp888ih/r/MCgUDNyB8AjhgWvvVyV83K/7GDA/jL7r5KXo9JMdv+Wb19PSBrqTCUA3nkUf6F8Jwup+3m8v5pVy/+tqev5uS/b8DE59YczbiySjnrTwCX7uu23lwv8gfSPwOk/IvgCQAXjQJaNVFQ47dslbISv4Amnd9M5eIN2AqdXUAs5c5pmzsXoTHjHH/HbFvpL+tITS+KF+3txdH1a+PXIuV6BIJ+LF92EULBoKNgbpomkl8VNowoHnvyWYQjEcqfvJRLYcM0Y64v79deOoZZzX5Ma9ZrQv6GrfCZlYfRG7OrUn+2bd8IaP+qB/kvX75U6LVSmJEIvktHCfzHZO/cTNl5Ao3Sxj1J6w81T5uKOW9/lzsWbbFiOLZhQ9q/X3ft1bjqzZcXzcu1PPL0aafgBz/9Vc3JX1D+Jck/tb24tbyGpXDr6iP4yXkTMDogPS1/BeBL649hY3e0eu1F4TVH++y2MU2yC7W7aJBAfPZfyQIP3kD5p/P2RFTG5Xy9JX+gz1LYlTLojXb3IpYhC2C15W9FwggfOpTxemzdtjPj0r9O5S+EwKYt2z3T/orhCcq/JPnbtvJMeXcPxPDplYcRNm1XxBen8r9j3THct6ev2vWnRSxzcY3Lf2hFKb2Ag4cw/F0Byn9w2xoG/v0lBb/MhVNZcE5TGZSfF7Pj2QmTgcbxLqz+1teg5Vo9LiMww2UUztfit6IGlGVnvB6dTzyNZ55/ET5dK6K8dob6i59fzLRgGEbNyZ8j//qRf4K35piBG54/hO+fPR6NemH3n1vkbyuF29cdw73Vl3885phiYg3LX0/iZO8ADO4cdPHIX7gh+MYUELNqRw7JPGXbMIt5Hj4C5TUMAwU7my8QUv4lyF8I4anyru0y8OHnDuEH54xHU55OgFvkbwG4be0x3Le3b8TqTwkEalT+/kHGUJZAPcfOAbj5bUhl62nBkW+Dk0deRt5Nn/oYpp0yxfHb79GokfpUB35/wLO8iRPHFyV/KQWWXngB5sye7bnyHug5iikr7kWgr8vV8o8Fm7DnrMtx9RWTcHUZ6+/g4cP4zC23V6wz4ZFPB/3JI38gvgKwnqOnkJpVaMBl0yKUA3nkFchbvPAMzJ09s2hUrncmnKRZdgtvYGAg4/sjuVIENze34JQpkz1ZXnXha2Dc93OYq590pfy1OWei8a0fxqiG5rLX385de+pd/sHUkX9HR6cFpDwCSHpGkHrwsIufiVAO5JGXh+dkpFmr8i925O90xUA3lVf4Awi+9SOITT8Nxv2/AGJRd8jf50fgtVfBd8Flae2+3PVXp/IPpYz8FeJPWpDWAUD87cDUg0cof/LIqy8e5V878h/m28UXQZ95OsIP/wH9q54aOflLCd+i18B/8VUQzaMqXn91Kv+GDPI3k5P/6Uk/yCR/g/InjzzKn/KvofI2jwIuux7BRctgPPJnWDs2VFX++txF8L/+7ZDjJ1fp+qp6lH/iMbmdxIumZv6Vgz8QGa6p4epPIYQ0GczJI4/yp/yd8eTEUxB6xyfgP+u1VZ32D7ztxqrJ37ZtGEakHuWfaTCfVupEjabuHM20s5sqB84/fKccyCOP8q9r+SfzQs2trliUrBrlrVP5R7L5XM9wTS23y7+jo9O+5IprXRF8zz7rTMw7dfZQP8rn8zn+tCcWi6X0azLzbNtGx6NPYNfuvcP+fcIoHy49txW6Hl8iIWba6TxdOl3dt2DevkNRPPh8d8bqPqNJYFFzPIeBmZbLANBLyI1QDG9FD/BSP+VP+de3/IPBIEwn8Yry94L8beR5gT+1A+AJ+bsl+La1teK/PvEfEINvWVfzZl94xnx87NO3DPu3j1w5HmfPa4StFCKGnc4LSEgnb4M74O09HMW67eG0xnbTVMAnxWDwQFmCx4lgVDjv9aOB6zao7NNIlH/Nyj/Bo/wddp4of6+M/PN+uj+0LGAJGYXqNlFQb28v1qzbgHlz5yAYDCIajZW18WfjKaXw5NPPpf370+t7MW9qEKapYCfxpBAIBgTiOFVkUePyL4b3ytEodh1IX57PBPBMD7CwUSFZvWIweEQdXN1EMCqGZyrg70cof8qf8q8H+ZeygqOH5V8QTwfiKwJR/sXzLMvG7V+50zXB/KHne/DQc92ultf3dmfKFVB8xyT3+ZWbR/nXKo/yr335BwJBR/dHrcs/fv0cbkwRTB55lD/lT/kLl7c/J+9k1YP8h2YAKH/yyKP8603+8bXkKf+RkL/h4vKKuIdqXv6OZgAof/LIo/xrgUf5V1/+CnB1eQFAk8LdifDKyJO1VBjKgTzyKC/K38Xyt5Sr5T+4YmBdyL+oDgDlTx553uUppSj/MqwbQPmXb8VAl8of9SL/5cuXCr1WCkM5kEde9s0wDMq/RPlLqVH+lL/nfTm49L8EoGSBB2+g/Mkjz7s8yr98KwZS/pS/x+Wvnbi2+Q8eStmP8iePPI/yKH/KfyTlL+F6+cOylVHD8teTeTLPzkEXj/wF5UAeeZQ/5e8h+WvC1fK3FaAcrCLmEfn7U3kyx87u/hRC2TrlQB55lH81EwXVZP0pVTX5u2HRoFzyd7J5ZNGgVJ+jo6NT6QX2FGwUkFigypVDOZBHXoG8QCBA+VP+mXmxGOVf2/IPDjISpVQdHZ0WkLISYKZnBIM/Crv4mQjlQB55eXhOlkOl/OtluWBQ/rUr/1ASJ/FfK7FP6gyAluHgEcqfPPLARYMo/5quP8q/5uTfkEH+ZnLyPz3pB5nkb1D+5JFH+VP+lL9X5a+Uqkf5Jx6T20m8aGrmX33wByLDNY06SRNctcoR0mQwJ488yp/yL5WHmpW/bdswjEg9yl+kjPyNTD5P1KjwlPyBPjhN+E45kEce5U/5n+D5fDUr/1ReHcl/2GXK5nM9wzW13C7/jo5O+5Irri1bsNSkwCkT/BmnifI1sLQelaNABOw5HEUsZmcN5g0SmOAv8vwylFeWIJty8/ZFFKKUNeVP+Y8oz3TQZih/T8jfRp4X+FM7AJ6Qf7mD5WeunYjXnNlclAgjhp3eWAPSkRBtpfDiy/34/M/3ZTy/oATunCMw2lfszYmy3JyV4u0IA5/fnjSVQ1lT/lXkUf7uWDSI8q8YL++n+4kOgCoho5DnEwU1NRTeyCoh/4hhIxSSWc9PE0CDNjI3ZyV54/yUNeVP+VP+lS9vHcq/IJ4OxFcEqlf5A8CXfv0KzpgRyitwBcC27GEvHwgAUpOOZWhbNmyhsGlnOOv59VvAp7YoTA3mZ1oZyquJxNGK3yrJOxAdpFDWlP8I8Sj/2pd/IBB0dH/UuvyTZwDqVv4AMBCx8dxL/a4O5gej8T+UF3mUP+VP+RfOc7IIVj3I33EHoJbkTx55lH99yt/vD1D+IyR/w8XlFXEP1bz849eR8iePPMq/DhMFUf7Vl78CXF1eANCkcHcivDLyZC0VhnIgjzzKi/J3d4pgN8t/8FPwupB/UR0Ayp888rzLU0pR/mVYN4DyL9/XRC6VP/RQ0F8P8l++fKnQa6UwlAN55GXfDMOg/EuUv5Qa5V/j8geAcbPmzMeel2pW/oNL/0sAShZ48AbKnzzyvMuj/Mu3YiDlX7vyB4CmCRPPqHH5ayeubf6Dh1L2o/zJI8+jPMqf8h9J+Uu4W/4AEGobPfusb/2ytUblryfz9Dw7B1088hflCpahYBCnz5sLBRupq/xoUnMWgJWCZVtl59m2wsbNWzEwEM5a3lBAYP70hry5DSwrvdo1rbAbqavXxJa9RlbZBMeNRaBlFIQmAYfhQ1k2UiuwnDzj+HFEjh2l/Cl/yh/VW0HUzfK3FQApNJ8VexOAX9SY/P2DjKEsgXqOnd39KYSy9bTg6DBY3vSZj8Y7AB4Jbi9v2opbvvCVrOW99T2TcObshuy8Mi1nfMtP9mHl5v4M8h+H+e/7ALRAyHlnJ2oMF5gQ0PyBsvKk7sO6H/wPIkePUP6Uf075JxYNqsn6U8rVy4dXVf4nCnFNoR0AjywalPxiI4D4CsAyR08hNauQ256JlC1Y9vT0eiq4RWNGzvJGDFVx+SsFRBPZC1Nko2wbUvO5Wv6aPwAzPAA7atS8/AEgEAhQ/pR/Zl4sRvmn3zLLb3ru5TE1Iv+0mfyOjk4LSHkEkOkZweDBwy5+IaLkYHnn//wY90ydAiEFBARkCdPM8VwBSY21zDzYwM49+3L+6ku/2Y+ZJweQ6VNn27ZTXejoRjrea+LAMTOjbIxjx7D6f+6Ev6Wl+BLbVroLpeb88mbhCQWEDx+CFY0W3V68Jn8I4Wg5VMq/XpYLBuWfvuk+n/5mAD/zuPxDKSN/BWAoKKY+AtAyHDxSy/IHANO0sHXbTs8E83ybaSls2hMZ0fOL9fQg1tNTF3KtRR7lX3+5Aij/tDJdk60D4BH5N2SQv5mc/E8m/aAu5U8eeeRR/pR/7cpfKVW0/AeP+tqbVm4Z51H5NyH9671oauZfOfgDkeGaGq7+DlJIk8GcPPIof8q/VB5qVv62bcMwIg7kDwDQfEK82aPyFxl8nlbqRI2KfD0FlxWmD04T0lMO5JFH+VP+J3g+X83KP5VXhPwTZby0BuQfyeZzmeGamm6XPxcNIo88yp/yLxPPQZupB/nHf4Dl7Rs2+D0qfxt5XuBPrVmL8iePPMq/HtYNoPzdsWiQa+UPAALNsT5xgUdH/gP5eImvAFQJGYUof/LIo/wpf8rfteW1lfN7zYoMvBHASo/JvyCeBOIrAlH+5JFH+dfbioGUf+3LPxAIOpd/1IDQ9NfVovyTZwAof4e86SGBGQ3Fo3aFga0D6bym5iacd/YSaJpW5KkpWJaZnntA0x0tAqOUQiwWxQsr16C3py+t/oQAzj+9CU0hreCqs+xMuRak0wX+qs7btt/A1r0Ryp/yr0v590RiGIiYiJkm7ME8IkFNIuzTEPBraAj4IaRwXXlFiSuICqmdPvvq607acvev96PGsgQ66gBQ/vGtWRf44kwBvyz+Zuo3FT65BTgWG35+n/zoh3D+OWe5Jnis2/AS2r/4rbT6u/TcNtz4lvGF8cq0/PBI80xL4T1f3I6uXovyrwH5+/0Byr+AzbRsHOoOw4iZyX1jBKQAlEI0aiIaNdE3EEVzQwANIX/e8zNcXN5My4c3zzh9GYAf1VqKYFlLhal28DVs4GiRqxEkptGOxRTCVvr57dm731XBYyBsZKy/V45GYRXwYK1W5A8Ah7tNhA3bNe2P8i91ZEj559siUQv7jvZllH/a4jG2Qk9fBN29kdzxz83yB6AsMy13iH/U6NfWmvyLngGg/Ifzogr4xGaFVr0YXJzXawGJXDrJ5/d/v/497rn/QWgFNl5lZ0iZ67DhZ+J19/RmrL9Vmwdwbfs2BPNMfyiVPq3uJPC6gdfdbyFmKsq/Bnjxf7dZfzm2aMzCwa4+2MNvj4zyT97CkSiEUGhpCmUc/ISUGvq92+Sf9gTjRCKy5e2PPqq3L1tW8JDPC77Ua6kwIxF8TQUcjZX3/Lq6jru2vMlbX9hGX9h2wLPLfH5u5428/JVSlH/J6wZEEQgE6qP+lMLB7v6i5Z/YBsIxBPw6An7fMPknv0DoIfkDQJvVMmUhgBdrxZfLly8Veq0Uhm+Dk0de9s0wDMq/xEWDZFJWylp/4e9YvwHTUo7kn9h6+gyMG+2rBfnH60Xg/EI6AB5YNEgg/vhfyQIP3kD5k0eed3mUf/lWDKyHT/0GwtGS5A8AlmUjEjW9J38gTf6D2wU1Iv+hnqws4OAhpGcVovzJI8+DPMqf8s8n/6hpDb3g61T+Cd7xcDR93QCXy19oesZ7TuTpAHhE/noyT+bZOejikb+gHMgjj/Kn/MsnfxsKpmmXRf6GrRCLWcPlrwlXy99WOe+5ye0rtp3iYfn7U3kyx84BuHntY2XrlAN55FH+1UwUVKsv/CVP09uDb+mXKn8FDM0kuGnFwJzyz7NZUp3vQfnLDD5HR0dnegcgS0/BhvtWQKIcyCOvQF4gEKD8Kf/MvFhs+DN6qLLIP9EWa0X+g9sFHpR/2kx+R0enFe+Ypctfh4OsQiM4LUI5kEdeHp6T5VAp/3pZLjhJjiXIOk3+g4G6huQPqBMdAI/IP5SBN/RcJnUaXcuwc4Tyz7yN9gH/MUmg1Rdv9IaV3vgDpdxMVeIZtsCP9insSVrASwb8mPnmtyIwalTWurNi0bQVszSfH04X48/EsyIGtt/zV5gD/Wk/mT1zOm744PUIBPwZcAqGkTkxSDYZGkYUP/zpr7Bpy7a670xQ/vWXKyAh/4Eyyt/pTIIr5R8v1ML/2rixeeWNH+73gPwbkjiJ/5rJyf/0pB9kkr9B+Wffzm4RWNgMKIj4MzRf+XrS1eYtaxP41YETddI6dTpGnXpadlmnrJWd6bvZouSfg9c2Zw6OrF6V9rNLX/9azJs7u6zB47JLLs7cAaD8Kf86kH85R/5ukb9SqjzyH6wqfw/OBfCcy+WfeExuJ/GiqZl/9cEfiAzXNOokTXDVpkWENEc6WD5xXGFGSCAkVfra0SU8Q4va1eWFLeCBY8N/d3z7Nhx8/lkE2kal1Z1txtJ4Uvc5ln8uXrS3F8de3pDxp3++9+9obmoaNgOglEI0mr7oTTzxS+7zM4wo7v7rfZQ/5V9nuQJQs/K3bRuGESmX/AGlEOvreRWA510uf5Ey8jcy+VxPumbJB4+5Wv5AH4av4D4iwbLfAv53j51pmsjx6WU+v+rzlGli1wN/d7W89u0/gC9+/Tt1K2vKn/IvC8/ng5PRlBfkn8orVf5W1AA0/XQPyD+ZF8nmc5nhmppulz8XDSKPPMqf8i8Tz0GbqVv5KwUptfkekb8NIJyLl1qzFuVPHnmUfz2sG0D5O180qF7lDwBCE6c2T5osPTDyz/v1XuIRgCohoxDlTx55lD/lT/m7trzlkn8cKP2nf/izk24/74wur/tSAvEVgSh/8sij/OttxUDKv/blHwgEyyj/+NdJwh+aXwu+lE7jEOVPHnmUP+VP+QuXtz9RgU+TFbCgFnypU/7kkUf516P845+GUv4jIX/DxeUtaJ0TqQruALjZl3IkD075k0ce5T9yI0PKv9ryV4CrywsAyjLzL3KmREEdALf7Uo7kwSl/8sij/Cn/OpK/pVwt/7QVA7OvcHpK+7NbWrzuS32kDl4L8pdS4qZPfRSnzz+1AJQabKx2Eko6ekZl2zZ+/6d7cN/fHxr27xcsaMIHr5gAXXPwXa9SiETt9JvJL4s+v31Horjt//ahb8BK+3/vnyxxbksJwWNYMHK+fOkT3Qq/3Fc/8j/R/ih/5+sGRBEIBCj/EuQ/LOugd+UPALD82nQAa7zqy+XLlwp9JA5eKyP/ttYWXHjBOQU31oDfV7bGf9EF56Z1AF7/qjaMayv+tQ5bKUQMGwGfHH5+AelokZDWpiCmTfRj/fbw8N6mEFg+uvgXTxLBw6+Xb+3yC5qBXyFlPcQankkwDIPyL3HRICk1yp/yT9rFmpapA+CBLIEC8dl/pRd48AbKP513rOs4vn7nDzB/3pycI69YLJr6SAk+n9/R26lKKRhRA/965Im08/vJfYdw4NioomYAlAJippV+frrmdGl/7D4UwYYd4bTzMwF8YxewpLm44BGzVYqqBXylBCNT4fFuVTfyh1KUfxlWDEz8vNblH7HqXP5AQYnNbMhpHpW/lgioegEHD2H4uwKUf9L2r84n8K/OJ1xxfnsPx/CDew65uv5W9Cis6CmVB5Q3N0L9vENA+TuTf72M/Mst/6g9XP4S7pe/0PSC7jlpq+kelL+exMn+EuDgzkEXj/wFXwgjjzzKn/Ivn/yHjdRLlH/qY4TEYzs3y99Whd9zSmCax+TvT+XJHDsH4OZ8x8rWKQfyyKP8q5koqCbrT6mqyd8NiwbllH9x5ZzuEfnLDD5HR0dnegcgS0/BhvsSH1AO5JFXIC8QCFD+lH9mXixG+Tt4oiiAaQs/f4vmAfmnzeR3dHRa8WuTLn8dDrIKVVn+gnIgj7zCeU5eOKX862W54PLIOtvXA7Uo/8EG07Jo9KRJLpd/KAPPOnG9h29ahp3DlD955IGLBlH+NV1/lZC/W3IFVED+sKIGRp18yjQXyz/T13tmcvI/mfSDTPKPUP7kkUf5U/6Uv1flr5SqiPyhFLTG5ikulX8T0r/ei6Zm/tUHfyAyXNOokzTBVXshQkiTwZw88ih/yr9UHmpW/rZtwzAiFZE/AEi/Psal8hc48a20AmBk8rlMumbekT/QB6cfglMO5JFH+VP+J3g+X83KP5VXTvkDgND8o1wq/2ReJJvPZYZrarpd/kwURB55lH9J8rdNGEacJ0S82jRNIBSK8xL/NuwP7NqsPycrklL+gBDwNbU0u1j+NvK8w5f6Lb1F+ZNHHuVfy/KPbv8zIht/C6ikKVABBH0i47PiofPQArAWfBramEX13Xmi/E/kCoAY42Jf5v16b2hZwBIyClH+5JFH+bufpxSMTT9F3/YH03Jf5JN/fHhkILr6K/DPvxHaxAspf4/IXwhRGfkLUXQHwG2+1IH4ikCUP3nkUf41Ky87hsi6b6N/33PO5D9U/xai678LX7QbcvJllL8H5B8IBCskf0BAFdwBcKMvdadxiPInjzzK3ws8FetHZPWXMXD45dLknxSWjE2/gNlzGPr0t1H+Lm9/wmFa03zyH9ytoA6AW33pqANA+ZNHHuXvCflHjiC88g6Ej+8pk/zjz5IjMQW1637YRg/8c98LITXKv4jzM1xc3kLlP1gRY73sSzmSB6f8ySOP8q8Uz+7bjfDzN1dG/oM868DjiG34LoJ+WUP1J8on/5R2qgBXtz8AUJZZmPzj26h2paRXfSlH8uCUP3nkUf4VkX/XS4i8cCvCfUcqJv8Ez9ezCrHVd0CZ/TVRf4nPAssx8pdyeGciYilXyz+tXeSWPwDIyLrdrV71pT5SB3eT/KdNDODiJS1IXvtDKSBm2kBK8/fpEg4fKWXlAQpPre3Dxt2RrL8/pxWY2zC8oswMN6deQhavavNe7BHY0K8qfn3Jiy+HWi/ytw49g8i67yJiRCsu/wTPPr4R0RW3wr/oFojAaE/Xn88nyzbtr2tymPyHZR30vvwBAEEzHALQ5TVfLl++VOgjcXC3jfw/ec0EzJocTLrZFSKGnd64AtLRohmF8Jae2YLr7tie8fejdODTp5y48U7cTMlTOaWt5T0SvNePBq7bUOCSjpR/STzDMOpC/uaeB2Bs+jkiUbtq8h/ar283jBW3ILDoFoiGkz07E6NpGkwhoMqQIjjo12ta/gBgagh4yZeDS/9LAEov8OANqOFp/yfW9uLksX5IKeIjJcOGnZwoQwgEAwLRGFDsCsSF8BQUOlf3ZGX0WMDqXuC0xqRptGE3Z1yuUQe1N5K8LQOUf7V49SD/2Na7EN3x14JkXW75D1V1+BCMFbfAf+bNkC0zPSf/xP0bDPrR1x8pSf5SCgSCPu/JHyhY/vHN7/eY/LWEyPQCDh5Celahmnrmf/ejXbj70S7XBnNLAV/cqXLwiu+Y5D4/t/Mof6e8mpS/shB96X8R2//YiMp/6HSiPTBe/DwCCz8DOXqh5+RvQ6Eh6EM4EoVt2Y7f9m9sCMCwMUz+Eu6Xv9D0ou453YzPAHhE/noSBzLPzkEXj/wF5UAeeXUufyuC6Oovu0b+yedlrP4yrINPeU7+iU57W3PI8WPAQECH9PvS5a8JV8vfVsXfczGh/B6Rvz+Vp+fYOeDiwkgoW0//JpNyII+8epG/inYjuvpLMLu3uUv+QyAT0XV3QjOOwxy7zL2PYZRKm6aXEGgI6AjJRnR1Dwx7hJlf/j4EGoIZ5e+GRYNyyt/JZhohl8tfDsp/aOQPxFcAlgX2FGy4L+Uh5UAeeQXyAoFAbck/fADGCze7V/5Jcu1d/3NEt//JvZ2xWCyrrH0+DWNGNSLgz/++uBQCzc0hBBuDUKJO5K8UwocOtbpc/mkz+R0dnRaQ8g5A0jOC1IOHXZzykHIgj7w8PCfLobpV/nbPNkRXfwmW0e1y+Z/gmbvuBaI98M+9HsFgyGWdMeSUtaZJjGptQMy0EInEEI1ZsOz4VxZSCuiaRDCgIxDww7BV/Yz8EysGQgVcLP9QyshfAbAS+6R267QMB49Q/uSRBy4a5Ab5H38J0Q3/A1sBBpoB34mAIID4inyiwLpJkYARtQG9QF6e+h3iJZ2fffRFaLsE5LwPu/J65JO1T9fga9IyNz0g42OEmpe/UpCaDLhU/g0Z5G8mJ//Tk36QSf4G5U8eeZS/W6atRXgt9HHjEDFs+Apcp8M2wogd2Fb0yD/gExBm7u9XAqecnhqVhtb9yHx+h4HYMcA/zlPyz9n0XC5/pVTF5D94dn4Xyj/xmNxO4kVTM//qgz8QGa5p1Ema4Kq9DSmkyWBOHnmor+WCy75IV/mf+Zf1/KpyPVCz8rdtG4YRqaD8AaHppgvlL1JG/kYmn8uk2S7vyB/og9MPyykH8sjzpvxtG5FIjPIv9/Xw+WpW/qm8cssfQiDQOqrXxTPlicf4GUutZ7imltvl39HRaV9yxbVlC5ZnzD8Vl11ycXFvISuFaDSa1hj8fr+jF64K5Sml8NC/HsPK1euyss6Y2YBLz2lFzLTS/p/fpznOZRCNneAd6zHxu391IRpLr1MhJSZdtAyBMWNyAm0zllZeqfvg9ASL5UUOH8a+xzuzPx+m/F346SAo/zJfD9NJvKL8h5YLVhrCLvWljTwv8Kd2ADwh/3IHyxs+cD2mTzvFM8Fy3tzZeM8H/zMr74Yrx2Ncq1bxXAZ7D5v414vpSxg3T5uOk1+z1NHN5FT+Tnnd27aib+8eyt+LiYIo/xHhUf4p8UWpqEt9OZCPN7QsYAkZhTyfKOjev/8TV195eUGNLVdWNdHT62jkXwzPthX+9o+HcjL/0nkUbzx/1FB2QyGAoF+D6LcdVV0kaqUFy/6IwqrNmdOf9u3bg+NbNyM0ZmzmmymWPtOh+fwwBwac3ZwOeEoBPdu2on//Psqf8i9N/lHK383yF0JUTv4AhILhVV/qQHxFoHqVPwA8+PCjePDhRz0fzBO8h1/owcMv9IzY+dlGFJvv+rVn64/yd7P8gYBr5I/4yB+Uv5vlHwgEKyb/+H6ioBkAN/pSOo1DtZ4imDzyKH838nzukX9MceTvcvkHg0FH72QV81hR0/PPALjVl7qT4EH5k0ce5T9SwbzY6cpKrvBH+Ttrf4aLy1vsO0WmLQyv+lKO5MEpf/LIo/wrylMKEcOi/F0ifwW4urwAoCyzqBeKVY5HAG73pRzJg1P+5JFH+VdW/jbl7yb5W8rV8k9rFwV8TeQzM88AeMGXcqQOTvmTR171eLm+Nqlt+asKyh+Ufwm8WpB//GTMAS/6cvnypUIfiYNT/uSRV12eYRiUf0WyBDqRv6L8a0X+QKz9nFm9XvLl4NL/EoCSBR68gfInjzzv8ih/t6QIpvxdL3+gmEXJjkAI5TH5ayeubf6Dh1L2o/zJI8+jvPqTv0AwoLlH/pnOr47kL+F++QtNL+aeO+Ix+evJPD3PzkEXj/zFSAdfAeDtE4B5jSJj44/a6S8M+WX8ZtoSVvjNK+kZjc5ccDreftUV0DQt5dRUxmncQCDgOPdAPp5l2fjr3/6B51asSvv9mFYdN1w5Hk0hbajqjJg1vEACCJSQe8BtvJd3hvHLB4+cuKaUvyeyBCIKWC6Sv6hn+WvC1fK3VdH33FEPyT+Rtngoguk5dg64uDASytbTLlSVg+8on8Bbxzu7mU5rFHjkmMK+lPdH33nNlThj/jzXBN+WlqaMHYCLl7TgvPlNeYNvOXMPjDTvjBkhPPjccRw4ZlL+HkoRbBfNq6+Rv61U1eTvhkWDcsq/+HHoEQ/IXw7KH0nyR0dHZ3oHIKmnkJpVaMBlKQ9HPPh2xRT+fGj4DMCJkb/KOPJPbFvDCvszfDxy1x/+irdfZQ3NAIzEyD95e+zJZzNy/vViD+aeEkRjUKv5kX/yDMDBLtOTjxECgUBdyr94Xp3J37YRicUof4e5ApRtHfWA/IOpI/+Ojk4LSHkEkOkZweCPwi7OdzxiwVcB+N3BYZ2qLLyUfXJsq9eux+q166sqBye8o90mbv/Ffj6j9whP1OCnapR/+VMsU/7F3Wtmf2+vy+UfShn5KyQ9EUudAdAyHDxC+ZNHHrhoEOVfnvOT7uyMUf7F3WtW1IAdM4+5WP4NGeRvJif/05N+kEn+BuVPHnmUP+U/gudH+Zf2gqNSFZE/lIKyYsdcKv/EY3I7iRdNzfyrD/5AZLimUSdpgqv2QoSQJoM5eeRR/pR/qTzUrPxt24ZhRCoifwCwo9HDLpW/SBn5G5l8npgBSD14zNXyB/oAqFoLvuSRl20b7RO4fKxKC76mrVLfb8SjxwUOGMWfmjdyBcQo/3JfD58PTkZTXkwRXE75A0A00r/LpfIfdpmy+VzPcE0tt8u/o6PTvuSKaykH8uqGd0YjcMVYkSH4Ii34HrcUHiiyA+CdREGg/Mt8PUwnXxNR/oAQ8EcbXnax/G3keYE/tWY9IX/Kgbx6481uUAUH39mh4s7Ts1kCKf8R4VH+8QYj/YFXvvnOiwdc7Mu8n+4nZgBUCRmFKH/yyHPAG+sD3nOSQEwB39ujcj7TmtUgCg6+s0MFBMl92yEnTIEtNcq/VPlHKX83y18IUXb5D+YK2Ol1X+pAfEUgyp888qrD82sCV4wFrhwHBAZjXLcJ/PKVzAifAKaFCg++JwWARg3oz7L+rbV9A8K/+hpEyxhYr70K2uyFHpI/EHCN/BEf+YPyd7P8A4FgJeQPBRTVAXCjL3WncY3yB8b6BT5xikCbg1pUAAwr9QUugUAJb+M65Q3YwP/uUdiR9LKsHgxi+hVXIjCqbXjj9/nhdEk+KxZNv5ny8KK9Pdh69x8R6+tN+39zZ83Ah99/Hfx+/7Dg4XSFxJ6eHvzwZ7/Gxk1bKyb/s1slrj8JGO8f/u9vHCvQZSr87XA6ZloovkBHMcF3dgOwujdDkHxlJyK//RaUZSJ85ADsP34P2owzEHj9NdDGnuT6kX8g6IPod4n8Ywp+pYYiFuXvzvIKp/Eqh/wHy19wB8CtvnTUAaD849viFmBuA5zfTFqZv8MtgffqUcCOpBFo48mT0TJ9etbG7+Rm0lVD0bzAqNFomzULh1evSmNedOF5OGXK5LIFj9aWZrz2ogtOdADK2F5ODgDvPVnizObsP7luokCPqdDZlS7zYoPv7FB6B8DuOoTwr74G24gM41nb1yHyk5fQdMGlEMveChQ5Yqp2MC92urJS8i/bC4l1KH/DxeUtRP7xf1M7vO5LfSQP7mX5Qwg83gVMDgCtuirqZopl+HTLJwHh4MvGcvAilsD9h4eXt2fHVhx4+in4R7XFX3jRfY7lb5ux9BdoCuTFentxdMOGjNfjr3/7B3w+HYFAAH6/3/HIPxqNz0wY0Sj+dM/9ZW0vQQ14yzjginESeh6kAPCRSfFOwMokeU8LoujgO7sh+TNgQEb6Efnl92H3dWcO5lCwnvoHBtY8jcAlb4e+8NUF1UG15VW8/BUihkX5uylLYCQC+ALulD8AZZn55R+vibwzAG73pT6SB/f6M/+IDfx8v3Lt+RV3Ww7nKVth3+OdLjm/zLzDR47h+z/6hauf+f/3VIH5TYUHS00AnzoFaN8BbBmI82YkDcgLDb7JLwIGJDD6X7+GdfRA3mCu+o4j8ucfQnv+Xwi88XrIk6Z5Vl4n3s6n/N2UIjjk0pTIQIblgnPMVNpQO73uS32kDs4X/sirB97OiMDpTcVhAlLgpmnAg4dtnBIExvmLD77NOnDjZIEVPQrLRwv4juwrKphbe7bA3LQK/iwdAG+uGFhu+YPyL4HnZfkDsLqb7N1e9uXy5UuFPhIHp/zJqxfeyl6Fy8cWx1UAfFD4t3HFj/yTt6WjgKWjhONgrp+6hPJHvncInMhfUf7elj8AbP7e7NmGF305uPS/BKBkgQdvoPzJI6943kv98UdFbgq+hfBk2zjIiadQ/pVYN4Dyd7f8gbwvKCtgrYflr524tvkPHkrZj/Inj7wCeaYC1vR6S/4AoM1bXCPyFwgGNPfIP9P51ZH8Jdwvf6HpeWOCEGqtR+WvJ/Nknp2DLh75C8qGPC/wVvTmf1HUTfIH0qf/vZ0lkPJ3U/tzs/xtVVhMULZc60H5+1N5MsfOARcXRkLZOmVDnhd4q3pzp650m/xFqBHatFNrRP4uzhXgpvpzUfsbUfkXuOnqxCMAD8hfZvA5Ojo60zsAWXoKNtyX8pCyIW/EeeMDwNmtQCjHR/7HTWDbgPKE/AHElwaWWs7gG1Am5V8L8rdtRGIxyr+4r7m725fM2OMh+afN5Hd0dFpAymeAmZ4RDB487OKUh5QXeSPGe89EgbNbBSwAL/cBK3sVVvQCr6S8H7xhAJjV4H75A4A+b0l68LUtWLs3w9q8Br5taxExo2j4+DchgoUvhUn5uz/FMuVfwAyZwDoIoTwi/1ASJ/HfoSwhqdPoWoaDRyj/LCMlTUP7zZ/CGafNq+o0KQD89u6/4k9/vX/Yb173qhZ84I3joWsiQ1HjwchO4snBYOl0Bb1svP1HYrj5x3vQM5B+mT86BTi3RWQNHqm5DEoNRtl4Tx4HfrCvtEWcFrcAZ7fGeTqAM5qAM5oE3nNSvAPwYp/Ciz0CCgqXjBaekD8AWFvWQs57FcLdXTC3roO5ZQ2sresgjDCC2omshNFH/4zApddR/sWen3TnugGUf4FltcVaj8i/IYP8zeTkf3rSDzLJ36D8s28tLc141eIzhxprIOAv682Zi/eaC85N6wBccEYzmkIySzBS8PswVIWlBcvcvJmTAph6UhDrtg1Pla0DOL9VwCcyBw+fLG8wysU7txX44T4UvrRsSnvxy/ja/tnO76QAcHlA4PIxSGu2bpY/AERXPoa+nZtgHj0I2FZWXuzZh+Fb8lrI8ZMo/0qeH+Vf2guOSpVN/gBgW+Y6D8g/8ZjcTuJFUzP/6oM/EBmuadRJmuCq9YyENEd6Wrir6zhu+8q3MHvmdCBlrOnz+RyPrGOxWE6eshU6Hn0i7bc/vOcgdr7SBj3pebRSQMy003m6dLq0f17eK0eiWL99IO23JoAv71TDEuIoAGaGXAZ6CbkR8vEsJfBYl3IsfwC4fJzERP/IB8uK8Q7vz8+zLRj/+BVC199E+XtY/kKgZuVv2zYMI1I2+UMpDOzftdkD8hcpI38jk8/1pBiZfPCYq+UP9AFQTi9gOeSf2J5+5gU8/cwLZeOVcn4Hjpn4xQNHKlreUnlr++J/Rvb8VEm8MX6Bt45TaSP7mpF/ETxr23qYL70A/bRXVUleMcq/3I8VfT6YLmh/1ShvqfK3jEh4z8N/WecB+Q+7TNl8LjMNoNwufy4aRN5I8t59Uny9/nqXf2KLPnAXEItWKZiD8i/3C4ROZirrUf5RA8qyVh7ftDHqEfnbyPMCf2rNWpQ/eeRl553WCFzQOnLytwEcM4GX+hRW9ylsHwC6YoBSI9eZsI8fRvTJ+6svL8p/RHj1Kn8oBTsWe9ZDI/+BfLzEIwBVQkYhyp+8uuBJAO87ufryPxIDnu8BXuhReLkPMDOcnxDA7AaFc1qBs1sETvJX7/wAIPrEffAtvgiqeVT9yT/qUfnbFqJP/R2x5x6qanuO/Opr8L/uGmhT51atvOWQPwCY4f5nPSL/gng6EF8RiPInj7zcvNOagKnB6sn/UBT47QGFp7qT3lrIcn4KwOaB+J9fv6KwqBm4bqLAKcEqjeRiUYQfvAt44/sr/sJawDXyR3zkD4/JPxCAveYphP91N1T30aqP/K1dmxD+6W0QsxZAXXgFxIRTKlreQCDoOBYkyx+A6t64/rlakX/yDADlTx55eXjr+4Cf71f495PzB7xSgqWpgN8dAP5+VMFUzsq7qhdY3auwbBTw3pMFgrKC8gegdD+s08+DrLC8AkEfRL9L5B9T8Cs1FLG8IH9/3zFEfvkD2Pt3Vn0mK5UX3rQG9uY18J15IQKvezuEP1CRmQ7h8HOnFPnDttXLOx/4075akb/jDgDlT1698v5xFDhuKnxsikC21X9LCZY9FvD1XcDL/ark8ioAj3QBWweAry8eBa2vqyLBHP4Q8LaPQk6eVdmR62AwL3a6slLyL9sLiVWSv/by84j8/ZdpL2yOhPyHeAqIrXocau9WtF77cciGKSNef5nkDyGgBwNP1JL849eR8iePvKJ4T3cDX9wJROzyBssDBvDZraos8k/edhsKhy//MLTJM8sv/1AT8I5PVUX+ztcNsOpa/ogZwN9/gdg9P3GX/JN4/mMHEPnRrYg93zGy8gegLDNN/po/ACHEk7XmS1lLhaG8yKsWb12fwq3bFY6b5QmW/RbwpV0Kh6OVKa8daID/HZ+E0TiqfPJvbAPe+WmIiZV9hlv6okF1LP9IH/Cbr8Ne+1TZZF2xr03MGIz7/g/Gg3eNmPzT2sWg/CEElG4VVYle8KWspcJQXuRVk7cjDNy8TeFgtLRgaQP49h6F/UblyquUgqH5Ebj6BkD3ly7/ljHAdZ+BGHuyy+WvKih/uFv+4T7gt9+GOrjb/fJP2mJP/QPGP37tKvkDYscdC+burCVfLl++VMhaKQzlRd5I8A5Ggf+3TaEr6jxYPnAEWN1b2fIahhFfE33iVARe86aSgrloGwdx3X9BtI2rb/n7hEP5qyrJ/1tQh/Z4Sv5DnYBnHoRx/y9dIn9AwH4QQhT0+okHEgWJRO4fWeDBGyh/8sjLEqxshYDmLFgO2MCfDquKlzc5WPrPvhgNo8c7D+at44DmUZS/03UDKi3/aCQu/8N7PSn/ofvquYcQffgP1Zc/MEz+gwV5oFbkj3jW38Frkf/goZT9KH/y6prnE8DsBuDSMQIfngTcNgOOg+U9hxR6zeqVVwiBYFMzAq+72nEwt/bvhH1oH6CUB+QvEAxo7pF/pvMrc/2JB3/tefkP9WWeuA/mxpVVlb/Q9NR7LjqgDzxSI/LXk3l6np2DLh75C8qLvGrwJgaAOSFgdkhgdiMwLQhoovTgpgD8q6vK8h8MlvL0cyHu/wVUZKD4YB7pw8BPPg8tEELolNkwT5kNbcpsyCmzIEKNLpO/BKKA5SL5iwrKX1v1GGIvvVAT8k+0YePPPwA+8kVEg00Vl7+tMt5zj39j4cL+GpC/f5AxlCVQz7FzAG5OeahsPe1CUV7kVYD3zVkCAVn+4LaxX6HbrL7843OcGrQ5Z8Jc+7TjYB4wDajt6xHdvn7ofOXYk6BNngXf+ZdCJn0dMFLyl0LALprnzZG/7/AeGA//rnbknyhvZADdd30LwXf/N6D7Kiv/zOV50OPyl4PyR5L80dHRqWSOnkJqViG3LX9IeZFXdV45g9tz3aJq5Q0EAmnBMlMK35LKqxTsw/sRW/U4rH3bXCH/4nnelH9Q1xD78w8Ayxqx9lxJnvnKLkSffqDq8gcAHeoBj8s/bSa/o6PTil+bdPnrcJBVaAQrh/Iiz3PyB4BtkeqVN9NyqPLkadUdyVH+FXtnwnzuIdhdh2tS/gle7Nl/Av09VZU/gN3ti2a97GH5hzLwrBPXZ/imZdg5TPmTR/mXP7gdi9kjWl6Z9CY/5V/FFMGyvOsGiHAfYo/dW9PyBwARi0I+dX815Q8IZPz8zyPyz/T1npmc/E8m/SCT/COUP3mUf2WCW1dshMur+yBCTZR/NeVfgUWDoo/8BcoI17T8Ezxr9eOwD+4t8tZQzuQf/+3fPSr/JqR/vRdNzfwrB38gMlwDw9WJD4Q0KS/yqsGrVHAbNgEwQuUVgRDl72H5q+NHEFvxiKP25zX5i3iFINr5l6LqzzAijuQPhV69W3/Io/IXGXyeVmo5NNGRp6fgssL0AUUnBaMMyXPEUxUKbs36yJfX7jvuHfnbNiKRGOWfVH+xlY8BtuWo/ZXcnnX/iHQmzJdfhBrodVR/BcsfAKS4t33Z9EgNyD+SzecywzUw3S5/LhpE3kjwyh3c2nQxouVV4T7AjFVG/hVb8Q6Uf6L+lEJs1eNVl78cNwmB6z4D+dGvQzvndYDUqjuTYJkwVz9VWfkDUDb+4HH528jzDl/qOgAW5U8eeZWXv4TAeD+w2xi58qruY5UZGQKIRKPQK/32ez3LH4C1fT3U8SNVk78INcK/7K3QXvVaRKIxQCkElr8NvkWvQexfd8O3fV31cgWsegy+899QMfkD6NaDkYc8PvLP+/VeogOgSsgoRPmTV9M8UaGRzZnNCit6R6681vaXKvYM11dP8o9WX/4AEHvxserIX0r4zroY/ouvggo2pJ2fNvYkNL7nv6C2r4fxj9/APryvovIHAPvAbtj7d0CePL2g+rNV0Q35r+3z50c9LP+CeBKIrwhE+ZNHXmbebw4CjxwDdkdQ1Kpy+YLb2S0oXrJlLG/05Rcq8wxXDK6nXhH5w0Xyx4iM/BPtwNq6tuLy12bMR8MNX0bgjddnlH/y+WmzFqDhxi8jcNm7C1oSutTzM7euK6j+AoGgk7b8h1qXf/IMAOVPHnlZeJ1dQGdX/P8FJTBjMC/AnAZgVgMwxucsuI32CcxuUNg8UP3yWv09GNixyVHwFcEGyAlTIFpGQbSMhmwZDdXcBiPQiFBTG0RTG6BpFRn5B4I+iH6XyD+m4FdqKGJVTf4A7MP7ocL9FZOrHD0B/je8E/q8JcWdn9TgO+8S6AsvQPRff0Jsxb8A265I58Tevbmg+hNFXg/btAZ2vvT0o7Uuf8cdAMqfvHrlRWzgpX7gpf4T+88KAV+ZJRwFt6smAF/aUd3y2raN3sfug5309ngxwdd34Rvhf80VacFXVmHFOyFE0Z//VEr+ZXsh0cHXEtaezRWRvwgE4bvoSvjPfwMwOIvj5PxEQxMCb7wevrOXw3jg17C2rS+r/ON1sMXx+eXaIl3H1h5/5C/uzoVTJp5eS4WhvMgbCd72MNBvAY1a8cFtcZPA/EZgQ7+qmvzDhw/AePYh58HXjJUkr1JkWLz8FSKGVVPyBwB71+byyl8I+BZfBP/yt0E0tZbt+soJkxG6/iaYL7+I6IN3wTp2sGyPndRAH8zD+xFtbCtrlsDuPbs2BOpA/vG6p/zJI88xTwfw8VOEI/kntvecpKCL6sg/Eokg8sifADPqOPhGH/0Loo/8ueryL17Wibfza0v+AGDt3VY2+WtT56Lhw3cg8OYPlFX+w+6TeUsQvPErsC56K5Q/ULL8E+Ud2P5S2VMEH922fXs9yL+oGQDKnzzyhm8+AXx6qsCS5tKmNWeEBD40Cfj+XlWx8qrB7/KjL3bCXPdMycHXePQv6D92BP5L3gEMBlt3yl9VUP4YEfkDgOrtKln+onUMApe8A/oZ51b+etg2IjEz/n7AGeci+tg9sNY8haBESS+c6j1d0Moo//i1ttSgGmta/suXLxV6rRSG8iKvmryABP57KnBGU3meaS4bBeyNCNx7RFWkvIZhwNy5EcY/f1uWkVfEUrBXdsLu70bwzR+E8PnrT/4+4VD+JS6SZNvD1v4vuv35/PC/5gr4L/g3wOevjvyTeKKpFcHLr4f/gksRe+DXsHZtctb+oIDwQFnlXw8j/8Gl/yUAJQs8eAPlTx558a1BAp+bLnBGkyjrd/TvOgl4y3hRkfIG9m9F5O7vA7ZV1kWNzE2rEPndtxGAXYfyh7PzK7G8Ktw31EaKan9CQF94ARo//i34l145IvJP5umTpiP0/s8heM1HIdrGOmp/KtJP+Rcv/8SkSe5HAEn5hCXlTx558fX7b50W/xSw3IvoAMCVYxUm+oCf7ANiqjzlvWwM0PbY7+NZ0SqwqJF/31YYP78Doff8N0Rzm8vkLxAIaLDcIn/DhiixvInP/4ppf9rkmfBf9m5oU2ZV73oUyNNPPxf6qUsQffJ+xJ64DypqFNz+EOkvt/xh2cqoYfnrSZzsHYDBnYMuHvkLyou8avJG6fGR/5RgZbOgndMa72DcfQh4rsd5eWcEFa4eD5zWFC97Jddqtw/ugfG3nyH4zk+5Sv7BgASigFU0r3LLBYsSy6si/QW3P9E8CoHXXwN94atztvURf6FT98G/9Er4Fi+F8dDvYa59atj9mq28gZhRVvnbClDFf2nqFfn7BxmJ8ik9x87u/g5S2Xpag6a8yKsgr1kTmBSsTgrUCX6BT08V2DoA3HtYYVUfYBRwp0gApzYCF49SOKsFFTu/TDzfhW90nfylELCL5lUpV4DD8iqpFdz+/MuuhH7mhdW9HiXwRMsoBK/6CPp3bRzKc5Cr/UndV1b5O9k8smKgP3nkD8RXANZz9BRSswoNuGxahPIir6q83YbCg0cElo2qXgrU2Q3xLw2iNrCuH1jfp3AsJnA0ptBtAk06MEoXGO0D5jQAi5oBHWqY9Kohf33hBdBOmeM6+RfPc7f8bdtGRAmXp2wuHy9f+xMOlvmtQ/kHU0f+HR2dFpDyCCDpGUHqwcMufiZCeZFXNd7vD9pY0hwXb7XkCgB+CSxpBpY0D00gl8Qr5/kJfxCBS66l/Ksh/0gE0P2Uf4IfCFH+uXmhlJG/QtITsdSrolH+5JGXnTdgAX86VF35u53nu+hNEM2jKP9Cz0+Wtm6ACDUBUta2/FWBnc+GZso/Oy/T13tmcvI/mfSDTPKPUP7kkTec19kF7AqD8gcgx0yE/4LLKP9Knl9q/Wka9NaxNSt/27YRseyC2p8Yd3IBt66qR/k3If3rvWhq5l85+AORIQa4+1MIIU3Ki7yR4CkAdx2scfkLFMTzX/quoaQxlH8V5D/IC02cXLvyj0QKbs9y7El5eYYRqUf5iww+Tyt14gqJfD0Ft1UOAGeXkDIkrwy8TQPA48dz/7Tfis8W/GCfwvGYd+Qv55+Nxv/8JoJXvA+++Wcj1NCYkafNORP63EVVlkOs7uUfDAahj59cs/JP5uVrzzLHDEAmXp3KP5LN53qGGGC5Xf4dHZ32JVdcW7ZgThmS54T3mwMKZ7cIBJNi3fEY8HyPwnO9wPo+wBpEvGIo3DQVrpe/8vkRvOK98bzuC85H89mvhYSCtWszzE0rYW1eDfvwfkDTEbjsuhGQA+pe/lJKyAK+uKh9+U+Kvw9B+Wfj2cjzDl9qB8AT8qe8yHMD71gM+NMhhUtGCzzbo/BcN7BpIPPU1OmN8MYz/5gBa+826FPnDgvm2vR50KbPA97wTtjHDkJ1HYYcM3FkZVOn8gcAbcZp8SRMtl0WnhvlH8iXxXDm6ZR/bl7eT/eHlgUsIaMQ5U9e3fLuPQzcc1jl5VV7UZ5SePa2dQjOXZg1mMvRE4DREyj/hPyj1ZU/EP/8TZs0A9aerXnPL+Yx+QshENTyszJ1ALLx6lD+BfEkEF8RiPInj7zieaoA3qQgMMHvDflLCPi2r3exHOAi+aPqI/9hApx3Vv7rG416T/7BYN5bWPiD0GbML4gXcLBYUD3IP3kGwHOVowS6BLA/T/AVgEriCQUhlNMyQylh2faE4VkUc7fU8eOyZ7lStp2iEAHhcF1r8tzLu7DJABDxzKd+6vA+2F2HIUeNc50cAkEfRL9L5B9T8Cs1FAKqKX8A8J35akQ7/pjxMUDi+vpS35nwgPwL4emnnwPhDxTYmSj+etSD/B13ANxQmIfu/d1/APiPap/fnmOxzQDGJ4JAzl6qEHj4vj+4/mYir7K8gZ98ATi011Pf+VubV0Ge83oXjgyLz9RSKfmX7YVEh/UnmkdBm3UGrM1r8l7fWrvf9MUXVez8RNwbNS//+H3PnpETnuNnSpRrffHs44c9J38AMDeuqo3roRQihlVz8h+aBXjV8rqTvxw/CdrUuRU5PwDQpHB3Irwy8mQtFYbyJ89tPHvjKmeyFgL6q/8N0UmzHMlfNDTBd87roS8431Fnwtr5cta87N6Sv12z8gcAfe4iyJOm5pA/au5+8y+9smLyH2wXdeG3ojoAlH+cZytnCxBRhvXJ07etcTRS9112HewL3xxfac/ByF+ffw4Cl78HwatugD7/7OJnEmwL6vC+GpC/qqD8MaLyT5yEf9lbsl9fv7+m7jc5fhL008+tpPxRL/JfvnypkLVQmCqvQEgZklcwL3jpuyAnzSiKp7/mTbAWXBhfw3zMSRD+UNHT/nLyzBOCeMuHEZ08u2D5a9NPQ8MNX8563pR/Ms+J/FVZyyvnLEJ0wrSM13dEOycV4Pkvflv8+lL+pfBEIvePLPDgDZS/Mx5lWN88bcosNHzoNgTe/H6Ixpa8PG3JUtgXXH6CJwS0k6cV/RhBG+wA2LaNiGkhcNV/QI6fklP+onUMgm//T4TeezPkhMmUPyq0bkC5688w4L/sOkBqRc8Ueen66qcuhjx1MeVfovwRz/o72FbyHzyE9KxClH+BG2VIHoSAb8kyNHz8m/Cd94ahQJ0WiOYtgVp+bRqvcfrcooK5CAQhx00ankI22IDQtf8JvS1DFjndB/+yt6DxP78Bff7Z3r0eWVfk09wj/0znV4b6kxOmwH/uJTUrfxEIwnf59dWQPyxbGTUsfz2Zp+fZOciRfwkvWAhQhuSd+P/BBgQuuw6+s14L4++/hLV9w4n/N3UucPn7oJKmbBM8e8psFJP6Uk6amXGkKZtHofW9/w/GT2+D6u+JB4DTzoL/0ndBto1zZf1Zh1+Auf0PUMbx+D5SINPaG7bC4Ip8KbL2SygByEBb0fKPRm0I34mjJXjJ0ogd2l94Z2Lw/NSBLwyWRUfDxDMhT30fgEDJ9Rd4zRXw79gAdWhPzd1v+qXXwfCFKi5/WwGq+C9NvSJ//yAjUT6l59i5bj6FqJT8hwV/ypC8RNsYPwmhf/9/MDc8D+PBu4BAA/CWG6CS0uom88Tk4t4hEJNmZD+/hgbI6z4D4/5fwL/8bVnXU3dD/anwQUTXfgNQ1rCbstCResAnACOeEaUcI/+ATwDR8vLUK48gpvngO/UDpddfUzPwzk8g/INboCIDNXO/aYsvgjXv7KrI38nmEb/5U2+hjo7O9A5AUk8hNavQAOVP+ZNXxmea88+GnLUA4b5uIBDKyhNNbRCtY6C6j+ZlKgDRcVOg5Tg/OWkGQh+6zfX1Zx/fOEz+1Zmmrz7P6toAX7nqb/QEBN76EUR++63My1p77H4TJ02Duvgayr80XjB15N/R0WkBKe8AZHpGgAKzClH+lD95DniWDQSb8vK0KbMKkn/EUpAnT6uJ+hMtM3LmYagF+QOAbJxU1vrTT12MwKXXeV/+oyYAV92YdWaM8i+IF8rAG+pVp84AaBl2jlD+zhsXZUheOXi+c14POeGUHLJRiERj8Gk6RFNbTdSfbJwC36kfRGzzLwDLqE35N50C3+z3lL+9nHcJVDTi2fsDrWOAy98LJH05Q/kXzWtI4iT+ayYn/9OTfpBJ/gbl77xxWbbCHV//TtEspRRsy0LymkMCAlLTHCW2IK/OeM9trany6nIxQnpsGM+y0h8NaCWc30jwbCXRH/MBj91VsfoLqRjCj6z1XHv2mzrCP/ttWXi9vb31KP/EtKKdxIumZv7VB38gMswmRp2kCa4D+ZvF9Cx/84c/j2hPlTzyyCOPvMJ5QmV/8cRjflMpg/m0UsuhDhblXxBPAAd4M5FHHnnk1SZPCXGwxvwWyeZzmWHkb1L+ORoH1BreTOSRRx55tcnTLbG6RvxmAwjn4qV2ACzKP+/2D95M5JFHHnk1ydu2cc2Tm2vEb3m/3ku8BKhKyChUVy/8NYvw3/vRcADARN5M5JFHHnk1xFP4UT35TQOA7dt3OqroeswSGAoFQ32GGlAKb+DNRB555JFXM7x9IbvxPQcPbovVi980pxVdzymCG3R7TV8U5wKYxpuJPPLII8/zPGULcc3G1Y++XE9+c7SiQj3LHwB0XVetmrjeVtjAm4k88sgjz9s8JfDJbSuffLDe/CZrqTDV5LW06Hs0X+wiAJ28mcgjjzzyPMmLKuD9W1c+dWc9+k2rpcJUm3dk377wsQvP/c3YYz3dAuJsxNdd5s1JHnnkked+Xqcm7Ss3r3y67kb+iU3USmFGmjd//tImwx+7RgJvVMBZAE5KnWHhzUkeeeSRN2K8AQCbAfG4sK27Nq955vl69dsgs7CFlSl/RzyxdOlSDQCEgJQSjSkdAtu20a9UfK3mC772gy8LqX26ILJSsKIGUjONaP5Azuxp5HmD95qxzVgQkhkTqwghHZxe9kQtXuM9eLAb67oH2F7IQ6y7+6Orvnnr7zLF07Sh/rhxCnffbdFvQ0v/SwBKL/DgDZR/0TzV2dlpJtWfjeGJGfoeffQEb5HZ9LmmoHEZgNPyNv5YNJ4UI3HzlHozkecqXqMdA5R/qAFKWZpcDSMCKFUTvDa/DpErGxzbX13wlG3+c8WPvvIbXQzluR8WT+mjnPLXBhm5XwJMyicsKf/K8r59/pQwIN8BIMqefn3zmrQTt1upco1EIrCT5kW9zmvVJdtLnfOUbR86tnbFjXp/v6I/ipa/nsyTeXYOUtbV492+eMYapdR/82avb16rT1L+WbZWn872Uuc8o+voDdv/+tvD9EfR8ven8mSOnQOUdfV5+uJZ3wHUw7zZ65PnEwJBKSn/LFubT7K91DHPNowfrr3zCx30R9G8VJ+jo6MzvQOQpadgs7Krw2sXwtZgXQ/gKINH/fFafJR/rq1J16Anv/7N9lc/8rfsl3f+5fft9EfRvLSZ/I6OTgtIeQSQ6RkBCswqxMouH6998an7hVDvY/CoP16rT6f882wtiXck2F7qhqegjIF9u99/5OUVYfqjKF4oA2/oa4jUu07LsHOYlV193tOf+cB9Zn/v/zF41BdvXHMT5Z9na/PrbC91xrMGBj7/0k+/uZ7+KIqX6es9s6OjU6V1AJYvX5pJ/hFW9sjxtj/8p1uUZW9h8KgfXqtPc4CrH/nHZwAE20sd8WzL7Hj+f+74If1RNC/1671osvyHOgCDU/9I2dlgZY8s7+gzT/f379t9PYAwg0d98Np8epG4+pK/UjYa7BjbS53wlLL3dq1+/oN6X18v/VESz0iVf/IMgMjXU2BljwzvpZ9+c730+d7H4FEfvFafLAJXf/KPRCJo0TS2l3qQP5QRPnTwndvu/d0u+qMkXiSbz2WGkb9J+buLd8erTv0dIL7F4FHbPAGgVdcKxNWn/G1boSWxGBDbX03zrP7wJ9Z//0tP0h+OeTbyvMOXeidalL87eVr37s+i0NTDDB6e5IU0CZ8UBeDqV/4A0OLX2F5qnKfM2P+t/Opnf0R/lMTL+/Ve4m5UHR2dlL+Lee3LlpmahmsA7GPwqE1eIS8A1rv8Ex2lUDDI9ler8odacaj/wH/QH5XnSSC+IhAcbKzs6vLaF846BNt+K7LlC2Dw8DSvLU8HgPI/wWvz+9j+apEHHNaE/dafXXpZmP6oPK/4u5KVPaK828+a85wQuJHBo/Z4uZ7/U/7DeW0+yfZXezwLuvb2O847czfjfXV4jjoArOyR5d22aNZPIPBjBo/a4rX5Ncq/QF5Rn0uy/XmCJ3Xfp7509mmPMN5XjydZOd7kHbSO3wglHmLwqB1eS4YZAMo/M69Fl2x/tSR/n/97d7xq7ncY76vLk6wcb/J+fNZZsdjBg9fEIuH1DEa1wUt9B4Dyz84raMVEtj+vyP++Tdtnf4Lxvrq8ojoArGz38VZ86//ZPRtXX6OUOsBg5G2eFMNHtZR/bl6+FybZ/rzC86+I+MLX3v02YTHeV1f+y5cvLexOZWW7l7f5dz/fF3ll19UA+hmMvMtr1jXIwX0p//y8Vl2DYPvz9sjf79+lWb43fmPhwn7G+6ryRCL3jyzw4A2sbPfy1v3wm2uh6W+DKK4XzWDkHl7iCwDKvzCeLgUaM70HwPbnEfkHunUhLms/e/oBxvvqyh/xrL/xe62Ag4eQnlWIle0y3pfOmfcPKNzAYORNXotPUv5F8tI+m2T78wrPBMRb2hfPfonxvury15N5Ms/OQVa2d3i3L5n1YwHxZQYj7/FadY3yL5I37EVAtj/P8IQU779jyaxH6i0+u0D+/lSezLFzgJXtPd5ti2bcLIAfMRh5ixcyo5R/kbyhDgDbn5d4/3nbolm/ZLyvOi/V5+jo6EzvAGTpKdisbI/whFBy0cwbFPBrBiPv8FqSRrOUf2Fbm09n+/MST+Lm2xfP+i7jfdV5aTP5HR2dFpDyCCDTMwIUmFWIle0eXrsQtt69572A+BODkTd4Lbqg/IvcWnTB9ueVaX8hv3z7otlfYnyuOi+UgTf0snjqXadl2DnMyvYer33ZMlMLRN4Jhb8zGLmbp0mBRk2j/IvkBcwY258n5C++d9uiGTczPledl+nrPTM5+Z9M+kEm+UdY2d7ltc+fH9V6tKuUbT/CYOReXosUlL8DXrMmhtZOYPtzKw8/l4tmfhxCKMbnqvNSv96Lpmb+1QZ/IAYPLpJOIuokTTAvnrt4+r41tmH0/LNt6twLhZSTGdzcx5sY8uPMcW2UvwPeS71hGApsfy7kKSH+sGnrrH//39OFzfg8YjyVNJhP83niDhT5egqsbO/yDj/xWHjP4w9fDdtaxeDmPt6YxgbK3yGv1a+z/bmTd+8h+/h1xSzxy3hfMV4km89T78K0ZwSs7NrgHXj07z17n+q8Qvr8TzG4uYvXFvBR/g55Yxob2f5cOPI/aB+/+sdnnRVjfB5Rno087/Cl3okW5V+7vP0P37M/4gtfAqCDwc09vDZdKxJH+Sd4owI625+r5C9/tmnrzHdS/q7g5f16L3E3qo6OTsq/DnjfWLiwX+vW3gjgbwxu7uC16rIIHOWfzGstsvPE9ldR3nf1RTM+yGl/7/ActABWdi3wPrhihW+8bPulAK5lcBtZ3sdmjkdQigJwlH8q75VIDL/ec4ztb4R5Qoov3nbmzFv5tr+3eMXflazsmuD9+KyzYpu2zrwOwE8Z3EaOF5CC8i+BNywfANvfiPCUkP9926JZt1D+3uM56gCwsmuDd/fbhHX7opkfhFB3MriNDK/VpxeAo/yz8Ro0CV++a8T2Vzn5AzfesXjmVxlPvcmTrJw65wmhbj9z1icB8XkGt+rzWn2S8i+Rl3MWgO2vUrwYgOvuWDL7+4yn3uVJVg55EELdvnjmbcoyr7eMSIzBrXq8thwzAJR/YbysHQC2v4rwpD/QBYHX3b5k9m8YT73LK6oDwMqufd6zN334r+GjB69USnUzWFaHl+0LAMq/cF5bpg4A21+l5L9DV/r5ty+e/Rjjqbflv3z5UqGxcshL5h167vHdLTNmPxBoG3OJFgi2MlhWlrd4VCNGpQiM8i+OdyxqYcdAlO2v0vIPBJ7TdXFx++KZuxhPPc0TM2ZM0wqaAciRVYiVXaO8jT//7saYsM4VAs8zWFaWlzoDQPkXz2vza2x/lZa/z/eX/khwWfvCWYcYT70tfwzmAMrbAUjKJyxZ2fXF+/p5Z74i7cZlEPgrg2VleAJAS9JCNpS/M97QYkBsf5WRv+7/pr5kztXfPn9KmPHU8/LXk3kiz84J+cvBfVnZdca7+o9KO3Xmtq9A4NMMluXlNWkC7x4fGsQpRCIRqCSeEAkZCgenVz88SwG/OdCHHiPK9ldOHoQpff6P3fGqOT9gPK0J+ftxIjugDcAWOXYODE4ViKQZAFZ2nfJuXbn17QB+hvjjIAbLMvAm+gRe549BKQXDMNJkGAgEHMu13ngPdZt4JXkBWra/EuWPg0LXrv7i2ac9wfhXEzz/4F/tRAego6PTkjl6CqlZhVjZdcy7ffGs30OqswFsYbAsD29uUFL+ZeJN87H9lYunlP2sGcMSyr9meMFUXkdHpwWkvAOQ6RkBCswqxMqufd7tZ87eELXFq6BwD4NlaTxdAqfApPzLxJusK2iC7a9k+ZuxH4UPbl32tYvO2Mf4VxO8UAbe0FyZSPmBjuHP/AWACCubvJTAIW5+cctnVSz6RSglKf/iea9r1tA20E35l5G3W/nwvM3254SnlIqY/X0fW/W1//czxr+a4TUkcezBP9HkrL9a0g+0FPEDgMHKJi+N99Rjcl/HfWvGLjjrOS3U8HohRAPlX/i2pEHiZKOX8i8zb0LQB0PTcdRi+ytS/rvCB19509o7v3Af41/N8RKNJk3+Qx2Awal/kSL/tJ1Z2eQl8w4+99iu0PhJfwmNG3+WHghNovxzbxLA8mYNJxu9sG2b8q8Ab5K0EfRp2B9j+yuEZ5vWg0fXPHfV5l//4CXGv5rkJd76j2TyuQYAM2ZMSx35xyh/8grhdW1Y2d3c1vCT4NS5lgBegxyfltZr8JUAljTqWBay4BvgyL/SvLHCxryQhKlE7tmAOpa/UipiDgz81/Pfbb+5Z+2Lhxn/apZnZ5M/EjsO/jDRAbAof/Kc8G5dueU8CPEbKMyg/IFJfomzGiRarShsy6KsR4AnpMRx6FgZUdgXU0PzoXUtf8teO7B75/s3/N+3NzH+1TTPQp4X+FM7ADblT14pvP/auLE50K9/FwLX16P8GzSJec1BzGsO4uSgbxDHFf5GmqcA7B6IYmNfBJt6I+gPh+tR/sqKRr677e6fffH4po0G4xV5iQ6AcCJ+VjZ52X73uVVbrlZK/AjAqFqXvwAwszGARa0hTG0IQArK2s28gXAEuwYMbOiJYNtAFAqoefkr237F6Dr8wbV33v4E4xV5wzoATjdWNnm5tvY1WyZbpvgVBJbVqvxPCflx8bhmjAvolKsHeT2WwtO9MWxLziZYY/K3Y7F7j6x85uM77/9jF+MVeWXpALCyyStka1dKWqu23agUvmRHI421JP+z2xqwdFwz5VoDvJXHB/Cvw71QHmp/eXlG5Eist+e/X/jfL/1Z7+9XjFfkpW4a5U9eJXmdX/iC8m9b+0LTxMl/9bW1zRZSzqwF+fukwNsnj87Yg6Zcvcc7KejDpj4DA5btifaXjxcd6PvtwWc7r930q++/KGMxxivyyjMDwMomzynPbGwUr7rh/73V19zyVS0QHOP1Z66LWkO4YEwTGjRJuXqYF1MKLx4fwBNH+vLPALhc/kqpHZFDBz6x+pu3djJekVfWDgArm7xy8M6++WtBrXX01wG826vyH5pCEwITAzrGBnS06RJNtolWXcA3+CagELIMKXOTFw0irxSeDYkBqeO4aaMrZuFo1MT+SAwxW3my/SVtNiz7Wzvv/eM3Xnm2I8J4RV5ZOwCsbPLKzbtl9ZbXC1v8CMC0Wph2JY+8EeEprLKj0Q89d+sNmxhfyCuCWVhLY2WTVynep9esaWywGm5VEJ/AiZzVlAN55OXfupVSX+h65oHvb7rnL0HGF/IKFT/iC5QqUeDBGzD8hUFWNnll5d2yYstMIcXXALyFciCPvJybLYCfxKBufeG/PnCU8YW8IuWvDTJydwCS8glrGJ5cgJVNXkV4n1u9dZlt404BLKAcyCMvlYFHNciPty+ZsZbxhTwH8k8sWGLn7AAM7hwanCpILBXMyiav4ryr/6i0udM2v982o3cAGEs5kEee2KEUPn3H4hl/hRCK8YU8B/L340R2QHtwJinrzoGkkX/i+xtWNnlV401c9m+tky9Y9l/S7/8QhPRRDuTVIa8PEF/UuuWd7cumRxhfyHPI8yeP/BHP+2OJHD2F5JE/kCerECubvErx5r/3P6c3zZn/KSnlO5I6o5QDebXMM4TCD6WlfaX97OkHGA/IK4EXTB35d3R0mkg5SPIzgtSRf5iVTd5I8255cfM8CPl5AbwNhXzCStmQ5z1eTED9VGr4UvvC2XsZD8grkRdKGflbAMxE8r/UDoCeMvIXACKsbPLcxLt1xbYzIFU7Ur8YoGzI8y7PUhC/0IW8o33R9J2MB+SVgdeQxLEH/0STM/+KpB9oSeJPjPyjrGzy3Mq7dfWWRbDlFwD1RsqGPI/ybAXcpSvc1r5k1lbGA/LKyEsd+RvJ8h/qACQtDJA88o+m7szKJs+NvFtXbXsVYH8OSlxO2ZDnEZ4FqD8ope64Y8mclxkPyKsAL3nkH87kc5H0o+SRv0n5k+c13q1PvXxa1Ix8Rvq0awVEgLIhz4W8Hij82Lbw3S+ePWsP71/yKshLjPyzvsOX2gEQACzKnzwv82Zc+Y5xo+ad+X4tGHp/LWQdJK8meLsB9R0tip+2nzu7h/cveVXgWcjz9V5qB8Cm/MmrFd6sj9xujZ120ruEwCcBzKG8yBsB3osC4psH7K4//fiss2K8f8lzE2/oHQAn4mdlk+cFXrtS0ly19XIB8VEAyykv8irKgzBlwH+/EOLO2xfNehxCKN6/5LmR56DFs7LJ8y6v/YXNMywp3weBfwdwEuVFXrl4yra3W7HYL33w//SLyxbs5/1Gntt5jjsArGzyvMxrf/RR3WydfJkQ+ACUuAxKScqQvGJ5StmGMmN/M7q7frnqZ3c+qff19fJ+I88rPEcdAFY2ebXEu+WZ1afEDPMD0ud7jxByCmVIXj6eGQm/bEbCv+pa9+Lvd97/xy7eb+R5kSdYOeSRF+cFxo6Tc9/14QsCLaPeojU2v1EIMZYyJC9p260s++7efbvuW/N/d67R+/sV7zfyvMwTrBzyyEvnvfrmz0u7beoypey3AeotgBhNGdYlbx+Eutu28Yf7//bLF8Y99WQj7w/yaoFXVAeAlU1evfI+uGKFb7xou1gA10DgSgCtlGtN8w4CuBtK/lFbPP2pdiFs3h/k1Zr8ly9fWtidxcomj7z49tEtWwKtffJiCVymoC6FwgzKtSZ464TCg7ZU/9i0ZdYTd79NWLw/yKtRXmLVXyUKPHgD4imCWdnkkXdCNOKmZzfMscIDb5I+3+uE1F4thAhSrp7gdQPiYQH7Qanhn6mpd3l/kFfD8tcGGbk7AEn5hDUMX1+YlU0eeSm8tgXnh6Yuf8P5DeMmXgRNvgHFrD5IWVecp4CVQuFBW4kHDqPruXwr8/H+IK8G5a8P/tXO2QEY3DmE4VkCWdnkkVcg7+bVmyZpSp6vgPOhxAUAFiXdgJR1JRflUSoiFF7UgsEnhKY9Kf3+Z9rnTznG9kxeHcvfnxj5D3YAbJFj50DSyD+RJZCVTR55DnntK/Y3WLL/VRC4YLBDcD6ANsq/DDwjcsg2zefsmPFs9HjXc/sf//vqYxvWd7H9kUfeUjko/6GRP+J5fyyRo6eQPPIH8mQVYmWTR15xvHalpPXs+tMi/X3naJp2OnTfaUJqpwspT6b8c2wC22FjLYRaC9Ne17V5w5b1d/9sF7/LJ4+8jLxg6si/o6PTROp0ZNIzgtSDh1nZ5JFXXt6Tr1sGALsB7AHwpwTv7C98xw+B+QpYIIAFAmqBgjgd8Zdx60n+xwGsE0KsVbZaa0OtjTVaG7526qm9qddDZ/sjj7xMvFASJ/HfoS9cUp9HahkOHmFlk0de9XhfumChDeCxwT8JeYr2F3ZOiGnmNCnENCXUdAExDUpNR/y/U62o4fdY1rwB4fNvh8ROobBDQe1USu4Umr0jJv07v3LGKcezZdJjeyGPvLy8hgzyN5Mz/4qkH2g4MeWfeOYfZWWTR577eYEJE1omLX3DSaHxk6b4/IGx0hds87e2NQtdG62UGCOgxgqBMUphDDD0Ryuz/GMAjiiFo0LgCCCOCKijSllHjePH+5QZO2ab5tFYJHLE2L9r17NH9uxe84U7LF5f8sirCA848czfAmAky3+oA5C0MECiAyAG5a9Y2eSRV4M8pcQHX3xRPzkUChh9MiC0kN+vqYARC4cGdu1qVVIEhS79mqb5IYQdGjuhW+qBiKbDgBLRmC0MnykMNOrRPvOY0ROLRX+8ZImZOmLn9SCPvBHjDT3zR/wxfprP9dSZgMEfxSh/8sirYZ4Q6sfxEXsMQF8Kbz/rjzzyaoYXyebz1HcAFACL8iePPPLII488T/MSI/+sPJnyd8qfPPLII4888rzPy/vpfmIGQJWQUYiVTR555JFHHnke4zn4noeVTR555JFHHnle5znuALCyySOPPPLII8+7PEcdAFY2eeSRRx555HmbJ1g55JFHHnnkkVd/PMHKIY888sgjj7z64hXVAWBlk0ceeeSRR15tyH/58qWFZfVgZZNHHnnkkUdeTfASS/8rWeDBG1jZ5JFHHnnkked5+Q8lAZMFHDyUsh8rmzzyyCOPPPK8J389mSfz7BxkZZNHHnnkkUee5+XvT+XpOXYOsLLJI4888sgjz/M8fxIHANDR0ZneAcjSU7BRQGIBVjZ55JFHHnnkuYoXHGQk5K86OjotICUdcKZnBIM/CrOyySOPPPLII89TvFDKyF8BsBL7pM4AaBkOHmFlk0ceeeSRR56neA0Z5G92dHSqtA7A8uVLM8nfYGWTRx555JFHnud4QPzxfYIXTZb/UAdgcOofKQdP25mVTR555JFHHnme4KmUwXyazxMzAKkHj1H+5JFHHnnkked5XiSbz/UMI3+L8iePPPLII488T/Ns5HmBP3UhIMqfPPLII4888rzPy/vpfmIGQJWQUYiVTR555JFHHnke4wmUsLGyySOPPPLII8+bPMcdAFY2eeSRRx555HmX56gDwMomjzzyyCOPPG/zBCuHPPLII4888uqPJ1g55JFHHnnkkVdfvKI6AKxs8sgjjzzyyKsN+S9fvlQIVg555JFHHnnk1Q1PIL4GkJIFHryBlU0eeeSRRx55npe/lvi7LODgoZT9WNnkkUceeeSR5z3568k8mWfnICubPPLII4888jwvf38qT8+xc4CVTR555JFHHnme5/mTOACAjo7O9A5Alp6CjQISC7CyySOPPPLII89VvOAgIyF/1dHRaQEp6YAzPSMY/FGYlU0eeeSRRx55nuKFUkb+CoCV2Cd1BkDLcPAIK5s88sgjjzzyPMVryCB/s6OjU6V1AJYvX5pJ/gYrmzzyyCOPPPI8xwPij+8TvGiy/Ic6AINT/0g5eNrOrGzyyCOPPPLI8wRPpQzm03yemAFIPXiM8iePPPLII488z/Mi2XyuZxj5W5Q/eeSRRx555HmaZyPPC/ypCwFR/uSRRx555JHnfV7eT/cTMwCqhIxCrGzyyCOPPPLI8xhPoISNlU0eeeSRRx553uQ57gCwsskjjzzyyCPPuzy9BPmnLjJQ6nLB5JFHHnnkkUdelXjC4cFDKO9yweSRRx555JFHXhV5wsHBM6UIjpSYqIA88sgjjzzyyKsSr6gOQI4UwYbDTwfJI4888sgjj7wq8xJMUcTB/RkOHi2hMOSRRx555JFHXvV5EoASBe6cKUWwWcLBySOPPPLII4+86vO0QYbSCtw59eBWiQcnjzzyyCOPPPJGhqeA/J8BykRPIfEPHR2dFpxv5JFHHnnkkUfeyPOyzwBkShHs9E1D8sgjjzzyyCPPVTz1/wGXhpSVB+AzKQAAAABJRU5ErkJggg=='; ================================================ FILE: tsconfig.compiler.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "commonjs", "moduleResolution": "Node", "experimentalDecorators": true, "emitDecoratorMetadata": true, "strict": true, "jsx": "react-jsx", "sourceMap": false, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noUnusedLocals": false, "noUnusedParameters": false, "strictNullChecks": true, "noImplicitAny": false, "allowJs": true, "lib": [ "ESNext", "DOM" ], "skipLibCheck": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "rootDir": ".", "paths": { "@share/*": [ "share/*" ] }, "outDir": "./dist", "types": [ "utools-api-types", "node" ] }, "exclude": [ "node_modules", "dist", "llm", "build.js", "uTools.js", "esbuild.js" ] } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "commonjs", "moduleResolution": "Node", "experimentalDecorators": true, "emitDecoratorMetadata": true, "strict": true, "jsx": "react-jsx", "sourceMap": false, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "noUnusedLocals": false, "noUnusedParameters": false, "strictNullChecks": true, "noImplicitAny": false, "allowJs": true, "lib": [ "ESNext", "DOM" ], "skipLibCheck": true, "allowSyntheticDefaultImports": true, "baseUrl": ".", "rootDir": ".", "paths": { "@share/*": [ "share/*" ] }, "outDir": "./dist", "types": [ "utools-api-types", "node" ] }, "exclude": [ "node_modules", "dist" ] } ================================================ FILE: uTools/Ask ChatGPT/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask ChatGPT/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { askChatGPT as askOpenai } from '@share/utils/uTools'; import { AskChatGPT } from '@share/uTools/webviewBaseController'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: clipboard.readText(), }), }); }; // 给页面调用的 export const askChatGPT: AskChatGPT = (data) => askOpenai({ ...data, model: undefined }); ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON/config/schema.ts ================================================ export type IOption = { value: string; label: string }[]; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON/config/template.ejs ================================================ export const <%- rawSelectedText %>Options = <%- content %> export const <%- rawSelectedText %>Map = <%- rawSelectedText %>Options.reduce((obj, { label, value }) => { obj[value] = label return obj }, {}) ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON/script/src/main.ts ================================================ import path from 'path'; import fs from 'fs'; import { clipboard } from 'electron'; import { validate } from '@share/TypeChatSlim/utools'; import { compile as compileEjs } from '@share/utils/ejs'; import { askChatGPT as askOpenai, getBlockConfigPath, getBlockJsonValidSchema, } from '@share/utils/uTools'; export const bootstrap = async (scriptFile?: string) => { const configPath = getBlockConfigPath(scriptFile!); const schema = getBlockJsonValidSchema(scriptFile!); const clipboardText = (clipboard.readText() || '').trim() || '客户验收状态:1.无需验收、2.待验收、3已验收'; const typeName = 'IOption'; const requestPrompt = `You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; const content = await askOpenai({ messages: [{ role: 'user', content: requestPrompt }], handleChunk: () => {}, }); const valid = validate(content.content, schema, typeName); if (valid.success) { const template = fs.readFileSync( path.join(configPath, 'template.ejs'), 'utf8', ); const code = compileEjs(template, { rawSelectedText: '请手动修改名称', content: JSON.stringify(valid.data), } as any); utools.outPlugin(); utools.hideMainWindowPasteText(code); } else { return valid.message; } }; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Form/config/schema.ts ================================================ export type IOption = { value: string; label: string }[]; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Form/config/template.ejs ================================================ export const <%- rawSelectedText %>Options = <%- content %> export const <%- rawSelectedText %>Map = <%- rawSelectedText %>Options.reduce((obj, { label, value }) => { obj[value] = label return obj }, {}) ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Form/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Form/script/src/main.ts ================================================ import path from 'path'; import fs from 'fs'; import { clipboard } from 'electron'; import { validate } from '@share/TypeChatSlim/utools'; import { compile as compileEjs } from '@share/utils/ejs'; import { askChatGPT as askOpenai, getBlockConfigPath, getBlockJsonValidSchema, } from '@share/utils/uTools'; export const bootstrap = async (scriptFile?: string) => { const schema = getBlockJsonValidSchema(scriptFile!); const clipboardText = (clipboard.readText() || '').trim() || '客户验收状态:1.无需验收、2.待验收、3已验收'; const typeName = 'IOption'; const requestPrompt = `You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: requestPrompt, }), }); }; type LLMMessage = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; // 给页面调用的 export const askChatGPT = async (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; scriptFile: string; }) => { const configPath = getBlockConfigPath(data.scriptFile); const schema = getBlockJsonValidSchema(data.scriptFile); const template = fs.readFileSync( path.join(configPath, 'template.ejs'), 'utf8', ); const typeName = 'IOption'; if ( data.messages.length >= 3 && (data.messages[data.messages.length - 1].content as string).includes('>>>') ) { const name = (data.messages[data.messages.length - 1].content as string) .split('>>>')[1] .trim(); const jsonValid = validate( data.messages[data.messages.length - 2].content as string, schema, typeName, ); if (jsonValid.success) { setTimeout(() => { const code = compileEjs(template, { rawSelectedText: name || '请手动修改名称', content: JSON.stringify(jsonValid.data), } as any); clipboard.writeText(code); utools.outPlugin(); utools.hideMainWindowPasteText(code); }, 300); } else { data.handleChunk(` > 生成代码时 JSON 校验不通过 ${jsonValid.message} `); } return ''; } const content = await askOpenai({ messages: data.messages, handleChunk: data.handleChunk, }); const valid = validate(content.content, schema, typeName); if (valid.success) { data.handleChunk(` JSON 校验通过,输入\`>>>\`生成代码 `); } else { data.handleChunk(` > JSON 校验不通过 ${valid.message} `); } return content; }; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Prompt/config/schema.ts ================================================ export type IOption = { value: string; label: string }[]; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Prompt/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask ChatGPT-生成 value-label 格式 JSON - Prompt/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { getBlockJsonValidSchema } from '@share/utils/uTools'; export const bootstrap = async (scriptFile?: string) => { const schema = getBlockJsonValidSchema(scriptFile!); const clipboardText = (clipboard.readText() || '').trim() || '客户验收状态:1.无需验收、2.待验收、3已验收'; const typeName = 'IOption'; const requestPrompt = `You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; clipboard.writeText(requestPrompt); utools.showNotification('prompt 已经写入剪贴板'); return requestPrompt; }; ================================================ FILE: uTools/Ask Gemini/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask Gemini/script/src/main.ts ================================================ import { clipboard } from 'electron'; export const bootstrap = async () => { utools.setUBrowserProxy({ proxyRules: 'http://127.0.0.1:7890' }); utools.ubrowser // .devTools('bottom') .useragent('Chrome') .goto('https://gemini.google.com/') .wait('rich-textarea') .when('rich-textarea') .wait(1000) .focus('rich-textarea') // .wait(300) // .paste('你好') // .wait(1000) // .press('Enter') .end() .run({}); utools.outPlugin(); utools.hideMainWindow(); }; ================================================ FILE: uTools/Ask Groq/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask Groq/script/src/main.ts ================================================ import { clipboard } from 'electron'; export const bootstrap = async () => { utools.setUBrowserProxy({ proxyRules: 'http://127.0.0.1:7890' }); utools.ubrowser // .devTools('bottom') .useragent('Chrome') .goto('https://groq.com/') .wait('#model-selector') .wait('#chat') .when('#chat') .wait(1000) .focus('#chat') // .wait(300) // .paste('你好') // .wait(1000) // .press('Enter') .end() .run({}); utools.outPlugin(); utools.hideMainWindow(); }; ================================================ FILE: uTools/Ask Kimi/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask Kimi/script/src/main.ts ================================================ import { clipboard } from 'electron'; export const bootstrap = async () => { utools.ubrowser .goto('https://kimi.moonshot.cn/') .wait('div[role="textbox"]') .focus('div[role="textbox"]') // .paste('你好') // .wait(300) // .press('Enter') .run({}) .catch((err) => { utools.showNotification(err.message); }); utools.outPlugin(); utools.hideMainWindow(); }; ================================================ FILE: uTools/Ask Perplexity/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Ask Perplexity/script/src/main.ts ================================================ import { clipboard } from 'electron'; export const bootstrap = async () => { utools.setUBrowserProxy({ proxyRules: 'http://127.0.0.1:7890' }); utools.ubrowser // .devTools('bottom') // .useragent('Chrome') // .useragent( // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0', // ) // windows // .useragent( // 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) uTools/4.0.1 Chrome/108.0.5359.215 Electron/22.3.12 Safari/537.36', // ) .goto('https://www.perplexity.ai/') .wait('textarea') .when('textarea') .wait(1000) .focus('textarea') // .wait(300) // .paste('你好') // .wait(1000) // .press('Enter') .end() .run({}); utools.outPlugin(); utools.hideMainWindow(); }; ================================================ FILE: uTools/Chat With Form Demo/config/config.json ================================================ { "scripts": [] } ================================================ FILE: uTools/Chat With Form Demo/config/model.json ================================================ { "file": "", "items": [] } ================================================ FILE: uTools/Chat With Form Demo/config/schema.json ================================================ { "type": "page", "body": [ { "type": "form", "body": [ { "type": "input-file", "name": "file", "label": "", "accept": "*", "id": "u:c04ee740c509", "asBase64": true, "btnLabel": "文件上传", "multiple": false, "uploadType": "bos", "proxy": false, "autoUpload": true, "useChunk": false, "drag": true, "asBlob": false, "bos": "default" }, { "type": "select", "label": "选项", "name": "select", "id": "u:fce7d967cf31", "multiple": false, "source": "${items}" } ], "title": "", "submitText": "", "id": "u:750900f46569", "actions": [], "feat": "Insert", "dsType": "api", "labelAlign": "left" } ], "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": {}, "asideResizor": false, "themeCss": { "baseControlClassName": { "boxShadow:default": " 0px 0px 0px 0px transparent" } }, "id": "u:92f76614d33d" } ================================================ FILE: uTools/Chat With Form Demo/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Chat With Form Demo/script/src/controller.ts ================================================ import path from 'path'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { clipboard } from 'electron'; import { generalBasic } from '@share/BaiduOCR/index'; import { getShareData } from '@share/utils/shareData'; import { renderEjsTemplates } from '@share/utils/ejs'; import { typescriptToMock } from '@share/utils/platformIndependent/json'; import { MethodHandle } from '@share/uTools/webviewBaseController'; import { getBlockJsonValidSchema, getBlockPath } from '@share/utils/uTools'; export const openChatGPT: MethodHandle = async (data) => { const schema = getBlockJsonValidSchema(data.scriptFile); const clipboardText = JSON.stringify(data.model); const typeName = 'IOption'; const requestPrompt = `You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; return { updateModelImmediately: true, onlyUpdateParams: false, params: '', showChat: true, chatContent: requestPrompt, model: data.model, }; }; ================================================ FILE: uTools/Chat With Form Demo/script/src/main.ts ================================================ import { AskChatGPT, LLMMessage, MethodHandle, } from '@share/uTools/webviewBaseController'; import { askChatGPT as askOpenai } from '@share/utils/uTools'; import * as controller from './controller'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', form: { name: 123, }, }), }); }; // 给页面调用的 export const askChatGPT: AskChatGPT = async (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; model?: object; }) => { // data.handleChunk(JSON.stringify(data.model || { a: 123 })); const res = await askOpenai({ ...data, model: undefined }); return { content: res.content, showForm: true, updateModelImmediately: true, model: { ...data.model, items: [ { label: 'A', value: 'a', }, { label: 'B', value: 'b', }, { label: 'C', value: 'c', }, ], }, }; }; export const runDynamicFormScript: MethodHandle = (data) => { if (controller[data.method]) { return controller[data.method](data); } return Promise.reject(`方法不存在:${data.method}`); }; ================================================ FILE: uTools/Git 获取当前用户最近一次 Commit 信息/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Git 获取当前用户最近一次 Commit 信息/script/src/main.ts ================================================ import { clipboard } from 'electron'; import * as execa from 'execa'; import { getShareData } from '@share/utils/shareData'; export const bootstrap = (scriptFile?: string) => { const { activeWindow } = getShareData(); try { const projectPath = utools.isWindows() && activeWindow?.startsWith('/') ? activeWindow.substring(1) : activeWindow; const userName = execa.sync('git', [ '-C', projectPath || '.', 'config', 'user.name', ]); const res = execa.sync('git', [ '-C', projectPath || '.', 'log', '-1', '--pretty=format:%s', `--author=${userName.stdout}`, // 'log -1 --pretty=format:"%s" --author="$(git config user.name)"', ]); if (res.stdout) { utools.outPlugin(); utools.hideMainWindowPasteText(res.stdout); return; } return JSON.stringify(res); } catch (ex) { return (ex as object).toString(); } }; ================================================ FILE: uTools/Open ChatGPT/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Open ChatGPT/script/src/main.ts ================================================ import { askChatGPT as askOpenai } from '@share/utils/uTools'; import { AskChatGPT } from '@share/uTools/webviewBaseController'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', }), }); }; // 给页面调用的 export const askChatGPT: AskChatGPT = (data) => askOpenai({ ...data, model: undefined }); ================================================ FILE: uTools/Open ChatGPT-获取命令行命令/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Open ChatGPT-获取命令行命令/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { askChatGPT as askOpenai } from '@share/utils/uTools'; export const bootstrap = async (scriptFile?: string) => { const text = clipboard.readText(); let platform = utools.isWindows() ? 'windows' : 'mac'; if (utools.isLinux()) { platform = 'linux'; } if (!text) { utools.showNotification('请先复制内容'); return; } utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: `${platform} 平台下, ${text},返回可执行的命令即可,不要带多余的信息`, }), }); }; type LLMMessage = ( | { role: 'system'; content: string; } | { role: 'user'; content: | string | ( | { type: 'image_url'; image_url: { url: string }; } | { type: 'text'; text: string } )[]; } )[]; // 给页面调用的 export const askChatGPT = async (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; scriptFile: string; }) => { const content = await askOpenai({ messages: data.messages, handleChunk: data.handleChunk, }); return content; }; ================================================ FILE: uTools/Open Tldraw/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/Open Tldraw/script/src/main.ts ================================================ import { askChatGPT as askOpenai } from '@share/utils/uTools'; import { AskChatGPT } from '@share/uTools/webviewBaseController'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/tldraw' }), }); }; // 给页面调用的 export const askChatGPT: AskChatGPT = (data) => askOpenai({ ...data, model: undefined }); ================================================ FILE: uTools/TS 类型新增字段 - 根据 YAPI 文档字段格式/prompt.md ================================================ ``` dateType integer 非必须 日期类型 0~录入时间、1~成交时间、2~结案时间、3~申佣时间 ``` 根据上面的内容生成如下 TS 代码 ```js /** 日期类型 0~录入时间、1~成交时间、2~结案时间、3~申佣时间 */ dateType: number; ``` 按照上面的规则,请根据下面的内容生成代码: ``` ``` ================================================ FILE: uTools/TS 类型新增字段 - 根据 YAPI 文档字段格式/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/TS 类型新增字段 - 根据 YAPI 文档字段格式/script/src/main.ts ================================================ import { askChatGPT as askOpenai } from '@share/utils/uTools'; import { AskChatGPT } from '@share/uTools/webviewBaseController'; import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { const clipboardText = clipboard.readText() || ''; if (!clipboardText) { return '请复制内容'; } const requestPrompt = ` \`\`\` dateType integer 非必须 日期类型 0~录入时间、1~成交时间、2~结案时间、3~申佣时间 \`\`\` 根据上面的内容生成如下 TS 代码 \`\`\`js /** 日期类型 0~录入时间、1~成交时间、2~结案时间、3~申佣时间 */ dateType: number; \`\`\` 按照上面的规则,请根据下面的内容生成代码: \`\`\` ${clipboardText .split(/\t|\n/) .filter((s) => s) .join('\n')} \`\`\` `; // const content = await askOpenai({ // messages: [{ role: 'user', content: requestPrompt }], // handleChunk: () => {}, // }); // utools.outPlugin(); // utools.hideMainWindowPasteText( // content.content.replace(/```js/g, '').replace(/```/g, ''), // ); utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: requestPrompt, }), }); }; // 给页面调用的 export const askChatGPT: AskChatGPT = async (data) => { const res = await askOpenai({ ...data, model: undefined }); setTimeout(() => { utools.outPlugin(); utools.hideMainWindowPasteText( res.content.replace(/```js/g, '').replace(/```/g, ''), ); }, 2000); return res; }; ================================================ FILE: uTools/TS 类型新增字段 - 根据 YAPI 文档字段格式 - 截图/README.md ================================================ ```html
provinceName string 非必须 省份
provinceCode string 非必须 省份编码
cityName string 非必须 城市
``` 根据上面的内容生成如下 TS 代码 ```js /** 省份 */ provinceName: string; /** 省份编码 */ provinceCode: string; /** 城市 */ cityName: string; ``` 按照上面的规则,请根据下面的内容生成代码: ```html ``` ================================================ FILE: uTools/TS 类型新增字段 - 根据 YAPI 文档字段格式 - 截图/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/TS 类型新增字段 - 根据 YAPI 文档字段格式 - 截图/script/src/main.ts ================================================ import { AskChatGPT } from '@share/uTools/webviewBaseController'; import { askChatGPT as askOpenai, ocr } from '@share/utils/uTools'; import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { const availableFormats = clipboard.availableFormats('clipboard'); if (!availableFormats.some((s) => s.includes('image'))) { return '剪贴板里没有截图'; } const base64 = clipboard.readImage('clipboard').toDataURL(); const ocrRes = await ocr({ base64, model: 'structure_table' }); if (ocrRes.result?.texts && ocrRes.result.texts[0]) { const requestPrompt = ` \`\`\`html
provinceName string 非必须 省份
provinceCode string 非必须 省份编码
cityName string 非必须 城市
\`\`\` 根据上面的内容生成如下 TS 代码 \`\`\`js /** 省份 */ provinceName: string; /** 省份编码 */ provinceCode: string; /** 城市 */ cityName: string; \`\`\` 按照上面的规则,请根据下面的内容生成代码: \`\`\`html ${ocrRes.result.texts[0]} \`\`\` `; // const content = await askOpenai({ // messages: [{ role: 'user', content: requestPrompt }], // handleChunk: () => {}, // }); // utools.outPlugin(); // utools.hideMainWindowPasteText( // content.content.replace(/```js/g, '').replace(/```/g, ''), // ); utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: requestPrompt, }), }); } }; // 给页面调用的 export const askChatGPT: AskChatGPT = async (data) => { const res = await askOpenai({ ...data, model: undefined }); setTimeout(() => { utools.outPlugin(); utools.hideMainWindowPasteText( res.content.replace(/```js/g, '').replace(/```/g, ''), ); }, 2000); return res; }; ================================================ FILE: uTools/vscode 选中的文件夹/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/vscode 选中的文件夹/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { getShareData } from '@share/utils/shareData'; export const bootstrap = async (scriptFile?: string) => { const data = getShareData(); return data.selectedFolder || ''; }; ================================================ FILE: uTools/中文翻译英文/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/中文翻译英文/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { createChatCompletion } from '@share/LLM/openaiV2'; export const bootstrap = async () => { const text = clipboard.readText(); if (!text) { utools.showNotification('请先复制内容'); return; } const res = await createChatCompletion({ messages: [ { role: 'system', content: `你是一个翻译家,你的目标是把中文翻译成英文,请翻译时不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面用户输入的内容`, }, { role: 'user', content: text, }, ], }); return res; }; ================================================ FILE: uTools/动态表单 demo/config/config.json ================================================ { "scripts": [ { "method": "initFiltersFromImage", "remark": "使用 OCR 识别查询条件截图内容" }, { "method": "initFiltersFromText", "remark": "使用文本初始化查询条件" }, { "method": "initColumnsFromText", "remark": "使用文本初始化表格" }, { "method": "openChatGPT", "remark": "使用 ChatGPT 翻译模版数据里的指定中文字段" }, { "method": "generateCode", "remark": "生成代码" } ] } ================================================ FILE: uTools/动态表单 demo/config/model.json ================================================ { "filters": [], "columns": [], "pagination": { "show": true, "page": "page", "size": "size", "total": "result.total" }, "includeModifyModal": false, "fetchName": "fetchTableList", "result": "[\"result\"][\"records\"]", "serviceName": "getTableList" } ================================================ FILE: uTools/动态表单 demo/config/schema.json ================================================ { "type": "page", "body": [ { "type": "form", "title": "", "body": [ { "type": "combo", "label": "查询条件", "name": "filters", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:47ecb9e15ff1" }, "items": [ { "type": "input-text", "name": "key", "placeholder": "字段名", "id": "u:25b0c7b5e5a0", "label": "字段名(key)" }, { "type": "input-text", "label": "label", "name": "label", "id": "u:6496cac4f4b8", "description": "" }, { "type": "select", "name": "component", "placeholder": "选项", "options": [ { "label": "input", "value": "input" }, { "label": "select", "value": "select" } ], "id": "u:995915eabcca", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "label": "placeholder", "name": "placeholder", "id": "u:d7f1a8a39449", "description": "" } ], "id": "u:186f183e9320", "strictMode": false, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false }, { "type": "combo", "label": "表格", "name": "columns", "multiple": true, "addable": true, "removable": true, "removableMode": "button", "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:1e8070edc3d3" }, "items": [ { "type": "input-text", "name": "title", "id": "u:152dd82b82f9", "label": "title" }, { "type": "input-text", "label": "dataIndex", "name": "dataIndex", "id": "u:ecc7298e0550", "description": "" }, { "type": "input-text", "label": "key", "name": "key", "id": "u:fbaa95c3f15d", "description": "" }, { "type": "input-text", "label": "width", "name": "width", "id": "u:b143127e097b", "description": "" }, { "type": "switch", "label": "自定义插槽", "option": "", "name": "slot", "falseValue": false, "trueValue": true, "id": "u:ee1ce1faee0b", "value": false } ], "id": "u:9b9fb0cf38f9", "strictMode": true, "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "deleteBtn": { "label": "删除", "level": "default" }, "tabsLabelTpl": "列${index+1}" }, { "type": "fieldset", "title": "分页参数", "collapsable": true, "body": [ { "type": "switch", "label": "是否分页", "option": "", "name": "pagination.show", "falseValue": false, "trueValue": true, "id": "u:6c70041d5143", "value": true, "className": "" }, { "type": "input-text", "label": "查询接口页数参数字段名", "name": "pagination.page", "id": "u:cbbf6853cf64", "value": "page" }, { "type": "input-text", "label": "查询接口每页数据行数参数字段名", "name": "pagination.size", "id": "u:a8fae66fa927", "value": "size" }, { "type": "input-text", "label": "接口返回总数据量字段 PATH", "name": "pagination.total", "id": "u:e1cd979c7ee8", "value": "result.total", "themeCss": { "inputControlClassName": { "padding-and-margin:default": { "marginBottom": "", "marginTop": "", "marginRight": "", "marginLeft": "" } } } } ], "id": "u:0f1bd8fc2f2b", "collapsed": true, "headingClassName": "", "bodyClassName": "p" }, { "type": "fieldset", "title": "请求方法", "collapsable": true, "body": [ { "type": "input-text", "label": "请求名称", "name": "fetchName", "id": "u:a3e712484fae", "value": "fetchTableList", "description": "追加了YAPI数据则不使用此参数", "themeCss": { "labelClassName": { "padding-and-margin:default": { "marginTop": "", "marginRight": "", "marginBottom": "", "marginLeft": "" } } }, "labelClassName": "labelClassName-a3e712484fae" }, { "type": "input-text", "label": "接口数据字段 PATH", "name": "result", "id": "u:8c082acf7db2", "value": "[\"result\"][\"records\"]", "description": "" }, { "type": "input-text", "label": "service方法名", "name": "serviceName", "id": "u:cfbbdd07366b", "value": "getTableList", "description": "" } ], "id": "u:382f8cdf59a6", "collapsed": true, "className": "", "headingClassName": "", "bodyClassName": "p-r p-l p-b" }, { "type": "fieldset", "title": "新增/编辑弹框", "collapsable": true, "body": [ { "type": "switch", "label": "是否包含弹框", "option": "", "name": "includeModifyModal", "falseValue": false, "trueValue": true, "id": "u:03957070af9e", "value": false }, { "type": "combo", "label": "表单项", "name": "modifyModal.formItems", "multiple": true, "addable": true, "removable": true, "removableMode": "icon", "strictMode": false, "addBtn": { "label": "新增", "icon": "fa fa-plus", "level": "primary", "size": "sm", "id": "u:86cc27b6a663" }, "items": [ { "type": "input-text", "name": "key", "id": "u:62cc1cf36c73", "label": "字段名(key)" }, { "type": "select", "name": "type", "options": [ { "label": "string", "value": "string" }, { "label": "number", "value": "number" }, { "label": "boolean", "value": "boolean" }, { "label": "Dayjs", "value": "Dayjs" }, { "label": "string[]", "value": "string[]" }, { "label": "number[]", "value": "number[]" }, { "label": "boolean[]", "value": "boolean[]" }, { "label": "[Dayjs,Dayjs]", "value": "[Dayjs,Dayjs]" } ], "id": "u:b165c75e5e1a", "multiple": false, "label": "字段类型", "value": "" }, { "type": "switch", "label": "字段可选", "option": "", "name": "optional", "falseValue": false, "trueValue": true, "id": "u:68fc4c85fb03", "value": false, "description": "字段名字后加?" }, { "type": "select", "name": "defaultValue", "options": [ { "label": "\"\"", "value": "\"\"" }, { "label": "false", "value": "false" }, { "label": "true", "value": "true" }, { "label": "0", "value": "0" }, { "label": "undefined", "value": "undefined" }, { "label": "[]", "value": "[]" } ], "id": "u:379ea92fb3c6", "multiple": false, "label": "默认值", "value": "" }, { "type": "select", "name": "component", "options": [ { "label": "input", "value": "input" }, { "label": "input-password", "value": "input-password" }, { "label": "input-number", "value": "input-number" }, { "label": "textarea", "value": "textarea" }, { "label": "select", "value": "select" }, { "label": "radio-group", "value": "radio-group" }, { "label": "checkbox-group", "value": "checkbox-group" }, { "label": "switch", "value": "switch" }, { "label": "date-picker", "value": "date-picker" }, { "label": "time-ticker", "value": "time-picker" }, { "label": "range-picker", "value": "range-picker" }, { "label": "transfer", "value": "transfer" } ], "id": "u:7932ea3b05da", "multiple": false, "label": "组件", "value": "" }, { "type": "input-text", "name": "label", "id": "u:5bb237f20098", "label": "label" }, { "type": "input-text", "name": "placeholder", "id": "u:580898257491", "label": "placeholder" }, { "type": "switch", "label": "required", "option": "", "name": "required", "falseValue": false, "trueValue": true, "id": "u:559dbdbb01da", "value": false, "description": "验证规则加required" }, { "type": "input-text", "name": "message", "id": "u:55013279d659", "label": "校验失败 message", "value": "不能为空" }, { "type": "switch", "label": "更多组件配置", "option": "", "name": "showMore", "falseValue": false, "trueValue": true, "id": "u:67e0cb5b7496", "value": false, "description": "" }, { "type": "switch", "label": "labelInValue", "option": "", "name": "labelInValue", "falseValue": false, "trueValue": true, "id": "u:7fd6f1b233d9", "value": false, "description": "是否把每个选项的 label 包装到 value 中", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "select", "name": "mode", "options": [ { "label": "multiple", "value": "multiple" }, { "label": "tags", "value": "tags" } ], "multiple": false, "label": "mode", "value": "", "description": "设置 Select 的模式为多选或标签", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "optionFilterProp", "label": "optionFilterProp", "description": "搜索时过滤对应的 option 属性", "value": "label", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "showSearch", "option": "", "name": "showSearch", "falseValue": false, "trueValue": true, "value": false, "description": "使单选模式可搜索", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "switch", "label": "hideArrow", "option": "", "name": "hideArrow", "falseValue": false, "trueValue": true, "value": false, "description": "是否隐藏下拉小箭头", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'select'}" }, { "type": "input-text", "name": "maxlength", "label": "maxlength", "description": "最大长度", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "switch", "label": "showCount", "option": "", "name": "showCount", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示字数", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'input' && modifyModal.formItems[index].component !== 'input-password' && modifyModal.formItems[index].component !== 'textarea')}" }, { "type": "input-text", "name": "max", "label": "max", "description": "最大值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "min", "label": "min", "description": "最小值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "step", "label": "step", "description": "每次改变步数,可以为小数", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'input-number'}" }, { "type": "input-text", "name": "checkedChildren", "label": "checkedChildren", "description": "选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedChildren", "label": "unCheckedChildren", "description": "非选中时的内容", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "checkedValue", "label": "checkedValue", "description": "选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "input-text", "name": "unCheckedValue", "label": "unCheckedValue", "description": "非选中时的值", "hiddenOn": "${!modifyModal.formItems[index].showMore || modifyModal.formItems[index].component !== 'switch'}" }, { "type": "select", "name": "picker", "options": [ { "label": "date", "value": "date" }, { "label": "week", "value": "week" }, { "label": "month", "value": "month" }, { "label": "quarter", "value": "quarter" }, { "label": "year", "value": "year" } ], "multiple": false, "label": "picker", "description": "设置选择器类型", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker')}" }, { "type": "switch", "label": "showTime", "name": "showTime", "falseValue": false, "trueValue": true, "value": false, "description": "增加时间选择功能", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showNow", "name": "showNow", "falseValue": false, "trueValue": true, "value": false, "description": "当设定了 showTime 的时候,面板是否显示“此刻”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" }, { "type": "switch", "label": "showToday", "name": "showToday", "falseValue": false, "trueValue": true, "value": false, "description": "是否展示“今天”按钮", "hiddenOn": "${!modifyModal.formItems[index].showMore || (modifyModal.formItems[index].component !== 'date-picker' && modifyModal.formItems[index].component !== 'range-picker' || modifyModal.formItems[index].picker !== 'date')}" } ], "syncFields": [], "tabsMode": true, "draggable": true, "draggableTip": "可拖动排序", "tabsStyle": "line", "tabsLabelTpl": "表单项${index+1}", "multiLine": true, "noBorder": false, "hiddenOn": "${!includeModifyModal}" } ], "bodyClassName": "p", "collapsed": true } ], "submitText": "" } ], "pullRefresh": { "disabled": true }, "regions": [ "body" ], "style": { "boxShadow": " 0px 0px 0px 0px transparent" }, "asideResizor": false } ================================================ FILE: uTools/动态表单 demo/config/schema.ts ================================================ export type PageConfig = { filters: { component: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ label: string; /** * @description 保持原始内容,不要翻译 * @type {string} */ placeholder: string; }[]; columns: { slot: boolean; /** * @description 保持原始内容,不要翻译 * @type {string} */ title: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ dataIndex: string; /** * @description 翻译成英文,驼峰格式 * @type {string} */ key: string; }[]; pagination: { show: boolean; page: string; size: string; total: string; }; includeModifyModal: boolean; fetchName: string; result: string; serviceName: string; }; ================================================ FILE: uTools/动态表单 demo/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/动态表单 demo/script/src/controller.ts ================================================ import path from 'path'; import * as fs from 'fs-extra'; import * as execa from 'execa'; import * as ejs from 'ejs'; import axios from 'axios'; import { clipboard } from 'electron'; import { generalBasic } from '@share/BaiduOCR/index'; import { renderEjsTemplates } from '@share/utils/ejs'; import { typescriptToMock } from '@share/utils/platformIndependent/json'; import { getShareData } from '@share/utils/shareData'; import { MethodHandle } from '@share/uTools/webviewBaseController'; import { getBlockJsonValidSchema } from '@share/utils/uTools'; export const initFiltersFromImage: MethodHandle = async (data) => { const availableFormats = clipboard.availableFormats('clipboard'); if (!availableFormats.some((s) => s.includes('image'))) { throw new Error('剪贴板里没有截图'); } const base64 = clipboard.readImage('clipboard').toDataURL(); const ocrRes = await generalBasic({ image: base64 }); return { updateModelImmediately: false, model: data.model, onlyUpdateParams: true, params: ocrRes.words_result.map((s) => s.words).join('\r\n'), }; }; export const initFiltersFromText: MethodHandle = async (data) => { const filters = data.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); const formatedFilters = filters.map((item) => { const s = item.replace(/:|:/g, ':').split(':'); return { component: (s[1] || '').indexOf('选择') > -1 ? 'select' : 'input', key: s[0].trim(), label: s[0].trim(), placeholder: s[1] || '', }; }); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...data.model, filters: formatedFilters }, }; }; export const initColumnsFromText: MethodHandle = async (data) => { const columns = data.params .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .split('\n'); const formatedColumns = columns.map((s) => ({ slot: false, title: s, dataIndex: s, key: s, })); return { updateModelImmediately: false, onlyUpdateParams: false, params: '', model: { ...data.model, columns: formatedColumns }, }; }; export const openChatGPT: MethodHandle = async (data) => { const schema = getBlockJsonValidSchema(data.scriptFile); const clipboardText = JSON.stringify(data.model); const typeName = 'PageConfig'; const requestPrompt = `You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions:\n` + `\`\`\`\n${schema}\`\`\`\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n` + `The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined:\n`; return { updateModelImmediately: true, onlyUpdateParams: false, params: '', showChat: true, chatContent: requestPrompt, model: data.model, }; }; export const generateCode: MethodHandle = async (data) => { const { selectedFolder, activeWindow } = getShareData(); const tempWorkPath = path.join(activeWindow || '', '.lowcode'); const blockPath = path.join( data.scriptFile .replace('/script/src/mainBundle', '') .replace('/script/src/main', ''), ); fs.copySync(blockPath, tempWorkPath); try { await renderEjsTemplates( { ...data.model, createBlockPath: path.join(selectedFolder || '').replace(/\\/g, '/'), }, path.join(tempWorkPath, 'src'), ); // #region 更新 mock 服务 const mockType = fs .readFileSync(path.join(tempWorkPath, 'src', 'temp.mock.type').toString()) .toString(); fs.removeSync(path.join(tempWorkPath, 'src', 'temp.mock.type')); const { mockCode, mockData } = typescriptToMock(mockType); const mockTemplate = fs .readFileSync( path.join(tempWorkPath, 'src', 'temp.mock.script').toString(), ) .toString(); fs.removeSync(path.join(tempWorkPath, 'src', 'temp.mock.script')); const mockScript = ejs.render(mockTemplate, { ...data.model, mockCode, mockData, createBlockPath: selectedFolder?.replace(':', ''), }); const mockProjectPathRes = await axios .get('http://localhost:3000/mockProjectPath', { timeout: 1000 }) .catch(() => { // window.showInformationMessage( // '获取 mock 项目路径失败,跳过更新 mock 服务', // ); }); if (mockProjectPathRes?.data.result) { const projectName = activeWindow?.replace(/\\/g, '/').split('/').pop(); const mockRouteFile = path.join( mockProjectPathRes.data.result, `${projectName}.js`, ); let mockFileContent = ` import KoaRouter from 'koa-router'; import proxy from '../middleware/Proxy'; import { delay } from '../lib/util'; const Mock = require('mockjs'); const { Random } = Mock; const router = new KoaRouter(); router{{mockScript}} module.exports = router; `; if (fs.existsSync(mockRouteFile)) { mockFileContent = fs.readFileSync(mockRouteFile).toString().toString(); const index = mockFileContent.lastIndexOf(')') + 1; mockFileContent = `${mockFileContent.substring( 0, index, )}{{mockScript}}\n${mockFileContent.substring(index)}`; } mockFileContent = mockFileContent.replace(/{{mockScript}}/g, mockScript); fs.writeFileSync(mockRouteFile, mockFileContent); try { execa.sync('node', [ path.join( mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '/node_modules/eslint/bin/eslint.js', ), mockRouteFile, '--resolve-plugins-relative-to', mockProjectPathRes.data.result .replace(/\\/g, '/') .replace('/src/routes', ''), '--fix', ]); } catch (err) { console.log(err); } // #endregion } } catch (ex) { fs.removeSync(tempWorkPath); throw ex; } fs.copySync(path.join(tempWorkPath, 'src'), path.join(selectedFolder || '')); fs.removeSync(tempWorkPath); return { updateModelImmediately: false, model: data.model, onlyUpdateParams: true, params: `代码生成目录:${selectedFolder}`, }; }; ================================================ FILE: uTools/动态表单 demo/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { AskChatGPTForDynamicFormPageWebviewData, MethodHandle, baseAskChatGPTForDynamicFormPage, } from '@share/uTools/webviewBaseController'; import * as controller from './controller'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/dynamicForm' }), }); }; // #region 给 webview 调用的 export { getDynamicForm } from '@share/uTools/webviewBaseController'; export const askChatGPTForDynamicFormPage = ( data: AskChatGPTForDynamicFormPageWebviewData, ) => { return baseAskChatGPTForDynamicFormPage({ ...data, validateJsonSchemaTypeName: 'PageConfig', }); }; export const runDynamicFormScript: MethodHandle = (data) => { if (controller[data.method]) { return controller[data.method](data); } return Promise.reject(`方法不存在:${data.method}`); }; // #endregion ================================================ FILE: uTools/动态表单 demo/src/api.ts.ejs ================================================ import { request } from "@/utils/request"; <% if (locals.api) { %> // #region <%= api.title %> <%= type %> <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { _%> export interface I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params { <% api.req_query.filter(query => query.name !== pagination.page && query.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.req_params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% api.query_path.params.filter(s => s.name !== pagination.page && s.name !== pagination.size).map(query => { _%> <%= query.name %>?: string; <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } <% } %> <% if (requestBodyType && api.req_body_other.indexOf('{}') < 0) { %> <%= requestBodyType %> <% } %> /** * <%= api.title %> * /project/<%= api.project_id %>/interface/api/<%= api._id %> * @author <%= api.username %> * <% if (api.req_query.length > 0 || api.req_params.length > 0 || api.query_path.params.length > 0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params} params<%- "\n" %><% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) { -%>* @param {I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data} data<%- "\n" %><% } _%> * @returns */ export function <%= funcName %> ( <% if (api.req_query.length>0 || api.req_params.length > 0 || api.query_path.params.length > 0) { %> params: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Params, <% } _%> <% if (requestBodyType) { %> data: I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Data <% } %> ) { return request<<%= typeName %>>({ url: `http://127.0.0.1:3000<%= api.query_path.path.replace(/\{/g,"${params.") %>`, method: '<%= api.method %>', <% if(api.req_query.length>0 || api.req_params.length > 0) { %>params,<% } _%> <% if (requestBodyType && api.req_body_other.indexOf('{}')<0) {%>data,<% } %> }) } // #endregion <% } else { %> // #region export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } export interface I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params { <% filters.map(item => { _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>?: string; <% } else { _%> <%= item.key %>Start?: string; <%= item.key %>End?: string; <% } _%> <% }) _%> <% if (pagination.show) { _%> <%= pagination.page %>: number; <%= pagination.size %>: number; <% } _%> } export function <%= fetchName %>( params: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Params ) { return requestResult>({ url: `http://127.0.0.1:3000<%= createBlockPath %>/<%= fetchName %>`, method: "GET", params, }); } // #endregion <% } %> ================================================ FILE: uTools/动态表单 demo/src/index.vue.ejs ================================================ ================================================ FILE: uTools/动态表单 demo/src/model.ts.ejs ================================================ import { reactive, ref } from "vue"; <% if(locals.api){ %> import { I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Result } from "./api"; <% } else { %> import { I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result } from "./api"; <% } %> <% if(!locals.api){ %> interface ITableListItem { <% columns.map((item, index) => { _%> /** <%= item.title %> */ <%= item.key || `column${index+1}` %>: string; <% }) _%> /** * 接口返回的数据,新增字段不需要改 ITableListItem 直接从这里取 */ apiResult: I<%= fetchName.slice(0, 1).toUpperCase() + fetchName.slice(1) %>Result<%- result %>[0] } <% } %> interface IFormData { <% filters.map(item => { _%> /** <%= item.label %> */ <% if(item.component === "range-picker") { _%> <%= item.key %>?: [string,string]; <% } _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>?: string; <% } _%> <% }) _%> } <% if(filters.some(s => s.component === "select" )){ %> interface IOptionItem { label: string; value: string; } interface IOptions { <% filters.filter(s => s.component === "select").map(item => { _%> <%= item.key %>: IOptionItem[], <% }) _%> } const defaultOptions: IOptions = { <% filters.filter(s => s.component === "select").map(item => { _%> <%= item.key %>: [], <% }) _%> }; <% } %> export const defaultFormData: IFormData = { <% filters.map(item => { _%> <%= item.key %>: undefined, <% }) _%> }; export const useModel = () => { const filterForm = reactive({ ...defaultFormData }); <% if(filters.some(s => s.component === "select" )){ %> const options = reactive({ ...defaultOptions }); <% } %> <% if(locals.api){ _%> const tableList = ref<(I<%= funcName.slice(0, 1).toUpperCase() + funcName.slice(1) %>Result<%- result %>[0] & { _?: unknown })[]>( [], ); <% } else { _%> const tableList = ref<(ITableListItem & { _?: unknown })[]>( [], ); <% } _%> <% if(pagination.show) { %> const pagination = reactive<{ page: number; pageSize: number; total: number; }>({ page: 1, pageSize: 10, total: 0, }); <% } %> const loading = reactive<{ list: boolean }>({ list: false, }); <% if(includeModifyModal) { %> const modalInfo = reactive<{ visible: boolean; title: string; id?: number; action: "add" | "edit" | "view"; }>({ visible: false, title: "", action: "add", }); <% } %> return { filterForm, <% if(filters.some(s => s.component === "select" )){ _%> options, <% } _%> tableList, <% if(pagination.show) { _%> pagination, <% } _%> loading, <% if(includeModifyModal) { _%> modalInfo, <% } _%> }; }; export type Model = ReturnType; ================================================ FILE: uTools/动态表单 demo/src/presenter.ts.ejs ================================================ import Service from "./service"; import { defaultFormData, useModel } from "./model"; import { createVNode, onMounted } from "vue"; import { message, Modal } from "ant-design-vue"; import { ExclamationCircleOutlined } from "@ant-design/icons-vue"; <% if(filters.some(item => item.component === "select" && item.remoteFetch)) { _%> import { useDebounceFn } from "@vueuse/core"; <% } _%> export const usePresenter = () => { const model = useModel(); const service = new Service(model); onMounted(() => { service.<%= serviceName %>(); }); const handleClear = () => { Object.assign(model.filterForm, defaultFormData) <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; const handleSearch = () => { <% if(pagination.show) { _%> model.pagination.page = 1; <% } _%> service.<%= serviceName %>(); }; <% if(pagination.show) { _%> const handlePageChange = (page: number, pageSize: number) => { if (pageSize !== model.pagination.pageSize) { model.pagination.pageSize = pageSize; model.pagination.page = 1; } else { model.pagination.page = page; } service.<%= serviceName %>(); }; <% } _%> <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> const handleSearch<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %> = useDebounceFn((value: string) => { if (!value) { return; } service.search<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>(value); }, 400); <% }) _%> const handleDel = (record: typeof model.tableList.value[0]) => { Modal.confirm({ title: "此操作将删除该选项,是否继续?", icon: createVNode(ExclamationCircleOutlined), okText: "确定", cancelText: "取消", onOk() { message.success("删除成功"); }, }); }; const handleCreate = () => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "新建"; model.modalInfo.action = "add"; model.modalInfo.id = undefined; <% } _%> }; const handleEdit = (record: typeof model.tableList.value[0]) => { <% if(includeModifyModal) { _%> model.modalInfo.visible = true; model.modalInfo.title = "编辑"; model.modalInfo.action = "edit"; model.modalInfo.id = record.id; <% } _%> }; <% if(includeModifyModal) { _%> const handleView = (record: typeof model.tableList.value[0]) => { model.modalInfo.visible = true; model.modalInfo.title = "查看"; model.modalInfo.action = "view"; model.modalInfo.id = record.id; }; const handleModalOk = () => { model.modalInfo.visible = false; service.<%= serviceName %>(); }; const handleModalCancel = () => { model.modalInfo.visible = false; }; <% } _%> return { model, service, handleClear, handleSearch, <% if(pagination.show) { _%> handlePageChange, <% } _%> <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> handleSearch<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>, <% }) _%> handleDel, handleCreate, handleEdit, <% if(includeModifyModal) { _%> handleView, handleModalOk, handleModalCancel <% } _%> }; }; ================================================ FILE: uTools/动态表单 demo/src/service.ts.ejs ================================================ import { <%= locals.api ? funcName : fetchName %> } from "./api"; import { Model } from "./model"; export default class Service { private model: Model; constructor(model: Model) { this.model = model; } async <%= serviceName %>() { this.model.loading.list = true; <% filters.map(item => { _%> <% if(item.component === "range-picker") { _%> let <%= item.key %>Start: string | undefined = undefined; let <%= item.key %>End: string | undefined = undefined; if ( this.model.filterForm.<%= item.key %> && this.model.filterForm.<%= item.key %>[0] ) { <%= item.key %>Start = `${this.model.filterForm.<%= item.key %>[0]} 00:00:00`; } if ( this.model.filterForm.<%= item.key %> && this.model.filterForm.<%= item.key %>[1] ) { <%= item.key %>End = `${this.model.filterForm.<%= item.key %>[1]} 23:59:59`; } <% } _%> <% }) _%> const res = await <%= locals.api ? funcName : fetchName %>({ <% filters.map(item => { _%> <% if(item.component !== "range-picker") { _%> <%= item.key %>: this.model.filterForm.<%= item.key %>, <% } else { _%> <%= item.key %>Start: <%= item.key %>Start, <%= item.key %>End: <%= item.key %>End, <% } _%> <% }) _%> <% if(pagination.show) { _%> <%= pagination.page %>: this.model.pagination.page, <%= pagination.size %>: this.model.pagination.pageSize, <% } _%> }).finally(() => { this.model.loading.list = false; }); this.model.tableList.value = res<%- result %>.map((s) => { return { ...s, <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: s.<%= item.key || `column${index+1}` %>, <% }) _%> apiResult: s }; }); <% if(pagination.show) { _%> this.model.pagination.total = res.<%- pagination.total %>; <% } _%> } <% filters.filter(item => item.component === "select" && item.remoteFetch).map(item => { _%> async search<%= item.key.slice(0, 1).toUpperCase() + item.key.slice(1) %>(value: string) { const res = await Promise.resolve([{ label: "1", value: "1" }]); this.model.options.<%= item.key %> = res; } <% }) _%> } ================================================ FILE: uTools/动态表单 demo/src/temp.mock.script ================================================ .get(`<%= createBlockPath %>/<%= fetchName %>`, async (ctx, next) => { <%- mockCode %> ctx.body = <%- mockData %> }) ================================================ FILE: uTools/动态表单 demo/src/temp.mock.type.ejs ================================================ { code: number; msg: string; <% if (!pagination.show) { _%> result: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; <% } else { _%> result: { records: { <% columns.map((item, index) => { _%> <%= item.key || `column${index+1}` %>: string; <% }) _%> }[]; total: number; } <% } _%> } ================================================ FILE: uTools/截屏并转base64/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/截屏并转base64/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { screenCapture } from '@share/utils/uTools'; export const bootstrap = async () => { const base64 = await screenCapture(); clipboard.writeText(base64); utools.outPlugin(); utools.hideMainWindow(); }; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT commit - 截图/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT commit - 截图/script/src/main.ts ================================================ import { ocr } from '@share/utils/uTools'; import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { const availableFormats = clipboard.availableFormats('clipboard'); if (!availableFormats.some((s) => s.includes('image'))) { return '剪贴板里没有截图'; } const base64 = clipboard.readImage('clipboard').toDataURL(); const ocrRes = await ocr({ base64, model: 'ocr_system' }); if (ocrRes.result?.texts && ocrRes.result.texts.length > 1) { const task = ocrRes.result.texts[0].trim(); const title = ocrRes.result.texts[1].trimStart(); utools.outPlugin(); utools.hideMainWindowPasteText(`feat(T${task}): #${task}/${title}`); } else { const result = ocrRes.result?.texts?.[0]?.trimStart() || ''; const match = result.match(/^(\d+)(.*)/); if (match) { const task = match[1]; // 开头的数字部分 const title = match[2]; // 其余的内容 utools.outPlugin(); utools.hideMainWindowPasteText(`feat(T${task}): #${task}/${title}`); } else { return result; } } }; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT 分支名 - 截图/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT 分支名 - 截图/script/src/main.ts ================================================ /* eslint-disable prefer-destructuring */ import { LLMMessage } from '@share/uTools/webviewBaseController'; import { askChatGPT as askOpenai, ocr } from '@share/utils/uTools'; import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { const availableFormats = clipboard.availableFormats('clipboard'); if (!availableFormats.some((s) => s.includes('image'))) { return '剪贴板里没有截图'; } const base64 = clipboard.readImage('clipboard').toDataURL(); const ocrRes = await ocr({ base64, model: 'ocr_system' }); let task = ''; let title = ''; if (ocrRes.result?.texts && ocrRes.result.texts.length > 1) { task = ocrRes.result.texts[0].trim(); title = ocrRes.result.texts[1].trimStart(); } else { const result = ocrRes.result?.texts?.[0]?.trimStart() || ''; const match = result.match(/^(\d+)(.*)/); if (match) { task = match[1]; // 开头的数字部分 title = match[2]; // 其余的内容 } } if (title) { const requestPrompt = ` 下面是一个需求的简要索说明,需要根据这个说明创建 git 分支,请给出英文分支名,不要直接翻译,要自然、流畅和地道,不要加 feature、feat 等分支特性,用户自己处理 \`\`\` ${title} \`\`\` `; // const content = await askOpenai({ // messages: [{ role: 'user', content: requestPrompt }], // handleChunk: () => {}, // }); // return `feat/T${task}_${content.content}`; utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: requestPrompt, form: { task, }, }), }); return; } return JSON.stringify(ocrRes); }; // 给页面调用的 export const askChatGPT = async (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; model: { task: string; }; }) => { const res = await askOpenai({ ...data, model: undefined }); data.handleChunk(` 完整分支名: \`\`\` feat/T${data.model.task}_${res.content.replace(/```/g, '').replace(/`/g, '')} \`\`\` `); return res; }; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/config/schema.ts ================================================ export type TaskInfo = { // 需求 id,用户发送内容的开头数字部分 taskId: string; // 需求标题,用户发送内容去掉开头数字部分,请翻译成英文,不要直接翻译,要自然、流畅和地道,这部分内容会作为 git 分支名,请用中划线代替翻译后的空格 taskTitle: string; }; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/根据 DevOps 需求标题创建 GIT 分支名 - 截图 - TypeCheck/script/src/main.ts ================================================ /* eslint-disable prefer-destructuring */ import { validate } from '@share/TypeChatSlim/utools'; import { LLMMessage } from '@share/uTools/webviewBaseController'; import { askChatGPT as askOpenai, getBlockJsonValidSchema, ocr, } from '@share/utils/uTools'; import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { const availableFormats = clipboard.availableFormats('clipboard'); if (!availableFormats.some((s) => s.includes('image'))) { return '剪贴板里没有截图'; } const base64 = clipboard.readImage('clipboard').toDataURL(); const ocrRes = await ocr({ base64, model: 'ocr_system' }); const schema = getBlockJsonValidSchema(scriptFile!); const text = ocrRes.result?.texts?.join(' ') || ''; const typeName = 'TaskInfo'; const requestPrompt = ` You are a service that translates user requests into JSON objects of type "${typeName}" according to the following TypeScript definitions: \`\`\`ts ${schema} \`\`\` The following is a user request: \`\`\` ${text} \`\`\` The following is the user request translated into a JSON object with 2 spaces of indentation and no properties with the value undefined: `; utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: requestPrompt, }), }); }; // 给页面调用的 export const askChatGPT = async (data: { messages: LLMMessage; handleChunk: (chunck: string) => void; scriptFile: string; }) => { const schema = getBlockJsonValidSchema(data.scriptFile); const typeName = 'TaskInfo'; const content = await askOpenai({ messages: data.messages, handleChunk: data.handleChunk, }); const valid = validate<{ taskId: string; taskTitle: string }>( content.content, schema, typeName, ); if (valid.success) { data.handleChunk(` 完整分支名: \`\`\` feat/T${valid.data.taskId}_${valid.data.taskTitle .replace(/```/g, '') .replace(/`/g, '')} \`\`\` `); } else { data.handleChunk(` > JSON 校验不通过 ${valid.message} `); } return content; }; ================================================ FILE: uTools/根据当前分支名称创建 GIT commit/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/根据当前分支名称创建 GIT commit/script/src/main.ts ================================================ import { clipboard } from 'electron'; import * as execa from 'execa'; import { getShareData } from '@share/utils/shareData'; export const bootstrap = (scriptFile?: string) => { const { activeWindow } = getShareData(); try { const projectPath = utools.isWindows() && activeWindow?.startsWith('/') ? activeWindow.substring(1) : activeWindow; const res = execa.sync('git', [ '-C', projectPath || '.', 'rev-parse', '--abbrev-ref', 'HEAD', ]); if (res.stdout) { const match = res.stdout.match(/T(\d+)_/); if (match && match[1]) { utools.outPlugin(); utools.hideMainWindowPasteText(`feat(T${match[1]}): #${match[1]}/`); return res.stdout; } utools.outPlugin(); utools.hideMainWindowPasteText(res.stdout); return; } return JSON.stringify(res); } catch (ex) { return (ex as object).toString(); } }; ================================================ FILE: uTools/翻译为驼峰格式-首字母大写/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/翻译为驼峰格式-首字母大写/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { createChatCompletion } from '@share/LLM/openaiV2'; export const bootstrap = async () => { const text = clipboard.readText(); if (!text) { utools.showNotification('请先复制内容'); return; } let res = await createChatCompletion({ messages: [ { role: 'system', content: `你是一个翻译家,你的目标是把中文翻译成英文单词,请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面用户输入的内容`, }, { role: 'user', content: text, }, ], }); res = res.charAt(0).toUpperCase() + res.slice(1); utools.outPlugin(); utools.hideMainWindowPasteText(res); }; ================================================ FILE: uTools/翻译为驼峰格式-首字母小写/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/翻译为驼峰格式-首字母小写/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { createChatCompletion } from '@share/LLM/openaiV2'; export const bootstrap = async () => { const text = clipboard.readText(); if (!text) { utools.showNotification('请先复制内容'); return; } let res = await createChatCompletion({ messages: [ { role: 'system', content: `你是一个翻译家,你的目标是把中文翻译成英文单词,请翻译时使用驼峰格式,小写字母开头,不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面用户输入的内容`, }, { role: 'user', content: text, }, ], }); res = res.charAt(0).toLowerCase() + res.slice(1); utools.outPlugin(); utools.hideMainWindowPasteText(res); }; ================================================ FILE: uTools/英文翻译中文/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/英文翻译中文/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { createChatCompletion } from '@share/LLM/openaiV2'; export const bootstrap = async () => { const text = clipboard.readText(); if (!text) { utools.showNotification('请先复制内容'); return; } const res = await createChatCompletion({ messages: [ { role: 'system', content: `你是一个翻译家,你的目标是把英文翻译成中文,请翻译时不要带翻译腔,而是要翻译得自然、流畅和地道,使用优美和高雅的表达方式。请翻译下面用户输入的内容`, }, { role: 'user', content: text, }, ], }); return res; }; ================================================ FILE: uTools/英文:中文格式的描述转 TS 类型/README.md ================================================ customerName:甲方姓名 customerMobile:甲方手机号 receiverMobile:报价单手机号 idCard:证件号码 idCardType:证件类型 houseAddress:房屋地址 orderCode:报价单号 designArea:设计面积(可修改) productType:产品类型 totalAmount:总报价款 designPrice:设计单价(可修改) ================================================ FILE: uTools/英文:中文格式的描述转 TS 类型/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/英文:中文格式的描述转 TS 类型/script/src/main.ts ================================================ import { clipboard } from 'electron'; import { askChatGPT as askOpenai } from '@share/utils/uTools'; import { AskChatGPT } from '@share/uTools/webviewBaseController'; export const bootstrap = async (scriptFile?: string) => { const clipboardText = (clipboard.readText() || '').trim() || '客户验收状态:1.无需验收、2.待验收、3已验收'; const requestPrompt = `You are a service that translates user requests into TypeScript Interface, use value as a comment in JSDoc format:\n` + `The following is a user request:\n` + `"""\n${clipboardText}\n"""\n`; utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/chat', content: requestPrompt, }), }); }; // 给页面调用的 export const askChatGPT: AskChatGPT = (data) => askOpenai({ ...data, model: undefined }); ================================================ FILE: uTools/获取命令行命令/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/获取命令行命令/script/src/main.ts ================================================ import { createChatCompletion } from '@share/LLM/openaiV2'; import { clipboard } from 'electron'; export const bootstrap = async (scriptFile?: string) => { const text = clipboard.readText(); let platform = utools.isWindows() ? 'windows' : 'mac'; if (utools.isLinux()) { platform = 'linux'; } if (!text) { utools.showNotification('请先复制内容'); return; } let res = await createChatCompletion({ messages: [ // { // role: 'system', // content: `你精通各种 OS 平台命令行,你的目标是根据用户的要求,帮助用户获取命令行命令,直接给出可执行的命令,用户复制后可直接执行。下面用户输入的内容:`, // }, { role: 'user', content: `${platform} 平台下, ${text},返回可执行的命令即可,不要带多余的信息`, }, ], }); res = res.replace(/`/g, ''); utools.outPlugin(); utools.hideMainWindowPasteText(res); }; ================================================ FILE: uTools/设置配置信息/script/index.ts ================================================ import { bootstrap } from './src/main'; const res = bootstrap(); // @ts-ignore return res; ================================================ FILE: uTools/设置配置信息/script/src/main.ts ================================================ /* eslint-disable no-template-curly-in-string */ import { getShareData, saveShareData as save } from '@share/utils/shareData'; export const bootstrap = async (scriptFile?: string) => { utools.redirect(['lowcode', 'lowcode'], { type: 'text', data: JSON.stringify({ scriptFile, route: '/config' }), }); }; const schema = { type: 'page', name: 'page', body: [ { type: 'form', name: 'form', data: {}, title: '', body: [ // { // type: 'input-text', // id: 'u:11b127c5df46', // label: 'activeWindow', // name: 'activeWindow', // description: '', // }, { type: 'combo', label: 'oneAPI', name: 'oneAPI', multiple: true, addable: true, removable: true, removableMode: 'icon', addBtn: { label: '新增', icon: 'fa fa-plus', level: 'primary', size: 'sm', id: 'u:47ecb9e15ff1', }, items: [ { type: 'input-text', label: 'hostname', name: 'hostname', id: 'u:6496cac4f4b8', description: '', }, { type: 'input-text', label: 'apiPath', name: 'apiPath', id: 'u:a701e97d81a6', description: '', }, { type: 'switch', label: 'notHttps', option: '', name: 'notHttps', falseValue: false, trueValue: true, id: 'u:a3283c2ac1b6', value: false, }, { type: 'input-number', label: 'port', name: 'port', keyboard: true, id: 'u:3d3695bd7ab2', step: 1, }, { type: 'input-text', name: 'apiKey', placeholder: '', id: 'u:25b0c7b5e5a0', label: 'apiKey', }, { type: 'input-text', label: 'model', name: 'model', id: 'u:ac0737858444', description: '', }, { type: 'input-number', label: 'maxTokens', name: 'maxTokens', keyboard: true, id: 'u:a8cfb52c5e43', step: 1, }, { type: 'input-number', label: 'temperature', name: 'temperature', keyboard: true, id: 'u:8797d33941aa', step: 1, }, { type: 'input-text', label: 'proxyUrl', name: 'proxyUrl', description: '', }, { type: 'switch', label: 'use', option: '', name: 'use', falseValue: false, trueValue: true, id: 'u:1ac806d7ad60', value: false, }, ], id: 'u:186f183e9320', strictMode: false, syncFields: [], tabsMode: false, draggable: true, draggableTip: '可拖动排序', tabsStyle: 'line', tabsLabelTpl: '表单项${index+1}', multiLine: true, value: [{}], }, ], submitText: '', }, ], pullRefresh: { disabled: true, }, regions: ['body'], style: { boxShadow: ' 0px 0px 0px 0px transparent', }, asideResizor: false, }; export const getDynamicFormSchema = () => { schema.body[0].data = getShareData(); return schema; }; export const saveDynamicFormSchema = (data: object) => save(data); ================================================ FILE: uTools.js ================================================ const path = require('path'); const fs = require('fs-extra'); const { build } = require('esbuild'); /** * @description * @param {string} dirPath * @return {string[]} */ function getAllFiles(dirPath) { const files = fs.readdirSync(dirPath); let result = []; // eslint-disable-next-line no-restricted-syntax for (const file of files) { const filePath = `${dirPath}/${file}`; // eslint-disable-next-line no-await-in-loop const stats = fs.statSync(filePath); if (stats.isDirectory()) { result = result.concat(getAllFiles(filePath)); } else { result.push(filePath); } } return result; } getAllFiles(path.join(__dirname, 'dist', 'uTools')) .filter((s) => s.includes('index.js')) .forEach(async (file) => { const mainFilePath = file .replace(/\\/g, '/') .replace('index.js', 'src/main'); const mainBundleFilePath = file .replace(/\\/g, '/') .replace('index.js', 'src/mainBundle'); const content = fs.readFileSync(file).toString(); const indexContent = content .replace( 'Object.defineProperty(exports, "__esModule", { value: true });', `const mainFilePath = "${mainFilePath}";`, ) .replace('// @ts-ignore', '') .replace( 'const main_1 = require("./src/main");', `const main_1 = require(mainFilePath);`, ) .replace( '(0, main_1.bootstrap)()', `(0, main_1.bootstrap)(mainFilePath)`, ); const indexBundleContent = content .replace( 'Object.defineProperty(exports, "__esModule", { value: true });', `const mainFilePath = "${mainBundleFilePath}";`, ) .replace('// @ts-ignore', '') .replace( 'const main_1 = require("./src/main");', `const main_1 = require(mainFilePath);`, ) .replace( '(0, main_1.bootstrap)()', `(0, main_1.bootstrap)(mainFilePath)`, ); fs.writeFileSync(file, indexContent); const indexBundleFile = file .replace(/\\/g, '/') .replace('index.js', 'indexBundle.js'); fs.writeFileSync(indexBundleFile, indexBundleContent); await build({ entryPoints: [`${mainFilePath}.js`], bundle: true, minify: true, // only needed if you have dependencies external: ['electron', 'typescript-json-schema'], platform: 'node', format: 'cjs', outfile: mainFilePath.replace('/main', '/mainBundle.js'), }); const conetnt = fs.readFileSync(`${mainFilePath}.js`).toString(); fs.writeFileSync( `${mainFilePath}.js`, `const moduleAlias = require('module-alias'); moduleAlias.addAlias('@share', '${path .join(__dirname, 'dist', 'share') .replace(/\\/g, '/')}'); ${conetnt}`, ); build({ entryPoints: [`${mainFilePath}.js`], bundle: false, minify: false, // only needed if you have dependencies // external: ['electron'], platform: 'node', format: 'cjs', outfile: `${mainFilePath}.js`, allowOverwrite: true, }); }); // bundle other files ['share/uTools/webviewBaseController.js'].forEach(async (file) => { const entryFile = path.join(__dirname, 'dist', file); build({ entryPoints: [entryFile], bundle: true, minify: true, // only needed if you have dependencies external: ['electron'], platform: 'node', format: 'cjs', outfile: entryFile.replace('.js', 'Bundle.js'), allowOverwrite: true, }); }); // 复制物料文件 const copyDir = () => { const dirPath = path.join(__dirname, 'uTools'); const distDirPath = path.join(__dirname, 'dist', 'uTools'); const dirNameArry = fs.readdirSync(dirPath); // eslint-disable-next-line no-restricted-syntax for (const dirName of dirNameArry) { const childDirPath = path.join(dirPath, dirName); const stats = fs.statSync(childDirPath); if (stats.isDirectory()) { const copyDirArray = fs.readdirSync(childDirPath); // eslint-disable-next-line no-restricted-syntax for (const copyDirName of copyDirArray) { if (copyDirName !== 'script') { const copyDirPath = path.join(childDirPath, copyDirName); fs.copySync( copyDirPath, path.join(distDirPath, dirName, copyDirName), ); } } } } }; copyDir(); ================================================ FILE: uToolsUpload.js ================================================ const path = require('path'); const OSS = require('ali-oss'); const tar = require('tar'); const fs = require('fs-extra'); const tempPath = path.join(__dirname, '.lowcode', 'dist'); const file = path.join(__dirname, '.lowcode', 'download.tar.gz'); fs.copySync( path.join(__dirname, 'dist', 'uTools'), path.join(tempPath, 'uTools'), ); fs.copySync( path.join(__dirname, 'dist', 'share/uTools/webviewBaseControllerBundle.js'), path.join(tempPath, 'share/uTools/webviewBaseControllerBundle.js'), ); const dirNameArray = fs.readdirSync(path.join(tempPath, 'uTools')); // eslint-disable-next-line no-restricted-syntax for (const dirName of dirNameArray) { const scriptPath = path.join(tempPath, 'uTools', dirName, 'script'); fs.removeSync(path.join(scriptPath, 'index.js')); const files = fs.readdirSync(path.join(scriptPath, 'src')); files.forEach((f) => { if (f !== 'mainBundle.js') { fs.removeSync(path.join(path.join(scriptPath, 'src'), f)); } }); } const upload = async () => { await tar.create( { gzip: true, file, cwd: path.join(__dirname, '.lowcode'), }, ['dist'], ); if (process.env.bucket) { const store = new OSS({ region: 'oss-cn-beijing', accessKeyId: process.env.accessKeyId, accessKeySecret: process.env.accessKeySecret, bucket: process.env.bucket, }); store.put('download-free.tar.gz', file).then((result) => { fs.removeSync(path.join(__dirname, '.lowcode')); console.log(result.url); }); } }; upload();