Container.vue 43 KB

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