index.vue 13 KB

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