feat(components): 添加发票批量上传功能并优化列表组件

- 添加 InvoiceBatchUpload 组件支持发票批量上传
- 修改 PageList 组件类型定义支持泛型
- 在 PageList 组件中添加 toolbar footer 支持
- 优化发票页面 UI 布局和交互体验
- 更新应用版本号到 v0.0.53
- 重构发票上传页面实现批量选择功能
This commit is contained in:
shenyifei 2025-12-23 17:24:37 +08:00
parent ebd955de97
commit ff39dc01d2
10 changed files with 960 additions and 853 deletions

View File

@ -48,7 +48,7 @@ export default <T extends {}, Q extends Query = Query>(
); );
const [lightTheme, setLightTheme] = useState(false); const [lightTheme, setLightTheme] = useState(false);
const [data, setData] = useState<Record<any>[]>(); const [data, setData] = useState<T[]>([]);
const [query, setQuery] = useState<Query>(); const [query, setQuery] = useState<Query>();
const [selectRows, setSelectRows] = useState<any[]>([]); const [selectRows, setSelectRows] = useState<any[]>([]);
@ -345,7 +345,7 @@ export default <T extends {}, Q extends Query = Query>(
> >
{toolbar?.actions && toolbar?.actions.map((item) => item)} {toolbar?.actions && toolbar?.actions.map((item) => item)}
{toolbar?.selectRow?.onClick && ( {toolbar?.selectRow?.onClick && !toolbar?.footer && (
<View key={"confirm"} className={"flex justify-between gap-2 p-2.5"}> <View key={"confirm"} className={"flex justify-between gap-2 p-2.5"}>
<View className={"flex flex-1 items-center justify-between gap-1"}> <View className={"flex flex-1 items-center justify-between gap-1"}>
<View className={"flex items-center gap-2"}> <View className={"flex items-center gap-2"}>
@ -393,6 +393,7 @@ export default <T extends {}, Q extends Query = Query>(
</View> </View>
)} )}
{toolbar?.footer && toolbar?.footer?.(data || [])}
{tabbar ? tabbar : <SafeArea position="bottom" />} {tabbar ? tabbar : <SafeArea position="bottom" />}
<Picker <Picker

View File

@ -21,7 +21,7 @@ export type TabPane = {
title: string | number; title: string | number;
}; };
export type ToolBar = { export type ToolBar<T> = {
selectRow?: { selectRow?: {
onClick: (selectRow: any[]) => void; onClick: (selectRow: any[]) => void;
}; };
@ -38,6 +38,7 @@ export type ToolBar = {
items: any[]; items: any[];
}; };
render?: () => React.ReactNode | undefined; render?: () => React.ReactNode | undefined;
footer?: (record: T[]) => React.ReactNode | undefined;
}; };
export type IRecordListProps<T = {}, Q extends Query = Query> = { export type IRecordListProps<T = {}, Q extends Query = Query> = {
@ -47,7 +48,7 @@ export type IRecordListProps<T = {}, Q extends Query = Query> = {
type: "virtual" | "infinite"; type: "virtual" | "infinite";
skeleton?: React.ReactNode; skeleton?: React.ReactNode;
actionRef: React.MutableRefObject<any>; actionRef: React.MutableRefObject<any>;
toolbar?: ToolBar; toolbar?: ToolBar<T>;
render: (record: T, index: number) => React.ReactNode; render: (record: T, index: number) => React.ReactNode;
request: (params: Q) => Promise<Record<T>>; request: (params: Q) => Promise<Record<T>>;
pagination: { pagination: {

View File

@ -9,3 +9,4 @@ export * from "./delivery";
export * from "./cost"; export * from "./cost";
export * from "./expenses"; export * from "./expenses";
export * from "./audit"; export * from "./audit";
export * from "./invoce";

View File

@ -0,0 +1,298 @@
import { Image, Text, View } from "@tarojs/components";
import { Button, Popup, SafeArea, Toast } from "@nutui/nutui-react-taro";
import Taro from "@tarojs/taro";
import { uploadFile } from "@/utils";
import { Icon } from "@/components";
import { useState } from "react";
import { business } from "@/services";
import { globalStore } from "@/store/global-store";
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
interface IInvoiceBatchUploadProps {
visible: boolean;
setVisible: (visible: boolean) => void;
onFinish: () => void;
selectedOrders: BusinessAPI.OrderSupplierVO[];
}
export default function InvoiceBatchUpload(props: IInvoiceBatchUploadProps) {
const { visible, setVisible, onFinish, selectedOrders } = props;
const { setLoading } = globalStore((state: any) => state);
const [invoiceImg, setInvoiceImg] = useState<
BusinessAPI.OrderSupplier["invoiceImg"]
>([]);
// 提交申请
const handleSubmit = async () => {
// 这里添加提交申请的逻辑
const {
data: { success },
} = await business.orderSupplier.batchUploadInvoice({
orderSupplierIdList: selectedOrders.map((order) => order.orderSupplierId),
invoiceImg: invoiceImg,
invoiceUpload: true,
});
if (success) {
Toast.show("toast", {
icon: "success",
title: "提交成功",
content: "发票已批量上传",
});
onFinish?.();
setVisible(false);
setInvoiceImg([]);
}
};
// 计算选中车次的总重量和总金额
const calculateTotals = () => {
return selectedOrders.reduce(
(totals, order) => {
totals.totalWeight = DecimalUtils.add(
totals.totalWeight,
order.netWeight || 0,
);
totals.totalAmount = DecimalUtils.add(
totals.totalAmount,
order.invoiceAmount || 0,
);
return totals;
},
{ totalWeight: 0, totalAmount: 0 },
);
};
const { totalWeight, totalAmount } = calculateTotals();
return (
<Popup
duration={150}
style={{
minHeight: "auto",
}}
closeable
destroyOnClose
visible={visible}
title={"批量上传发票"}
position="bottom"
onClose={() => setVisible(false)}
onOverlayClick={() => setVisible(false)}
lockScroll
round
>
<View className="flex flex-col">
{/* 统计信息 */}
<View className="mb-2 flex items-center justify-between p-2.5">
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">{selectedOrders.length}</View>{" "}
</View>
<View className="text-sm font-medium text-gray-800">
<View className="text-primary inline">{totalWeight}</View>
</View>
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{totalAmount.toFixed(2)}
</View>
</View>
</View>
{/* 上传发票 */}
<View className="rounded-lg bg-white p-2.5">
<View className="flex flex-col gap-2.5">
{invoiceImg && invoiceImg?.length > 0 ? (
invoiceImg?.map((uploadFileItem, index) => (
<View
key={uploadFileItem.fileName || index}
className="bg-primary/10 flex flex-col gap-2.5 rounded-lg p-2.5"
>
<View className="flex items-center">
<View className="relative mr-3 h-16 w-16 overflow-hidden rounded-lg">
<Image
className="h-full w-full object-cover"
src={uploadFileItem.filePath!}
/>
</View>
<View className="flex-1">
<View className="font-medium" id="invoice-filename">
{uploadFileItem.fileName}
</View>
<View className="text-sm text-gray-500">
{Number(uploadFileItem.fileSize! / 1024 / 1024).toFixed(
2,
)}{" "}
MB
</View>
</View>
</View>
<View className="flex flex-row gap-2.5">
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
fill={"outline"}
block
onClick={() => {
setInvoiceImg([]);
}}
>
<View></View>
</Button>
</View>
<View className={"flex-1"}>
<Button
type={"warning"}
size={"large"}
fill={"outline"}
block
onClick={() => {
setInvoiceImg([]);
Toast.show("toast", {
title: "删除成功",
icon: "success",
content: "发票已删除",
});
}}
>
<View></View>
</Button>
</View>
</View>
</View>
))
) : (
<View className={`flex w-full flex-row items-center gap-2.5`}>
<View
className={
"border-primary bg-primary/5 flex h-24 flex-1 flex-col items-center justify-center rounded-md border-2 border-dashed p-2.5"
}
onClick={async () => {
await Taro.chooseMessageFile({
type: "file",
count: 1,
success: (res) => {
setLoading(true);
const file = res.tempFiles[0];
uploadFile(file.path).then(({ url }) => {
setInvoiceImg([
{
fileName: url.split("/").pop(),
filePath: url,
fileSize: file.size,
fileType: url.split(".").pop(),
},
]);
setLoading(false);
Toast.show("toast", {
title: "上传成功",
icon: "success",
content: "发票已添加",
});
});
},
fail: (err) => {
Toast.show("toast", {
title: "上传失败",
icon: "fail",
content: err.errMsg,
});
},
});
}}
>
<Icon name={"file-pdf"} size={28} className="text-primary" />
<Text className="text-primary text-sm font-medium">
PDF文档
</Text>
<Text className="mt-1 text-xs text-gray-400">
PDF
</Text>
</View>
<View className={"text-primary text-sm font-medium"}></View>
<View
className={
"border-primary bg-primary/5 flex h-24 flex-1 flex-col items-center justify-center rounded-md border-2 border-dashed p-2.5"
}
onClick={async () => {
await Taro.chooseImage({
count: 1,
success: (res) => {
setLoading(true);
const file = res.tempFiles[0];
uploadFile(file.path).then(({ url }) => {
setInvoiceImg([
{
fileName: url.split("/").pop(),
filePath: url,
fileSize: file.size,
fileType: url.split(".").pop(),
},
]);
setLoading(false);
Toast.show("toast", {
title: "上传成功",
icon: "success",
content: "发票已添加",
});
});
},
fail: (err) => {
Toast.show("toast", {
title: "上传失败",
icon: "fail",
content: err.errMsg,
});
},
});
}}
>
<Icon name={"camera"} size={28} className="text-primary" />
<Text className="text-primary text-sm font-medium">
/
</Text>
<Text className="mt-1 text-xs text-gray-400">
</Text>
</View>
</View>
)}
</View>
</View>
{/* 操作按钮 */}
<View className={"flex flex-row gap-2 p-2.5"}>
<View className={"flex-1"}>
<Button
size={"large"}
block
type="default"
onClick={() => {
setVisible(false);
setInvoiceImg([]);
}}
>
</Button>
</View>
{/* 提交按钮 */}
<View className={"flex-1"}>
<Button
size={"large"}
block
type="primary"
disabled={invoiceImg?.length === 0}
onClick={handleSubmit}
>
</Button>
</View>
</View>
</View>
<SafeArea position={"bottom"} />
</Popup>
);
}

View File

@ -0,0 +1 @@
export { default as InvoiceBatchUpload } from "./InvoiceBatchUpload";

View File

@ -1,2 +1,2 @@
// App 相关常量 // App 相关常量
export const APP_VERSION = "v0.0.52"; export const APP_VERSION = "v0.0.53";

View File

@ -11,10 +11,11 @@ import { useRef, useState } from "react";
import { business } from "@/services"; import { business } from "@/services";
import hocAuth from "@/hocs/auth"; import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings"; import { CommonComponent } from "@/types/typings";
import { View } from "@tarojs/components"; import { Label, Text, View } from "@tarojs/components";
import dayjs from "dayjs"; import dayjs from "dayjs";
import order from "@/constant/order"; import order from "@/constant/order";
import { Button, Toast } from "@nutui/nutui-react-taro"; import { Button, Toast } from "@nutui/nutui-react-taro";
import classNames from "classnames";
export default hocAuth(function Page(props: CommonComponent) { export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props; const { shareOptions } = props;
@ -94,81 +95,154 @@ export default hocAuth(function Page(props: CommonComponent) {
type={"infinite"} type={"infinite"}
actionRef={actionRef} actionRef={actionRef}
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => ( render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => (
<View className={"mb-2.5"} key={index}> <View className={"mb-2"} key={index}>
<View <View
className={ className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5" "relative flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-xl bg-white shadow-sm"
} }
> >
<View className={"flex flex-col divide-y-2 divide-neutral-100"}> {/* 头部区域:名称 + 状态 */}
<View className={"py-2.5"}> <View
<View className="flex items-start justify-between"> className={
<View className="text-base font-medium text-gray-800"> "flex cursor-pointer items-center justify-between gap-2 px-4 py-3"
{orderSupplierVO.name} ( }
{orderSupplierVO.orderVO?.orderVehicle?.origin}- onClick={() => {
{orderSupplierVO.orderVO?.orderVehicle?.destination}) const imageUrls =
</View> orderSupplierVO.invoiceImg?.map((file) => file.filePath!) ||
<View className={"flex-shrink-0"}> [];
{orderSupplierVO.poState && ( // 使用 Taro.previewImage 预览图片
<State if (imageUrls?.length > 0) {
position="relative" Taro.previewImage({
state={orderSupplierVO.poState} urls: imageUrls,
stateMap={order.stateMap} });
/> } else {
)} Toast.show("toast", {
</View> title: "暂无发票图片",
</View> icon: "none",
<View className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600"> content: "",
<View className="text-primary"> });
{orderSupplierVO.orderVO?.orderVehicle?.vehicleNo }
? "第" + }}
orderSupplierVO.orderVO?.orderVehicle?.vehicleNo + >
"车" <View className={"flex min-w-0 flex-1 flex-col gap-1.5"}>
: "暂未生成车次"} <Text className={"text-neutral-darkest text-lg font-bold"}>
</View> {orderSupplierVO.name}
<View> </Text>
{dayjs( <Text className={"text-neutral-dark text-xs"}>
orderSupplierVO.orderVO?.orderVehicle?.deliveryTime, {orderSupplierVO.orderVO?.orderVehicle?.origin} {" "}
).format("MM-DD")} {orderSupplierVO.orderVO?.orderVehicle?.destination}
</View> </Text>
<View>|</View> </View>
<View>{orderSupplierVO.netWeight}</View> <View className={"flex flex-col items-end gap-1"}>
<View>|</View> {orderSupplierVO.poState && (
<View>¥{orderSupplierVO.invoiceAmount}</View> <State
</View> position="relative"
<View className="text-neutral-darker text-xs"> state={orderSupplierVO.poState}
{`品种:${orderSupplierVO.productName}`} stateMap={order.stateMap}
</View> />
)}
</View> </View>
</View> </View>
<View className={"py-2.5"}> {/* 详细信息区域 */}
<View className={"flex flex-row justify-end gap-2"}> <View
<Button className={"flex cursor-pointer flex-col gap-2 px-4 py-3"}
size={"small"} onClick={() => {
type={"primary"} const imageUrls =
onClick={() => { orderSupplierVO.invoiceImg?.map((file) => file.filePath!) ||
const imageUrls = [];
orderSupplierVO.invoiceImg?.map( // 使用 Taro.previewImage 预览图片
(file) => file.filePath!, if (imageUrls?.length > 0) {
) || []; Taro.previewImage({
// 使用 Taro.previewImage 预览图片 urls: imageUrls,
if (imageUrls?.length > 0) { });
Taro.previewImage({ } else {
urls: imageUrls, Toast.show("toast", {
}); title: "暂无发票图片",
} else { icon: "none",
Toast.show("toast", { content: "",
title: "暂无数据", });
icon: "none", }
content: "", }}
}); >
} <View className={"flex flex-row items-center justify-between"}>
}} <Label className={"text-neutral-dark text-xs"}>
</Label>
<Text
className={classNames(
"text-xs font-medium",
"text-primary",
)}
> >
{orderSupplierVO.orderVO?.orderVehicle?.vehicleNo
</Button> ? `${orderSupplierVO.orderVO?.orderVehicle?.vehicleNo}`
: "暂未生成车次"}
</Text>
</View> </View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}>
</Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{dayjs(
orderSupplierVO.orderVO?.orderVehicle?.deliveryTime,
).format("YYYY-MM-DD")}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}></Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{orderSupplierVO.netWeight}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}>
</Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
¥{orderSupplierVO.invoiceAmount}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}></Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{orderSupplierVO.productName}
</Text>
</View>
</View>
{/* 操作按钮区域 */}
<View
className={
"flex justify-end gap-2 border-t border-neutral-100 bg-gray-50 px-4 py-3"
}
>
<Button
size={"small"}
type={"primary"}
onClick={(e) => {
e.stopPropagation();
const imageUrls =
orderSupplierVO.invoiceImg?.map(
(file) => file.filePath!,
) || [];
// 使用 Taro.previewImage 预览图片
if (imageUrls?.length > 0) {
Taro.previewImage({
urls: imageUrls,
});
} else {
Toast.show("toast", {
title: "暂无发票图片",
icon: "none",
content: "",
});
}
}}
>
</Button>
</View> </View>
</View> </View>
</View> </View>

View File

@ -1,37 +1,31 @@
import { import {
ActionType, ActionType,
Icon, Icon,
InvoiceBatchUpload,
PageList, PageList,
State, State,
SupplierPicker, SupplierPicker,
ToolBar, ToolBar,
} from "@/components"; } from "@/components";
import Taro, { useShareAppMessage } from "@tarojs/taro"; import { useShareAppMessage } from "@tarojs/taro";
import { useRef, useState } from "react";
import { business } from "@/services"; import { business } from "@/services";
import hocAuth from "@/hocs/auth"; import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings"; import { CommonComponent } from "@/types/typings";
import { Image, View } from "@tarojs/components"; import { Label, Text, View } from "@tarojs/components";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Button, Popup, SafeArea, Toast } from "@nutui/nutui-react-taro"; import { Button } from "@nutui/nutui-react-taro";
import { uploadFile } from "@/utils";
import { globalStore } from "@/store/global-store";
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
import order from "@/constant/order"; import order from "@/constant/order";
import classNames from "classnames";
import { useRef, useState } from "react";
export default hocAuth(function Page(props: CommonComponent) { export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props; const { shareOptions } = props;
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>(); const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const [popupVisible, setPopupVisible] = useState(false); const [visible, setVisible] = useState(false);
const [selectedOrders, setSelectedOrders] = useState< const [selectedOrders, setSelectedOrders] = useState<
BusinessAPI.OrderSupplierVO[] BusinessAPI.OrderSupplierVO[]
>([]); >([]);
const { setLoading } = globalStore((state: any) => state);
const [invoiceImg, setInvoiceImg] = useState<
BusinessAPI.OrderSupplier["invoiceImg"]
>([]);
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const toolbar: ToolBar = { const toolbar: ToolBar = {
@ -53,220 +47,14 @@ export default hocAuth(function Page(props: CommonComponent) {
}, },
render: () => ( render: () => (
<> <>
{/* Popup 弹窗 */} <InvoiceBatchUpload
<Popup visible={visible}
duration={150} setVisible={setVisible}
style={{ selectedOrders={selectedOrders}
minHeight: "auto", onFinish={() => {
actionRef.current?.reload();
}} }}
closeable />
destroyOnClose
visible={popupVisible}
title={"上传发票"}
position="bottom"
onClose={() => setPopupVisible(false)}
onOverlayClick={() => setPopupVisible(false)}
lockScroll
round
>
<View className="flex flex-col gap-2.5">
{/* 统计信息 */}
<View className="mb-2 flex items-center justify-between p-2.5">
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{selectedOrders.length}
</View>{" "}
</View>
<View className="text-sm font-medium text-gray-800">
<View className="text-primary inline">{totalWeight}</View>
</View>
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{totalAmount.toFixed(2)}
</View>
</View>
</View>
{/* 上传发票 */}
<View className="rounded-lg bg-white p-2.5">
<View className="flex flex-col gap-2.5">
{invoiceImg && invoiceImg?.length > 0 ? (
invoiceImg?.map((uploadFileItem, index) => (
<View
key={uploadFileItem.fileName || index}
className="bg-primary/10 flex flex-col gap-2.5 rounded-lg p-2.5"
>
<View className="flex items-center">
<View className="relative mr-3 h-16 w-16 overflow-hidden rounded-lg">
<Image
className="h-full w-full object-cover"
src={uploadFileItem.filePath!}
/>
</View>
<View className="flex-1">
<View className="font-medium" id="invoice-filename">
{uploadFileItem.fileName}
</View>
<View className="text-sm text-gray-500">
{Number(
uploadFileItem.fileSize! / 1024 / 1024,
).toFixed(2)}{" "}
MB
</View>
</View>
</View>
<View className="flex flex-row gap-2.5">
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
fill={"outline"}
block
onClick={() => {
setInvoiceImg([]);
}}
>
<View></View>
</Button>
</View>
<View className={"flex-1"}>
<Button
type={"warning"}
size={"large"}
fill={"outline"}
block
onClick={() => {
setInvoiceImg([]);
Toast.show("toast", {
title: "删除成功",
icon: "success",
content: "发票已删除",
});
}}
>
<View></View>
</Button>
</View>
</View>
</View>
))
) : (
<View className={`flex w-full flex-row items-center gap-2.5`}>
<View
className={
"border-primary bg-primary/5 flex h-24 flex-1 flex-col items-center justify-center rounded-md border-2 border-dashed p-2.5"
}
onClick={async () => {
await Taro.chooseMessageFile({
type: "file",
count: 1,
success: (res) => {
setLoading(true);
const file = res.tempFiles[0];
uploadFile(file.path).then(({ url }) => {
setInvoiceImg([
{
fileName: url.split("/").pop(),
filePath: url,
fileSize: file.size,
fileType: url.split(".").pop(),
},
]);
setLoading(false);
});
},
fail: (err) => {
Toast.show("toast", {
title: "上传失败",
icon: "fail",
content: err.errMsg,
});
},
});
}}
>
<Icon name={"camera"} size={24} />
<View className="text-primary text-sm"></View>
<View className="mt-1 text-xs text-gray-400">
PDF文档
</View>
</View>
<View className={"text-primary text-base"}></View>
<View
className={
"border-primary bg-primary/5 flex h-24 flex-1 flex-col items-center justify-center rounded-md border-2 border-dashed p-2.5"
}
onClick={async () => {
await Taro.chooseImage({
count: 1,
success: (res) => {
setLoading(true);
const file = res.tempFiles[0];
uploadFile(file.path).then(({ url }) => {
setInvoiceImg([
{
fileName: url.split("/").pop(),
filePath: url,
fileSize: file.size,
fileType: url.split(".").pop(),
},
]);
setLoading(false);
});
},
fail: (err) => {
Toast.show("toast", {
title: "上传失败",
icon: "fail",
content: err.errMsg,
});
},
});
}}
>
<Icon name={"camera"} size={24} />
<View className="text-primary text-sm"></View>
<View className="mt-1 text-xs text-gray-400">
</View>
</View>
</View>
)}
</View>
</View>
<View className={"flex flex-1 flex-row gap-2.5 p-2.5"}>
<View className={"flex-1"}>
<Button
size={"xlarge"}
block
type="default"
onClick={() => setPopupVisible(false)}
>
</Button>
</View>
{/* 提交按钮 */}
<View className={"flex-1"}>
<Button
size={"xlarge"}
block
type="primary"
disabled={invoiceImg?.length === 0}
onClick={handleSubmit}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
<View className={"flex flex-row gap-2.5"}> <View className={"flex flex-row gap-2.5"}>
<SupplierPicker <SupplierPicker
@ -313,50 +101,6 @@ export default hocAuth(function Page(props: CommonComponent) {
return {}; return {};
}); });
// 计算选中车次的总重量和总金额
const calculateTotals = () => {
return selectedOrders.reduce(
(totals, order) => {
totals.totalWeight = DecimalUtils.add(
totals.totalWeight,
order.netWeight || 0,
);
totals.totalAmount = DecimalUtils.add(
totals.totalAmount,
order.invoiceAmount || 0,
);
return totals;
},
{ totalWeight: 0, totalAmount: 0 },
);
};
const { totalWeight, totalAmount } = calculateTotals();
// 提交申请
const handleSubmit = async () => {
// 这里添加提交申请的逻辑
const {
data: { success },
} = await business.orderSupplier.batchUploadInvoice({
orderSupplierIdList: selectedOrders.map((order) => order.orderSupplierId),
invoiceImg: invoiceImg,
invoiceUpload: true,
});
if (success) {
Toast.show("toast", {
icon: "success",
title: "提交成功",
content: "",
});
}
actionRef.current?.reload();
setPopupVisible(false);
setInvoiceImg([]);
};
return ( return (
<> <>
<PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry> <PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry>
@ -365,70 +109,123 @@ export default hocAuth(function Page(props: CommonComponent) {
type={"infinite"} type={"infinite"}
actionRef={actionRef} actionRef={actionRef}
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => ( render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => (
<View className={"mb-2.5"} key={index}> <View className={"mb-2"} key={index}>
<View <View
className={ className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5" "relative flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-xl bg-white shadow-sm"
} }
> >
<View className={"flex flex-col divide-y-2 divide-neutral-100"}> {/* 头部区域:名称 + 状态 */}
<View className={"py-2.5"}> <View
<View className="flex items-start justify-between"> className={
<View className="text-base font-medium text-gray-800"> "flex cursor-pointer items-center justify-between gap-2 px-4 py-3"
{orderSupplierVO.name} ( }
{orderSupplierVO.orderVO?.orderVehicle?.origin}- >
{orderSupplierVO.orderVO?.orderVehicle?.destination}) <View className={"flex min-w-0 flex-1 flex-col gap-1.5"}>
</View> <Text className={"text-neutral-darkest text-lg font-bold"}>
<View className={"flex-shrink-0"}> {orderSupplierVO.name}
{orderSupplierVO.poState && ( </Text>
<State <Text className={"text-neutral-dark text-xs"}>
position="relative" {orderSupplierVO.orderVO?.orderVehicle?.origin} {" "}
state={orderSupplierVO.poState} {orderSupplierVO.orderVO?.orderVehicle?.destination}
stateMap={order.stateMap} </Text>
/> </View>
)} <View className={"flex flex-col items-end gap-1"}>
</View> {orderSupplierVO.poState && (
</View> <State
<View className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600"> position="relative"
<View className="text-primary"> state={orderSupplierVO.poState}
{orderSupplierVO.orderVO?.orderVehicle?.vehicleNo stateMap={order.stateMap}
? "第" + />
orderSupplierVO.orderVO?.orderVehicle?.vehicleNo + )}
"车"
: "暂未生成车次"}
</View>
<View>
{dayjs(
orderSupplierVO.orderVO?.orderVehicle?.deliveryTime,
).format("MM-DD")}
</View>
<View>|</View>
<View>{orderSupplierVO.netWeight}</View>
<View>|</View>
<View>¥{orderSupplierVO.invoiceAmount}</View>
</View>
<View className="text-neutral-darker text-xs">
{`品种:${orderSupplierVO.productName}`}
</View>
</View> </View>
</View> </View>
<View className={"py-2.5"}>
<View className={"flex flex-row justify-end gap-2"}> {/* 详细信息区域 */}
{!orderSupplierVO.invoiceUpload && <View className={"flex flex-col gap-2 px-4 py-3"}>
(orderSupplierVO.poState === "WAITING_AUDIT" || <View className={"flex flex-row items-center justify-between"}>
orderSupplierVO.poState === "COMPLETED") && ( <Label className={"text-neutral-dark text-xs"}>
<Button
size={"small"} </Label>
type={"primary"} <Text
onClick={() => { className={classNames(
setSelectedOrders([orderSupplierVO]); "text-xs font-medium",
setPopupVisible(true); "text-primary",
}}
>
</Button>
)} )}
>
{orderSupplierVO.orderVO?.orderVehicle?.vehicleNo
? `${orderSupplierVO.orderVO?.orderVehicle?.vehicleNo}`
: "暂未生成车次"}
</Text>
</View> </View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}>
</Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{dayjs(
orderSupplierVO.orderVO?.orderVehicle?.deliveryTime,
).format("YYYY-MM-DD")}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}></Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{orderSupplierVO.netWeight}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}>
</Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
¥{orderSupplierVO.invoiceAmount}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}></Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{orderSupplierVO.productName}
</Text>
</View>
{/* 发票状态提示 */}
{orderSupplierVO.invoiceUpload ? (
<View className="mt-1 flex items-center gap-1 rounded-full bg-green-50 px-2 py-1">
<Icon name="check-circle" size={12} color={"#2E7D32"} />
<Text className="text-xs font-medium text-green-700">
</Text>
</View>
) : (
<View className="mt-1 flex items-center gap-1 rounded-full bg-orange-50 px-2 py-1">
<Icon name="clock" size={12} color={"#D97A05"} />
<Text className="text-xs font-medium text-orange-700">
</Text>
</View>
)}
</View>
{/* 操作按钮区域 */}
<View
className={
"flex justify-end gap-2 border-t border-neutral-100 bg-gray-50 px-4 py-3"
}
>
{!orderSupplierVO.invoiceUpload &&
(orderSupplierVO.poState === "WAITING_AUDIT" ||
orderSupplierVO.poState === "COMPLETED") && (
<Button
size={"small"}
type={"primary"}
onClick={() => {
setSelectedOrders([orderSupplierVO]);
setVisible(true);
}}
>
</Button>
)}
</View> </View>
</View> </View>
</View> </View>

View File

@ -1,48 +1,87 @@
import { import {
ActionType, ActionType,
Icon, Icon,
InvoiceBatchUpload,
PageList, PageList,
State, State,
SupplierPicker, SupplierPicker,
ToolBar, ToolBar,
} from "@/components"; } from "@/components";
import Taro, { useShareAppMessage } from "@tarojs/taro"; import { useShareAppMessage } from "@tarojs/taro";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { business } from "@/services"; import { business } from "@/services";
import hocAuth from "@/hocs/auth"; import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings"; import { CommonComponent } from "@/types/typings";
import { Image, View } from "@tarojs/components"; import { Text, View } from "@tarojs/components";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Button, Popup, SafeArea, Toast } from "@nutui/nutui-react-taro";
import { uploadFile } from "@/utils";
import { globalStore } from "@/store/global-store";
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
import order from "@/constant/order"; import order from "@/constant/order";
import classNames from "classnames";
import { Button, Checkbox, SafeArea } from "@nutui/nutui-react-taro";
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
export default hocAuth(function Page(props: CommonComponent) { export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props; const { shareOptions } = props;
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>(); const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const [popupVisible, setPopupVisible] = useState(false); const [visible, setVisible] = useState(false);
const [selectedOrders, setSelectedOrders] = useState< const [selectedOrders, setSelectedOrders] = useState<
BusinessAPI.OrderSupplierVO[] BusinessAPI.OrderSupplierVO[]
>([]); >([]);
const { setLoading } = globalStore((state: any) => state); const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const [invoiceImg, setInvoiceImg] = useState<
BusinessAPI.OrderSupplier["invoiceImg"]
>([]);
const actionRef = useRef<ActionType>(); const actionRef = useRef<ActionType>();
const toolbar: ToolBar = {
selectRow: { // 切换选中状态
onClick: async (orderSupplierVOList: BusinessAPI.OrderSupplierVO[]) => { const toggleSelect = (orderSupplierVO: BusinessAPI.OrderSupplierVO) => {
console.log("orderSupplierVOList", orderSupplierVOList); const newSelectedIds = new Set(selectedIds);
// 点击弹出popup if (newSelectedIds.has(orderSupplierVO.orderSupplierId)) {
setSelectedOrders(orderSupplierVOList); newSelectedIds.delete(orderSupplierVO.orderSupplierId);
setPopupVisible(true); setSelectedOrders(
selectedOrders.filter(
(orderSupplierVO) =>
orderSupplierVO.orderSupplierId !== orderSupplierVO.orderSupplierId,
),
);
} else {
newSelectedIds.add(orderSupplierVO.orderSupplierId);
setSelectedOrders([...selectedOrders, orderSupplierVO]);
}
setSelectedIds(newSelectedIds);
};
// 全选/取消全选
const toggleSelectAll = (allIds: string[]) => {
if (selectedIds.size === allIds.length) {
// 取消全选
setSelectedIds(new Set());
} else {
// 全选
setSelectedIds(new Set(allIds));
}
};
// 计算选中订单的总重量和总金额
const calculateSelectedTotals = (data: BusinessAPI.OrderSupplierVO[]) => {
const selected = data.filter((item) =>
selectedIds.has(item.orderSupplierId),
);
return selected.reduce(
(totals, order) => {
totals.totalWeight = DecimalUtils.add(
totals.totalWeight,
order.netWeight || 0,
);
totals.totalAmount = DecimalUtils.add(
totals.totalAmount,
order.invoiceAmount || 0,
);
return totals;
}, },
}, { totalWeight: 0, totalAmount: 0 },
);
};
const toolbar: ToolBar<BusinessAPI.OrderSupplierVO> = {
search: { search: {
activeKey: "vehicleNo", activeKey: "vehicleNo",
defaultActiveKey: "vehicleNo", defaultActiveKey: "vehicleNo",
@ -59,256 +98,125 @@ export default hocAuth(function Page(props: CommonComponent) {
}, },
], ],
}, },
render: () => ( render: () => {
<> return (
{/* Popup 弹窗 */} <>
<Popup {/* Popup 弹窗 */}
duration={150} <InvoiceBatchUpload
style={{ selectedOrders={selectedOrders}
minHeight: "auto", visible={visible}
}} setVisible={setVisible}
closeable onFinish={() => {
destroyOnClose
visible={popupVisible}
title={"上传发票"}
position="bottom"
onClose={() => setPopupVisible(false)}
onOverlayClick={() => setPopupVisible(false)}
lockScroll
round
>
<View className="flex flex-col gap-2.5">
{/* 统计信息 */}
<View className="mb-2 flex items-center justify-between p-2.5">
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{selectedOrders.length}
</View>{" "}
</View>
<View className="text-sm font-medium text-gray-800">
<View className="text-primary inline">{totalWeight}</View>
</View>
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{totalAmount.toFixed(2)}
</View>
</View>
</View>
{/* 上传发票 */}
<View className="rounded-lg bg-white p-2.5">
<View className="flex flex-col gap-2.5">
{invoiceImg && invoiceImg?.length > 0 ? (
invoiceImg?.map((uploadFileItem, index) => (
<View
key={uploadFileItem.fileName || index}
className="bg-primary/10 flex flex-col gap-2.5 rounded-lg p-2.5"
>
<View className="flex items-center">
<View className="relative mr-3 h-16 w-16 overflow-hidden rounded-lg">
<Image
className="h-full w-full object-cover"
src={uploadFileItem.filePath!}
/>
</View>
<View className="flex-1">
<View className="font-medium" id="invoice-filename">
{uploadFileItem.fileName}
</View>
<View className="text-sm text-gray-500">
{Number(
uploadFileItem.fileSize! / 1024 / 1024,
).toFixed(2)}{" "}
MB
</View>
</View>
</View>
<View className="flex flex-row gap-2.5">
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
fill={"outline"}
block
onClick={() => {
setInvoiceImg([]);
}}
>
<View></View>
</Button>
</View>
<View className={"flex-1"}>
<Button
type={"warning"}
size={"large"}
fill={"outline"}
block
onClick={() => {
setInvoiceImg([]);
Toast.show("toast", {
title: "删除成功",
icon: "success",
content: "发票已删除",
});
}}
>
<View></View>
</Button>
</View>
</View>
</View>
))
) : (
<View className={`flex w-full flex-row items-center gap-2.5`}>
<View
className={
"border-primary bg-primary/5 flex h-24 flex-1 flex-col items-center justify-center rounded-md border-2 border-dashed p-2.5"
}
onClick={async () => {
await Taro.chooseMessageFile({
type: "file",
count: 1,
success: (res) => {
setLoading(true);
const file = res.tempFiles[0];
uploadFile(file.path).then(({ url }) => {
setInvoiceImg([
{
fileName: url.split("/").pop(),
filePath: url,
fileSize: file.size,
fileType: url.split(".").pop(),
},
]);
setLoading(false);
});
},
fail: (err) => {
Toast.show("toast", {
title: "上传失败",
icon: "fail",
content: err.errMsg,
});
},
});
}}
>
<Icon name={"camera"} size={24} />
<View className="text-primary text-sm"></View>
<View className="mt-1 text-xs text-gray-400">
PDF文档
</View>
</View>
<View className={"text-primary text-base"}></View>
<View
className={
"border-primary bg-primary/5 flex h-24 flex-1 flex-col items-center justify-center rounded-md border-2 border-dashed p-2.5"
}
onClick={async () => {
await Taro.chooseImage({
count: 1,
success: (res) => {
setLoading(true);
const file = res.tempFiles[0];
uploadFile(file.path).then(({ url }) => {
setInvoiceImg([
{
fileName: url.split("/").pop(),
filePath: url,
fileSize: file.size,
fileType: url.split(".").pop(),
},
]);
setLoading(false);
});
},
fail: (err) => {
Toast.show("toast", {
title: "上传失败",
icon: "fail",
content: err.errMsg,
});
},
});
}}
>
<Icon name={"camera"} size={24} />
<View className="text-primary text-sm"></View>
<View className="mt-1 text-xs text-gray-400">
</View>
</View>
</View>
)}
</View>
</View>
<View className={"flex flex-1 flex-row gap-2.5 p-2.5"}>
<View className={"flex-1"}>
<Button
size={"xlarge"}
block
type="default"
onClick={() => setPopupVisible(false)}
>
</Button>
</View>
{/* 提交按钮 */}
<View className={"flex-1"}>
<Button
size={"xlarge"}
block
type="primary"
disabled={invoiceImg?.length === 0}
onClick={handleSubmit}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
<View className={"flex flex-row gap-2.5"}>
<SupplierPicker
type={"FARMER"}
onFinish={(supplierVO) => {
setSupplierVO(supplierVO);
actionRef.current?.reload(); actionRef.current?.reload();
setSelectedIds(new Set());
}} }}
trigger={
<View
className={`border-primary flex h-6 items-center rounded-md border-2 px-2.5`}
>
<View className={"text-primary text-xs"}>
{supplierVO?.name || "瓜农"}
</View>
{supplierVO?.name ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setSupplierVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/> />
<View className={"flex flex-row gap-2.5"}>
<SupplierPicker
type={"FARMER"}
onFinish={(supplierVO) => {
setSupplierVO(supplierVO);
actionRef.current?.reload();
}}
trigger={
<View
className={`border-primary flex h-6 items-center rounded-md border-2 px-2.5`}
>
<View className={"text-primary text-xs"}>
{supplierVO?.name || "瓜农"}
</View>
{supplierVO?.name ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setSupplierVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/>
</View>
</>
);
},
footer: (data: BusinessAPI.OrderSupplierVO[]) => {
const allIds = data.map((item) => item.orderSupplierId);
const isSelectedAll =
selectedIds.size === allIds.length && allIds.length > 0;
const totals = calculateSelectedTotals(data);
return (
<View className="sticky bottom-0 z-10 bg-white">
{/* 批量操作栏 */}
{selectedIds.size > 0 && (
<View className="flex items-center justify-between p-2.5">
<View className="flex flex-1 flex-row items-center gap-2">
<Checkbox
checked={isSelectedAll}
onChange={() => toggleSelectAll(allIds)}
/>
<View className="flex flex-col">
<Text className="text-sm font-medium text-gray-700">
{selectedIds.size}
</Text>
<Text className="text-xs text-gray-500">
: {totals.totalWeight} | : ¥
{totals.totalAmount.toFixed(2)}
</Text>
</View>
</View>
<View className="flex flex-row items-center gap-2">
<Button
type={"default"}
size={"large"}
onClick={() => {
setSelectedIds(new Set());
}}
>
</Button>
<Button
type={"primary"}
size={"large"}
disabled={selectedIds.size === 0}
onClick={() => {
if (selectedIds.size > 0) {
const selected = data.filter((item) =>
selectedIds.has(item.orderSupplierId),
);
setSelectedOrders(selected);
setVisible(true);
}
}}
>
</Button>
</View>
</View>
)}
{/* 全选按钮 */}
{selectedIds.size === 0 && data.length > 0 && (
<View className="p-2.5">
<View
className="flex cursor-pointer items-center gap-2 rounded-lg bg-gray-50 px-3 py-2"
onClick={() => toggleSelectAll(allIds)}
>
<Checkbox checked={false} />
<Text className="text-sm text-gray-600"></Text>
</View>
</View>
)}
<SafeArea position={"bottom"} />
</View> </View>
</> );
), },
}; };
useShareAppMessage((res) => { useShareAppMessage((res) => {
@ -321,70 +229,65 @@ export default hocAuth(function Page(props: CommonComponent) {
return {}; return {};
}); });
// 计算选中车次的总重量和总金额
const calculateTotals = () => {
return selectedOrders.reduce(
(totals, order) => {
totals.totalWeight = DecimalUtils.add(
totals.totalWeight,
order.netWeight || 0,
);
totals.totalAmount = DecimalUtils.add(
totals.totalAmount,
order.invoiceAmount || 0,
);
return totals;
},
{ totalWeight: 0, totalAmount: 0 },
);
};
const { totalWeight, totalAmount } = calculateTotals();
// 提交申请
const handleSubmit = async () => {
// 这里添加提交申请的逻辑
const {
data: { success },
} = await business.orderSupplier.batchUploadInvoice({
orderSupplierIdList: selectedOrders.map((order) => order.orderSupplierId),
invoiceImg: invoiceImg,
invoiceUpload: true,
});
if (success) {
Toast.show("toast", {
icon: "success",
title: "提交成功",
content: "",
});
}
actionRef.current?.reload();
setPopupVisible(false);
setInvoiceImg([]);
};
return ( return (
<> <>
<PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry> <PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry>
rowId={"orderSupplierId"} rowId={"orderSupplierId"}
itemHeight={182} itemHeight={100}
type={"infinite"} type={"infinite"}
actionRef={actionRef} actionRef={actionRef}
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => ( render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => {
<View className={"flex-1"} key={index}> const isSelected = selectedIds.has(orderSupplierVO.orderSupplierId);
return (
<View <View
className={"relative flex flex-col divide-y-2 divide-neutral-100"} className={classNames("mb-2", isSelected && "p-0.5")}
key={index}
> >
<View className="flex-1"> <View
<View className="flex items-start justify-between"> className={classNames(
<View className="text-base font-medium text-gray-800"> "relative flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-lg bg-white shadow-sm transition-all",
{orderSupplierVO.name} ( isSelected && "ring-primary ring-2 ring-offset-1",
{orderSupplierVO.orderVO?.orderVehicle.origin}- )}
{orderSupplierVO.orderVO?.orderVehicle.destination}) >
{/* 选中遮罩层 */}
{isSelected && (
<View className="bg-primary/5 pointer-events-none absolute inset-0 z-10" />
)}
{/* 复选框 + 头部区域 */}
<View
className={
"flex cursor-pointer items-center justify-between gap-2 px-3 py-2"
}
onClick={(e) => {
// 点击卡片切换选中状态
e.stopPropagation();
toggleSelect(orderSupplierVO);
}}
>
<View className={"flex flex-1 flex-row items-center gap-2"}>
<Checkbox
checked={isSelected}
onClick={(e) => {
e.stopPropagation();
toggleSelect(orderSupplierVO);
}}
/>
<View className={"flex min-w-0 flex-1 flex-col gap-1"}>
<Text
className={"text-neutral-darkest text-sm font-bold"}
>
{orderSupplierVO.name}
</Text>
<View className={"flex flex-row items-center gap-2"}>
<Text className={"text-neutral-dark text-xs"}>
{orderSupplierVO.orderVO?.orderVehicle?.origin} {" "}
{orderSupplierVO.orderVO?.orderVehicle?.destination}
</Text>
</View>
</View>
</View> </View>
<View className={"flex-shrink-0"}> <View className={"flex flex-col items-end gap-1"}>
{orderSupplierVO.poState && ( {orderSupplierVO.poState && (
<State <State
position="relative" position="relative"
@ -392,33 +295,66 @@ export default hocAuth(function Page(props: CommonComponent) {
stateMap={order.stateMap} stateMap={order.stateMap}
/> />
)} )}
{/* 发票状态提示 */}
{orderSupplierVO.invoiceUpload ? (
<View className="flex items-center gap-1">
<Icon name="check-circle" size={10} color={"#2E7D32"} />
<Text className="text-xs text-green-700"></Text>
</View>
) : (
<View className="flex items-center gap-1">
<Icon name="clock" size={10} color={"#D97A05"} />
<Text className="text-xs text-orange-700"></Text>
</View>
)}
</View> </View>
</View> </View>
<View className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600">
<View className="text-primary"> {/* 详细信息区域 - 紧凑一行显示 */}
{orderSupplierVO.orderVO?.orderVehicle?.vehicleNo <View
? "第" + className={
orderSupplierVO.orderVO?.orderVehicle?.vehicleNo + "flex flex-row items-center justify-between gap-2 px-3 py-2 text-xs"
"车" }
: "暂未生成车次"} onClick={(e) => {
// 点击详细信息区域也切换选中状态
e.stopPropagation();
toggleSelect(orderSupplierVO);
}}
>
<View
className={
"flex flex-1 flex-row items-center gap-1 text-gray-600"
}
>
<Text
className={classNames(
"text-xs font-medium",
"text-primary",
)}
>
{orderSupplierVO.orderVO?.orderVehicle?.vehicleNo
? `${orderSupplierVO.orderVO?.orderVehicle?.vehicleNo}`
: "暂未生成车次"}
</Text>
|
<Text>
{dayjs(
orderSupplierVO.orderVO?.orderVehicle?.deliveryTime,
).format("MM-DD")}
</Text>
<Text>|</Text>
<Text>{orderSupplierVO.netWeight}</Text>
<Text>|</Text>
<Text>¥{orderSupplierVO.invoiceAmount}</Text>
</View> </View>
<View> <Text className={"text-neutral-darkest font-medium"}>
{dayjs( {orderSupplierVO.productName}
orderSupplierVO.orderVO?.orderVehicle?.deliveryTime, </Text>
).format("MM-DD")}
</View>
<View>|</View>
<View>{orderSupplierVO.netWeight}</View>
<View>|</View>
<View>¥{orderSupplierVO.invoiceAmount}</View>
</View>
<View className="text-neutral-darker text-xs">
{`品种:${orderSupplierVO.productName}`}
</View> </View>
</View> </View>
</View> </View>
</View> );
)} }}
toolbar={toolbar} toolbar={toolbar}
request={async (params) => { request={async (params) => {
const { const {

View File

@ -60,134 +60,132 @@ export default hocAuth(function Page(props: CommonComponent) {
type={"infinite"} type={"infinite"}
actionRef={actionRef} actionRef={actionRef}
render={(supplierVO: BusinessAPI.SupplierVO, index) => ( render={(supplierVO: BusinessAPI.SupplierVO, index) => (
<View className={"mb-2.5"} key={index}> <View className={"mb-2"} key={index}>
<View <View
className={ className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5" "relative flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-xl bg-white shadow-sm"
} }
> >
<View className={"flex flex-col divide-y-2 divide-neutral-100"}> {/* 头部区域:名称 + 编辑按钮 */}
<View className={"py-2.5"}> <View
<View className={"flex flex-row items-center"}> className={
<View className={"flex flex-1 flex-col gap-2"}> "flex cursor-pointer items-center justify-between gap-2 px-4 py-3"
<View className={"flex flex-row gap-1"}> }
{/* 复制 */} onClick={() => {
<Text Taro.navigateTo({
className={"text-neutral-darkest text-xl font-bold"} url: buildUrl("/pages/supplier/create", {
> supplierId: supplierVO.supplierId,
{supplierVO?.name} }),
</Text> });
</View> }}
</View> >
<Button <View className={"flex min-w-0 flex-1 flex-col gap-1.5"}>
size={"small"} <Text className={"text-neutral-darkest text-lg font-bold"}>
type={"primary"} {supplierVO?.name}
fill={"none"} </Text>
icon={<Icon name={"pen-to-square"} size={18} />} <Text className={"text-neutral-dark text-xs"}>
onClick={() => { ID: {supplierVO.supplierId}
Taro.navigateTo({ </Text>
url: buildUrl("/pages/supplier/create", { </View>
supplierId: supplierVO.supplierId, <Button
}), size={"small"}
}); type={"primary"}
}} fill={"none"}
/> icon={<Icon name={"pen-to-square"} size={16} />}
onClick={(e) => {
e.stopPropagation();
Taro.navigateTo({
url: buildUrl("/pages/supplier/create", {
supplierId: supplierVO.supplierId,
}),
});
}}
/>
</View>
{/* 详细信息区域 */}
<View
className={"flex cursor-pointer flex-col gap-2 px-4 py-3"}
onClick={() => {
Taro.navigateTo({
url: buildUrl("/pages/supplier/create", {
supplierId: supplierVO.supplierId,
}),
});
}}
>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}></Label>
<Text className={"text-neutral-darkest text-xs font-medium"}>
{dayjs(supplierVO.createdAt).format("YYYY-MM-DD HH:mm")}
</Text>
</View>
<View className={"flex flex-row items-center justify-between"}>
<Label className={"text-neutral-dark text-xs"}></Label>
<View
className={
"flex flex-1 flex-row items-center justify-end gap-1"
}
>
{supplierVO.phone && <Phone phone={supplierVO.phone} />}
</View> </View>
</View> </View>
<View className={"py-2.5"}> <View className={"flex flex-row items-center justify-between"}>
<View className={"flex flex-col gap-2"}> <Label className={"text-neutral-dark text-xs"}></Label>
<View <Text className={"text-neutral-darkest text-xs font-medium"}>
className={ 12 | 2
"flex flex-row items-center justify-between gap-2.5" </Text>
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(supplierVO.createdAt).format("MM-DD HH:mm")}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<View
className={
"flex flex-1 flex-row items-center justify-end gap-1"
}
>
{supplierVO.phone && <Phone phone={supplierVO.phone} />}
</View>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<View
className={
"flex flex-1 flex-row items-center justify-end gap-1"
}
>
12 | 2
</View>
</View>
</View>
</View> </View>
</View> </View>
<View className={"py-2.5"}>
<View className={"flex flex-row justify-end gap-2"}> {/* 操作按钮区域 */}
{/* 联系 */} <View
{supplierVO.phone && ( className={
<Button "flex justify-end gap-2 border-t border-neutral-100 bg-gray-50 px-4 py-3"
size={"small"} }
type={"default"} >
onClick={() => { {supplierVO.phone && (
Taro.makePhoneCall({
phoneNumber: supplierVO.phone!,
});
}}
>
</Button>
)}
{/* 新建采购 */}
<Button <Button
size={"small"} size={"small"}
type={"default"} type={"default"}
onClick={() => { onClick={(e) => {
Taro.navigateTo({ e.stopPropagation();
url: buildUrl("/pages/purchase/made/create", { Taro.makePhoneCall({
supplierId: supplierVO.supplierId, phoneNumber: supplierVO.phone!,
}),
}); });
}} }}
> >
</Button> </Button>
{/* 协助开票 */} )}
<Button <Button
size={"small"} size={"small"}
type={"primary"} type={"default"}
onClick={() => { onClick={(e) => {
Taro.navigateTo({ e.stopPropagation();
url: buildUrl("/pages/invoice/upload", { Taro.navigateTo({
supplierId: supplierVO.supplierId, url: buildUrl("/pages/purchase/made/create", {
}), supplierId: supplierVO.supplierId,
}); }),
}} });
> }}
>
</Button>
</View> </Button>
<Button
size={"small"}
type={"primary"}
onClick={(e) => {
e.stopPropagation();
Taro.navigateTo({
url: buildUrl("/pages/invoice/upload", {
supplierId: supplierVO.supplierId,
}),
});
}}
>
</Button>
</View> </View>
</View> </View>
</View> </View>