feat(delivery-template): 重构发货模板创建页面并新增打印预览功能
- 将模块库、预览画布和配置面板拆分为独立组件 - 简化页面主组件结构,移除大量内联样式和冗余代码 - 新增 PrintPreview 组件用于模板打印预览 - 更新装箱规格表格数据结构,增加箱类字段 - 扩展车辆信息和合计金额模块的配置选项 - 优化导入语句,仅保留必要的第三方组件引用 - 移除 react-beautiful-dnd 拖拽相关依赖和实现 - 导出 DeliveryTemplate 组件供其他模块使用
This commit is contained in:
parent
4154eb75f5
commit
d17eb56a4d
@ -0,0 +1,354 @@
|
||||
import React from 'react';
|
||||
import { Card, Checkbox, Empty, Form, Input, Switch } from 'antd';
|
||||
|
||||
interface ConfigPanelProps {
|
||||
selectedModule: any;
|
||||
onConfigChange: (moduleId: string, config: any) => void;
|
||||
}
|
||||
|
||||
const ConfigPanel: React.FC<ConfigPanelProps> = ({
|
||||
selectedModule,
|
||||
onConfigChange,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedModule) {
|
||||
form.setFieldsValue(selectedModule.config);
|
||||
}
|
||||
}, [selectedModule, form]);
|
||||
|
||||
const handleValuesChange = (changedValues: any, allValues: any) => {
|
||||
if (selectedModule) {
|
||||
onConfigChange(selectedModule.id, {
|
||||
...selectedModule.config,
|
||||
...allValues,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderTitleConfig = () => (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">标题配置</div>
|
||||
<Form.Item name="text" label="标题文本">
|
||||
<Input placeholder="例如:西瓜发货清单" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderDealerInfoConfig = () => (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">经销商信息配置</div>
|
||||
<Form.Item
|
||||
name="showDealerName"
|
||||
valuePropName="checked"
|
||||
label="显示经销商名称"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dealerName"
|
||||
label="经销商名称"
|
||||
>
|
||||
<Input placeholder="请输入经销商名称" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showVehicleNumber"
|
||||
valuePropName="checked"
|
||||
label="显示车次信息"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showDestination"
|
||||
valuePropName="checked"
|
||||
label="显示收货地"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showWatermelonGrade"
|
||||
valuePropName="checked"
|
||||
label="填写西瓜品级"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderWeightInfoConfig = () => (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">重量信息配置</div>
|
||||
<Form.Item
|
||||
name="showGrossWeight"
|
||||
valuePropName="checked"
|
||||
label="显示毛重"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showBoxWeight" valuePropName="checked" label="显示箱重">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showNetWeight" valuePropName="checked" label="显示净重">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showUnitPrice" valuePropName="checked" label="显示单价">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showAmount" valuePropName="checked" label="显示金额">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showGrade" valuePropName="checked" label="填写品级">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showAccountCompany" valuePropName="checked" label="显示入账公司">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderPackingSpecConfig = () => (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">表格列配置</div>
|
||||
<Form.Item name="showBoxCategory" valuePropName="checked" label="显示品牌">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showBoxType" valuePropName="checked" label="显示箱号">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showQuantity" valuePropName="checked" label="显示数量">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showUnitPrice" valuePropName="checked" label="显示单价">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showAmount" valuePropName="checked" label="显示金额">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showUnitWeight" valuePropName="checked" label="显示单重">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showWeight" valuePropName="checked" label="显示重量">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderOtherFeesConfig = () => (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">费用项目配置</div>
|
||||
<Form.Item name="enabled" valuePropName="checked" label="启用费用模块">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="feeItems" label="显示费用项目">
|
||||
<Checkbox.Group className="flex flex-col gap-1">
|
||||
<div className="checkbox-item">
|
||||
<Checkbox value="trademark">商标</Checkbox>
|
||||
</div>
|
||||
<div className="checkbox-item">
|
||||
<Checkbox value="labor">人工</Checkbox>
|
||||
</div>
|
||||
<div className="checkbox-item">
|
||||
<Checkbox value="paperBox">纸箱</Checkbox>
|
||||
</div>
|
||||
<div className="checkbox-item">
|
||||
<Checkbox value="fee">费用</Checkbox>
|
||||
</div>
|
||||
<div className="checkbox-item">
|
||||
<Checkbox value="codingFee">打码费</Checkbox>
|
||||
</div>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="laborDetails"
|
||||
valuePropName="checked"
|
||||
label="人工费用细分"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderModuleConfig = () => {
|
||||
if (!selectedModule) return null;
|
||||
|
||||
const configs = {
|
||||
title: renderTitleConfig(),
|
||||
dealerInfo: renderDealerInfoConfig(),
|
||||
shippingInfo: (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">发货信息配置</div>
|
||||
<Form.Item
|
||||
name="showShippingFrom"
|
||||
valuePropName="checked"
|
||||
label="显示发货地"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showDate" valuePropName="checked" label="显示发货日期">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
weightInfo: renderWeightInfoConfig(),
|
||||
packingSpec: renderPackingSpecConfig(),
|
||||
vehicleInfo: (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">车辆信息配置</div>
|
||||
<Form.Item
|
||||
name="showDriverPhone"
|
||||
valuePropName="checked"
|
||||
label="显示司机号码"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showLicensePlate"
|
||||
valuePropName="checked"
|
||||
label="显示车牌"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showEstimatedArrivalTime"
|
||||
valuePropName="checked"
|
||||
label="填写预计到仓时间"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showFreightDebt"
|
||||
valuePropName="checked"
|
||||
label="显示运费欠"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showSeller"
|
||||
valuePropName="checked"
|
||||
label="填写卖货"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showStrawMatDebt"
|
||||
valuePropName="checked"
|
||||
label="显示草帘欠"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showRemarks"
|
||||
valuePropName="checked"
|
||||
label="填写备注"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="freightDebtTitle"
|
||||
label="运费欠标题"
|
||||
>
|
||||
<Input placeholder="请输入运费欠标题" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
otherFees: renderOtherFeesConfig(),
|
||||
totalAmount: (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">合计金额配置</div>
|
||||
<Form.Item
|
||||
name="enabled"
|
||||
valuePropName="checked"
|
||||
label="显示合计金额"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showFarmer"
|
||||
valuePropName="checked"
|
||||
label="信息瓜农"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="sumTitle"
|
||||
label="合计金额标题"
|
||||
>
|
||||
<Input placeholder="请输入合计金额标题" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
otherInfo: (
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="font-bold mb-2 text-blue-500">其他信息配置</div>
|
||||
<Form.Item
|
||||
name="enabled"
|
||||
valuePropName="checked"
|
||||
label="启用其他信息"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name="showOrigin" valuePropName="checked" label="显示产地">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showSupplier"
|
||||
valuePropName="checked"
|
||||
label="显示供应商"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showDepartureTime"
|
||||
valuePropName="checked"
|
||||
label="显示发车时间"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showArrivalTime"
|
||||
valuePropName="checked"
|
||||
label="显示预计到达"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="showProductName"
|
||||
valuePropName="checked"
|
||||
label="显示品名"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
} as any;
|
||||
|
||||
return configs[selectedModule.type] || <div>该模块暂无配置项</div>;
|
||||
};
|
||||
|
||||
if (!selectedModule) {
|
||||
return (
|
||||
<Card
|
||||
title="配置面板"
|
||||
size="small"
|
||||
className="bg-white rounded-lg p-4 h-full"
|
||||
>
|
||||
<Empty description="请选择要配置的模块" />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="模块配置"
|
||||
size="small"
|
||||
className="bg-white rounded-lg p-4 h-full"
|
||||
>
|
||||
<Form form={form} layout="horizontal" onValuesChange={handleValuesChange}>
|
||||
{renderModuleConfig()}
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigPanel;
|
||||
@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const DealerInfoModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>经销商信息模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 text-center flex flex-col gap-2.5">
|
||||
<div className="flex justify-around w-[70%] font-bold text-lg leading-8 border-b border-black py-0 px-2.5 my-0 mx-auto">
|
||||
{config.showDealerName && (
|
||||
<span className="px-2.5 min-w-[80px] text-center inline-block">{config.dealerName}</span>
|
||||
)}
|
||||
{config.showWatermelonGrade && (
|
||||
<span className="px-2.5 min-w-[80px] text-center inline-block">{config.watermelonGrade}</span>
|
||||
)}
|
||||
{config.showDestination && (
|
||||
<span className="px-2.5 min-w-[80px] text-center inline-block">{config.destination}</span>
|
||||
)}
|
||||
{config.showVehicleNumber && (
|
||||
<span className="px-2.5 min-w-[80px] text-center inline-block">{config.vehicleNumber}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealerInfoModule;
|
||||
@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { Card, List, Typography } from 'antd';
|
||||
import {
|
||||
FileTextOutlined,
|
||||
ShopOutlined,
|
||||
EnvironmentOutlined,
|
||||
InboxOutlined,
|
||||
TableOutlined,
|
||||
CarOutlined,
|
||||
DollarOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleLibraryItem {
|
||||
type: string;
|
||||
name: string;
|
||||
icon: React.ReactNode;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ModuleLibraryProps {
|
||||
onModuleAdd: (moduleType: string) => void;
|
||||
}
|
||||
|
||||
const moduleLibrary: ModuleLibraryItem[] = [
|
||||
{
|
||||
type: 'title',
|
||||
name: '发货单标题',
|
||||
icon: React.createElement(FileTextOutlined),
|
||||
description: '发货单主标题',
|
||||
},
|
||||
{
|
||||
type: 'dealerInfo',
|
||||
name: '经销商信息',
|
||||
icon: React.createElement(ShopOutlined),
|
||||
description: '经销商名称、车次等信息',
|
||||
},
|
||||
{
|
||||
type: 'shippingInfo',
|
||||
name: '发货地信息',
|
||||
icon: React.createElement(EnvironmentOutlined),
|
||||
description: '发货地、日期等信息',
|
||||
},
|
||||
{
|
||||
type: 'weightInfo',
|
||||
name: '重量金额信息',
|
||||
icon: React.createElement(InboxOutlined),
|
||||
description: '毛重、净重、单价、金额',
|
||||
},
|
||||
{
|
||||
type: 'packingSpec',
|
||||
name: '装箱规格表格',
|
||||
icon: React.createElement(TableOutlined),
|
||||
description: '箱号、数量、单价表格',
|
||||
},
|
||||
{
|
||||
type: 'vehicleInfo',
|
||||
name: '车辆信息',
|
||||
icon: React.createElement(CarOutlined),
|
||||
description: '司机、车牌、运费信息',
|
||||
},
|
||||
{
|
||||
type: 'otherFees',
|
||||
name: '其他费用明细',
|
||||
icon: React.createElement(DollarOutlined),
|
||||
description: '商标、人工、纸箱等费用',
|
||||
},
|
||||
{
|
||||
type: 'totalAmount',
|
||||
name: '合计金额',
|
||||
icon: React.createElement(DollarOutlined),
|
||||
description: '总金额显示',
|
||||
},
|
||||
{
|
||||
type: 'otherInfo',
|
||||
name: '其他信息',
|
||||
icon: React.createElement(InfoCircleOutlined),
|
||||
description: '产地、供应商等信息',
|
||||
},
|
||||
];
|
||||
|
||||
const ModuleLibrary: React.FC<ModuleLibraryProps> = ({ onModuleAdd }) => {
|
||||
return (
|
||||
<Card title="模块库" size="small" className="bg-white rounded-lg p-4 h-full">
|
||||
<List
|
||||
dataSource={moduleLibrary}
|
||||
renderItem={(module) => (
|
||||
<div
|
||||
className="flex items-center p-2 cursor-pointer border border-gray-300 rounded hover:border-blue-500 hover:bg-blue-50 transition-all duration-300 mb-2"
|
||||
onClick={() => onModuleAdd(module.type)}
|
||||
>
|
||||
<div className="text-blue-500 text-lg mr-2">{module.icon}</div>
|
||||
<div>
|
||||
<div>
|
||||
<Text strong>{module.name}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary" style={{ fontSize: '12px' }}>
|
||||
{module.description}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModuleLibrary;
|
||||
@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const OtherFeesModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
const feeLabels = {
|
||||
trademark: '商标',
|
||||
labor: '人工',
|
||||
paperBox: '纸箱',
|
||||
fee: '费用',
|
||||
codingFee: '打码费',
|
||||
} as any;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>其他费用明细模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 text-center flex flex-col gap-2.5">
|
||||
<div
|
||||
className="grid grid-cols-4 gap-2.5 text-base leading-4 my-0 mx-auto"
|
||||
>
|
||||
{config.feeItems.map((feeType: any) => (
|
||||
<div key={feeType} className="flex items-center mb-2">
|
||||
<span className="min-w-[60px] text-center py-0 px-1.5">{feeLabels[feeType]}:</span>
|
||||
<div className="flex min-w-[90px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config[feeType]}</span>
|
||||
<span className="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OtherFeesModule;
|
||||
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const OtherInfoModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>其他信息模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 flex flex-col gap-2.5">
|
||||
<table className="w-1/2 border-collapse text-sm leading-4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">车次:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">第175车</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">收货地:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">北京果多美</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">产地:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">内蒙</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">供应商:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">北京新发龙盛商贸有限公司</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">发车时间:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">9月8号</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">到达时间:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">9月9号</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">品名:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">A级-麒麟瓜</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">发货重量:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">以公司入库重量为准。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">净瓜单价:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">3元/斤</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">大约重量:</td>
|
||||
<td className="p-2 text-left border border-black">29062</td>
|
||||
<td className="p-2 text-left border border-black">斤</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">箱数</td>
|
||||
<td className="p-2 text-left border border-black">688</td>
|
||||
<td className="p-2 text-left border border-black">件</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">车号:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">蒙L80367</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="font-bold p-2 text-left border border-black w-1/3">手机号:</td>
|
||||
<td colSpan={2} className="p-2 text-left border border-black">15849849656</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OtherInfoModule;
|
||||
@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const PackingSpecModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
const columns = [];
|
||||
if (config.showBoxCategory) columns.push('');
|
||||
if (config.showBoxType) columns.push('箱号');
|
||||
if (config.showQuantity) columns.push('数量');
|
||||
if (config.showUnitPrice) columns.push('单价');
|
||||
if (config.showAmount) columns.push('金额');
|
||||
if (config.showUnitWeight) columns.push('单重');
|
||||
if (config.showWeight) columns.push('重量');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>装箱规格表格模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 flex flex-col gap-2.5">
|
||||
<table className="w-full border-collapse text-lg leading-4">
|
||||
<thead>
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th key={col} className="font-bold p-2 text-center">{col}</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{config.data.map((row: any, index: any) => (
|
||||
<tr key={index}>
|
||||
{config.showBoxCategory && <td className="p-2 text-center border border-black">{row.boxCategory}</td>}
|
||||
{config.showBoxType && <td className="p-2 text-center border border-black">{row.boxType}</td>}
|
||||
{config.showQuantity && <td className="p-2 text-center border border-black">{row.quantity}</td>}
|
||||
{config.showUnitPrice && <td className="p-2 text-center border border-black">{row.unitPrice}</td>}
|
||||
{config.showAmount && <td className="p-2 text-center border border-black">{row.amount}</td>}
|
||||
{config.showUnitWeight && <td className="p-2 text-center border border-black">{row.unitWeight}</td>}
|
||||
{config.showWeight && <td className="p-2 text-center border border-black">{row.weight}</td>}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PackingSpecModule;
|
||||
@ -0,0 +1,164 @@
|
||||
import React, { Suspense } from 'react';
|
||||
import { Card, Empty } from 'antd';
|
||||
import {
|
||||
DragDropContext,
|
||||
Droppable,
|
||||
Draggable,
|
||||
DropResult,
|
||||
} from 'react-beautiful-dnd';
|
||||
|
||||
// 直接导入组件而不是使用 React.lazy
|
||||
import TitleModule from './TitleModule';
|
||||
import DealerInfoModule from './DealerInfoModule';
|
||||
import ShippingInfoModule from './ShippingInfoModule';
|
||||
import WeightInfoModule from './WeightInfoModule';
|
||||
import PackingSpecModule from './PackingSpecModule';
|
||||
import VehicleInfoModule from './VehicleInfoModule';
|
||||
import OtherFeesModule from './OtherFeesModule';
|
||||
import TotalAmountModule from './TotalAmountModule';
|
||||
import OtherInfoModule from './OtherInfoModule';
|
||||
|
||||
interface PreviewCanvasProps {
|
||||
modules: any[];
|
||||
selectedModule: any;
|
||||
onModuleSelect: (module: any) => void;
|
||||
onModuleDelete: (moduleId: string) => void;
|
||||
onModuleReorder: (newModules: any[]) => void;
|
||||
}
|
||||
|
||||
const PreviewCanvas: React.FC<PreviewCanvasProps> = ({
|
||||
modules,
|
||||
selectedModule,
|
||||
onModuleSelect,
|
||||
onModuleDelete,
|
||||
onModuleReorder,
|
||||
}) => {
|
||||
const handleDragEnd = (result: DropResult) => {
|
||||
// 如果没有拖拽到有效位置,则直接返回
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = Array.from(modules);
|
||||
const [reorderedItem] = items.splice(result.source.index, 1);
|
||||
items.splice(result.destination.index, 0, reorderedItem);
|
||||
|
||||
onModuleReorder(items);
|
||||
};
|
||||
|
||||
const renderModule = (module: any, index: number) => {
|
||||
const props = {
|
||||
config: module.config,
|
||||
isSelected: selectedModule?.id === module.id,
|
||||
onSelect: () => onModuleSelect(module),
|
||||
onDelete: () => onModuleDelete(module.id),
|
||||
onMoveUp: () => {
|
||||
if (index > 0) {
|
||||
const newModules = [...modules];
|
||||
const temp = newModules[index];
|
||||
newModules[index] = newModules[index - 1];
|
||||
newModules[index - 1] = temp;
|
||||
onModuleReorder(newModules);
|
||||
}
|
||||
},
|
||||
onMoveDown: () => {
|
||||
if (index < modules.length - 1) {
|
||||
const newModules = [...modules];
|
||||
const temp = newModules[index];
|
||||
newModules[index] = newModules[index + 1];
|
||||
newModules[index + 1] = temp;
|
||||
onModuleReorder(newModules);
|
||||
}
|
||||
},
|
||||
canMoveUp: index > 0,
|
||||
canMoveDown: index < modules.length - 1,
|
||||
};
|
||||
|
||||
// 创建组件映射对象
|
||||
const componentMap = {
|
||||
title: TitleModule,
|
||||
dealerInfo: DealerInfoModule,
|
||||
shippingInfo: ShippingInfoModule,
|
||||
weightInfo: WeightInfoModule,
|
||||
packingSpec: PackingSpecModule,
|
||||
vehicleInfo: VehicleInfoModule,
|
||||
otherFees: OtherFeesModule,
|
||||
totalAmount: TotalAmountModule,
|
||||
otherInfo: OtherInfoModule,
|
||||
};
|
||||
|
||||
const Component = componentMap[module.type as keyof typeof componentMap];
|
||||
|
||||
return (
|
||||
<Draggable
|
||||
key={module.id}
|
||||
draggableId={module.id}
|
||||
index={index}
|
||||
>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={{
|
||||
...provided.draggableProps.style,
|
||||
marginBottom: '8px',
|
||||
borderRadius: '4px',
|
||||
border: snapshot.isDragging ? '1px dashed #1890ff' : 'none',
|
||||
backgroundColor: snapshot.isDragging ? '#e6f7ff' : 'transparent',
|
||||
}}
|
||||
>
|
||||
{Component ? <Component {...props} /> : null}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
// 为 Droppable 组件创建一个不使用 memo 的包装组件
|
||||
const DroppableWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<Droppable droppableId="modules">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
style={{
|
||||
minHeight: '100px',
|
||||
padding: '8px',
|
||||
backgroundColor: snapshot.isDraggingOver
|
||||
? '#f0f8ff'
|
||||
: 'transparent',
|
||||
borderRadius: '4px',
|
||||
transition: 'background-color 0.2s ease',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="模板预览"
|
||||
size="small"
|
||||
className="bg-white rounded-lg p-4 min-h-[600px] border border-gray-300"
|
||||
bodyStyle={{ maxHeight: 'calc(100vh - 200px)', overflowY: 'auto' }}
|
||||
>
|
||||
{modules.length === 0 ? (
|
||||
<div className="empty-preview">
|
||||
<Empty description="从左侧模块库点击添加模块" />
|
||||
</div>
|
||||
) : (
|
||||
<DragDropContext onDragEnd={handleDragEnd}>
|
||||
<DroppableWrapper>
|
||||
{modules.map((module, index) => renderModule(module, index))}
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewCanvas;
|
||||
@ -0,0 +1,539 @@
|
||||
import React from 'react';
|
||||
import { Button, message } from 'antd';
|
||||
import { PrinterOutlined } from '@ant-design/icons';
|
||||
|
||||
interface PrintPreviewProps {
|
||||
modules: any[];
|
||||
}
|
||||
|
||||
const PrintPreview: React.FC<PrintPreviewProps> = ({ modules }) => {
|
||||
const handlePrint = () => {
|
||||
// 创建一个用于打印的隐藏iframe
|
||||
const printWindow = window.open('', '_blank');
|
||||
if (printWindow) {
|
||||
// 构建打印内容
|
||||
let printContent = `
|
||||
<html>
|
||||
<head>
|
||||
<title>发货单打印预览</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
width: 794px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
}
|
||||
body {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
.print-controls {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.print-module {
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.print-controls {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.print-button, .close-button {
|
||||
padding: 8px 16px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.print-button {
|
||||
background-color: #1890ff;
|
||||
color: white;
|
||||
}
|
||||
.close-button {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
#print-content {
|
||||
min-height: 1123px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="print-controls">
|
||||
<button class="print-button" onclick="window.print()">打印</button>
|
||||
<button class="close-button" onclick="window.close()">关闭</button>
|
||||
</div>
|
||||
<div id="print-content">
|
||||
`;
|
||||
|
||||
// 添加模块内容到打印内容中
|
||||
modules.forEach((module) => {
|
||||
printContent += `
|
||||
<div class="print-module">
|
||||
`;
|
||||
|
||||
switch (module.type) {
|
||||
case 'title':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="font-bold leading-8 text-center text-2xl">
|
||||
${module.config.text}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
case 'dealerInfo':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="flex justify-around w-[70%] font-bold text-lg leading-8 border-b border-black py-0 px-2.5 my-0 mx-auto">
|
||||
`;
|
||||
if (module.config.showDealerName) {
|
||||
printContent += `
|
||||
<span class="px-2.5 min-w-[80px] text-center inline-block">${module.config.dealerName}</span>
|
||||
`;
|
||||
}
|
||||
if (module.config.showWatermelonGrade) {
|
||||
printContent += `
|
||||
<span class="px-2.5 min-w-[80px] text-center inline-block">${module.config.watermelonGrade}</span>
|
||||
`;
|
||||
}
|
||||
if (module.config.showDestination) {
|
||||
printContent += `
|
||||
<span class="px-2.5 min-w-[80px] text-center inline-block">${module.config.destination}</span>
|
||||
`;
|
||||
}
|
||||
if (module.config.showVehicleNumber) {
|
||||
printContent += `
|
||||
<span class="px-2.5 min-w-[80px] text-center inline-block">${module.config.vehicleNumber}</span>
|
||||
`;
|
||||
}
|
||||
printContent += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
case 'shippingInfo':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
`;
|
||||
if (module.config.showShippingFrom) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">发货地:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.shippingFrom}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showDate) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">日期:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
printContent += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
case 'weightInfo':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
`;
|
||||
if (module.config.showGrossWeight) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">毛重:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.grossWeight}</span>
|
||||
<span class="inline-block">斤</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showBoxWeight) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">箱重:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.boxWeight}</span>
|
||||
<span class="inline-block">斤</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showNetWeight) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">净重:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.netWeight}</span>
|
||||
<span class="inline-block">斤</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showUnitPrice) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">单价:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.unitPrice}</span>
|
||||
<span class="inline-block">元/斤</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showAmount) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">金额:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.amount}</span>
|
||||
<span class="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showGrade) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">品级:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.grade}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showAccountCompany) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">入账公司:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.accountCompany}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
printContent += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
case 'packingSpec': {
|
||||
const columns = [];
|
||||
if (module.config.showBoxCategory) columns.push('');
|
||||
if (module.config.showBoxType) columns.push('箱号');
|
||||
if (module.config.showQuantity) columns.push('数量');
|
||||
if (module.config.showUnitPrice) columns.push('单价');
|
||||
if (module.config.showAmount) columns.push('金额');
|
||||
if (module.config.showUnitWeight) columns.push('单重');
|
||||
if (module.config.showWeight) columns.push('重量');
|
||||
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<table class="w-full border-collapse text-lg leading-4">
|
||||
<thead>
|
||||
<tr>
|
||||
${columns.map((col) => `<td class="p-2 text-center">${col}</td>`).join('')}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="border-2">
|
||||
`;
|
||||
|
||||
module.config.data.forEach((row: any) => {
|
||||
printContent += `<tr>`;
|
||||
if (module.config.showBoxCategory)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.boxCategory}</td>`;
|
||||
if (module.config.showBoxType)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.boxType}</td>`;
|
||||
if (module.config.showQuantity)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.quantity}</td>`;
|
||||
if (module.config.showUnitPrice)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.unitPrice}</td>`;
|
||||
if (module.config.showAmount)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.amount}</td>`;
|
||||
if (module.config.showUnitWeight)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.unitWeight}</td>`;
|
||||
if (module.config.showWeight)
|
||||
printContent += `<td class="p-2 text-center border border-black">${row.weight}</td>`;
|
||||
printContent += `</tr>`;
|
||||
});
|
||||
|
||||
printContent += `
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
case 'vehicleInfo':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="grid grid-cols-1 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
`;
|
||||
if (module.config.showDriverPhone) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">司机号码:</span>
|
||||
<div class="flex min-w-[480px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.driverPhone}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showLicensePlate) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">车牌:</span>
|
||||
<div class="flex min-w-[480px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.licensePlate}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showEstimatedArrivalTime) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">预计到仓时间:</span>
|
||||
<div class="flex min-w-[480px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.estimatedArrivalTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
printContent += `
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
`;
|
||||
if (module.config.showFreightDebt) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">${module.config.freightDebtTitle || '运费欠'}:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.freightDebt}</span>
|
||||
<span class="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showRemarks) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">备注:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.remarks}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showSeller) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">卖货:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.seller}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (module.config.showStrawMatDebt) {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">草帘欠:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.strawMatDebt}</span>
|
||||
<span class="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
printContent += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
case 'otherFees': {
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="grid grid-cols-4 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
`;
|
||||
const feeLabels = {
|
||||
trademark: '商标',
|
||||
labor: '人工',
|
||||
paperBox: '纸箱',
|
||||
fee: '费用',
|
||||
codingFee: '打码费',
|
||||
};
|
||||
module.config.feeItems.forEach((feeType: string) => {
|
||||
printContent += `
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[60px] text-center py-0 px-1.5">${feeLabels[feeType as keyof typeof feeLabels]}:</span>
|
||||
<div class="flex min-w-[90px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config[feeType]}</span>
|
||||
<span class="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
printContent += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
case 'totalAmount':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<div class="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">${module.config.sumTitle}:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.amount}</span>
|
||||
<span class="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
${module.config.showFarmer ? `<div class="flex items-center mb-2">
|
||||
<span class="min-w-[120px] text-center py-0 px-1.5">瓜农:</span>
|
||||
<div class="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span class="text-center inline-block">${module.config.farmer}</span>
|
||||
</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
case 'otherInfo':
|
||||
printContent += `
|
||||
<div class="py-2 text-center flex flex-col gap-2.5">
|
||||
<table class="w-1/2 border-collapse text-sm leading-4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">车次:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">第175车</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">收货地:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">北京果多美</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">产地:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">内蒙</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">供应商:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">北京新发龙盛商贸有限公司</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">发车时间:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">9月8号</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">到达时间:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">9月9号</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">品名:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">A级-麒麟瓜</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">发货重量:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">以公司入库重量为准。</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">净瓜单价:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">3元/斤</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">大约重量:</td>
|
||||
<td class="p-2 text-left border border-black">29062</td>
|
||||
<td class="p-2 text-left border border-black">斤</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">箱数</td>
|
||||
<td class="p-2 text-left border border-black">688</td>
|
||||
<td class="p-2 text-left border border-black">件</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">车号:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">蒙L80367</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-bold p-2 text-left border border-black w-1/3">手机号:</td>
|
||||
<td colSpan="2" class="p-2 text-left border border-black">15849849656</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
default:
|
||||
printContent += `
|
||||
<div>未支持的模块类型: ${module.type}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
printContent += `
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
printContent += `
|
||||
</div>
|
||||
<script>
|
||||
// 打印前隐藏按钮
|
||||
window.onbeforeprint = function() {
|
||||
var controls = document.querySelector('.print-controls');
|
||||
if (controls) {
|
||||
controls.style.visibility = 'hidden';
|
||||
}
|
||||
};
|
||||
|
||||
// 打印后显示按钮
|
||||
window.onafterprint = function() {
|
||||
var controls = document.querySelector('.print-controls');
|
||||
if (controls) {
|
||||
controls.style.visibility = 'visible';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// 写入内容到打印窗口
|
||||
printWindow.document.write(printContent);
|
||||
printWindow.document.close();
|
||||
} else {
|
||||
message.error('无法打开打印预览窗口,请检查浏览器设置');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button icon={<PrinterOutlined />} onClick={handlePrint}>
|
||||
预览打印
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintPreview;
|
||||
@ -0,0 +1,105 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const ShippingInfoModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>发货地信息模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 text-center flex flex-col gap-2.5">
|
||||
<div className="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
{config.showShippingFrom && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">发货地:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.shippingFrom}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showDate && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">日期:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShippingInfoModule;
|
||||
@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const TitleModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>标题模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2">
|
||||
<div className={`font-bold leading-8 text-center text-2xl`}>
|
||||
{config.text}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TitleModule;
|
||||
@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const TotalAmountModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>合计金额模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 text-center flex flex-col gap-2.5">
|
||||
<div className="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">{config.sumTitle}:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.amount}</span>
|
||||
<span className="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
{config.showFarmer && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">瓜农:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.farmer}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TotalAmountModule;
|
||||
@ -0,0 +1,179 @@
|
||||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
DeleteOutlined,
|
||||
MenuOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const VehicleInfoModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected
|
||||
? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]'
|
||||
: 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>车辆信息模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 text-center flex flex-col gap-2.5">
|
||||
<div className="grid grid-cols-1 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
{config.showDriverPhone && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
司机号码:
|
||||
</span>
|
||||
<div className="flex min-w-[480px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.driverPhone}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showLicensePlate && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
车牌:
|
||||
</span>
|
||||
<div className="flex min-w-[480px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.licensePlate}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showEstimatedArrivalTime && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
预计到仓时间:
|
||||
</span>
|
||||
<div className="flex min-w-[480px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.estimatedArrivalTime}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
{config.showFreightDebt && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
{config.freightDebtTitle || '运费欠'}:
|
||||
</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.freightDebt}
|
||||
</span>
|
||||
<span className="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showRemarks && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
备注:
|
||||
</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.remarks}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showSeller && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
卖货:
|
||||
</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.seller}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showStrawMatDebt && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">
|
||||
草帘欠:
|
||||
</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">
|
||||
{config.strawMatDebt}
|
||||
</span>
|
||||
<span className="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VehicleInfoModule;
|
||||
@ -0,0 +1,150 @@
|
||||
import React from 'react';
|
||||
import { Button, Typography } from 'antd';
|
||||
import {
|
||||
MenuOutlined,
|
||||
ArrowUpOutlined,
|
||||
ArrowDownOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ModuleProps {
|
||||
config: any;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onDelete: () => void;
|
||||
onMoveUp?: () => void;
|
||||
onMoveDown?: () => void;
|
||||
canMoveUp?: boolean;
|
||||
canMoveDown?: boolean;
|
||||
}
|
||||
|
||||
const WeightInfoModule: React.FC<ModuleProps> = ({
|
||||
config,
|
||||
isSelected,
|
||||
onSelect,
|
||||
onDelete,
|
||||
onMoveUp,
|
||||
onMoveDown,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`border border-gray-300 rounded p-2 mb-2 bg-white transition-all duration-200 ${
|
||||
isSelected ? 'border-2 border-blue-500 shadow-[0_0_0_2px_rgba(24,144,255,0.2)]' : 'hover:border-blue-300'
|
||||
}`}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<div className="flex justify-between items-center mb-2 pb-2 border-b border-gray-100">
|
||||
<Text strong>重量金额信息模块</Text>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<MenuOutlined />}
|
||||
size="small"
|
||||
style={{ cursor: 'move' }}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowUpOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveUp) onMoveUp();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveUp}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (onMoveDown) onMoveDown();
|
||||
}}
|
||||
size="small"
|
||||
disabled={!canMoveDown}
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete();
|
||||
}}
|
||||
danger
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="py-2 text-center flex flex-col gap-2.5">
|
||||
<div className="grid grid-cols-2 gap-2.5 text-base leading-4 my-0 mx-auto">
|
||||
{config.showGrossWeight && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">毛重:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.grossWeight}</span>
|
||||
<span className="inline-block">斤</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showBoxWeight && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">箱重:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.boxWeight}</span>
|
||||
<span className="inline-block">斤</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showNetWeight && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">净重:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.netWeight}</span>
|
||||
<span className="inline-block">斤</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showUnitPrice && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">单价:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.unitPrice}</span>
|
||||
<span className="inline-block">元/斤</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showAmount && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">金额:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.amount}</span>
|
||||
<span className="inline-block">元</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showGrade && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">品级:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.grade}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{config.showAccountCompany && (
|
||||
<div className="flex items-center mb-2">
|
||||
<span className="min-w-[120px] text-center py-0 px-1.5">入账公司:</span>
|
||||
<div className="flex min-w-[180px] py-0 px-2.5 items-center justify-around border-b border-black">
|
||||
<span className="text-center inline-block">{config.accountCompany}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WeightInfoModule;
|
||||
@ -0,0 +1,15 @@
|
||||
export { default as ModuleLibrary } from './ModuleLibrary';
|
||||
export { default as PreviewCanvas } from './PreviewCanvas';
|
||||
export { default as ConfigPanel } from './ConfigPanel';
|
||||
export { default as PrintPreview } from './PrintPreview';
|
||||
|
||||
// Individual module components
|
||||
export { default as TitleModule } from './TitleModule';
|
||||
export { default as DealerInfoModule } from './DealerInfoModule';
|
||||
export { default as ShippingInfoModule } from './ShippingInfoModule';
|
||||
export { default as WeightInfoModule } from './WeightInfoModule';
|
||||
export { default as PackingSpecModule } from './PackingSpecModule';
|
||||
export { default as VehicleInfoModule } from './VehicleInfoModule';
|
||||
export { default as OtherFeesModule } from './OtherFeesModule';
|
||||
export { default as TotalAmountModule } from './TotalAmountModule';
|
||||
export { default as OtherInfoModule } from './OtherInfoModule';
|
||||
@ -18,3 +18,4 @@ export * from './Role';
|
||||
export * from './Setting';
|
||||
export * from './User';
|
||||
export * from './Purchase';
|
||||
export * from './DeliveryTemplate';
|
||||
|
||||
@ -47,170 +47,3 @@ body::-webkit-scrollbar {
|
||||
padding: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 发货单模板配置
|
||||
.module-library {
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.module-item {
|
||||
padding: 12px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.module-item:hover {
|
||||
border-color: #1890ff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
.module-icon {
|
||||
margin-right: 12px;
|
||||
font-size: 16px;
|
||||
color: #1890ff;
|
||||
}
|
||||
.preview-canvas {
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
min-height: 600px;
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
.config-panel {
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
.preview-module {
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.preview-module.selected {
|
||||
border: 2px solid #1890ff;
|
||||
}
|
||||
.module-header {
|
||||
background: #fafafa;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.module-content {
|
||||
padding: 16px;
|
||||
}
|
||||
.empty-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 400px;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
.template-preview {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.template-preview th, .template-preview td {
|
||||
border: 1px solid #d9d9d9;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.template-preview th {
|
||||
background-color: #fafafa;
|
||||
font-weight: 500;
|
||||
}
|
||||
.ant-divider {
|
||||
margin: 16px 0;
|
||||
}
|
||||
.config-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.config-section-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
color: #262626;
|
||||
}
|
||||
.field-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.field-label {
|
||||
width: 100px;
|
||||
color: #595959;
|
||||
}
|
||||
.field-control {
|
||||
flex: 1;
|
||||
}
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.template-title {
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.template-title.large {
|
||||
font-size: 20px;
|
||||
}
|
||||
.template-title.medium {
|
||||
font-size: 18px;
|
||||
}
|
||||
.template-title.small {
|
||||
font-size: 16px;
|
||||
}
|
||||
.template-title.left {
|
||||
text-align: left;
|
||||
}
|
||||
.template-title.center {
|
||||
text-align: center;
|
||||
}
|
||||
.template-title.right {
|
||||
text-align: right;
|
||||
}
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.info-label {
|
||||
width: 80px;
|
||||
color: #595959;
|
||||
}
|
||||
.info-value {
|
||||
flex: 1;
|
||||
}
|
||||
.weight-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
}
|
||||
.weight-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.fee-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.fee-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user