EventConfig.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. <template>
  2. <div class="_fd-event">
  3. <el-badge :value="eventNum" type="warning" :hidden="eventNum < 1">
  4. <el-button size="small" @click="visible=true">{{ t('event.title') }}</el-button>
  5. </el-badge>
  6. <el-dialog class="_fd-event-dialog" :title="t('event.title')" v-model="visible" destroy-on-close
  7. :close-on-click-modal="false"
  8. append-to-body
  9. width="980px">
  10. <el-container class="_fd-event-con" style="height: 600px">
  11. <el-aside style="width:300px;">
  12. <el-container class="_fd-event-l">
  13. <el-header class="_fd-event-head" height="40px">
  14. <el-dropdown popper-class="_fd-event-dropdown" trigger="click" size="default"
  15. :placement="'bottom-start'">
  16. <span class="el-dropdown-link">
  17. <el-button link type="primary" size="default">
  18. {{ t('event.create') }}<i class="el-icon-arrow-down el-icon--right"></i>
  19. </el-button>
  20. </span>
  21. <template #dropdown>
  22. <el-dropdown-menu>
  23. <el-dropdown-item v-for="name in eventName" :key="name" @click="add(name)"
  24. :disabled="Object.keys(event).indexOf(name) > -1">
  25. <div class="_fd-event-item">
  26. <span>{{ name }}</span>
  27. <span class="_fd-label" v-if="eventInfo[name]">
  28. {{ eventInfo[name] }}
  29. </span>
  30. </div>
  31. </el-dropdown-item>
  32. <template v-for="(hook, idx) in hookList">
  33. <el-dropdown-item :divided="eventName.length > 0 && !idx"
  34. @click="add(hook)"
  35. :disabled="Object.keys(event).indexOf(hook) > -1">
  36. <div class="_fd-event-item">
  37. <div> {{ hook }}</div>
  38. <span class="_fd-label">
  39. {{ eventInfo[hook] }}
  40. </span>
  41. </div>
  42. </el-dropdown-item>
  43. </template>
  44. <el-dropdown-item :divided="eventName.length > 0" @click="cusEvent">
  45. <div>{{ t('props.custom') }}</div>
  46. </el-dropdown-item>
  47. </el-dropdown-menu>
  48. </template>
  49. </el-dropdown>
  50. </el-header>
  51. <el-main>
  52. <el-menu
  53. :default-active="defActive"
  54. v-model="activeData">
  55. <template v-for="(item, name) in event">
  56. <template v-if="Array.isArray(item)">
  57. <template v-for="(event, index) in item" :key="name + index">
  58. <el-menu-item :index="name + index">
  59. <div class="_fd-event-title"
  60. @click.stop="edit({name, item, index})">
  61. <div class="_fd-event-method">
  62. <span>function<span>{{
  63. name
  64. }}</span></span>
  65. <span class="_fd-label"
  66. v-if="eventInfo[name]">{{ eventInfo[name] }}</span>
  67. </div>
  68. <i class="fc-icon icon-delete"
  69. @click.stop="rm({name, item, index})"></i>
  70. </div>
  71. </el-menu-item>
  72. </template>
  73. </template>
  74. <el-menu-item v-else :index="name + 0">
  75. <div class="_fd-event-title" @click.stop="edit({name})">
  76. <div class="_fd-event-method">
  77. <span>function<span>{{
  78. name
  79. }}</span></span>
  80. <span class="_fd-label"
  81. v-if="eventInfo[name]">{{ eventInfo[name] }}</span>
  82. </div>
  83. <i class="fc-icon icon-delete" @click.stop="rm({name})"></i>
  84. </div>
  85. </el-menu-item>
  86. </template>
  87. <el-menu-item v-if="cus" style="padding-left: 10px;" index="custom">
  88. <div class="_fd-event-title" @click.stop>
  89. <el-input type="text" v-model="cusValue" size="default"
  90. @keydown.enter="addCus"
  91. :placeholder="t('event.placeholder')">
  92. </el-input>
  93. <div>
  94. <i class="fc-icon icon-add" @click.stop="addCus"></i>
  95. <i class="fc-icon icon-delete" @click.stop="closeCus"></i>
  96. </div>
  97. </div>
  98. </el-menu-item>
  99. </el-menu>
  100. </el-main>
  101. </el-container>
  102. </el-aside>
  103. <el-main>
  104. <el-container class="_fd-event-r">
  105. <el-header class="_fd-event-head" height="40px" v-if="activeData">
  106. <div><a target="_blank" href="https://form-create.com/v3/instance/">{{t('form.document')}}</a></div>
  107. <div>
  108. <el-button size="small" @click="close">{{ t('props.cancel') }}</el-button>
  109. <el-button size="small" type="primary" @click="save" color="#2f73ff">{{
  110. t('props.save')
  111. }}
  112. </el-button>
  113. </div>
  114. </el-header>
  115. <el-main v-if="activeData">
  116. <FnEditor ref="fn" v-model="eventStr" body :name="activeData.name"
  117. :args="fnArgs"
  118. style="height: 519px;"/>
  119. </el-main>
  120. </el-container>
  121. </el-main>
  122. </el-container>
  123. <template #footer>
  124. <div>
  125. <el-button size="default" @click="visible=false">{{ t('props.cancel') }}</el-button>
  126. <el-button type="primary" size="default" @click="submit" color="#2f73ff">{{
  127. t('props.ok')
  128. }}
  129. </el-button>
  130. </div>
  131. </template>
  132. </el-dialog>
  133. </div>
  134. </template>
  135. <script>
  136. import unique from '@form-create/utils/lib/unique';
  137. import deepExtend from '@form-create/utils/lib/deepextend';
  138. import is from '@form-create/utils/lib/type';
  139. import {defineComponent} from 'vue';
  140. import FnEditor from './FnEditor.vue';
  141. import errorMessage from '../utils/message';
  142. import {getInjectArg} from '../utils';
  143. const $T = '$FNX:';
  144. const isFNX = v => {
  145. return is.String(v) && v.indexOf($T) === 0;
  146. };
  147. export default defineComponent({
  148. name: 'EventConfig',
  149. emits: ['update:modelValue'],
  150. props: {
  151. modelValue: [Object, undefined, null],
  152. componentName: '',
  153. eventName: {
  154. type: Array,
  155. default: () => []
  156. }
  157. },
  158. inject: ['designer'],
  159. components: {
  160. FnEditor,
  161. },
  162. data() {
  163. return {
  164. visible: false,
  165. activeData: null,
  166. val: null,
  167. defActive: 'no',
  168. hookList: ['hook_load', 'hook_mounted', 'hook_deleted', 'hook_watch', 'hook_value', 'hook_hidden'],
  169. event: {},
  170. cus: false,
  171. cusValue: '',
  172. eventStr: '',
  173. };
  174. },
  175. computed: {
  176. t() {
  177. return this.designer.setupState.t;
  178. },
  179. activeRule() {
  180. return this.designer.setupState.activeRule;
  181. },
  182. eventInfo() {
  183. const info = {};
  184. this.eventName.forEach(v => {
  185. info[v] = this.t('com.' + this.componentName + '.event.' + v) || this.t('eventInfo.' + v) || '';
  186. })
  187. this.hookList.forEach(v => {
  188. info[v] = this.t('eventInfo.' + v) || '';
  189. })
  190. return info;
  191. },
  192. eventNum() {
  193. let num = 0;
  194. Object.keys(this.modelValue || {}).forEach(k => {
  195. num += Array.isArray(this.modelValue[k]) ? this.modelValue[k].length : 1;
  196. });
  197. const hooks = this.activeRule ? {...this.activeRule.hook || {}} : {};
  198. Object.keys(hooks).forEach(k => {
  199. num += Array.isArray(this.activeRule.hook[k]) ? this.activeRule.hook[k].length : 1;
  200. });
  201. return num;
  202. },
  203. fnArgs() {
  204. return [getInjectArg(this.t)];
  205. }
  206. },
  207. watch: {
  208. visible(v) {
  209. this.event = v ? this.loadFN() : {};
  210. if (!v) {
  211. this.destroy();
  212. this.closeCus();
  213. }
  214. },
  215. },
  216. methods: {
  217. addCus() {
  218. const val = this.cusValue && this.cusValue.trim();
  219. if (val) {
  220. this.closeCus();
  221. this.add(val);
  222. }
  223. },
  224. closeCus() {
  225. this.cus = false;
  226. this.cusValue = '';
  227. },
  228. cusEvent() {
  229. this.cus = true;
  230. },
  231. loadFN() {
  232. const e = deepExtend({}, this.modelValue || {});
  233. const hooks = this.activeRule ? {...this.activeRule.hook || {}} : {};
  234. Object.keys(hooks).forEach(k => {
  235. e['hook_' + k] = hooks[k];
  236. })
  237. const val = {};
  238. Object.keys(e).forEach(k => {
  239. if (Array.isArray(e[k])) {
  240. const data = [];
  241. e[k].forEach(v => {
  242. if (isFNX(v)) {
  243. data.push(v.replace($T, ''));
  244. } else if (is.Function(v) && isFNX(v.__json)) {
  245. data.push(v.__json.replace($T, ''));
  246. } else if (v && v.indexOf('$GLOBAL:') === 0) {
  247. data.push(v);
  248. }
  249. });
  250. val[k] = data;
  251. } else if (isFNX(e[k])) {
  252. val[k] = [e[k].replace($T, '')];
  253. } else if (is.Function(e[k]) && isFNX(e[k].__json)) {
  254. val[k] = [e[k].__json.replace($T, '')];
  255. } else if (e[k] && e[k].indexOf('$GLOBAL:') === 0) {
  256. val[k] = [e[k]];
  257. }
  258. });
  259. return val;
  260. },
  261. parseFN(e) {
  262. const on = {};
  263. const hooks = {};
  264. Object.keys(e).forEach(k => {
  265. const lst = [];
  266. e[k].forEach((v, i) => {
  267. lst[i] = v.indexOf('$GLOBAL:') !== 0 ? ($T + v) : v;
  268. });
  269. if (lst.length > 0) {
  270. if (k.indexOf('hook_') > -1) {
  271. hooks[k.replace('hook_', '')] = lst.length === 1 ? lst[0] : lst;
  272. } else {
  273. on[k] = lst.length === 1 ? lst[0] : lst;
  274. }
  275. }
  276. });
  277. return {hooks, on};
  278. },
  279. add(name) {
  280. let data = {};
  281. if (Array.isArray(this.event[name])) {
  282. this.event[name].push('');
  283. data = {
  284. name,
  285. item: this.event[name],
  286. index: this.event[name].length - 1,
  287. };
  288. } else if (this.event[name]) {
  289. const arr = [this.event[name], ''];
  290. this.event[name] = arr;
  291. data = {
  292. name,
  293. item: arr,
  294. index: 1,
  295. };
  296. } else {
  297. const arr = [''];
  298. this.event[name] = arr;
  299. data = {
  300. name,
  301. item: arr,
  302. index: 0,
  303. };
  304. }
  305. if (!this.activeData) {
  306. this.edit(data);
  307. }
  308. },
  309. edit(data) {
  310. data.key = unique();
  311. if (data.item) {
  312. this.val = data.item[data.index];
  313. } else {
  314. this.val = this.event[data.name];
  315. }
  316. this.activeData = data;
  317. this.eventStr = this.val;
  318. this.defActive = data.name + (data.index || 0);
  319. },
  320. save() {
  321. if (!this.$refs.fn.save()) {
  322. return;
  323. }
  324. const str = this.eventStr;
  325. if (this.activeData.item) {
  326. this.activeData.item[this.activeData.index] = str;
  327. } else {
  328. this.event[this.activeData.name] = str;
  329. }
  330. this.destroy();
  331. },
  332. rm(data) {
  333. if (data.index !== undefined) {
  334. data.item.splice(data.index, 1);
  335. } else {
  336. this.$delete(this.event, data.name);
  337. }
  338. if (this.defActive === (data.name + (data.index || 0))) {
  339. this.destroy();
  340. }
  341. },
  342. destroy() {
  343. this.activeData = null;
  344. this.val = null;
  345. this.defActive = 'no';
  346. },
  347. close() {
  348. this.destroy();
  349. },
  350. submit() {
  351. if (this.activeData) {
  352. return errorMessage(this.t('event.saveMsg'));
  353. }
  354. const {on, hooks} = this.parseFN(this.event);
  355. this.$emit('update:modelValue', on);
  356. this.activeRule.hook = hooks;
  357. this.visible = false;
  358. this.destroy();
  359. this.closeCus();
  360. },
  361. },
  362. beforeCreate() {
  363. window.$inject = {
  364. $f: {},
  365. rule: [],
  366. self: {},
  367. option: {},
  368. inject: {},
  369. args: [],
  370. };
  371. }
  372. });
  373. </script>
  374. <style>
  375. ._fd-event .el-button {
  376. font-weight: 400;
  377. width: 100%;
  378. border-color: #2E73FF;
  379. color: #2E73FF;
  380. }
  381. ._fd-event .el-badge {
  382. width: 100%;
  383. }
  384. ._fd-event-dialog .el-dialog__body {
  385. padding: 10px 20px;
  386. }
  387. ._fd-event-con .el-main {
  388. padding: 0;
  389. }
  390. ._fd-event-l, ._fd-event-r {
  391. display: flex;
  392. flex-direction: column;
  393. flex: 1;
  394. height: 100%;
  395. border: 1px solid #ececec;
  396. }
  397. ._fd-event-dropdown .el-dropdown-menu {
  398. max-height: 500px;
  399. overflow: auto;
  400. }
  401. ._fd-event-head {
  402. display: flex;
  403. padding: 5px 15px;
  404. border-bottom: 1px solid #eee;
  405. background: #f8f9ff;
  406. align-items: center;
  407. }
  408. ._fd-event-head .el-button.is-link {
  409. color: #2f73ff;
  410. }
  411. ._fd-event-r {
  412. border-left: 0 none;
  413. }
  414. ._fd-event-r ._fd-event-head {
  415. justify-content: space-between;
  416. }
  417. ._fd-event-l > .el-main, ._fd-event-r > .el-main {
  418. display: flex;
  419. flex-direction: row;
  420. flex: 1;
  421. flex-basis: auto;
  422. box-sizing: border-box;
  423. min-width: 0;
  424. width: 100%;
  425. }
  426. ._fd-event-r > .el-main {
  427. flex-direction: column;
  428. }
  429. ._fd-event-item {
  430. display: flex;
  431. flex-direction: column;
  432. justify-content: center;
  433. max-width: 250px;
  434. font-size: 14px;
  435. overflow: hidden;
  436. white-space: pre-wrap;
  437. }
  438. ._fd-event-item ._fd-label {
  439. font-size: 12px;
  440. color: #AAAAAA;
  441. }
  442. ._fd-event-l .el-menu {
  443. padding: 0 10px 5px;
  444. border-right: 0 none;
  445. width: 100%;
  446. border-top: 0 none;
  447. overflow: auto;
  448. }
  449. ._fd-event-l .el-menu-item.is-active {
  450. background: #e4e7ed;
  451. color: #303133;
  452. }
  453. ._fd-event-l .el-menu-item {
  454. height: auto;
  455. line-height: 1em;
  456. border: 1px solid #ECECEC;
  457. border-radius: 5px;
  458. padding: 0;
  459. margin-top: 5px;
  460. }
  461. ._fd-event-method {
  462. display: flex;
  463. flex-direction: column;
  464. justify-content: center;
  465. width: 225px;
  466. font-size: 14px;
  467. font-family: monospace;
  468. color: #9D238C;
  469. overflow: hidden;
  470. white-space: pre-wrap;
  471. }
  472. ._fd-event-method ._fd-label {
  473. margin-top: 4px;
  474. color: #AAAAAA;
  475. font-size: 12px;
  476. }
  477. ._fd-event-method > span:first-child, ._fd-fn-list-method > span:first-child {
  478. color: #9D238C;
  479. }
  480. ._fd-event-method > span:first-child > span, ._fd-fn-list-method > span:first-child > span {
  481. color: #000;
  482. margin-left: 10px;
  483. }
  484. ._fd-event-title {
  485. display: flex;
  486. align-items: center;
  487. justify-content: space-between;
  488. width: 100%;
  489. padding: 10px 0;
  490. }
  491. ._fd-event-title .fc-icon {
  492. margin-right: 6px;
  493. font-size: 18px;
  494. color: #282828;
  495. }
  496. ._fd-event-title .el-input {
  497. width: 200px;
  498. }
  499. ._fd-event-title .el-input__wrapper {
  500. box-shadow: none;
  501. }
  502. ._fd-event-title .el-menu-item.is-active i {
  503. color: #282828;
  504. }
  505. ._fd-event-con .CodeMirror {
  506. height: 100%;
  507. width: 100%;
  508. }
  509. ._fd-event-con .CodeMirror-wrap pre.CodeMirror-line {
  510. padding-left: 20px;
  511. }
  512. </style>