feat(components): 添加发票批量上传功能并优化列表组件
- 添加 InvoiceBatchUpload 组件支持发票批量上传 - 修改 PageList 组件类型定义支持泛型 - 在 PageList 组件中添加 toolbar footer 支持 - 优化发票页面 UI 布局和交互体验 - 更新应用版本号到 v0.0.53 - 重构发票上传页面实现批量选择功能
This commit is contained in:
parent
ebd955de97
commit
ff39dc01d2
@ -48,7 +48,7 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
);
|
||||
|
||||
const [lightTheme, setLightTheme] = useState(false);
|
||||
const [data, setData] = useState<Record<any>[]>();
|
||||
const [data, setData] = useState<T[]>([]);
|
||||
const [query, setQuery] = useState<Query>();
|
||||
|
||||
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?.selectRow?.onClick && (
|
||||
{toolbar?.selectRow?.onClick && !toolbar?.footer && (
|
||||
<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 items-center gap-2"}>
|
||||
@ -393,6 +393,7 @@ export default <T extends {}, Q extends Query = Query>(
|
||||
</View>
|
||||
)}
|
||||
|
||||
{toolbar?.footer && toolbar?.footer?.(data || [])}
|
||||
{tabbar ? tabbar : <SafeArea position="bottom" />}
|
||||
|
||||
<Picker
|
||||
|
||||
@ -21,7 +21,7 @@ export type TabPane = {
|
||||
title: string | number;
|
||||
};
|
||||
|
||||
export type ToolBar = {
|
||||
export type ToolBar<T> = {
|
||||
selectRow?: {
|
||||
onClick: (selectRow: any[]) => void;
|
||||
};
|
||||
@ -38,6 +38,7 @@ export type ToolBar = {
|
||||
items: any[];
|
||||
};
|
||||
render?: () => React.ReactNode | undefined;
|
||||
footer?: (record: T[]) => React.ReactNode | undefined;
|
||||
};
|
||||
|
||||
export type IRecordListProps<T = {}, Q extends Query = Query> = {
|
||||
@ -47,7 +48,7 @@ export type IRecordListProps<T = {}, Q extends Query = Query> = {
|
||||
type: "virtual" | "infinite";
|
||||
skeleton?: React.ReactNode;
|
||||
actionRef: React.MutableRefObject<any>;
|
||||
toolbar?: ToolBar;
|
||||
toolbar?: ToolBar<T>;
|
||||
render: (record: T, index: number) => React.ReactNode;
|
||||
request: (params: Q) => Promise<Record<T>>;
|
||||
pagination: {
|
||||
|
||||
@ -9,3 +9,4 @@ export * from "./delivery";
|
||||
export * from "./cost";
|
||||
export * from "./expenses";
|
||||
export * from "./audit";
|
||||
export * from "./invoce";
|
||||
|
||||
298
packages/app-client/src/components/invoce/InvoiceBatchUpload.tsx
Normal file
298
packages/app-client/src/components/invoce/InvoiceBatchUpload.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
1
packages/app-client/src/components/invoce/index.ts
Normal file
1
packages/app-client/src/components/invoce/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as InvoiceBatchUpload } from "./InvoiceBatchUpload";
|
||||
@ -1,2 +1,2 @@
|
||||
// App 相关常量
|
||||
export const APP_VERSION = "v0.0.52";
|
||||
export const APP_VERSION = "v0.0.53";
|
||||
|
||||
@ -11,10 +11,11 @@ import { useRef, useState } from "react";
|
||||
import { business } from "@/services";
|
||||
import hocAuth from "@/hocs/auth";
|
||||
import { CommonComponent } from "@/types/typings";
|
||||
import { View } from "@tarojs/components";
|
||||
import { Label, Text, View } from "@tarojs/components";
|
||||
import dayjs from "dayjs";
|
||||
import order from "@/constant/order";
|
||||
import { Button, Toast } from "@nutui/nutui-react-taro";
|
||||
import classNames from "classnames";
|
||||
|
||||
export default hocAuth(function Page(props: CommonComponent) {
|
||||
const { shareOptions } = props;
|
||||
@ -94,21 +95,45 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
type={"infinite"}
|
||||
actionRef={actionRef}
|
||||
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => (
|
||||
<View className={"mb-2.5"} key={index}>
|
||||
<View className={"mb-2"} key={index}>
|
||||
<View
|
||||
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 className="flex items-start justify-between">
|
||||
<View className="text-base font-medium text-gray-800">
|
||||
{orderSupplierVO.name} (
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.origin}-
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.destination})
|
||||
{/* 头部区域:名称 + 状态 */}
|
||||
<View
|
||||
className={
|
||||
"flex cursor-pointer items-center justify-between gap-2 px-4 py-3"
|
||||
}
|
||||
onClick={() => {
|
||||
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: "",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View className={"flex min-w-0 flex-1 flex-col gap-1.5"}>
|
||||
<Text className={"text-neutral-darkest text-lg font-bold"}>
|
||||
{orderSupplierVO.name}
|
||||
</Text>
|
||||
<Text className={"text-neutral-dark text-xs"}>
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.origin} →{" "}
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.destination}
|
||||
</Text>
|
||||
</View>
|
||||
<View className={"flex-shrink-0"}>
|
||||
<View className={"flex flex-col items-end gap-1"}>
|
||||
{orderSupplierVO.poState && (
|
||||
<State
|
||||
position="relative"
|
||||
@ -118,36 +143,86 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600">
|
||||
<View className="text-primary">
|
||||
|
||||
{/* 详细信息区域 */}
|
||||
<View
|
||||
className={"flex cursor-pointer flex-col gap-2 px-4 py-3"}
|
||||
onClick={() => {
|
||||
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: "",
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<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
|
||||
? "第" +
|
||||
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("MM-DD")}
|
||||
).format("YYYY-MM-DD")}
|
||||
</Text>
|
||||
</View>
|
||||
<View>|</View>
|
||||
<View>{orderSupplierVO.netWeight}斤</View>
|
||||
<View>|</View>
|
||||
<View>¥{orderSupplierVO.invoiceAmount}</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="text-neutral-darker text-xs">
|
||||
{`品种:${orderSupplierVO.productName}`}
|
||||
<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={"py-2.5"}>
|
||||
<View className={"flex flex-row justify-end gap-2"}>
|
||||
{/* 操作按钮区域 */}
|
||||
<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={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const imageUrls =
|
||||
orderSupplierVO.invoiceImg?.map(
|
||||
(file) => file.filePath!,
|
||||
@ -159,7 +234,7 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
});
|
||||
} else {
|
||||
Toast.show("toast", {
|
||||
title: "暂无数据",
|
||||
title: "暂无发票图片",
|
||||
icon: "none",
|
||||
content: "",
|
||||
});
|
||||
@ -171,7 +246,6 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
toolbar={toolbar}
|
||||
request={async (params) => {
|
||||
|
||||
@ -1,37 +1,31 @@
|
||||
import {
|
||||
ActionType,
|
||||
Icon,
|
||||
InvoiceBatchUpload,
|
||||
PageList,
|
||||
State,
|
||||
SupplierPicker,
|
||||
ToolBar,
|
||||
} from "@/components";
|
||||
import Taro, { useShareAppMessage } from "@tarojs/taro";
|
||||
import { useRef, useState } from "react";
|
||||
import { useShareAppMessage } from "@tarojs/taro";
|
||||
import { business } from "@/services";
|
||||
import hocAuth from "@/hocs/auth";
|
||||
import { CommonComponent } from "@/types/typings";
|
||||
import { Image, View } from "@tarojs/components";
|
||||
import { Label, Text, View } from "@tarojs/components";
|
||||
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 { Button } from "@nutui/nutui-react-taro";
|
||||
import order from "@/constant/order";
|
||||
import classNames from "classnames";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export default hocAuth(function Page(props: CommonComponent) {
|
||||
const { shareOptions } = props;
|
||||
|
||||
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedOrders, setSelectedOrders] = useState<
|
||||
BusinessAPI.OrderSupplierVO[]
|
||||
>([]);
|
||||
const { setLoading } = globalStore((state: any) => state);
|
||||
|
||||
const [invoiceImg, setInvoiceImg] = useState<
|
||||
BusinessAPI.OrderSupplier["invoiceImg"]
|
||||
>([]);
|
||||
|
||||
const actionRef = useRef<ActionType>();
|
||||
const toolbar: ToolBar = {
|
||||
@ -53,220 +47,14 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
},
|
||||
render: () => (
|
||||
<>
|
||||
{/* Popup 弹窗 */}
|
||||
<Popup
|
||||
duration={150}
|
||||
style={{
|
||||
minHeight: "auto",
|
||||
<InvoiceBatchUpload
|
||||
visible={visible}
|
||||
setVisible={setVisible}
|
||||
selectedOrders={selectedOrders}
|
||||
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"}>
|
||||
<SupplierPicker
|
||||
@ -313,50 +101,6 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
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 (
|
||||
<>
|
||||
<PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry>
|
||||
@ -365,21 +109,28 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
type={"infinite"}
|
||||
actionRef={actionRef}
|
||||
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => (
|
||||
<View className={"mb-2.5"} key={index}>
|
||||
<View className={"mb-2"} key={index}>
|
||||
<View
|
||||
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 className="flex items-start justify-between">
|
||||
<View className="text-base font-medium text-gray-800">
|
||||
{orderSupplierVO.name} (
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.origin}-
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.destination})
|
||||
{/* 头部区域:名称 + 状态 */}
|
||||
<View
|
||||
className={
|
||||
"flex cursor-pointer items-center justify-between gap-2 px-4 py-3"
|
||||
}
|
||||
>
|
||||
<View className={"flex min-w-0 flex-1 flex-col gap-1.5"}>
|
||||
<Text className={"text-neutral-darkest text-lg font-bold"}>
|
||||
{orderSupplierVO.name}
|
||||
</Text>
|
||||
<Text className={"text-neutral-dark text-xs"}>
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.origin} →{" "}
|
||||
{orderSupplierVO.orderVO?.orderVehicle?.destination}
|
||||
</Text>
|
||||
</View>
|
||||
<View className={"flex-shrink-0"}>
|
||||
<View className={"flex flex-col items-end gap-1"}>
|
||||
{orderSupplierVO.poState && (
|
||||
<State
|
||||
position="relative"
|
||||
@ -389,31 +140,78 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<View className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600">
|
||||
<View className="text-primary">
|
||||
|
||||
{/* 详细信息区域 */}
|
||||
<View className={"flex flex-col gap-2 px-4 py-3"}>
|
||||
<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
|
||||
? "第" +
|
||||
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("MM-DD")}
|
||||
).format("YYYY-MM-DD")}
|
||||
</Text>
|
||||
</View>
|
||||
<View>|</View>
|
||||
<View>{orderSupplierVO.netWeight}斤</View>
|
||||
<View>|</View>
|
||||
<View>¥{orderSupplierVO.invoiceAmount}</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="text-neutral-darker text-xs">
|
||||
{`品种:${orderSupplierVO.productName}`}
|
||||
<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={"py-2.5"}>
|
||||
<View className={"flex flex-row justify-end gap-2"}>
|
||||
) : (
|
||||
<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") && (
|
||||
@ -422,7 +220,7 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
type={"primary"}
|
||||
onClick={() => {
|
||||
setSelectedOrders([orderSupplierVO]);
|
||||
setPopupVisible(true);
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
上传发票
|
||||
@ -431,7 +229,6 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
toolbar={toolbar}
|
||||
request={async (params) => {
|
||||
|
||||
@ -1,48 +1,87 @@
|
||||
import {
|
||||
ActionType,
|
||||
Icon,
|
||||
InvoiceBatchUpload,
|
||||
PageList,
|
||||
State,
|
||||
SupplierPicker,
|
||||
ToolBar,
|
||||
} from "@/components";
|
||||
import Taro, { useShareAppMessage } from "@tarojs/taro";
|
||||
import { useShareAppMessage } from "@tarojs/taro";
|
||||
import { useRef, useState } from "react";
|
||||
import { business } from "@/services";
|
||||
import hocAuth from "@/hocs/auth";
|
||||
import { CommonComponent } from "@/types/typings";
|
||||
import { Image, View } from "@tarojs/components";
|
||||
import { Text, View } from "@tarojs/components";
|
||||
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 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) {
|
||||
const { shareOptions } = props;
|
||||
|
||||
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedOrders, setSelectedOrders] = useState<
|
||||
BusinessAPI.OrderSupplierVO[]
|
||||
>([]);
|
||||
const { setLoading } = globalStore((state: any) => state);
|
||||
|
||||
const [invoiceImg, setInvoiceImg] = useState<
|
||||
BusinessAPI.OrderSupplier["invoiceImg"]
|
||||
>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
||||
|
||||
const actionRef = useRef<ActionType>();
|
||||
const toolbar: ToolBar = {
|
||||
selectRow: {
|
||||
onClick: async (orderSupplierVOList: BusinessAPI.OrderSupplierVO[]) => {
|
||||
console.log("orderSupplierVOList", orderSupplierVOList);
|
||||
// 点击弹出popup
|
||||
setSelectedOrders(orderSupplierVOList);
|
||||
setPopupVisible(true);
|
||||
},
|
||||
|
||||
// 切换选中状态
|
||||
const toggleSelect = (orderSupplierVO: BusinessAPI.OrderSupplierVO) => {
|
||||
const newSelectedIds = new Set(selectedIds);
|
||||
if (newSelectedIds.has(orderSupplierVO.orderSupplierId)) {
|
||||
newSelectedIds.delete(orderSupplierVO.orderSupplierId);
|
||||
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: {
|
||||
activeKey: "vehicleNo",
|
||||
defaultActiveKey: "vehicleNo",
|
||||
@ -59,222 +98,19 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
},
|
||||
],
|
||||
},
|
||||
render: () => (
|
||||
render: () => {
|
||||
return (
|
||||
<>
|
||||
{/* Popup 弹窗 */}
|
||||
<Popup
|
||||
duration={150}
|
||||
style={{
|
||||
minHeight: "auto",
|
||||
<InvoiceBatchUpload
|
||||
selectedOrders={selectedOrders}
|
||||
visible={visible}
|
||||
setVisible={setVisible}
|
||||
onFinish={() => {
|
||||
actionRef.current?.reload();
|
||||
setSelectedIds(new Set());
|
||||
}}
|
||||
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"}>
|
||||
<SupplierPicker
|
||||
@ -308,7 +144,79 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
useShareAppMessage((res) => {
|
||||
@ -321,70 +229,65 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
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 (
|
||||
<>
|
||||
<PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry>
|
||||
rowId={"orderSupplierId"}
|
||||
itemHeight={182}
|
||||
itemHeight={100}
|
||||
type={"infinite"}
|
||||
actionRef={actionRef}
|
||||
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => (
|
||||
<View className={"flex-1"} key={index}>
|
||||
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => {
|
||||
const isSelected = selectedIds.has(orderSupplierVO.orderSupplierId);
|
||||
return (
|
||||
<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 className="flex items-start justify-between">
|
||||
<View className="text-base font-medium text-gray-800">
|
||||
{orderSupplierVO.name} (
|
||||
{orderSupplierVO.orderVO?.orderVehicle.origin}-
|
||||
{orderSupplierVO.orderVO?.orderVehicle.destination})
|
||||
<View
|
||||
className={classNames(
|
||||
"relative flex flex-col divide-y divide-neutral-100 overflow-hidden rounded-lg bg-white shadow-sm transition-all",
|
||||
isSelected && "ring-primary ring-2 ring-offset-1",
|
||||
)}
|
||||
>
|
||||
{/* 选中遮罩层 */}
|
||||
{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 className={"flex-shrink-0"}>
|
||||
</View>
|
||||
</View>
|
||||
<View className={"flex flex-col items-end gap-1"}>
|
||||
{orderSupplierVO.poState && (
|
||||
<State
|
||||
position="relative"
|
||||
@ -392,33 +295,66 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
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 className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600">
|
||||
<View className="text-primary">
|
||||
|
||||
{/* 详细信息区域 - 紧凑一行显示 */}
|
||||
<View
|
||||
className={
|
||||
"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 +
|
||||
"车"
|
||||
? `第${orderSupplierVO.orderVO?.orderVehicle?.vehicleNo}车`
|
||||
: "暂未生成车次"}
|
||||
</View>
|
||||
<View>
|
||||
</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>
|
||||
<View>{orderSupplierVO.netWeight}斤</View>
|
||||
<View>|</View>
|
||||
<View>¥{orderSupplierVO.invoiceAmount}</View>
|
||||
</View>
|
||||
<View className="text-neutral-darker text-xs">
|
||||
{`品种:${orderSupplierVO.productName}`}
|
||||
<Text className={"text-neutral-darkest font-medium"}>
|
||||
{orderSupplierVO.productName}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
toolbar={toolbar}
|
||||
request={async (params) => {
|
||||
const {
|
||||
|
||||
@ -60,31 +60,40 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
type={"infinite"}
|
||||
actionRef={actionRef}
|
||||
render={(supplierVO: BusinessAPI.SupplierVO, index) => (
|
||||
<View className={"mb-2.5"} key={index}>
|
||||
<View className={"mb-2"} key={index}>
|
||||
<View
|
||||
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 className={"flex flex-row items-center"}>
|
||||
<View className={"flex flex-1 flex-col gap-2"}>
|
||||
<View className={"flex flex-row gap-1"}>
|
||||
{/* 复制 */}
|
||||
<Text
|
||||
className={"text-neutral-darkest text-xl font-bold"}
|
||||
{/* 头部区域:名称 + 编辑按钮 */}
|
||||
<View
|
||||
className={
|
||||
"flex cursor-pointer items-center justify-between gap-2 px-4 py-3"
|
||||
}
|
||||
onClick={() => {
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/supplier/create", {
|
||||
supplierId: supplierVO.supplierId,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<View className={"flex min-w-0 flex-1 flex-col gap-1.5"}>
|
||||
<Text className={"text-neutral-darkest text-lg font-bold"}>
|
||||
{supplierVO?.name}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className={"text-neutral-dark text-xs"}>
|
||||
瓜农 ID: {supplierVO.supplierId}
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
size={"small"}
|
||||
type={"primary"}
|
||||
fill={"none"}
|
||||
icon={<Icon name={"pen-to-square"} size={18} />}
|
||||
onClick={() => {
|
||||
icon={<Icon name={"pen-to-square"} size={16} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/supplier/create", {
|
||||
supplierId: supplierVO.supplierId,
|
||||
@ -93,29 +102,26 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View className={"py-2.5"}>
|
||||
<View className={"flex flex-col gap-2"}>
|
||||
|
||||
{/* 详细信息区域 */}
|
||||
<View
|
||||
className={
|
||||
"flex flex-row items-center justify-between gap-2.5"
|
||||
}
|
||||
className={"flex cursor-pointer flex-col gap-2 px-4 py-3"}
|
||||
onClick={() => {
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/supplier/create", {
|
||||
supplierId: supplierVO.supplierId,
|
||||
}),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Label className={"text-neutral-dark text-sm"}>
|
||||
登记时间
|
||||
</Label>
|
||||
<Text className={"text-neutral-darkest text-sm"}>
|
||||
{dayjs(supplierVO.createdAt).format("MM-DD HH:mm")}
|
||||
<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 gap-2.5"
|
||||
}
|
||||
>
|
||||
<Label className={"text-neutral-dark text-sm"}>
|
||||
联系方式
|
||||
</Label>
|
||||
<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"
|
||||
@ -124,33 +130,26 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
{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"
|
||||
}
|
||||
>
|
||||
<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"}>
|
||||
已开票 12 张 | 待开票 2 张
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View className={"py-2.5"}>
|
||||
<View className={"flex flex-row justify-end gap-2"}>
|
||||
{/* 联系 */}
|
||||
|
||||
{/* 操作按钮区域 */}
|
||||
<View
|
||||
className={
|
||||
"flex justify-end gap-2 border-t border-neutral-100 bg-gray-50 px-4 py-3"
|
||||
}
|
||||
>
|
||||
{supplierVO.phone && (
|
||||
<Button
|
||||
size={"small"}
|
||||
type={"default"}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
Taro.makePhoneCall({
|
||||
phoneNumber: supplierVO.phone!,
|
||||
});
|
||||
@ -159,11 +158,11 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
联系
|
||||
</Button>
|
||||
)}
|
||||
{/* 新建采购 */}
|
||||
<Button
|
||||
size={"small"}
|
||||
type={"default"}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/purchase/made/create", {
|
||||
supplierId: supplierVO.supplierId,
|
||||
@ -173,11 +172,11 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
>
|
||||
新建采购
|
||||
</Button>
|
||||
{/* 协助开票 */}
|
||||
<Button
|
||||
size={"small"}
|
||||
type={"primary"}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
Taro.navigateTo({
|
||||
url: buildUrl("/pages/invoice/upload", {
|
||||
supplierId: supplierVO.supplierId,
|
||||
@ -190,7 +189,6 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
toolbar={toolbar}
|
||||
request={async (params) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user