Index.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. <template>
  2. <el-form ref="form" :model="form" :rules="rules">
  3. <ele-pro-table
  4. ref="table"
  5. :needPage="false"
  6. :columns="columns"
  7. :datasource="form.datasource"
  8. :toolkit="[]"
  9. class="time-form"
  10. >
  11. <!-- 表头工具栏 -->
  12. <template v-slot:toolbar>
  13. <div class="headbox">
  14. <el-button
  15. size="small"
  16. type="primary"
  17. icon="el-icon-plus"
  18. class="ele-btn-icon"
  19. @click="handlAdd"
  20. v-if="showAddBtn"
  21. >
  22. 新增
  23. </el-button>
  24. <div class="pricebox">
  25. <span class="amount">比例合计:{{ allRatio }}%</span>
  26. <span class="amount">计划{{ menu === 'purchase' ? '收款' : '付款' }}金额合计:{{ allPrice }}元</span>
  27. <!-- <el-form-item
  28. style="width: 300px"
  29. label="优惠后总金额:"
  30. prop="discountTotalPrice"
  31. :rules="{
  32. required: true,
  33. message: '请输入优惠后总金额',
  34. trigger: 'change'
  35. }"
  36. >
  37. <el-input
  38. type="number"
  39. :min="0"
  40. :max="allPrice"
  41. :disabled="!allPrice"
  42. v-model="form.discountTotalPrice"
  43. style="width: 180px"
  44. placeholder="请输入"
  45. @input="discountInputByOrder(form.discountTotalPrice)"
  46. >
  47. <template slot="append">元</template>
  48. </el-input>
  49. </el-form-item> -->
  50. </div>
  51. </div>
  52. </template>
  53. <template v-slot:headerIssueNumber="{ column }">
  54. <span class="is-required">{{ column.label }}</span>
  55. </template>
  56. <template v-slot:headerType="{ column }">
  57. <span class="is-required">{{ column.label }}</span>
  58. </template>
  59. <template v-slot:issueNumber="scope">
  60. <span>第{{ scope.row.issueNumber }}期</span>
  61. </template>
  62. <template v-slot:moneyName="scope">
  63. <el-form-item
  64. style="margin-bottom: 20px"
  65. :prop="'datasource.' + scope.$index + '.moneyName'"
  66. :rules="{
  67. required: true,
  68. message: '请输入',
  69. trigger: 'blur'
  70. }"
  71. >
  72. <el-input
  73. v-model="scope.row.moneyName"
  74. placeholder="请输入"
  75. :disabled="type=='view'"
  76. ></el-input>
  77. </el-form-item>
  78. </template>
  79. <template v-slot:headerMoneyName="{ column }">
  80. <span class="is-required">{{ column.label }}</span>
  81. </template>
  82. <template v-slot:ratio="scope">
  83. <el-form-item
  84. style="margin-bottom: 20px"
  85. :prop="'datasource.' + scope.$index + '.ratio'"
  86. :rules="{
  87. required: true,
  88. pattern: numberReg,
  89. message: '请输入正确的比例',
  90. trigger: 'change'
  91. }"
  92. >
  93. <el-input
  94. v-model="scope.row.ratio"
  95. placeholder="请输入"
  96. :disabled="type=='view'"
  97. @input="(val) => ratioInput(val, scope.$index)"
  98. >
  99. <template slot="append">%</template>
  100. </el-input>
  101. </el-form-item>
  102. </template>
  103. <template v-slot:type="scope">
  104. <el-form-item
  105. style="margin-bottom: 20px"
  106. :prop="'datasource.' + scope.$index + '.type'"
  107. :rules="{
  108. required: true,
  109. message: '请选择款项类型',
  110. trigger: 'change'
  111. }"
  112. >
  113. <el-select
  114. v-model="scope.row.type"
  115. placeholder="请选择"
  116. style="width: 100%"
  117. :disabled="type=='view'"
  118. @change="(val) => typeChange(val, scope.$index)"
  119. >
  120. <el-option
  121. v-for="item in paymentTypeOp"
  122. :key="item.value"
  123. :label="item.label"
  124. :value="item.value"
  125. >
  126. </el-option>
  127. </el-select>
  128. </el-form-item>
  129. </template>
  130. <template v-slot:headerRatio="{ column }">
  131. <span class="is-required">{{ column.label }}</span>
  132. </template>
  133. <template v-slot:headerPrice="{ column }">
  134. <span class="is-required">{{ column.label }}</span>
  135. </template>
  136. <template v-slot:price="scope">
  137. <el-form-item
  138. style="margin-bottom: 20px"
  139. :prop="'datasource.' + scope.$index + '.price'"
  140. :rules="{
  141. required: true,
  142. pattern: numberReg,
  143. message: '请输入正确的金额',
  144. trigger: 'change'
  145. }"
  146. >
  147. <el-input
  148. type="number"
  149. :min="0"
  150. :disabled="type=='view'"
  151. v-model="scope.row.price"
  152. style="width: 100%"
  153. placeholder="请输入"
  154. >
  155. <template slot="append">元</template>
  156. </el-input>
  157. </el-form-item>
  158. </template>
  159. <template v-slot:deadLine="scope">
  160. <el-form-item
  161. style="margin-bottom: 20px"
  162. :prop="'datasource.' + scope.$index + '.deadLine'"
  163. :rules="{
  164. required: true,
  165. message: '请选择日期',
  166. trigger: 'change'
  167. }"
  168. >
  169. <el-date-picker
  170. style="width: 140px"
  171. v-model="scope.row.deadLine"
  172. :disabled="!showAddBtn"
  173. type="date"
  174. placeholder="选择日期"
  175. >
  176. </el-date-picker>
  177. </el-form-item>
  178. </template>
  179. <template v-slot:headerDeadLine="{ column }">
  180. <span class="is-required">{{ column.label }}</span>
  181. </template>
  182. <template v-slot:remark="scope">
  183. <el-form-item
  184. style="margin-bottom: 20px"
  185. :prop="'datasource.' + scope.$index + '.remark'"
  186. >
  187. <el-input
  188. v-model="scope.row.remark"
  189. type="textarea"
  190. placeholder="请输入"
  191. :disabled="type=='view'"
  192. ></el-input>
  193. </el-form-item>
  194. </template>
  195. <!-- 操作列 -->
  196. <template v-slot:action="{ row }">
  197. <el-popconfirm
  198. class="ele-action"
  199. title="确定要删除吗?"
  200. @confirm="remove(row)"
  201. >
  202. <template v-slot:reference>
  203. <el-link type="danger" :underline="false" icon="el-icon-delete">
  204. 删除
  205. </el-link>
  206. </template>
  207. </el-popconfirm>
  208. </template>
  209. </ele-pro-table>
  210. </el-form>
  211. </template>
  212. <script>
  213. import { emailReg, phoneReg, numberReg } from 'ele-admin';
  214. import { paymentTypeOp } from '@/enum/dict';
  215. export default {
  216. props: {
  217. delDetailIds: Array,
  218. type: String,
  219. menu: {
  220. type: String,
  221. default: 'sale'
  222. },
  223. discountTotalPrice: {
  224. type: [Number, String],
  225. default: 0
  226. },
  227. info: {
  228. type: Object,
  229. default: () => {}
  230. }
  231. },
  232. data() {
  233. const defaultForm = {
  234. key: null,
  235. deadLine: null,
  236. moneyName: '',
  237. price: null,
  238. ratio: null,
  239. remark: '',
  240. type: '',
  241. typeName: '',
  242. issueNumber: null
  243. };
  244. return {
  245. // allPrice: 0,
  246. // allRatio: 0,
  247. numberReg,
  248. defaultForm,
  249. discountAmount: 0,
  250. form: {
  251. datasource: []
  252. },
  253. paymentTypeOp,
  254. rules: {}
  255. };
  256. },
  257. computed: {
  258. canHandl() {
  259. return this.form.datasource.length;
  260. },
  261. showAddBtn() {
  262. // console.log('showAddBtn~~~~', this.type, this.info.settlementMode);
  263. // console.log('showAddBtn!!!!',!['1', '2'].includes(this.info.settlementMode) || this.type != 'view');
  264. return !(['1', '2'].includes(this.info?.settlementMode)) && this.type != 'view'
  265. },
  266. columns() {
  267. return [
  268. {
  269. width: 45,
  270. type: 'index',
  271. columnKey: 'index',
  272. align: 'center',
  273. fixed: 'left'
  274. },
  275. {
  276. width: 180,
  277. prop: 'issueNumber',
  278. label: '期数',
  279. slot: 'issueNumber',
  280. headerSlot: 'headerIssueNumber',
  281. align: 'center'
  282. },
  283. {
  284. width: 150,
  285. prop: 'type',
  286. label: '款项类型',
  287. slot: 'type',
  288. headerSlot: 'headerType',
  289. align: 'center'
  290. },
  291. {
  292. prop: 'moneyName',
  293. label: '款项名称',
  294. slot: 'moneyName',
  295. headerSlot: 'headerMoneyName',
  296. align: 'center',
  297. // width: 170
  298. },
  299. {
  300. width: 150,
  301. prop: 'ratio',
  302. label: '比例',
  303. slot: 'ratio',
  304. headerSlot: 'headerRatio',
  305. align: 'center'
  306. },
  307. {
  308. width: 170,
  309. prop: 'price',
  310. label: this.menu === 'purchase' ? '计划付款金额' : '计划收款金额',
  311. slot: 'price',
  312. align: 'center',
  313. headerSlot: 'headerPrice',
  314. },
  315. {
  316. width: 160,
  317. prop: 'deadLine',
  318. label: this.menu === 'purchase' ? '计划付款日期' : '计划收款日期',
  319. slot: 'deadLine',
  320. headerSlot: 'headerDeadLine',
  321. align: 'center'
  322. },
  323. {
  324. // width: 220,
  325. prop: 'remark',
  326. label: '说明',
  327. slot: 'remark',
  328. align: 'center'
  329. },
  330. {
  331. columnKey: 'action',
  332. label: '操作',
  333. align: 'center',
  334. resizable: false,
  335. slot: 'action',
  336. showOverflowTooltip: true,
  337. show: this.showAddBtn
  338. }
  339. ];
  340. },
  341. allRatio() {
  342. return this.form.datasource.reduce((acc, cur) => acc + Number(cur.ratio), 0).toFixed(2);
  343. },
  344. allPrice() {
  345. return this.form.datasource.reduce((acc, cur) => acc + Number(cur.price), 0).toFixed(2);
  346. },
  347. },
  348. watch: {
  349. discountAmount(newval) {
  350. if (newval) {
  351. this.refreshprice();
  352. }
  353. },
  354. info: {
  355. handler(newval) {
  356. if (newval) {
  357. // this.form = newval;
  358. console.log('111111', newval.settlementMode);
  359. }
  360. },
  361. deep: true
  362. }
  363. },
  364. methods: {
  365. setDiscountAmount(val) {
  366. console.log(val, '000000');
  367. this.discountAmount = val;
  368. },
  369. typeChange(val, index = null) {
  370. console.log(val, index, '55555');
  371. if (val) {
  372. this.$set(this.form.datasource[index], 'typeName', this.paymentTypeOp.find(item => item.value === val).label);
  373. }
  374. },
  375. //输入比例更新金额
  376. async ratioInput(val, index = null) {
  377. console.log(this.discountAmount, '77777');
  378. val = Number(val);
  379. try {
  380. if (index != null) {
  381. await this.checkRatio();
  382. }
  383. let newval = (val / 100).toFixed(2);
  384. let price = (this.discountAmount * newval).toFixed(2);
  385. console.log(newval, price, index, '88888');
  386. if (index != null) {
  387. console.log(newval, price, index, '999999');
  388. this.$set(this.form.datasource[index], 'price', price);
  389. } else {
  390. return price;
  391. }
  392. } catch (error) {
  393. return 0;
  394. }
  395. //this.$set( this.form.datasource[index], 'price', price)
  396. },
  397. //检验比例
  398. checkRatio() {
  399. return new Promise((resolve, reject) => {
  400. let newData = this.form.datasource,
  401. sum = 0;
  402. newData.forEach((r) => {
  403. if (r.ratio) {
  404. sum += Number(r.ratio);
  405. }
  406. });
  407. console.log(sum, '3333333');
  408. if (sum > 100) {
  409. this.$message.error('总共比例不能超过100');
  410. this.$set(
  411. this.form.datasource[this.form.datasource.length - 1],
  412. 'ratio',
  413. null
  414. );
  415. this.$set(
  416. this.form.datasource[this.form.datasource.length - 1],
  417. 'price',
  418. 0.0
  419. );
  420. reject(false);
  421. } else {
  422. resolve(true);
  423. }
  424. });
  425. },
  426. refreshprice() {
  427. console.log(this.form, '666666');
  428. let newData = this.form.datasource;
  429. newData.forEach(async (r, index) => {
  430. if (r.ratio) {
  431. console.log(this.ratioInput(Number(r.ratio)), '9999888888');
  432. r.price = await this.ratioInput(Number(r.ratio));
  433. }
  434. });
  435. },
  436. // 返回列表数据
  437. getTableValue() {
  438. return this.form.datasource;
  439. },
  440. //修改回显
  441. putTableValue(data) {
  442. console.log('data~~~~', data);
  443. if (data) {
  444. this.form.datasource = data.receiptPaymentList;
  445. this.setDiscountAmount(data.payAmount);
  446. }
  447. },
  448. remove(row) {
  449. let index = this.form.datasource.findIndex((n) => n.key == row.key);
  450. if (index !== -1) {
  451. this.form.datasource.splice(index, 1);
  452. this.setSort();
  453. if (row.id) {
  454. this.delDetailIds.push(row.id);
  455. }
  456. this.$emit('getIssueNumber', this.form.datasource.length);
  457. }
  458. },
  459. // 清空表格
  460. restTable() {
  461. this.form.datasource = [];
  462. },
  463. // 重新排序
  464. setSort() {
  465. this.form.datasource.forEach((n, index) => {
  466. n.key = index + 1;
  467. });
  468. },
  469. defaultList(method, issueNumber, dateRange) {
  470. console.log('method, issueNumber', method, issueNumber);
  471. const tempList = []
  472. if(dateRange) {
  473. this.setDefaultList(dateRange, issueNumber);
  474. return
  475. }
  476. if(method == 3) {
  477. let params = ['预付款', '交货款'];
  478. for(let i = 0; i < issueNumber; i++) {
  479. let item = JSON.parse(JSON.stringify(this.defaultForm));
  480. item.moneyName = i < params.length ? params[i] : '';
  481. item.type = i < params.length ? this.paymentTypeOp[i].value : '';
  482. item.typeName = i < params.length ? this.paymentTypeOp[i].label : '';
  483. item.key = i + 1;
  484. item.issueNumber = i + 1;
  485. tempList.push(item);
  486. }
  487. // params.forEach((item, index) => {
  488. // let i = JSON.parse(JSON.stringify(this.defaultForm));
  489. // i.moneyName = item;
  490. // i.type = this.paymentTypeOp[index].value;
  491. // i.key = index + 1;
  492. // i.issueNumber = index + 1;
  493. // tempList.push(i);
  494. // });
  495. } else {
  496. // method == 4 || method == 5 || method == 6 || method == 7 || method == 8
  497. // let params = [''];
  498. // params.forEach((item, index) => {
  499. // let i = JSON.parse(JSON.stringify(this.defaultForm));
  500. // i.key = index + 1;
  501. // i.issueNumber = index + 1;
  502. // tempList.push(i);
  503. // });
  504. for(let i = 0; i < issueNumber; i++) {
  505. let item = JSON.parse(JSON.stringify(this.defaultForm));
  506. item.moneyName = '';
  507. item.type = '';
  508. item.typeName = '';
  509. item.key = i + 1;
  510. item.issueNumber = i + 1;
  511. tempList.push(item);
  512. }
  513. }
  514. console.log('tempList~~~', tempList);
  515. this.form.datasource = tempList;
  516. },
  517. transformDaysFun(date) {
  518. const startDate = new Date(date[0]);
  519. const endDate = new Date(date[1]);
  520. // 计算毫秒差并转换为天数,使用Math.ceil确保结果为整数
  521. const days = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
  522. console.log('包含两头的天数:', days);
  523. // 生成包括头尾在内的所有日期
  524. const allDates = [];
  525. const current = new Date(startDate);
  526. // 遍历从开始日期到结束日期的所有天数
  527. while (current <= endDate) {
  528. // 格式化日期为YYYY-MM-dd
  529. const year = current.getFullYear();
  530. const month = current.getMonth() + 1; // 月份从0开始,需要+1
  531. const day = current.getDate();
  532. const formattedMonth = String(month).padStart(2, '0');
  533. const formattedDay = String(day).padStart(2, '0');
  534. const formattedDateStr = `${year}-${formattedMonth}-${formattedDay}`;
  535. allDates.push(formattedDateStr);
  536. // 移动到下一天
  537. current.setDate(current.getDate() + 1);
  538. }
  539. return allDates;
  540. },
  541. transformMonthFun(date, day) {
  542. // 处理月份数据(转换为Date对象)
  543. const parseMonthDate = (dateStr) => {
  544. return dateStr instanceof Date ? dateStr : new Date(dateStr + '-01');
  545. };
  546. const start = parseMonthDate(date[0]);
  547. const end = parseMonthDate(date[1]);
  548. // 先生成所有月份的日期数组
  549. const allMonthDates = [];
  550. const currentDate = new Date(start);
  551. // 遍历从开始月份到结束月份的所有月份
  552. while (currentDate <= end) {
  553. const year = currentDate.getFullYear();
  554. const month = currentDate.getMonth() + 1; // 月份从0开始,需要+1
  555. const receiptDate = day;
  556. // 格式化日期为YYYY-MM-dd
  557. const formattedMonth = String(month).padStart(2, '0');
  558. const formattedDate = String(receiptDate).padStart(2, '0');
  559. const deadLine = receiptDate ? `${year}-${formattedMonth}-${formattedDate}` : '';
  560. allMonthDates.push(deadLine);
  561. // 使用Date对象的setMonth方法正确移动到下一个月(自动处理年份变化)
  562. currentDate.setMonth(currentDate.getMonth() + 1);
  563. }
  564. return allMonthDates;
  565. },
  566. setDefaultList(dateRange, issueNumber) {
  567. const tempPeriod = issueNumber || 0;
  568. // 计算基本比例
  569. const basicRatio = 100 / tempPeriod;
  570. let totalRatio = 0;
  571. // 生成付款计划列表项
  572. const tempList = [];
  573. // 根据传入的period参数生成付款计划
  574. for (let i = 0; i < tempPeriod; i++) {
  575. let item = JSON.parse(JSON.stringify(this.defaultForm));
  576. // 获取日期:如果i小于日期数组长度则使用对应日期,否则设为空
  577. const deadLine = i < dateRange.length ? dateRange[i] : '';
  578. const ratio = parseFloat(basicRatio.toFixed(2));
  579. item.issueNumber = i + 1;
  580. item.deadLine = deadLine;
  581. // item.ratio = ratio;
  582. tempList.push(item);
  583. // totalRatio += ratio;
  584. }
  585. // 调整最后一项的比例,确保总和为100
  586. // if (tempList.length > 0) {
  587. // const difference = 100 - totalRatio;
  588. // if (difference !== 0) {
  589. // tempList[tempList.length - 1].ratio = parseFloat((tempList[tempList.length - 1].ratio + difference).toFixed(2));
  590. // }
  591. // }
  592. console.log('付款计划列表:~~', tempList);
  593. this.form.datasource = tempList;
  594. // this.$set(this.form, 'datasource', tempList);
  595. console.log('付款计划列表:', this.form.datasource);
  596. },
  597. // 添加
  598. handlAdd() {
  599. let item = JSON.parse(JSON.stringify(this.defaultForm));
  600. item.key = this.form.datasource.length + 1;
  601. item.issueNumber = this.form.datasource.length + 1;
  602. this.form.datasource.push(item);
  603. this.$emit('getIssueNumber', this.form.datasource.length);
  604. },
  605. // validateForm(callback) {
  606. // //开始表单校验
  607. // this.$refs.form.validate((valid) => {
  608. // callback(valid);
  609. // });
  610. // }
  611. validateForm(callback) {
  612. this.$refs.form.validate((valid, obj) => {
  613. if (obj) {
  614. let messages = Object.keys(obj).map((key) => obj[key][0]);
  615. if (messages.length > 0) {
  616. this.$message.warning(messages[0].message);
  617. }
  618. }
  619. callback(valid);
  620. });
  621. },
  622. }
  623. };
  624. </script>
  625. <style lang="scss" scoped>
  626. .time-form .el-form-item {
  627. margin-bottom: 0 !important;
  628. }
  629. .headbox {
  630. display: flex;
  631. justify-content: space-between;
  632. align-items: center;
  633. .amount {
  634. font-size: 14px;
  635. font-weight: bold;
  636. padding-right: 30px;
  637. }
  638. }
  639. .pricebox {
  640. display: flex;
  641. justify-content: flex-start;
  642. align-items: center;
  643. font-weight: bold;
  644. }
  645. :deep(.el-input-group__append) {
  646. padding: 0;
  647. }
  648. </style>