新特性

明细导出+样式优化+删除上传文件

核心代码

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>阿滚专属计算利润</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js"></script>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
	
    <style>
      
    </style>
</head>
<body class="container">

    <h3>
	1. 选择订单EXCEL
	</h3>
	
	<div>
	    <div class="row" style="margin-top: 10px;">
		  <label class="col-sm-1 col-form-label col-form-label"> 👩‍💼当前用户</label>
		  <div class="col-sm-11">
			<img class="img-thumbnail" style="width:40px;height:40px;"src="https://bkimg.cdn.bcebos.com/pic/242dd42a2834349b033bb3129abe02ce36d3d5391055?x-bce-process=image/format,f_auto/quality,Q_70/resize,m_lfit,limit_1,w_536"/>
	        <span>阿滚</span>
		  </div>
		</div>
		
		<div class="row" style="margin-top: 10px;">
		  <label class="col-sm-1 col-form-label col-form-label">🔏输入密码</label>
		  <div class="col-sm-11">
			<input id="password" type="password" class="form-control" placeholder="请输入密码">
		  </div>
		</div>
	</div>
	
	<br/>
	
	<div class="row">
	  <label class="col-sm-1 col-form-label col-form-label">📗文件上传</label>
	  <div class="col-sm-6">
		<input type="file" id="fileInput" accept=".xls,.xlsx" class="form-control">
	  </div>
	  <div class="col-sm-2">
		<button class = "btn btn-warning" onclick="deleteFile()">❌删除文件</button>
	  </div>
	  <div class="col-sm-2">
		<button class = "btn btn-success" onclick="exportOrderSummary()">📘导出交易汇总</button>
	  </div>
	</div>
	
    
	
	
	<br/>
    <table id="dataTable" class="table table-striped table-bordered" data-toggle="table" data-resizable="true">
        <thead>
            <tr>
                <th>商品名称</th>
				<th>商品属性</th>
                <th>成交金额总计(元)</th>
                <th>成交数总计(个)</th>
                <th>退款金额总计(元)</th>
                <th>退款数总计(个)</th>
                <th>成交人数预估(人)</th>
                <th>商品客单价(元)</th>
                <th>商品采购公斤价(元)</th>
                <th>商品重量(克)</th>
            </tr>
        </thead>
        <tbody id="dataBody"></tbody>
    </table>

	
	<h3>
	2. 利润计算
	</h3>
	
	<h4>2.1 计算解释</h4>
	毛利=(单场成交GMV-退款金额)-原料成本-(成交人数*快递单价)-(成交单数*包材单价)-(成交单数*技术服务费单价)-(成交单数*运费险单价) <br/>
	原料成本=包数*商品克价*规格克重       <br/>
	包数=(成交金额-退款金额)/客单价      <br/>
	克价=商品公斤价/1000  <br/>
	技术服务费=成交金额*0.02  <br/>
	运费险=(成交单数-退款单数)*0.14  <br/>

	<h4>2.2 费用配置</h4>
	
	<div class="row">
	  <label class="col-sm-1 col-form-label col-form-label">🕵️‍♂️成交人数</label>
	  <div class="col-sm-11">
		<input id="configSuccessPersonCount" class="form-control" value="0">
	  </div>
	</div>
	
	<div class="row" style="margin-top: 10px;">
	  <label class="col-sm-1 col-form-label col-form-label">💼快递单价</label>
	  <div class="col-sm-11">
		<input id="configKuaiduFeePerson" value="2" class="form-control">
	  </div>
	</div>


	
	<!-- 成交总人数: <input id="configSuccessPersonCount" value="0" class="form-control"></input> -->
	<!-- 快递单价(元):<input id="configKuaiduFeePerson" value="2" class="form-control"></input> -->
    <br/> 				
    <button id="calculateProfit" class="hidden btn btn-primary">🏅计算利润</button>
    <button id="exportTableProfit" class="btn btn-success" onclick="exportTableProfit()">📘导出利润明细</button>
    
    <h4>2.3 利润汇总</h4>
	总利润=商品利润之和-(成交人数*快递单价)
	
	<div id="totalProfit" class="hidden"></div>
	
    <table id="costTable" class="hidden table table-striped table-bordered" style="margin-top: 10px;">
        <thead>
            <tr>
                <th>商品名称</th>
				<th>商品属性</th>
                <th>包数</th>
                <th>克价</th>
                <th>技术服务费</th>
                <th>运费险</th>
                <th>利润</th>
				<th>利润明细=(成交订单金额总计-退款订单金额总计)-原料成本-(成交单数*包材单价)-(成交单数*技术服务费单价)</th>
            </tr>
        </thead>
        <tbody id="costBody"></tbody>
    </table>

    

    <script>
	function exportOrderSummary() {
		exportTableToExcel('dataTable', '交易汇总');
	}
	function exportTableProfit() {
		exportTableToExcel('costTable', '利润明细');
	}
	
	// 表格导出到 EXCEL
	function exportTableToExcel(tableId, tableName) {
		 // 获取表格元素
		  var table = document.getElementById(tableId);
		  
		  // 将表格转换为工作簿
		  var ws = XLSX.utils.table_to_sheet(table);
		  var wb = XLSX.utils.book_new();
		  XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
		  
		  // 为文件名添加时间戳
		  var timestamp = new Date().toISOString().replace(/[:\.]/g, '-');
		  let filename = tableName+"_"+ timestamp + '.xlsx';
		  
		  // 使用JSZip生成Excel文件
		  XLSX.writeFile(wb, filename);
	}
	

	// 删除文件的函数
	function deleteFile() {
	    var fileInput = document.getElementById('fileInput');
		// 清空文件输入框的值
		fileInput.value = '';
		// 可以在这里添加额外的逻辑,比如更新界面显示等
	}

	    function calculateMD5(input) {
		    if(input && input != "") {
				return CryptoJS.MD5(input).toString();
			}
			return "";
		}
		function isExpire() {
			const today = new Date();
			const targetDate = new Date('2024-10-31');

			if (today > targetDate) {
			    alert('账户已过期,请联系技术管理员!');
				return true;
			} else {
				return false;
			}
		}

	    function checkPassword() {
		    //expire
			if(isExpire()) {
				return false;
			}
			
			let passwordElement = document.getElementById('password');
			let pwdMd5 = calculateMD5(passwordElement.value);
			if ('d5792307ca46844fd13e0ee78c858a11' == pwdMd5) {
				return true;
			}
			alert('账户或者密码错误!');
			return false;
		}
		
        document.getElementById('fileInput').addEventListener('change', function(event) {
		    let valid = checkPassword();
			if(!valid) {
				return;
			}
		
            const file = event.target.files[0];
            const reader = new FileReader();

            reader.onload = function(e) {
                const data = new Uint8Array(e.target.result);
                const workbook = XLSX.read(data, { type: 'array' });
                const firstSheetName = workbook.SheetNames[0];
                const worksheet = workbook.Sheets[firstSheetName];
                const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });

                generateProductTable(jsonData);
            };

            reader.readAsArrayBuffer(file);
        });
		
		// 提取重量
		function extraWeight(description) {
			  // 定义正则表达式,用于匹配数字、克或g以及可能的乘法
			  const weightRegex = /(\d+)(克|g)/g;
			  const multiplierRegex = /\*(\d+)/g;

			  // 查找所有匹配的重量
			  let weightMatches = description.match(weightRegex);
			  let multiplierMatches = description.match(multiplierRegex);

			  // 如果没有找到匹配项,返回0
			  if (!weightMatches) {
				return 0;
			  }

			  // 初始化总重量
			  let totalWeight = 0;

			  // 遍历所有匹配的重量
			  weightMatches.forEach((match, index) => {
				// 提取数字
				let weight = parseInt(match.match(/\d+/)[0], 10);

				// 如果存在乘数,并且是当前重量的乘数,则乘以乘数
				if (multiplierMatches && multiplierMatches[index]) {
				  let multiplier = parseInt(multiplierMatches[index].match(/\d+/)[0], 10);
				  weight *= multiplier;
				}

				// 将提取的重量加到总重量上
				totalWeight += weight;
			  });

			  // 返回总重量
			  return totalWeight;
		}

        function generateProductTable(data) {
            const productStats = {};
            const headers = data[0];

            const productNameIndex = headers.indexOf('商品名称');
			const productPropIndex = headers.indexOf('商品属性');
			const productPriceIndex = headers.indexOf('商品价格');
            const orderAmountIndex = headers.indexOf('订单实际支付金额');
            const refundedAmountIndex = headers.indexOf('商品已退款金额');
            const recipientNameIndex = headers.indexOf('收件人姓名');
            const recipientAddressIndex = headers.indexOf('收件人地址');
            const recipientPhoneIndex = headers.indexOf('收件人手机');

            let  totalSuccessPerson = 0;
            for (let i = 1; i < data.length; i++) {
                const row = data[i];
                const productName = row[productNameIndex];
                const productProp = row[productPropIndex];
				const productPrice = row[productPriceIndex];
                const orderAmount = parseFloat(row[orderAmountIndex]) || 0;
                const refundedAmount = parseFloat(row[refundedAmountIndex]) || 0;
                const recipientKey = `${row[recipientNameIndex]}_${row[recipientAddressIndex]}_${row[recipientPhoneIndex]}`;

                if (productName && productProp) {
                    if (!productStats[productName+productProp]) {
                        productStats[productName+productProp] = {
                            totalOrderAmount: 0,
                            totalOrders: 0,
                            totalRefundedAmount: 0,
                            totalRefundedOrders: 0,
							productName: productName,
							productProp: productProp,
							// 商品价格
							productPrice: productPrice,
                            uniqueCustomers: new Set()
                        };
                    }

                    if (orderAmount > 0) {
                        productStats[productName+productProp].totalOrderAmount += orderAmount;
                        productStats[productName+productProp].totalOrders += 1;
                        productStats[productName+productProp].uniqueCustomers.add(recipientKey);
                    }

                    if (refundedAmount > 0) {
                        productStats[productName+productProp].totalRefundedAmount += refundedAmount;
                        productStats[productName+productProp].totalRefundedOrders += 1;
                    }
                }
            }

            const dataBody = document.getElementById('dataBody');
            dataBody.innerHTML = '';

            for (const [productName, stats] of Object.entries(productStats)) {
                const tr = document.createElement('tr');

                const nameCell = document.createElement('td');
                nameCell.textContent = stats.productName;
                tr.appendChild(nameCell);

				const propCell = document.createElement('td');
                propCell.textContent = stats.productProp;
                tr.appendChild(propCell);
				
                const orderAmountCell = document.createElement('td');
                orderAmountCell.textContent = stats.totalOrderAmount.toFixed(2);
                tr.appendChild(orderAmountCell);

                const orderCountCell = document.createElement('td');
                orderCountCell.textContent = stats.totalOrders;
                tr.appendChild(orderCountCell);

                const refundedAmountCell = document.createElement('td');
                refundedAmountCell.textContent = stats.totalRefundedAmount.toFixed(2);
                tr.appendChild(refundedAmountCell);

                const refundedCountCell = document.createElement('td');
                refundedCountCell.textContent = stats.totalRefundedOrders;
                tr.appendChild(refundedCountCell);

                const uniqueCustomersCell = document.createElement('td');
                uniqueCustomersCell.textContent = stats.uniqueCustomers.size;
                tr.appendChild(uniqueCustomersCell);
				
				// 更新总人数
				totalSuccessPerson += stats.uniqueCustomers.size;

                const priceCell = document.createElement('td');
                const inputPrice = document.createElement('input');
                inputPrice.type = 'number';
                inputPrice.placeholder = '客单价';
				inputPrice.value= stats.productPrice.toFixed(2);
                priceCell.appendChild(inputPrice);
                tr.appendChild(priceCell);

                const weightCell = document.createElement('td');
                const inputWeight = document.createElement('input');
                inputWeight.type = 'number';
                inputWeight.placeholder = '公斤价';
				inputWeight.value=10;
                weightCell.appendChild(inputWeight);
                tr.appendChild(weightCell);

                const customWeightCell = document.createElement('td');
                const inputCustomWeight = document.createElement('input');
                inputCustomWeight.type = 'number';
                inputCustomWeight.placeholder = '商品重量';
				inputCustomWeight.value=extraWeight(stats.productProp);
				if(inputCustomWeight.value == 0) {
				   inputCustomWeight.style.color = 'red';
				}
                customWeightCell.appendChild(inputCustomWeight);
                tr.appendChild(customWeightCell);

                dataBody.appendChild(tr);
            }

            document.getElementById('configSuccessPersonCount').value = totalSuccessPerson;
            document.getElementById('calculateProfit').classList.remove('hidden');
        }

        document.getElementById('calculateProfit').addEventListener('click', function() {
		    let valid = checkPassword();
			if(!valid) {
				return;
			}
			
            const rows = document.querySelectorAll('#dataBody tr');
            const costBody = document.getElementById('costBody');
            costBody.innerHTML = ''; // 清空之前的内容
            let totalProfit = 0;

            for (const row of rows) {
                const cells = row.querySelectorAll('td');
                const productName = cells[0].textContent;
                const productProp = cells[1].textContent;
                const totalOrderAmount = parseFloat(cells[2].textContent);
                const totalRefundedAmount = parseFloat(cells[4].textContent);
                const totalOrders = parseInt(cells[3].textContent);
                const totalRefundedOrders = parseInt(cells[5].textContent);
                const uniqueCustomersCount = parseInt(cells[6].textContent);
                const inputPrice = cells[7].querySelector('input');
                const inputWeight = cells[8].querySelector('input');
                const inputCustomWeight = cells[9].querySelector('input');

                const userPrice = parseFloat(inputPrice.value);
                const userKgPrice = parseFloat(inputWeight.value);
                const userCustomWeight = parseFloat(inputCustomWeight.value);

                if (!userPrice || userPrice <= 0 || !userKgPrice || userKgPrice <= 0 || !userCustomWeight || userCustomWeight <= 0) {
                    alert('请输入有效的价格和重量!');
                    return;
                }

                const packageCount = Math.ceil((totalOrderAmount - totalRefundedAmount) / userPrice);
                const gramPrice = userKgPrice / 1000;
                const technicalServiceFee = totalOrderAmount * 0.02;
                const shippingInsurance = (totalOrders - totalRefundedOrders) * 0.14;
                const rawMaterialCost = packageCount * gramPrice * userCustomWeight;
                const profit = (totalOrderAmount - totalRefundedAmount) - rawMaterialCost - technicalServiceFee - shippingInsurance;

                const tr = document.createElement('tr');
                tr.innerHTML = `
                    <td>${productName}</td>
					<td>${productProp}</td>
                    <td>${packageCount}</td>
                    <td>${gramPrice.toFixed(4)}</td>
                    <td>${technicalServiceFee.toFixed(2)}</td>
                    <td>${shippingInsurance.toFixed(2)}</td>
					<td>${profit.toFixed(2)}</td>
                    <td>(${totalOrderAmount.toFixed(2)} - ${totalRefundedAmount.toFixed(2)}) - ${rawMaterialCost.toFixed(2)} - (${technicalServiceFee.toFixed(2)}) - (${shippingInsurance.toFixed(2)})</td>
                `;
                costBody.appendChild(tr);
                totalProfit += profit;
            }

            let configSuccessPersonCount = document.getElementById('configSuccessPersonCount').value;
            let configKuaiduFeePerson = document.getElementById('configKuaiduFeePerson').value;
			let totalPureProfit = totalProfit - configSuccessPersonCount * configKuaiduFeePerson;
            document.getElementById('totalProfit').innerHTML = `总利润: ${totalPureProfit.toFixed(2)}=${totalProfit.toFixed(2)}-(${configSuccessPersonCount}*${configKuaiduFeePerson})`;
            document.getElementById('totalProfit').classList.remove('hidden');
            document.getElementById('costTable').classList.remove('hidden');
        });
    </script>
</body>
</html>

总结

TODO:

  1. 默认的商品+属性

  2. 默认的商品采购价格

参考资料