来,先看需求,
总体来说简单归纳一下需求:
就是表头数据的一些特定值变更后会影响明细的税率值,实时变更,明细变更后影响会总行数据,然后表头也变更。如果单独改了明细的数据值,那么相应的汇总,和表头都要自动进行计算。
明细行和会总行是可编辑表格,这个直接用公司封装的组件就行。编辑表格的需求就是: 明细行可编辑的字段涉及到计算的就是:数量。单价。含税单价。
需要注意的是,会总行数据来源源自于明细,且明细行相同的商品带出的数据,(也就是相同商品所在的数据,每个金额计算以及数量都要相加后赋值到会总行展示)要计算结果后展示在汇总数据行。
并且在新增、编辑、删除的时候不会影响每行的数据变化。
会总行 的结果是明细行的计算,表头金额值是会总行的计算。(是不是觉得,把明细行的每行值结果加起来以后直接给表头就行了,会总行要不要都行,但是人家就非要这样,相同的在一块,不想同的自己站一行。)
新增的原型
来看代码吧。
提示,相对于所有的金额计算方面的最后都单独做个封装,一般计算逻辑都是一样的。一个公司的一个项目不会出现2个以上的计算逻辑。(出现两个的情况也是区分的不通的状态下,并且应该也是计算的反值计算。其实都一样。)
1,现在我们已经点击了新增,跳转到了新增页面
handleCreate = (keys, rows, type) => {
if (type === 'create') {
this.props.push('/fin/payablesManagement/payable/BillsPayable/create'); // 跳转到新增页面
}
};
2 看下边的布局解释
修改的原型
代码有点多 最外层的inde文件,切记要引入我们的组件哦。
class AdjustEdit extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
loading: false,
formRef: null,
editTableRef: null,
editTotTableRef: null,
formData: {}, //表单值
editDataSource: [], //表格值
editTotDataSource: [], //汇总表格值
type: this.props.match.path.includes('/edit/') ? 'edit' : 'create',
id: this.props.match.params?.id,
basicRef: null
};
}
componentDidMount() { // 钩子函数
if (this.state.type === 'create') {
this.initAddNewData(); // 新增逻辑
} else {
this.getDetail(this.state.id); // 编辑赋值逻辑
}
}
// 新增
initAddNewData = async () => {
const initData: any = {
apOrderNo: '', //应付单号
sourceNo: '', //来源系统单号
createMode: 'SG', //来源单据 默认为手动
taxFlag: true, //是否含税单价 默认为是
orderState: 'DRAFT', //状态 默认为草稿
buDate: dayjs().format('YYYY-MM-DD'), // 业务日期 默认为当前日期
initFlag: false, //是否期初 默认为否
taxRate: 0, //税率
exchangeRate: 1, //汇率
totalAmt: 0, //含税金额
exclTaxAmt: 0, //不含税金额
totalCurAmt: 0, //含税金额本位币
exclTaxCurAmt: 0, //不含税金额本位币
taxAmt: 0, //税额
taxCurAmt: 0 //税额本位币
};
this.setState({
loading: true
});
// 获取默认应付单类型接口,需要就用不要就删
const res = await service.getApTypeDefault();
this.setState({
loading: false
});
if (res.success) {
const data = res.data;
initData.apTypeId = {
id: data.id,
apTypeCode: data.apTypeCode,
apTypeName: data.apTypeName
};
} else {
ElNotification({
type: 'error',
message: res.msg || '获取默认应付单类型失败'
});
}
this.setState({ formData: initData });
};
// 查详情
getDetail = async (id) => {
this.setState({ loading: true });
const res = await service.searchEditInfo({ id }); // 获取所有的返回数据
this.setState({ loading: false });
if (res.success) {
const { apOrderDtlVOList, apOrderDtlGroupVOList, ...masterData } =
res.data;
// 表单信息
const transMasterData = this.transBasicFormData(masterData);
// 明细数据
const transDetailData = this.transDetailData(apOrderDtlVOList);
// 汇总数据
const transTotalData = this.transTotalData(apOrderDtlGroupVOList);
this.setState({
formData: transMasterData,
editDataSource: transDetailData,
editTotDataSource: transTotalData
});
} else {
ElNotification({
type: 'error',
message: res.msg || '操作失败!'
});
}
};
// 查询返回数据后 特殊处理
// 表单数据处理
transBasicFormData = (data) => {
return {
...data,
currCode: {
currCode: data.currCode,
currName: data.currName
},
operUserId: {
id: data.operUserId,
empName: data.operator
},
ouId: {
id: data.ouId,
ouCode: data.ouCode,
ouName: data.ouName
},
suppId: {
id: data.suppId,
suppCode: data.suppCode,
suppName: data.suppName
},
// 付款条件
payMentCode: {
// id: data.payMentId,
payMentCode: data.payMentCode,
payMentName: data.payMentName
},
apTypeId: {
id: data.apTypeId,
apTypeCode: data.apTypeCode,
apTypeName: data.apTypeName
},
buId: {
id: data.suppId,
buCode: data.buCode,
buName: data.buName
},
buDate: dayjs(data.buDate).format('YYYY-MM-DD')
};
};
//明细数据处理
transDetailData = (dataArr) => {
const tempData = dataArr.map((item) => {
return {
...item,
itemCode: {
spuCode: item.itemCode,
spuId: item.itemId,
spuName: item.itemName,
spec: item.itemType,
itemSource: item.smallCateCode,
itemSourceName: item.smallCateName,
uom: item.uom,
uomName: item.uomName
}
};
});
return tempData;
};
// 汇总数据处理
transTotalData = (dataArr) => {
const tempData = dataArr.map((item) => {
return {
...item,
buId: {
buId: item.buId,
buName: item.buName
},
expensesType: {
udcVal: item.expensesType,
valDesc: item.expensesTypeName
}
};
});
return tempData;
};
// 保存按钮触发
beforeSave = async () => {
const data = await this.proceData();
data && this.presave(data);
};
// 保存
presave = async (data) => {
this.setState({ loading: true });
let res = null;
if (this.state.type === 'create') {
res = await service.addNewSave(data);
} else {
res = await service.editSave(data);
}
this.setState({ loading: false });
this.handSave(res)
};
// 提交按钮触发
beforeSubmit = async () => {
const data = await this.proceData();
data && this.submit(data);
};
// 提交
submit = async (data) => {
this.setState({ loading: true });
await this.state.editTableRef.quitEditState();
const res = await service.submit(data);
this.setState({ loading: false });
this.handSave(res)
};
// 保存或者提交后的页面返回封装
handSave = (res) =>{
if (res.success) {
ElNotification({ type: 'success', message: '操作成功!' });
const { store } = this.props;
store.MultiTabMobx.closeCurrentToPath(
'/fin/payablesManagement/payable/billsPayable'
);
} else {
ElNotification({
type: 'error',
message: res.msg || res.data || '操作失败!'
});
}
}
// 处理数据
proceData = async () => {
const { formRef, editTableRef, editTotTableRef } = this.state;
const formDataRes = await formRef.validateFields(); // 表头数据
await editTableRef.quitEditState(); // 明细行保存退出编辑状态, 这个editTableRef.quitEditState()是哥哥项目里封装的方法,看看自己的项目怎么退出编辑状态的
const detailTableValues = await editTableRef.validateTableRows(); // 明细行数据
if (detailTableValues.data.length === 0) {
// 判断是否新增行
ElNotification({
type: 'error',
message: '请添加明细信息!'
});
return false;
}
if (!detailTableValues.success) {
ElNotification({
type: 'error',
message:
'明细数据有误:' + this.getDetailErrorMsg(detailTableValues.msg)
});
return false;
}
let tableArr = detailTableValues.data;
tableArr = tableArr.map((item) => {
return {
...item,
itemCode: item.itemCode.spuCode
};
});
await editTotTableRef.quitEditState(); // 会总行保存退出编辑状态
const totalTableValues = await editTotTableRef.validateTableRows(); // 汇总行数据处理
// 以为只要新增就必须增加明细,要不然就无法保存,所有必有明细和汇总信息,这里就不会在去验证汇总行是否有数据了,但是明细还是要给的,因为,明细里面是有必填校验的。
if (!totalTableValues.success) {
ElNotification({
type: 'error',
message: '汇总数据有误:' + this.getDetailErrorMsg(totalTableValues.msg)
});
return false;
}
let totalTableArr = totalTableValues.data;
totalTableArr = totalTableArr.map((item) => {
return {
...item,
buId: item.buId?.buId,
buName: item.buId?.buName,
expensesType: item?.expensesType?.udcVal
};
});
// 后台接口所需参数 解析
const params = {
...this.state.formData, // 表单数据 新增时的默认或者是编辑时的赋值信息
...formDataRes, // 处理所有表单头的数据
// 币种
currCode: formDataRes.currCode?.currCode,
currName: formDataRes.currCode?.currName,
// 经办人信息 员工信息
operUserId: formDataRes.operUserId?.id || '',
operator: formDataRes.operUserId?.empName || '',
// 部门信息
buId: formDataRes.buId?.id || '',
buCode: formDataRes.buId?.buCode || '',
buName: formDataRes.buId?.buName || '',
// 公司
ouId: formDataRes.ouId?.id,
ouCode: formDataRes.ouId?.ouCode,
ouName: formDataRes.ouId?.ouName,
// 供应商
suppId: formDataRes.suppId?.id,
suppCode: formDataRes.suppId?.suppCode,
suppName: formDataRes.suppId?.suppName,
// 付款条件
payMentCode:
formDataRes.payMentCode?.ptCode || formDataRes.suppId?.paymentTerm,
payMentName:
formDataRes.payMentCode?.ptName || formDataRes.suppId?.paymentTermName,
// 应付款类型
apTypeId: formDataRes.apTypeId?.id,
apTypeCode: formDataRes.apTypeId?.apTypeCode,
apTypeName: formDataRes.apTypeId?.apTypeName,
// 业务日期
buDate: dayjs(formDataRes.buDate).format('YYYY-MM-DD hh:mm:ss'),
// 编辑表格信息行内容
apOrderDtlSaveParamList: tableArr,
//汇总行信息内容
apOrderDtlGroupSaveParamList: totalTableArr
};
return params;
};
// 这个是封装的报错信息,因为有的时候必填校验比较多,这个可以拿到那个地方有错,当然还要和后台接口配合,用不到就不要看了。
getDetailErrorMsg = (msg: ValidatorResult) => {
const map = new Map();
msg.errors.forEach((err) => {
if (err.path.length == 2) {
if (!map.has(err.path[0])) map.set(err.path[0], []);
map.get(err.path[0]).push((err.schema as any)?.message);
}
});
const str = [...map.entries()]
.map(([index, errArr]) => {
return `第${index + 1}行${errArr.join(',')};`;
})
.join();
return str;
};
// 返回按钮
onBack = () => {
const { push } = this.props;
push('/fin/payablesManagement/payable/billsPayable', () => false);
};
// 父子组件传值,接收表头的信息改变值处理联动
onBaseFormValuesChange = async (changedFields) => {
const changeField = Object.keys(changedFields)[0];
switch (changeField) {
case 'currCode':
this.currChange(changedFields);
break;
case 'suppId':
this.suppIdChange(changedFields);
break;
case 'ouId':
this.ouIdChange(changedFields);
break;
}
};
// 表头币别改变
currChange = async (changedFields) => {
await this.state.editTableRef.quitEditState();
this.calcDetailCurrAmt(); //计算明细本位币类金额
setTimeout(() => {
this.updateTotTable(); //更新汇总行
this.calcMasterCurrAmt(); //计算表头本位币类金额
}, 0);
};
// 表头供应商改变
suppIdChange = async (changedFields) => {
let taxRate = 0;
if (changedFields.suppId) {
taxRate = changedFields.suppId?.taxRateValue || '';
}
this.setState({
formData: {
...this.state.formData,
...this.state.formRef.getFieldsValue(),
taxRate
}
});
// 表头税率改变 明细需要重新计算
const taxFlag = this.state.formRef.getFieldValue('taxFlag'); //是否单价含税
const exchangeRate = this.state.formRef.getFieldValue('exchangeRate'); //是否单价含税
await this.state.editTableRef.quitEditState();
this.state.editTableRef.updateTableData((record) => {
let updateObj: any = {};
const { qty } = record;
let { exclTaxPrice, price } = record;
if (taxFlag) {
//是否含税单价为是 以含税单价为准 算未税单价
exclTaxPrice = calcService.Price2exclTaxPrice(price, taxRate);
updateObj.exclTaxPrice = exclTaxPrice;
} else {
//是都含税单价为否 以未税单价为准 算含税单价
// 封装的计算方法calcService.exclTaxPrice2Price(xx,yy);
updateObj.price = calcService.exclTaxPrice2Price(exclTaxPrice, taxRate);
}
updateObj = {
taxRate,
...updateObj,
...calcService.calcDetailRow(qty, exclTaxPrice, taxRate, exchangeRate)
};
return updateObj;
});
setTimeout(() => {
this.updateTotTable(); //更新汇总行
this.calcMaster(); //表头计算
}, 0);
};
// 表头公司改变 ==》 本位币变化 ==》 汇率变化
ouIdChange = async (changedFields) => {
await this.state.editTableRef.quitEditState();
this.calcDetailCurrAmt(); //计算明细本位币类金额
setTimeout(() => {
this.updateTotTable(); //更新汇总行
this.calcMasterCurrAmt(); //计算表头本位币类金额
}, 0);
};
// 父子传值事件, 明细行数据变化处理联动
onDetailValuesChange = async (
changedValues,
allValues,
record,
formRef,
editTableRef,
extraData
) => {
const changeField = Object.keys(changedValues)[0];
switch (changeField) {
case 'itemCode':
this.updateTotTable(true, extraData); //更新汇总行
break;
case 'exclTaxPrice':
case 'price':
case 'qty':
this.updateTotTable(true, extraData); //更新汇总行
this.calcMaster(true, extraData);
break;
}
};
// 明细行删除按钮
onDetailRowsDelete = async () => {
await this.state.editTableRef.quitEditState();
this.updateTotTable(); //更新汇总行
this.calcMaster();
};
//计算明细本位币类金额
calcDetailCurrAmt = () => {
let exchangeRate = this.state.formRef.getFieldValue('exchangeRate');
this.state.editTableRef.updateTableData((record) => {
const { exclTaxAmt, taxAmt } = record;
return calcService.calcDetailRowCurrAmt(exclTaxAmt, taxAmt, exchangeRate);
});
};
//计算表头本位币类金额
calcMasterCurrAmt = async () => {
const fields = ['exclTaxCurAmt', 'taxCurAmt', 'totalCurAmt'];
const initCurrAmtObj = {};
fields.forEach((field) => {
initCurrAmtObj[field] = 0;
});
const masCurrAmtObj = this.state.editTableRef
.getRows()
.reduce((totalAmtObj, curr) => {
fields.forEach((field) => {
totalAmtObj[field] = add(totalAmtObj[field], curr[field]);
});
return totalAmtObj;
}, initCurrAmtObj);
this.state.formRef.setFieldsValue(masCurrAmtObj);
};
//计算表头金额
calcMaster = async (isDetailEditing = false, extraData: any = {}) => {
const fields = [
'exclTaxAmt',
'exclTaxCurAmt',
'taxAmt',
'taxCurAmt',
'totalAmt',
'totalCurAmt'
];
const initAmtObj = {};
fields.forEach((field) => {
initAmtObj[field] = 0;
});
const masAmtObj = this.state.editTableRef
.getRows()
.reduce((totalAmtObj, curr) => {
if (isDetailEditing && extraData.id == curr.id) {
curr = extraData;
}
fields.forEach((field) => {
totalAmtObj[field] = add(totalAmtObj[field], curr[field]);
});
return totalAmtObj;
}, initAmtObj);
this.state.formRef.setFieldsValue(masAmtObj);
};
// 更新汇总行数据处理
updateTotTable = async (isDetailEditing = false, extraData: any = {}) => {
const fields = [
'qty',
'exclTaxAmt',
'exclTaxCurAmt',
'taxAmt',
'taxCurAmt',
'totalAmt',
'totalCurAmt'
];
const newRowFields = [
'itemId',
'itemName',
'smallCateCode',
'smallCateName'
];
const sumItemCodeRecords = this.state.editTableRef
.getRows()
.reduce((totalByItemCode, curr) => {
if (isDetailEditing && extraData.id == curr.id) {
curr = extraData;
}
const { itemCode } = curr;
const spuCode = itemCode.spuCode;
let currItemRecord: any = totalByItemCode[spuCode];
if (!currItemRecord) {
currItemRecord = totalByItemCode[spuCode] = {
record0: curr
};
fields.forEach((field) => {
currItemRecord[field] = 0;
});
}
fields.forEach((field) => {
currItemRecord[field] = add(currItemRecord[field], curr[field]);
});
return totalByItemCode;
}, {});
await this.state.editTotTableRef.quitEditState();
const deleteRowIds = [];
this.state.editTotTableRef.updateTableData((record) => {
const { itemCode } = record;
const sumItemCodeRow = sumItemCodeRecords[itemCode];
if (!sumItemCodeRow) {
//说明是要删除的行项
deleteRowIds.push(record.id);
return {};
} else {
delete sumItemCodeRecords[itemCode]; //更新过了之后就删除掉 sumItemCodeRecords剩下的就是要新增的
delete sumItemCodeRow.record0; //删除这个属性 是不需要更新的 新增行的时候用
return sumItemCodeRow;
}
});
if (deleteRowIds.length > 0) {
this.state.editTotTableRef.removeRowsByKeys(deleteRowIds);
}
const newRows = Object.values(sumItemCodeRecords).map((item: any) => {
const newRow = item;
newRowFields.forEach((field) => [(newRow[field] = item.record0[field])]);
newRow.itemCode = item.record0.itemCode.spuCode;
newRow.id = maths.genFakeId(-1);
delete newRow.record0;
return newRow;
});
if (newRows.length > 0) {
this.state.editTotTableRef.addRows(newRows);
}
};
// 制单基本信息ref
basicRef = (ref) => {
this.setState({
basicRef: ref
});
};
// form表单ref
formRef = (ref) => {
this.setState({
formRef: ref
});
};
// 可编辑表格ref
editTableRef = (ref) => {
this.setState({
editTableRef: ref
});
};
// 汇总表格ref
editTotTableRef = (ref) => {
this.setState({
editTotTableRef: ref
});
};
render() {
return (
<ElPage spinning={this.state.loading}>
<ElRowContainer
blocks={[
{
key: 'save',
text: '保存',
handleClick: this.beforeSave,
icon: <SaveBlue />,
authCode: 'fin-pur-tf-save'
},
{
key: 'submit',
text: '提交',
handleClick: this.beforeSubmit,
icon: <SubmitBlue />
}
]}
onBack={this.onBack}
position='top'
/>
<ElCard key='base' id='base' title='应付单'>
<BaseForm
editTableRef={this.state.editTableRef}
onRef={this.formRef}
formData={this.state.formData}
type={this.state.type}
onValuesChange={this.onBaseFormValuesChange}
/>
</ElCard>
<ElCard title='制单信息'>
<ControlForm
onRef={this.basicRef}
formData={this.state.formData}
type={this.state.type}
/>
</ElCard>
<ElCard key='detail' id='detail' title='明细信息'>
<EditTable
onRef={this.editTableRef}
editTableRef={this.state.editTableRef}
formRef={this.state.formRef}
dataSource={this.state.editDataSource}
type={this.state.type}
baseFormData={this.state.formData}
onvaluesChange={this.onDetailValuesChange}
onRowsDelete={this.onDetailRowsDelete}
/>
</ElCard>
<ElCard title='汇总信息'>
<EditTotTable
dataSource={this.state.editTotDataSource} //数据展示
onRef={this.editTotTableRef}
/>
</ElCard>
</ElPage>
);
}
}
export default AdjustEdit;
3。然后看看表头自己的内部处理
import React from 'react';
import ElForm from '@/components/el/ElForm';
import { getFormItems } from './config';
import { ElNotification } from '@/components/el';
import { FormInstance } from 'antd';
interface Props {
onRef: Function;
editTableRef: any;
formData: any;
type: any;
onValuesChange?: Function;
}
class BaseForm extends React.Component<Props> {
formRef: FormInstance = null;
constructor(props) {
super(props);
}
/**
* 基本信息改变changedFields 改变的字段信息
*/
onValuesChange = async (changedFields) => {
const changeField = Object.keys(changedFields)[0];
switch (changeField) {
case 'currCode':
this.currcyChange(changedFields);
break;
case 'ouId':
this.ouIdChange(changedFields);
break;
case 'suppId':
this.suppIdChange(changedFields);
break;
// case 'payMentCode':
// this.payMentCodeChange(changedFields);
}
this.props?.onValuesChange(changedFields);
};
currcyChange = async (changedFields) => {
if (changedFields.currCode) {
//todo 根据币别设置汇率
this.formRef.setFieldsValue({
exchangeRate: changedFields.currCode?.dispDecimal
});
} else {
this.formRef.setFieldsValue({
exchangeRate: 1
});
}
};
ouIdChange = (changedFields) => {
if (changedFields.ouId) {
console.log(changedFields.ouId);
// 根据币别设置汇率
this.formRef.setFieldsValue({
exchangeRate: changedFields.ouId?.exchangeRate || 7
});
} else {
this.formRef.setFieldsValue({
exchangeRate: 1
});
}
};
suppIdChange = (changedFields) => {
if (changedFields.suppId) {
this.formRef.setFieldsValue({
payMentCode: changedFields.suppId.paymentTermName
});
} else {
this.formRef.setFieldsValue({
payMentCode: ''
});
}
};
// payMentCodeChange = (changedFields) => {
// if (changedFields.payMentCode) {
// this.formRef.setFieldsValue({
// payMentCode: changedFields.payMentCode.ptCode
// });
// } else {
// this.formRef.setFieldsValue({
// payMentCode: ''
// });
// }
// };
onRef = (form) => {
this.formRef = form;
this.props?.onRef(form);
};
render() {
return (
<ElForm
onRef={this.onRef}
formProps={
{
onValuesChange: this.onValuesChange,
items: getFormItems(this.props.type, this.props.formData)
}}
data={this.props.formData}
/>
);
}
}
export default BaseForm;
4.然后看看明细的,这个确实有点多的多。
先看基础数据的传值,他是组件内部之间的传值,也就是自己给自己引入的。
config文件,好多数据是由商品编码带出来的,所以这个文件少不了。但是你们自己公司肯定也有自己的方法。
代码如下
// 明细信息 基础数据展示处理
import React from 'react';
import { AddBlue, DeleteRed, ImportBlue } from '@/components/el/ElIcon';
import { ElEditTableColumns } from '@/components/el/ElEditTable';
import { ActionButtonProps } from '@/components/el/ElSearchTable';
import { ElNotification } from '@/components/el';
import * as calcService from '../calc'; // 封装的计算金额方法,
const getTableColumns = (type, that): Array<ElEditTableColumns> => [
{
title: '来源单号',
dataIndex: 'sourceNo',
width: 160
},
{
title: '来源行号',
dataIndex: 'sourceLine',
width: 160
},
{
title: '行号',
dataIndex: 'index',
width: 60,
editable: true,
align: 'left',
cellRender: (text, record, index) => index + 1
},
{
title: '商品编码',
dataIndex: 'itemCode',
align: 'left',
width: 180,
editable: true,
rule: {
required: true,
message: '商品编码必填'
},
cellRender: (text) => text?.spuCode,
field: () => {
return {
formOption: {
type: '$fin-Goods-list',
props: {
placeholder: '请选择商品编码',
paramData: {}
}
},
name: 'itemCode'
};
},
selectMapping: async ({
changedValues,
allValues,
record,
editTableRef,
formRef
}) => {
const itemCode = allValues.itemCode || {};
const {
spuId = '',
spuName = '',
spec = '',
itemSource = '',
itemSourceName = '',
uom = 'ge',
uomName = '个'
} = itemCode;
//todo 单位取值
const initObj = calcService.initDetailRow();
const updateObj = {
itemId: spuId,
itemName: spuName,
itemType: spec,
smallCateCode: itemSource,
smallCateName: itemSourceName,
uom: uom,
uomName: uomName,
totalAmt: initObj.totalAmt,
totalCurAmt: initObj.totalCurAmt,
taxAmt: initObj.taxAmt,
taxCurAmt: initObj.taxCurAmt,
exclTaxAmt: initObj.exclTaxAmt,
exclTaxCurAmt: initObj.exclTaxCurAmt
};
editTableRef.updateRows(updateObj, [record.id]);
const returnObj = {
qty: initObj.qty,
exclTaxPrice: initObj.exclTaxPrice,
price: initObj.price
};
setTimeout(() => {
that.props?.onvaluesChange(
changedValues,
allValues,
record,
formRef,
editTableRef,
{
...record,
...allValues,
...updateObj,
...returnObj
}
);
}, 0);
return returnObj;
}
},
{
title: '商品名称',
dataIndex: 'itemName',
width: 160
},
{
title: '规格型号',
dataIndex: 'itemType',
width: 160
},
{
title: '小类编码',
dataIndex: 'smallCateCode',
width: 160
},
{
title: '小类名称',
dataIndex: 'smallCateName',
width: 160
},
{
title: '单位',
dataIndex: 'uomName',
width: 70
},
{
title: '数量',
width: 100,
align: 'left',
dataIndex: 'qty',
editable: true,
selectMapping: ({
changedValues,
allValues,
editTableRef,
formRef,
record
}) => {
if (allValues.itemCode == '' || allValues.itemCode == undefined) {
ElNotification({
type: 'warning',
message: '请优先处理商品信息'
});
return {
qty: 0,
price: 0,
exclTaxPrice: 0
};
} else {
let { qty, exclTaxPrice } = allValues;
let { taxRate } = record;
let exchangeRate = that.props.formRef.getFieldValue('exchangeRate');
const updateObj = calcService.calcDetailRow(
qty,
exclTaxPrice,
taxRate,
exchangeRate
);
editTableRef.updateRows(updateObj, [record.id]);
setTimeout(() => {
that.props?.onvaluesChange(
changedValues,
allValues,
record,
formRef,
editTableRef,
{
...record,
...allValues,
...updateObj
}
);
}, 0);
return {};
}
},
field: () => {
return {
formOption: {
props: {
// min: 1
// precision: 0,
},
type: '$inputNumber'
},
name: 'qty'
};
}
},
{
title: '单价',
dataIndex: 'exclTaxPrice',
width: 160,
rule: {
required: true,
message: '必填'
},
editable: true,
selectMapping: ({
changedValues,
allValues,
editTableRef,
formRef,
record
}) => {
if (allValues.itemCode == '' || allValues.itemCode == undefined) {
ElNotification({
type: 'warning',
message: '请优先处理商品信息'
});
return {
qty: 0,
price: 0,
exclTaxPrice: 0
};
} else {
let { qty, exclTaxPrice } = allValues;
let { taxRate } = record;
let exchangeRate = that.props.formRef.getFieldValue('exchangeRate');
let price = calcService.exclTaxPrice2Price(exclTaxPrice, taxRate);
const updateObj = calcService.calcDetailRow(
qty,
exclTaxPrice,
taxRate,
exchangeRate
);
editTableRef.updateRows(updateObj, [record.id]);
const returnObj = {
price
};
setTimeout(() => {
that.props?.onvaluesChange(
changedValues,
allValues,
record,
formRef,
editTableRef,
{
...record,
...allValues,
...updateObj,
...returnObj
}
);
}, 0);
return returnObj;
}
},
field: ({}) => {
return {
formOption: {
type: '$inputNumber',
props: {
placeholder: '请输入单价'
}
},
name: 'exclTaxPrice'
};
}
},
{
title: '含税单价',
dataIndex: 'price',
width: 160,
rule: {
required: true,
message: '必填'
},
editable: true,
selectMapping: ({
changedValues,
allValues,
editTableRef,
formRef,
record
}) => {
if (allValues.itemCode == '' || allValues.itemCode == undefined) {
ElNotification({
type: 'warning',
message: '请优先处理商品信息'
});
return {
qty: 0,
price: 0,
exclTaxPrice: 0
};
} else {
let { qty, price } = allValues;
let { taxRate } = record;
let exchangeRate = that.props.formRef.getFieldValue('exchangeRate');
let exclTaxPrice = calcService.Price2exclTaxPrice(price, taxRate);
const updateObj = calcService.calcDetailRow(
qty,
exclTaxPrice,
taxRate,
exchangeRate
);
editTableRef.updateRows(updateObj, [record.id]);
const returnObj = {
exclTaxPrice
};
setTimeout(() => {
that.props?.onvaluesChange(
changedValues,
allValues,
record,
formRef,
editTableRef,
{
...record,
...allValues,
...updateObj,
...returnObj
}
);
}, 0);
return returnObj;
}
},
field: ({ record }) => {
return {
formOption: {
type: '$inputNumber',
props: {
placeholder: ''
}
},
name: 'price'
};
}
},
{
title: '含税金额', // 含税金额=含税单价*数量;
dataIndex: 'totalAmt',
width: 160,
align: 'right'
},
{
title: '含税金额本位币', //含税金额本位币=含税金额*汇率;
dataIndex: 'totalCurAmt',
width: 160
},
{
title: '税率', // 表头供应商带出
dataIndex: 'taxRate',
width: 160
},
{
title: '税额', // 税额=四舍五入 ((数量*含税单价)*税率/(1+税率)),2);
dataIndex: 'taxAmt',
width: 160
},
{
title: '税额本位币', // 税额本位币=税额*汇率;
dataIndex: 'taxCurAmt',
width: 160
},
{
title: '不含税金额', // 不含税金额=含税金额-税额;
dataIndex: 'exclTaxAmt',
width: 160
},
{
title: '不含税金额本位币', // 不含税金额本位币=含税金额本位币-税额本位币;
dataIndex: 'exclTaxCurAmt',
width: 160
},
{
title: '备注',
dataIndex: 'remark',
width: 160,
editable: true,
field: () => ({
name: 'remark',
formOption: {
type: '$input',
props: {
placeholder: '请输入备注'
}
}
})
}
];
const getTableActionButtons = (
PayCreate,
PaydDel
): Array<ActionButtonProps> => [
{
text: '新增',
key: 'fin-withdraw-create',
handleClick: PayCreate,
location: 'left',
icon: <AddBlue />,
authCode: 'fin-pay-tf-add'
},
{
text: '删除',
key: 'fin-withdraw-del',
handleClick: PaydDel,
location: 'left',
minSelection: 1,
needConfirm: true,
icon: <DeleteRed />,
authCode: 'fin-pay-tf-del'
}
];
// 非手工不展示按钮
const SpaceshipButton = (): Array<ActionButtonProps> => [];
export { getTableColumns, getTableActionButtons, SpaceshipButton };
5,再看 明细表格的index文件,总共就下边图那么多,就不写了。
6 最最主要的计算封装来了。这个封装你可以拿去直接用,换换字段就好了。
import { maths } from '@/utils';
const { add, mul, div } = maths;
// 保留小数位
const toFixedPrice = 6;
const toFixedAmt = 2;
// 客户输入(导入)含税单价/单价和数量后的计算逻辑:
//含税单价 = 未税单价 * (1+税率) 输入未税单价时执行
//未税单价 = 含税单价 / (1+税率) 输入含税单价时执行
//未税金额 = 未税单价 * 数量
//税额 = 未税金额 * 税率
//含税金额 = 未税金额 + 税额
export function initDetailRow() {
return {
qty: 0, // 数量 4
exclTaxPrice: 0, // 单价 6
price: 0, // 含税单价 6
totalAmt: 0, // 含税金额 2
totalCurAmt: 0, //含税金额本位币2
taxAmt: 0, // 税额2
taxCurAmt: 0, // 税额本位币2
exclTaxAmt: 0, // 不含税金额2
exclTaxCurAmt: 0 // 不含税金额本位币2
};
}
export function exclTaxPrice2Price(exclTaxPrice, taxRate) {
return maths.rounds(mul(exclTaxPrice, 1 + taxRate), toFixedPrice);
}
export function Price2exclTaxPrice(price, taxRate) {
return maths.rounds(div(price, 1 + taxRate), toFixedPrice);
}
export function calcDetailRow(qty, exclTaxPrice, taxRate, exchangeRate) {
if (!Number.isFinite(Number(qty))) qty = 0;
if (!Number.isFinite(Number(exclTaxPrice))) exclTaxPrice = 0;
if (!Number.isFinite(Number(exchangeRate))) exchangeRate = 1;
if (!Number.isFinite(Number(taxRate))) exchangeRate = 0;
const exclTaxAmt = maths.rounds(mul(exclTaxPrice, qty), toFixedAmt); // 不含税金额
const exclTaxCurAmt = maths.rounds(mul(exclTaxAmt, exchangeRate), toFixedAmt); // 不含税金额本位币
const taxAmt = maths.rounds(mul(exclTaxAmt, taxRate), toFixedAmt); //税额
const taxCurAmt = maths.rounds(mul(taxAmt, exchangeRate), toFixedAmt); // 税额本位币
const totalAmt = maths.rounds(add(exclTaxAmt, taxAmt), toFixedAmt); //含税金额
const totalCurAmt = maths.rounds(add(exclTaxCurAmt, taxCurAmt), toFixedAmt); //含税金额本位币
return {
exclTaxAmt,
exclTaxCurAmt,
taxAmt,
taxCurAmt,
totalAmt,
totalCurAmt
};
}
export function calcDetailRowCurrAmt(exclTaxAmt, taxAmt, exchangeRate) {
if (!Number.isFinite(Number(exclTaxAmt))) exclTaxAmt = 0;
if (!Number.isFinite(Number(taxAmt))) taxAmt = 0;
if (!Number.isFinite(Number(exchangeRate))) exchangeRate = 1;
const exclTaxCurAmt = maths.rounds(mul(exclTaxAmt, exchangeRate), toFixedAmt); // 不含税金额本位币
const taxCurAmt = maths.rounds(mul(taxAmt, exchangeRate), toFixedAmt); // 税额本位币
const totalCurAmt = maths.rounds(add(exclTaxCurAmt, taxCurAmt), toFixedAmt); // 含税金额本位币
return {
exclTaxCurAmt,
taxCurAmt,
totalCurAmt
};
}