systemLib.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. import _ from 'lodash'
  2. import {Lib, lookupScope} from './lib'
  3. import {ajax} from "./config";
  4. import {Model} from 'src/types';
  5. import {msg, showErrorDialog} from "../message";
  6. export const SIMPLE_RE = /^(?:\{(?:(\d+)|([a-z_][\w\.]*))\})$/i
  7. /**
  8. * 对某个表达式进行求值
  9. * a:{query.a},b:{query.b} -> a:aValue,b:bValue
  10. *
  11. * @example
  12. * calcExpress(cc.viewModel.data, "WH_ID:{query.WH_ID},C:{theGrid.selection.data.WH_ID}")
  13. * 计算出来的值是: "WH_ID:queryWhId,C:JH000000001"
  14. *
  15. * @param data 数据环境对象
  16. * @param express 表达式对象
  17. */
  18. export function calcExpress(data, express) {
  19. let result = express
  20. if (SIMPLE_RE.test(express)) {
  21. // '{foo}' 简单表达式
  22. const path = express.substring(1, express.length - 1);
  23. return _.get(data, path)
  24. }
  25. while (true) {
  26. const mlist = result.match(/{(.*?)}/)
  27. if (!mlist) {
  28. break
  29. }
  30. const pathC = mlist[0] // {query.a}
  31. const path = mlist[1] // query.a
  32. let value = _.get(data, path)
  33. result = result.replaceAll(pathC, value || '')
  34. }
  35. return result
  36. }
  37. /**
  38. * 对个对象进行表达式求值,不用回调
  39. * @example
  40. * calcObjectFlat({query:{a:'aValue',b1:'b1Value',b2:'b2Value',d1:1,d2:2}}, { a:'{query.a}', b:{b1:'{query.b1}', b2:'{query.b2}'},c:'aa',d:['{query.d1}','{query.d2}'] })
  41. *
  42. * {
  43. * a: '{query.a}',
  44. * b: {
  45. * b1: '{query.b1}',
  46. * b2: '{query.b2}',
  47. * },
  48. * c: 'aa',
  49. * d: [
  50. * '{query.d1}',
  51. * '{query.d2}'
  52. * ]
  53. * }
  54. *
  55. * 计算结果为
  56. * {
  57. * a: 'aValue',
  58. * b: {
  59. * b1: 'b1Value',
  60. * b2: 'b2Value'
  61. * },
  62. * c: 'aa'
  63. * d: [
  64. * '1',
  65. * '2'
  66. * ]
  67. * }
  68. *
  69. * @param data
  70. * @param paramObject
  71. */
  72. export function calcObjectFlat(data, paramObject) {
  73. const result = _.cloneDeep(paramObject)
  74. const trav = (param) => {
  75. _.forOwn(param, (value, key) => {
  76. if (_.isPlainObject(value)) {
  77. // 深度递归,对子对象进行求解
  78. trav(value)
  79. } else if (_.isString(value)) {
  80. // 字符串直接用 calcExpress 表达式求解
  81. param[key] = calcExpress(data, param[key])
  82. } else if (_.isArray(value)) {
  83. // 数组求解
  84. _.each(value, (v, idx) => {
  85. value[idx] = calcExpress(data, v)
  86. })
  87. }
  88. })
  89. }
  90. trav(result)
  91. return result
  92. }
  93. /**
  94. * 根据表达式进入写值
  95. * express="{query.a}" 写值就是 viewModel.set('query.a', value)
  96. * express="test-{query.a}" 写值就会失败
  97. *
  98. * @example
  99. * tryWriteByExpress(cc.viewModel, "{query.WH_ID}", "111")
  100. * 写值成功
  101. *
  102. * tryWriteByExpress(cc.viewModel, "test-{query.WH_ID}", "111")
  103. * 写值失败
  104. *
  105. * @param viewModel VM对象
  106. * @param express 表达式对象
  107. * @param value 目标值
  108. */
  109. export function tryWriteByExpress(viewModel, express, value) {
  110. if (SIMPLE_RE.test(express)) {
  111. // '{foo}' 简单表达式
  112. express = express.substring(1, express.length - 1);
  113. viewModel.set(express, value)
  114. }
  115. }
  116. /**
  117. * 尝试根据含表达式的对象回写, calcObjectFlat 的逆向方法
  118. * @example
  119. * tryWriteObject({ a:'{query.a}', b:{b1:'{query.b1}', b2:'{query.b2}'},c:'aa',d:['{query.d1}','{query.d2}']}, {a:'aValue', b:{b1:'b1Value', b2:'b2Value'}, c:'aa', d:[1,2]})
  120. *
  121. * expressObject:
  122. * {
  123. * a: '{query.a}',
  124. * b: {
  125. * b1: '{query.b1}',
  126. * b2: '{query.b2}',
  127. * },
  128. * c: 'aa',
  129. * d: [
  130. * '{query.a}',
  131. * '{query.b2}'
  132. * ]
  133. * }
  134. *
  135. * valueObject:
  136. * {
  137. * a: 'aValue',
  138. * b: {
  139. * b1: 'b1Value',
  140. * b2: 'b2Value'
  141. * },
  142. * c: 'aa'
  143. * c: [
  144. * 'aValue',
  145. * 'b2Value'
  146. * ]
  147. * }
  148. *
  149. * 系统会尝试回写
  150. * viewModel.set('query.a', 'aValue')
  151. * viewModel.set('query.b1', 'b1Value')
  152. * viewModel.set('query.b2', 'b2Value')
  153. *
  154. * @param expressObject 含表达式的对象
  155. * @param valueObject 表达式计算完成之后的结果对象
  156. * @param writeFn 写入的方法 (path, value)=>void
  157. */
  158. export function tryWriteObject(expressObject, valueObject, writeFn) {
  159. const trav = (pathPrefix) => {
  160. let parent = expressObject
  161. if (_.size(pathPrefix) > 1) {
  162. parent = _.get(parent, pathPrefix.substring(1))
  163. }
  164. _.forOwn(parent, (value, key) => {
  165. if (_.isPlainObject(value)) {
  166. // 深度递归,对子对象进行求解
  167. trav(pathPrefix + "." + key)
  168. } else if (_.isString(value)) {
  169. // 字符串直接用 calcExpress 表达式求解
  170. if (SIMPLE_RE.test(value)) {
  171. // If we have '{foo}' alone it is a literal 简单表达式
  172. const targetPath = value.substring(1, value.length - 1);
  173. const targetValue = _.get(valueObject, (pathPrefix + "." + key).substr(1))
  174. if (!writeFn) {
  175. console.log(`viewModel.set('${targetPath}', '${targetValue}')`)
  176. } else {
  177. writeFn(targetPath, targetValue)
  178. }
  179. }
  180. } else if (_.isArray(value)) {
  181. _.each(value, (v, idx) => {
  182. if (SIMPLE_RE.test(v)) {
  183. const targetPath = (pathPrefix + "." + key).substr(1) + "[" + idx + "]";
  184. const targetValue = _.get(valueObject, (pathPrefix + "." + key).substr(1) + "[" + idx + "]")
  185. if (!writeFn) {
  186. console.log(`viewModel.set('${targetPath}', '${targetValue}')`)
  187. } else {
  188. writeFn(targetPath, targetValue)
  189. }
  190. }
  191. })
  192. }
  193. })
  194. }
  195. trav("")
  196. }
  197. /**
  198. * 对多个表达式进行求值. 异步回调的方式返回
  199. * {
  200. * a: 1,
  201. * b: '{someBind}',
  202. * c: ['a', 'b', 'c'],
  203. * d: ['a', 'b', '{someBind}'],
  204. * e: {
  205. * y: 1,
  206. * z: 2
  207. * },
  208. * f: {
  209. * y: 1,
  210. * z: '{someBind}'
  211. * }
  212. * }
  213. *
  214. * // Will produce
  215. * {
  216. * b: value,
  217. * d: ['a', 'b', value],
  218. * f: {
  219. * y: 1,
  220. * z: value
  221. * }
  222. * }
  223. * @param viewModel scope.viewModel对象
  224. * @param paramObject 求值对象
  225. */
  226. export function calcObject(viewModel, paramObject) {
  227. // new Ext.app.bind.Multi({a:'1',b:'ddd{query.WH_ID}'},currentScope.viewModel,function(v){console.log(v)},currentScope, {single: true})
  228. return new Promise(resolve => {
  229. const schedule = new Ext.app.bind.Multi(
  230. paramObject,
  231. viewModel,
  232. (ret) => {
  233. schedule.destroy()
  234. // 从 Ext.data.Model 对象转换为 js-object 对象
  235. ret = toPlainObject(ret)
  236. resolve(ret)
  237. },
  238. viewModel,
  239. {single: true})
  240. })
  241. }
  242. /**
  243. * 用于任意组件 Ext.Component 构造时,获取当前组件对应的表格(如果不是 grid.columns 对象就会返回 undefined)
  244. * @param config 组件构造函数传入的 config 配置文件
  245. */
  246. export function getParentGrid(config) {
  247. return config.$initParent?.grid
  248. }
  249. /**
  250. * 动态的为 combo 或 columns.combo 设置下拉框的值
  251. * @param sender 目标对象
  252. * @param config 目标对象的配置(在构造函数之前也可以)
  253. * @param getDictFn 获取字典的方法
  254. * @param bizKey 传入字典的参数
  255. */
  256. export function setComboStore(sender, config, getDictFn, bizKey) {
  257. if (sender.$className === 'Ext.form.field.ComboBox') {
  258. getDictFn(bizKey, (r) => {
  259. if (sender.el?.dom) {
  260. // 异步回传
  261. sender.setStore(new Ext.data.Store(r))
  262. } else {
  263. // 同步回传
  264. config.store = new Ext.data.Store(r)
  265. }
  266. })
  267. return
  268. } else if (sender.xtype === 'gridcolumn') {
  269. const grid = getParentGrid(config)
  270. const {editor, renderer} = config
  271. getDictFn(bizKey, (r) => {
  272. if (sender.rendered) {
  273. // 已经渲染出来了, 用方法进行修改
  274. const editor = sender.getEditor()
  275. if (editor && editor.xtype === 'combo') {
  276. const valueField = r.field[0]
  277. const displayField = r.field[1]
  278. editor.valueField = valueField
  279. editor.setDisplayField(displayField)
  280. editor.setStore(new Ext.data.Store({
  281. field: ['id', 'text'],
  282. data: [
  283. {id: "Y", text: "启用"},
  284. {id: "N", text: "禁用"},
  285. {id: "D", text: "删除"},
  286. ]
  287. }))
  288. }
  289. } else {
  290. // 没有渲染之前,修改 config 即可
  291. if (editor && editor.xtype === 'combo') {
  292. // 带编辑模式
  293. editor.store = new Ext.data.Store(r)
  294. }
  295. }
  296. const renderer = (value, metaData) => {
  297. const valueField = r.field[0]
  298. const displayField = r.field[1]
  299. _.each(r.data, row => {
  300. // 从 valueField 找到要显示的 displayField
  301. if (row[valueField] == value) {
  302. value = row[displayField]
  303. return false
  304. }
  305. })
  306. return value
  307. }
  308. if (sender.rendered) {
  309. // 已经渲染出来了, 对列进行渲染
  310. sender.renderer = renderer
  311. sender.getView().refresh()
  312. } else {
  313. config.renderer = renderer
  314. }
  315. })
  316. return
  317. }
  318. throw new TypeError("无法识别的组件类型")
  319. }
  320. /**
  321. * 调用服务器 Ajax
  322. */
  323. export function invokeServer(url: string, ...args: any[]) {
  324. // @ts-ignore
  325. return ajax.func({
  326. url: url,
  327. method: 'invoke',
  328. args: args
  329. })
  330. }
  331. export function clearViewModelValues(viewModel, propertyName) {
  332. const dd = _.get(viewModel.getData(), propertyName)
  333. _.forOwn(dd, (value, key) => {
  334. viewModel.set(propertyName + '.' + key, '')
  335. })
  336. }
  337. export function reloadGrid(scope, gridRefName) {
  338. scope.refs[gridRefName]?.reload()
  339. }
  340. /**
  341. * 将 Ext.data.Model 对象 (及子属性) 转换为 js.object 对象
  342. */
  343. export function toPlainObject(obj) {
  344. if (obj.isModel) {
  345. obj = obj.data
  346. }
  347. _.forOwn(obj, (v, k) => {
  348. // Ext.data.Model.constructor
  349. if (!v) {
  350. return
  351. }
  352. if (v.isModel) {
  353. v = v.data
  354. }
  355. if (typeof v === 'object') {
  356. obj[k] = toPlainObject(v)
  357. } else {
  358. obj[k] = v
  359. }
  360. })
  361. return obj
  362. }
  363. class SystemEventFu {
  364. @Lib({
  365. title: '提交表单',
  366. author: '罗一帆',
  367. createAt: '2021-07-02',
  368. updateAt: '2021-07-02',
  369. type: 'system',
  370. category: '表单',
  371. args: [
  372. {
  373. type: 'string',
  374. title: 'groovy 服务路径',
  375. name: 'groovyUrl',
  376. },
  377. {
  378. type: 'object',
  379. title: '参数的 lookup 表达式, 如果不填 默认提交所有的 viewModel.data',
  380. name: 'arg0',
  381. allowEmpty: true,
  382. }
  383. ]
  384. })
  385. formCommit(groovyUrl: string, arg0: any) {
  386. return function (sender) {
  387. const scope = lookupScope(sender)
  388. const valid = scope.down('form').isValid()
  389. if (!valid) {
  390. return
  391. }
  392. scope.setLoading(true)
  393. let data = scope.viewModel.data
  394. if (arg0) {
  395. data = calcObjectFlat(data, arg0)
  396. }
  397. invokeServer(groovyUrl, data).then(res => {
  398. if (res.success) {
  399. scope.dialogSuccess(res)
  400. } else {
  401. showErrorDialog(res.msg || '未知错误', sender)
  402. }
  403. }).catch((e) => {
  404. const msg = e.response?.data?.msg
  405. showErrorDialog(msg || e.toString(), sender)
  406. }).finally(() => {
  407. scope.setLoading(false)
  408. })
  409. }
  410. }
  411. @Lib({
  412. title: '对话框成功回调',
  413. author: '罗一帆',
  414. createAt: '2021-07-02',
  415. updateAt: '2021-07-02',
  416. type: 'system',
  417. category: '对话框',
  418. args: []
  419. })
  420. dialogSuccess() {
  421. debugger
  422. }
  423. @Lib({
  424. title: '清空 viewModel 某个属性',
  425. author: '罗一帆',
  426. createAt: '2021-07-02',
  427. updateAt: '2021-07-02',
  428. type: 'system',
  429. category: '表单',
  430. args: [
  431. {
  432. type: 'viewModel',
  433. title: 'propertyName 属性路径',
  434. name: 'propertyName',
  435. }
  436. ]
  437. })
  438. clearViewModelValues(propertyName: string) {
  439. return function (sender) {
  440. const scope = lookupScope(sender)
  441. clearViewModelValues(scope.viewModel, propertyName)
  442. }
  443. }
  444. @Lib({
  445. title: '清空 viewModel 某个属性,并刷新表格',
  446. author: '罗一帆',
  447. createAt: '2021-07-02',
  448. updateAt: '2021-07-02',
  449. type: 'system',
  450. category: '表单',
  451. args: [
  452. {
  453. type: 'viewModel',
  454. title: 'propertyName 属性路径',
  455. name: 'propertyName',
  456. },
  457. {
  458. type: 'refs',
  459. title: 'gridRef 表格引用名',
  460. allowEmpty: true,
  461. name: 'gridRefName',
  462. }
  463. ]
  464. })
  465. clearViewModelReloadGrid(propertyName: string, gridRefName?: string) {
  466. return function (sender) {
  467. const scope = lookupScope(sender)
  468. clearViewModelValues(scope.viewModel, propertyName)
  469. if (!gridRefName) {
  470. scope.down('grid')?.reload()
  471. } else {
  472. scope.refs[gridRefName]?.reload()
  473. }
  474. }
  475. }
  476. @Lib({
  477. title: '刷新表格',
  478. author: '罗一帆',
  479. createAt: '2021-07-02',
  480. updateAt: '2021-07-02',
  481. type: 'system',
  482. category: '表单',
  483. args: [
  484. {
  485. type: 'refs',
  486. title: 'gridRef 表格引用名, 不填写的情况下刷新所有',
  487. allowEmpty: true,
  488. name: 'gridRefName',
  489. }
  490. ]
  491. })
  492. reloadGrid(gridRefName: string) {
  493. return function (sender) {
  494. const scope = lookupScope(sender)
  495. if (!gridRefName) {
  496. scope.down('grid')?.reload()
  497. } else {
  498. scope.refs[gridRefName]?.reload()
  499. }
  500. }
  501. }
  502. @Lib({
  503. title: '显示对话框',
  504. author: '罗一帆',
  505. createAt: '2021-07-02',
  506. updateAt: '2021-07-02',
  507. type: 'system',
  508. category: '对话框',
  509. args: [
  510. {
  511. type: 'module',
  512. title: '业务模块名',
  513. name: 'url',
  514. },
  515. {
  516. type: 'object',
  517. title: '参数数据 lookup 表达式',
  518. name: 'lookupForData',
  519. allowEmpty: true,
  520. },
  521. {
  522. type: 'event',
  523. title: '成功回调',
  524. name: 'successCallback',
  525. allowEmpty: true,
  526. }
  527. ]
  528. })
  529. showDialog(url: string, lookupForData: any, successCallback) {
  530. return function (sender) {
  531. const scope = lookupScope(sender)
  532. const data = scope.viewModel.data
  533. const param = calcObjectFlat(data, lookupForData)
  534. // @ts-ignore
  535. require([url], (module) => {
  536. const ScopeClass = module.default
  537. const scope = new ScopeClass()
  538. scope.success = successCallback
  539. scope.showDialog(sender, {}, {data: param})
  540. })
  541. }
  542. }
  543. @Lib({
  544. title: '弹出查找框(不借助 search)',
  545. author: '罗一帆',
  546. createAt: '2021-07-02',
  547. updateAt: '2021-07-02',
  548. type: 'system',
  549. category: '对话框',
  550. args: [
  551. {
  552. type: 'module',
  553. title: '模块名 (WidgetDialog)',
  554. name: 'widgetUrl',
  555. },
  556. {
  557. type: 'object',
  558. title: 'lookup 映射关系',
  559. name: 'lookupSetting',
  560. allowEmpty: true,
  561. }
  562. ]
  563. })
  564. showWidget(widgetUrl, lookup, params) {
  565. return function (sender, queryValue) {
  566. showWidget(widgetUrl, lookup, params, sender, queryValue)
  567. }
  568. }
  569. @Lib({
  570. title: '根据 lookup 清空 viewModel',
  571. author: '罗一帆',
  572. createAt: '2021-07-05',
  573. updateAt: '2021-07-05',
  574. type: 'system',
  575. category: '表单',
  576. args: [
  577. {
  578. type: 'viewModel',
  579. title: 'lookup 设值',
  580. name: 'lookup',
  581. },
  582. ]
  583. })
  584. clearViewModelByLookup(lookup) {
  585. return function (sender) {
  586. clearViewModelByLookup(sender, lookup)
  587. }
  588. }
  589. @Lib({
  590. title: '关闭对话框',
  591. author: '罗一帆',
  592. createAt: '2021-07-05',
  593. updateAt: '2021-07-05',
  594. type: 'system',
  595. category: '对话框',
  596. args: [
  597. {
  598. type: 'event',
  599. title: '对话框的返回值回调',
  600. name: 'callBack',
  601. },
  602. ]
  603. })
  604. closeMe(callBack) {
  605. return function (sender) {
  606. const scope = lookupScope(sender)
  607. scope.close()
  608. if (callBack) {
  609. callBack.call(sender)
  610. }
  611. }
  612. }
  613. }
  614. export function clearViewModelByLookup(sender, lookup) {
  615. if (_.isPlainObject(lookup)) {
  616. const parentScope = lookupScope(sender)
  617. _.forOwn(lookup, (value, key) => {
  618. if (SIMPLE_RE.test(value)) {
  619. // '{foo}' 简单表达式
  620. const path = value.substring(1, value.length - 1);
  621. if (path !== 'queryValue') {
  622. parentScope.viewModel.set(path, '')
  623. }
  624. }
  625. })
  626. }
  627. }
  628. export function showWidget(widgetUrl, lookup, params, sender, queryValue, vjson = {}) {
  629. const parentScope = lookupScope(sender)
  630. const me = sender
  631. // @ts-ignore
  632. require([widgetUrl], (widgetScope) => {
  633. const WidgetScopeClass = widgetScope.default
  634. widgetScope = new WidgetScopeClass()
  635. // 传递进 widget.model 的数据
  636. const widgetDialogData = calcObjectFlat({
  637. queryValue: queryValue,
  638. ...parentScope.viewModel.data
  639. }, lookup)
  640. _.map(params, (value, key) => {
  641. widgetDialogData[key] = value
  642. })
  643. widgetScope.parentScope = parentScope
  644. widgetScope.searchWidgetSuccess = (data) => {
  645. if (typeof lookup === 'string') {
  646. // lookup 是字符串的情况下,就是取某个列作为 value 值
  647. me.setValue(data[lookup])
  648. return
  649. }
  650. /**
  651. * lookup: {
  652. * // 扩展到 viewModel 的值做更改
  653. * WH_CODE: "{queryValue}",
  654. * WH_NAME: "{query.WH_NAME}",
  655. * }
  656. */
  657. if (_.isPlainObject(lookup)) {
  658. const parentScope = lookupScope(sender)
  659. tryWriteObject(lookup, data, (path, value) => {
  660. if (path === 'queryValue') {
  661. me.setValue(value)
  662. } else {
  663. parentScope.viewModel.set(path, value)
  664. }
  665. }
  666. )
  667. }
  668. return true
  669. }
  670. widgetScope.showDialog(sender, vjson, {data: widgetDialogData})
  671. })
  672. }
  673. /**
  674. * 停止事件的默认行为
  675. * @param e
  676. */
  677. export function stopEvent(e) {
  678. e.preventDefault()
  679. e.stopPropagation()
  680. // @ts-ignore
  681. window.event.cancelBubble = true
  682. e.returnValue = false;
  683. e.cancelBubble = true;
  684. }