Container.vue 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289
  1. <template>
  2. <el-container class="FormMaking fm-form" :class="{
  3. ['fm-'+formKey]: true,
  4. 'is-fullscreen': fullscreen
  5. }" :style="{'z-index': zIndex}">
  6. <el-main class="FormMaking-main">
  7. <el-container>
  8. <el-aside width="250px" class="widget-left-panel" ref="widgetLeftPanel">
  9. <el-tabs v-model="activeLeft" class="left-tabs" v-show="!leftHide">
  10. <el-tab-pane name="field" >
  11. <template #label>
  12. <el-tooltip :content="$t('fm.actions.components')" placement="bottom-start">
  13. <span>&nbsp;&nbsp;<i class="fm-iconfont icon-yuanshuju-zujianku"></i>&nbsp;&nbsp;</span>
  14. </el-tooltip>
  15. </template>
  16. <el-scrollbar>
  17. <div class="components-list">
  18. <el-collapse v-model="activeFields">
  19. <el-collapse-item name="layout" v-if="layoutFields.length" :title="$t('fm.components.layout.title')">
  20. <draggable tag="ul" :list="layoutComponents"
  21. :group="{ name:'people', pull:handlePull,put:false}"
  22. :animation="200"
  23. :sort="false"
  24. ghost-class="ghost"
  25. @end="handleMoveEnd"
  26. @start="handleMoveStart"
  27. :move="handleMove"
  28. item-key="name"
  29. >
  30. <template #item="{element}">
  31. <Transition>
  32. <li @click="handleField(element)"
  33. v-if="layoutFields.indexOf(element.type) >=0 "
  34. class="form-edit-widget-label no-put"
  35. >
  36. <a>
  37. <i class="icon fm-iconfont" :class="element.icon"></i>
  38. <span>{{element.name}}</span>
  39. </a>
  40. </li>
  41. </Transition>
  42. </template>
  43. </draggable>
  44. </el-collapse-item>
  45. <el-collapse-item name="collection" v-if="collectionFields.length" :title="$t('fm.components.collection.title')">
  46. <draggable tag="ul" :list="collectionComponents"
  47. :group="{ name:'people', pull:handlePull,put:false}"
  48. :animation="200"
  49. :sort="false"
  50. ghost-class="ghost"
  51. @end="handleMoveEnd"
  52. @start="handleMoveStart"
  53. :move="handleMove"
  54. item-key="name"
  55. >
  56. <template #item="{element}">
  57. <Transition>
  58. <li @click="handleField(element)" v-if="collectionFields.indexOf(element.type) >= 0" class="form-edit-widget-label"
  59. :class="{
  60. 'no-put': true,
  61. 'subform-put': element.type == 'table' || element.type == 'subform',
  62. 'dialog-put': element.type == 'dialog'
  63. }">
  64. <a>
  65. <i class="icon fm-iconfont" :class="element.icon"></i>
  66. <span>{{element.name}}</span>
  67. </a>
  68. </li>
  69. </Transition>
  70. </template>
  71. </draggable>
  72. </el-collapse-item>
  73. <el-collapse-item name="basic" v-if="basicFields.length" :title="$t('fm.components.basic.title')">
  74. <draggable tag="ul"
  75. :list="basicComponents"
  76. :group="{ name:'people', pull:handlePull,put:false}"
  77. :animation="200"
  78. :sort="false"
  79. ghost-class="ghost"
  80. @end="handleMoveEnd"
  81. @start="handleMoveStart"
  82. :move="handleMove"
  83. item-key="name"
  84. >
  85. <template #item="{element}">
  86. <Transition>
  87. <li @click="handleField(element)"
  88. v-if="basicFields.indexOf(element.type)>=0 && !(isVantMobile && (element.type == 'color' || element.type == 'transfer' || element.type == 'link'))"
  89. class="form-edit-widget-label"
  90. :class="{
  91. 'no-put': element.type == 'divider',
  92. 'is-disabled': isVantMobile && (element.type == 'color' || element.type == 'transfer' || element.type == 'link')
  93. }">
  94. <a>
  95. <i class="icon fm-iconfont" :class="element.icon"></i>
  96. <span>{{element.name}}</span>
  97. </a>
  98. </li>
  99. </Transition>
  100. </template>
  101. </draggable>
  102. </el-collapse-item>
  103. <el-collapse-item name="advance" v-if="advanceFields.length" :title="$t('fm.components.advance.title')">
  104. <draggable tag="ul" :list="advanceComponents"
  105. :group="{ name:'people', pull:handlePull,put:false}"
  106. :animation="200"
  107. :sort="false"
  108. ghost-class="ghost"
  109. @end="handleMoveEnd"
  110. @start="handleMoveStart"
  111. :move="handleMove"
  112. item-key="name"
  113. >
  114. <template #item="{element}">
  115. <Transition>
  116. <li @click="handleField(element)"
  117. v-if="advanceFields.indexOf(element.type) >= 0 && !(isVantMobile && (element.type == 'editor' || element.type == 'list'))"
  118. class="form-edit-widget-label"
  119. :class="{
  120. 'no-put': element.type == 'table' || element.type == 'subform' || element.type == 'dialog' || element.type == 'group',
  121. 'subform-put': element.type == 'table' || element.type == 'subform',
  122. 'dialog-put': element.type == 'dialog',
  123. 'is-new': element.type == null,
  124. 'is-disabled': isVantMobile && (element.type == 'editor' || element.type == 'list')
  125. }">
  126. <a>
  127. <i class="icon fm-iconfont" :class="element.icon"></i>
  128. <span>{{element.name}}</span>
  129. </a>
  130. </li>
  131. </Transition>
  132. </template>
  133. </draggable>
  134. </el-collapse-item>
  135. <el-collapse-item name="custom" v-if="customFields.length" :title="$t('fm.components.custom.title')">
  136. <draggable tag="ul" :list="customComponents"
  137. :group="{ name:'people', pull:handlePull,put:false}"
  138. :animation="200"
  139. :sort="false"
  140. ghost-class="ghost"
  141. @end="handleMoveEnd"
  142. @start="handleMoveStart"
  143. :move="handleMove"
  144. item-key="name"
  145. >
  146. <template #item="{element}">
  147. <Transition>
  148. <li @click="handleField(element)" class="form-edit-widget-label" >
  149. <a>
  150. <i class="icon fm-iconfont custom" :class="element.icon ? '' : 'icon-extend'">
  151. <span v-html="element.icon" v-if="element.icon"></span>
  152. </i>
  153. <span>{{element.name}}</span>
  154. </a>
  155. </li>
  156. </Transition>
  157. </template>
  158. </draggable>
  159. </el-collapse-item>
  160. </el-collapse>
  161. </div>
  162. </el-scrollbar>
  163. </el-tab-pane>
  164. <el-tab-pane name="outline">
  165. <template #label>
  166. <el-tooltip :content="$t('fm.actions.outline')" placement="bottom">
  167. <span>&nbsp;&nbsp;<i class="fm-iconfont icon-fuhao-dagangshu"></i>&nbsp;&nbsp;</span>
  168. </el-tooltip>
  169. </template>
  170. <outline ref="outlineRef" :show="activeLeft == 'outline'" :data="widgetForm.list" @select="onSelectWidget"></outline>
  171. </el-tab-pane>
  172. </el-tabs>
  173. <div class="container-left-arrow" @click="handleLeftToggle"></div>
  174. </el-aside>
  175. <el-container class="center-container" direction="vertical">
  176. <el-header class="btn-bar" style="height: 45px;">
  177. <div class="btn-bar-plat">
  178. <a :class="{'active': platform == 'pc'}" @click="handlePlatform('pc')"><i class="fm-iconfont icon-pc"></i></a>
  179. <a :class="{'active': platform == 'pad'}" @click="handlePlatform('pad')"><i class="fm-iconfont icon-pad"></i></a>
  180. <a :class="{'active': platform == 'mobile'}" @click="handlePlatform('mobile')"><i class="fm-iconfont icon-mobile"></i></a>
  181. </div>
  182. <div class="btn-diviler"></div>
  183. <div class="btn-bar-action" v-if="showDo">
  184. <el-tooltip :content="$t('fm.actions.undo')" placement="bottom">
  185. <a @click="handleUndo" :class="{'disabled': !undo}"><i class="fm-iconfont icon-007caozuo_chexiao"></i></a>
  186. </el-tooltip>
  187. <el-tooltip :content="$t('fm.actions.redo')" placement="bottom">
  188. <a @click="handleRedo" :class="{'disabled': !redo}"><i class="fm-iconfont icon-8zhongzuo"></i></a>
  189. </el-tooltip>
  190. </div>
  191. <div class="btn-diviler" v-if="showDo"></div>
  192. <div class="btn-bar-action">
  193. <el-tooltip :content="$t('fm.actions.fullScreen')" placement="bottom" v-if="!fullscreen">
  194. <a @click="handleFullScreen"><i class="fm-iconfont icon-quanping_o"></i></a>
  195. </el-tooltip>
  196. <el-tooltip :content="$t('fm.actions.exitFullScreen')" placement="bottom" v-else>
  197. <a @click="handleExitFullScreen"><i class="fm-iconfont icon-quxiaoquanping_o"></i></a>
  198. </el-tooltip>
  199. </div>
  200. <slot name="action">
  201. </slot>
  202. <el-button v-if="upload" link type="primary" size="default" @click="handleUpload"><i class="fm-iconfont icon-daoru" style="font-size: 18px; font-weight: 600; margin: 5px;" />{{$t('fm.actions.import')}}</el-button>
  203. <el-button v-if="preview" link type="primary" size="default" @click="handlePreview"><i class="fm-iconfont icon-icon_yulan" style="font-size: 18px; font-weight: 600; margin: 5px;" />{{$t('fm.actions.preview')}}</el-button>
  204. <el-button v-if="clearable" link type="primary" size="default" @click="handleClear"><i class="fm-iconfont icon-qingkong" style="font-size: 18px; font-weight: 600; margin: 5px;" />{{$t('fm.actions.clear')}}</el-button>
  205. <el-button v-if="generateJson" link type="primary" size="default" @click="handleGenerateJson"><i class="fm-iconfont icon-json1" style="font-size: 18px; font-weight: 600; margin: 5px;" />{{$t('fm.actions.json')}}</el-button>
  206. <el-button v-if="generateCode" link type="primary" size="default" @click="handleGenerateCode"><i class="fm-iconfont icon-daimakuai" style="font-size: 18px; font-weight: 600; margin: 5px;" />{{$t('fm.actions.code')}}</el-button>
  207. </el-header>
  208. <el-main :class="{'widget-empty': widgetForm.list.length == 0}">
  209. <template v-if="!resetJson && !isVantMobile">
  210. <antdv-widget-form v-if="widgetForm.config.ui == 'antd'">
  211. <widget-form ref="widgetForm" :data="widgetForm" v-model:select="widgetFormSelect" :platform="platform" :form-key="formKey"></widget-form>
  212. </antdv-widget-form>
  213. <widget-form v-else ref="widgetForm" :data="widgetForm" v-model:select="widgetFormSelect" :platform="platform" :form-key="formKey"></widget-form>
  214. </template>
  215. <vant-widget-form v-if="!resetJson && isVantMobile" ref="widgetForm" :data="widgetForm" v-model:select="widgetFormSelect" :platform="platform" :form-key="formKey"></vant-widget-form>
  216. </el-main>
  217. </el-container>
  218. <el-aside class="widget-config-container" ref="widgetConfigContainer">
  219. <el-container v-show="!rightHide">
  220. <el-header height="45px">
  221. <div class="config-tab" :class="{active: configTab=='widget'}" @click="handleConfigSelect('widget')">{{$t('fm.config.widget.title')}}</div>
  222. <div class="config-tab" :class="{active: configTab=='form'}" @click="handleConfigSelect('form')">{{$t('fm.config.form.title')}}</div>
  223. </el-header>
  224. <el-main class="config-content">
  225. <widget-config v-show="configTab=='widget'" ref="widgetConfig"
  226. :platform="platform"
  227. :sheets="styleSheetsArray"
  228. :datasources="dataSourceArray"
  229. :eventscripts="eventScriptArray"
  230. :data="widgetFormSelect"
  231. :key="formConfigKey"
  232. @on-event-add="handleEventAdd"
  233. @on-event-edit="handleEventEdit"
  234. @on-event-remove="handleEventRemove"
  235. @on-datasource-edit="handleDatasourceEdit"
  236. @on-class-edit="handleClassEdit"
  237. :form-key="formKey"
  238. :field-models="fieldModels"
  239. >
  240. <template #widgetconfig="{type, customProps, data}">
  241. <slot name="widgetconfig" :type="type" :customProps="customProps" :data="data"></slot>
  242. </template>
  243. </widget-config>
  244. <form-config v-show="configTab=='form'" ref="formConfig"
  245. :sheets="styleSheetsArray"
  246. :data="widgetForm.config"
  247. @on-style-update="onStyleUpdate"
  248. @on-datasource-update="onDataSourceUpdate"
  249. @on-eventscript-update="onEventScriptUpdate"
  250. @on-eventscript-confirm="onEventScriptConfirm"
  251. :form-key="formKey"
  252. ></form-config>
  253. </el-main>
  254. </el-container>
  255. <div class="container-right-arrow" @click="handleRightToggle"></div>
  256. </el-aside>
  257. <preview-dialog ref="previewDialog" @get-data-success="preivewGetData"></preview-dialog>
  258. <import-json-dialog ref="importJsonDialog" @load-json="handleLoadJson"></import-json-dialog>
  259. <cus-dialog
  260. :visible="jsonVisible"
  261. @on-close="jsonVisible = false"
  262. ref="jsonPreview"
  263. width="800px"
  264. :title="jsonTitle"
  265. >
  266. <code-editor height="400px" mode="json" v-model="jsonTemplate" wrap></code-editor>
  267. <template #action>
  268. <el-button type="primary" class="json-btn" :data-clipboard-text="jsonCopyValue">{{$t('fm.actions.copyData')}}</el-button>
  269. <el-button type="primary" @click="handleExportJSON">{{$t('fm.actions.export')}}</el-button>
  270. </template>
  271. </cus-dialog>
  272. <cus-dialog
  273. :visible="codeVisible"
  274. @on-close="codeVisible = false"
  275. ref="codePreview"
  276. width="800px"
  277. :title="$t('fm.actions.code')"
  278. >
  279. <el-tabs type="border-card" style="box-shadow: none;" v-model="codeActiveName">
  280. <el-tab-pane label="Vue Component" name="vue">
  281. <code-editor height="450px" mode="html" v-model="vueTemplate"></code-editor>
  282. </el-tab-pane>
  283. <el-tab-pane label="HTML" name="html">
  284. <code-editor height="450px" mode="html" v-model="htmlTemplate"></code-editor>
  285. </el-tab-pane>
  286. </el-tabs>
  287. <template #action>
  288. <el-button type="primary" class="code-btn" :data-clipboard-text="codeCopyValue">{{$t('fm.actions.copyData')}}</el-button>
  289. <el-button type="primary" @click="handleExport">{{$t('fm.actions.export')}}</el-button>
  290. </template>
  291. </cus-dialog>
  292. </el-container>
  293. </el-main>
  294. <el-footer height="30px" style="font-weight: 600;"><a target="_blank" href="https://form.making.link">Powered by FormMaking {{version ? `@ ${version}` : ''}}</a></el-footer>
  295. </el-container>
  296. </template>
  297. <script>
  298. import Draggable from 'vuedraggable/src/vuedraggable'
  299. import WidgetConfig from './WidgetConfig.vue'
  300. import FormConfig from './FormConfig.vue'
  301. import WidgetForm from './WidgetForm.vue'
  302. import VantWidgetForm from './VantWidget/WidgetForm.vue'
  303. import AntdvWidgetForm from './AntdvWidget/WidgetForm.vue'
  304. import CusDialog from './CusDialog.vue'
  305. import ClipboardJS from 'clipboard'
  306. import CodeEditor from './CodeEditor/index.vue'
  307. import {basicComponents, layoutComponents, advanceComponents, collectionComponents} from './componentsConfig.js'
  308. import {updateStyleSheets, splitStyleSheets, splitSheetName, addClass, removeClass} from '../util/index.js'
  309. import { EventBus } from '../util/event-bus.js'
  310. import generateCode from './generateCode.js'
  311. import historyManager from '../util/history-manager.js'
  312. import _ from 'lodash'
  313. import { UpgradeData } from '../util/version-upgrade'
  314. import PreviewDialog from './PreviewDialog.vue'
  315. import ImportJsonDialog from './ImportJson/dialog.vue'
  316. import Outline from './Outline.vue'
  317. import { findModelNodeString } from '../util/find-node.js'
  318. import { getModels } from '../util/model-outline.js'
  319. import { ElMessage } from '../util/message.js'
  320. import { defaultDataSource } from './defaultDataSource.js'
  321. export default {
  322. name: 'fm-making-form',
  323. components: {
  324. Draggable,
  325. WidgetConfig,
  326. FormConfig,
  327. WidgetForm,
  328. VantWidgetForm,
  329. CusDialog,
  330. CodeEditor,
  331. PreviewDialog,
  332. ImportJsonDialog,
  333. Outline,
  334. AntdvWidgetForm
  335. },
  336. props: {
  337. preview: {
  338. type: Boolean,
  339. default: false
  340. },
  341. generateCode: {
  342. type: Boolean,
  343. default: false
  344. },
  345. generateJson: {
  346. type: Boolean,
  347. default: false
  348. },
  349. upload: {
  350. type: Boolean,
  351. default: false
  352. },
  353. clearable: {
  354. type: Boolean,
  355. default: false
  356. },
  357. basicFields: {
  358. type: Array,
  359. default: () => ['input', 'textarea', 'number', 'radio', 'checkbox', 'time', 'date', 'rate', 'color', 'select', 'switch', 'slider', 'text', 'html', 'button', 'link', 'cascader', 'treeselect', 'deptCascader', 'steps', 'transfer', 'pagination']
  360. },
  361. advanceFields: {
  362. type: Array,
  363. default: () => ['blank', 'component', 'fileupload', 'imgupload', 'editor', 'list']
  364. },
  365. layoutFields: {
  366. type: Array,
  367. default: () => ['grid', 'report', 'tabs', 'collapse', 'inline', 'card', 'divider', 'alert']
  368. },
  369. collectionFields: {
  370. type: Array,
  371. default: () => ['table', 'subform', 'dialog', 'group']
  372. },
  373. customFields: {
  374. type: Array,
  375. default: () => []
  376. },
  377. globalConfig: {
  378. type: Object,
  379. default: () => ({})
  380. },
  381. fieldConfig: {
  382. type: Array,
  383. default: () => []
  384. },
  385. name: {
  386. type: String,
  387. default: ''
  388. },
  389. cache: {
  390. type: Boolean,
  391. default: false
  392. },
  393. jsonTemplates: {
  394. type: Array,
  395. default: () => []
  396. },
  397. initFromTemplate: {
  398. type :Boolean,
  399. default: false
  400. },
  401. fieldModels: {
  402. type: Array,
  403. default: () => []
  404. },
  405. panel: {
  406. type: String,
  407. default: 'field'
  408. },
  409. zIndex: {
  410. type: Number,
  411. default: 2000
  412. },
  413. useAntdForm: {
  414. type: Boolean,
  415. default: false
  416. },
  417. useVantForm: {
  418. type: Boolean,
  419. default: false
  420. }
  421. },
  422. emits: ['ready', 'preview'],
  423. computed: {
  424. isVantMobile () {
  425. return this.widgetForm.config.mobileVant && this.platform === 'mobile'
  426. }
  427. },
  428. data () {
  429. return {
  430. version: window.FormMaking_OPTIONS['version'],
  431. basicComponents,
  432. layoutComponents,
  433. advanceComponents,
  434. collectionComponents,
  435. customComponents: [],
  436. resetJson: false,
  437. widgetForm: {
  438. list: [],
  439. config: {
  440. labelWidth: 100,
  441. labelPosition: 'left',
  442. size: 'default',
  443. customClass: '',
  444. ui: 'element',
  445. layout: 'horizontal',
  446. width: '100%',
  447. hideLabel: false,
  448. hideErrorMessage: false
  449. },
  450. },
  451. configTab: 'form',
  452. widgetFormSelect: null,
  453. previewVisible: false,
  454. jsonVisible: false,
  455. codeVisible: false,
  456. uploadVisible: false,
  457. blank: '',
  458. htmlTemplate: '',
  459. jsonTemplate: '',
  460. vueTemplate: '',
  461. uploadEditor: null,
  462. jsonCopyValue: '',
  463. jsonClipboard: null,
  464. codeCopyValue: '',
  465. codeClipboard: null,
  466. codeActiveName: 'vue',
  467. undo: false,
  468. redo: false,
  469. formKey: Math.random().toString(36).slice(-8),
  470. formConfigKey: Math.random().toString(36).slice(-8),
  471. styleSheetsArray: [],
  472. dataSourceArray: [],
  473. eventScriptArray: [],
  474. platform: 'pc',
  475. activeFields: ['basic', 'advance', 'layout', 'custom', 'collection'],
  476. activeLeft: this.panel,
  477. isScrollTo: true,
  478. jsonTitle: this.$t('fm.actions.json'),
  479. modelNode: '',
  480. rightHide: false,
  481. leftHide: false,
  482. fullscreen: false,
  483. showDo: false,
  484. dragging: false
  485. }
  486. },
  487. created () {
  488. this._loadComponents()
  489. },
  490. provide () {
  491. return {
  492. 'changeConfigTab': this.changeConfigTab,
  493. 'getModelNode': this.getModelNode,
  494. 'isMobile': this.isMobile,
  495. 'getFormModels': this.getFormModels,
  496. 'getFormFields': this.getFormFields,
  497. 'getDataSourceArray': this.getDataSourceArray,
  498. 'useAntdForm': this.useAntdForm,
  499. 'setDragging': this.setDragging,
  500. 'getDragging': this.getDragging,
  501. 'useVantForm': this.useVantForm,
  502. 'getVantMobile': () => this.isVantMobile
  503. }
  504. },
  505. mounted () {
  506. const _this = this
  507. // 添加表单默认事件
  508. this.widgetForm.config.eventScript = [
  509. {key: 'mounted', name: 'mounted', func: ''},
  510. {key: 'refresh', name: 'refresh', func: ''},
  511. {key: 'onFormChange', name: 'onFormChange', type: 'rule'}
  512. ]
  513. // 加载全局配置项
  514. this.widgetForm.config = {
  515. ...this.widgetForm.config,
  516. ...this.globalConfig
  517. }
  518. // 注入默认数据源(部门树 getDeptTree 等),与 vue-form-making 保持一致
  519. // 合并而非直接覆盖,避免清掉用户已保存的数据源
  520. this.widgetForm.config.dataSource = this.mergeDefaultDataSource(this.widgetForm.config.dataSource)
  521. this.platform = this.widgetForm.config.platform || 'pc'
  522. this.initConfig()
  523. this.cache && this.setJSON(localStorage.getItem('fmjson'+this.name) ?? this.widgetForm)
  524. this.$emit('ready')
  525. window.onbeforeunload = (e) => {
  526. this.saveJsonCache()
  527. }
  528. // 从模板导入
  529. if (this.initFromTemplate) {
  530. this.handleUpload()
  531. }
  532. historyManager.clear().then(() => {
  533. this.showDo = true
  534. EventBus.$on('on-history-add-' + this.formKey, () => {
  535. historyManager.add(this.widgetForm, (this.widgetFormSelect && this.widgetFormSelect.key) ? this.widgetFormSelect.key : '').then(() => {
  536. _this.undo = true
  537. _this.redo = false
  538. })
  539. this.saveJsonCache()
  540. })
  541. })
  542. },
  543. beforeUnmount () {
  544. EventBus.$off('on-history-add-' + this.formKey)
  545. this.saveJsonCache()
  546. },
  547. methods: {
  548. handlePull (a, b, c) {
  549. if (c.className.split(' ').indexOf('is-disabled') >= 0) {
  550. return false
  551. }
  552. return 'clone'
  553. },
  554. setDragging (dragging) {
  555. this.dragging = dragging
  556. },
  557. getDragging () {
  558. return this.dragging
  559. },
  560. generatePreviewQrcode (url) {
  561. this.$refs.previewDialog.generateQrcode(url)
  562. },
  563. handleRightToggle () {
  564. if (this.rightHide) {
  565. removeClass(this.$refs['widgetConfigContainer'].$el, 'hide-status')
  566. this.rightHide = false
  567. } else {
  568. addClass(this.$refs['widgetConfigContainer'].$el, 'hide-status')
  569. this.rightHide = true
  570. }
  571. },
  572. handleLeftToggle () {
  573. if (this.leftHide) {
  574. removeClass(this.$refs['widgetLeftPanel'].$el, 'hide-status')
  575. this.leftHide = false
  576. } else {
  577. addClass(this.$refs['widgetLeftPanel'].$el, 'hide-status')
  578. this.leftHide = true
  579. }
  580. },
  581. saveJsonCache () {
  582. this.cache && localStorage.setItem('fmjson'+this.name, JSON.stringify(this.widgetForm))
  583. },
  584. removeJsonCache () {
  585. localStorage.setItem('fmjson'+this.name, JSON.stringify(this.widgetForm))
  586. },
  587. initConfig () {
  588. this.platform = this.widgetForm.config.platform || 'pc'
  589. this.onStyleUpdate(splitStyleSheets(this.widgetForm.config.styleSheets || ''))
  590. this.onDataSourceUpdate(this.widgetForm.config.dataSource || [])
  591. this.onEventScriptUpdate(this.widgetForm.config.eventScript || [])
  592. this.formConfigKey = Math.random().toString(36).slice(-8)
  593. },
  594. handleGoGithub () {
  595. window.location.href = 'https://github.com/GavinZhuLei/vue-form-making'
  596. },
  597. handleConfigSelect (value) {
  598. this.configTab = value
  599. },
  600. handleMoveEnd (evt) {
  601. },
  602. handleMoveStart ({oldIndex}) {
  603. },
  604. handleMove () {
  605. return true
  606. },
  607. handlePreview () {
  608. this.$emit('preview', _.cloneDeep(this.widgetForm))
  609. this.$refs.previewDialog.preview(_.cloneDeep(this.widgetForm), this.platform)
  610. },
  611. preivewGetData (data) {
  612. this.jsonTitle = this.$t('fm.actions.getData')
  613. this.jsonVisible = true
  614. this.jsonTemplate = data
  615. this.$nextTick(() => {
  616. this.jsonClipboard = new ClipboardJS(document.getElementsByClassName('json-btn')[0])
  617. this.jsonClipboard.on('success', (e) => {
  618. ElMessage({
  619. message: this.$t('fm.message.copySuccess'),
  620. type: 'success'
  621. }, this)
  622. })
  623. this.jsonCopyValue = JSON.stringify(data)
  624. })
  625. },
  626. handleGenerateJson () {
  627. this.jsonTitle = this.$t('fm.actions.json')
  628. this.jsonVisible = true
  629. this.jsonTemplate = this.widgetForm
  630. this.$nextTick(() => {
  631. this.jsonClipboard = new ClipboardJS(document.getElementsByClassName('json-btn')[0])
  632. this.jsonClipboard.on('success', (e) => {
  633. ElMessage({
  634. message: this.$t('fm.message.copySuccess'),
  635. type: 'success'
  636. }, this)
  637. e.clearSelection()
  638. })
  639. this.jsonCopyValue = JSON.stringify(this.widgetForm)
  640. })
  641. },
  642. handleGenerateCode () {
  643. this.codeVisible = true
  644. this.htmlTemplate = generateCode(JSON.stringify(this.widgetForm), 'html', this.widgetForm.config.ui, this.isVantMobile)
  645. this.vueTemplate = generateCode(JSON.stringify(this.widgetForm), 'vue', this.widgetForm.config.ui, this.isVantMobile)
  646. this.$nextTick(() => {
  647. this.codeClipboard = new ClipboardJS(document.getElementsByClassName('code-btn')[0])
  648. this.codeClipboard.on('success', (e) => {
  649. ElMessage({
  650. message: this.$t('fm.message.copySuccess'),
  651. type: 'success'
  652. }, this)
  653. })
  654. this.codeCopyValue = this.codeActiveName == 'vue' ? this.vueTemplate : this.htmlTemplate
  655. })
  656. },
  657. handleUpload () {
  658. this.$refs.importJsonDialog.open(this.jsonTemplates)
  659. },
  660. handleLoadJson (json) {
  661. try {
  662. this.setJSON(json)
  663. } catch (e) {
  664. ElMessage({
  665. message: e.message,
  666. type: 'error'
  667. }, this)
  668. }
  669. },
  670. handleClear () {
  671. this.widgetForm = {
  672. ...this.widgetForm,
  673. list: [],
  674. }
  675. this.widgetFormSelect = {}
  676. this.$nextTick(() => {
  677. EventBus.$emit('on-history-add-' + this.formKey)
  678. })
  679. },
  680. clear () {
  681. this.handleClear()
  682. },
  683. getJSON () {
  684. return JSON.stringify(this.widgetForm)
  685. },
  686. getHtml (type = 'html', ui = 'element') {
  687. return generateCode(JSON.stringify(this.widgetForm), type, ui, this.isVantMobile)
  688. },
  689. setJSON (json) {
  690. if (typeof json === 'string') {
  691. json = JSON.parse(json)
  692. }
  693. this.widgetForm = _.cloneDeep({
  694. ...json,
  695. list: json.list ?? json.list.map(item => UpgradeData(item))
  696. })
  697. // 缓存/导入恢复后,补齐默认数据源(部门树 getDeptTree 等),
  698. // 否则被覆盖为空的 config.dataSource 会导致数据源下拉为空、远程数据拉取失败
  699. if (!this.widgetForm.config) this.widgetForm.config = {}
  700. this.widgetForm.config.dataSource = this.mergeDefaultDataSource(this.widgetForm.config.dataSource)
  701. if (this.widgetForm.config?.eventScript) {
  702. this.widgetForm.config.eventScript.findIndex(item => item.key === 'onFormChange') < 0
  703. && this.widgetForm.config.eventScript.unshift({key: 'onFormChange', name: 'onFormChange', type: 'rule'})
  704. this.widgetForm.config.eventScript.findIndex(item => item.key === 'refresh') < 0
  705. && this.widgetForm.config.eventScript.unshift({key: 'refresh', name: 'refresh', func: ''})
  706. this.widgetForm.config.eventScript.findIndex(item => item.key === 'mounted') < 0
  707. && this.widgetForm.config.eventScript.unshift({key: 'mounted', name: 'mounted', func: ''})
  708. }
  709. if (json.list.length> 0) {
  710. this.widgetFormSelect = this.widgetForm.list[0]
  711. } else {
  712. this.widgetFormSelect = {}
  713. }
  714. this.initConfig()
  715. this.$nextTick(() => { EventBus.$emit('on-history-add-' + this.formKey) })
  716. },
  717. handleInput (val) {
  718. this.blank = val
  719. },
  720. handleField (item) {
  721. EventBus.$emit('on-field-add-' + this.formKey, item)
  722. },
  723. handleUndo () {
  724. if (this.undo) {
  725. historyManager.updateLatest(this.widgetForm, (this.widgetFormSelect && this.widgetFormSelect.key) ? this.widgetFormSelect.key : '').then(() => {
  726. historyManager.undo().then((data) => {
  727. this.widgetForm = {...data.data}
  728. this.widgetFormSelect = this._findWidgetItem(this.widgetForm.list, data.key)
  729. this.undo = data.undo
  730. this.redo = data.redo
  731. this.initConfig()
  732. })
  733. })
  734. }
  735. },
  736. handleRedo () {
  737. if (this.redo) {
  738. historyManager.redo().then((data) => {
  739. this.widgetForm = {...data.data}
  740. this.widgetFormSelect = this._findWidgetItem(this.widgetForm.list, data.key)
  741. this.undo = data.undo
  742. this.redo = data.redo
  743. this.initConfig()
  744. })
  745. }
  746. },
  747. handleFullScreen() {
  748. this.fullscreen = true
  749. },
  750. handleExitFullScreen () {
  751. this.fullscreen = false
  752. },
  753. _findWidgetItem (list, key, type = 'key') {
  754. const index = list.findIndex(item => item[type] == key)
  755. if (index >= 0) {
  756. return list[index]
  757. } else {
  758. for (let m = 0; m < list.length; m++) {
  759. const item = list[m]
  760. if (item.type === 'grid') {
  761. let findItem = this._findWidgetItem(item.columns, key, type)
  762. if (findItem.key) {
  763. return findItem
  764. }
  765. for (let i = 0; i < item.columns.length; i++) {
  766. let findItem = this._findWidgetItem(item.columns[i].list, key, type)
  767. if (findItem.key) {
  768. return findItem
  769. }
  770. }
  771. }
  772. if (item.type === 'table') {
  773. let findItem = this._findWidgetItem(item.tableColumns, key, type)
  774. if (findItem.key) {
  775. return findItem
  776. }
  777. }
  778. if (item.type === 'subform') {
  779. let findItem = this._findWidgetItem(item.list, key, type)
  780. if (findItem.key) {
  781. return findItem
  782. }
  783. }
  784. if (item.type === 'tabs') {
  785. for (let i = 0; i < item.tabs.length; i++) {
  786. let findItem = this._findWidgetItem(item.tabs[i].list, key, type)
  787. if (findItem.key) {
  788. return findItem
  789. }
  790. }
  791. }
  792. if (item.type === 'collapse') {
  793. for (let i = 0; i < item.tabs.length; i++) {
  794. let findItem = this._findWidgetItem(item.tabs[i].list, key, type)
  795. if (findItem.key) {
  796. return findItem
  797. }
  798. }
  799. }
  800. if (item.type === 'report') {
  801. for (let r = 0; r < item.rows.length; r++) {
  802. let findItem = this._findWidgetItem(item.rows[r].columns, key, type)
  803. if (findItem.key) {
  804. return findItem
  805. }
  806. for (let c = 0; c < item.rows[r].columns.length; c++) {
  807. let findItem = this._findWidgetItem(item.rows[r].columns[c].list, key, type)
  808. if (findItem.key) {
  809. return findItem
  810. }
  811. }
  812. }
  813. }
  814. if (item.type === 'inline') {
  815. let findItem = this._findWidgetItem(item.list, key, type)
  816. if (findItem.key) {
  817. return findItem
  818. }
  819. }
  820. if (item.type === 'dialog') {
  821. let findItem = this._findWidgetItem(item.list, key, type)
  822. if (findItem.key) {
  823. return findItem
  824. }
  825. }
  826. if (item.type === 'card') {
  827. let findItem = this._findWidgetItem(item.list, key, type)
  828. if (findItem.key) {
  829. return findItem
  830. }
  831. }
  832. if (item.type === 'group') {
  833. let findItem = this._findWidgetItem(item.list, key, type)
  834. if (findItem.key) {
  835. return findItem
  836. }
  837. }
  838. }
  839. return {}
  840. }
  841. },
  842. _loadComponents () {
  843. this.basicComponents = this.basicComponents.map(item => {
  844. return {
  845. ...item,
  846. name: this.$t(`fm.components.fields.${item.type}`),
  847. options: (() => {
  848. let newField = this.fieldConfig.find(o => o.type == item.type)
  849. if (newField) {
  850. return {...item.options, ...newField.options}
  851. } else {
  852. return {...item.options}
  853. }
  854. })()
  855. }
  856. })
  857. this.advanceComponents = this.advanceComponents.map(item => {
  858. return {
  859. ...item,
  860. name: this.$t(`fm.components.fields.${item.type}`),
  861. options: (() => {
  862. let newField = this.fieldConfig.find(o => o.type == item.type)
  863. if (newField) {
  864. return {...item.options, ...newField.options}
  865. } else {
  866. return {...item.options}
  867. }
  868. })()
  869. }
  870. })
  871. this.layoutComponents = this.layoutComponents.map(item => {
  872. return {
  873. ...item,
  874. name: this.$t(`fm.components.fields.${item.type}`),
  875. options: (() => {
  876. let newField = this.fieldConfig.find(o => o.type == item.type)
  877. if (newField) {
  878. return {...item.options, ...newField.options}
  879. } else {
  880. return {...item.options}
  881. }
  882. })()
  883. }
  884. })
  885. this.collectionComponents = this.collectionComponents.map(item => {
  886. return {
  887. ...item,
  888. name: this.$t(`fm.components.fields.${item.type}`),
  889. options: (() => {
  890. let newField = this.fieldConfig.find(o => o.type == item.type)
  891. if (newField) {
  892. return {...item.options, ...newField.options}
  893. } else {
  894. return {...item.options}
  895. }
  896. })()
  897. }
  898. })
  899. this.customComponents = this.customFields.map(item => {
  900. return {
  901. ...item,
  902. type: 'custom',
  903. options: (() => {
  904. let newField = this.fieldConfig.find(o => o.type == item.type)
  905. if (newField) {
  906. return {...item.options, ...newField.options}
  907. } else {
  908. return {...item.options}
  909. }
  910. })()
  911. }
  912. })
  913. },
  914. onStyleUpdate (sheets) {
  915. let head = '.fm-' + this.formKey + ' '
  916. updateStyleSheets(sheets, head)
  917. this.styleSheetsArray = splitSheetName(sheets)
  918. },
  919. onDataSourceUpdate (dataSource) {
  920. this.dataSourceArray = dataSource.map(item => ({
  921. value: item.key,
  922. label: item.name,
  923. args: item.args ? Object.fromEntries(item.args.map(o => [o, ''])) : {}
  924. }))
  925. },
  926. // 将默认数据源(getDeptTree 等)合并进已保存的数据源列表,
  927. // 保留用户自定义项,仅补齐缺失的默认项(深拷贝避免污染模块级常量)
  928. mergeDefaultDataSource (existing) {
  929. const list = Array.isArray(existing) ? existing : []
  930. const existingKeys = list.map(item => item && item.key)
  931. const appends = defaultDataSource
  932. .filter(item => !existingKeys.includes(item.key))
  933. .map(item => ({ ...item }))
  934. return [...list, ...appends]
  935. },
  936. onEventScriptUpdate (eventScript) {
  937. this.eventScriptArray = eventScript.map(item => ({
  938. value: item.key,
  939. label: item.name
  940. }))
  941. },
  942. onEventScriptConfirm (eventObj) {
  943. this.$refs.widgetConfig.setEvent(eventObj)
  944. this.$nextTick(() => {
  945. EventBus.$emit('on-history-add-' + this.formKey)
  946. })
  947. },
  948. handlePlatform (platform) {
  949. this.widgetForm.config.platform = this.platform = platform
  950. },
  951. handleExport () {
  952. const fileName = (new Date().getTime()) + '.' + this.codeActiveName
  953. const fileData = this.codeActiveName == 'vue' ? this.vueTemplate : this.htmlTemplate
  954. this._exportFile(fileData, fileName)
  955. },
  956. handleExportJSON () {
  957. this._exportFile(JSON.stringify(this.jsonTemplate), (new Date().getTime()) + '.json')
  958. },
  959. handleClassEdit () {
  960. this.$refs.formConfig.editClass()
  961. },
  962. handleDatasourceEdit (datasource) {
  963. this.$refs.formConfig.editDatasource(datasource)
  964. },
  965. handleEventAdd (name) {
  966. this.$refs.formConfig.editScript(name)
  967. },
  968. handleEventEdit ({eventName, functionKey}) {
  969. this.$refs.formConfig.editScript(eventName, functionKey)
  970. },
  971. handleEventRemove (eventName) {
  972. this.widgetFormSelect.events[eventName] = ''
  973. },
  974. _exportFile (data, fileName) {
  975. let blob = new Blob([data], {
  976. type: 'application/octet-stream'
  977. })
  978. if(navigator.msSaveOrOpenBlob ){
  979. navigator.msSaveOrOpenBlob(blob, fileName);
  980. }else{
  981. // Create download link element
  982. let downloadLink = document.createElement("a");
  983. // Create a link to the file
  984. downloadLink.href = window.URL.createObjectURL(blob);
  985. // Setting the file name
  986. downloadLink.download = fileName;
  987. downloadLink.style.display = 'none'
  988. document.body.appendChild(downloadLink);
  989. //triggering the function
  990. downloadLink.click();
  991. document.body.removeChild(downloadLink);
  992. }
  993. },
  994. setSelect (field) {
  995. let selectWidget = this._findWidgetItem(this.widgetForm.list, field, 'model')
  996. if (selectWidget) {
  997. this.widgetFormSelect = selectWidget
  998. }
  999. },
  1000. changeConfigTab (tab) {
  1001. this.configTab = tab
  1002. },
  1003. onSelectWidget (key) {
  1004. let selectWidget = this._findWidgetItem(this.widgetForm.list, key, 'key')
  1005. if (selectWidget) {
  1006. this.isScrollTo = false
  1007. this.widgetFormSelect = selectWidget
  1008. }
  1009. setTimeout(() => {
  1010. this.$refs.widgetForm.scrollTo()
  1011. }, 200)
  1012. },
  1013. getModelNode () {
  1014. return this.modelNode
  1015. },
  1016. isMobile () {
  1017. return this.platform === 'mobile'
  1018. },
  1019. getFormModels () {
  1020. return getModels(this.widgetForm.list)
  1021. },
  1022. getFormFields () {
  1023. return this._getFormFields(this.widgetForm.list)
  1024. },
  1025. _getFormFields (list, group = '', parent = '') {
  1026. let currentNode = []
  1027. for (let i = 0; i < list.length; i++) {
  1028. if (!list[i].type) continue
  1029. const curLabel = parent
  1030. ? [parent, `<${this.$t('fm.components.fields.' + list[i].type)}>${list[i].model ?? ''}`].join(' / ')
  1031. : `<${this.$t('fm.components.fields.' + list[i].type)}>${list[i].model ?? ''}`
  1032. currentNode.push({
  1033. id: list[i].model ? ( group ? group + '.' + list[i].model : list[i].model ) : list[i].key,
  1034. label: curLabel,
  1035. icon: list[i].icon,
  1036. type: list[i].type,
  1037. model: list[i].model,
  1038. children: [],
  1039. dataBind: list[i].options?.dataBind,
  1040. options: list[i].options,
  1041. disabled: (list[i].model ? false : true) || ['grid', 'report', 'tabs', 'collapse', 'inline', 'card', 'divider', 'alert'].includes(list[i].type),
  1042. hideDisabled: list[i].model ? false : true,
  1043. setdataDisabled: (list[i].model ? false : true) || ['grid', 'report', 'tabs', 'collapse', 'inline', 'card', 'divider', 'alert', 'button', 'link'].includes(list[i].type),
  1044. remoteOptionDisabled: !list[i].options.remote,
  1045. dynamicValueDisabled: !list[i].options.isDynamicValue,
  1046. dialogDisabled: list[i].type !== 'dialog',
  1047. optionDisabled: !(list[i].type == 'steps' || list[i].type == 'transfer' || Object.keys(list[i].options).indexOf('options')>=0 )
  1048. })
  1049. if (list[i].type == 'grid') {
  1050. currentNode[i].children = this._getFormFields(list[i].columns, group, curLabel)
  1051. }
  1052. if (list[i].type == 'col') {
  1053. currentNode[i].children = this._getFormFields(list[i].list, group, curLabel)
  1054. }
  1055. if (list[i].type == 'report') {
  1056. let reportList = []
  1057. for (let r = 0; r < list[i].rows.length; r++) {
  1058. for (let c = 0; c < list[i].rows[r].columns.length; c++) {
  1059. let td = list[i].rows[r].columns[c]
  1060. if (!td.options.invisible) {
  1061. reportList.push(td)
  1062. }
  1063. }
  1064. }
  1065. currentNode[i].children = this._getFormFields(reportList, group, curLabel)
  1066. }
  1067. if (list[i].type == 'td') {
  1068. currentNode[i].children = this._getFormFields(list[i].list, group, curLabel)
  1069. }
  1070. if (list[i].type == 'tabs') {
  1071. for (let t = 0; t < list[i].tabs.length; t++) {
  1072. currentNode[i].children.push({
  1073. label: list[i].tabs[t].label,
  1074. children: [],
  1075. disabled: true,
  1076. id: Math.random().toString(36).slice(-8),
  1077. setdataDisabled: true,
  1078. remoteOptionDisabled: true,
  1079. dialogDisabled: true
  1080. })
  1081. currentNode[i].children[t].children = this._getFormFields(list[i].tabs[t].list, group, curLabel)
  1082. }
  1083. }
  1084. if (list[i].type == 'collapse') {
  1085. for (let t = 0; t < list[i].tabs.length; t++) {
  1086. currentNode[i].children.push({
  1087. label: list[i].tabs[t].title,
  1088. children: [],
  1089. disabled: true,
  1090. id: Math.random().toString(36).slice(-8),
  1091. setdataDisabled: true,
  1092. remoteOptionDisabled: true,
  1093. dialogDisabled: true
  1094. })
  1095. currentNode[i].children[t].children = this._getFormFields(list[i].tabs[t].list, group, curLabel)
  1096. }
  1097. }
  1098. if (list[i].type == 'inline') {
  1099. currentNode[i].children = this._getFormFields(list[i].list, group, curLabel)
  1100. }
  1101. if (list[i].type == 'table') {
  1102. let curGroup = group ? group + '.' + list[i].model : list[i].model
  1103. currentNode[i].children = this._getFormFields(list[i].tableColumns, curGroup, curLabel)
  1104. }
  1105. if (list[i].type == 'subform') {
  1106. let curGroup = group ? group + '.' + list[i].model : list[i].model
  1107. currentNode[i].children = this._getFormFields(list[i].list, curGroup, curLabel)
  1108. }
  1109. if (list[i].type == 'dialog') {
  1110. let curGroup = group ? group + '.' + list[i].model : list[i].model
  1111. currentNode[i].children = this._getFormFields(list[i].list, curGroup, curLabel)
  1112. }
  1113. if (list[i].type == 'card') {
  1114. currentNode[i].children = this._getFormFields(list[i].list, group, curLabel)
  1115. }
  1116. if (list[i].type == 'group') {
  1117. let curGroup = group ? group + '.' + list[i].model : list[i].model
  1118. currentNode[i].children = this._getFormFields(list[i].list, curGroup, curLabel)
  1119. }
  1120. }
  1121. return currentNode
  1122. },
  1123. getDataSourceArray () {
  1124. return _.cloneDeep(this.dataSourceArray)
  1125. },
  1126. },
  1127. watch: {
  1128. '$i18n.locale': function (val) {
  1129. this._loadComponents()
  1130. },
  1131. codeActiveName (val) {
  1132. this.codeCopyValue = this.codeActiveName == 'vue' ? this.vueTemplate : this.htmlTemplate
  1133. },
  1134. widgetFormSelect (val) {
  1135. val.key && this.$refs.outlineRef?.setCurrentKey(val.key, this.isScrollTo)
  1136. this.isScrollTo = true
  1137. this.modelNode = findModelNodeString(this.widgetForm.list, 'key', val.key)
  1138. },
  1139. activeLeft (val) {
  1140. if (val == 'outline') {
  1141. this.widgetFormSelect?.key && this.$refs.outlineRef?.setCurrentKey(this.widgetFormSelect.key, this.isScrollTo)
  1142. }
  1143. },
  1144. 'widgetFormSelect.key': function (val) {
  1145. this.formConfigKey = Math.random().toString(36).slice(-8)
  1146. }
  1147. }
  1148. }
  1149. </script>
  1150. <style lang="scss">
  1151. .widget-empty {
  1152. background-position: 50%;
  1153. }
  1154. // 添加过渡动画样式
  1155. .v-enter-active,
  1156. .v-leave-active {
  1157. transition: opacity .3s ease, transform 0.3s ease;
  1158. }
  1159. .v-enter-from,
  1160. .v-leave-to {
  1161. opacity: 0;
  1162. transform: scale(0);
  1163. }
  1164. .v-enter-to,
  1165. .v-leave-from {
  1166. opacity: 1;
  1167. transform: scale(1);
  1168. }
  1169. </style>