systemLib.ts 18 KB

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