| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775 |
- <template>
- <el-form ref="form" :model="form" :rules="rules">
- <ele-pro-table
- ref="table"
- :needPage="false"
- :columns="columns"
- :datasource="form.datasource"
- :toolkit="[]"
- class="time-form"
- >
- <!-- 表头工具栏 -->
- <template v-slot:toolbar>
- <div class="headbox">
- <div>
- <el-button
- size="small"
- type="primary"
- icon="el-icon-plus"
- class="ele-btn-icon"
- @click="handlAdd"
- v-if="showAddBtn"
- >
- 新增
- </el-button>
- </div>
- <div class="pricebox">
- <span class="amount">比例合计:{{ allRatio }}%</span>
- <span class="amount">计划{{ menu === 'purchase' ? '付款' : '收款' }}金额合计:{{ allPrice }}元</span>
- <!-- <el-form-item
- style="width: 300px"
- label="优惠后总金额:"
- prop="discountTotalPrice"
- :rules="{
- required: true,
- message: '请输入优惠后总金额',
- trigger: 'change'
- }"
- >
- <el-input
- type="number"
- :min="0"
- :max="allPrice"
- :disabled="!allPrice"
- v-model="form.discountTotalPrice"
- style="width: 180px"
- placeholder="请输入"
- @input="discountInputByOrder(form.discountTotalPrice)"
- >
- <template slot="append">元</template>
- </el-input>
- </el-form-item> -->
- </div>
- </div>
- </template>
- <template v-slot:headerIssueNumber="{ column }">
- <span class="is-required">{{ column.label }}</span>
- </template>
- <template v-slot:headerType="{ column }">
- <span class="is-required">{{ column.label }}</span>
- </template>
- <template v-slot:issueNumber="scope">
- <span>第{{ scope.row.issueNumber }}期</span>
- </template>
- <template v-slot:moneyName="scope">
- <el-form-item
- style="margin-bottom: 20px"
- :prop="'datasource.' + scope.$index + '.moneyName'"
- :rules="{
- required: false,
- message: '请输入',
- trigger: 'blur'
- }"
- >
- <el-input
- v-model="scope.row.moneyName"
- placeholder="请输入"
- :disabled="type=='view'"
- ></el-input>
- </el-form-item>
- </template>
- <template v-slot:headerMoneyName="{ column }">
- <span class="is-required">{{ column.label }}</span>
- </template>
- <template v-slot:ratio="scope">
- <el-form-item
- style="margin-bottom: 20px"
- :prop="'datasource.' + scope.$index + '.ratio'"
- :rules="{
- required: true,
- pattern: numberReg,
- message: '请输入正确的比例',
- trigger: 'change'
- }"
- >
- <el-input
- v-model="scope.row.ratio"
- placeholder="请输入"
- :disabled="type=='view'"
- @input="(val) => ratioInput(val, scope.$index)"
- >
- <template slot="append">%</template>
- </el-input>
- </el-form-item>
- </template>
- <template v-slot:type="scope">
- <el-form-item
- style="margin-bottom: 20px"
- :prop="'datasource.' + scope.$index + '.type'"
- :rules="{
- required: true,
- message: '请选择款项类型',
- trigger: 'change'
- }"
- >
- <el-select
- v-model="scope.row.type"
- placeholder="请选择"
- style="width: 100%"
- :disabled="type=='view'"
- @change="(val) => typeChange(val, scope.$index)"
- >
- <el-option
- v-for="item in paymentTypeOp"
- :key="item.value"
- :label="item.label"
- :value="item.value"
- >
- </el-option>
- </el-select>
- </el-form-item>
- </template>
- <template v-slot:headerRatio="{ column }">
- <span class="is-required">{{ column.label }}</span>
- </template>
- <template v-slot:headerPrice="{ column }">
- <span class="is-required">{{ column.label }}</span>
- </template>
- <template v-slot:price="scope">
- <el-form-item
- style="margin-bottom: 20px"
- :prop="'datasource.' + scope.$index + '.price'"
- :rules="{
- required: true,
- pattern: numberReg,
- message: '请输入正确的金额',
- trigger: 'change'
- }"
- >
- <el-input
- type="number"
- :min="0"
- disabled
- v-model="scope.row.price"
- style="width: 100%"
- placeholder="请输入"
- >
- <template slot="append">元</template>
- </el-input>
- </el-form-item>
- </template>
- <template v-slot:deadLine="scope">
- <el-form-item
- style="margin-bottom: 20px"
- :prop="'datasource.' + scope.$index + '.deadLine'"
- :rules="{
- required: false,
- message: '请选择日期',
- trigger: 'change'
- }"
- >
- <el-date-picker
- style="width: 140px"
- v-model="scope.row.deadLine"
- :disabled="!showAddBtn"
- type="date"
- placeholder="选择日期"
- >
- </el-date-picker>
- </el-form-item>
- </template>
- <template v-slot:headerDeadLine="{ column }">
- <span class="is-required">{{ column.label }}</span>
- </template>
- <template v-slot:remark="scope">
- <el-form-item
- style="margin-bottom: 20px"
- :prop="'datasource.' + scope.$index + '.remark'"
- >
- <el-input
- v-model="scope.row.remark"
- type="textarea"
- placeholder="请输入"
- :disabled="type=='view'"
- ></el-input>
- </el-form-item>
- </template>
- <!-- 操作列 -->
- <template v-slot:action="{ row }">
- <el-popconfirm
- class="ele-action"
- title="确定要删除吗?"
- @confirm="remove(row)"
- >
- <template v-slot:reference>
- <el-link type="danger" :underline="false" icon="el-icon-delete">
- 删除
- </el-link>
- </template>
- </el-popconfirm>
- </template>
- </ele-pro-table>
- </el-form>
- </template>
- <script>
- import { emailReg, phoneReg, numberReg } from 'ele-admin';
- import { paymentTypeOp } from '@/enum/dict';
- import { formatPrice } from '@/BIZComponents/setProduct.js';
- export default {
- props: {
- delDetailIds: Array,
- type: String,
- menu: {
- type: String,
- default: 'sale'
- },
- discountTotalPrice: {
- type: [Number, String],
- default: 0
- },
- info: {
- type: Object,
- default: () => {}
- }
- },
- data() {
- const defaultForm = {
- key: null,
- deadLine: null,
- moneyName: '',
- price: null,
- ratio: null,
- remark: '',
- type: '',
- typeName: '',
- issueNumber: null
- };
- return {
- // allPrice: 0,
- // allRatio: 0,
- numberReg,
- defaultForm,
- discountAmount: 0,
- form: {
- datasource: []
- },
- paymentTypeOp,
- rules: {},
- isLoadingFromApi: false
- };
- },
- computed: {
- canHandl() {
- return this.form.datasource.length;
- },
- showAddBtn() {
- // console.log('showAddBtn~~~~', this.type, this.info);
- // console.log('showAddBtn!!!!',!['1', '2'].includes(this.info.settlementMode) || this.type != 'view');
- return !(['1', '2'].includes(this.info?.settlementMode)) && this.type != 'view'
- },
- columns() {
- return [
- {
- width: 45,
- type: 'index',
- columnKey: 'index',
- align: 'center',
- fixed: 'left'
- },
- {
- width: 180,
- prop: 'issueNumber',
- label: '期数',
- slot: 'issueNumber',
- headerSlot: 'headerIssueNumber',
- align: 'center'
- },
- {
- width: 150,
- prop: 'type',
- label: '款项类型',
- slot: 'type',
- headerSlot: 'headerType',
- align: 'center'
- },
- {
- prop: 'moneyName',
- label: '款项名称',
- slot: 'moneyName',
- // headerSlot: 'headerMoneyName',
- align: 'center',
- // width: 170
- },
- {
- width: 150,
- prop: 'ratio',
- label: '比例',
- slot: 'ratio',
- headerSlot: 'headerRatio',
- align: 'center'
- },
- {
- width: 170,
- prop: 'price',
- label: this.menu === 'purchase' ? '计划付款金额' : '计划收款金额',
- slot: 'price',
- align: 'center',
- headerSlot: 'headerPrice',
- },
- {
- width: 160,
- prop: 'deadLine',
- label: this.menu === 'purchase' ? '计划付款日期' : '计划收款日期',
- slot: 'deadLine',
- // headerSlot: 'headerDeadLine',
- align: 'center'
- },
- {
- // width: 220,
- prop: 'remark',
- label: '说明',
- slot: 'remark',
- align: 'center'
- },
- {
- columnKey: 'action',
- label: '操作',
- align: 'center',
- resizable: false,
- slot: 'action',
- showOverflowTooltip: true,
- show: this.showAddBtn
- }
- ];
- },
- allRatio() {
- return formatPrice(this.form.datasource.reduce((acc, cur) => acc + Number(cur.ratio), 0));
- },
- allPrice() {
- // 使用分进行计算,避免浮点数精度问题
- return formatPrice(this.form.datasource.reduce((acc, cur) => acc + Math.round(Number(cur.price) * 100), 0) / 100);
- },
- },
- watch: {
- discountAmount(newval) {
- if (newval && !this.isLoadingFromApi) {
- this.refreshprice();
- }
- },
- info: {
- handler(newval) {
- // if (newval && newval.receiptPaymentList) {
- // this.isLoadingFromApi = true;
- // this.form.datasource = newval.receiptPaymentList;
- // if (newval.payAmount) {
- // this.discountAmount = newval.payAmount;
- // }
- // setTimeout(() => {
- // this.isLoadingFromApi = false;
- // }, 100);
- // }
- },
- deep: true
- }
- },
- methods: {
- setDiscountAmount(val) {
- console.log(val, '000000');
- this.discountAmount = val;
- // 立即更新价格,无需设置isLoadingFromApi为true
- this.refreshprice();
- },
- typeChange(val, index = null) {
- // console.log(val, index, '55555');
- if (val) {
- this.$set(this.form.datasource[index], 'typeName', this.paymentTypeOp.find(item => item.value === val).label);
- }
- },
- //输入比例更新金额
- async ratioInput(val, index = null) {
- // console.log(this.discountAmount, '77777');
- // console.log(val, '5555555');
- val = Number(val);
- try {
- if (index != null) {
- await this.checkRatio();
- }
- let newval = (val / 100).toFixed(2);
- // console.log('newval~~~', newval)
- let price = formatPrice(this.discountAmount * newval);
- // console.log(newval, price, index, '88888');
- if (index != null) {
- // console.log(newval, price, index, '999999');
- this.$set(this.form.datasource[index], 'price', price);
- } else {
- return price;
- }
- } catch (error) {
- return 0;
- }
- //this.$set( this.form.datasource[index], 'price', price)
- },
- //检验比例
- checkRatio() {
- return new Promise((resolve, reject) => {
- let newData = this.form.datasource,
- sum = 0;
- newData.forEach((r) => {
- if (r.ratio) {
- sum += Number(r.ratio);
- }
- });
- // console.log(sum, '3333333');
- if (sum > 100) {
- this.$message.error('总共比例不能超过100');
- this.$set(
- this.form.datasource[this.form.datasource.length - 1],
- 'ratio',
- null
- );
- this.$set(
- this.form.datasource[this.form.datasource.length - 1],
- 'price',
- 0.0
- );
- reject(false);
- } else {
- resolve(true);
- }
- });
- },
- refreshprice() {
- // 如果正在从API加载数据,则不执行刷新操作
- if (this.isLoadingFromApi) {
- return;
- }
-
- // 创建一个新的数组,避免直接修改原数组
- let newData = JSON.parse(JSON.stringify(this.form.datasource));
- console.log('newData after copy:', newData);
-
- // 先根据比例计算所有价格
- for (let i = 0; i < newData.length; i++) {
- const r = newData[i];
- if (r.ratio) {
- // 直接计算价格,避免使用async/await的forEach
- let val = Number(r.ratio);
- let newval = (val / 100).toFixed(2);
- let price = formatPrice(this.discountAmount * newval);
- newData[i] = {
- ...r,
- price: price
- };
- }
- }
-
- // 验证并调整最后一行价格,确保总和等于优惠后的总金额
- // 使用分进行计算,避免浮点数精度问题
- const allPriceSum = newData.reduce((acc, cur) => acc + Math.round(Number(cur.price || 0) * 100), 0) / 100;
- const discountAmount = Number(this.discountAmount);
- // 直接计算比例总和,避免字符串转换问题
- const allRatio = newData.reduce((acc, cur) => acc + Number(cur.ratio || 0), 0);
-
- console.log('allPriceSum:', allPriceSum, 'discountAmount:', discountAmount, 'allRatio:', allRatio);
-
- // 检查是否所有数据都有price且比例合计为100%
- const allHavePrice = newData.every(item => item.price != null && item.price !== '');
-
- console.log('allHavePrice:', allHavePrice);
-
- if (allHavePrice && Math.abs(allRatio - 100) < 0.01 && Math.abs(allPriceSum - discountAmount) >= 0.01) {
- // 调整最后一行的价格
- if (newData.length > 0) {
- const lastIndex = newData.length - 1;
- // 使用分进行计算,避免浮点数精度问题
- const otherPriceSum = newData.slice(0, lastIndex).reduce((acc, cur) => acc + Math.round(Number(cur.price || 0) * 100), 0) / 100;
- // 计算最后一行应该有的价格
- const lastPrice = Math.round((discountAmount - otherPriceSum) * 100) / 100;
- newData[lastIndex] = {
- ...newData[lastIndex],
- price: formatPrice(lastPrice)
- };
- console.log('adjusted last item price:', lastPrice);
- }
- }
-
- console.log('newData before update:', newData);
-
- // 确保更新this.form.datasource,触发Vue的响应式更新
- this.form.datasource = newData;
- console.log('after update, datasource:', this.form.datasource);
- },
- // 返回列表数据
- getTableValue() {
- return this.form.datasource;
- },
- //修改回显
- putTableValue(data) {
- console.log('data~~~~111', data);
- if (data) {
- this.form.datasource = data.receiptPaymentList;
- this.setDiscountAmount(data.payAmount);
- }
- },
- remove(row) {
- let index = this.form.datasource.findIndex((n) => n.key == row.key);
- if (index !== -1) {
- this.form.datasource.splice(index, 1);
- this.setSort();
- if (row.id) {
- this.delDetailIds.push(row.id);
- }
- this.$emit('getIssueNumber', this.form.datasource.length);
- }
- },
- // 清空表格
- restTable() {
- this.form.datasource = [];
- },
- // 重新排序
- setSort() {
- this.form.datasource.forEach((n, index) => {
- n.key = index + 1;
- });
- },
- defaultList(method, issueNumber, dateRange) {
- // console.log('method, issueNumber', method, issueNumber, dateRange);
- const tempList = []
- if(dateRange) {
- this.setDefaultList(dateRange, issueNumber);
- return
- }
- if(method == 3) {
- let params = ['预付款', '交货款'];
- for(let i = 0; i < issueNumber; i++) {
- let item = JSON.parse(JSON.stringify(this.defaultForm));
- item.moneyName = i < params.length ? params[i] : '';
- item.type = i < params.length ? this.paymentTypeOp[i].value : '';
- item.typeName = i < params.length ? this.paymentTypeOp[i].label : '';
- item.key = i + 1;
- item.issueNumber = i + 1;
- tempList.push(item);
- }
- // params.forEach((item, index) => {
- // let i = JSON.parse(JSON.stringify(this.defaultForm));
- // i.moneyName = item;
- // i.type = this.paymentTypeOp[index].value;
- // i.key = index + 1;
- // i.issueNumber = index + 1;
- // tempList.push(i);
- // });
- } else {
- // method == 4 || method == 5 || method == 6 || method == 7 || method == 8
- // let params = [''];
- // params.forEach((item, index) => {
- // let i = JSON.parse(JSON.stringify(this.defaultForm));
- // i.key = index + 1;
- // i.issueNumber = index + 1;
- // tempList.push(i);
- // });
- for(let i = 0; i < issueNumber; i++) {
- let item = JSON.parse(JSON.stringify(this.defaultForm));
- item.moneyName = '';
- item.type = '';
- item.typeName = '';
- item.key = i + 1;
- item.issueNumber = i + 1;
- tempList.push(item);
- }
- }
- console.log('tempList~~~', tempList);
- // 清空原数组并逐个添加新元素,确保Vue能够检测到变化
- this.form.datasource = [];
- this.$nextTick(() => {
- this.form.datasource = tempList;
- console.log('form.datasource after update:', this.form.datasource);
- // 强制组件重新渲染
- this.$forceUpdate();
- console.log('forceUpdate called');
- });
- },
- transformDaysFun(date) {
- const startDate = new Date(date[0]);
- const endDate = new Date(date[1]);
- // 计算毫秒差并转换为天数,使用Math.ceil确保结果为整数
- const days = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
- console.log('包含两头的天数:', days);
- // 生成包括头尾在内的所有日期
- const allDates = [];
- const current = new Date(startDate);
-
- // 遍历从开始日期到结束日期的所有天数
- while (current <= endDate) {
- // 格式化日期为YYYY-MM-dd
- const year = current.getFullYear();
- const month = current.getMonth() + 1; // 月份从0开始,需要+1
- const day = current.getDate();
-
- const formattedMonth = String(month).padStart(2, '0');
- const formattedDay = String(day).padStart(2, '0');
- const formattedDateStr = `${year}-${formattedMonth}-${formattedDay}`;
-
- allDates.push(formattedDateStr);
-
- // 移动到下一天
- current.setDate(current.getDate() + 1);
- }
- return allDates;
- },
- transformMonthFun(date, day) {
- // 处理月份数据(转换为Date对象)
- const parseMonthDate = (dateStr) => {
- return dateStr instanceof Date ? dateStr : new Date(dateStr + '-01');
- };
-
- const start = parseMonthDate(date[0]);
- const end = parseMonthDate(date[1]);
-
- // 先生成所有月份的日期数组
- const allMonthDates = [];
- const currentDate = new Date(start);
-
- // 遍历从开始月份到结束月份的所有月份
- while (currentDate <= end) {
- const year = currentDate.getFullYear();
- const month = currentDate.getMonth() + 1; // 月份从0开始,需要+1
- const receiptDate = day;
-
- // 格式化日期为YYYY-MM-dd
- const formattedMonth = String(month).padStart(2, '0');
- const formattedDate = String(receiptDate).padStart(2, '0');
- const deadLine = receiptDate ? `${year}-${formattedMonth}-${formattedDate}` : '';
-
- allMonthDates.push(deadLine);
-
- // 使用Date对象的setMonth方法正确移动到下一个月(自动处理年份变化)
- currentDate.setMonth(currentDate.getMonth() + 1);
- }
- return allMonthDates;
- },
- setDefaultList(dateRange, issueNumber) {
- // console.log('setDefaultList-dateRange~~~', dateRange);
- const tempPeriod = issueNumber || 0;
- // 计算基本比例
- const basicRatio = 100 / tempPeriod;
- let totalRatio = 0;
-
- // 保存原有数据
- const existingData = [...this.form.datasource];
-
- // 生成付款计划列表项
- const tempList = [];
-
- // 根据传入的period参数生成付款计划
- for (let i = 0; i < tempPeriod; i++) {
- let item = JSON.parse(JSON.stringify(this.defaultForm));
- // let item;
- // // 如果原有数据中有对应项,则保留原有数据
- // if (i < existingData.length) {
- // item = JSON.parse(JSON.stringify(existingData[i]));
- // } else {
- // item = JSON.parse(JSON.stringify(this.defaultForm));
- // }
-
- // 获取日期:如果i小于日期数组长度则使用对应日期,否则设为空
- const deadLine = i < dateRange.length ? dateRange[i] : '';
- const ratio = parseFloat(basicRatio.toFixed(2));
- item.issueNumber = i + 1;
- item.deadLine = deadLine;
- // item.ratio = ratio;
- tempList.push(item);
- // totalRatio += ratio;
- }
-
- // 调整最后一项的比例,确保总和为100
- // if (tempList.length > 0) {
- // const difference = 100 - totalRatio;
- // if (difference !== 0) {
- // tempList[tempList.length - 1].ratio = parseFloat((tempList[tempList.length - 1].ratio + difference).toFixed(2));
- // }
- // }
-
- console.log('付款计划列表:~~', tempList);
- // this.$set(this.form, 'datasource', tempList);
- console.log('付款计划列表:', this.form.datasource);
- this.form.datasource = [];
- this.$nextTick(() => {
- this.form.datasource = tempList;
- console.log('form.datasource after update:', this.form.datasource);
- // 强制组件重新渲染
- this.$forceUpdate();
- console.log('forceUpdate called');
- });
- },
- // 添加
- handlAdd() {
- let item = JSON.parse(JSON.stringify(this.defaultForm));
- item.key = this.form.datasource.length + 1;
- item.issueNumber = this.form.datasource.length + 1;
- this.form.datasource.push(item);
- this.$emit('getIssueNumber', this.form.datasource.length);
- },
- // validateForm(callback) {
- // //开始表单校验
- // this.$refs.form.validate((valid) => {
- // callback(valid);
- // });
- // }
- validateForm(callback) {
- this.$refs.form.validate((valid, obj) => {
- if (obj) {
- let messages = Object.keys(obj).map((key) => obj[key][0]);
- if (messages.length > 0) {
- this.$message.warning(messages[0].message);
- }
- }
- callback(valid);
- });
- },
- }
- };
- </script>
- <style lang="scss" scoped>
- .time-form .el-form-item {
- margin-bottom: 0 !important;
- }
- .headbox {
- display: flex;
- justify-content: space-between;
- align-items: center;
- .amount {
- font-size: 14px;
- font-weight: bold;
- padding-right: 30px;
- }
- }
- .pricebox {
- display: flex;
- justify-content: flex-start;
- align-items: center;
- font-weight: bold;
- }
- :deep(.el-input-group__append) {
- padding: 0;
- }
- </style>
|