risk-criteria-child-table.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. <template>
  2. <div :class="[$style.wrapper, $style.sdFormforxmchild]">
  3. <div v-if="captionvisiable" :class="[$style.caption]">
  4. {{ label }}
  5. <div v-if="!readOnly" :class="$style.header">
  6. <a-button v-if="addbuttonvisiable" type="link" @click="add">
  7. <a-icon type="plus-circle" :theme="'filled'" />
  8. 添加
  9. </a-button>
  10. <a-button
  11. type="link"
  12. :disabled="selectColumnsLength === 0"
  13. @click="remove(selectedRowKeys)"
  14. >
  15. <a-icon type="minus-circle" :theme="'filled'" />
  16. 删除
  17. </a-button>
  18. <a-button v-if="savebuttonvisiable" type="link" @click="save">
  19. <a-icon type="check-circle" :theme="'filled'" />
  20. 保存
  21. </a-button>
  22. </div>
  23. </div>
  24. <sd-table
  25. v-if="sdtableflag"
  26. ref="sdtable"
  27. :show-selection="false"
  28. size="middle"
  29. :columns="_columns"
  30. :width="width"
  31. sortable
  32. :data-source="value"
  33. :show-header="false"
  34. :row-key="(record, index) => index"
  35. :pagination="false"
  36. :row-selection="null"
  37. :custom-row="
  38. (record, index) => {
  39. return {
  40. on: {
  41. click: () => {
  42. edit(index)
  43. },
  44. },
  45. }
  46. }
  47. "
  48. @update:data-source="fnupdate"
  49. >
  50. <template
  51. v-for="(field, index) in fields"
  52. :slot="'sd_' + field.name"
  53. slot-scope="text, record, index"
  54. >
  55. <slot v-bind="{ field, text, record, value, index }" :name="field.name + '_xm'">
  56. <sdForm :ref="field.name + '_' + index" :init-values="[field]" :read-only="readOnly">
  57. <template v-if="!readOnly" v-slot="scope">
  58. <slot v-bind="{ field, text, record, value, index }" :name="field.name"> </slot>
  59. </template>
  60. <template v-if="readOnly">
  61. {{ getDisplayVaule(field, text) }}
  62. </template>
  63. </sdForm>
  64. </slot>
  65. </template>
  66. </sd-table>
  67. <div :class="$style.loading">
  68. <a-spin v-if="!sdtableflag" />
  69. </div>
  70. </div>
  71. </template>
  72. <script>
  73. import { Modal } from 'ant-design-vue'
  74. import { getComponentSpec } from '@/common/components/sd-form'
  75. import moment from 'moment'
  76. import components from './_import-components/risk-criteria-child-table-import'
  77. // Vue.use(DatePicker)
  78. // Vue.use(TimePicker)
  79. /**
  80. * 主子表、动态表格字段
  81. * @displayName XmChildTable 主子表
  82. */
  83. export default {
  84. name: 'RiskCriteriaChildTable',
  85. components: {
  86. ...components,
  87. sdForm: () => import('@/common/components/sd-form'),
  88. },
  89. model: {
  90. prop: 'value',
  91. event: 'change',
  92. },
  93. props: {
  94. /**
  95. * 字表的值,{key:value}的格式
  96. */
  97. value: {
  98. type: Array,
  99. default: () => [],
  100. },
  101. /**
  102. * 字表的字段属性
  103. */
  104. fields: {
  105. type: Array,
  106. default: () => [],
  107. },
  108. /**
  109. * 是否只读
  110. */
  111. readOnly: {
  112. type: Boolean,
  113. default: false,
  114. },
  115. /**
  116. * 标签名
  117. */
  118. label: {
  119. type: String,
  120. default: '',
  121. },
  122. /**
  123. * 自定义子表列属性,如[{dataIndex:'shuxingmingzi15',width:'120px'}],8.0.4及以后版本支持
  124. */
  125. columns: {
  126. type: Array,
  127. default: () => [],
  128. },
  129. /**
  130. * 某行是否可选
  131. * @since 8.0.11
  132. */
  133. showSelection: {
  134. type: Function,
  135. default: () => true,
  136. },
  137. width: {
  138. type: Number,
  139. default: 100,
  140. },
  141. datachange: {
  142. type: Function,
  143. default: null,
  144. },
  145. dzcl: {
  146. type: Boolean,
  147. default: false,
  148. },
  149. addfun: {
  150. type: Function,
  151. default: null,
  152. },
  153. captionvisiable: {
  154. type: Boolean,
  155. default: true,
  156. },
  157. addbuttonvisiable: {
  158. type: Boolean,
  159. default: true,
  160. },
  161. savebuttonvisiable: {
  162. type: Boolean,
  163. default: false,
  164. },
  165. fncheckdelete: {
  166. type: Function,
  167. default: null,
  168. },
  169. // 是否批量提交
  170. patchSave: {
  171. type: Boolean,
  172. default: false,
  173. },
  174. selectColumns: {
  175. type: Number,
  176. default: 0,
  177. },
  178. },
  179. data() {
  180. return {
  181. selectedRowKeys: [],
  182. visible: false,
  183. editRecord: this.fields, // 待修改或编辑的记录
  184. attkeys: {}, // 附件组件的key,行编辑了就修改对应的附件key,强制更新附件列表
  185. savevisible: true,
  186. adddataxm: {},
  187. selectitem: [],
  188. sjjs: this.getsjsl(),
  189. ndata: [],
  190. newfield: [],
  191. validflag: true,
  192. sdtableflag: true,
  193. localLoading: false,
  194. }
  195. },
  196. computed: {
  197. _columns() {
  198. const fields = JSON.parse(JSON.stringify(this.fields))
  199. return fields
  200. .filter((item) => item.dataType !== 'id')
  201. .map((item) => {
  202. const t = {
  203. dataIndex: item.name,
  204. key: item.name,
  205. title: item.caption,
  206. scopedSlots: { customRender: 'sd_' + item.name },
  207. width: item.width || '40',
  208. }
  209. const column = this.columns.find((c) => c.dataIndex === item.name)
  210. if (column) Object.assign(t, column)
  211. return t
  212. })
  213. .filter((item) => item.sdHidden !== true)
  214. },
  215. selectColumnsLength() {
  216. return this.selectColumns
  217. },
  218. },
  219. mounted() {
  220. if (this.datachange) {
  221. this.datachange()
  222. }
  223. if (this.dzcl) {
  224. this.fields.forEach((item) => {
  225. if (item.name === 'userType') {
  226. let index = item.attr.selectListItem.findIndex((item) => item.label === '项目组长')
  227. item.attr.selectListItem.splice(index, 1)
  228. index = item.attr.selectListItem.findIndex((item) => item.label === '项目负责人')
  229. item.attr.selectListItem.splice(index, 1)
  230. this.selectitem = item.attr.selectListItem
  231. }
  232. })
  233. }
  234. },
  235. destroyed() {
  236. const data = this.value || [] // 兼容初始值是null的情况
  237. const index = data.findIndex((item) => item.editable === true)
  238. if (index !== -1) {
  239. this.optbuttonClick('delete', [], index)
  240. }
  241. },
  242. methods: {
  243. fnupdate(data) {
  244. this.sdtableflag = false
  245. const newData = [...data]
  246. this.value.splice(0)
  247. newData.forEach((item) => {
  248. this.value.push(item)
  249. })
  250. setTimeout(() => {
  251. this.sdtableflag = true
  252. }, 0)
  253. this.$emit('change', this.value)
  254. },
  255. moment,
  256. getsjsl() {
  257. let index
  258. this.fields.forEach((f) => {
  259. if (f.sdHidden === true) {
  260. index = index + 1
  261. }
  262. })
  263. return index
  264. },
  265. fngetfield(field, value) {
  266. const editRecord = [field]
  267. editRecord.forEach((item) => {
  268. // 把值放到字段信息fields里
  269. item.value = value[item.name]
  270. if (item.attr.controlType === 'DictType') {
  271. if (item.attr.selectListItem.find((t) => t.value === item.value) !== undefined) {
  272. item.attr.displayValue = item.attr.selectListItem.find(
  273. (t) => t.value === item.value
  274. ).label
  275. }
  276. }
  277. })
  278. return editRecord
  279. },
  280. getDisplayVaule(field, text) {
  281. if (getComponentSpec(field).getDisplayValue) {
  282. if (typeof text !== 'undefined' && text !== null) {
  283. const value = getComponentSpec(field).parseBackendValue?.(text) || text
  284. return getComponentSpec(field).getDisplayValue(value)
  285. } else {
  286. return ''
  287. }
  288. }
  289. return text
  290. },
  291. changolddata() {
  292. const h = new Promise((resolve) => {
  293. if (this.value !== null) {
  294. if (this.value.length > 0) {
  295. this.value.forEach((d, index) => {
  296. if (d.mainId === 0) {
  297. // 获取该行数据
  298. d.mainId = Math.random()
  299. }
  300. this.savetableh(index, null).then((r) => {
  301. if (index === this.value.length - 1) {
  302. resolve(true)
  303. }
  304. })
  305. })
  306. } else {
  307. resolve(false)
  308. }
  309. } else {
  310. resolve(true)
  311. }
  312. })
  313. return h
  314. },
  315. savetableh(i, field) {
  316. // 保存最新增的行
  317. const data = this.value || [] // 兼容初始值是null的情况
  318. const value = data[i]
  319. let fields = this.fields
  320. if (field !== null) {
  321. fields = [field]
  322. }
  323. const savef = new Promise((resolve) => {
  324. fields.forEach((item, index) => {
  325. const refname = item.name
  326. const dataType = item.dataType
  327. if (this.$refs[refname + '_' + i]) {
  328. if (dataType === 'attachment') {
  329. } else if (
  330. typeof this.$refs[refname + '_' + i][0].model[refname] === 'object' &&
  331. dataType !== 'date'
  332. ) {
  333. if (this.$refs[refname + '_' + i][0].model[refname] !== undefined) {
  334. value[item.name] = JSON.stringify(this.$refs[refname + '_' + i][0].model[refname])
  335. }
  336. } else if (dataType === 'date') {
  337. if (this.$refs[refname + '_' + i][0].model[refname] !== undefined) {
  338. value[item.name] = this.$refs[refname + '_' + i][0].getFieldValue(refname).valueOf()
  339. }
  340. } else {
  341. if (this.dzcl && refname === 'userType') {
  342. if (value[item.name] !== '04' && value[item.name] !== '05') {
  343. this.$refs[refname + '_' + i][0].setFieldValue(refname, value[item.name])
  344. }
  345. } else {
  346. if (this.$refs[refname + '_' + i][0].model[refname] !== undefined) {
  347. value[item.name] = this.$refs[refname + '_' + i][0].getFieldValue(refname)
  348. }
  349. }
  350. }
  351. }
  352. })
  353. // 一行数据已存储
  354. data[i] = value
  355. this.value = data
  356. if (!this.patchSave) {
  357. this.$emit('saved', data)
  358. }
  359. // this.$emit('change', data)
  360. resolve(true)
  361. // 打开结束时间
  362. if (field && field.name === 'joinStartTime') {
  363. document
  364. .querySelector("[nodename='joinEndTime_" + i + "'] .ant-calendar-picker-input")
  365. .click()
  366. }
  367. })
  368. return savef
  369. },
  370. add() {
  371. this.$emit('addData')
  372. },
  373. edit(index) {},
  374. setvalid() {
  375. this.value.forEach((h, index) => {
  376. // if (this.validflag) {
  377. const value = {}
  378. this.fields.forEach((item, fi) => {
  379. const refname = item.name
  380. const dataType = item.dataType
  381. if (this.$refs[refname + '_' + index]) {
  382. this.$refs[refname + '_' + index][0].validateFields().then((res) => {})
  383. }
  384. })
  385. })
  386. },
  387. valid() {
  388. this.validflag = true
  389. const data = this.value || [] // 兼容初始值是null的情况
  390. const valid = new Promise((resolve) => {
  391. // 轮询所有行数据
  392. if (data.length === 0) {
  393. resolve(true)
  394. } else {
  395. data.forEach((h, index) => {
  396. this.fields.forEach((item, fi) => {
  397. const refname = item.name
  398. if (this.$refs[refname + '_' + index]) {
  399. this.$refs[refname + '_' + index][0]
  400. .validateFields()
  401. .then((res) => {
  402. if (!res) {
  403. this.validflag = false
  404. } else {
  405. if (index === data.length - 1 && fi === this.fields.length - 1) {
  406. setTimeout(() => {
  407. if (this.validflag) {
  408. resolve(true)
  409. } else {
  410. resolve(false)
  411. }
  412. }, 1000)
  413. }
  414. }
  415. })
  416. .catch(() => {
  417. this.validflag = false
  418. })
  419. } else {
  420. if (index === data.length - 1 && fi === this.fields.length - 1) {
  421. setTimeout(() => {
  422. if (this.validflag) {
  423. resolve(true)
  424. } else {
  425. resolve(false)
  426. }
  427. }, 1000)
  428. }
  429. }
  430. })
  431. })
  432. }
  433. })
  434. return valid
  435. },
  436. save() {
  437. if (this.readOnly || !this.visible) {
  438. // 判断visable为false时,modal没有是为了防止快速重复点击确定
  439. this.visible = false
  440. }
  441. this.changolddata().then((res) => {})
  442. const save = new Promise((resolve, reject) => {
  443. const data = this.value || [] // 兼容初始值是null的情况
  444. // 轮询所有行数据
  445. if (data.length === 0) {
  446. resolve(false)
  447. } else {
  448. let canSubmit = true
  449. data.forEach((h, index) => {
  450. const value = {}
  451. this.fields.forEach((item, fi) => {
  452. const refname = item.name
  453. const dataType = item.dataType
  454. if (this.$refs[refname + '_' + index]) {
  455. return this.$refs[refname + '_' + index][0]
  456. .validateFields()
  457. .then(() => {
  458. if (index === data.length - 1 && fi === this.fields.length - 1) {
  459. setTimeout(() => {
  460. if (canSubmit) {
  461. resolve(true)
  462. this.$emit('change', this.value)
  463. } else {
  464. resolve(false)
  465. }
  466. }, 0)
  467. }
  468. })
  469. .catch(() => {
  470. canSubmit = false
  471. })
  472. }
  473. })
  474. if (index === data.length - 1) {
  475. // resolve(true)
  476. if (canSubmit) {
  477. // this.$emit('change', this.value)
  478. // resolve(true)
  479. }
  480. }
  481. })
  482. }
  483. })
  484. return save
  485. },
  486. onSelectChange(selectedRowKeys) {
  487. this.selectedRowKeys = selectedRowKeys
  488. this.$emit('onSelectChange', this.selectedRowKeys)
  489. },
  490. remove(keys) {
  491. this.$emit('deleteData')
  492. },
  493. optbuttonClick(val, record, i) {
  494. let value = {}
  495. const data = this.value || [] // 兼容初始值是null的情况
  496. const adddata = this.adddataxm || {}
  497. let index = 0
  498. const qqcs = this._columns.length - 2
  499. const editrow = data.find((c) => c.editable === true)
  500. switch (val) {
  501. case 'save':
  502. value = data[i]
  503. this.fields.forEach((item) => {
  504. const refname = item.name
  505. const dataType = item.dataType
  506. if (this.$refs[refname]) {
  507. this.$refs[refname][0].validateFields().then(() => {
  508. this.$refs[refname][0].getBackendValues().forEach((item) => {
  509. if (item.name === refname) {
  510. if (dataType === 'attachment') {
  511. if (adddata[refname] !== undefined) {
  512. var obj = JSON.parse(adddata[refname])
  513. value[item.name] = '{"value":"' + obj.value + '"}'
  514. } else {
  515. value[item.name] = item.value
  516. }
  517. } else {
  518. value[item.name] = item.value
  519. }
  520. }
  521. })
  522. index = index + 1
  523. if (index === qqcs) {
  524. this.attkeys[0] = Math.random()
  525. value.editable = false
  526. data.splice(i, 1)
  527. data.splice(i, 0, value)
  528. this.$emit('change', data)
  529. this.value = data
  530. }
  531. })
  532. }
  533. })
  534. this.adddataxm = {}
  535. // event.stopPropagation()
  536. break
  537. case 'delete':
  538. if (this.fncheckdelete !== null) {
  539. if (this.fncheckdelete(data, i)) {
  540. data.splice(i, 1)
  541. this.$emit('change', data)
  542. this.adddataxm = {}
  543. } else {
  544. Modal.warning({
  545. title: '提示',
  546. content: '选择的子表数据不允许删除,请检查!',
  547. })
  548. return false
  549. }
  550. } else {
  551. data.splice(i, 1)
  552. this.$emit('change', data)
  553. this.adddataxm = {}
  554. }
  555. this.value = data
  556. break
  557. case 'edit':
  558. // 判断当前动态表格中是否有已打开编辑的行,如果有则需要保存后才能再新增
  559. if (editrow !== undefined) {
  560. Modal.warning({
  561. title: '提示',
  562. content: '请先保存动态表格中内容!',
  563. })
  564. return false
  565. }
  566. if (data[i].editable === undefined || data[i].editable === false) {
  567. data[i].editable = true
  568. const value = data[i]
  569. data.splice(i, 1)
  570. data.splice(i, 0, value)
  571. this.$emit('change', data)
  572. this.editRecord = JSON.parse(JSON.stringify(this.fields))
  573. this.editRecord[0].editable = true
  574. this.editRecord.forEach((item) => {
  575. // 把值放到字段信息fields里
  576. item.value = value[item.name]
  577. if (item.attr.controlType === 'DictType') {
  578. if (item.attr.selectListItem.find((t) => t.value === item.value) !== undefined) {
  579. item.attr.displayValue = item.attr.selectListItem.find(
  580. (t) => t.value === item.value
  581. ).label
  582. }
  583. }
  584. })
  585. this.adddataxm = value
  586. }
  587. break
  588. }
  589. },
  590. fnsettablesize() {
  591. const obj = this.$refs.sdtable
  592. obj.columnInfos.forEach((col) => {
  593. col.width = col.defaultWidth
  594. col.show = col.defaultShow
  595. })
  596. },
  597. },
  598. }
  599. </script>
  600. <style module lang="scss">
  601. @use '@/common/design' as *;
  602. @use "@/common/components/sd-web-print.scss" as print;
  603. .header {
  604. position: absolute;
  605. top: 0;
  606. right: 0;
  607. :global .ant-input-search {
  608. width: 200px;
  609. }
  610. :global .ant-btn {
  611. margin: 5px;
  612. }
  613. }
  614. .caption {
  615. // position: relative;
  616. position: absolute;
  617. width: 100%;
  618. min-height: 40px;
  619. margin: 3px 0;
  620. text-align: center;
  621. }
  622. .wrapper {
  623. :global(.ant-table-tbody) {
  624. .clickable-cell {
  625. color: $primary-color;
  626. cursor: pointer;
  627. &:hover {
  628. color: $primary-5;
  629. }
  630. &:active {
  631. color: $primary-7;
  632. }
  633. }
  634. }
  635. }
  636. // 打印状态下 子表上选择列的按钮
  637. @include print.wrapper-for-printer {
  638. .wrapper {
  639. :global(.ant-table-thead .anticon) {
  640. display: none;
  641. }
  642. }
  643. }
  644. .operatecol {
  645. // display: flex;
  646. // flex-wrap: wrap;
  647. justify-content: flex-end;
  648. width: 100px;
  649. :global(.ant-btn-sm) {
  650. padding: 0 0;
  651. }
  652. :global(.menu_sd-table-header_common) {
  653. display: none;
  654. }
  655. }
  656. .sdFormforxmchild {
  657. :global(.ant-upload.ant-upload-drag p.ant-upload-drag-icon) {
  658. margin-bottom: 0;
  659. }
  660. :global(.ant-upload-text) {
  661. display: none !important;
  662. }
  663. :global(.ant-upload.ant-upload-drag .ant-upload) {
  664. padding: 0 0;
  665. }
  666. :global(.ant-form-item) {
  667. margin-bottom: 6px;
  668. }
  669. :global(.actionlist_sd-attachment_common > span) {
  670. overflow: hidden;
  671. text-overflow: ellipsis;
  672. white-space: nowrap;
  673. }
  674. :global(.actionlist_sd-attachment_common > span :hover) {
  675. white-space: normal;
  676. // overflow: auto;
  677. }
  678. :global(.ant-upload.ant-upload-drag) {
  679. text-align: left;
  680. background: none;
  681. border: none;
  682. }
  683. :global(.ant-upload.ant-upload-drag p.ant-upload-drag-icon .anticon) {
  684. font-size: 28px;
  685. }
  686. :global(.form_sd-form_common > .ant-form-item > .ant-form-item-control-wrapper) {
  687. width: 100%;
  688. }
  689. .loading {
  690. position: fixed;
  691. top: 310px;
  692. left: 50%;
  693. text-align: center;
  694. }
  695. :global(.ant-calendar-cell) {
  696. border: none !important;
  697. }
  698. }
  699. </style>