diff --git a/packages/app-client/config/dev.ts b/packages/app-client/config/dev.ts index 44c4197..da65932 100644 --- a/packages/app-client/config/dev.ts +++ b/packages/app-client/config/dev.ts @@ -13,7 +13,7 @@ export default { "process.env.TARO_API_DOMAIN": process.env.TARO_ENV === "h5" ? '"/api"' - : '"https://api.erp.qilincloud168.com"', + : '"http://api.erp.xunhong168.test"', "process.env.TARO_POSTER_DOMAIN": process.env.TARO_ENV === "h5" ? '""' diff --git a/packages/app-client/src/components/delivery/Step3Success.tsx b/packages/app-client/src/components/delivery/Step3Success.tsx index 2ecb43d..afa1313 100644 --- a/packages/app-client/src/components/delivery/Step3Success.tsx +++ b/packages/app-client/src/components/delivery/Step3Success.tsx @@ -1,17 +1,77 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Image, Text, View } from "@tarojs/components"; -import { Button } from "@nutui/nutui-react-taro"; +import { Button, TabPane, Tabs } from "@nutui/nutui-react-taro"; import Taro from "@tarojs/taro"; import { Icon } from "@/components"; +import { business, poster } from "@/services"; +import { + exportProfitTableExcel, + OrderCalculator, + ProfitTableRow, + ProfitTableTemplate, +} from "@/utils"; +import dayjs from "dayjs"; interface Step3SuccessProps { pdfUrl: string; picUrl?: string; + orderVO: BusinessAPI.OrderVO; } export default function Step3Success(props: Step3SuccessProps) { - const { pdfUrl, picUrl } = props; + const { pdfUrl, picUrl, orderVO } = props!; const [tempFilePath, setTempFilePath] = useState(); + const [tabValue, setTabValue] = useState("0"); // 默认选中发货单 + const [excelTempFilePath, setExcelTempFilePath] = useState(); + + // 利润表相关状态 + const [profitPicUrl, setProfitPicUrl] = useState(); + + // 获取当前月份 + const currentMonth = dayjs(orderVO.orderVehicle.deliveryTime).format( + "YYYY-MM-01", + ); + const [profitData, setProfitData] = useState([]); + + const init = async (currentMonth: string) => { + // 查询本月的所有订单数据 + const { + data: { data: orderVOList = [] }, + } = await business.order.listOrder({ + orderListQry: { + month: currentMonth, + state: "COMPLETED", // 只查询已完成的订单 + }, + }); + + // 获取当前车次 + const currentVehicleNo = orderVO.orderVehicle.vehicleNo; + + // 筛选截止到当前车次之前的数据 + const profitData: ProfitTableRow[] = []; + let includeNext = true; + + for (const order of orderVOList) { + // 如果还没有到达当前车次,继续 + if (includeNext) { + const profitRow = calculateOrderProfit(order); + profitData.push(profitRow); + + // 如果是当前车次,停止添加 + if (order.orderVehicle.vehicleNo === currentVehicleNo) { + includeNext = false; + } + } + } + + setProfitData(profitData); + }; + + useEffect(() => { + if (currentMonth) { + init(currentMonth).then(); + } + }, [currentMonth]); // 保存图片 const handleSaveImage = async () => { @@ -95,90 +155,371 @@ export default function Step3Success(props: Step3SuccessProps) { } }; + // 下载Excel + const handleDownloadExcel = async () => { + Taro.showToast({ + title: "正在下载Excel文件", + icon: "loading", + duration: 2000, + }); + + const profitExcelUrl = await exportProfitTableExcel( + profitData, + currentMonth, + ); + + // 根据环境选择不同的导出方式 + const processEnv = process.env.TARO_ENV; + + if (processEnv === "h5") { + const link = document.createElement("a"); + link.href = profitExcelUrl; + link.download = `诚信志远利润明细_${currentMonth}.xlsx`; + link.style.display = "none"; + + // 触发下载 + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // 清理URL对象 + window.URL.revokeObjectURL(profitExcelUrl); + + await Taro.showToast({ + title: "导出成功", + icon: "success", + }); + } else { + try { + // 下载文件 + const downloadRes = await Taro.downloadFile({ + url: profitExcelUrl!, + }); + + if (downloadRes.tempFilePath) { + setExcelTempFilePath(downloadRes.tempFilePath); + Taro.showToast({ + title: "Excel下载成功", + icon: "success", + duration: 2000, + }); + } + } catch (error) { + console.error("下载Excel失败:", error); + Taro.showToast({ + title: "下载Excel失败", + icon: "none", + duration: 2000, + }); + } + } + }; + + // 查看Excel文档 + const handleViewExcel = async () => { + if (!excelTempFilePath) { + await handleDownloadExcel(); + } + + if (excelTempFilePath) { + Taro.openDocument({ + filePath: excelTempFilePath, + showMenu: true, + }); + } + }; + + // 保存利润表图片 + const handleSaveProfitImage = async () => { + if (!profitPicUrl) { + Taro.showToast({ + title: "没有可保存的利润表图片", + icon: "none", + duration: 2000, + }); + return; + } + + try { + // 下载文件 + const downloadRes = await Taro.downloadFile({ + url: profitPicUrl!, + }); + // 保存图片到相册 + await Taro.saveImageToPhotosAlbum({ + filePath: downloadRes.tempFilePath, + }); + + Taro.showToast({ + title: "图片已保存到相册", + icon: "success", + duration: 2000, + }); + } catch (error) { + console.error("保存图片失败:", error); + Taro.showToast({ + title: "保存图片失败,请检查相册权限", + icon: "none", + duration: 2000, + }); + } + }; + + // 计算单车的利润数据 + const calculateOrderProfit = (order: BusinessAPI.OrderVO): ProfitTableRow => { + // 车次 + const vehicleNo = `第${order.orderVehicle.vehicleNo || ""}车`; + + // 发货日期 - 从 orderShipList 获取 + const shippingDate = dayjs(order.orderVehicle.deliveryTime).format( + "YYYY/MM/DD", + ); + + const orderCalculator = new OrderCalculator(order); + // 计算产地成本 + const originCost = orderCalculator + .getCostCalculator() + .calculateMelonPurchaseCost(); + // 报价金额 + const quoteAmount = orderCalculator.getSalesAmount(); + // 计算利润 + const profit = orderCalculator.getPersonalProfit(); + + return { + vehicleNo, + shippingDate, + originCost, + quoteAmount, + profit, + }; + }; + + // 生成利润表 + const generateProfitTable = async () => { + try { + if (!profitData || profitData.length === 0) { + Taro.showToast({ + title: "本月暂无订单数据", + icon: "none", + }); + return; + } + + // 生成预览图片 + const template = new ProfitTableTemplate(profitData, currentMonth); + const { + data: { data: picData }, + } = await poster.poster.postApiV1Poster({ + html: template.generateHtmlString(), + //@ts-ignore + format: "a4", + }); + + setProfitPicUrl(picData?.path); + } catch (error) { + console.error("生成利润表失败:", error); + Taro.showToast({ + title: "生成利润表失败", + icon: "none", + }); + } + }; + return ( - {/* 预览区域 */} - - - {picUrl ? ( - - { - Taro.previewImage({ - urls: [picUrl], - current: picUrl, - }); - }} - /> - - ) : ( - - 暂无预览图片 - - )} - - - - {/* 底部按钮区域 */} - - - {/* 保存图片按钮 */} - {picUrl && ( - - )} - - {/* PDF操作按钮 */} - - - - - {tempFilePath && ( - - + {/* Tab 切换 */} + + { + if (value === 1 && !profitPicUrl) { + await generateProfitTable(); + } + setTabValue(value.toString()); + }} + > + + {/* 预览区域 */} + + + {picUrl ? ( + + { + Taro.previewImage({ + urls: [picUrl], + current: picUrl, + }); + }} + /> + + ) : ( + + 暂无预览图片 + + )} - )} - + - {/* 提示信息 */} - - - 点击预览图片可放大查看 - - - + {/* 底部按钮区域 */} + + + {/* 保存图片按钮 */} + {picUrl && ( + + )} + + {/* PDF操作按钮 */} + + + + + {tempFilePath && ( + + + + )} + + + {/* 提示信息 */} + + + 点击预览图片可放大查看 + + + + + + + {orderVO.orderDealer.shareAdjusted && ( + + {/* 预览区域 */} + + + {profitPicUrl ? ( + + { + Taro.previewImage({ + urls: [profitPicUrl], + current: profitPicUrl, + }); + }} + /> + + ) : ( + + 暂无利润表预览图片 + + )} + + + + {/* 底部按钮区域 */} + + + {/* 保存利润表图片按钮 */} + {profitPicUrl && ( + + )} + + {/* Excel操作按钮 */} + + + + + {excelTempFilePath && ( + + + + )} + + + {/* 提示信息 */} + + + 点击预览图片可放大查看 + + + + + + )} + ); diff --git a/packages/app-client/src/components/icon/Icon.tsx b/packages/app-client/src/components/icon/Icon.tsx index 568964a..8ca45bd 100644 --- a/packages/app-client/src/components/icon/Icon.tsx +++ b/packages/app-client/src/components/icon/Icon.tsx @@ -3,6 +3,7 @@ import classNames from "classnames"; import React from "react"; export type IconNames = + | "file-excel" | "copy" | "wallet" | "download" diff --git a/packages/app-client/src/constant/app.ts b/packages/app-client/src/constant/app.ts index d55325f..8f5de5c 100644 --- a/packages/app-client/src/constant/app.ts +++ b/packages/app-client/src/constant/app.ts @@ -1,2 +1,2 @@ // App 相关常量 -export const APP_VERSION = "v0.0.65"; +export const APP_VERSION = "v0.0.66"; diff --git a/packages/app-client/src/iconfont.css b/packages/app-client/src/iconfont.css index 2c29cef..333a5b3 100644 --- a/packages/app-client/src/iconfont.css +++ b/packages/app-client/src/iconfont.css @@ -1,11 +1,11 @@ @font-face { font-family: "iconfont"; /* Project id 5042354 */ src: - url("//at.alicdn.com/t/c/font_5042354_c95qf1bojkl.woff2?t=1766645005700") + url("//at.alicdn.com/t/c/font_5042354_y6uqn91vagr.woff2?t=1767522385418") format("woff2"), - url("//at.alicdn.com/t/c/font_5042354_c95qf1bojkl.woff?t=1766645005700") + url("//at.alicdn.com/t/c/font_5042354_y6uqn91vagr.woff?t=1767522385418") format("woff"), - url("//at.alicdn.com/t/c/font_5042354_c95qf1bojkl.ttf?t=1766645005700") + url("//at.alicdn.com/t/c/font_5042354_y6uqn91vagr.ttf?t=1767522385418") format("truetype"); } @@ -17,6 +17,10 @@ -moz-osx-font-smoothing: grayscale; } +.icon-file-excel:before { + content: "\e639"; +} + .icon-wallet:before { content: "\e638"; } diff --git a/packages/app-client/src/pages/delivery/document.tsx b/packages/app-client/src/pages/delivery/document.tsx index 72d051e..7a6d6ae 100644 --- a/packages/app-client/src/pages/delivery/document.tsx +++ b/packages/app-client/src/pages/delivery/document.tsx @@ -364,7 +364,11 @@ export default hocAuth(function Page(props: CommonComponent) { {step === 2 && } {step === 3 && pdfUrl && ( - + )} diff --git a/packages/app-client/src/services/business/typings.d.ts b/packages/app-client/src/services/business/typings.d.ts index 5552b6f..cd56aa0 100644 --- a/packages/app-client/src/services/business/typings.d.ts +++ b/packages/app-client/src/services/business/typings.d.ts @@ -3219,6 +3219,8 @@ declare namespace BusinessAPI { | "BOSS_REJECTED"; /** 采购类型:1_产地采购;2_市场采购; */ type?: "PRODUCTION_PURCHASE" | "MARKET_PURCHASE"; + /** 月份 */ + month?: string; }; type OrderPackage = { @@ -3447,6 +3449,10 @@ declare namespace BusinessAPI { pdfUrl?: string; /** 图片文件地址 */ picUrl?: string; + /** 利润表图片文件地址 */ + profitPicUrl?: string; + /** 利润表文件地址 */ + profitExcelUrl?: string; }; type OrderShipCreateCmd = { @@ -3645,6 +3651,10 @@ declare namespace BusinessAPI { pdfUrl?: string; /** 图片文件地址 */ picUrl?: string; + /** 利润表图片文件地址 */ + profitPicUrl?: string; + /** 利润表文件地址 */ + profitExcelUrl?: string; /** 发货单状态:0_草稿;1_待发货;2_待回款;3_待改签;4_部分回款;5_已回款;6_拒收完结;7_已完结; */ state: | "DRAFT" diff --git a/packages/app-client/src/utils/classes/index.ts b/packages/app-client/src/utils/classes/index.ts index 50f6218..12f296d 100644 --- a/packages/app-client/src/utils/classes/index.ts +++ b/packages/app-client/src/utils/classes/index.ts @@ -9,4 +9,4 @@ export { OrderCalculator, WeightCalculationService } from "./calculators"; // 模板类 -export { PdfTemplate } from "./templates"; +export { PdfTemplate, ProfitTableTemplate } from "./templates"; diff --git a/packages/app-client/src/utils/classes/templates/PdfTemplate.ts b/packages/app-client/src/utils/classes/templates/PdfTemplate.ts index 13ea4d8..ad6d742 100644 --- a/packages/app-client/src/utils/classes/templates/PdfTemplate.ts +++ b/packages/app-client/src/utils/classes/templates/PdfTemplate.ts @@ -8,7 +8,48 @@ export class PdfTemplate { // 将预览内容转换为HTML字符串的函数 generateHtmlString = () => { let htmlString = ` - +
`; diff --git a/packages/app-client/src/utils/classes/templates/ProfitTableTemplate.ts b/packages/app-client/src/utils/classes/templates/ProfitTableTemplate.ts new file mode 100644 index 0000000..b256ce0 --- /dev/null +++ b/packages/app-client/src/utils/classes/templates/ProfitTableTemplate.ts @@ -0,0 +1,107 @@ +// 利润表数据接口 +import dayjs from "dayjs"; +import "dayjs/locale/zh-cn"; + +dayjs.locale("zh-cn"); + +export interface ProfitTableRow { + vehicleNo: string; // 车次 + shippingDate: string; // 发货日期 + originCost: number; // 产地成本 + quoteAmount: number; // 报价金额 + profit: number; // 利润 +} + +export class ProfitTableTemplate { + private data: ProfitTableRow[]; + private month: string; + + constructor(data: ProfitTableRow[], month: string) { + this.data = data; + this.month = month; + } + + // 将利润表内容转换为HTML字符串的函数 + generateHtmlString = () => { + // 计算合计 + const totalOriginCost = this.data.reduce( + (sum, row) => sum + row.originCost, + 0, + ); + const totalQuoteAmount = this.data.reduce( + (sum, row) => sum + row.quoteAmount, + 0, + ); + const totalProfit = this.data.reduce((sum, row) => sum + row.profit, 0); + + return ` + +
+
+
+
诚信志远利润明细
+ + + + + + + + + + + + ${this.data + .map( + (row) => ` + + + + + + + + `, + ) + .join("")} + + + + + + + + + +
车次发货日期产地成本报价金额利润
${row.vehicleNo}${row.shippingDate}${row.originCost}${row.quoteAmount}${row.profit.toFixed(2)}
${dayjs(this.month).format("MMMM")}合计${totalOriginCost.toFixed(2)}${totalQuoteAmount.toFixed(2)}${totalProfit.toFixed(2)}
+
+
+
+ `; + }; +} diff --git a/packages/app-client/src/utils/classes/templates/index.ts b/packages/app-client/src/utils/classes/templates/index.ts index 661c7db..b349e5a 100644 --- a/packages/app-client/src/utils/classes/templates/index.ts +++ b/packages/app-client/src/utils/classes/templates/index.ts @@ -5,4 +5,5 @@ * import { PdfTemplate } from '@/utils/classes/templates' */ -export { PdfTemplate } from './PdfTemplate' +export { PdfTemplate } from "./PdfTemplate"; +export { ProfitTableTemplate } from "./ProfitTableTemplate"; diff --git a/packages/app-client/src/utils/functions/exportExcel.ts b/packages/app-client/src/utils/functions/exportExcel.ts index 1244d9d..7564233 100644 --- a/packages/app-client/src/utils/functions/exportExcel.ts +++ b/packages/app-client/src/utils/functions/exportExcel.ts @@ -1,6 +1,15 @@ import Taro from "@tarojs/taro"; import dayjs from "dayjs"; -import { utils, write } from "xlsx"; +import {utils, write} from "xlsx"; + +// 利润表行数据接口 +export interface ProfitTableRow { + vehicleNo: string; // 车次 + shippingDate: string; // 发货日期 + originCost: number; // 产地成本 + quoteAmount: number; // 报价金额 + profit: number; // 利润 +} // 费用统计数据接口 interface ExpenseStatistics { @@ -336,3 +345,98 @@ export const exportExcel = async ( }); } }; + +/** + * 导出利润表Excel文件 + * @param data 利润表数据 + * @param month 月份(格式:YYYY-MM) + * @returns Promise + */ +export const exportProfitTableExcel = async ( + data: ProfitTableRow[], + month: string, +): Promise => { + // 创建工作簿 + const wb = utils.book_new(); + + // 创建工作表数据 + const wsData: (string | number)[][] = []; + + // 添加标题 + wsData.push([`诚信志远利润明细`]); + wsData.push([]); // 空行 + + // 添加表头 + wsData.push(["车次", "发货日期", "产地成本", "报价金额", "利润"]); + + // 添加数据行 + data.forEach((row) => { + wsData.push([ + row.vehicleNo, + row.shippingDate, + row.originCost, + row.quoteAmount, + row.profit, + ]); + }); + + // 添加合计行 + const totalOriginCost = data.reduce((sum, row) => sum + row.originCost, 0); + const totalQuoteAmount = data.reduce((sum, row) => sum + row.quoteAmount, 0); + const totalProfit = data.reduce((sum, row) => sum + row.profit, 0); + + wsData.push([]); + wsData.push([ + dayjs(month).format("MMMM") + "合计", + "-", + totalOriginCost.toFixed(2), + totalQuoteAmount.toFixed(2), + totalProfit.toFixed(2), + ]); + + // 创建工作表 + const ws = utils.aoa_to_sheet(wsData); + + // 设置列宽 + const wscols = [ + { wch: 15 }, // 车次 + { wch: 15 }, // 发货日期 + { wch: 15 }, // 产地成本 + { wch: 15 }, // 报价金额 + { wch: 15 }, // 利润 + ]; + ws["!cols"] = wscols; + + // 将工作表添加到工作簿 + utils.book_append_sheet(wb, ws, "诚信志远利润明细"); + + // 生成文件名 + const fileName = `诚信志远利润明细_${month}_${dayjs().format("YYYY-MM-DD_HH-mm-ss")}.xlsx`; + + // 根据环境选择不同的导出方式 + const processEnv = process.env.TARO_ENV; + + if (processEnv === "h5") { + // H5环境:创建下载链接 + const excelBuffer = write(wb, { type: "array", bookType: "xlsx" }); + const blob = new Blob([excelBuffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + + // 创建下载链接 + return window.URL.createObjectURL(blob); + } else { + // 小程序环境:使用文件系统API + const excelBuffer = write(wb, { type: "array", bookType: "xlsx" }); + + // 将文件保存到本地 + const filePath = `${Taro.env.USER_DATA_PATH}/${fileName}`; + Taro.getFileSystemManager().writeFile({ + filePath, + data: excelBuffer, + encoding: "binary", + }); + + return filePath; + } +};