index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. <script setup lang="ts">
  2. import AForm from '@/components/AForm/index.vue'
  3. import { FormConfigType } from '@/components/AForm/type'
  4. import { ToolbarConfigType } from '@/components/AToolbar/type'
  5. import { ColumnConfigType } from '@/components/ATable/type'
  6. import { StrAnyObj, StrAnyObjArr } from '@/typings'
  7. import { useHandleData } from '@/utils/useHandleData'
  8. import {
  9. addApi,
  10. deleteApi,
  11. editApi,
  12. excelExportApi,
  13. getDetailApi,
  14. getPageApi,
  15. retrieveApi
  16. } from '@/api/business/payment/requests'
  17. import { getPageApi as getCorporationPageApi } from '@/api/business/corporation/corporation'
  18. import DeptTreeSelect from '@/views/components/DeptTreeSelect/index.vue'
  19. import { getPageApi as getCapitalAccountPageApi } from '@/api/business/capital/account'
  20. import { getDictByCode } from '@/utils/dict'
  21. import MoneyPDF from '@/components/PDF/moneyPDF.vue'
  22. import { getPdf } from '@/utils/getPdf.js'
  23. const queryRef = ref<InstanceType<typeof AForm>>()
  24. const formRef = ref<InstanceType<typeof AForm>>()
  25. const showQuery = ref<boolean>(true)
  26. const selectKeys = ref<string[]>([])
  27. const pageTotal = ref<number>(0)
  28. const queryData = ref<StrAnyObj>({ pageNum: 1, pageSize: 10 })
  29. const tableData = ref<StrAnyObjArr>([])
  30. const formData = ref<StrAnyObj>({ paymentRequestsDetailList: [{}] })
  31. const dialogTitle = ref<string>('')
  32. const dialogVisible = ref<boolean>(false)
  33. const dialogPrint = ref<boolean>(false)
  34. const rowData = ref<StrAnyObj>({})
  35. const deptIdRef = ref<InstanceType<typeof DeptTreeSelect>>()
  36. const selectDeptIdRef = ref<InstanceType<typeof DeptTreeSelect>>()
  37. const disabled = ref(false)
  38. const queryConfig: FormConfigType[] = [
  39. {
  40. type: 'select',
  41. prop: 'corporationId',
  42. label: '归属公司',
  43. keyName: 'id',
  44. labelName: 'name',
  45. async option() {
  46. const data = await getCorporationPageApi({ searchAll: true })
  47. return data.records
  48. }
  49. },
  50. {
  51. type: 'slot',
  52. prop: 'deptId',
  53. label: '归属部门'
  54. },
  55. {
  56. type: 'select',
  57. prop: 'type',
  58. label: '请款类型',
  59. dict: 'payment_requests_type'
  60. },
  61. {
  62. type: 'select',
  63. prop: 'payType',
  64. label: '付款方式',
  65. dict: 'pay_type'
  66. },
  67. {
  68. type: 'select',
  69. prop: 'approvalStatus',
  70. label: '审批状态',
  71. option: [
  72. {
  73. key: 0,
  74. label: '待提交'
  75. },
  76. {
  77. key: 1,
  78. label: '审批中'
  79. },
  80. {
  81. key: 2,
  82. label: '审批通过'
  83. },
  84. {
  85. key: 4,
  86. label: '终止'
  87. },
  88. {
  89. key: 6,
  90. label: '撤销'
  91. },
  92. {
  93. key: 7,
  94. label: '取回'
  95. }
  96. ]
  97. },
  98. {
  99. type: 'select',
  100. prop: 'paymentStatus',
  101. label: '放款状态',
  102. option: [
  103. {
  104. key: 0,
  105. label: '未打款'
  106. },
  107. {
  108. key: 1,
  109. label: '部分打款'
  110. },
  111. {
  112. key: 2,
  113. label: '已打款'
  114. }
  115. ]
  116. }
  117. ]
  118. const toolbarConfig: ToolbarConfigType[] = [
  119. {
  120. common: 'search',
  121. click() {
  122. queryData.value.pageNum = 1
  123. getPage()
  124. }
  125. },
  126. {
  127. common: 'reset',
  128. click() {
  129. queryRef.value?.resetFields()
  130. getPage()
  131. }
  132. },
  133. {
  134. common: 'add',
  135. click() {
  136. disabled.value = false
  137. dialogVisible.value = true
  138. dialogTitle.value = '新增'
  139. nextTick(() => deptIdRef.value?.load())
  140. }
  141. },
  142. {
  143. text: '导出',
  144. icon: 'Download',
  145. type: 'primary',
  146. click() {
  147. excelExportApi(queryData.value).then(() => {
  148. ElMessage.success('导出成功')
  149. })
  150. }
  151. }
  152. ]
  153. const columnConfig: ColumnConfigType[] = [
  154. {
  155. prop: 'corporationName',
  156. label: '归属公司'
  157. },
  158. {
  159. prop: 'deptName',
  160. label: '归属部门'
  161. },
  162. {
  163. prop: 'type',
  164. label: '请款类型',
  165. dict: 'payment_requests_type'
  166. },
  167. {
  168. prop: 'userName',
  169. label: '请款人'
  170. },
  171. {
  172. prop: 'createTime',
  173. label: '请款时间'
  174. },
  175. {
  176. prop: 'useTime',
  177. label: '用款时间'
  178. },
  179. {
  180. prop: 'useRemark',
  181. label: '用款说明',
  182. showOverflowTooltip: true
  183. },
  184. {
  185. prop: 'totalAmount',
  186. label: '请款金额'
  187. },
  188. {
  189. prop: 'payType',
  190. label: '付款方式',
  191. dict: 'pay_type'
  192. },
  193. {
  194. prop: 'capitalAccountName',
  195. label: '付款账户'
  196. },
  197. {
  198. prop: 'approvalStatus',
  199. label: '审批状态',
  200. formatter(row) {
  201. switch (row.approvalStatus) {
  202. case 0:
  203. return '待提交'
  204. case 1:
  205. return '审批中'
  206. case 2:
  207. return '审批通过'
  208. case 4:
  209. return '驳回'
  210. case 6:
  211. return '撤销'
  212. case 7:
  213. return '取回'
  214. default:
  215. return ''
  216. }
  217. }
  218. },
  219. {
  220. prop: 'paymentStatus',
  221. label: '放款状态',
  222. formatter(row) {
  223. switch (row.paymentStatus) {
  224. case 0:
  225. return '未打款'
  226. case 1:
  227. return '部分打款'
  228. case 2:
  229. return '已打款'
  230. default:
  231. return ''
  232. }
  233. }
  234. },
  235. {
  236. width: 250,
  237. handleConfig: [
  238. {
  239. text: '取回',
  240. if(row){
  241. return row.approvalStatus == 1
  242. },
  243. click(row) {
  244. retrieveApi({ businessId: row.id }).then(() => {
  245. ElMessage.success('取回成功')
  246. getPage()
  247. })
  248. }
  249. },
  250. {
  251. text: '重新发起',
  252. if(row) {
  253. return row.approvalStatus == 0 || row.approvalStatus == 7
  254. },
  255. click(row) {
  256. dialogVisible.value = true
  257. dialogTitle.value = '重新发起'
  258. disabled.value = false
  259. getDetailApi({ id: row.id }).then((resp: StrAnyObj) => {
  260. formData.value = resp
  261. })
  262. nextTick(() => deptIdRef.value?.load())
  263. }
  264. },
  265. {
  266. common: 'detail',
  267. click(row) {
  268. dialogVisible.value = true
  269. dialogTitle.value = '详情'
  270. disabled.value = true
  271. nextTick(() => deptIdRef.value?.load())
  272. getDetailApi({ id: row.id }).then((resp: StrAnyObj) => {
  273. formData.value = resp
  274. })
  275. }
  276. },
  277. {
  278. text: '打印',
  279. click(row) {
  280. rowData.value = row
  281. dialogPrint.value = true
  282. }
  283. }
  284. ]
  285. }
  286. ]
  287. const formConfig: FormConfigType[] = [
  288. {
  289. type: 'select',
  290. prop: 'corporationId',
  291. label: '归属公司',
  292. keyName: 'id',
  293. labelName: 'name',
  294. async option() {
  295. const data = await getCorporationPageApi({ searchAll: true })
  296. return data.records
  297. },
  298. rule: [{ required: true, message: '归属公司id不能为空', trigger: 'blur' }]
  299. },
  300. {
  301. type: 'slot',
  302. prop: 'deptId',
  303. label: '归属部门',
  304. rule: [{ required: true, message: '部门id不能为空', trigger: 'blur' }]
  305. },
  306. {
  307. type: 'select',
  308. prop: 'type',
  309. label: '请款类型',
  310. dict: 'payment_requests_type',
  311. rule: [{ required: true, message: '请款类型不能为空', trigger: 'blur' }]
  312. },
  313. {
  314. type: 'datePicker',
  315. prop: 'useTime',
  316. label: '用款时间',
  317. datePickerType: 'datetime',
  318. format: 'YYYY-MM-DD HH:mm:ss',
  319. valueFormat: 'YYYY-MM-DD HH:mm:ss'
  320. },
  321. {
  322. type: 'input',
  323. itemType: 'textarea',
  324. prop: 'useRemark',
  325. label: '用款说明',
  326. rows: 3,
  327. span: 24,
  328. placeholder: '自动拼接请款明细中的款项说明',
  329. disabled: true
  330. },
  331. {
  332. type: 'upload',
  333. prop: 'atts',
  334. label: '上传附件',
  335. span: 24
  336. },
  337. {
  338. type: 'slot',
  339. prop: 'detailTable',
  340. label: '请款明细',
  341. span: 24
  342. },
  343. {
  344. type: 'input',
  345. prop: 'totalAmount',
  346. label: '付款总金额',
  347. disabled: true,
  348. placeholder: '自动统计请款明细中的请款金额'
  349. },
  350. {
  351. type: 'inputNumber',
  352. prop: 'documentQuantity',
  353. label: '单据数量',
  354. min: 0,
  355. precision: 0,
  356. rule: [{ required: true, message: '单据数量不能为空', trigger: 'blur' }]
  357. },
  358. {
  359. type: 'select',
  360. prop: 'payType',
  361. label: '付款方式',
  362. dict: 'pay_type',
  363. rule: [{ required: true, message: '付款方式不能为空', trigger: 'blur' }]
  364. },
  365. {
  366. type: 'select',
  367. prop: 'capitalAccountId',
  368. label: '付款账户',
  369. keyName: 'id',
  370. labelName: 'accountAlias',
  371. async option() {
  372. const data = await getCapitalAccountPageApi({ searchAll: true })
  373. return data.records
  374. }
  375. },
  376. {
  377. type: 'input',
  378. prop: 'accountName',
  379. label: '户名'
  380. },
  381. {
  382. type: 'input',
  383. prop: 'account',
  384. label: '银行账号'
  385. },
  386. {
  387. type: 'input',
  388. prop: 'depositBank',
  389. label: '开户银行'
  390. },
  391. {
  392. type: 'input',
  393. prop: 'correspondentNumber',
  394. label: '联行号/SWIFT Code'
  395. }
  396. ]
  397. const detailTableColumnConfig: ColumnConfigType[] = [
  398. {
  399. slot: 'expenseType',
  400. label: '费用类型',
  401. width: 250
  402. },
  403. {
  404. slot: 'remark',
  405. label: '款项说明'
  406. },
  407. {
  408. slot: 'amount',
  409. label: '请款金额',
  410. width: 180
  411. },
  412. {
  413. width: 100,
  414. if: () => !disabled.value,
  415. handleConfig: [
  416. {
  417. common: 'delete',
  418. click(row, index) {
  419. formData.value.paymentRequestsDetailList.splice(index, 1)
  420. updateAmount()
  421. }
  422. }
  423. ]
  424. }
  425. ]
  426. onMounted(() => {
  427. getPage()
  428. selectDeptIdRef.value?.load()
  429. })
  430. function getPage() {
  431. getPageApi(queryData.value).then((resp) => {
  432. tableData.value = resp.records
  433. pageTotal.value = resp.total
  434. })
  435. }
  436. function tableSelectionChange(item: StrAnyObjArr) {
  437. selectKeys.value = item.map((item) => item.id)
  438. }
  439. function formSubmit() {
  440. formRef.value?.validate(() => {
  441. if (formData.value.paymentRequestsDetailList.length === 0) {
  442. ElMessage.error('请款明细为空,无法提交')
  443. return
  444. }
  445. for (const item of formData.value.paymentRequestsDetailList) {
  446. if (!item.expenseType) {
  447. ElMessage.error('请款明细存在费用类型为空,无法提交')
  448. return
  449. }
  450. if (!item.remark) {
  451. ElMessage.error('请款明细存在款项说明为空,无法提交')
  452. return
  453. }
  454. if (!item.amount) {
  455. ElMessage.error('请款明细存在请款金额为空,无法提交')
  456. return
  457. }
  458. }
  459. if (formData.value.id) {
  460. editApi(formData.value).then(() => {
  461. dialogVisible.value = false
  462. ElMessage.success('修改成功')
  463. getPage()
  464. })
  465. } else {
  466. addApi(formData.value).then(() => {
  467. dialogVisible.value = false
  468. ElMessage.success('新增成功')
  469. getPage()
  470. })
  471. }
  472. })
  473. }
  474. function formClosed() {
  475. formRef.value?.resetFields()
  476. }
  477. function handleRemove(idList: string[]) {
  478. useHandleData('是否确认删除?', () => {
  479. deleteApi({ idList }).then(() => {
  480. ElMessage.success('删除成功')
  481. getPage()
  482. })
  483. })
  484. }
  485. function addPaymentRequestsDetailList() {
  486. formData.value.paymentRequestsDetailList.push({})
  487. }
  488. async function updateDetailRemark() {
  489. const expenseTypes = await getDictByCode('expense_type')
  490. const expenseMap = new Map<number, string>()
  491. for (const item of expenseTypes) {
  492. expenseMap.set(item.value, item.label)
  493. }
  494. formData.value.useRemark = formData.value.paymentRequestsDetailList
  495. .map(
  496. (item) =>
  497. `【${expenseMap.get(item.expenseType) ?? ''}】 - ${item.remark ?? ''} - ${item.amount ?? ''}`
  498. )
  499. .join('\n')
  500. }
  501. function updateAmount() {
  502. formData.value.totalAmount = formData.value.paymentRequestsDetailList
  503. .map((item) => item.amount)
  504. .filter((item) => item !== '' && item !== null && item !== undefined)
  505. .reduce((pre, next) => pre + next, 0)
  506. updateDetailRemark()
  507. }
  508. const printObj = ref({
  509. id: 'pdfDom',
  510. popTitle: '',
  511. extraCss:
  512. 'https://cdn.bootcdn.net/ajax/libs/animate.css/4.1.1/animate.compat.css, https://cdn.bootcdn.net/ajax/libs/hover.css/2.3.1/css/hover-min.css',
  513. extraHead: '<meta http-equiv="Content-Language"content="zh-cn"/>'
  514. })
  515. const clickDownload = () => {
  516. getPdf('请款PDF文件')
  517. }
  518. </script>
  519. <template>
  520. <div>
  521. <el-card v-if="showQuery">
  522. <a-form ref="queryRef" v-model="queryData" :config="queryConfig" :span="6">
  523. <template #deptId>
  524. <dept-tree-select ref="selectDeptIdRef" v-model="formData.deptId" />
  525. </template>
  526. </a-form>
  527. </el-card>
  528. <a-table
  529. :data="tableData"
  530. :page-total="pageTotal"
  531. :toolbar-config="toolbarConfig"
  532. :column-config="columnConfig"
  533. v-model:showQuery="showQuery"
  534. v-model:page-num="queryData.pageNum"
  535. v-model:page-size="queryData.pageSize"
  536. @page-num-change="getPage"
  537. @page-size-change="getPage"
  538. @selection-change="tableSelectionChange"
  539. >
  540. </a-table>
  541. <a-dialog
  542. v-if="dialogVisible"
  543. v-model="dialogVisible"
  544. :title="dialogTitle"
  545. :footer="!disabled"
  546. @submit="formSubmit"
  547. @closed="formClosed"
  548. width="1400px"
  549. height="200px"
  550. >
  551. <a-form ref="formRef" v-model="formData" :config="formConfig" :span="12" :disabled="disabled">
  552. <template #deptId>
  553. <dept-tree-select ref="deptIdRef" v-model="formData.deptId" :disabled="disabled" />
  554. </template>
  555. <template #detailTable>
  556. <a-table
  557. :data="formData.paymentRequestsDetailList"
  558. :columnConfig="detailTableColumnConfig"
  559. style="width: 100%"
  560. :card="false"
  561. >
  562. <template #expenseType="scope">
  563. <a-select
  564. v-model="scope.row.expenseType"
  565. dict="expense_type"
  566. :disabled="disabled"
  567. @change="updateDetailRemark"
  568. />
  569. </template>
  570. <template #remark="scope">
  571. <a-input
  572. v-model="scope.row.remark"
  573. type="textarea"
  574. :rows="2"
  575. @change="updateDetailRemark"
  576. :disabled="disabled"
  577. />
  578. </template>
  579. <template #amount="scope">
  580. <a-inputNumber
  581. v-model="scope.row.amount"
  582. :min="0.01"
  583. :precision="2"
  584. @change="updateAmount"
  585. :disabled="disabled"
  586. />
  587. </template>
  588. </a-table>
  589. <el-button
  590. v-if="!disabled"
  591. style="width: 100%; margin-bottom: 20px"
  592. type="primary"
  593. @click="addPaymentRequestsDetailList"
  594. >
  595. 添加行
  596. </el-button>
  597. </template>
  598. </a-form>
  599. </a-dialog>
  600. <a-dialog :footer="false" v-if="dialogPrint" v-model="dialogPrint" title="打印" width="840px">
  601. <MoneyPDF :rowData="rowData"></MoneyPDF>
  602. <template #footer>
  603. <div>
  604. <el-button @click="dialogPrint = false" size="large">取消</el-button>
  605. <el-button type="primary" v-print="printObj" size="large">打印</el-button>
  606. <el-button type="primary" @click="clickDownload()" size="large">下载PDF</el-button>
  607. </div>
  608. </template>
  609. </a-dialog>
  610. </div>
  611. </template>