This commit is contained in:
ylj20011123 2025-09-02 18:28:02 +08:00
parent d3a4c2ad28
commit 30fa7f3f4c
15 changed files with 879 additions and 181 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "ant-design-pro", "name": "ant-design-pro",
"version": "4.5.42", "version": "4.5.45",
"private": true, "private": true,
"description": "An out-of-box UI solution for enterprise applications", "description": "An out-of-box UI solution for enterprise applications",
"scripts": { "scripts": {
@ -68,6 +68,7 @@
"compression-webpack-plugin": "^11.1.0", "compression-webpack-plugin": "^11.1.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"eslint-webpack-plugin": "^4.2.0", "eslint-webpack-plugin": "^4.2.0",
"exceljs": "^4.4.0",
"html-webpack-plugin": "^5.6.0", "html-webpack-plugin": "^5.6.0",
"increase-memory-limit": "^1.0.7", "increase-memory-limit": "^1.0.7",
"insert-css": "^2.0.0", "insert-css": "^2.0.0",

View File

@ -19,6 +19,7 @@ import session from "@/utils/session";
import { handleGetCHECKCOMMODITYList, handleGetTypeStatisticsList } from "../service"; import { handleGetCHECKCOMMODITYList, handleGetTypeStatisticsList } from "../service";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
import InventoryDetails from "../InventoryDetails"; import InventoryDetails from "../InventoryDetails";
import { exportXlsxFromProColumns, exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const InventoryCategory: React.FC<{ currentUser: CurrentUser }> = (props) => { const InventoryCategory: React.FC<{ currentUser: CurrentUser }> = (props) => {
@ -855,7 +856,7 @@ const InventoryCategory: React.FC<{ currentUser: CurrentUser }> = (props) => {
setReqDetailList([]) setReqDetailList([])
return { data: [], success: true } return { data: [], success: true }
} }
const req: any = { const req: any = {
ProvinceCode: currentUser?.ProvinceCode, ProvinceCode: currentUser?.ProvinceCode,
ServerpartId: params.servicePartId ? params.servicePartId : '', ServerpartId: params.servicePartId ? params.servicePartId : '',
@ -868,7 +869,7 @@ const InventoryCategory: React.FC<{ currentUser: CurrentUser }> = (props) => {
const data = await handleGetTypeStatisticsList(req) const data = await handleGetTypeStatisticsList(req)
console.log('data321312', data); console.log('data321312', data);
setReqDetailList(data)
if (data && data.length > 0) { if (data && data.length > 0) {
return { data, success: true } return { data, success: true }
} }
@ -881,7 +882,7 @@ const InventoryCategory: React.FC<{ currentUser: CurrentUser }> = (props) => {
buttonText={'导出excel'} buttonText={'导出excel'}
ref={downloadBtnRef} ref={downloadBtnRef}
table="table-to-xls-InventoryCategory" table="table-to-xls-InventoryCategory"
filename={`进销存类别报表${searchParams?.StartDate}-${searchParams?.EndDate}`} filename={`进销存类别报表${searchParams?.InventoryTime ? searchParams?.InventoryTime.split('-')[1] : ""}`}
sheet="sheet1" sheet="sheet1"
/> />
</span>, </span>,
@ -890,16 +891,30 @@ const InventoryCategory: React.FC<{ currentUser: CurrentUser }> = (props) => {
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(currentDataType === '1' ? [...columns, ...columnsType1] : currentDataType === '2' ? [...columns, ...columnsType2] : [],
setShowExportTable(true) reqDetailList,
setTimeout(() => { `进销存类别报表${searchParams?.InventoryTime ? searchParams?.InventoryTime.split('-')[1] : ""}`,
exportTable(e) {
}, 100) topTitle: `进销存类别报表`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel

View File

@ -621,7 +621,7 @@ const COMMODITYINFO = ({ onShow, onCancel, parentRow }: DetailProps) => {
SERVERPART_ID: parentRow?.SERVERPART_ID || "", SERVERPART_ID: parentRow?.SERVERPART_ID || "",
SERVERPARTSHOP_ID: parentRow?.SERVERPARTSHOP_ID || "", SERVERPARTSHOP_ID: parentRow?.SERVERPARTSHOP_ID || "",
// CHECKDATE: params.InventoryTime ? params.InventoryTime : "", // CHECKDATE: params.InventoryTime ? params.InventoryTime : "",
COMMODITY_CODE: parentRow?.COMMODITY_CODE || "", COMMODITY_IDS: parentRow?.COMMODITY_ID || "",
COMMODITY_BARCODE: parentRow?.COMMODITY_BARCODE || "" COMMODITY_BARCODE: parentRow?.COMMODITY_BARCODE || ""
}, },
PageIndex: 1, PageIndex: 1,

View File

@ -20,6 +20,7 @@ import { getMyShopList } from "@/pages/account/center/sevice";
import { render } from "react-dom"; import { render } from "react-dom";
import { initial } from "lodash"; import { initial } from "lodash";
import InventoryDetailModal from "./components/InventoryDetailModal"; import InventoryDetailModal from "./components/InventoryDetailModal";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const InventoryDetails: React.FC<{ currentUser: CurrentUser, isComponents?: any, parentRow?: any, birthIn?: any, parentSearchForm?: any }> = (props) => { const InventoryDetails: React.FC<{ currentUser: CurrentUser, isComponents?: any, parentRow?: any, birthIn?: any, parentSearchForm?: any }> = (props) => {
@ -1057,16 +1058,30 @@ const InventoryDetails: React.FC<{ currentUser: CurrentUser, isComponents?: any,
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(isComponents ? [...columns, ...columnsType1] : currentDataType === '1' ? [...columns, ...columnsType1] : currentDataType === '2' ? [...columns, ...columnsType2] : [],
setShowExportTable(true) reqDetailList,
setTimeout(() => { `进销存明细报表${searchParams?.InventoryTime || ""}`,
exportTable(e) {
}, 100) topTitle: `${currentShopInfo?.SERVERPART_NAME}${currentShopInfo?.SHOPNAME}进销存报表`, // 顶部大标题
}, 100) infoRowLeft: `统计方式:按成本结算(${currentDataType === '1' ? '含税' : '除税'})`,//左侧文字
}
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel

View File

@ -7,6 +7,7 @@ import Draggable from "react-draggable";
import ProTable, { ActionType } from "@ant-design/pro-table"; import ProTable, { ActionType } from "@ant-design/pro-table";
import { handleGetShopInventoryList } from "../../service"; import { handleGetShopInventoryList } from "../../service";
import InventoryDetailModal from "../../InventoryDetails/components/InventoryDetailModal"; import InventoryDetailModal from "../../InventoryDetails/components/InventoryDetailModal";
import { highlightText } from "@/utils/highlightText";
type DetailProps = { type DetailProps = {
onShow: any onShow: any
@ -15,8 +16,9 @@ type DetailProps = {
currentUser: any currentUser: any
ServerpartId: any // 服务区id ServerpartId: any // 服务区id
shopId: any // 门店id shopId: any // 门店id
searchParams?: any
} }
const inventoryDetail = ({ onShow, onCancel, parentRow, currentUser, ServerpartId, shopId }: DetailProps) => { const inventoryDetail = ({ onShow, onCancel, parentRow, currentUser, ServerpartId, shopId, searchParams }: DetailProps) => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>(); const formRef = useRef<FormInstance>();
const draggleRef = React.createRef<any>() const draggleRef = React.createRef<any>()
@ -84,7 +86,7 @@ const inventoryDetail = ({ onShow, onCancel, parentRow, currentUser, ServerpartI
setCurrentRow(record) setCurrentRow(record)
setShowDetail(true) setShowDetail(true)
}}> }}>
{record?.COMMODITY_NAME} {highlightText(record?.COMMODITY_NAME, searchParams?.searchText)}
</a> : "" </a> : ""
} }
}, },
@ -95,6 +97,21 @@ const inventoryDetail = ({ onShow, onCancel, parentRow, currentUser, ServerpartI
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
render: (_, record) => {
return record?.COMMODITY_BARCODE ? highlightText(record?.COMMODITY_BARCODE, searchParams?.searchText) : "-"
}
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "COMMODITY_BRAND",
align: 'left',
width: 120,
ellipsis: true,
hideInSearch: true,
render: (_, record) => {
return record?.COMMODITY_BRAND ? highlightText(record?.COMMODITY_BRAND, searchParams?.searchText) : "-"
}
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,

View File

@ -18,6 +18,8 @@ import { handleGetNestingCOMMODITYTYPETree } from "@/pages/reports/productContro
import { handleGetInventoryList } from "../service"; import { handleGetInventoryList } from "../service";
import { keyBy } from "lodash"; import { keyBy } from "lodash";
import InventoryDetail from "./components/inventoryDetail"; import InventoryDetail from "./components/inventoryDetail";
import { highlightText } from "@/utils/highlightText";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const { Text } = Typography; const { Text } = Typography;
@ -27,6 +29,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>(); const formRef = useRef<FormInstance>();
const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源 const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源
const [reqDefaultDetailList, setReqDefaultDetailList] = useState<any>(); // 合计项数据源
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容 const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const [collapsible, setCollapsible] = useState<boolean>(false) const [collapsible, setCollapsible] = useState<boolean>(false)
const [treeView, setTreeView] = useState<any>() const [treeView, setTreeView] = useState<any>()
@ -53,6 +56,10 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
const [showDetail, setShowDetail] = useState<boolean>(false) const [showDetail, setShowDetail] = useState<boolean>(false)
// 当前选中的服务区id // 当前选中的服务区id
const [currentSelectServerpartId, setCurrentSelectServerpartId] = useState<any>() const [currentSelectServerpartId, setCurrentSelectServerpartId] = useState<any>()
// 判断是不是点击了 排序、分页等
const isNoTableReload = useRef<boolean>(false)
// 表格数据
const [tableData, setTableData] = useState<any>()
const buildLeafMaps = (nodes: any[]) => { const buildLeafMaps = (nodes: any[]) => {
@ -114,7 +121,6 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
} }
}, },
{ {
title: "序号", title: "序号",
dataIndex: "index", dataIndex: "index",
@ -136,7 +142,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
setCurrentRow(record) setCurrentRow(record)
setShowDetail(true) setShowDetail(true)
}}> }}>
{record?.COMMODITY_NAME} {highlightText(record?.COMMODITY_NAME, searchParams?.searchText)}
</a> : "-" </a> : "-"
} }
}, },
@ -147,15 +153,21 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
render: (_, record) => {
return record?.COMMODITY_BARCODE ? highlightText(record?.COMMODITY_BARCODE, searchParams?.searchText) : "-"
}
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "COMMODITY_BRAND",
align: 'left',
width: 120,
ellipsis: true,
hideInSearch: true,
render: (_, record) => {
return record?.COMMODITY_BRAND ? highlightText(record?.COMMODITY_BRAND, searchParams?.searchText) : "-"
}
}, },
// {
// title: <div style={{ textAlign: 'center' }}>商品编码</div>,
// dataIndex: "COMMODITY_BRAND",
// align: 'left',
// width: 120,
// ellipsis: true,
// hideInSearch: true,
// },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "COMMODITY_RULE", dataIndex: "COMMODITY_RULE",
@ -169,8 +181,9 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
dataIndex: "OVERPLUSCOUNT", dataIndex: "OVERPLUSCOUNT",
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
sorter: true, // 这里如果要加默认的排序的话 需要单独在导出表格里面 单领出来
defaultSortOrder: "descend", sorter: (a, b) => a.OVERPLUSCOUNT - b.OVERPLUSCOUNT,
// defaultSortOrder: "descend",
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
@ -180,7 +193,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
dataIndex: "OVERPLUS_AMOUNT", dataIndex: "OVERPLUS_AMOUNT",
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
sorter: true, sorter: (a, b) => a.OVERPLUS_AMOUNT - b.OVERPLUS_AMOUNT,
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
@ -190,7 +203,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
dataIndex: "OVERPLUS_PRICE", dataIndex: "OVERPLUS_PRICE",
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
sorter: true, sorter: (a, b) => a.OVERPLUS_PRICE - b.OVERPLUS_PRICE,
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
@ -199,7 +212,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "OVERPLUS_XS_AMOUNT", dataIndex: "OVERPLUS_XS_AMOUNT",
align: 'right', align: 'right',
sorter: true, sorter: (a, b) => a.OVERPLUS_XS_AMOUNT - b.OVERPLUS_XS_AMOUNT,
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
ellipsis: true, ellipsis: true,
@ -210,7 +223,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
dataIndex: "OVERPLUS_XS_PRICE", dataIndex: "OVERPLUS_XS_PRICE",
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
sorter: true, sorter: (a, b) => a.OVERPLUS_XS_PRICE - b.OVERPLUS_XS_PRICE,
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
@ -244,6 +257,50 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
setShowDetail(false) setShowDetail(false)
} }
const sortListByState = (
list: any[],
state?: { field?: string; order?: 'ascend' | 'descend' }
) => {
const arr = Array.isArray(list) ? list.slice() : [];
if (!state?.field || !state?.order) return arr; // ★ 返回副本,避免共享引用
const { field, order } = state;
const factor = order === 'ascend' ? 1 : -1;
const getVal = (v: any) => {
if (v == null) return { type: 3, v: Number.POSITIVE_INFINITY }; // 统一丢到最后
if (typeof v === 'number') return { type: 0, v };
if (v instanceof Date) return { type: 1, v: v.getTime() };
if (typeof v === 'string') {
const t = Date.parse(v);
if (!Number.isNaN(t)) return { type: 1, v: t };
const n = Number(v);
// 纯数字字符串才按数字比较(去掉前后空格,避免把 '0012' 和 '12 '误判为不同)
if (!Number.isNaN(n) && v.trim() !== '' && String(n) === v.trim())
return { type: 0, v: n };
return { type: 2, v: v }; // 普通字符串
}
// 其它类型转字符串
return { type: 2, v: String(v) };
};
return arr
.map((item, idx) => {
const val = getVal(item?.[field]);
return { item, idx, val };
})
.sort((a, b) => {
// 先按类型分组,再按值比
if (a.val.type !== b.val.type) return (a.val.type - b.val.type) * factor;
if (a.val.v < b.val.v) return -1 * factor;
if (a.val.v > b.val.v) return 1 * factor;
// 值相等,用原始下标保证稳定
return a.idx - b.idx;
})
.map(d => d.item);
};
return ( return (
<div ref={(el) => { <div ref={(el) => {
@ -353,12 +410,10 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
search={{ span: 6 }} search={{ span: 6 }}
request={async (params, sorter) => { request={async (params, sorter) => {
console.log('paramsparamsparamsparamsparamsparams', params); console.log('paramsparamsparamsparamsparamsparams', params);
if (!selectedId) { if (!selectedId || isNoTableReload.current) {
isNoTableReload.current = false
return return
} }
console.log('selectedIdselectedIdselectedId', selectedId);
console.log('handleAllShopIdhandleAllShopIdhandleAllShopIdhandleAllShopId', handleAllShopId);
// 根据门店去判断 是哪个服务区里面的 再把服务区的id 记录下来 而且要去重 // 根据门店去判断 是哪个服务区里面的 再把服务区的id 记录下来 而且要去重
let serverpartId: any = [] let serverpartId: any = []
@ -367,8 +422,6 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
if (list && list.length > 0) { if (list && list.length > 0) {
list.forEach((item: any) => { list.forEach((item: any) => {
for (let key in handleAllShopId) { for (let key in handleAllShopId) {
console.log('handleAllShopId[key]', handleAllShopId[key]);
if (handleAllShopId[key] && handleAllShopId[key].length > 0) { if (handleAllShopId[key] && handleAllShopId[key].length > 0) {
if (handleAllShopId[key].indexOf(Number(item)) !== -1) { if (handleAllShopId[key].indexOf(Number(item)) !== -1) {
if (serverpartId && serverpartId.length > 0) { if (serverpartId && serverpartId.length > 0) {
@ -385,8 +438,6 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
} }
} }
console.log('serverpartIdserverpartIdserverpartIdserverpartId', serverpartId);
const sortstr = Object.keys(sorter).map(n => { const sortstr = Object.keys(sorter).map(n => {
const value = sorter[n] const value = sorter[n]
return value ? `${n} ${value.replace('end', '')}` : '' return value ? `${n} ${value.replace('end', '')}` : ''
@ -397,7 +448,7 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
ServerpartId: serverpartId && serverpartId.length > 0 ? serverpartId.toString() : "", ServerpartId: serverpartId && serverpartId.length > 0 ? serverpartId.toString() : "",
ServerpartShopId: selectedId, ServerpartShopId: selectedId,
CommodityTypeId: params?.COMMODITY_TYPE ? params?.COMMODITY_TYPE.toString() : "", CommodityTypeId: params?.COMMODITY_TYPE ? params?.COMMODITY_TYPE.toString() : "",
SearchKeyName: "COMMODITY_NAME,COMMODITY_BARCODE,COMMODITY_BRAND", SearchKeyName: "COMMODITY_NAME,COMMODITY_BRAND,COMMODITY_BARCODE",
SearchKeyValue: params?.searchText || "", SearchKeyValue: params?.searchText || "",
PageIndex: "1", PageIndex: "1",
PageSize: "999999", PageSize: "999999",
@ -410,11 +461,14 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
console.log('datadata', data); console.log('datadata', data);
setTableSumObj(data.OtherData) setTableSumObj(data.OtherData)
setReqDetailList(data.List) setReqDetailList(data.List)
if (data.List && data.List.length > 0) { setReqDefaultDetailList(data.List)
return { data: data.List, success: true } setTableData(data.List)
} // if (data.List && data.List.length > 0) {
return { data: [], success: true } // return { data: data.List, success: true }
// }
// return { data: [], success: true }
}} }}
dataSource={tableData}
toolbar={{ toolbar={{
actions: [ actions: [
<span style={{ visibility: 'hidden' }}> <span style={{ visibility: 'hidden' }}>
@ -431,16 +485,30 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(columns,
setShowExportTable(true) reqDetailList,
setTimeout(() => { `库存信息统计表`,
exportTable(e) {
}, 100) topTitle: `库存信息统计表`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel
@ -520,13 +588,24 @@ const inventoryInformation: React.FC<{ currentUser: CurrentUser }> = (props) =>
</Table.Summary> </Table.Summary>
) )
}} }}
onChange={(_pagination: any, _filters: any, sorter: any, extra: any) => {
console.log('onChange');
isNoTableReload.current = true
if (extra?.action === 'sort') {
const field = Array.isArray(sorter) ? sorter[0]?.field : sorter?.field;
const order = Array.isArray(sorter) ? sorter[0]?.order : sorter?.order;
const newList: any = sortListByState(reqDefaultDetailList, { field, order })
console.log('newListnewList', newList);
setReqDetailList(newList)
}
}}
/> />
</div> </div>
</div> </div>
{/* 库存的详情信息 */} {/* 库存的详情信息 */}
<InventoryDetail onShow={showDetail} onCancel={handleClose} parentRow={currentRow} currentUser={currentUser} ServerpartId={currentSelectServerpartId} shopId={selectedId} /> <InventoryDetail onShow={showDetail} onCancel={handleClose} parentRow={currentRow} currentUser={currentUser} ServerpartId={currentSelectServerpartId} shopId={selectedId} searchParams={searchParams} />
</div> </div>
) )

View File

@ -19,6 +19,7 @@ import session from "@/utils/session";
import InventoryDetailModal from "../InventoryDetails/components/InventoryDetailModal"; import InventoryDetailModal from "../InventoryDetails/components/InventoryDetailModal";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
import InventoryDetails from "../InventoryDetails"; import InventoryDetails from "../InventoryDetails";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const oneProductMultipleSizes: React.FC<{ currentUser: CurrentUser }> = (props) => { const oneProductMultipleSizes: React.FC<{ currentUser: CurrentUser }> = (props) => {
@ -176,8 +177,8 @@ const oneProductMultipleSizes: React.FC<{ currentUser: CurrentUser }> = (props)
} }
}, },
{ {
title: "商品码", title: "商品码",
dataIndex: "COMMODITY_BARCODE", dataIndex: "COMMODITY_CODE",
align: 'center', align: 'center',
width: 120, width: 120,
ellipsis: true, ellipsis: true,
@ -935,16 +936,29 @@ const oneProductMultipleSizes: React.FC<{ currentUser: CurrentUser }> = (props)
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(currentDataType === '1' ? [...columns, ...columnsType1] : currentDataType === '2' ? [...columns, ...columnsType2] : [],
setShowExportTable(true) reqDetailList,
setTimeout(() => { `一品多码统计表`,
exportTable(e) {
}, 100) topTitle: `一品多码统计表`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel

View File

@ -15,6 +15,7 @@ import LeftSelectTree from "@/pages/reports/settlementAccount/component/leftSele
import PageTitleBox from "@/components/PageTitleBox"; import PageTitleBox from "@/components/PageTitleBox";
import moment from "moment"; import moment from "moment";
import { handleGetMERCHANTSList, handleGetStorageBackSummary } from "../service"; import { handleGetMERCHANTSList, handleGetStorageBackSummary } from "../service";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => { const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
@ -23,6 +24,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>(); const formRef = useRef<FormInstance>();
const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源 const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源
const [reqDefaultDetailList, setReqDefaultDetailList] = useState<any>(); // 合计项数据源
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容 const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const [collapsible, setCollapsible] = useState<boolean>(false) const [collapsible, setCollapsible] = useState<boolean>(false)
const [treeView, setTreeView] = useState<any>() const [treeView, setTreeView] = useState<any>()
@ -39,6 +41,10 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
const [searchParams, setSearchParams] = useState<any>() const [searchParams, setSearchParams] = useState<any>()
// 拿到服务区下的所有门店id集合 // 拿到服务区下的所有门店id集合
const [handleAllShopId, sethandleAllShopId] = useState<any>() const [handleAllShopId, sethandleAllShopId] = useState<any>()
// 判断是不是点击了 排序、分页等
const isNoTableReload = useRef<boolean>(false)
// 表格数据
const [tableData, setTableData] = useState<any>()
const columns: any = [ const columns: any = [
{ {
@ -71,8 +77,13 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
valueType: 'select', valueType: 'select',
request: async () => { request: async () => {
const req: any = { const req: any = {
PROVINCE_CODE: currentUser?.ProvinceCode, SearchParameter: {
MERCHANTS_STATE: 1 PROVINCE_CODE: currentUser?.ProvinceCode,
MERCHANTS_STATE: 1
},
PageIndex: 1,
PageSize: 999999
} }
const data = await handleGetMERCHANTSList(req) const data = await handleGetMERCHANTSList(req)
console.log('datadatadatadata', data); console.log('datadatadatadata', data);
@ -129,14 +140,16 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
// defaultSortOrder: 'descend',
// 如果要默认排序的话 默认的那一项 要在导出里面单独去写
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "Storage_Count", dataIndex: "Storage_Count",
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: (a, b) => a.Storage_Count - b.Storage_Count,
defaultSortOrder: 'descend',
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
@ -146,7 +159,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: (a, b) => a.Storage_TaxAmount - b.Storage_TaxAmount,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
@ -156,7 +169,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: (a, b) => a.Storage_Amount - b.Storage_Amount,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
@ -166,7 +179,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: (a, b) => a.Back_Count - b.Back_Count,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
@ -176,7 +189,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: (a, b) => a.Back_TaxAmount - b.Back_TaxAmount,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
@ -186,7 +199,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: (a, b) => a.Back_Amount - b.Back_Amount,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
} }
@ -239,10 +252,152 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
width: 200, width: 200,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "ServerpartShop_Name",
align: 'center',
width: 150,
ellipsis: true,
hideInSearch: true,
}, },
] ]
type SortState = { field?: string; order?: 'ascend' | 'descend' };
type KeyOf<T> = Extract<keyof T, string>;
interface SortTreeOptions<T = any> {
childrenKey?: KeyOf<T>; // 子节点字段名,默认 'children'
}
/** 树形排序:父与父排,父内的子与子排(递归) */
const sortListByState = <T extends Record<string, any>>(
list: T[] = [],
state?: SortState,
options?: SortTreeOptions<T>
): T[] => {
const { childrenKey = 'children' } = options || {};
// 拷贝数组,避免改原数据
const arr = Array.isArray(list) ? list.slice() : [];
if (!state?.field || !state?.order) {
// 即便无排序条件也返回副本,避免引用共享
return arr.map(cloneNode);
}
const { field, order } = state;
const factor = order === 'ascend' ? 1 : -1;
// 取可比较值:数值 > 时间 > 字符串 > 其它null/undefined 放最后
const getComparable = (v: any) => {
if (v == null) return { type: 9, v: Number.POSITIVE_INFINITY };
if (typeof v === 'number') return { type: 0, v };
if (v instanceof Date) return { type: 1, v: v.getTime() };
if (typeof v === 'string') {
const t = Date.parse(v);
if (!Number.isNaN(t)) return { type: 1, v: t };
const n = Number(v);
if (!Number.isNaN(n) && v.trim() !== '' && String(n) === v.trim()) {
return { type: 0, v: n };
}
return { type: 2, v };
}
return { type: 3, v: String(v) };
};
// 比较器:同层内比较 field值相等用原始下标保证稳定
const withIndex = arr.map((item, idx) => ({ item, idx }));
const compare = (a: any, b: any) => {
const va = getComparable(a?.[field as string]);
const vb = getComparable(b?.[field as string]);
if (va.type !== vb.type) return (va.type - vb.type) * factor;
if (va.v < vb.v) return -1 * factor;
if (va.v > vb.v) return 1 * factor;
return 0; // 真正稳定通过 map 阶段的 idx 来保证
};
// 顶层排序(父与父)
const topSorted = withIndex
.slice()
.sort((A, B) => {
const c = compare(A.item, B.item);
return c !== 0 ? c : A.idx - B.idx;
})
.map(({ item }) => item);
// 递归对子节点排序(每个父内子与子)
return topSorted.map((node) => {
const cloned = cloneNode(node);
const children = cloned?.[childrenKey] as T[] | undefined;
if (Array.isArray(children) && children.length) {
// 同样做稳定排序
const childrenWithIndex = children.map((ch, i) => ({ ch, i }));
const childSorted = childrenWithIndex
.slice()
.sort((A, B) => {
const c = compare(A.ch, B.ch);
return c !== 0 ? c : A.i - B.i;
})
.map(({ ch }) => ch);
// 递归:若还有下一级,继续排
cloned[childrenKey] = sortListByState(childSorted, state, { childrenKey });
}
return cloned;
});
function cloneNode<N extends Record<string, any>>(n: N): N {
if (!n || typeof n !== 'object') return n;
const c: any = { ...n };
// children 数组引用也要分离(排序时会重建,保险起见先浅拷贝)
if (Array.isArray(c[childrenKey])) c[childrenKey] = c[childrenKey].slice();
return c;
}
};
const isParent = (r: any) => !r?.Commodity_Id;
// 父级行合并单元格里要显示的自定义内容(你可按需改)
const renderParentCell = (record: any) => {
// 你可以自由拼接:服务区/供应商/门店等
return (
<div style={{ paddingLeft: 8, fontWeight: 600 }}>
{record?.Supplier_Name}-{record?.Serverpart_Name}-{record?.ServerpartShop_Name}
</div>
);
};
const EXPORT_COLSPAN = exportColumns.length;
const mergedExportColumns = [
{
...exportColumns[0],
// 第一列:父级→合并整段导出列;子级→显示序号(原 valueType:'index' 需手动展示)
render: (_: any, record: any, index: number) => {
if (isParent(record)) {
return {
children: renderParentCell(record),
props: { colSpan: EXPORT_COLSPAN },
};
}
// 子级:正常显示序号
return { children: index + 1, props: {} };
},
},
// 其余导出列父级隐藏colSpan:0子级正常显示
...exportColumns.slice(1).map((col: any) => ({
...col,
render: (text: any, record: any) => {
if (isParent(record)) {
return { children: null, props: { colSpan: 0 } };
}
return { children: text, props: {} };
},
})),
];
return ( return (
<div ref={(el) => { <div ref={(el) => {
// 打印报表 // 打印报表
@ -283,7 +438,7 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
{ {
showExportTable && reqDetailList && reqDetailList.length > 0 ? showExportTable && reqDetailList && reqDetailList.length > 0 ?
<ProTable <ProTable
columns={[...exportColumns, ...columns.slice(4, columns.length)]} columns={[...mergedExportColumns, ...columns.slice(5, columns.length)]}
dataSource={reqDetailList} dataSource={reqDetailList}
pagination={false} pagination={false}
expandable={{ expandable={{
@ -316,10 +471,12 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
scroll={{ x: "100%", y: "calc(100vh - 410px)" }} scroll={{ x: "100%", y: "calc(100vh - 410px)" }}
headerTitle={<PageTitleBox props={props} />} // 列表表头 headerTitle={<PageTitleBox props={props} />} // 列表表头
search={{ span: 6 }} search={{ span: 6 }}
dataSource={tableData}
request={async (params, sorter) => { request={async (params, sorter) => {
console.log('selectedIdselectedIdselectedId', selectedId); console.log('selectedIdselectedIdselectedId', selectedId);
if (!selectedId) { if (!selectedId || isNoTableReload.current) {
isNoTableReload.current = false
return return
} }
@ -331,8 +488,6 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
if (list && list.length > 0) { if (list && list.length > 0) {
list.forEach((item: any) => { list.forEach((item: any) => {
for (let key in handleAllShopId) { for (let key in handleAllShopId) {
console.log('handleAllShopId[key]', handleAllShopId[key]);
if (handleAllShopId[key] && handleAllShopId[key].length > 0) { if (handleAllShopId[key] && handleAllShopId[key].length > 0) {
if (handleAllShopId[key].indexOf(Number(item)) !== -1) { if (handleAllShopId[key].indexOf(Number(item)) !== -1) {
if (serverpartId && serverpartId.length > 0) { if (serverpartId && serverpartId.length > 0) {
@ -349,10 +504,10 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
} }
} }
// 排序字段 // 排序字段
const sortstr = Object.keys(sorter).map(n => { // const sortstr = Object.keys(sorter).map(n => {
const value = sorter[n] // const value = sorter[n]
return value ? `${n} ${value.replace('end', '')}` : '' // return value ? `${n} ${value.replace('end', '')}` : ''
}) // })
const req: any = { const req: any = {
ServerpartId: serverpartId && serverpartId.length > 0 ? serverpartId.toString() : "", ServerpartId: serverpartId && serverpartId.length > 0 ? serverpartId.toString() : "",
ServerpartShopId: selectedId, ServerpartShopId: selectedId,
@ -361,31 +516,38 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
EndDate: params?.EndDate || "", EndDate: params?.EndDate || "",
SearchKeyName: "", SearchKeyName: "",
SearchKeyValue: "", SearchKeyValue: "",
sortstr: sortstr.length ? sortstr.toString() : "", // sortstr: sortstr.length ? sortstr.toString() : "",
} }
setSearchParams(params) setSearchParams(params)
const data = await handleGetStorageBackSummary(req) const data = await handleGetStorageBackSummary(req)
console.log('dafjkdjaf', data); console.log('dafjkdjaf', data);
if (data && data.length > 0) { if (data && data.length > 0) {
let exportList: any = [] let exportList: any = []
data.forEach((item: any) => { data.forEach((item: any) => {
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
item.children.forEach((subItem: any) => { item.children.forEach((subItem: any) => {
let newChildren: any = []
if (subItem.children && subItem.children.length > 0) { if (subItem.children && subItem.children.length > 0) {
subItem.children.forEach((thirdItem: any) => { subItem.children.forEach((thirdItem: any) => {
exportList.push(thirdItem) // exportList.push(thirdItem)
newChildren.push(thirdItem)
}) })
} }
subItem.children = newChildren
exportList.push(subItem)
}) })
} }
}) })
setReqDetailList(exportList) setReqDetailList(exportList)
return { data, success: true } setReqDefaultDetailList(exportList)
setTableData(data)
// return { data, success: true }
} else {
setTableData([])
// return { data: [], success: true }
} }
return { data: [], success: true }
}} }}
toolbar={{ toolbar={{
actions: [ actions: [
@ -402,23 +564,47 @@ const purchaseReceiving: React.FC<{ currentUser: CurrentUser }> = (props) => {
key="new" key="new"
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
console.log('reqDetailListreqDetailListreqDetailList', reqDetailList);
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(columns,
setShowExportTable(true) reqDetailList,
setTimeout(() => { `入库退货统计表${searchParams?.StartDate}-${searchParams?.EndDate}`,
exportTable(e) {
}, 100) topTitle: `入库退货统计表`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel
</Button> </Button>
] ]
}} }}
onChange={(_pagination: any, _filters: any, sorter: any, extra: any) => {
console.log('onChange');
isNoTableReload.current = true
if (extra?.action === 'sort') {
const field = Array.isArray(sorter) ? sorter[0]?.field : sorter?.field;
const order = Array.isArray(sorter) ? sorter[0]?.order : sorter?.order;
const newList: any = sortListByState(reqDefaultDetailList, { field, order }, { childrenKey: 'children' })
console.log('newListnewList', newList);
setReqDetailList(newList)
}
}}
/> />
</div> </div>
</div> </div>

View File

@ -7,13 +7,15 @@ import Draggable from "react-draggable";
import ProTable, { ActionType } from "@ant-design/pro-table"; import ProTable, { ActionType } from "@ant-design/pro-table";
import { handleGetBACKCOMMODITYDetail, handleGetBACKCOMMODITYList, handleGetSALESTOREPROINSTDetail } from "../../service"; import { handleGetBACKCOMMODITYDetail, handleGetBACKCOMMODITYList, handleGetSALESTOREPROINSTDetail } from "../../service";
import { handleNewPrint, handleNewPrintAHJG } from "@/utils/format"; import { handleNewPrint, handleNewPrintAHJG } from "@/utils/format";
import { highlightText } from "@/utils/highlightText";
type DetailProps = { type DetailProps = {
showDetail: boolean; showDetail: boolean;
parentRow: any; parentRow: any;
onCencel: any; onCencel: any;
searchParams?: any
} }
const ReturnGoodsTable = ({ showDetail, parentRow, onCencel }: DetailProps) => { const ReturnGoodsTable = ({ showDetail, parentRow, onCencel, searchParams }: DetailProps) => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>(); const formRef = useRef<FormInstance>();
const draggleRef = React.createRef<any>() const draggleRef = React.createRef<any>()
@ -50,32 +52,38 @@ const ReturnGoodsTable = ({ showDetail, parentRow, onCencel }: DetailProps) => {
width: 120, width: 120,
dataIndex: "COMMODITY_BARCODE", dataIndex: "COMMODITY_BARCODE",
align: 'center', align: 'center',
ellipsis: true ellipsis: true,
render: (_, record) => {
return highlightText(record?.COMMODITY_BARCODE, searchParams?.searchText)
}
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
width: 150, width: 250,
dataIndex: "COMMODITY_NAME", dataIndex: "COMMODITY_NAME",
align: 'left', align: 'left',
ellipsis: true ellipsis: true,
render: (_, record) => {
return highlightText(record?.COMMODITY_NAME, searchParams?.searchText)
}
}, },
{ {
title: '退货门店', title: '退货门店',
width: 120, width: 150,
dataIndex: "SHOPNAME", dataIndex: "SHOPNAME",
align: 'center', align: 'center',
ellipsis: true ellipsis: true
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
width: 150, width: 250,
dataIndex: "SUPPLIER_NAME", dataIndex: "SUPPLIER_NAME",
align: 'left', align: 'left',
ellipsis: true ellipsis: true
}, },
{ {
title: <div style={{ textAlign: 'center' }}>退</div>, title: <div style={{ textAlign: 'center' }}>退</div>,
width: 120, width: 100,
dataIndex: "BACK_COUNT", dataIndex: "BACK_COUNT",
valueType: 'digit', valueType: 'digit',
align: 'right', align: 'right',
@ -83,14 +91,14 @@ const ReturnGoodsTable = ({ showDetail, parentRow, onCencel }: DetailProps) => {
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
width: 120, width: 100,
dataIndex: "DUTY_PARAGRAPH", dataIndex: "DUTY_PARAGRAPH",
align: 'right', align: 'right',
ellipsis: true ellipsis: true
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
width: 120, width: 100,
dataIndex: "PURCHASE_TAXPRICE", dataIndex: "PURCHASE_TAXPRICE",
valueType: 'digit', valueType: 'digit',
align: 'right', align: 'right',
@ -98,7 +106,7 @@ const ReturnGoodsTable = ({ showDetail, parentRow, onCencel }: DetailProps) => {
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
width: 120, width: 100,
dataIndex: "BACKTAXPRICE", dataIndex: "BACKTAXPRICE",
valueType: 'digit', valueType: 'digit',
align: 'right', align: 'right',
@ -112,10 +120,10 @@ const ReturnGoodsTable = ({ showDetail, parentRow, onCencel }: DetailProps) => {
ellipsis: true ellipsis: true
}, },
{ {
title: '退货原由', title: <div style={{ textAlign: 'center' }}>退</div>,
width: 120, width: 250,
dataIndex: "BACK_DESC", dataIndex: "BACK_DESC",
align: 'center', align: 'left',
ellipsis: true ellipsis: true
} }
] ]
@ -226,6 +234,7 @@ const ReturnGoodsTable = ({ showDetail, parentRow, onCencel }: DetailProps) => {
pagination={false} pagination={false}
columns={columns} columns={columns}
options={false} options={false}
scroll={{ x: "100%", y: 400 }}
request={async () => { request={async () => {
console.log('parentRow', parentRow); console.log('parentRow', parentRow);
if (!parentRow?.SALESTOREPROINST_ID) { if (!parentRow?.SALESTOREPROINST_ID) {

View File

@ -16,6 +16,7 @@ import PageTitleBox from "@/components/PageTitleBox";
import moment from "moment"; import moment from "moment";
import { handleGetSALESTOREPROINSTList } from "../service"; import { handleGetSALESTOREPROINSTList } from "../service";
import ReturnGoodsTable from "./components/returnGoodsTable"; import ReturnGoodsTable from "./components/returnGoodsTable";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => { const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
@ -76,19 +77,19 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
}, },
initialValue: [moment().startOf('M'), moment()], initialValue: [moment().startOf('M'), moment()],
}, },
{ // {
title: '退货模式', // title: '退货模式',
dataIndex: "", // dataIndex: "",
align: 'center', // align: 'center',
valueType: 'select', // valueType: 'select',
width: 120, // width: 120,
ellipsis: true, // ellipsis: true,
hideInTable: true, // hideInTable: true,
fieldProps: { // fieldProps: {
options: [{ label: "全部", value: '' }, { label: "自采", value: 5 }, { label: "统配", value: 107 }] // options: [{ label: "全部", value: '' }, { label: "自采", value: 5 }, { label: "统配", value: 107 }]
}, // },
initialValue: '' // initialValue: ''
}, // },
{ {
title: "序号", title: "序号",
@ -125,11 +126,12 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
// }, // },
{ {
title: <div style={{ textAlign: 'center' }}>退</div>, title: <div style={{ textAlign: 'center' }}>退</div>,
dataIndex: "退货数量", dataIndex: "OPERATE_COUNT",
align: 'right', align: 'right',
width: 150, width: 150,
ellipsis: true, ellipsis: true,
sorter: true, // sorter: true,
sorter: (a, b) => a.OPERATE_COUNT - b.OPERATE_COUNT,
hideInSearch: true, hideInSearch: true,
render: (_, record) => { render: (_, record) => {
return <a onClick={() => { return <a onClick={() => {
@ -138,40 +140,50 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
setCurrentRow(record) setCurrentRow(record)
setOnShow(true) setOnShow(true)
}}> }}>
1 {record?.OPERATE_COUNT || ""}
</a> </a>
} }
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "合计含税进价", dataIndex: "OPERATE_TAXAMOUNT",
align: 'right', align: 'right',
width: 150, width: 150,
sorter: true, sorter: (a, b) => a.OPERATE_TAXAMOUNT - b.OPERATE_TAXAMOUNT,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
{ {
title: "待审核人", title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "待审核人", dataIndex: "OPERATE_AMOUNT",
align: 'center', align: 'right',
width: 150,
ellipsis: true,
hideInSearch: true,
},
{
title: "流程名称",
dataIndex: "流程名称",
align: 'center',
width: 150, width: 150,
sorter: (a, b) => a.OPERATE_AMOUNT - b.OPERATE_AMOUNT,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
// {
// title: "待审核人",
// dataIndex: "待审核人",
// align: 'center',
// width: 150,
// ellipsis: true,
// hideInSearch: true,
// },
// {
// title: "流程名称",
// dataIndex: "流程名称",
// align: 'center',
// width: 150,
// ellipsis: true,
// hideInSearch: true,
// },
{ {
title: "流程开始时间", title: "流程开始时间",
dataIndex: "CREATEDATE", dataIndex: "CREATEDATE",
align: 'center', align: 'center',
width: 150, width: 150,
sorter: (a, b) => new Date(a.CREATEDATE).getTime() - new Date(b.CREATEDATE).getTime(),
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
}, },
@ -179,7 +191,9 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
title: "流程结束时间", title: "流程结束时间",
dataIndex: "ENDDATE", dataIndex: "ENDDATE",
align: 'center', align: 'center',
defaultSortOrder: 'descend',
width: 150, width: 150,
sorter: (a, b) => new Date(a.ENDDATE).getTime() - new Date(b.ENDDATE).getTime(),
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
} }
@ -280,7 +294,7 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
expandable={{ expandable={{
expandRowByClick: true expandRowByClick: true
}} }}
scroll={{ x: "100%", y: "calc(100vh - 410px)" }} scroll={{ x: "100%", y: "calc(100vh - 430px)" }}
headerTitle={<PageTitleBox props={props} />} // 列表表头 headerTitle={<PageTitleBox props={props} />} // 列表表头
search={{ span: 6 }} search={{ span: 6 }}
request={async (params, sorter) => { request={async (params, sorter) => {
@ -298,10 +312,14 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
DEPT_IDS: selectedId, DEPT_IDS: selectedId,
CREATEDATE_Start: params.CREATEDATE_Start || "", CREATEDATE_Start: params.CREATEDATE_Start || "",
CREATEDATE_End: params.CREATEDATE_End || "", CREATEDATE_End: params.CREATEDATE_End || "",
ACCEPT_CODE: "200300",
SearchOtherKeyName: "COMMODITY_NAME,COMMODITY_BARCODE",
SearchOtherKeyValue: params?.searchText || '',
}, },
PageIndex: 1, PageIndex: 1,
PageSize: 999999, PageSize: 999999,
sortstr: sortstr.length ? sortstr.toString() : "", // sortstr: sortstr.length ? sortstr.toString() : "",
sortstr: "ENDDATE desc",
} }
setSearchParams(params) setSearchParams(params)
@ -330,16 +348,30 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(columns,
setShowExportTable(true) reqDetailList,
setTimeout(() => { `退货流程统计${searchParams?.CREATEDATE_Start}-${searchParams?.CREATEDATE_End}`,
exportTable(e) {
}, 100) topTitle: `退货流程统计表`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel
@ -351,7 +383,7 @@ const returnProcess: React.FC<{ currentUser: CurrentUser }> = (props) => {
</div> </div>
{/* 退货单 */} {/* 退货单 */}
<ReturnGoodsTable showDetail={onShow} parentRow={currentRow} onCencel={handleCloseModal} /> <ReturnGoodsTable showDetail={onShow} parentRow={currentRow} onCencel={handleCloseModal} searchParams={searchParams} />
</div> </div>
) )
} }

View File

@ -7,14 +7,16 @@ import Draggable from "react-draggable";
import ProTable, { ActionType } from "@ant-design/pro-table"; import ProTable, { ActionType } from "@ant-design/pro-table";
import ReactHTMLTableToExcel from "react-html-table-to-excel"; import ReactHTMLTableToExcel from "react-html-table-to-excel";
import { handleGetRECEIVEDETAILSERVERPARTList, handleGetRECEIVESERVERPARTDetail } from "../../service"; import { handleGetRECEIVEDETAILSERVERPARTList, handleGetRECEIVESERVERPARTDetail } from "../../service";
import { highlightText } from "@/utils/highlightText";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
type DetailProps = { type DetailProps = {
onShow: boolean; onShow: boolean;
parentRow: any; parentRow: any;
onCencel: any onCencel: any
searchParams?: any // 如果需要高亮查询文字 这个就有值
} }
const warehouseInfo = ({ onShow, parentRow, onCencel }: DetailProps) => { const warehouseInfo = ({ onShow, parentRow, onCencel, searchParams }: DetailProps) => {
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>(); const formRef = useRef<FormInstance>();
const draggleRef = React.createRef<any>() const draggleRef = React.createRef<any>()
@ -65,6 +67,9 @@ const warehouseInfo = ({ onShow, parentRow, onCencel }: DetailProps) => {
width: 200, width: 200,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
render: (_, record) => {
return highlightText(record?.COMMODITY_NAME, searchParams?.searchText)
}
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
@ -73,6 +78,9 @@ const warehouseInfo = ({ onShow, parentRow, onCencel }: DetailProps) => {
width: 120, width: 120,
ellipsis: true, ellipsis: true,
hideInSearch: true, hideInSearch: true,
render: (_, record) => {
return highlightText(record?.COMMODITY_BARCODE, searchParams?.searchText)
}
}, },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
@ -336,16 +344,30 @@ const warehouseInfo = ({ onShow, parentRow, onCencel }: DetailProps) => {
loading={showLoading} loading={showLoading}
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(columns,
setShowExportTable(true) reqDetailList,
setTimeout(() => { `${parentRow?.SERVERPART_NAME}入库验收单${parentRow?.RECEIVECENTER_DATE || ""}`,
exportTable(e) {
}, 100) topTitle: `${currentModalDetail?.SERVERPART_NAME || ''}${currentModalDetail?.SHOPNAME || ''}入库单`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel

View File

@ -23,6 +23,7 @@ import shop from "@/pages/BussinessProject/shop";
import { getMyShopList } from "@/pages/account/center/sevice"; import { getMyShopList } from "@/pages/account/center/sevice";
import { handleGetRECEIVESERVERPARTList } from "../service"; import { handleGetRECEIVESERVERPARTList } from "../service";
import WarehouseInfo from "./components/warehouseInfo"; import WarehouseInfo from "./components/warehouseInfo";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
const { Text } = Typography; const { Text } = Typography;
@ -111,7 +112,7 @@ const shopProcurement: React.FC<{ currentUser: CurrentUser }> = (props) => {
title: '查询内容', title: '查询内容',
hideInTable: true, hideInTable: true,
fieldProps: { fieldProps: {
placeholder: "请输入商品名称/入库单号" placeholder: "请输入商品名称/商品条码"
} }
}, },
{ {
@ -404,11 +405,13 @@ const shopProcurement: React.FC<{ currentUser: CurrentUser }> = (props) => {
SERVERPARTSHOP_IDS: selectedId, SERVERPARTSHOP_IDS: selectedId,
RECEIVECENTER_DATE_Start: params?.RECEIVECENTER_DATE_Start || "", RECEIVECENTER_DATE_Start: params?.RECEIVECENTER_DATE_Start || "",
RECEIVECENTER_DATE_End: params?.RECEIVECENTER_DATE_End || "", RECEIVECENTER_DATE_End: params?.RECEIVECENTER_DATE_End || "",
SearchOtherKeyName: "COMMODITY_NAME,COMMODITY_BARCODE",
SearchOtherKeyValue: params?.searchText || '',
}, },
keyWord: { // keyWord: {
Key: 'RECEIVECENTER_CODE', // Key: 'RECEIVECENTER_CODE',
Value: params?.searchText || '' // Value: params?.searchText || ''
}, // },
PageIndex: 1, PageIndex: 1,
PageSize: 999999, PageSize: 999999,
sortstr: sortstr.length ? sortstr.toString() : "", sortstr: sortstr.length ? sortstr.toString() : "",
@ -452,16 +455,30 @@ const shopProcurement: React.FC<{ currentUser: CurrentUser }> = (props) => {
type="primary" type="primary"
onClick={(e) => { onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) { if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true) // 尝试一下 导出新方法
setTimeout(() => { exportXlsxFromProColumnsExcelJS(columns,
setShowExportTable(true) reqDetailList,
setTimeout(() => { `商品入库统计表${searchParams?.RECEIVECENTER_DATE_Start}-${searchParams?.RECEIVECENTER_DATE_End}`,
exportTable(e) {
}, 100) topTitle: `商品入库统计表`, // 顶部大标题
}, 100) }
)
} else { } else {
message.error('暂无数据可导出!') message.error('暂无数据可导出!')
} }
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}} }}
> >
excel excel
@ -529,8 +546,8 @@ const shopProcurement: React.FC<{ currentUser: CurrentUser }> = (props) => {
{/* 说是要里面高亮 查询的内容 那么就要传入 searchParams */}
<WarehouseInfo onShow={showDetail} parentRow={currentRow} onCencel={handleGetClose} /> <WarehouseInfo onShow={showDetail} parentRow={currentRow} onCencel={handleGetClose} searchParams={searchParams} />
</div> </div>
) )
} }

View File

@ -251,8 +251,6 @@ const ConvenienceStoreProductReview: React.FC<{ currentUser: CurrentUser }> = (p
if (selectModalRowList && selectModalRowList.length > 0) { if (selectModalRowList && selectModalRowList.length > 0) {
// 商品勾选的行 // 商品勾选的行
let list: any = JSON.parse(JSON.stringify(selectedModalOrderRowKeys)) let list: any = JSON.parse(JSON.stringify(selectedModalOrderRowKeys))
console.log('listlistlist', list);
console.log('tableDatatableDatatableData', tableData);
let reqList: any = [] let reqList: any = []
// 判断是不是所有的价格都已经输入了 // 判断是不是所有的价格都已经输入了
let isAllOk: boolean = true let isAllOk: boolean = true
@ -261,7 +259,7 @@ const ConvenienceStoreProductReview: React.FC<{ currentUser: CurrentUser }> = (p
tableData.forEach((item: any) => { tableData.forEach((item: any) => {
if (list.indexOf(item.COMMODITY_ID.toString()) !== -1) { if (list.indexOf(item.COMMODITY_ID.toString()) !== -1) {
if (item.COMMODITY_TYPE === '普通商品') { if (item.COMMODITY_TYPE === '普通商品') {
item.COMMODITY_TYPE = 1012 item.COMMODITY_TYPE = 1903
} }
reqList.push(item) reqList.push(item)
} }
@ -283,10 +281,7 @@ const ConvenienceStoreProductReview: React.FC<{ currentUser: CurrentUser }> = (p
let req: any = { let req: any = {
list: reqList list: reqList
} }
console.log('reqreqreqreq', req);
const data = await handleApproveCommodityInfo_AHJG(req) const data = await handleApproveCommodityInfo_AHJG(req)
console.log('datadatadatadata', data);
if (data.Result_Code === 100) { if (data.Result_Code === 100) {
message.success(data.Result_Desc) message.success(data.Result_Desc)

296
src/utils/exportExcelFun.ts Normal file
View File

@ -0,0 +1,296 @@
// exportExcel.ts
import ExcelJS from 'exceljs';
/** ======== 列类型(按需裁剪) ======== */
type AnyCol = {
title?: any;
dataIndex?: string | (string | number)[];
children?: AnyCol[];
valueEnum?: Record<string | number, { text?: string } | string>;
renderText?: (text: any, record: any, index: number) => any;
hideInTable?: boolean;
valueType?: 'index' | string;
};
/** ========== 新增:拍平树形数据 ========== */
function flattenTree<T extends Record<string, any>>(
list: T[] = [],
childrenKey = 'children',
out: T[] = []
): T[] {
for (const node of list) {
// 先推当前节点(浅拷贝去掉 children避免把对象树写入单元格
const { [childrenKey]: kids, ...rest } = node as any;
out.push(rest as T);
if (Array.isArray(kids) && kids.length) {
flattenTree(kids, childrenKey, out);
}
}
return out;
}
/** 抽取 React 节点文本 */
function extractText(node: any): string {
if (node == null || node === false) return '';
if (typeof node === 'string' || typeof node === 'number') return String(node);
const children = node?.props?.children;
if (Array.isArray(children)) return children.map(extractText).join('');
if (children != null) return extractText(children);
const html = node?.props?.dangerouslySetInnerHTML?.__html;
if (typeof html === 'string') return html.replace(/<[^>]+>/g, '');
return String(node ?? '');
}
/** dataIndex 路径取值 */
const toPath = (di?: AnyCol['dataIndex']): (string | number)[] =>
Array.isArray(di) ? di : (typeof di === 'string' ? di.split('.') : []);
const getByPath = (obj: any, path: (string | number)[]) =>
path.reduce((acc, k) => (acc == null ? acc : acc[k]), obj);
/** 过滤 hideInTable父级联动 */
function pruneHiddenColumns(cols: AnyCol[]): AnyCol[] {
const walk = (arr: AnyCol[]): AnyCol[] =>
(arr || [])
.filter(col => !col?.hideInTable)
.map(col => {
if (col.children?.length) {
const kids = walk(col.children);
if (!kids.length) return null as any;
return { ...col, children: kids };
}
return col;
})
.filter(Boolean);
return walk(cols);
}
/** 叶子列(有 dataIndex 的) */
const getLeaves = (cols: AnyCol[]): AnyCol[] => {
const out: AnyCol[] = [];
const walk = (arr: AnyCol[]) => {
arr.forEach(c => {
if (c?.children?.length) {
walk(c.children!);
} else if (c && (c.dataIndex || c.valueType === 'index')) { // << 修改
out.push(c);
}
});
};
walk(cols);
return out;
};
/** 深度和列跨度 */
const getDepth = (cols: AnyCol[]): number => {
const dfs = (c: AnyCol): number =>
c.children?.length ? 1 + Math.max(...c.children.map(dfs)) : 1;
return Math.max(...cols.map(dfs));
};
const getColSpan = (c: AnyCol): number =>
c.children?.length ? c.children.map(getColSpan).reduce((a, b) => a + b, 0) : 1;
/** 构造多级表头矩阵 & 合并信息(不含顶部大标题/信息行) */
function buildHeaderMatrix(cols: AnyCol[]) {
const depth = getDepth(cols);
const rows: string[][] = Array.from({ length: depth }, () => []);
let colCursor = 0;
const merges: Array<{ r1: number; c1: number; r2: number; c2: number }> = [];
const place = (list: AnyCol[], level: number) => {
list.forEach(col => {
const span = getColSpan(col);
const rowSpan = col.children?.length ? 1 : depth - level;
const title = extractText(col.title ?? '');
rows[level][colCursor] = title;
for (let i = 1; i < span; i++) rows[level][colCursor + i] = '';
if (span > 1 || rowSpan > 1) {
merges.push({ r1: level + 1, c1: colCursor + 1, r2: level + rowSpan, c2: colCursor + span });
}
if (col.children?.length) {
place(col.children, level + 1);
} else {
colCursor += 1;
}
});
};
place(cols, 0);
const maxLen = Math.max(...rows.map(r => r.length));
rows.forEach(r => { for (let i = 0; i < maxLen; i++) if (typeof r[i] === 'undefined') r[i] = ''; });
return { headerAOA: rows, merges, depth, columnCount: maxLen };
}
/** 单元格显示值valueEnum / renderText / 原始值) */
function getCellValue(col: AnyCol, record: any, rowIndex: number) {
// << 新增:序号列
if (col.valueType === 'index') return rowIndex + 1;
const raw = getByPath(record, toPath(col.dataIndex));
if (col.valueEnum) {
const ve = col.valueEnum[raw as any];
if (typeof ve === 'string') return ve;
if (ve?.text != null) return ve.text;
}
if (col.renderText) {
try { return col.renderText(raw, record, rowIndex); } catch { }
}
return raw;
}
/** 估算列宽(简单) */
const estimateWidth = (v: any) => {
const s = (v ?? '').toString();
const len = Array.from(s).reduce((n, ch) => n + (/[^\x00-\xff]/.test(ch) ? 2 : 1), 0);
return Math.min(Math.max(len + 2, 8), 60);
};
export async function exportXlsxFromProColumnsExcelJS(
rawColumns: AnyCol[],
dataSource: any[],
filename?: string,
options?: {
sheetName?: string;
chunkSize?: number;
topTitle?: string; // 顶部大标题(整表合并 + 居中)
infoRowLeft?: string; // 标题下插入的左侧文字
infoRowRight?: string; // 标题下插入的右侧文字(右对齐)
freezeHeader?: boolean; // 冻结到哪一行(自动计算)
childrenKey?: string;
}
) {
const {
sheetName = '数据',
chunkSize = 100_000,
topTitle,
infoRowLeft,
infoRowRight,
freezeHeader = true,
childrenKey = 'children',
} = options || {};
// === 新增:拍平树形数据 ===
const flatData = flattenTree<any>(Array.isArray(dataSource) ? dataSource : [], childrenKey);
const columns = pruneHiddenColumns(rawColumns);
const leafCols = getLeaves(columns);
if (!leafCols.length) throw new Error('无可导出的列(可能被 hideInTable 全部隐藏)');
const { headerAOA, merges, columnCount } = buildHeaderMatrix(columns);
const wb = new ExcelJS.Workbook();
wb.created = new Date();
wb.modified = new Date();
const total = flatData?.length ?? 0;
const totalSheets = Math.max(1, Math.ceil(total / chunkSize));
for (let si = 0; si < totalSheets; si++) {
const ws = wb.addWorksheet(totalSheets === 1 ? sheetName : `${sheetName}_${si + 1}`, {
views: [{ state: 'frozen' }],
});
// 1) 顶部大标题(可选)
let currentRowIndex = 1;
if (topTitle) {
const row = ws.getRow(currentRowIndex);
// 写入一个单元格,再合并整行
row.getCell(1).value = topTitle;
ws.mergeCells(currentRowIndex, 1, currentRowIndex, columnCount);
// 居中 + 加粗 + 较大字号
const cell = ws.getCell(currentRowIndex, 1);
cell.alignment = { horizontal: 'center', vertical: 'middle' };
cell.font = { bold: true, size: 14 };
row.height = 22;
currentRowIndex += 1;
}
// 2) 信息行(可选,左右结构)
if (infoRowLeft != null || infoRowRight != null) {
const row = ws.getRow(currentRowIndex);
// 左侧:合并 1..(columnCount/2),左对齐
const split = Math.max(1, Math.floor(columnCount / 2));
if (infoRowLeft != null) {
row.getCell(1).value = infoRowLeft;
ws.mergeCells(currentRowIndex, 1, currentRowIndex, split);
const leftCell = ws.getCell(currentRowIndex, 1);
leftCell.alignment = { horizontal: 'left', vertical: 'middle' };
leftCell.font = { size: 11 };
}
// 右侧:合并 (split+1)..columnCount右对齐
if (infoRowRight != null) {
row.getCell(split + 1).value = infoRowRight;
ws.mergeCells(currentRowIndex, split + 1, currentRowIndex, columnCount);
const rightCell = ws.getCell(currentRowIndex, split + 1);
rightCell.alignment = { horizontal: 'right', vertical: 'middle' };
rightCell.font = { size: 11 };
}
row.height = 18;
currentRowIndex += 1;
}
// 3) 多级表头(全部居中 + 加粗)
const headerStartRow = currentRowIndex;
headerAOA.forEach((r, idx) => {
const row = ws.getRow(headerStartRow + idx);
r.forEach((v, cIdx) => {
const cell = row.getCell(cIdx + 1);
cell.value = v;
cell.alignment = { horizontal: 'center', vertical: 'middle' };
cell.font = { bold: true };
});
row.height = 18;
});
// 应用表头合并
merges.forEach(m => {
ws.mergeCells(headerStartRow + (m.r1 - 1), m.c1, headerStartRow + (m.r2 - 1), m.c2);
});
currentRowIndex += headerAOA.length;
// 4) 数据(从 currentRowIndex 开始)
const start = si * chunkSize;
const end = Math.min(start + chunkSize, total);
const batch = flatData.slice(start, end);
for (let i = 0; i < batch.length; i++) {
const rec = batch[i];
const row = ws.getRow(currentRowIndex + i);
leafCols.forEach((col, j) => {
// 这里 rowIndex 仍然传全局行号 start + i序号列会自动正确
row.getCell(j + 1).value = getCellValue(col, rec, start + i);
});
// 适度让出主线程ExcelJS 是纯 JS通常也很稳如需进一步优化可用 setTimeout 分批
}
currentRowIndex += batch.length;
// 5) 列宽:基于表头 + 采样数据估算
const sampleRows = Math.min(batch.length, 200);
for (let c = 1; c <= columnCount; c++) {
const headerMax = Math.max(...headerAOA.map(r => estimateWidth(r[c - 1])));
let dataMax = 8;
for (let i = 0; i < sampleRows; i++) {
const v = leafCols[c - 1] ? getCellValue(leafCols[c - 1], batch[i], start + i) : '';
dataMax = Math.max(dataMax, estimateWidth(v));
}
ws.getColumn(c).width = Math.max(headerMax, dataMax);
}
// 6) 冻结窗格:冻结到(标题 + 信息行 + 表头)这一行的下一行
if (freezeHeader) {
// const freezeRow = (topTitle ? 1 : 0) + (infoRowLeft != null || infoRowRight != null ? 1 : 0) + headerAOA.length;
// ws.views = [{ state: 'frozen', ySplit: freezeRow }];
}
}
// 生成并下载(浏览器环境)
const buf = await wb.xlsx.writeBuffer();
const blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${filename}.xlsx`;
a.click();
URL.revokeObjectURL(url);
}

View File

@ -1,4 +1,4 @@
// 由 scripts/writeVersion.js 自动生成 // 由 scripts/writeVersion.js 自动生成
export const VERSION = "4.5.42"; export const VERSION = "4.5.45";
export const GIT_HASH = "a05dd91"; export const GIT_HASH = "d3a4c2a";
export const BUILD_TIME = "2025-09-01T07:45:38.579Z"; export const BUILD_TIME = "2025-09-02T10:19:24.187Z";