index.vue 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <!-- 收款明细表单公共组件 -->
  2. <!-- 职责:收款信息汇总区 + 新增按钮 + 可编辑表格(列/填写/校验/汇总内置),form 由父组件传入 -->
  3. <template>
  4. <div>
  5. <!-- 收款信息汇总区 -->
  6. <headerTitle :title="title"></headerTitle>
  7. <div style="margin: 10px 0; font-size: 14px; color: #606266;">
  8. <span>{{ totalLabel }}:{{ totalPrice || 0 }} 元</span>
  9. <span style="margin-left: 20px;">已收金额:{{ receivedAmount }} 元</span>
  10. <span style="margin-left: 20px;">未收金额:{{ unreceivedAmount }} 元</span>
  11. </div>
  12. <el-button
  13. v-if="!isView"
  14. size="small"
  15. type="primary"
  16. icon="el-icon-plus"
  17. class="ele-btn-icon"
  18. style="margin-bottom: 10px;"
  19. @click="addRow"
  20. >
  21. 新增
  22. </el-button>
  23. <el-form ref="form" :model="form" class="table-form">
  24. <ele-pro-table
  25. ref="table"
  26. :needPage="false"
  27. :columns="columns"
  28. :datasource="form.tableData"
  29. :toolkit="[]"
  30. >
  31. <!-- 收款时间 -->
  32. <template v-slot:receivedDate="{ row: r, $index }">
  33. <el-form-item
  34. :prop="`tableData.${$index}.receivedDate`"
  35. :rules="{ required: true, message: '请选择收款时间', trigger: 'change' }"
  36. class="table-form-item"
  37. >
  38. <el-date-picker
  39. v-model="r.receivedDate"
  40. type="date"
  41. placeholder="年/月/日"
  42. value-format="yyyy-MM-dd"
  43. style="width: 100%"
  44. size="small"
  45. :disabled="isView || r?.id"
  46. />
  47. </el-form-item>
  48. </template>
  49. <template v-slot:columnRequired="{ column }">
  50. <span class="is-required">{{ column.label }}</span>
  51. </template>
  52. <!-- 已收金额 -->
  53. <template v-slot:receivedTotalPrice="{ row: r, $index }">
  54. <el-form-item
  55. :prop="`tableData.${$index}.receivedTotalPrice`"
  56. :rules="{ required: true, message: '请输入已收金额', trigger: 'blur' }"
  57. class="table-form-item"
  58. >
  59. <el-input
  60. v-model="r.receivedTotalPrice"
  61. placeholder="请输入"
  62. size="small"
  63. :disabled="isView || r?.id"
  64. @input="calcAmount"
  65. />
  66. </el-form-item>
  67. </template>
  68. <!-- 支付类型 -->
  69. <template v-slot:paymentType="{ row: r, $index }">
  70. <el-form-item
  71. :prop="`tableData.${$index}.paymentType`"
  72. :rules="{ required: true, message: '请选择支付类型', trigger: 'change' }"
  73. class="table-form-item"
  74. >
  75. <DictSelection
  76. v-model="r.paymentType"
  77. dictName="支付类型"
  78. placeholder="请选择"
  79. class="payment-select"
  80. :disabled="isView || r?.id"
  81. />
  82. </el-form-item>
  83. </template>
  84. <!-- 附件 -->
  85. <template v-slot:files="{ row: r }">
  86. <fileMain v-model="r.files" :limit="1" :type="isView || r?.id ? 'view' : 'add'"></fileMain>
  87. </template>
  88. <!-- 备注 -->
  89. <template v-slot:remark="{ row: r }">
  90. <el-input
  91. v-model="r.remark"
  92. placeholder="请输入"
  93. size="small"
  94. :disabled="isView || r?.id"
  95. />
  96. </template>
  97. <!-- 操作 -->
  98. <template v-slot:action="{ row: r }">
  99. <el-link
  100. v-if="!isView && !r?.id"
  101. type="danger"
  102. :underline="false"
  103. @click="removeRow(r)"
  104. >
  105. 删除
  106. </el-link>
  107. </template>
  108. </ele-pro-table>
  109. </el-form>
  110. </div>
  111. </template>
  112. <script>
  113. import headerTitle from '@/components/header-title';
  114. import fileMain from '@/components/addDoc/index.vue';
  115. export default {
  116. name: 'CollectTableForm',
  117. components: {
  118. headerTitle,
  119. fileMain
  120. },
  121. props: {
  122. title: {
  123. type: String,
  124. default: '收款详情'
  125. },
  126. // 表单数据(含 tableData),.sync 双向,父组件传入
  127. form: {
  128. type: Object,
  129. default: () => ({ tableData: [] })
  130. },
  131. // 汇总区总金额(如预收金额/应收金额)
  132. totalPrice: {
  133. type: [Number, String],
  134. default: 0
  135. },
  136. // 汇总区总金额标签(如"预收金额"/"应收金额")
  137. totalLabel: {
  138. type: String,
  139. default: '预收金额'
  140. },
  141. // 新增行的空白模板
  142. rowTemplate: {
  143. type: Object,
  144. default: () => ({
  145. receivedDate: '',
  146. receivedTotalPrice: '',
  147. paymentType: '',
  148. files: [],
  149. remark: ''
  150. })
  151. },
  152. // 弹窗类型:view 查看模式禁用编辑,其它(add/update)可编辑
  153. dialogType: {
  154. type: String,
  155. default: 'update'
  156. }
  157. },
  158. data() {
  159. return {};
  160. },
  161. computed: {
  162. // 查看模式:禁用所有编辑
  163. isView() {
  164. return this.dialogType === 'view';
  165. },
  166. columns() {
  167. return [
  168. {
  169. type: 'index',
  170. label: '序号',
  171. width: 55,
  172. align: 'center',
  173. fixed: 'left'
  174. },
  175. {
  176. prop: 'receivedDate',
  177. headerSlot: 'columnRequired',
  178. label: '收款时间',
  179. align: 'center',
  180. minWidth: 160,
  181. slot: 'receivedDate'
  182. },
  183. {
  184. prop: 'receivedTotalPrice',
  185. headerSlot: 'columnRequired',
  186. label: '已收金额',
  187. align: 'center',
  188. minWidth: 140,
  189. slot: 'receivedTotalPrice'
  190. },
  191. {
  192. prop: 'paymentType',
  193. headerSlot: 'columnRequired',
  194. label: '支付类型',
  195. align: 'center',
  196. minWidth: 140,
  197. slot: 'paymentType'
  198. },
  199. {
  200. prop: 'files',
  201. label: '附件',
  202. align: 'center',
  203. minWidth: 120,
  204. slot: 'files'
  205. },
  206. {
  207. prop: 'remark',
  208. label: '备注',
  209. align: 'center',
  210. minWidth: 140,
  211. slot: 'remark'
  212. },
  213. {
  214. columnKey: 'action',
  215. label: '操作',
  216. width: 80,
  217. align: 'center',
  218. resizable: false,
  219. slot: 'action',
  220. fixed: 'right',
  221. show: !this.isView
  222. }
  223. ];
  224. },
  225. receivedAmount() {
  226. console.log('this.form.tableData', this.form.tableData)
  227. return this.form.tableData?.reduce((sum, item) => {
  228. const val = parseFloat(item.receivedTotalPrice);
  229. return sum + (isNaN(val) ? 0 : val);
  230. }, 0).toFixed(2);
  231. },
  232. unreceivedAmount() {
  233. const total = parseFloat(this.totalPrice) || 0;
  234. const received = parseFloat(this.receivedAmount) || 0;
  235. return (total - received).toFixed(2);
  236. }
  237. },
  238. methods: {
  239. addRow() {
  240. console.log('this.form.tableData', this.form.tableData)
  241. if (this.isView) return;
  242. this.form.tableData.push({ ...this.rowTemplate });
  243. this.calcAmount();
  244. },
  245. removeRow(row) {
  246. if (this.isView) return;
  247. const index = this.form.tableData.indexOf(row);
  248. if (index !== -1) {
  249. this.form.tableData.splice(index, 1);
  250. }
  251. this.calcAmount();
  252. },
  253. calcAmount() {
  254. this.$forceUpdate();
  255. },
  256. // 校验:查看模式直接通过,否则至少一条 + 各列必填
  257. validate() {
  258. return new Promise((resolve, reject) => {
  259. if (this.isView) {
  260. resolve(true);
  261. return;
  262. }
  263. if (this.form.tableData.length === 0) {
  264. this.$message.warning('请至少添加一条收款记录');
  265. reject(false);
  266. return;
  267. }
  268. this.$refs.form.validate((valid) => {
  269. valid ? resolve(true) : reject(false);
  270. });
  271. });
  272. },
  273. // 获取整个表单数据,供父组件提交
  274. getData() {
  275. return {
  276. form: this.form,
  277. tableData: this.form.tableData,
  278. receivedAmount: this.receivedAmount,
  279. unreceivedAmount: this.unreceivedAmount
  280. };
  281. }
  282. }
  283. };
  284. </script>
  285. <style lang="scss" scoped>
  286. .table-form-item {
  287. margin-bottom: 0 !important;
  288. }
  289. </style>