parent
							
								
									2077481d89
								
							
						
					
					
						commit
						555b67132e
					
				| @ -0,0 +1,115 @@ | ||||
| # 基于 Vite2.x + Vue3.x + TypeScript H5 低代码平台 | ||||
| 
 | ||||
| [](LICENSE) | ||||
| 
 | ||||
| **中文** | [English](./README.md) | ||||
| 
 | ||||
| ## 克隆主分支,忽略 git-pages 等无关分支 | ||||
| 
 | ||||
| ```shell | ||||
| git clone --single-branch https://github.com/buqiyuan/vite-vue3-lowcode.git | ||||
| # or | ||||
| git clone --single-branch https://gitee.com/buqiyuan/vite-vue3-lowcode.git | ||||
| ``` | ||||
| 
 | ||||
| ## 技术栈 | ||||
| 
 | ||||
| - 编程语言:[TypeScript 4.x](https://www.typescriptlang.org/zh/) + [JavaScript](https://www.javascript.com/) | ||||
| - 构建工具:[Vite 2.x](https://cn.vitejs.dev/) | ||||
| - 前端框架:[Vue 3.x](https://v3.cn.vuejs.org/) | ||||
| - 路由工具:[Vue Router 4.x](https://next.router.vuejs.org/zh/index.html) | ||||
| - 状态管理:[Vuex 4.x](https://next.vuex.vuejs.org/) | ||||
| - PC 端 UI 框架:[Element Plus](https://element-plus.org/#/zh-CN) | ||||
| - H5 端 UI 框架:[vant](https://vant-contrib.gitee.io/vant/v3/#/zh-CN/) | ||||
| - CSS 预编译:[Stylus](https://stylus-lang.com/) / [Sass](https://sass.bootcss.com/documentation) / [Less](http://lesscss.cn/) | ||||
| - HTTP 工具:[Axios](https://axios-http.com/) | ||||
| - Git Hook 工具:[husky](https://typicode.github.io/husky/#/) + [lint-staged](https://github.com/okonet/lint-staged) | ||||
| - 代码规范:[EditorConfig](http://editorconfig.org) + [Prettier](https://prettier.io/) + [ESLint](https://eslint.org/) + [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript#translation) | ||||
| - 提交规范:[Commitizen](http://commitizen.github.io/cz-cli/) + [Commitlint](https://commitlint.js.org/#/) | ||||
| - 单元测试:[vue-test-utils](https://next.vue-test-utils.vuejs.org/) + [jest](https://jestjs.io/) + [vue-jest](https://github.com/vuejs/vue-jest) + [ts-jest](https://kulshekhar.github.io/ts-jest/) | ||||
| - 自动部署:[GitHub Actions](https://docs.github.com/cn/actions/learn-github-actions) | ||||
| 
 | ||||
| ### 功能清单 | ||||
| 
 | ||||
| - [x] 动态添加页面 | ||||
| - [x] 拖拽式生成组件 | ||||
| - [ ] service worker + indexeddb 实现无服务端的前端交互 | ||||
| - [ ] 数据源管理 | ||||
| - [ ] 提供预置函数 | ||||
| - [ ] 更多组件的封装 | ||||
| - [ ] 其他... | ||||
| 
 | ||||
| ### 简易说明 | ||||
| 
 | ||||
| 目前在使用表单时,需要把相关的`表单控件`放到`表单容器`内部,并且需要将`按钮`放到`表单容器`内, | ||||
| 然后再讲`按钮的type`设置为`表单提交按钮`这时候点击提交按钮才会自动收集表单容器内部的所有字段和值 | ||||
| 
 | ||||
| ### 快速生成组件属性 | ||||
| 
 | ||||
| ```javascript | ||||
| // 在vant文档中 chrome控制台输入以下代码,快速生成组件属性 | ||||
| let propObj = { | ||||
|   string: (config) => `createEditorInputProp(${JSON.stringify(config)})`, | ||||
|   number: (config) => `createEditorInputNumberProp(${JSON.stringify(config)})`, | ||||
|   boolean: (config) => `createEditorSwitchProp(${JSON.stringify(config)})` | ||||
| } | ||||
| 
 | ||||
| $$('#props + table tr').reduce((prev, curr) => { | ||||
|   const children = curr.children | ||||
|   const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase()) | ||||
|   const value = (propObj[children[2].textContent] ?? propObj['string'])({ | ||||
|     label: `'${children[1].textContent}'` | ||||
|   }).replaceAll('"', '') | ||||
|   prev[key] = value | ||||
|   return prev | ||||
| }, {}) | ||||
| ``` | ||||
| 
 | ||||
| ## 浏览器支持 | ||||
| 
 | ||||
| 本地开发推荐使用`Chrome 80+` 浏览器 | ||||
| 
 | ||||
| 支持现代浏览器, 不支持 IE | ||||
| 
 | ||||
| | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | | ||||
| | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | ||||
| |                                                                                             not support                                                                                              |                                                                                            last 2 versions                                                                                             |                                                                                                  last 2 versions                                                                                                  |                                                                                                last 2 versions                                                                                                |                                                                                                last 2 versions                                                                                                | | ||||
| 
 | ||||
| ### 提交规范 | ||||
| 
 | ||||
| - `feat` 增加新功能 | ||||
| - `fix` 修复问题/BUG | ||||
| - `style` 代码风格相关无影响运行结果的 | ||||
| - `perf` 优化/性能提升 | ||||
| - `refactor` 重构 | ||||
| - `revert` 撤销修改 | ||||
| - `test` 测试相关 | ||||
| - `docs` 文档/注释 | ||||
| - `build` 对构建系统或者外部依赖项进行了修改 | ||||
| - `chore` 依赖更新/脚手架配置修改等 | ||||
| - `workflow` 工作流改进 | ||||
| - `ci` 持续集成 | ||||
| - `types` 类型定义文件更改 | ||||
| - `wip` 开发中 | ||||
| 
 | ||||
| ## 快速开始 | ||||
| 
 | ||||
| ### 安装依赖 | ||||
| 
 | ||||
| ```sh | ||||
| npm install | ||||
| # or | ||||
| yarn add | ||||
| ``` | ||||
| 
 | ||||
| ### 启动项目 | ||||
| 
 | ||||
| ```sh | ||||
| npm run dev | ||||
| ``` | ||||
| 
 | ||||
| ### 项目打包 | ||||
| 
 | ||||
| ```sh | ||||
| npm run build | ||||
| ``` | ||||
| @ -0,0 +1,133 @@ | ||||
| /* | ||||
|  * @Author: 卜启缘 | ||||
|  * @Date: 2021-06-10 16:23:06 | ||||
|  * @LastEditTime: 2021-06-10 16:46:36 | ||||
|  * @LastEditors: 卜启缘 | ||||
|  * @Description: 组件属性编辑器 | ||||
|  * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\AttrEditor.tsx | ||||
|  */ | ||||
| import { defineComponent } from 'vue' | ||||
| import { | ||||
|   ElColorPicker, | ||||
|   ElForm, | ||||
|   ElFormItem, | ||||
|   ElInput, | ||||
|   ElInputNumber, | ||||
|   ElOption, | ||||
|   ElSelect, | ||||
|   ElSwitch, | ||||
|   ElPopover | ||||
| } from 'element-plus' | ||||
| import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props' | ||||
| import { TablePropEditor } from './' | ||||
| import { useDotProp } from '@/visual-editor/hooks/useDotProp' | ||||
| import { useVisualData } from '@/visual-editor/hooks/useVisualData' | ||||
| 
 | ||||
| export const AttrEditor = defineComponent({ | ||||
|   setup() { | ||||
|     const { visualConfig, currentBlock } = useVisualData() | ||||
| 
 | ||||
|     const renderEditor = (propName: string, propConfig: VisualEditorProps) => { | ||||
|       const { propObj, prop } = useDotProp(currentBlock.value.props, propName) | ||||
| 
 | ||||
|       return { | ||||
|         [VisualEditorPropsType.input]: () => ( | ||||
|           <ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} /> | ||||
|         ), | ||||
|         [VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />, | ||||
|         [VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />, | ||||
|         [VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />, | ||||
|         [VisualEditorPropsType.select]: () => ( | ||||
|           <ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}> | ||||
|             {propConfig.options?.map((opt) => ( | ||||
|               <ElOption label={opt.label} value={opt.value} /> | ||||
|             ))} | ||||
|           </ElSelect> | ||||
|         ), | ||||
|         [VisualEditorPropsType.table]: () => ( | ||||
|           <TablePropEditor v-model={propObj[prop]} propConfig={propConfig} /> | ||||
|         ) | ||||
|       }[propConfig.type]() | ||||
|     } | ||||
| 
 | ||||
|     // 表单项
 | ||||
|     const FormEditor = () => { | ||||
|       const content: JSX.Element[] = [] | ||||
|       if (!currentBlock.value) { | ||||
|         content.push( | ||||
|           <> | ||||
|             <ElFormItem label="容器宽度"> | ||||
|               <ElInputNumber v-model={currentBlock.value.width} {...({ step: 100 } as any)} /> | ||||
|             </ElFormItem> | ||||
|             <ElFormItem label="容器高度"> | ||||
|               <ElInputNumber v-model={currentBlock.value.height} {...({ step: 100 } as any)} /> | ||||
|             </ElFormItem> | ||||
|           </> | ||||
|         ) | ||||
|       } else { | ||||
|         const { componentKey } = currentBlock.value | ||||
|         const component = visualConfig.componentMap[componentKey] | ||||
|         console.log('props.block:', currentBlock.value) | ||||
|         content.push( | ||||
|           <> | ||||
|             <ElFormItem label="组件ID" labelWidth={'76px'}> | ||||
|               {currentBlock.value._vid} | ||||
|               <ElPopover | ||||
|                 width={200} | ||||
|                 trigger="hover" | ||||
|                 content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`} | ||||
|               > | ||||
|                 {{ | ||||
|                   reference: () => ( | ||||
|                     <i style={{ marginLeft: '6px' }} class={'el-icon-warning-outline'}></i> | ||||
|                   ) | ||||
|                 }} | ||||
|               </ElPopover> | ||||
|             </ElFormItem> | ||||
|           </> | ||||
|         ) | ||||
|         if (!!component) { | ||||
|           if (!!component.props) { | ||||
|             content.push( | ||||
|               ...Object.entries(component.props || {}).map(([propName, propConfig]) => ( | ||||
|                 <> | ||||
|                   <ElFormItem key={currentBlock.value._vid + propName}> | ||||
|                     {{ | ||||
|                       label: () => | ||||
|                         propConfig.tips ? ( | ||||
|                           <> | ||||
|                             <ElPopover width={200} trigger={'hover'} content={propConfig.tips}> | ||||
|                               {{ | ||||
|                                 reference: () => <i class={'el-icon-warning-outline'}></i> | ||||
|                               }} | ||||
|                             </ElPopover> | ||||
|                             {propConfig.label} | ||||
|                           </> | ||||
|                         ) : ( | ||||
|                           propConfig.label | ||||
|                         ), | ||||
|                       default: () => renderEditor(propName, propConfig) | ||||
|                     }} | ||||
|                   </ElFormItem> | ||||
|                 </> | ||||
|               )) | ||||
|             ) | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       return ( | ||||
|         <> | ||||
|           <ElForm size="mini" label-position="left"> | ||||
|             {content} | ||||
|           </ElForm> | ||||
|         </> | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     return () => ( | ||||
|       <> | ||||
|         <FormEditor /> | ||||
|       </> | ||||
|     ) | ||||
|   } | ||||
| }) | ||||
| @ -1,167 +1,65 @@ | ||||
| /** | ||||
|  * @name: RightAttributePanel | ||||
|  * @author: 卜启缘 | ||||
|  * @date: 2021/4/28 16:59 | ||||
|  * @description:属性编辑器 | ||||
|  * @update: 2021/4/28 16:59 | ||||
| /* | ||||
|  * @Author: 卜启缘 | ||||
|  * @Date: 2021-06-01 13:22:14 | ||||
|  * @LastEditTime: 2021-06-10 16:33:02 | ||||
|  * @LastEditors: 卜启缘 | ||||
|  * @Description: 属性编辑器 | ||||
|  * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\index.tsx | ||||
|  * RightAttributePanel | ||||
|  */ | ||||
| 
 | ||||
| import { defineComponent, reactive } from 'vue' | ||||
| import styles from './index.module.scss' | ||||
| import './index.common.scss' | ||||
| import { | ||||
|   ElColorPicker, | ||||
|   ElForm, | ||||
|   ElFormItem, | ||||
|   ElInput, | ||||
|   ElInputNumber, | ||||
|   ElOption, | ||||
|   ElSelect, | ||||
|   ElSwitch, | ||||
|   ElTabPane, | ||||
|   ElTabs, | ||||
|   ElPopover | ||||
| } from 'element-plus' | ||||
| import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props' | ||||
| import { TablePropEditor } from './components/' | ||||
| import { ElTabPane, ElTabs } from 'element-plus' | ||||
| 
 | ||||
| import MonacoEditor from '../common/monaco-editor/MonacoEditor' | ||||
| import { useDotProp } from '@/visual-editor/hooks/useDotProp' | ||||
| import { useVisualData } from '@/visual-editor/hooks/useVisualData' | ||||
| import { AttrEditor } from './components' | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|   name: 'RightAttributePanel', | ||||
|   setup() { | ||||
|     const { visualConfig, currentBlock } = useVisualData() | ||||
|     const { currentBlock } = useVisualData() | ||||
| 
 | ||||
|     const state = reactive({ | ||||
|       activeName: 'attr', | ||||
|       isOpen: true | ||||
|     }) | ||||
| 
 | ||||
|     const renderEditor = (propName: string, propConfig: VisualEditorProps) => { | ||||
|       const { propObj, prop } = useDotProp(currentBlock.value.props, propName) | ||||
| 
 | ||||
|       return { | ||||
|         [VisualEditorPropsType.input]: () => ( | ||||
|           <ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} /> | ||||
|         ), | ||||
|         [VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />, | ||||
|         [VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />, | ||||
|         [VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />, | ||||
|         [VisualEditorPropsType.select]: () => ( | ||||
|           <ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}> | ||||
|             {(() => { | ||||
|               return propConfig.options!.map((opt) => ( | ||||
|                 <ElOption label={opt.label} value={opt.value} /> | ||||
|               )) | ||||
|             })()} | ||||
|           </ElSelect> | ||||
|         ), | ||||
|         [VisualEditorPropsType.table]: () => ( | ||||
|           <TablePropEditor v-model={propObj[prop]} propConfig={propConfig} /> | ||||
|         ) | ||||
|       }[propConfig.type]() | ||||
|     } | ||||
| 
 | ||||
|     return () => { | ||||
|       const content: JSX.Element[] = [] | ||||
| 
 | ||||
|       if (!currentBlock.value) { | ||||
|         content.push( | ||||
|           <> | ||||
|             <ElFormItem label="容器宽度"> | ||||
|               <ElInputNumber v-model={currentBlock.value.width} {...({ step: 100 } as any)} /> | ||||
|             </ElFormItem> | ||||
|             <ElFormItem label="容器高度"> | ||||
|               <ElInputNumber v-model={currentBlock.value.height} {...({ step: 100 } as any)} /> | ||||
|             </ElFormItem> | ||||
|           </> | ||||
|         ) | ||||
|       } else { | ||||
|         const { componentKey } = currentBlock.value | ||||
|         const component = visualConfig.componentMap[componentKey] | ||||
|         console.log('props.block:', currentBlock.value) | ||||
|         content.push( | ||||
|           <ElFormItem label="组件ID" labelWidth={'76px'}> | ||||
|             {currentBlock.value._vid} | ||||
|             <ElPopover | ||||
|               width={200} | ||||
|               trigger="hover" | ||||
|               content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`} | ||||
|             > | ||||
|               {{ | ||||
|                 reference: () => ( | ||||
|                   <i style={{ marginLeft: '6px' }} class={'el-icon-warning-outline'}></i> | ||||
|                 ) | ||||
|               }} | ||||
|             </ElPopover> | ||||
|           </ElFormItem> | ||||
|         ) | ||||
|         if (!!component) { | ||||
|           if (!!component.props) { | ||||
|             content.push( | ||||
|               ...Object.entries(component.props || {}).map(([propName, propConfig]) => ( | ||||
|                 <ElFormItem | ||||
|                   key={currentBlock.value._vid + propName} | ||||
|                   v-slots={{ | ||||
|                     label: () => | ||||
|                       propConfig.tips ? ( | ||||
|                         <> | ||||
|                           <ElPopover width={200} trigger={'hover'} content={propConfig.tips}> | ||||
|                             {{ | ||||
|                               reference: () => <i class={'el-icon-warning-outline'}></i> | ||||
|                             }} | ||||
|                           </ElPopover> | ||||
|                           {propConfig.label} | ||||
|                         </> | ||||
|                       ) : ( | ||||
|                         propConfig.label | ||||
|                       ) | ||||
|                   }} | ||||
|                 > | ||||
|                   {renderEditor(propName, propConfig)} | ||||
|                 </ElFormItem> | ||||
|               )) | ||||
|             ) | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       const handleSchemaChange = (val) => { | ||||
|         try { | ||||
|           const newObj = JSON.parse(val) | ||||
|           Object.assign(currentBlock.value, newObj) | ||||
|         } catch (e) { | ||||
|           console.log('JSON格式有误:', e) | ||||
|         } | ||||
|     const handleSchemaChange = (val) => { | ||||
|       try { | ||||
|         const newObj = JSON.parse(val) | ||||
|         Object.assign(currentBlock.value, newObj) | ||||
|       } catch (e) { | ||||
|         console.log('JSON格式有误:', e) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|       return ( | ||||
|         <> | ||||
|           <div class={[styles.drawer, { [styles.isOpen]: state.isOpen }]}> | ||||
|             <div class={styles.floatingActionBtn} onClick={() => (state.isOpen = !state.isOpen)}> | ||||
|               <i class={`el-icon-d-arrow-${state.isOpen ? 'right' : 'left'}`}></i> | ||||
|             </div> | ||||
|             <div class={styles.attrs}> | ||||
|               <ElTabs v-model={state.activeName}> | ||||
|                 <ElTabPane label="属性面板" name="attr"> | ||||
|                   <ElForm size="mini" label-position="left"> | ||||
|                     {content} | ||||
|                   </ElForm> | ||||
|                 </ElTabPane> | ||||
|                 <ElTabPane label="JSON" name="json" lazy> | ||||
|                   <MonacoEditor | ||||
|                     code={JSON.stringify(currentBlock.value)} | ||||
|                     layout={{ width: 300, height: 800 }} | ||||
|                     vid={state.activeName == 'json' ? currentBlock.value._vid : -1} | ||||
|                     onChange={handleSchemaChange} | ||||
|                     title="" | ||||
|                   /> | ||||
|                 </ElTabPane> | ||||
|               </ElTabs> | ||||
|             </div> | ||||
|     return () => ( | ||||
|       <> | ||||
|         <div class={[styles.drawer, { [styles.isOpen]: state.isOpen }]}> | ||||
|           <div class={styles.floatingActionBtn} onClick={() => (state.isOpen = !state.isOpen)}> | ||||
|             <i class={`el-icon-d-arrow-${state.isOpen ? 'right' : 'left'}`}></i> | ||||
|           </div> | ||||
|         </> | ||||
|       ) | ||||
|     } | ||||
|           <div class={styles.attrs}> | ||||
|             <ElTabs v-model={state.activeName}> | ||||
|               <ElTabPane label="属性面板" name="attr"> | ||||
|                 <AttrEditor /> | ||||
|               </ElTabPane> | ||||
|               <ElTabPane label="JSON" name="json" lazy> | ||||
|                 <MonacoEditor | ||||
|                   code={JSON.stringify(currentBlock.value)} | ||||
|                   layout={{ width: 300, height: 800 }} | ||||
|                   vid={state.activeName == 'json' ? currentBlock.value._vid : -1} | ||||
|                   onChange={handleSchemaChange} | ||||
|                   title="" | ||||
|                 /> | ||||
|               </ElTabPane> | ||||
|             </ElTabs> | ||||
|           </div> | ||||
|         </div> | ||||
|       </> | ||||
|     ) | ||||
|   } | ||||
| }) | ||||
|  | ||||
					Loading…
					
					
				
		Reference in new issue