FcDesigner.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  1. <template>
  2. <ElContainer class="_fc-designer" :style="'height:' + dragHeight">
  3. <ElMain>
  4. <ElContainer style="height: 100%">
  5. <el-aside class="_fc-l" width="266px">
  6. <template v-for="(item, index) in menuList" :key="index">
  7. <div class="_fc-l-group">
  8. <h4 class="_fc-l-title">{{ item.title }}</h4>
  9. <draggable :group="{ name: 'default', pull: 'clone', put: false }" :sort="false" itemKey="name" :list="item.list">
  10. <template #item="{ element }">
  11. <div class="_fc-l-item">
  12. <div class="_fc-l-icon">
  13. <i class="fc-icon" :class="element.icon || 'icon-input'"></i>
  14. </div>
  15. <span class="_fc-l-name">{{ t('components.' + element.name + '.name') || element.label }}</span>
  16. </div>
  17. </template>
  18. </draggable>
  19. </div>
  20. </template>
  21. </el-aside>
  22. <ElContainer class="_fc-m">
  23. <el-header class="_fc-m-tools" height="45">
  24. <slot name="handle"></slot>
  25. <el-button size="small" @click="setJson">导入JSON</el-button>
  26. <el-button size="small" type="primary" @click="showJson">生成JSON</el-button>
  27. <el-button size="small" type="primary" @click="saveAsForm">暂存表单</el-button>
  28. <el-button type="primary" plain round size="small" @click="previewFc"
  29. ><i class="fc-icon icon-preview"></i> {{ t('designer.preview') }}
  30. </el-button>
  31. <el-popconfirm
  32. :title="t('designer.clearConfirmTitle')"
  33. width="200px"
  34. :confirm-button-text="t('designer.clearConfirm')"
  35. :cancel-button-text="t('designer.clearCancel')"
  36. @confirm="clearDragRule"
  37. >
  38. <template #reference>
  39. <el-button type="danger" plain round size="small"><i class="fc-icon icon-delete"></i>{{ t('designer.clear') }} </el-button>
  40. </template>
  41. </el-popconfirm>
  42. </el-header>
  43. <ElMain style="background: #f5f5f5; padding: 20px">
  44. <div class="_fc-m-drag">
  45. <DragForm :rule="dragForm.rule" :option="form.value" v-model:api="dragForm.api"></DragForm>
  46. </div>
  47. </ElMain>
  48. </ElContainer>
  49. <ElAside class="_fc-r" width="320px" v-if="!config || config.showConfig !== false">
  50. <ElContainer style="height: 100%">
  51. <el-header height="40px" class="_fc-r-tabs">
  52. <div
  53. class="_fc-r-tab"
  54. :class="{ active: activeTab === 'props' }"
  55. v-if="!!activeRule || (config && config.showFormConfig === false)"
  56. @click="activeTab = 'props'"
  57. >
  58. {{ t('designer.config.component') }}
  59. </div>
  60. <div
  61. class="_fc-r-tab"
  62. v-if="!config || config.showFormConfig !== false"
  63. :class="{ active: activeTab === 'form' && !!activeRule }"
  64. @click="activeTab = 'form'"
  65. >
  66. {{ t('designer.config.form') }}
  67. </div>
  68. </el-header>
  69. <ElMain v-show="activeTab === 'form'" v-if="!config || config.showFormConfig !== false">
  70. <DragForm :rule="form.rule" :option="form.option" v-model="form.value.form" v-model:api="form.api"></DragForm>
  71. </ElMain>
  72. <ElMain v-show="activeTab === 'props'" style="padding: 0 20px" :key="activeRule ? activeRule._id : ''">
  73. <div>
  74. <ElDivider v-if="showBaseRule">{{ t('designer.config.rule') }}</ElDivider>
  75. <DragForm
  76. v-show="showBaseRule"
  77. v-model:api="baseForm.api"
  78. :rule="baseForm.rule"
  79. :option="baseForm.options"
  80. :modelValue="baseForm.value"
  81. @change="baseChange"
  82. ></DragForm>
  83. <ElDivider>{{ t('designer.config.props') }}</ElDivider>
  84. <DragForm
  85. v-model:api="propsForm.api"
  86. :rule="propsForm.rule"
  87. :option="propsForm.options"
  88. :modelValue="propsForm.value"
  89. @change="propChange"
  90. @removeField="propRemoveField"
  91. ></DragForm>
  92. <ElDivider v-if="showBaseRule">{{ t('designer.config.validate') }}</ElDivider>
  93. <DragForm
  94. v-show="showBaseRule"
  95. v-model:api="validateForm.api"
  96. :rule="validateForm.rule"
  97. :option="validateForm.options"
  98. :modelValue="validateForm.value"
  99. @update:modelValue="validateChange"
  100. ></DragForm>
  101. </div>
  102. </ElMain>
  103. </ElContainer>
  104. </ElAside>
  105. <!-- 预览表单 -->
  106. <ElDialog v-model="preview.state" width="800px" append-to-body>
  107. <ViewForm :rule="preview.rule" :option="preview.option" v-if="preview.state"></ViewForm>
  108. </ElDialog>
  109. <!-- 生成JSON / 导入JSON -->
  110. <el-dialog :title="title[type]" v-model="state" class="_fc-t-dialog">
  111. <div ref="editorRef" id="editor" v-if="state"></div>
  112. <span style="color: red" v-if="err">输入内容格式有误!</span>
  113. <template #footer v-if="type > 2">
  114. <span slot="footer" class="dialog-footer">
  115. <el-button @click="state = false" size="small">取 消</el-button>
  116. <el-button type="primary" @click="onOk" size="small">确 定</el-button>
  117. </span>
  118. </template>
  119. </el-dialog>
  120. </ElContainer>
  121. </ElMain>
  122. </ElContainer>
  123. </template>
  124. <script>
  125. // 代码样式
  126. import 'codemirror/lib/codemirror.css'
  127. import 'codemirror/addon/lint/lint.css'
  128. import CodeMirror from 'codemirror/lib/codemirror'
  129. import 'codemirror/addon/lint/lint'
  130. import 'codemirror/addon/lint/json-lint'
  131. import 'codemirror/mode/javascript/javascript'
  132. import 'codemirror/mode/vue/vue'
  133. import 'codemirror/mode/xml/xml'
  134. import 'codemirror/mode/css/css'
  135. import 'codemirror/addon/mode/overlay'
  136. import 'codemirror/addon/mode/simple'
  137. import 'codemirror/addon/selection/selection-pointer'
  138. import 'codemirror/mode/handlebars/handlebars'
  139. import 'codemirror/mode/htmlmixed/htmlmixed'
  140. import 'codemirror/mode/pug/pug'
  141. // 代码样式
  142. import form from '../../config/base/form'
  143. import field from '../../config/base/field'
  144. import validate from '../../config/base/validate'
  145. import { deepCopy } from '@form-create/utils/lib/deepextend'
  146. import is, { hasProperty } from '@form-create/utils/lib/type'
  147. import { lower } from '@form-create/utils/lib/tocase'
  148. import ruleList from '../../config/rule'
  149. import draggable from 'vuedraggable/src/vuedraggable'
  150. import createMenu from '../../config/menu'
  151. import { upper, useLocale } from '../../utils/formCreateIndex'
  152. import { designerForm } from '../../utils/form'
  153. import viewForm from '../../utils/form'
  154. import { t as globalT } from '../../utils/locale'
  155. import { computed, reactive, toRefs, toRef, ref, getCurrentInstance, provide, nextTick, watch, defineComponent, markRaw, defineExpose } from 'vue'
  156. export default defineComponent({
  157. name: 'FcDesigner',
  158. components: {
  159. draggable,
  160. DragForm: designerForm.$form(),
  161. ViewForm: viewForm.$form()
  162. },
  163. props: ['menu', 'height', 'config', 'mask', 'locale'],
  164. setup(props) {
  165. const { menu, height, mask, locale } = toRefs(props)
  166. const vm = getCurrentInstance()
  167. provide('fcx', ref({ active: null }))
  168. provide('designer', vm)
  169. const config = toRef(props, 'config', {})
  170. const baseRule = toRef(config.value, 'baseRule', null)
  171. const componentRule = toRef(config.value, 'componentRule', {})
  172. const validateRule = toRef(config.value, 'validateRule', null)
  173. const formRule = toRef(config.value, 'formRule', null)
  174. const dragHeight = computed(() => {
  175. const h = height.value
  176. if (!h) return '100%'
  177. return is.Number(h) ? `${h}px` : h
  178. })
  179. let _t = globalT
  180. if (locale.value) {
  181. _t = useLocale(locale).t
  182. }
  183. const t = (...args) => _t(...args)
  184. const tidyRuleConfig = (orgRule, configRule, ...args) => {
  185. if (configRule) {
  186. if (is.Function(configRule)) {
  187. return configRule(...args)
  188. }
  189. if (configRule.rule) {
  190. let rule = configRule.rule(...args)
  191. if (configRule.append) {
  192. rule = [...rule, ...orgRule(...args)]
  193. }
  194. return rule
  195. }
  196. }
  197. return orgRule(...args)
  198. }
  199. const editorRef = ref()
  200. const data = reactive({
  201. cacheProps: {},
  202. moveRule: null,
  203. addRule: null,
  204. added: null,
  205. activeTab: 'form',
  206. activeRule: null,
  207. children: ref([]),
  208. menuList: menu.value || createMenu({ t }),
  209. showBaseRule: false,
  210. visible: {
  211. preview: false
  212. },
  213. t,
  214. preview: {
  215. state: false,
  216. rule: [],
  217. option: {}
  218. },
  219. dragForm: ref({
  220. rule: [],
  221. api: {}
  222. }),
  223. form: {
  224. rule: tidyRuleConfig(form, formRule.value, { t }),
  225. api: {},
  226. option: {
  227. form: {
  228. labelPosition: 'top',
  229. size: 'small'
  230. },
  231. submitBtn: false
  232. },
  233. value: {
  234. form: {
  235. inline: false,
  236. hideRequiredAsterisk: false,
  237. labelPosition: 'right',
  238. size: 'small',
  239. labelWidth: '125px',
  240. formCreateSubmitBtn: true,
  241. formCreateResetBtn: false
  242. },
  243. submitBtn: false
  244. }
  245. },
  246. baseForm: {
  247. rule: tidyRuleConfig(field, baseRule.value, { t }),
  248. api: {},
  249. value: {},
  250. options: {
  251. form: {
  252. labelPosition: 'top',
  253. size: 'small'
  254. },
  255. submitBtn: false,
  256. mounted: fapi => {
  257. fapi.activeRule = data.activeRule
  258. fapi.setValue(fapi.options.formData || {})
  259. }
  260. }
  261. },
  262. validateForm: {
  263. rule: tidyRuleConfig(validate, validateRule.value, { t }),
  264. api: {},
  265. value: [],
  266. options: {
  267. form: {
  268. labelPosition: 'top',
  269. size: 'small'
  270. },
  271. submitBtn: false,
  272. mounted: fapi => {
  273. fapi.activeRule = data.activeRule
  274. fapi.setValue(fapi.options.formData || {})
  275. }
  276. }
  277. },
  278. propsForm: {
  279. rule: [],
  280. api: {},
  281. value: {},
  282. options: {
  283. form: {
  284. labelPosition: 'top',
  285. size: 'small'
  286. },
  287. submitBtn: false,
  288. mounted: fapi => {
  289. fapi.activeRule = data.activeRule
  290. fapi.setValue(fapi.options.formData || {})
  291. }
  292. }
  293. },
  294. // 生成JSON / 导入JSON
  295. title: ['生成规则', '表单规则', '生成组件', '设置生成规则', '设置表单规则'],
  296. state: false,
  297. value: null,
  298. err: false,
  299. type: -1,
  300. editor: null
  301. })
  302. watch(
  303. () => data.preview.state,
  304. function (n) {
  305. if (!n) {
  306. nextTick(() => {
  307. data.preview.rule = data.preview.option = null
  308. })
  309. }
  310. }
  311. )
  312. watch(
  313. () => data.state,
  314. n => {
  315. if (!n) {
  316. data.value = null
  317. data.err = false
  318. }
  319. }
  320. )
  321. watch(
  322. () => data.value,
  323. n => {
  324. methods.load()
  325. }
  326. )
  327. let unWatchActiveRule = null
  328. watch(
  329. () => locale.value,
  330. n => {
  331. _t = n ? useLocale(locale).t : globalT
  332. const formVal = data.form.api.formData && data.form.api.formData()
  333. const baseFormVal = data.baseForm.api.formData && data.baseForm.api.formData()
  334. const validateFormVal = data.validateForm.api.formData && data.validateForm.api.formData()
  335. data.validateForm.rule = tidyRuleConfig(validate, validateRule.value, { t })
  336. data.baseForm.rule = tidyRuleConfig(field, baseRule.value, { t })
  337. data.form.rule = tidyRuleConfig(form, formRule.value, { t })
  338. data.cacheProps = {}
  339. const rule = data.activeRule
  340. let propsVal = null
  341. if (rule) {
  342. propsVal = data.propsForm.api.formData && data.propsForm.api.formData()
  343. data.propsForm.rule = data.cacheProps[rule._id] = tidyRuleConfig(
  344. rule.config.config.props,
  345. componentRule.value && componentRule.value[rule.config.config.name],
  346. rule,
  347. { t, api: data.dragForm.api }
  348. )
  349. }
  350. nextTick(() => {
  351. formVal && data.form.api.setValue(formVal)
  352. baseFormVal && data.baseForm.api.setValue(baseFormVal)
  353. validateFormVal && data.validateForm.api.setValue(validateFormVal)
  354. propsVal && data.propsForm.api.setValue(propsVal)
  355. })
  356. }
  357. )
  358. const methods = {
  359. unWatchActiveRule() {
  360. unWatchActiveRule && unWatchActiveRule()
  361. unWatchActiveRule = null
  362. },
  363. watchActiveRule() {
  364. methods.unWatchActiveRule()
  365. unWatchActiveRule = watch(
  366. () => data.activeRule,
  367. function (n) {
  368. n && methods.updateRuleFormData()
  369. },
  370. { deep: true, flush: 'post' }
  371. )
  372. },
  373. makeChildren(children) {
  374. return reactive({ children }).children
  375. },
  376. addMenu(config) {
  377. if (!config.name || !config.list) return
  378. let flag = true
  379. data.menuList.forEach((v, i) => {
  380. if (v.name === config.name) {
  381. data.menuList[i] = config
  382. flag = false
  383. }
  384. })
  385. if (flag) {
  386. data.menuList.push(config)
  387. }
  388. },
  389. removeMenu(name) {
  390. ;[...data.menuList].forEach((v, i) => {
  391. if (v.name === name) {
  392. data.menuList.splice(i, 1)
  393. }
  394. })
  395. },
  396. setMenuItem(name, list) {
  397. data.menuList.forEach(v => {
  398. if (v.name === name) {
  399. v.list = list
  400. }
  401. })
  402. },
  403. appendMenuItem(name, item) {
  404. data.menuList.forEach(v => {
  405. if (v.name === name) {
  406. v.list.push(...(Array.isArray(item) ? item : [item]))
  407. }
  408. })
  409. },
  410. removeMenuItem(item) {
  411. data.menuList.forEach(v => {
  412. let idx
  413. if (is.String(item)) {
  414. ;[...v.list].forEach((menu, idx) => {
  415. if (menu.name === item) {
  416. v.list.splice(idx, 1)
  417. }
  418. })
  419. } else {
  420. if ((idx = v.list.indexOf(item)) > -1) {
  421. v.list.splice(idx, 1)
  422. }
  423. }
  424. })
  425. },
  426. addComponent(component) {
  427. if (Array.isArray(component)) {
  428. component.forEach(v => {
  429. ruleList[v.name] = v
  430. })
  431. } else {
  432. ruleList[component.name] = component
  433. }
  434. },
  435. getParent(rule) {
  436. let parent = rule.__fc__.parent.rule
  437. const config = parent.config
  438. if (config && config.config.inside) {
  439. rule = parent
  440. parent = parent.__fc__.parent.rule
  441. }
  442. return { root: parent, parent: rule }
  443. },
  444. makeDrag(group, tag, children, on) {
  445. return {
  446. type: 'DragBox',
  447. wrap: {
  448. show: false
  449. },
  450. col: {
  451. show: false
  452. },
  453. inject: true,
  454. props: {
  455. rule: {
  456. props: {
  457. tag: 'el-col',
  458. group: group === true ? 'default' : group,
  459. ghostClass: 'ghost',
  460. animation: 150,
  461. handle: '._fc-drag-btn',
  462. emptyInsertThreshold: 0,
  463. direction: 'vertical',
  464. itemKey: 'type'
  465. }
  466. },
  467. tag
  468. },
  469. children,
  470. on
  471. }
  472. },
  473. clearDragRule() {
  474. methods.setRule([])
  475. },
  476. makeDragRule(children) {
  477. return methods.makeChildren([
  478. methods.makeDrag(true, 'draggable', children, {
  479. add: (inject, evt) => methods.dragAdd(children, evt),
  480. end: (inject, evt) => methods.dragEnd(children, evt),
  481. start: (inject, evt) => methods.dragStart(children, evt),
  482. unchoose: (inject, evt) => methods.dragUnchoose(children, evt)
  483. })
  484. ])
  485. },
  486. previewFc() {
  487. data.preview.state = true
  488. data.preview.rule = methods.getRule()
  489. data.preview.option = methods.getOption()
  490. },
  491. getRule() {
  492. return methods.parseRule(deepCopy(data.dragForm.api.rule[0].children))
  493. },
  494. getJson() {
  495. return designerForm.toJson(methods.getRule())
  496. },
  497. getOption() {
  498. const option = deepCopy(data.form.value)
  499. option.submitBtn = option._submitBtn
  500. option.resetBtn = option._resetBtn
  501. if (typeof option.submitBtn === 'object') {
  502. option.submitBtn.show = option.form.formCreateSubmitBtn
  503. } else {
  504. option.submitBtn = {
  505. show: option.form.formCreateSubmitBtn,
  506. innerText: t('form.submit')
  507. }
  508. }
  509. if (typeof option.resetBtn === 'object') {
  510. option.resetBtn.show = option.form.formCreateResetBtn
  511. } else {
  512. option.resetBtn = {
  513. show: option.form.formCreateResetBtn,
  514. innerText: t('form.reset')
  515. }
  516. }
  517. delete option.form.formCreateSubmitBtn
  518. delete option.form.formCreateResetBtn
  519. delete option._submitBtn
  520. delete option._resetBtn
  521. return option
  522. },
  523. getOptions() {
  524. methods.getOption()
  525. },
  526. setRule(rules) {
  527. if (!rules) {
  528. rules = []
  529. }
  530. data.children = methods.makeChildren(methods.loadRule(is.String(rules) ? designerForm.parseJson(rules) : deepCopy(rules)))
  531. methods.clearActiveRule()
  532. data.dragForm.rule = methods.makeDragRule(data.children)
  533. },
  534. setBaseRuleConfig(rule, append) {
  535. baseRule.value = { rule, append }
  536. data.baseForm.rule = tidyRuleConfig(field, baseRule.value, { t })
  537. },
  538. setComponentRuleConfig(name, rule, append) {
  539. componentRule.value[name] = { rule, append }
  540. data.cacheProps = {}
  541. const activeRule = data.activeRule
  542. if (activeRule) {
  543. const propsVal = data.propsForm.api.formData && data.propsForm.api.formData()
  544. data.propsForm.rule = data.cacheProps[activeRule._id] = tidyRuleConfig(
  545. activeRule.config.config.props,
  546. componentRule.value && componentRule.value[activeRule.config.config.name],
  547. activeRule,
  548. { t, api: data.dragForm.api }
  549. )
  550. nextTick(() => {
  551. propsVal && data.propsForm.api.setValue(propsVal)
  552. })
  553. }
  554. },
  555. setValidateRuleConfig(rule, append) {
  556. validateRule.value = { rule, append }
  557. data.validateForm.rule = tidyRuleConfig(field, validateRule.value, { t })
  558. },
  559. setFormRuleConfig(rule, append) {
  560. formRule.value = { rule, append }
  561. data.form.rule = tidyRuleConfig(field, formRule.value, { t })
  562. },
  563. clearActiveRule() {
  564. data.activeRule = null
  565. data.activeTab = 'form'
  566. },
  567. setOption(opt) {
  568. let option = { ...opt }
  569. option.form.formCreateSubmitBtn =
  570. typeof option.submitBtn === 'object' ? (option.submitBtn.show === undefined ? true : !!option.submitBtn.show) : !!option.submitBtn
  571. option.form.formCreateResetBtn = typeof option.resetBtn === 'object' ? !!option.resetBtn.show : !!option.resetBtn
  572. option._resetBtn = option.resetBtn
  573. option.resetBtn = false
  574. option._submitBtn = option.submitBtn
  575. option.submitBtn = false
  576. data.form.value = option
  577. },
  578. setOptions(opt) {
  579. methods.setOption(opt)
  580. },
  581. loadRule(rules) {
  582. const loadRule = []
  583. rules.forEach(rule => {
  584. if (is.String(rule)) {
  585. return loadRule.push(rule)
  586. }
  587. const config = ruleList[rule._fc_drag_tag] || ruleList[rule.type]
  588. const _children = rule.children
  589. rule.children = []
  590. if (rule.control) {
  591. rule._control = rule.control
  592. delete rule.control
  593. }
  594. if (config) {
  595. rule = methods.makeRule(config, rule)
  596. if (_children) {
  597. let children = rule.children[0].children
  598. if (config.drag) {
  599. children = children[0].children
  600. }
  601. children.push(...methods.loadRule(_children))
  602. }
  603. } else if (_children) {
  604. rule.children = methods.loadRule(_children)
  605. }
  606. loadRule.push(rule)
  607. })
  608. return loadRule
  609. },
  610. parseRule(children) {
  611. return [...children].reduce((initial, rule) => {
  612. if (is.String(rule)) {
  613. initial.push(rule)
  614. return initial
  615. } else if (rule.type === 'DragBox') {
  616. initial.push(...methods.parseRule(rule.children))
  617. return initial
  618. } else if (rule.type === 'DragTool') {
  619. rule = rule.children[0]
  620. if (rule.type === 'DragBox') {
  621. initial.push(...methods.parseRule(rule.children))
  622. return initial
  623. }
  624. }
  625. if (!rule) return initial
  626. rule = { ...rule }
  627. if (rule.children.length) {
  628. rule.children = methods.parseRule(rule.children)
  629. }
  630. delete rule._id
  631. delete rule.key
  632. delete rule.component
  633. if (rule.config) {
  634. delete rule.config.config
  635. }
  636. if (rule.effect) {
  637. delete rule.effect._fc
  638. delete rule.effect._fc_tool
  639. }
  640. if (rule._control) {
  641. rule.control = rule._control
  642. delete rule._control
  643. }
  644. Object.keys(rule)
  645. .filter(k => (Array.isArray(rule[k]) && rule[k].length === 0) || (is.Object(rule[k]) && Object.keys(rule[k]).length === 0))
  646. .forEach(k => {
  647. delete rule[k]
  648. })
  649. initial.push(rule)
  650. return initial
  651. }, [])
  652. },
  653. baseChange(field, value, _, fapi) {
  654. if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
  655. methods.unWatchActiveRule()
  656. data.activeRule[field] = value
  657. methods.watchActiveRule()
  658. data.activeRule.config.config?.watch?.['$' + field]?.({
  659. field,
  660. value,
  661. api: fapi,
  662. rule: data.activeRule
  663. })
  664. }
  665. },
  666. propRemoveField(field, _, fapi) {
  667. if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
  668. methods.unWatchActiveRule()
  669. const org = field
  670. data.dragForm.api.sync(data.activeRule)
  671. if (field.indexOf('formCreate') === 0) {
  672. field = field.replace('formCreate', '')
  673. if (!field) return
  674. field = lower(field)
  675. if (field.indexOf('effect') === 0 && field.indexOf('>') > -1) {
  676. delete data.activeRule.effect[field.split('>')[1]]
  677. } else if (field.indexOf('props') === 0 && field.indexOf('>') > -1) {
  678. delete data.activeRule.props[field.split('>')[1]]
  679. } else if (field.indexOf('attrs') === 0 && field.indexOf('>') > -1) {
  680. data.activeRule.attrs[field.split('>')[1]] = value
  681. } else if (field === 'child') {
  682. delete data.activeRule.children[0]
  683. } else if (field) {
  684. data.activeRule[field] = undefined
  685. }
  686. } else {
  687. delete data.activeRule.props[field]
  688. }
  689. methods.watchActiveRule()
  690. data.activeRule.config.config?.watch?.[org]?.({
  691. field: org,
  692. value: undefined,
  693. api: fapi,
  694. rule: data.activeRule
  695. })
  696. }
  697. },
  698. propChange(field, value, _, fapi) {
  699. if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
  700. methods.unWatchActiveRule()
  701. const org = field
  702. if (field.indexOf('formCreate') === 0) {
  703. field = field.replace('formCreate', '')
  704. if (!field) return
  705. field = lower(field)
  706. if (field.indexOf('effect') === 0 && field.indexOf('>') > -1) {
  707. data.activeRule.effect[field.split('>')[1]] = value
  708. } else if (field.indexOf('props') === 0 && field.indexOf('>') > -1) {
  709. data.activeRule.props[field.split('>')[1]] = value
  710. } else if (field.indexOf('attrs') === 0 && field.indexOf('>') > -1) {
  711. data.activeRule.attrs[field.split('>')[1]] = value
  712. } else if (field === 'child') {
  713. data.activeRule.children[0] = value
  714. } else {
  715. data.activeRule[field] = value
  716. }
  717. } else {
  718. data.activeRule.props[field] = value
  719. }
  720. methods.watchActiveRule()
  721. data.activeRule.config.config?.watch?.[org]?.({
  722. field: org,
  723. value,
  724. api: fapi,
  725. rule: data.activeRule
  726. })
  727. }
  728. },
  729. validateChange(formData) {
  730. if (!data.activeRule || data.validateForm.api[data.activeRule._id] !== data.activeRule) return
  731. data.activeRule.validate = formData.validate || []
  732. data.dragForm.api.refreshValidate()
  733. data.dragForm.api.nextTick(() => {
  734. data.dragForm.api.clearValidateState(data.activeRule.__fc__.id)
  735. })
  736. },
  737. toolActive(rule) {
  738. methods.unWatchActiveRule()
  739. if (data.activeRule) {
  740. delete data.propsForm.api[data.activeRule._id]
  741. delete data.baseForm.api[data.activeRule._id]
  742. delete data.validateForm.api[data.activeRule._id]
  743. delete data.dragForm.api.activeRule
  744. }
  745. data.activeRule = rule
  746. data.dragForm.api.activeRule = rule
  747. nextTick(() => {
  748. data.activeTab = 'props'
  749. nextTick(() => {
  750. data.propsForm.api[data.activeRule._id] = data.activeRule
  751. data.baseForm.api[data.activeRule._id] = data.activeRule
  752. data.validateForm.api[data.activeRule._id] = data.activeRule
  753. })
  754. })
  755. if (!data.cacheProps[rule._id]) {
  756. data.cacheProps[rule._id] = tidyRuleConfig(
  757. rule.config.config.props,
  758. componentRule.value && componentRule.value[rule.config.config.name],
  759. rule,
  760. { t, api: data.dragForm.api }
  761. ) // rule.config.config.props(rule, {t, api: data.dragForm.api});
  762. }
  763. data.propsForm.rule = data.cacheProps[rule._id]
  764. methods.updateRuleFormData()
  765. methods.watchActiveRule()
  766. },
  767. updateRuleFormData() {
  768. const rule = data.activeRule
  769. const formData = { ...rule.props, formCreateChild: deepCopy(rule.children[0]) }
  770. Object.keys(rule).forEach(k => {
  771. if (['effect', 'config', 'payload', 'id', 'type'].indexOf(k) < 0) formData['formCreate' + upper(k)] = deepCopy(rule[k])
  772. })
  773. ;['props', 'effect', 'attrs'].forEach(name => {
  774. rule[name] &&
  775. Object.keys(rule[name]).forEach(k => {
  776. formData['formCreate' + upper(name) + '>' + k] = deepCopy(rule[name][k])
  777. })
  778. })
  779. data.propsForm.value = formData
  780. data.showBaseRule = hasProperty(rule, 'field') && rule.input !== false && (!config.value || config.value.showBaseForm !== false)
  781. if (data.showBaseRule) {
  782. data.baseForm.value = {
  783. field: rule.field,
  784. title: rule.title || '',
  785. info: rule.info,
  786. _control: rule._control
  787. }
  788. data.validateForm.value = { validate: rule.validate ? [...rule.validate] : [] }
  789. data.dragForm.api.refreshValidate()
  790. data.dragForm.api.nextTick(() => {
  791. data.dragForm.api.clearValidateState(rule.__fc__.id)
  792. })
  793. }
  794. },
  795. dragStart(children) {
  796. data.moveRule = children
  797. data.added = false
  798. },
  799. dragUnchoose(children, evt) {
  800. data.addRule = {
  801. children,
  802. oldIndex: evt.oldIndex
  803. }
  804. },
  805. dragAdd(children, evt) {
  806. const newIndex = evt.newIndex
  807. const menu = evt.item._underlying_vm_
  808. if (!menu || menu.__fc__) {
  809. if (data.addRule) {
  810. const rule = data.addRule.children.splice(data.addRule.oldIndex, 1)
  811. children.splice(newIndex, 0, rule[0])
  812. }
  813. } else {
  814. const rule = methods.makeRule(ruleList[menu.name])
  815. children.splice(newIndex, 0, rule)
  816. }
  817. data.added = true
  818. // data.dragForm.api.refresh();
  819. },
  820. dragEnd(children, { newIndex, oldIndex }) {
  821. if (!data.added && !(data.moveRule === children && newIndex === oldIndex)) {
  822. const rule = data.moveRule.splice(oldIndex, 1)
  823. children.splice(newIndex, 0, rule[0])
  824. }
  825. data.moveRule = null
  826. data.addRule = null
  827. data.added = false
  828. // data.dragForm.api.refresh();
  829. },
  830. makeRule(config, _rule) {
  831. const rule = _rule || config.rule({ t })
  832. rule.config = { config }
  833. if (config.component) {
  834. rule.component = markRaw(config.component)
  835. }
  836. if (!rule.effect) rule.effect = {}
  837. rule.effect._fc = true
  838. rule._fc_drag_tag = config.name
  839. let drag
  840. if (config.drag) {
  841. rule.children.push(
  842. (drag = methods.makeDrag(config.drag, rule.type, methods.makeChildren([]), {
  843. end: (inject, evt) => methods.dragEnd(inject.self.children, evt),
  844. add: (inject, evt) => methods.dragAdd(inject.self.children, evt),
  845. start: (inject, evt) => methods.dragStart(inject.self.children, evt),
  846. unchoose: (inject, evt) => methods.dragUnchoose(inject.self.children, evt)
  847. }))
  848. )
  849. }
  850. if (config.children && !_rule) {
  851. for (let i = 0; i < (config.childrenLen || 1); i++) {
  852. const child = methods.makeRule(ruleList[config.children])
  853. ;(drag || rule).children.push(child)
  854. }
  855. }
  856. const dragMask = mask.value !== undefined ? mask.value !== false : config.mask !== false
  857. if (config.inside) {
  858. rule.children = methods.makeChildren([
  859. {
  860. type: 'DragTool',
  861. props: {
  862. dragBtn: config.dragBtn !== false,
  863. children: config.children,
  864. mask: dragMask
  865. },
  866. effect: {
  867. _fc_tool: true
  868. },
  869. inject: true,
  870. on: {
  871. delete: ({ self }) => {
  872. const parent = methods.getParent(self).parent
  873. parent.__fc__.rm()
  874. vm.emit('delete', parent)
  875. methods.clearActiveRule()
  876. },
  877. create: ({ self }) => {
  878. const top = methods.getParent(self)
  879. vm.emit('create', top.parent)
  880. top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, methods.makeRule(top.parent.config.config))
  881. },
  882. addChild: ({ self }) => {
  883. const top = methods.getParent(self)
  884. const config = top.parent.config.config
  885. const item = ruleList[config.children]
  886. if (!item) return
  887. ;(!config.drag ? top.parent : top.parent.children[0]).children[0].children.push(methods.makeRule(item))
  888. },
  889. copy: ({ self }) => {
  890. const top = methods.getParent(self)
  891. vm.emit('copy', top.parent)
  892. top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, designerForm.copyRule(top.parent))
  893. },
  894. active: ({ self }) => {
  895. const top = methods.getParent(self)
  896. vm.emit('active', top.parent)
  897. methods.toolActive(top.parent)
  898. }
  899. },
  900. children: rule.children
  901. }
  902. ])
  903. return rule
  904. } else {
  905. return {
  906. type: 'DragTool',
  907. props: {
  908. dragBtn: config.dragBtn !== false,
  909. children: config.children,
  910. mask: dragMask
  911. },
  912. effect: {
  913. _fc_tool: true
  914. },
  915. inject: true,
  916. on: {
  917. delete: ({ self }) => {
  918. vm.emit('delete', self.children[0])
  919. self.__fc__.rm()
  920. methods.clearActiveRule()
  921. },
  922. create: ({ self }) => {
  923. vm.emit('create', self.children[0])
  924. const top = methods.getParent(self)
  925. top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, methods.makeRule(self.children[0].config.config))
  926. },
  927. addChild: ({ self }) => {
  928. const config = self.children[0].config.config
  929. const item = ruleList[config.children]
  930. if (!item) return
  931. ;(!config.drag ? self : self.children[0]).children[0].children.push(methods.makeRule(item))
  932. },
  933. copy: ({ self }) => {
  934. vm.emit('copy', self.children[0])
  935. const top = methods.getParent(self)
  936. top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, designerForm.copyRule(top.parent))
  937. },
  938. active: ({ self }) => {
  939. vm.emit('active', self.children[0])
  940. methods.toolActive(self.children[0])
  941. }
  942. },
  943. children: methods.makeChildren([rule])
  944. }
  945. }
  946. },
  947. load() {
  948. let val
  949. if (data.type === 2) {
  950. val = data.value
  951. } else if (data.type === 0) {
  952. val = formCreate.toJson(data.value, 2)
  953. } else {
  954. val = JSON.stringify(data.value, null, 2)
  955. }
  956. nextTick(() => {
  957. data.editor = CodeMirror(document.getElementById('editor'), {
  958. lineNumbers: true,
  959. mode: data.type === 2 ? { name: 'vue' } : 'application/json',
  960. gutters: ['CodeMirror-lint-markers'],
  961. // lint: true,
  962. line: true,
  963. tabSize: 2,
  964. lineWrapping: true,
  965. value: val || ''
  966. })
  967. })
  968. },
  969. showJson() {
  970. data.state = true
  971. data.type = 0
  972. data.value = methods.getRule()
  973. let val = JSON.parse(JSON.stringify(data.value))
  974. vm.emit('export-json', val)
  975. },
  976. setJson() {
  977. data.state = true
  978. data.type = 3
  979. data.value = []
  980. },
  981. onOk() {
  982. if (data.err) return
  983. const json = data.editor.getValue()
  984. let val = JSON.parse(json)
  985. if (data.type === 3) {
  986. if (!Array.isArray(val)) {
  987. data.err = true
  988. return
  989. }
  990. methods.setRule(formCreate.parseJson(json))
  991. } else {
  992. if (!is.Object(val) || !val.form) {
  993. data.err = true
  994. return
  995. }
  996. methods.setOption(val)
  997. }
  998. data.state = false
  999. },
  1000. // 暂存
  1001. saveAsForm() {
  1002. data.value = methods.getRule()
  1003. let val = JSON.parse(JSON.stringify(data.value))
  1004. vm.emit('export-json', val)
  1005. },
  1006. // 初始化
  1007. initForm(json) {
  1008. // todo 这里需要针对json为空做处理
  1009. try {
  1010. methods.setRule(formCreate.parseJson(json))
  1011. } catch (e) {
  1012. console.log(e);
  1013. }
  1014. }
  1015. }
  1016. data.dragForm.rule = methods.makeDragRule(methods.makeChildren(data.children))
  1017. defineExpose({
  1018. initForm: methods.initForm
  1019. })
  1020. return {
  1021. editorRef,
  1022. ...toRefs(data),
  1023. ...methods,
  1024. dragHeight,
  1025. t
  1026. }
  1027. },
  1028. created() {
  1029. document.body.ondrop = e => {
  1030. e.preventDefault()
  1031. e.stopPropagation()
  1032. }
  1033. }
  1034. })
  1035. </script>
  1036. <style>
  1037. ._fc-designer {
  1038. height: 100%;
  1039. min-height: 500px;
  1040. overflow: hidden;
  1041. cursor: default;
  1042. position: relative;
  1043. }
  1044. ._fc-designer > .el-main {
  1045. position: absolute;
  1046. top: 0;
  1047. bottom: 0;
  1048. left: 0;
  1049. right: 0;
  1050. padding: 0px;
  1051. }
  1052. ._fc-m .form-create ._fc-l-item {
  1053. background: #2e73ff;
  1054. width: 100%;
  1055. height: 10px;
  1056. overflow: hidden;
  1057. transition: all 0.3s ease;
  1058. }
  1059. ._fc-l,
  1060. ._fc-m,
  1061. ._fc-r {
  1062. border-top: 1px solid #ececec;
  1063. box-sizing: border-box;
  1064. }
  1065. ._fc-l-group {
  1066. padding: 0 12px;
  1067. }
  1068. ._fc-l-title {
  1069. font-weight: 600;
  1070. font-size: 14px;
  1071. margin: 18px 0px 5px;
  1072. }
  1073. ._fc-l-item {
  1074. display: inline-block;
  1075. background: #fff;
  1076. color: #000;
  1077. min-width: 70px;
  1078. width: 33.33%;
  1079. height: 70px;
  1080. line-height: 1;
  1081. text-align: center;
  1082. transition: all 0.2s ease;
  1083. cursor: pointer;
  1084. }
  1085. ._fc-l-item i {
  1086. font-size: 21px;
  1087. display: inline-block;
  1088. }
  1089. ._fc-l-item ._fc-l-name {
  1090. font-size: 12px;
  1091. }
  1092. ._fc-l-item ._fc-l-icon {
  1093. padding: 10px 5px 12px;
  1094. }
  1095. ._fc-l-item:hover {
  1096. background: #2e73ff;
  1097. color: #fff;
  1098. }
  1099. ._fc-m-tools {
  1100. height: 40px;
  1101. align-items: center;
  1102. display: flex;
  1103. justify-content: flex-end;
  1104. border: 1px solid #ececec;
  1105. border-top: 0 none;
  1106. }
  1107. ._fc-m-tools button.el-button {
  1108. padding: 5px 14px;
  1109. display: flex;
  1110. align-items: center;
  1111. }
  1112. ._fc-m-tools .fc-icon {
  1113. font-size: 14px;
  1114. margin-right: 2px;
  1115. }
  1116. ._fc-r .el-tabs__nav-wrap::after {
  1117. height: 1px;
  1118. background-color: #ececec;
  1119. }
  1120. ._fc-r ._fc-r-tabs {
  1121. display: flex;
  1122. padding: 0;
  1123. border-bottom: 1px solid #ececec;
  1124. }
  1125. ._fc-r ._fc-r-tab {
  1126. height: 40px;
  1127. box-sizing: border-box;
  1128. line-height: 40px;
  1129. display: inline-block;
  1130. list-style: none;
  1131. font-size: 14px;
  1132. font-weight: 600;
  1133. color: #303133;
  1134. position: relative;
  1135. flex: 1;
  1136. text-align: center;
  1137. }
  1138. ._fc-r ._fc-r-tab.active {
  1139. color: #409eff;
  1140. border-bottom: 2px solid #409eff;
  1141. }
  1142. .drag-box {
  1143. min-height: 60px;
  1144. width: 100%;
  1145. }
  1146. ._fc-m-drag {
  1147. overflow: auto;
  1148. padding: 2px;
  1149. box-sizing: border-box;
  1150. }
  1151. ._fc-m-drag,
  1152. .draggable-drag {
  1153. background: #fff;
  1154. height: 100%;
  1155. position: relative;
  1156. }
  1157. ._fc-m-drag > form,
  1158. ._fc-m-drag > form > .el-row {
  1159. height: 100%;
  1160. }
  1161. </style>