update
This commit is contained in:
parent
83cb30fc12
commit
63f1b3dc8a
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ant-design-pro",
|
||||
"version": "4.5.128",
|
||||
"version": "4.5.131",
|
||||
"private": true,
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"scripts": {
|
||||
|
||||
@ -787,7 +787,7 @@ const OrderDetailModal = ({ modalVisible, handleCloseModal, currentRow, detailTy
|
||||
}
|
||||
<Col span={8} className="memberInfoDetailItem">
|
||||
<ProFormText
|
||||
name={"OrderDesc"}
|
||||
name={"SALEBILL_DESC"}
|
||||
label={"订单备注"}
|
||||
readonly
|
||||
style={{ marginBottom: '16px' }}
|
||||
@ -802,6 +802,8 @@ const OrderDetailModal = ({ modalVisible, handleCloseModal, currentRow, detailTy
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
|
||||
<span style={{ fontWeight: 'bold', fontSize: '14px' }}>物流信息</span>
|
||||
{
|
||||
Number(currentRow?.SALEBILL_STATE) >= 3000 ? '' :
|
||||
<Button
|
||||
type="dashed"
|
||||
icon={<PlusOutlined />}
|
||||
@ -811,6 +813,7 @@ const OrderDetailModal = ({ modalVisible, handleCloseModal, currentRow, detailTy
|
||||
>
|
||||
添加物流信息
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
{logisticsList.map((logistics, index) => (
|
||||
<div key={logistics.id} style={{ marginBottom: '12px', padding: '12px', border: '1px solid #d9d9d9', borderRadius: '6px', backgroundColor: '#fafafa' }}>
|
||||
@ -859,6 +862,8 @@ const OrderDetailModal = ({ modalVisible, handleCloseModal, currentRow, detailTy
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
{
|
||||
Number(currentRow?.SALEBILL_STATE) >= 3000 ? '' :
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
@ -869,6 +874,7 @@ const OrderDetailModal = ({ modalVisible, handleCloseModal, currentRow, detailTy
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
@ -137,7 +137,7 @@ const MallOrderManage: React.FC<{ currentUser: CurrentUser, isComponent?: boolea
|
||||
"0": "全部",
|
||||
"3000": "零售商城",
|
||||
"3001": "工会商城",
|
||||
"3002": "品诺商城",
|
||||
// "3002": "品诺商城",
|
||||
"3010": "积分商城"
|
||||
},
|
||||
initialValue: '0',
|
||||
@ -315,7 +315,7 @@ const MallOrderManage: React.FC<{ currentUser: CurrentUser, isComponent?: boolea
|
||||
valueEnum: {
|
||||
"3000": "零售商城",
|
||||
"3001": "工会商城",
|
||||
"3002": "品诺商城",
|
||||
// "3002": "品诺商城",
|
||||
"3010": "积分商城"
|
||||
},
|
||||
// valueEnum: {
|
||||
@ -424,40 +424,41 @@ const MallOrderManage: React.FC<{ currentUser: CurrentUser, isComponent?: boolea
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// title: '供应商',
|
||||
// dataIndex: "MERCHANTS_IDS",
|
||||
// valueType: 'select',
|
||||
// request: async () => {
|
||||
// const req = {
|
||||
// searchParameter: {
|
||||
// OWNERUNIT_ID: currentUser?.OwnerUnitId,
|
||||
// PROVINCE_CODE: currentUser?.ProvinceCode,
|
||||
// MERCHANTS_TYPE: ""
|
||||
// },
|
||||
// PageIndex: 1,
|
||||
// PageSize: 999999,
|
||||
// }
|
||||
// const data = await handeGetMERCHANTSList(req);
|
||||
// return data.List
|
||||
// },
|
||||
// hideInTable: true,
|
||||
// fieldProps: {
|
||||
// allowClear: true,
|
||||
// showSearch: true,
|
||||
// filterTreeNode: (input, node) => {
|
||||
// // ✅ 输入时根据 AUTOTYPE_NAME 模糊匹配
|
||||
// return node?.MERCHANTS_NAME?.toLowerCase()?.includes(input.toLowerCase());
|
||||
// },
|
||||
// treeDefaultExpandAll: true,
|
||||
// fieldNames: {
|
||||
// label: 'MERCHANTS_NAME',
|
||||
// value: 'MERCHANTS_ID',
|
||||
// },
|
||||
// disabled: currentUser?.UserPattern === 4000
|
||||
// },
|
||||
{
|
||||
title: '供应商',
|
||||
dataIndex: "MERCHANTS_IDS",
|
||||
valueType: 'select',
|
||||
request: async () => {
|
||||
const req = {
|
||||
searchParameter: {
|
||||
OWNERUNIT_ID: currentUser?.OwnerUnitId,
|
||||
PROVINCE_CODE: currentUser?.ProvinceCode,
|
||||
MERCHANTS_TYPE: ""
|
||||
},
|
||||
PageIndex: 1,
|
||||
PageSize: 999999,
|
||||
}
|
||||
const data = await handeGetMERCHANTSList(req);
|
||||
return data.List
|
||||
},
|
||||
hideInTable: true,
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
filterTreeNode: (input, node) => {
|
||||
// ✅ 输入时根据 AUTOTYPE_NAME 模糊匹配
|
||||
return node?.MERCHANTS_NAME?.toLowerCase()?.includes(input.toLowerCase());
|
||||
},
|
||||
treeDefaultExpandAll: true,
|
||||
fieldNames: {
|
||||
label: 'MERCHANTS_NAME',
|
||||
value: 'MERCHANTS_ID',
|
||||
},
|
||||
disabled: currentUser?.UserPattern === 4000
|
||||
},
|
||||
hideInSearch: currentUser?.UserPattern === 4000,
|
||||
// initialValue: currentUser?.UserPattern === 4000 ? currentUser?.SupplierID : ""
|
||||
// },
|
||||
},
|
||||
{
|
||||
title: '单位名称',
|
||||
dataIndex: "COMPANY_IDS",
|
||||
@ -789,15 +790,17 @@ const MallOrderManage: React.FC<{ currentUser: CurrentUser, isComponent?: boolea
|
||||
|
||||
// 跟交易台账保持一致的 导出Excel
|
||||
const handleGetExportData = async () => {
|
||||
console.log('searchParams', searchParams);
|
||||
|
||||
setGetExportDataLoading(true)
|
||||
const req: any = {
|
||||
ExportType: 1,
|
||||
OwnerUnitId: "911",
|
||||
CompanyId: searchParams?.CompanyId || "",
|
||||
MerchantId: currentUser?.SupplierID || "",
|
||||
SaleBillState: searchParams?.OrderStatus === '0' ? '' : (searchParams?.OrderStatus || ""),
|
||||
SaleBillType: searchParams?.OrderType === '0' ? '' : (searchParams?.OrderType || ""),
|
||||
ChannelType: searchParams?.PaymentMethod || "",
|
||||
CompanyId: searchParams?.COMPANY_IDS || "",
|
||||
MerchantId: currentUser?.UserPattern === 4000 ? currentUser?.SupplierID : searchParams?.MERCHANTS_IDS || "",
|
||||
SaleBillState: searchParams?.orderStatus === '0' ? '' : (searchParams?.orderStatus || ""),
|
||||
SaleBillType: searchParams?.orderType === '0' ? '' : (searchParams?.orderType || ""),
|
||||
ChannelType: searchParams?.CHANNEL_TYPE === '0' ? '' : searchParams?.CHANNEL_TYPE || "",
|
||||
StartDate: searchParams?.ORDER_DATE_Start || "",
|
||||
EndDate: searchParams?.ORDER_DATE_End || "",
|
||||
// SearchKeyName: "SupplierName,CommodityName,OrderCode",
|
||||
|
||||
@ -14,7 +14,7 @@ import ProTable from "@ant-design/pro-table";
|
||||
import ReactHTMLTableToExcel from "react-html-table-to-excel";
|
||||
import LeftSelectTree from "@/pages/reports/settlementAccount/component/leftSelectTree";
|
||||
import PageTitleBox from "@/components/PageTitleBox";
|
||||
import { handeGetCOMPANYList, handeGetOnlineBillAccountList, handeGetSalebillAccountList, handeGetSupplierSaleBillList } from "../service";
|
||||
import { handeGetCOMPANYList, handeGetMERCHANTSList, handeGetOnlineBillAccountList, handeGetSalebillAccountList, handeGetSupplierSaleBillList } from "../service";
|
||||
import moment from 'moment'
|
||||
import OrderDetailModal from "../BookingMealOrder/components/orderDetailModal";
|
||||
import { exportXlsxFromProColumnsExcelJS, formatTreeData, handleSetlogSave } from "@/utils/format";
|
||||
@ -78,17 +78,6 @@ const TradingLedger: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
placeholder: "请输入供货商/购买的商品/订单编号/会员名称/电话号码"
|
||||
}
|
||||
},
|
||||
{
|
||||
dataIndex: "CompanyId",
|
||||
title: "所属单位",
|
||||
hideInTable: true,
|
||||
valueType: 'select',
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
options: companyList,
|
||||
filterOption: (input: any, option: any) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '下单时间',
|
||||
dataIndex: 'search_date',
|
||||
@ -114,6 +103,98 @@ const TradingLedger: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
// initialValue: [moment().subtract(1, 'M').format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')],
|
||||
initialValue: [moment().startOf('M'), moment()],
|
||||
},
|
||||
{
|
||||
title: "订单状态",
|
||||
dataIndex: "OrderStatus",
|
||||
valueType: "select",
|
||||
valueEnum: {
|
||||
"0": "全部",
|
||||
"1005": "订单待支付",
|
||||
"1010": "订单待发货",
|
||||
"2010": "订单已发货",
|
||||
"3000": "订单已完成",
|
||||
"8000": "退款申请中",
|
||||
"8900": "订单已退款",
|
||||
"9000": "订单已关闭",
|
||||
"9999": "订单已撤销"
|
||||
},
|
||||
initialValue: '0',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: "订单类型",
|
||||
dataIndex: "OrderType",
|
||||
valueType: "select",
|
||||
valueEnum: {
|
||||
"0": "全部",
|
||||
"3000": "零售商城",
|
||||
"3001": "工会商城",
|
||||
// "3002": "品诺商城",
|
||||
"3010": "积分商城"
|
||||
},
|
||||
initialValue: '0',
|
||||
hideInTable: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: "支付方式",
|
||||
dataIndex: "CHANNEL_TYPE",
|
||||
valueType: "select",
|
||||
valueEnum: {
|
||||
"0": "全部",
|
||||
"工会余额": "工会余额",
|
||||
"组合支付": "组合支付",
|
||||
"微信": "微信"
|
||||
},
|
||||
initialValue: '0',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: '供应商',
|
||||
dataIndex: "MERCHANTS_IDS",
|
||||
valueType: 'select',
|
||||
request: async () => {
|
||||
const req = {
|
||||
searchParameter: {
|
||||
OWNERUNIT_ID: currentUser?.OwnerUnitId,
|
||||
PROVINCE_CODE: currentUser?.ProvinceCode,
|
||||
MERCHANTS_TYPE: ""
|
||||
},
|
||||
PageIndex: 1,
|
||||
PageSize: 999999,
|
||||
}
|
||||
const data = await handeGetMERCHANTSList(req);
|
||||
return data.List
|
||||
},
|
||||
hideInTable: true,
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
showSearch: true,
|
||||
filterTreeNode: (input, node) => {
|
||||
// ✅ 输入时根据 AUTOTYPE_NAME 模糊匹配
|
||||
return node?.MERCHANTS_NAME?.toLowerCase()?.includes(input.toLowerCase());
|
||||
},
|
||||
treeDefaultExpandAll: true,
|
||||
fieldNames: {
|
||||
label: 'MERCHANTS_NAME',
|
||||
value: 'MERCHANTS_ID',
|
||||
},
|
||||
disabled: currentUser?.UserPattern === 4000
|
||||
},
|
||||
hideInSearch: currentUser?.UserPattern === 4000,
|
||||
// initialValue: currentUser?.UserPattern === 4000 ? currentUser?.SupplierID : ""
|
||||
},
|
||||
{
|
||||
dataIndex: "CompanyId",
|
||||
title: "所属单位",
|
||||
hideInTable: true,
|
||||
valueType: 'select',
|
||||
fieldProps: {
|
||||
showSearch: true,
|
||||
options: companyList,
|
||||
filterOption: (input: any, option: any) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "序号",
|
||||
dataIndex: "index",
|
||||
@ -226,6 +307,7 @@ const TradingLedger: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
// "3002": "品诺商城",
|
||||
"3010": "积分商城"
|
||||
},
|
||||
hideInSearch: true,
|
||||
initialValue: '0',
|
||||
},
|
||||
{
|
||||
@ -305,6 +387,7 @@ const TradingLedger: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
},
|
||||
align: "center",
|
||||
initialValue: '0',
|
||||
hideInSearch: true
|
||||
},
|
||||
// {
|
||||
// dataIndex: 'desc',
|
||||
@ -575,10 +658,10 @@ const TradingLedger: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
ExportType: type,
|
||||
OwnerUnitId: "911",
|
||||
CompanyId: searchParams?.CompanyId || "",
|
||||
MerchantId: currentUser?.SupplierID || "",
|
||||
SaleBillState: searchParams?.OrderStatus === '0' ? '' : (searchParams?.OrderStatus || ""),
|
||||
SaleBillType: searchParams?.OrderType === '0' ? '' : (searchParams?.OrderType || ""),
|
||||
ChannelType: searchParams?.PaymentMethod || "",
|
||||
MerchantId: currentUser?.UserPattern === 4000 ? currentUser?.SupplierID : searchParams?.MERCHANTS_IDS || "",
|
||||
SaleBillState: searchParams?.orderStatus === '0' ? '' : (searchParams?.orderStatus || ""),
|
||||
SaleBillType: searchParams?.orderType === '0' ? '' : (searchParams?.orderType || ""),
|
||||
ChannelType: searchParams?.CHANNEL_TYPE === '0' ? '' : searchParams?.CHANNEL_TYPE || "",
|
||||
StartDate: searchParams?.ORDER_DATE_Start || "",
|
||||
EndDate: searchParams?.ORDER_DATE_End || "",
|
||||
// SearchKeyName: "SupplierName,CommodityName,OrderCode",
|
||||
@ -695,16 +778,17 @@ const TradingLedger: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
// SearchParameter: {
|
||||
OwnerUnitId: "911",
|
||||
CompanyId: params?.CompanyId || "",
|
||||
MerchantId: "",
|
||||
MerchantId: currentUser?.UserPattern === 4000 ? currentUser?.SupplierID : params?.MERCHANTS_IDS || "",
|
||||
SaleBillState: params?.OrderStatus === '0' ? '' : (params?.OrderStatus || ""),
|
||||
SaleBillType: params?.OrderType === '0' ? '' : (params?.OrderType || ""),
|
||||
ChannelType: params?.PaymentMethod || "",
|
||||
ChannelType: params?.CHANNEL_TYPE === '0' ? '' : params?.CHANNEL_TYPE || "",
|
||||
StartDate: params?.ORDER_DATE_Start || "",
|
||||
EndDate: params?.ORDER_DATE_End || "",
|
||||
SearchKeyName: "MERCHANTS_NAME,COMMODITY_NAME,SALEBILL_CHILD_CODE,ORDER_PERSON,ORDER_PERSONTEL",
|
||||
SearchKeyValue: params?.searchText || "",
|
||||
SortStr: ""
|
||||
}
|
||||
console.log('reqreqreq222', req);
|
||||
const data = await handeGetSalebillAccountList(req)
|
||||
setSearchParams(params)
|
||||
setCurrentSearchText(params?.searchText || "")
|
||||
|
||||
@ -29,15 +29,33 @@ function flattenTree<T extends Record<string, any>>(
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 抽取 React 节点文本 */
|
||||
/** 抽取 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 (typeof node === 'string' || typeof node === 'number') {
|
||||
// Excel 内部单元格换行的唯一标准是 \n
|
||||
return String(node).replace(/<br\s*\/?>/gi, '\n').replace(/\r\n/g, '\n');
|
||||
}
|
||||
|
||||
// 支持 React 的 <br/> 节点
|
||||
if (node?.type === 'br') return '\n';
|
||||
|
||||
const props = node?.props;
|
||||
if (props) {
|
||||
// 渲染 dangerouslySetInnerHTML
|
||||
const html = props?.dangerouslySetInnerHTML?.__html;
|
||||
if (typeof html === 'string') {
|
||||
return html
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/ /g, ' ');
|
||||
}
|
||||
// 递归渲染 children
|
||||
const children = 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 ?? '');
|
||||
}
|
||||
|
||||
@ -143,7 +161,7 @@ function getCellValue(col: AnyCol, record: any, rowIndex: number) {
|
||||
/** 估算列宽(简单) */
|
||||
const estimateWidth = (v: any) => {
|
||||
const s = (v ?? '').toString();
|
||||
const len = Array.from(s).reduce((n, ch) => n + (/[^\x00-\xff]/.test(ch) ? 2 : 1), 0);
|
||||
const len = Array.from(s).reduce((n: number, ch: any) => n + (/[^\x00-\xff]/.test(ch) ? 2 : 1), 0);
|
||||
return Math.min(Math.max(len + 2, 8), 60);
|
||||
};
|
||||
|
||||
@ -196,14 +214,28 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
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;
|
||||
const titleContent = extractText(topTitle);
|
||||
|
||||
// 重要:先执行合并,再分发数据和样式
|
||||
ws.mergeCells(currentRowIndex, 1, currentRowIndex, columnCount || 1);
|
||||
|
||||
// 对合并后的 Master Cell 进行赋值和核心样式设置
|
||||
const masterCell = ws.getCell(currentRowIndex, 1);
|
||||
masterCell.value = titleContent;
|
||||
masterCell.font = { bold: true, size: 14, name: 'Microsoft YaHei' };
|
||||
|
||||
// 重要:由于合并单元格会共享样式,直接对 Row 应用对齐是最稳妥的
|
||||
row.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
|
||||
// 双重加固:对合并范围内的所有单元格显式设置 wrapText (解决某些版本只看 Master Cell 却失效的问题)
|
||||
for (let i = 1; i <= (columnCount || 1); i++) {
|
||||
row.getCell(i).alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
}
|
||||
|
||||
// 动态行高计算(关键:确保换行后的内容不被遮盖)
|
||||
const lines = titleContent.split('\n').length;
|
||||
row.height = Math.max(lines * 32, 40);
|
||||
|
||||
currentRowIndex += 1;
|
||||
}
|
||||
|
||||
@ -231,17 +263,23 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
currentRowIndex += 1;
|
||||
}
|
||||
|
||||
// 3) 多级表头(全部居中 + 加粗)
|
||||
// 3) 多级表头(全部居中 + 加粗 + 换行支持)
|
||||
const headerStartRow = currentRowIndex;
|
||||
headerAOA.forEach((r, idx) => {
|
||||
const row = ws.getRow(headerStartRow + idx);
|
||||
let maxRowLines = 1;
|
||||
r.forEach((v, cIdx) => {
|
||||
const text = extractText(v);
|
||||
const cell = row.getCell(cIdx + 1);
|
||||
cell.value = v;
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
cell.value = text;
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
cell.font = { bold: true };
|
||||
|
||||
const lines = text.split('\n').length;
|
||||
if (lines > maxRowLines) maxRowLines = lines;
|
||||
});
|
||||
row.height = 18;
|
||||
row.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
row.height = Math.max(22, maxRowLines * 20);
|
||||
});
|
||||
// 应用表头合并
|
||||
merges.forEach(m => {
|
||||
@ -257,24 +295,35 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const rec = batch[i];
|
||||
const row = ws.getRow(currentRowIndex + i);
|
||||
let maxRowLines = 1;
|
||||
leafCols.forEach((col, j) => {
|
||||
// 这里 rowIndex 仍然传全局行号 start + i,序号列会自动正确
|
||||
row.getCell(j + 1).value = getCellValue(col, rec, start + i);
|
||||
const val = getCellValue(col, rec, start + i);
|
||||
const text = extractText(val);
|
||||
const cell = row.getCell(j + 1);
|
||||
cell.value = text;
|
||||
cell.alignment = { vertical: 'middle', wrapText: true };
|
||||
|
||||
const lines = text.split('\n').length;
|
||||
if (lines > maxRowLines) maxRowLines = lines;
|
||||
});
|
||||
// 适度让出主线程:ExcelJS 是纯 JS,通常也很稳;如需进一步优化可用 setTimeout 分批
|
||||
row.height = Math.max(18, maxRowLines * 16);
|
||||
}
|
||||
currentRowIndex += batch.length;
|
||||
|
||||
// 5) 列宽:基于表头 + 采样数据估算
|
||||
// 5) 列宽与默认样式
|
||||
const sampleRows = Math.min(batch.length, 200);
|
||||
for (let c = 1; c <= columnCount; c++) {
|
||||
const wsCol = ws.getColumn(c);
|
||||
// 默认开启换行
|
||||
wsCol.alignment = { vertical: 'middle', wrapText: true };
|
||||
|
||||
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);
|
||||
wsCol.width = Math.max(headerMax, dataMax);
|
||||
}
|
||||
|
||||
// 6) 冻结窗格:冻结到(标题 + 信息行 + 表头)这一行的下一行
|
||||
@ -290,7 +339,9 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${filename}.xlsx`;
|
||||
// 清理文件名中的换行符,防止下载异常
|
||||
const safeFilename = (filename || '数据导出').replace(/[\r\n]/g, ' ');
|
||||
a.download = `${safeFilename}.xlsx`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
@ -923,15 +923,33 @@ function flattenTree<T extends Record<string, any>>(
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 抽取 React 节点文本 */
|
||||
/** 抽取 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 (typeof node === 'string' || typeof node === 'number') {
|
||||
// Excel 内部单元格换行的标准是 \n
|
||||
return String(node).replace(/<br\s*\/?>/gi, '\n').replace(/\r\n/g, '\n');
|
||||
}
|
||||
|
||||
// 支持 React 的 <br/> 节点
|
||||
if (node?.type === 'br') return '\n';
|
||||
|
||||
const props = node?.props;
|
||||
if (props) {
|
||||
// 渲染 dangerouslySetInnerHTML
|
||||
const html = props?.dangerouslySetInnerHTML?.__html;
|
||||
if (typeof html === 'string') {
|
||||
return html
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.replace(/ /g, ' ');
|
||||
}
|
||||
// 递归渲染 children
|
||||
const children = 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 ?? '');
|
||||
}
|
||||
/** 过滤 hideInTable(父级联动) */
|
||||
@ -1059,7 +1077,7 @@ function getCellValue(col: AnyCol, record: any, rowIndex: number) {
|
||||
/** 估算列宽(简单) */
|
||||
const estimateWidth = (v: any) => {
|
||||
const s = (v ?? '').toString();
|
||||
const len = Array.from(s).reduce((n, ch) => n + (/[^\x00-\xff]/.test(ch) ? 2 : 1), 0);
|
||||
const len = Array.from(s).reduce((n: number, ch: any) => n + (/[^\x00-\xff]/.test(ch) ? 2 : 1), 0);
|
||||
return Math.min(Math.max(len + 2, 8), 60);
|
||||
};
|
||||
|
||||
@ -1126,12 +1144,27 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
// 顶部标题
|
||||
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;
|
||||
const titleContent = extractText(topTitle);
|
||||
|
||||
// 重要:先执行合并,再分发数据和样式
|
||||
ws.mergeCells(currentRowIndex, 1, currentRowIndex, columnCount || 1);
|
||||
|
||||
// 对合并后的 Master Cell 进行赋值和核心样式设置
|
||||
const masterCell = ws.getCell(currentRowIndex, 1);
|
||||
masterCell.value = titleContent;
|
||||
// 字体正常,不加粗
|
||||
masterCell.font = { size: 14, name: 'Microsoft YaHei' };
|
||||
|
||||
// 强制对主单元格设置居中和换行
|
||||
masterCell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
|
||||
// 强制整行设置换行对齐属性作为辅助
|
||||
row.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
|
||||
// 计算行高 (基准 32 保证显示完整)
|
||||
const lines = titleContent.split('\n').length;
|
||||
row.height = Math.max(lines * 32, 40);
|
||||
|
||||
currentRowIndex += 1;
|
||||
}
|
||||
|
||||
@ -1157,17 +1190,23 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
currentRowIndex += 1;
|
||||
}
|
||||
|
||||
// 表头(保持原有样式与合并)
|
||||
// 表头(多级支持)
|
||||
const headerStartRow = currentRowIndex;
|
||||
headerAOA.forEach((r, idx) => {
|
||||
const row = ws.getRow(headerStartRow + idx);
|
||||
let maxRowLines = 1;
|
||||
r.forEach((v, cIdx) => {
|
||||
const text = extractText(v);
|
||||
const cell = row.getCell(cIdx + 1);
|
||||
cell.value = v;
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||
cell.value = text;
|
||||
cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
cell.font = { bold: true };
|
||||
|
||||
const lines = text.split('\n').length;
|
||||
if (lines > maxRowLines) maxRowLines = lines;
|
||||
});
|
||||
row.height = 18;
|
||||
row.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
|
||||
row.height = Math.max(22, maxRowLines * 18);
|
||||
});
|
||||
merges.forEach(m => {
|
||||
ws.mergeCells(headerStartRow + (m.r1 - 1), m.c1, headerStartRow + (m.r2 - 1), m.c2);
|
||||
@ -1182,25 +1221,38 @@ export async function exportXlsxFromProColumnsExcelJS(
|
||||
for (let i = 0; i < batch.length; i++) {
|
||||
const rec = batch[i];
|
||||
const row = ws.getRow(currentRowIndex + i);
|
||||
let maxRowLines = 1;
|
||||
leafCols.forEach((col, j) => {
|
||||
const cell = row.getCell(j + 1);
|
||||
const v = getCellValue(col, rec, start + i);
|
||||
cell.value = v;
|
||||
if (col.align) cell.alignment = { horizontal: col.align, vertical: 'middle' };
|
||||
const val = getCellValue(col, rec, start + i);
|
||||
const text = extractText(val);
|
||||
cell.value = text;
|
||||
|
||||
const alignment: any = { vertical: 'middle', wrapText: true };
|
||||
if (col.align) alignment.horizontal = col.align;
|
||||
cell.alignment = alignment;
|
||||
|
||||
const lines = text.split('\n').length;
|
||||
if (lines > maxRowLines) maxRowLines = lines;
|
||||
});
|
||||
row.height = Math.max(18, maxRowLines * 16);
|
||||
}
|
||||
currentRowIndex += batch.length;
|
||||
|
||||
// 列宽估算
|
||||
const sampleRows = Math.min(batch.length, 200);
|
||||
for (let c = 1; c <= columnCount; c++) {
|
||||
const wsCol = ws.getColumn(c);
|
||||
// 注意:不再此处设置 wsCol.alignment,防止覆盖标题和表头的水平居中样式
|
||||
// 对齐已在数据行渲染阶段(Step 4)按列配置精准应用
|
||||
|
||||
const headerMax = Math.max(...headerAOA.map(r => estimateWidth(r[c - 1])));
|
||||
let dataMax = 8;
|
||||
for (let i = 0; i < sampleRows; i++) {
|
||||
const sampleRowsLimit = Math.min(batch.length, 100);
|
||||
for (let i = 0; i < sampleRowsLimit; 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);
|
||||
wsCol.width = Math.max(headerMax, dataMax);
|
||||
}
|
||||
|
||||
// 注意:不再对 ws.views 进行任何设置(避免冻结表头)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// 由 scripts/writeVersion.js 自动生成
|
||||
export const VERSION = "4.5.128";
|
||||
export const GIT_HASH = "772dc23";
|
||||
export const BUILD_TIME = "2026-01-23T11:34:36.109Z";
|
||||
export const VERSION = "4.5.131";
|
||||
export const GIT_HASH = "83cb30f";
|
||||
export const BUILD_TIME = "2026-01-26T10:28:52.569Z";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user