index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. <template>
  2. <div class="container">
  3. <div class="left-board">
  4. <div class="logo-wrapper">
  5. <div class="logo">
  6. <img :src="logo" alt="logo" />
  7. <!-- Form Generator-->
  8. <!-- <a-->
  9. <!-- class="github"-->
  10. <!-- href="https://github.com/JakHuang/form-generator"-->
  11. <!-- target="_blank"-->
  12. <!-- >-->
  13. <!-- <img src="https://github.githubassets.com/pinned-octocat.svg" alt />-->
  14. <!-- </a>-->
  15. </div>
  16. </div>
  17. <el-scrollbar class="left-scrollbar">
  18. <div class="components-list">
  19. <div v-for="(item, listIndex) in leftComponents" :key="listIndex">
  20. <div class="components-title">
  21. <svg-icon icon-class="component" />
  22. {{ item.title }}
  23. </div>
  24. <draggable
  25. class="components-draggable"
  26. :list="item.list"
  27. :group="{ name: 'componentsGroup', pull: 'clone', put: false }"
  28. :clone="cloneComponent"
  29. draggable=".components-item"
  30. :sort="false"
  31. @end="onEnd"
  32. >
  33. <div
  34. v-for="(element, index) in item.list"
  35. :key="index"
  36. class="components-item"
  37. @click="addComponent(element)"
  38. >
  39. <div class="components-body">
  40. <svg-icon :icon-class="element?.__config__?.tagIcon" />
  41. {{ element?.__config__?.label }}
  42. </div>
  43. </div>
  44. </draggable>
  45. </div>
  46. </div>
  47. </el-scrollbar>
  48. </div>
  49. <div class="center-board">
  50. <div class="action-bar">
  51. <slot name="action"> </slot>
  52. <!-- <el-button icon="el-icon-video-play" type="text" @click="run">
  53. 运行
  54. </el-button> -->
  55. <el-button icon="el-icon-view" type="text" @click="preview">
  56. 预览
  57. </el-button>
  58. <!-- <el-button icon="el-icon-view" type="text" @click="showJson">-->
  59. <!-- 查看json-->
  60. <!-- </el-button>-->
  61. <el-button icon="el-icon-check" type="text" @click="saveJson">
  62. 保存
  63. </el-button>
  64. <!-- <el-button icon="el-icon-download" type="text" @click="download">
  65. 导出vue文件
  66. </el-button>
  67. <el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy">
  68. 复制代码
  69. </el-button> -->
  70. <el-button
  71. class="delete-btn"
  72. icon="el-icon-delete"
  73. type="text"
  74. @click="empty"
  75. >
  76. 清空
  77. </el-button>
  78. </div>
  79. <el-scrollbar class="center-scrollbar">
  80. <el-row class="center-board-row" :gutter="formConf.gutter">
  81. <el-form
  82. :size="formConf.size"
  83. :label-position="formConf.labelPosition"
  84. :disabled="formConf.disabled"
  85. :label-width="formConf.labelWidth + 'px'"
  86. >
  87. <draggable
  88. class="drawing-board"
  89. :list="drawingList"
  90. :animation="340"
  91. group="componentsGroup"
  92. >
  93. <draggable-item
  94. v-for="(item, index) in drawingList"
  95. :key="item.renderKey"
  96. :drawing-list="drawingList"
  97. :current-item="item"
  98. :index="index"
  99. :active-id="activeId"
  100. :form-conf="formConf"
  101. @activeItem="activeFormItem"
  102. @copyItem="drawingItemCopy"
  103. @deleteItem="drawingItemDelete"
  104. />
  105. </draggable>
  106. <div v-show="!drawingList.length" class="empty-info">
  107. 点选组件进行表单设计
  108. </div>
  109. </el-form>
  110. </el-row>
  111. </el-scrollbar>
  112. </div>
  113. <right-panel
  114. :key="rightPanelKey"
  115. :active-data="activeData"
  116. :form-conf="formConf"
  117. :show-field="!!drawingList.length"
  118. @tag-change="tagChange"
  119. @fetch-data="fetchData"
  120. />
  121. <form-drawer
  122. :visible.sync="drawerVisible"
  123. :form-data="formData"
  124. size="100%"
  125. :generate-conf="generateConf"
  126. />
  127. <!-- <json-drawer
  128. size="60%"
  129. :visible.sync="jsonDrawerVisible"
  130. :json-str="JSON.stringify(formData)"
  131. @refresh="refreshJson"
  132. /> -->
  133. <el-dialog
  134. :visible.sync="jsonDrawerVisible"
  135. title="查看json"
  136. fullscreen
  137. center
  138. >
  139. <vue-ace-editor
  140. v-model="JSON.stringify(formData, null, 2)"
  141. @init="editorInit"
  142. lang="json"
  143. theme="chrome"
  144. width="100%"
  145. height="calc(100vh - 214px)"
  146. :options="{ wrap: true, readOnly: true }"
  147. >
  148. </vue-ace-editor>
  149. <span slot="footer">
  150. <el-button
  151. icon="el-icon-document"
  152. type="primary"
  153. v-clipboard:copy="JSON.stringify(formData)"
  154. v-clipboard:success="onCopy"
  155. >复 制</el-button
  156. >
  157. <el-button icon="el-icon-close" @click="jsonDrawerVisible = false"
  158. >关闭</el-button
  159. >
  160. </span>
  161. </el-dialog>
  162. <code-type-dialog
  163. :visible.sync="dialogVisible"
  164. title="选择生成类型"
  165. :show-file-name="showFileName"
  166. @confirm="generate"
  167. />
  168. <parser-dialog
  169. :visible.sync="previewVisible"
  170. title="预览"
  171. :formConf="formData"
  172. />
  173. <input id="copyNode" type="hidden" />
  174. </div>
  175. </template>
  176. <script>
  177. import draggable from 'vuedraggable';
  178. import { debounce } from 'throttle-debounce';
  179. // import { saveAs } from 'file-saver'
  180. import ClipboardJS from 'clipboard';
  181. import render from '@/components/FormGenerator/components/render/render';
  182. import FormDrawer from './FormDrawer';
  183. import JsonDrawer from './JsonDrawer';
  184. import RightPanel from './RightPanel';
  185. import ParserDialog from './ParserDialog';
  186. import {
  187. inputComponents,
  188. selectComponents,
  189. layoutComponents,
  190. formConf
  191. } from '@/components/FormGenerator/components/generator/config';
  192. import {
  193. exportDefault,
  194. beautifierConf,
  195. isNumberStr,
  196. titleCase,
  197. deepClone,
  198. isObjectObject
  199. } from '@/components/FormGenerator/utils/index';
  200. import {
  201. makeUpHtml,
  202. vueTemplate,
  203. vueScript,
  204. cssStyle
  205. } from '@/components/FormGenerator/components/generator/html';
  206. import { makeUpJs } from '@/components/FormGenerator/components/generator/js';
  207. import { makeUpCss } from '@/components/FormGenerator/components/generator/css';
  208. import drawingDefalut from '@/components/FormGenerator/components/generator/drawingDefalut';
  209. import logo from './assets/logo.png';
  210. import CodeTypeDialog from './CodeTypeDialog';
  211. import DraggableItem from './DraggableItem';
  212. import {
  213. getDrawingList,
  214. saveDrawingList,
  215. getIdGlobal,
  216. saveIdGlobal,
  217. getFormConf
  218. } from '@/components/FormGenerator/utils/db';
  219. import loadBeautifier from '@/components/FormGenerator/utils/loadBeautifier';
  220. import VueAceEditor from 'vue2-ace-editor';
  221. let beautifier;
  222. const emptyActiveData = { style: {}, autosize: {} };
  223. let oldActiveId;
  224. let tempActiveData;
  225. const drawingListInDB = getDrawingList();
  226. const formConfInDB = getFormConf();
  227. const idGlobal = getIdGlobal();
  228. export default {
  229. name: 'FormGenerator',
  230. components: {
  231. draggable,
  232. render,
  233. FormDrawer,
  234. JsonDrawer,
  235. RightPanel,
  236. CodeTypeDialog,
  237. DraggableItem,
  238. ParserDialog,
  239. VueAceEditor
  240. },
  241. props: ['formDataProp'],
  242. data() {
  243. return {
  244. logo,
  245. idGlobal,
  246. formConf,
  247. inputComponents,
  248. selectComponents,
  249. layoutComponents,
  250. labelWidth: 100,
  251. drawingList: [],
  252. drawingData: {},
  253. activeId: null,
  254. drawerVisible: false,
  255. formData: {},
  256. dialogVisible: false,
  257. previewVisible: false,
  258. jsonDrawerVisible: false,
  259. generateConf: null,
  260. showFileName: false,
  261. activeData: {},
  262. rightPanelKey: 0,
  263. saveDrawingListDebounce: debounce(340, saveDrawingList),
  264. saveIdGlobalDebounce: debounce(340, saveIdGlobal),
  265. leftComponents: [
  266. {
  267. title: '输入型组件',
  268. list: inputComponents
  269. },
  270. {
  271. title: '选择型组件',
  272. list: selectComponents
  273. }
  274. // {
  275. // title: '布局型组件',
  276. // list: layoutComponents
  277. // }
  278. ]
  279. };
  280. },
  281. computed: {},
  282. watch: {
  283. // eslint-disable-next-line func-names
  284. 'activeData.__config__.label': function (val, oldVal) {
  285. if (
  286. this.activeData.placeholder === undefined ||
  287. !this.activeData.__config__.tag ||
  288. oldActiveId !== this.activeId
  289. ) {
  290. return;
  291. }
  292. this.activeData.placeholder =
  293. this.activeData.placeholder.replace(oldVal, '') + val;
  294. },
  295. activeId: {
  296. handler(val) {
  297. oldActiveId = val;
  298. },
  299. immediate: true
  300. },
  301. drawingList: {
  302. handler(val) {
  303. this.saveDrawingListDebounce(val);
  304. if (val.length === 0) this.idGlobal = 100;
  305. },
  306. deep: true
  307. },
  308. idGlobal: {
  309. handler(val) {
  310. this.saveIdGlobalDebounce(val);
  311. },
  312. immediate: true
  313. }
  314. },
  315. mounted() {
  316. // if (Array.isArray(drawingListInDB) && drawingListInDB.length > 0) {
  317. // this.drawingList = drawingListInDB;
  318. // } else {
  319. // this.drawingList = drawingDefalut;
  320. // }
  321. // this.activeFormItem(this.drawingList[0]);
  322. // if (formConfInDB) {
  323. // this.formConf = formConfInDB;
  324. // }
  325. loadBeautifier((btf) => {
  326. beautifier = btf;
  327. });
  328. const clipboard = new ClipboardJS('#copyNode', {
  329. text: (trigger) => {
  330. const codeStr = this.generateCode();
  331. this.$notify({
  332. title: '成功',
  333. message: '代码已复制到剪切板,可粘贴。',
  334. type: 'success'
  335. });
  336. return codeStr;
  337. }
  338. });
  339. clipboard.on('error', (e) => {
  340. this.$message.error('代码复制失败');
  341. });
  342. },
  343. methods: {
  344. setObjectValueReduce(obj, strKeys, data) {
  345. const arr = strKeys.split('.');
  346. arr.reduce((pre, item, i) => {
  347. if (arr.length === i + 1) {
  348. pre[item] = data;
  349. } else if (!isObjectObject(pre[item])) {
  350. pre[item] = {};
  351. }
  352. return pre[item];
  353. }, obj);
  354. },
  355. setRespData(component, resp) {
  356. const { dataPath, renderKey, dataConsumer } = component.__config__;
  357. if (!dataPath || !dataConsumer) return;
  358. const respData = dataPath
  359. .split('.')
  360. .reduce((pre, item) => pre[item], resp);
  361. // 将请求回来的数据,赋值到指定属性。
  362. // 以el-tabel为例,根据Element文档,应该将数据赋值给el-tabel的data属性,所以dataConsumer的值应为'data';
  363. // 此时赋值代码可写成 component[dataConsumer] = respData;
  364. // 但为支持更深层级的赋值(如:dataConsumer的值为'options.data'),使用setObjectValueReduce
  365. this.setObjectValueReduce(component, dataConsumer, respData);
  366. const i = this.drawingList.findIndex(
  367. (item) => item.__config__.renderKey === renderKey
  368. );
  369. if (i > -1) this.$set(this.drawingList, i, component);
  370. },
  371. fetchData(component) {
  372. const { dataType, method, url } = component.__config__;
  373. if (dataType === 'dynamic' && method && url) {
  374. this.setLoading(component, true);
  375. this.$axios({
  376. method,
  377. url
  378. }).then((resp) => {
  379. this.setLoading(component, false);
  380. this.setRespData(component, resp.data);
  381. });
  382. }
  383. },
  384. setLoading(component, val) {
  385. const { directives } = component;
  386. if (Array.isArray(directives)) {
  387. const t = directives.find((d) => d.name === 'loading');
  388. if (t) t.value = val;
  389. }
  390. },
  391. activeFormItem(currentItem) {
  392. if (currentItem) {
  393. this.activeData = currentItem;
  394. this.activeId = currentItem.__config__.formId;
  395. this.rightPanelKey++;
  396. }
  397. },
  398. onEnd(obj) {
  399. if (obj.from !== obj.to) {
  400. this.fetchData(tempActiveData);
  401. this.activeData = tempActiveData;
  402. this.activeId = this.idGlobal;
  403. }
  404. },
  405. addComponent(item) {
  406. const clone = this.cloneComponent(item);
  407. this.fetchData(clone);
  408. this.drawingList.push(clone);
  409. this.activeFormItem(clone);
  410. },
  411. cloneComponent(origin) {
  412. const clone = deepClone(origin);
  413. const config = clone.__config__;
  414. config.span = this.formConf.span; // 生成代码时,会根据span做精简判断
  415. this.createIdAndKey(clone);
  416. clone.placeholder !== undefined && (clone.placeholder += config.label);
  417. tempActiveData = clone;
  418. return tempActiveData;
  419. },
  420. createIdAndKey(item) {
  421. const config = item.__config__;
  422. config.formId = ++this.idGlobal;
  423. config.renderKey = `${config.formId}${+new Date()}`; // 改变renderKey后可以实现强制更新组件
  424. if (config.layout === 'colFormItem') {
  425. item.__vModel__ = `field${this.idGlobal}`;
  426. } else if (config.layout === 'rowFormItem') {
  427. config.componentName = `row${this.idGlobal}`;
  428. !Array.isArray(config.children) && (config.children = []);
  429. delete config.label; // rowFormItem无需配置label属性
  430. }
  431. if (Array.isArray(config.children)) {
  432. config.children = config.children.map((childItem) =>
  433. this.createIdAndKey(childItem)
  434. );
  435. }
  436. return item;
  437. },
  438. AssembleFormData() {
  439. this.formData = {
  440. fields: deepClone(this.drawingList),
  441. ...this.formConf
  442. };
  443. },
  444. generate(data) {
  445. const func = this[`exec${titleCase(this.operationType)}`];
  446. this.generateConf = data;
  447. func && func(data);
  448. },
  449. execRun(data) {
  450. this.AssembleFormData();
  451. this.drawerVisible = true;
  452. },
  453. execDownload(data) {
  454. const codeStr = this.generateCode();
  455. const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' });
  456. // saveAs(blob, data.fileName)
  457. },
  458. execCopy(data) {
  459. document.getElementById('copyNode').click();
  460. },
  461. empty() {
  462. this.$confirm('确定要清空所有组件吗?', '提示', {
  463. type: 'warning'
  464. }).then(() => {
  465. this.drawingList = [];
  466. this.idGlobal = 100;
  467. });
  468. },
  469. drawingItemCopy(item, list) {
  470. let clone = deepClone(item);
  471. clone = this.createIdAndKey(clone);
  472. list.push(clone);
  473. this.activeFormItem(clone);
  474. },
  475. drawingItemDelete(index, list) {
  476. list.splice(index, 1);
  477. this.$nextTick(() => {
  478. const len = this.drawingList.length;
  479. if (len) {
  480. this.activeFormItem(this.drawingList[len - 1]);
  481. }
  482. });
  483. },
  484. generateCode() {
  485. const { type } = this.generateConf;
  486. this.AssembleFormData();
  487. const script = vueScript(makeUpJs(this.formData, type));
  488. const html = vueTemplate(makeUpHtml(this.formData, type));
  489. const css = cssStyle(makeUpCss(this.formData));
  490. return beautifier.html(html + script + css, beautifierConf.html);
  491. },
  492. editorInit: function () {
  493. // require('brace/ext/language_tools')
  494. // require('brace/mode/json')
  495. // require('brace/theme/chrome')
  496. },
  497. showJson() {
  498. this.AssembleFormData();
  499. this.jsonDrawerVisible = true;
  500. },
  501. saveJson() {
  502. this.AssembleFormData();
  503. this.$emit('submit', this.formData);
  504. },
  505. onCopy() {
  506. this.$message.success('内容复制成功');
  507. },
  508. preview() {
  509. this.AssembleFormData();
  510. this.previewVisible = true;
  511. },
  512. download() {
  513. this.dialogVisible = true;
  514. this.showFileName = true;
  515. this.operationType = 'download';
  516. },
  517. run() {
  518. this.dialogVisible = true;
  519. this.showFileName = false;
  520. this.operationType = 'run';
  521. },
  522. copy() {
  523. this.dialogVisible = true;
  524. this.showFileName = false;
  525. this.operationType = 'copy';
  526. },
  527. tagChange(newTag) {
  528. newTag = this.cloneComponent(newTag);
  529. const config = newTag.__config__;
  530. newTag.__vModel__ = this.activeData.__vModel__;
  531. config.formId = this.activeId;
  532. config.span = this.activeData.__config__.span;
  533. this.activeData.__config__.tag = config.tag;
  534. this.activeData.__config__.tagIcon = config.tagIcon;
  535. this.activeData.__config__.document = config.document;
  536. if (
  537. typeof this.activeData.__config__.defaultValue ===
  538. typeof config.defaultValue
  539. ) {
  540. config.defaultValue = this.activeData.__config__.defaultValue;
  541. }
  542. Object.keys(newTag).forEach((key) => {
  543. if (this.activeData[key] !== undefined) {
  544. newTag[key] = this.activeData[key];
  545. }
  546. });
  547. this.activeData = newTag;
  548. this.updateDrawingList(newTag, this.drawingList);
  549. },
  550. updateDrawingList(newTag, list) {
  551. const index = list.findIndex(
  552. (item) => item.__config__.formId === this.activeId
  553. );
  554. if (index > -1) {
  555. list.splice(index, 1, newTag);
  556. } else {
  557. list.forEach((item) => {
  558. if (Array.isArray(item.__config__.children))
  559. this.updateDrawingList(newTag, item.__config__.children);
  560. });
  561. }
  562. },
  563. refreshJson(data) {
  564. this.drawingList = deepClone(data.fields);
  565. delete data.fields;
  566. this.formConf = data;
  567. },
  568. setJSON(json) {
  569. let formDataTemp = deepClone(json);
  570. this.drawingList = formDataTemp.fields;
  571. if (this.drawingList && this.drawingList.length) {
  572. this.activeFormItem(this.drawingList[0]);
  573. }
  574. delete formDataTemp.fields;
  575. this.formConf = formDataTemp;
  576. },
  577. getJSON() {
  578. this.AssembleFormData();
  579. return this.formData;
  580. }
  581. }
  582. };
  583. </script>
  584. <style lang="scss">
  585. @import './styles/home';
  586. </style>