feat(audit): 新增审核功能模块

- 新增审核列表、详情、更新接口
- 新增审核相关常量配置
- 新增审核页面组件及路由
- 调整采购订单驳回描述文案
- 优化费用统计导出功能
- 修复部分页面tabbar显示问题
- 更新应用版本号至v0.0.52
This commit is contained in:
shenyifei 2025-12-23 00:20:43 +08:00
parent e546067226
commit ac170e8628
37 changed files with 18128 additions and 14645 deletions

View File

@ -57,6 +57,7 @@
"ua-parser-js": "^1.0.37",
"vconsole": "^3.15.1",
"weixin-js-sdk": "^1.6.5",
"xlsx": "^0.18.5",
"zustand": "^5.0.3"
},
"devDependencies": {

View File

@ -0,0 +1,320 @@
import {
ActionType,
CopyText,
DealerPicker,
Icon,
PageList,
PurchaseOrderRejectFinal,
State,
SupplierPicker,
ToolBar,
} from "@/components";
import React, { useRef, useState } from "react";
import { business } from "@/services";
import { Label, Text, View } from "@tarojs/components";
import { buildUrl } from "@/utils";
import { Button } from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import Taro from "@tarojs/taro";
import audit from "@/constant/audit";
interface IApprovalListProps {
params: BusinessAPI.AuditPageQry;
toolbar?: ToolBar;
actionRef?: React.MutableRefObject<ActionType | undefined>;
}
export default function ApprovalList(props: IApprovalListProps) {
const {
params: bizParams,
toolbar: defaultToolbar,
actionRef: externalActionRef,
} = props;
const internalActionRef = useRef<ActionType>();
const actionRef = externalActionRef || internalActionRef;
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const toolbar: ToolBar = {
...defaultToolbar,
search: {
activeKey: "plate",
defaultActiveKey: "plate",
items: [
{
key: "plate",
name: "车牌号",
placeholder: "请输入车牌号",
},
],
},
render: () => (
<View className={"item-start flex flex-row gap-2.5"}>
<View>
<DealerPicker
onFinish={(dealerVO) => {
setDealerVO(dealerVO);
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"}>
{dealerVO?.shortName || "经销商"}
</View>
{dealerVO?.shortName ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setDealerVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/>
</View>
<View>
<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>
</View>
),
};
return (
<PageList<BusinessAPI.AuditVO, BusinessAPI.AuditPageQry>
rowId={"purchaseOrderId"}
itemHeight={182}
toolbar={toolbar}
type={"infinite"}
actionRef={actionRef}
render={(auditVO: BusinessAPI.AuditVO, index) => {
const purchaseOrderVO = auditVO.purchaseOrderVO;
return (
purchaseOrderVO && (
<View className={"mb-2.5"} key={index}>
<View
className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5"
}
>
<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"}
>
{purchaseOrderVO.orderVehicle?.dealerName}{" "}
{purchaseOrderVO.orderVehicle?.vehicleNo
? "第" +
purchaseOrderVO.orderVehicle?.vehicleNo +
"车"
: "暂未生成车次"}
</Text>
</View>
<Text className={"text-neutral-dark text-sm"}>
{purchaseOrderVO.orderVehicle?.origin} {" 至 "}
{purchaseOrderVO.orderVehicle?.destination}
</Text>
</View>
<State
state={auditVO.state}
stateMap={audit.approvalStateMap}
/>
</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"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<CopyText copyData={purchaseOrderVO?.orderSn || "-"}>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.orderSn}
</Text>
</CopyText>
</View>
{/* 采购类型*/}
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.type === "PRODUCTION_PURCHASE"
? "产地采购"
: "市场采购"}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{auditVO.createdByName}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(auditVO.createdAt).format("MM-DD HH:mm")}
</Text>
</View>
{auditVO.state === "AUDIT_REJECTED" && (
<>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{auditVO.auditReason}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{auditVO.auditByName}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(auditVO.auditAt).format("MM-DD HH:mm")}
</Text>
</View>
</>
)}
</View>
</View>
</View>
<View className={"py-2.5"}>
{auditVO.state === "WAITING_AUDIT" &&
auditVO.type === "BOSS_AUDIT" && (
<View className={"flex flex-row gap-2"}>
<View className={"flex-1"}>
<PurchaseOrderRejectFinal
purchaseOrderVO={purchaseOrderVO}
size={"large"}
onFinish={() => {
actionRef.current?.reload();
}}
/>
</View>
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
block
onClick={(e) => {
Taro.navigateTo({
url: buildUrl("/pages/approval/audit", {
orderId: purchaseOrderVO.orderId,
auditId: auditVO.auditId,
}),
});
e.stopPropagation();
}}
>
</Button>
</View>
</View>
)}
</View>
</View>
</View>
)
);
}}
request={async (params) => {
const {
data: { data, success, notEmpty },
} = await business.audit.pageAudit({
auditPageQry: {
...params,
...bizParams,
},
});
return {
data,
success,
hasMore: notEmpty,
};
}}
pagination={{
pageSize: 10,
}}
/>
);
}

View File

@ -0,0 +1,370 @@
import {
ActionType,
CopyText,
DealerPicker,
Icon,
PageList,
PurchaseOrderRejectApprove,
PurchaseOrderWithdrawReview,
State,
SupplierPicker,
ToolBar,
} from "@/components";
import React, { useRef, useState } from "react";
import { business } from "@/services";
import { Label, Text, View } from "@tarojs/components";
import { buildUrl } from "@/utils";
import { Button } from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import Taro from "@tarojs/taro";
import audit from "@/constant/audit";
import { globalStore } from "@/store/global-store";
import { purchase } from "@/constant";
interface IAuditListProps {
params: BusinessAPI.AuditPageQry;
toolbar?: ToolBar;
actionRef?: React.MutableRefObject<ActionType | undefined>;
}
export default function AuditList(props: IAuditListProps) {
const {
params: bizParams,
toolbar: defaultToolbar,
actionRef: externalActionRef,
} = props;
const userRoleVO = globalStore((state: any) => state.userRoleVO);
const slug = userRoleVO?.slug;
const internalActionRef = useRef<ActionType>();
const actionRef = externalActionRef || internalActionRef;
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const toolbar: ToolBar = {
...defaultToolbar,
search: {
activeKey: "plate",
defaultActiveKey: "plate",
items: [
{
key: "plate",
name: "车牌号",
placeholder: "请输入车牌号",
},
],
},
render: () => (
<View className={"item-start flex flex-row gap-2.5"}>
<View>
<DealerPicker
onFinish={(dealerVO) => {
setDealerVO(dealerVO);
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"}>
{dealerVO?.shortName || "经销商"}
</View>
{dealerVO?.shortName ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setDealerVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/>
</View>
<View>
<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>
</View>
),
};
return (
<PageList<BusinessAPI.AuditVO, BusinessAPI.AuditPageQry>
rowId={"purchaseOrderId"}
itemHeight={182}
toolbar={toolbar}
type={"infinite"}
actionRef={actionRef}
render={(auditVO: BusinessAPI.AuditVO, index) => {
const { purchaseOrderVO } = auditVO;
return (
purchaseOrderVO && (
<View className={"mb-2.5"} key={index}>
<View
className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5"
}
>
<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"}
>
{purchaseOrderVO.orderVehicle?.dealerName}{" "}
{purchaseOrderVO.orderVehicle?.vehicleNo
? "第" +
purchaseOrderVO.orderVehicle?.vehicleNo +
"车"
: "暂未生成车次"}
</Text>
</View>
<Text className={"text-neutral-dark text-sm"}>
{purchaseOrderVO.orderVehicle?.origin} {" 至 "}
{purchaseOrderVO.orderVehicle?.destination}
</Text>
</View>
{bizParams.type === "BOSS_AUDIT" ? (
<State
state={"AUDITING"}
stateMap={audit.auditStateMap}
/>
) : (
<State
state={auditVO.state}
stateMap={audit.auditStateMap}
/>
)}
</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"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<CopyText copyData={purchaseOrderVO?.orderSn || "-"}>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.orderSn}
</Text>
</CopyText>
</View>
{/* 采购类型*/}
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.type === "PRODUCTION_PURCHASE"
? "产地采购"
: "市场采购"}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{auditVO.createdByName}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(auditVO.createdAt).format("MM-DD HH:mm")}
</Text>
</View>
{auditVO.state === "AUDIT_REJECTED" && (
<>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{auditVO.auditReason}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{auditVO.auditByName}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(auditVO.auditAt).format("MM-DD HH:mm")}
</Text>
</View>
</>
)}
</View>
</View>
</View>
<View className={"py-2.5"}>
{slug === "reviewer" &&
auditVO.state === "WAITING_AUDIT" &&
auditVO.type === "REVIEWER_AUDIT" && (
<View className={"flex flex-row gap-2"}>
<View className={"flex-1"}>
<PurchaseOrderRejectApprove
purchaseOrderVO={purchaseOrderVO}
size={"large"}
onFinish={() => {
actionRef.current?.reload();
}}
/>
</View>
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
block
onClick={(e) => {
Taro.navigateTo({
url: buildUrl("/pages/audit/audit", {
auditId: auditVO.auditId,
orderId: purchaseOrderVO.orderId,
}),
});
e.stopPropagation();
}}
>
</Button>
</View>
</View>
)}
{(slug === "origin-entry" || slug === "market-buyer") &&
auditVO.state === "WAITING_AUDIT" && (
<View className={"flex flex-row justify-end gap-2"}>
<PurchaseOrderWithdrawReview
purchaseOrderVO={purchaseOrderVO}
size={"small"}
onFinish={() => {
actionRef.current?.reload();
}}
/>
</View>
)}
{(slug === "origin-entry" || slug === "market-buyer") &&
auditVO.state === "AUDIT_REJECTED" && (
<View className={"flex flex-row justify-end gap-2"}>
<Button
type={"primary"}
size={"small"}
onClick={(e) => {
Taro.navigateTo({
url: buildUrl(
purchase.path[purchaseOrderVO.type].create,
{
orderId: purchaseOrderVO.orderId,
},
),
});
e.stopPropagation();
}}
>
</Button>
</View>
)}
</View>
</View>
</View>
)
);
}}
request={async (params) => {
const {
data: { data, success, notEmpty },
} = await business.audit.pageAudit({
auditPageQry: {
...params,
...bizParams,
},
});
return {
data,
success,
hasMore: notEmpty,
};
}}
pagination={{
pageSize: 10,
}}
/>
);
}

View File

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

View File

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

View File

@ -1,263 +0,0 @@
import {
ActionType,
CopyText,
DealerPicker,
Icon,
PageList,
PurchaseOrderRejectFinal,
State,
SupplierPicker,
ToolBar,
} from "@/components";
import React, { useRef, useState } from "react";
import { business } from "@/services";
import { Label, Text, View } from "@tarojs/components";
import { buildUrl } from "@/utils";
import { Button } from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import purchaseOrder from "@/constant/purchaseOrder";
import Taro from "@tarojs/taro";
interface IPurchaseOrderApprovalListProps {
params: BusinessAPI.PurchaseOrderPageQry;
toolbar?: ToolBar;
actionRef?: React.MutableRefObject<ActionType | undefined>;
}
export default function PurchaseOrderApprovalList(
props: IPurchaseOrderApprovalListProps,
) {
const {
params: bizParams,
toolbar: defaultToolbar,
actionRef: externalActionRef,
} = props;
const internalActionRef = useRef<ActionType>();
const actionRef = externalActionRef || internalActionRef;
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const toolbar: ToolBar = {
...defaultToolbar,
search: {
activeKey: "plate",
defaultActiveKey: "plate",
items: [
{
key: "plate",
name: "车牌号",
placeholder: "请输入车牌号",
},
],
},
render: () => (
<View className={"item-start flex flex-row gap-2.5"}>
<View>
<DealerPicker
onFinish={(dealerVO) => {
setDealerVO(dealerVO);
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"}>
{dealerVO?.shortName || "经销商"}
</View>
{dealerVO?.shortName ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setDealerVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/>
</View>
<View>
<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>
</View>
),
};
return (
<PageList<BusinessAPI.PurchaseOrderVO, BusinessAPI.PurchaseOrderPageQry>
rowId={"purchaseOrderId"}
itemHeight={182}
toolbar={toolbar}
type={"infinite"}
actionRef={actionRef}
render={(purchaseOrderVO: BusinessAPI.PurchaseOrderVO, index) => {
return (
<View className={"mb-2.5"} key={index}>
<View
className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5"
}
>
<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"}
>
{purchaseOrderVO.orderVehicle?.dealerName}{" "}
{purchaseOrderVO.orderVehicle?.vehicleNo
? "第" +
purchaseOrderVO.orderVehicle?.vehicleNo +
"车"
: "暂未生成车次"}
</Text>
</View>
<Text className={"text-neutral-dark text-sm"}>
{purchaseOrderVO.orderVehicle?.origin} {" 至 "}
{purchaseOrderVO.orderVehicle?.destination}
</Text>
</View>
<State
state={purchaseOrderVO.auditState}
stateMap={purchaseOrder.approvalStateMap}
/>
</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"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<CopyText copyData={purchaseOrderVO?.orderSn || "-"}>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.orderSn}
</Text>
</CopyText>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.createdByName}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(purchaseOrderVO.createdAt).format("MM-DD HH:mm")}
</Text>
</View>
</View>
</View>
</View>
<View className={"py-2.5"}>
{purchaseOrderVO.state === "WAITING_AUDIT" &&
purchaseOrderVO.auditState === "PENDING_BOSS_APPROVAL" && (
<View className={"flex flex-row gap-2"}>
<View className={"flex-1"}>
<PurchaseOrderRejectFinal
purchaseOrderVO={purchaseOrderVO}
size={"large"}
onFinish={() => {
actionRef.current?.reload();
}}
/>
</View>
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
block
onClick={(e) => {
Taro.navigateTo({
url: buildUrl("/pages/approval/audit", {
orderId: purchaseOrderVO.orderId,
}),
});
e.stopPropagation();
}}
>
</Button>
</View>
</View>
)}
</View>
</View>
</View>
);
}}
request={async (params) => {
const {
data: { data, success, notEmpty },
} = await business.purchaseOrder.pagePurchaseOrder({
purchaseOrderPageQry: {
...params,
...bizParams,
},
});
return {
data,
success,
hasMore: notEmpty,
};
}}
pagination={{
pageSize: 10,
}}
/>
);
}

View File

@ -1,263 +0,0 @@
import {
ActionType,
CopyText,
DealerPicker,
Icon,
PageList,
PurchaseOrderRejectApprove,
State,
SupplierPicker,
ToolBar,
} from "@/components";
import React, { useRef, useState } from "react";
import { business } from "@/services";
import { Label, Text, View } from "@tarojs/components";
import { buildUrl } from "@/utils";
import { Button } from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import purchaseOrder from "@/constant/purchaseOrder";
import Taro from "@tarojs/taro";
interface IPurchaseOrderAuditListProps {
params: BusinessAPI.PurchaseOrderPageQry;
toolbar?: ToolBar;
actionRef?: React.MutableRefObject<ActionType | undefined>;
}
export default function PurchaseOrderAuditList(
props: IPurchaseOrderAuditListProps,
) {
const {
params: bizParams,
toolbar: defaultToolbar,
actionRef: externalActionRef,
} = props;
const internalActionRef = useRef<ActionType>();
const actionRef = externalActionRef || internalActionRef;
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const toolbar: ToolBar = {
...defaultToolbar,
search: {
activeKey: "plate",
defaultActiveKey: "plate",
items: [
{
key: "plate",
name: "车牌号",
placeholder: "请输入车牌号",
},
],
},
render: () => (
<View className={"item-start flex flex-row gap-2.5"}>
<View>
<DealerPicker
onFinish={(dealerVO) => {
setDealerVO(dealerVO);
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"}>
{dealerVO?.shortName || "经销商"}
</View>
{dealerVO?.shortName ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setDealerVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/>
</View>
<View>
<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>
</View>
),
};
return (
<PageList<BusinessAPI.PurchaseOrderVO, BusinessAPI.PurchaseOrderPageQry>
rowId={"purchaseOrderId"}
itemHeight={182}
toolbar={toolbar}
type={"infinite"}
actionRef={actionRef}
render={(purchaseOrderVO: BusinessAPI.PurchaseOrderVO, index) => {
return (
<View className={"mb-2.5"} key={index}>
<View
className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5"
}
>
<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"}
>
{purchaseOrderVO.orderVehicle?.dealerName}{" "}
{purchaseOrderVO.orderVehicle?.vehicleNo
? "第" +
purchaseOrderVO.orderVehicle?.vehicleNo +
"车"
: "暂未生成车次"}
</Text>
</View>
<Text className={"text-neutral-dark text-sm"}>
{purchaseOrderVO.orderVehicle?.origin} {" 至 "}
{purchaseOrderVO.orderVehicle?.destination}
</Text>
</View>
<State
state={purchaseOrderVO.auditState}
stateMap={purchaseOrder.auditStateMap}
/>
</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"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<CopyText copyData={purchaseOrderVO?.orderSn || "-"}>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.orderSn}
</Text>
</CopyText>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{purchaseOrderVO.createdByName}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(purchaseOrderVO.createdAt).format("MM-DD HH:mm")}
</Text>
</View>
</View>
</View>
</View>
<View className={"py-2.5"}>
{purchaseOrderVO.state === "WAITING_AUDIT" &&
purchaseOrderVO.auditState === "PENDING_QUOTE_APPROVAL" && (
<View className={"flex flex-row gap-2"}>
<View className={"flex-1"}>
<PurchaseOrderRejectApprove
purchaseOrderVO={purchaseOrderVO}
size={"large"}
onFinish={() => {
actionRef.current?.reload();
}}
/>
</View>
<View className={"flex-1"}>
<Button
type={"primary"}
size={"large"}
block
onClick={(e) => {
Taro.navigateTo({
url: buildUrl("/pages/audit/audit", {
orderId: purchaseOrderVO.orderId,
}),
});
e.stopPropagation();
}}
>
</Button>
</View>
</View>
)}
</View>
</View>
</View>
);
}}
request={async (params) => {
const {
data: { data, success, notEmpty },
} = await business.purchaseOrder.pagePurchaseOrder({
purchaseOrderPageQry: {
...params,
...bizParams,
},
});
return {
data,
success,
hasMore: notEmpty,
};
}}
pagination={{
pageSize: 10,
}}
/>
);
}

View File

@ -106,7 +106,7 @@ export default function PurchaseOrderRejectApprove(
closeable
destroyOnClose
title={"驳回"}
description={"此采购单将退回到产地录入员"}
description={"此采购单将退回到采购员"}
>
<View className="p-2.5">
<View className="mb-2.5 text-sm font-bold"></View>

View File

@ -52,6 +52,7 @@ export { default as MaterialCostSection } from "./section/MaterialCostSection";
export { default as ProductionAdvanceSection } from "./section/ProductionAdvanceSection";
export { default as WorkerAdvanceSection } from "./section/WorkerAdvanceSection";
export { default as DeliveryFormSection } from "./section/DeliveryFormSection";
export type { DeliveryFormSectionRef } from "./section/DeliveryFormSection";
export { default as PurchaseFormSection } from "./section/PurchaseFormSection";
export { default as PurchaseStep1Form } from "./document/Step1Form";
@ -61,8 +62,6 @@ export { default as PurchaseStep3Success } from "./document/Step3Success";
export { default as PurchaseOrderItem } from "./PurchaseOrderItem";
export { default as PurchaseOrderList } from "./PurchaseOrderList";
export { default as PurchaseOrderAuditList } from "./PurchaseOrderAuditList";
export { default as PurchaseOrderApprovalList } from "./PurchaseOrderApprovalList";
export { default as CostCreate } from "./cost/CostCreate";
export { default as CostList } from "./cost/CostList";

View File

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

View File

@ -0,0 +1,88 @@
/**
*
*/
const auditStateList = [
{
title: "待我审核",
value: "WAITING_AUDIT",
},
{
title: "已通过",
value: "AUDIT_SUCCESS",
},
{
title: "已驳回",
value: "AUDIT_REJECTED",
},
];
const auditStateMap = {
WAITING_AUDIT: {
label: "待我审核",
color: "#7C2D12",
bgColor: "#FED7AA",
borderColor: "#EA580C",
},
AUDITING: {
label: "审批中",
color: "#1E40AF",
bgColor: "#DBEAFE",
borderColor: "#3B82F6",
},
AUDIT_SUCCESS: {
label: "已通过",
color: "#374151",
bgColor: "#E5E7EB",
borderColor: "#6B7280",
},
AUDIT_REJECTED: {
label: "已驳回",
color: "#6B7280",
bgColor: "#F3F4F6",
borderColor: "#6B7280",
},
};
/**
*
*/
const approvalStateList = [
{
title: "待我审批",
value: "WAITING_AUDIT",
},
{
title: "已通过",
value: "AUDIT_SUCCESS",
},
{
title: "已驳回",
value: "AUDIT_REJECTED",
},
];
const approvalStateMap = {
WAITING_AUDIT: {
label: "待我审批",
color: "#7C2D12",
bgColor: "#FED7AA",
borderColor: "#EA580C",
},
AUDIT_SUCCESS: {
label: "已通过",
color: "#374151",
bgColor: "#E5E7EB",
borderColor: "#6B7280",
},
AUDIT_REJECTED: {
label: "已驳回",
color: "#991B1B",
bgColor: "#FEE2E2",
borderColor: "#EF4444",
},
};
export default {
auditStateList,
auditStateMap,
approvalStateList,
approvalStateMap,
};

View File

@ -56,97 +56,7 @@ const stateMap = {
},
};
/**
*
*/
const auditStateList = [
{
title: "待我审核",
value: "PENDING_QUOTE_APPROVAL",
},
{
title: "审批中",
value: "PENDING_BOSS_APPROVAL",
},
{
title: "已通过",
value: "BOSS_APPROVED",
},
{
title: "已驳回",
value: "BOSS_REJECTED",
},
];
const auditStateMap = {
PENDING_QUOTE_APPROVAL: {
label: "待我审核",
color: "#7C2D12",
bgColor: "#FED7AA",
borderColor: "#EA580C",
},
PENDING_BOSS_APPROVAL: {
label: "审批中",
color: "#1E40AF",
bgColor: "#DBEAFE",
borderColor: "#3B82F6",
},
BOSS_APPROVED: {
label: "已通过",
color: "#374151",
bgColor: "#E5E7EB",
borderColor: "#6B7280",
},
BOSS_REJECTED: {
label: "已驳回",
color: "#6B7280",
bgColor: "#F3F4F6",
borderColor: "#6B7280",
},
};
/**
*
*/
const approvalStateList = [
{
title: "待我审批",
value: "PENDING_BOSS_APPROVAL",
},
{
title: "已通过",
value: "BOSS_APPROVED",
},
{
title: "已驳回",
value: "BOSS_REJECTED",
},
];
const approvalStateMap = {
PENDING_BOSS_APPROVAL: {
label: "待我审批",
color: "#7C2D12",
bgColor: "#FED7AA",
borderColor: "#EA580C",
},
BOSS_APPROVED: {
label: "已通过",
color: "#374151",
bgColor: "#E5E7EB",
borderColor: "#6B7280",
},
BOSS_REJECTED: {
label: "已驳回",
color: "#991B1B",
bgColor: "#FEE2E2",
borderColor: "#EF4444",
},
};
export default {
stateList,
stateMap,
auditStateList,
auditStateMap,
approvalStateList,
approvalStateMap,
};

View File

@ -103,8 +103,8 @@ const reviewCardMap = {
// 角色标识
const roleSlugMap = {
"origin-entry": "录入员",
"market-buyer": "采购员",
"origin-entry": "产地采购员",
"market-buyer": "市场采购员",
reviewer: "审核员",
boss: "老板",
};

View File

@ -1,16 +1,15 @@
import { ActionType, PurchaseOrderApprovalList } from "@/components";
import { ActionType, ApprovalList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import { useRef, useState } from "react";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import purchaseOrder from "@/constant/purchaseOrder";
import audit from "@/constant/audit";
export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props;
const [auditState, setAuditState] = useState<
BusinessAPI.PurchaseOrderPageQry["auditState"]
>("PENDING_BOSS_APPROVAL");
const [state, setState] =
useState<BusinessAPI.AuditPageQry["state"]>("WAITING_AUDIT");
const actionRef = useRef<ActionType>();
@ -25,24 +24,22 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderApprovalList
<ApprovalList
actionRef={actionRef}
toolbar={{
tabs: {
activeKey: "state",
defaultActiveKey: auditState!,
items: purchaseOrder.approvalStateList,
defaultActiveKey: state!,
items: audit.approvalStateList,
onChange: (item) => {
setAuditState(
item as BusinessAPI.PurchaseOrderPageQry["auditState"],
);
setState(item as BusinessAPI.AuditPageQry["state"]);
actionRef.current?.reload();
},
},
}}
params={{
state: "WAITING_AUDIT",
auditState: auditState,
state: state,
type: "BOSS_AUDIT",
}}
/>
);

View File

@ -3,7 +3,7 @@ import { CommonComponent } from "@/types/typings";
import Taro from "@tarojs/taro";
import { business } from "@/services";
import { useEffect, useState } from "react";
import { View } from "@tarojs/components";
import { Text, View } from "@tarojs/components";
import { SafeArea } from "@nutui/nutui-react-taro";
import {
PurchaseOrderFinalApprove,
@ -18,30 +18,51 @@ import {
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
export default hocAuth(function Page(props: CommonComponent) {
const { router, isInitialized, setIsInitialized } = props;
const { router, isInitialized, setIsInitialized, setLoading } = props;
const orderId = router.params
.orderId as BusinessAPI.PurchaseOrderVO["orderId"];
const auditId = router.params.auditId as BusinessAPI.AuditVO["auditId"];
const [auditVO, setAuditVO] = useState<BusinessAPI.AuditVO>();
const [purchaseOrderVO, setPurchaseOrderVO] =
useState<BusinessAPI.PurchaseOrderVO>();
const init = async (orderId: BusinessAPI.PurchaseOrderVO["orderId"]) => {
const { data } = await business.purchaseOrder.showPurchaseOrder({
const init = async (
orderId: BusinessAPI.PurchaseOrderVO["orderId"],
auditId: BusinessAPI.AuditVO["auditId"],
) => {
const {
data: { data: auditVO, success: auditSuccess },
} = await business.audit.showAudit({
auditShowQry: {
auditId,
},
});
if (auditSuccess && auditVO) {
setAuditVO(auditVO);
}
const {
data: { data: purchaseOrderVO, success: purchaseSuccess },
} = await business.purchaseOrder.showPurchaseOrder({
purchaseOrderShowQry: {
orderId,
},
});
if (data.success) {
setPurchaseOrderVO(data.data);
if (purchaseSuccess) {
setPurchaseOrderVO(purchaseOrderVO);
}
};
useEffect(() => {
if (orderId && !isInitialized) {
init(orderId).then(() => {
if (orderId && auditId && !isInitialized) {
setLoading(true);
init(orderId, auditId).then(() => {
setIsInitialized(true);
setLoading(false);
});
}
}, []);
@ -78,7 +99,14 @@ export default hocAuth(function Page(props: CommonComponent) {
className={"flex flex-1 flex-col gap-2.5 p-2.5"}
id={"purchase-order-approve"}
>
<View>
{/* 如果有驳回理由,展示一下 */}
<View className={"mb-2.5"}>
{auditVO?.state === "AUDIT_REJECTED" && (
<Text className={"text-sm text-red-500"}>
{auditVO.auditReason}
</Text>
)}
</View>
<View className="flex justify-between space-x-2.5">
<View className="price-card flex-1 rounded-lg bg-white p-3">
<View className="mb-1 text-center text-sm text-gray-500">
@ -103,6 +131,30 @@ export default hocAuth(function Page(props: CommonComponent) {
</View>
</View>
</View>
<View className="overflow-hidden rounded-lg bg-white shadow-sm">
<View className="border-b border-gray-100 px-4 py-3">
<View className="text-sm font-bold"></View>
</View>
{/* 采购单号,采购类型 */}
<View className="grid grid-cols-2 divide-x divide-y divide-gray-100">
<View className="flex flex-col px-3 py-2">
<View className="text-sm text-gray-500"></View>
<View className="font-medium">
{purchaseOrderVO.orderVehicle.dealerName}{" "}
{purchaseOrderVO?.orderVehicle?.vehicleNo
? "第" + purchaseOrderVO.orderVehicle.vehicleNo + "车"
: "暂未生成车次"}
</View>
</View>
<View className="flex flex-col px-3 py-2">
<View className="text-sm text-gray-500"></View>
<View className="font-medium">
{purchaseOrderVO.type === "PRODUCTION_PURCHASE"
? "产地采购单"
: "市场采购单"}
</View>
</View>
</View>
</View>
<View className="overflow-hidden rounded-lg bg-white shadow-sm">
<View className="border-b border-gray-100 px-4 py-3">

View File

@ -1,4 +1,4 @@
import { PurchaseOrderApprovalList } from "@/components";
import { ApprovalList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
@ -17,10 +17,10 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderApprovalList
<ApprovalList
params={{
state: "WAITING_AUDIT",
auditState: "PENDING_BOSS_APPROVAL",
type: "BOSS_AUDIT",
}}
/>
);

View File

@ -1,4 +1,4 @@
import { PurchaseOrderApprovalList } from "@/components";
import { ApprovalList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
@ -17,10 +17,10 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderApprovalList
<ApprovalList
params={{
state: "REJECTED",
auditState: "BOSS_REJECTED",
state: "AUDIT_REJECTED",
type: "BOSS_AUDIT",
}}
/>
);

View File

@ -1,4 +1,4 @@
import { PurchaseOrderAuditList } from "@/components";
import { AuditList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
@ -17,10 +17,10 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderAuditList
<AuditList
params={{
state: "WAITING_AUDIT",
auditState: "PENDING_BOSS_APPROVAL",
type: "BOSS_AUDIT",
}}
/>
);

View File

@ -1,16 +1,15 @@
import { ActionType, PurchaseOrderAuditList } from "@/components";
import { ActionType, AuditList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import { useRef, useState } from "react";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import purchaseOrder from "@/constant/purchaseOrder";
import audit from "@/constant/audit";
export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props;
const [auditState, setAuditState] = useState<
BusinessAPI.PurchaseOrderPageQry["auditState"]
>("PENDING_QUOTE_APPROVAL");
const [state, setState] =
useState<BusinessAPI.AuditPageQry["state"]>("WAITING_AUDIT");
const actionRef = useRef<ActionType>();
@ -25,24 +24,22 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderAuditList
<AuditList
actionRef={actionRef}
toolbar={{
tabs: {
activeKey: "state",
defaultActiveKey: auditState!,
items: purchaseOrder.auditStateList,
defaultActiveKey: state!,
items: audit.auditStateList,
onChange: (item) => {
setAuditState(
item as BusinessAPI.PurchaseOrderPageQry["auditState"],
);
setState(item as BusinessAPI.AuditPageQry["state"]);
actionRef.current?.reload();
},
},
}}
params={{
state: "WAITING_AUDIT",
auditState: auditState,
type: "REVIEWER_AUDIT",
state: state,
}}
/>
);

View File

@ -4,7 +4,6 @@ import Taro, { usePageScroll } from "@tarojs/taro";
import { business } from "@/services";
import { useEffect, useRef, useState } from "react";
import { View } from "@tarojs/components";
import purchaseOrder from "@/constant/purchaseOrder";
import {
ActionSheet,
Button,
@ -22,6 +21,7 @@ import {
CostSummarySection,
DealerInfoSection,
DeliveryFormSection,
DeliveryFormSectionRef,
EmptyBoxInfoSection,
Icon,
MarketPriceSection,
@ -39,7 +39,6 @@ import {
TaxSubsidySection,
WorkerAdvanceSection,
} from "@/components";
import { type DeliveryFormSectionRef } from "@/components/purchase/section/DeliveryFormSection";
import {
buildUrl,
convertPurchaseOrderToOrderShip,
@ -49,6 +48,7 @@ import {
} from "@/utils";
import classNames from "classnames";
import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils";
import audit from "@/constant/audit";
const defaultSections = [
"marketPrice",
@ -236,10 +236,12 @@ export default hocAuth(function Page(props: CommonComponent) {
const orderId = router.params
.orderId as BusinessAPI.PurchaseOrderVO["orderId"];
const auditId = router.params.auditId as BusinessAPI.AuditVO["auditId"];
// 费用项目列表
const [costList, setCostList] = useState<BusinessAPI.CostVO[]>([]);
const [auditVO, setAuditVO] = useState<BusinessAPI.AuditVO>();
const [purchaseOrderVO, setPurchaseOrderVO] =
useState<BusinessAPI.PurchaseOrderVO>();
@ -531,16 +533,31 @@ export default hocAuth(function Page(props: CommonComponent) {
return null;
};
const init = async (orderId: BusinessAPI.PurchaseOrderVO["orderId"]) => {
const init = async (
orderId: BusinessAPI.PurchaseOrderVO["orderId"],
auditId: BusinessAPI.AuditVO["auditId"],
) => {
const {
data: { data: purchaseOrderVO, success },
data: { data: auditVO, success: auditSuccess },
} = await business.audit.showAudit({
auditShowQry: {
auditId,
},
});
if (auditSuccess && auditVO) {
setAuditVO(auditVO);
}
const {
data: { data: purchaseOrderVO, success: purchaseSuccess },
} = await business.purchaseOrder.showPurchaseOrder({
purchaseOrderShowQry: {
orderId,
},
});
if (success && purchaseOrderVO) {
if (purchaseSuccess && purchaseOrderVO) {
if (
!purchaseOrderVO.orderShipList ||
purchaseOrderVO.orderShipList.length === 0
@ -688,9 +705,9 @@ export default hocAuth(function Page(props: CommonComponent) {
};
useEffect(() => {
if (orderId && !isInitialized) {
if (orderId && auditId && !isInitialized) {
setLoading(true);
init(orderId).then(() => {
init(orderId, auditId).then(() => {
setIsInitialized(true);
setLoading(false);
});
@ -802,10 +819,9 @@ export default hocAuth(function Page(props: CommonComponent) {
{purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}
</View>
<State
state={purchaseOrderVO.state}
stateMap={purchaseOrder.stateMap}
/>
{auditVO && (
<State state={auditVO.state} stateMap={audit.auditStateMap} />
)}
</View>
<View className="text-neutral-darker text-sm font-medium">
{purchaseOrderVO?.orderVehicle.origin || "-"} {" "}
@ -815,7 +831,9 @@ export default hocAuth(function Page(props: CommonComponent) {
{/* 展示产地负责人*/}
<View className="flex flex-row items-center gap-2.5">
<View className="flex-shrink-0 text-base font-bold text-gray-900">
{purchaseOrderVO.type === "PRODUCTION_PURCHASE"
? "产地负责人"
: "市场负责人"}
</View>
<View
@ -832,7 +850,7 @@ export default hocAuth(function Page(props: CommonComponent) {
purchaseOrderVO.auditState !== "BOSS_REJECTED")
)
}
placeholder="请输入产地负责人姓名"
placeholder="请输入姓名"
value={originPrincipal || purchaseOrderVO.createdByName}
onChange={(value) => setOriginPrincipal(value)}
onBlur={() => {
@ -1000,7 +1018,7 @@ export default hocAuth(function Page(props: CommonComponent) {
},
{
name: "驳回审核",
description: "此采购单将退回到产地录入员",
description: "此采购单将退回到采购员",
danger: true,
},
{
@ -1042,7 +1060,7 @@ export default hocAuth(function Page(props: CommonComponent) {
closeable
destroyOnClose
title={"驳回"}
description={"此采购单将退回到产地录入员"}
description={"此采购单将退回到采购员"}
>
<View className="p-2.5">
<View className="mb-2.5 text-sm font-bold"></View>

View File

@ -1,4 +1,4 @@
import { PurchaseOrderAuditList } from "@/components";
import { AuditList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
@ -17,10 +17,10 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderAuditList
<AuditList
params={{
state: "WAITING_AUDIT",
auditState: "PENDING_QUOTE_APPROVAL",
type: "REVIEWER_AUDIT",
}}
/>
);

View File

@ -1,4 +1,4 @@
import { PurchaseOrderList } from "@/components";
import { AuditList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
@ -17,10 +17,10 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderList
<AuditList
params={{
state: "REJECTED",
auditState: "QUOTE_REJECTED",
state: "AUDIT_REJECTED",
type: "REVIEWER_AUDIT",
}}
/>
);

View File

@ -1,7 +1,7 @@
import { PurchaseOrderList } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import { AuditList } from "@/components";
export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props;
@ -17,9 +17,10 @@ export default hocAuth(function Page(props: CommonComponent) {
});
return (
<PurchaseOrderList
<AuditList
params={{
state: "WAITING_AUDIT",
type: "REVIEWER_AUDIT",
}}
/>
);

View File

@ -8,6 +8,7 @@ import { Button, Calendar, SafeArea } from "@nutui/nutui-react-taro";
import { useEffect, useState } from "react";
import { business } from "@/services";
import { globalStore } from "@/store/global-store";
import { exportExpenseStatisticsExcel } from "@/utils/functions";
interface PageProps extends CommonComponent {
expenseRecordId?: string;
@ -178,6 +179,40 @@ export default hocAuth(function Page(props: PageProps) {
return costMap;
};
// 导出费用统计数据
const exportStatistics = async () => {
if (!expenseRecordList || expenseRecordList.length === 0) {
Taro.showToast({
title: "暂无数据可导出",
icon: "none",
});
return;
}
try {
setLoading(true);
const stats = calculateStatistics();
const startDate = currentDate[0];
const endDate = currentDate[1];
// 使用工具函数导出Excel
await exportExpenseStatisticsExcel({
title: "费用统计报表",
startDate,
endDate,
statistics: stats,
recordList: expenseRecordList,
fileName: `费用统计_${dayjs(startDate).format("YYYYMMDD")}_${dayjs(endDate).format("YYYYMMDD")}.xlsx`,
sheetName: "费用统计",
});
} catch (error) {
console.error("导出失败:", error);
} finally {
setLoading(false);
}
};
// 渲染计提明细
const renderProvisionDetails = (record: BusinessAPI.ExpenseRecordVO) => {
const provisionList = record.expenseProvisionList || [];
@ -581,6 +616,20 @@ export default hocAuth(function Page(props: PageProps) {
</Button>
</View>
{/* 导出按钮 */}
<View className="flex-1">
<Button
icon={<Icon name="download" size={12} color={"white"} />}
size={"large"}
block
type={"warning"}
onClick={exportStatistics}
>
</Button>
</View>
{userRoleVO.slug === "boss" && (
<View className="flex-1">
<Button

View File

@ -37,6 +37,7 @@ export default hocAuth(function Page(props: CommonComponent) {
} = await auth.user.userEmployee({
employeeShowQry: {
userId: user.userId,
//@ts-ignore
platformId: platformId,
},
});
@ -45,6 +46,7 @@ export default hocAuth(function Page(props: CommonComponent) {
setEmployeeVO(employeeVO);
const userRoleVOList =
employeeVO.userRoleList?.filter(
//@ts-ignore
(item) => item.platformId === platformId,
) || [];
setUserRoleVOList(userRoleVOList);
@ -333,7 +335,9 @@ export default hocAuth(function Page(props: CommonComponent) {
© {dayjs().format("YYYY")} {channel?.technicalSupport}
</Text>
</View>
{process.env.TARO_ENV === "weapp" && (
<CustomTabBar userRoleVO={userRoleVO} />
)}
{/* 选择身份 */}
<Popup

View File

@ -133,7 +133,9 @@ export default hocAuth(function Page(props: CommonComponent) {
</View>
)}
</View>
{process.env.TARO_ENV === "weapp" && (
<CustomTabBar userRoleVO={userRoleVO} />
)}
</>
);
});

View File

@ -91,7 +91,9 @@ export default hocAuth(function Page(props: CommonComponent) {
return (
<>
<View className="p-2.5">{renderMenuTree(menuList)}</View>
{process.env.TARO_ENV === "weapp" && (
<CustomTabBar userRoleVO={userRoleVO} />
)}
</>
);
});

View File

@ -381,7 +381,9 @@ export default hocAuth(function Page(props: CommonComponent) {
{activeTab === "done" && renderDoneList()}
{activeTab === "message" && renderMessageList()}
</View>
{process.env.TARO_ENV === "weapp" && (
<CustomTabBar userRoleVO={userRoleVO} />
)}
</>
);
});

View File

@ -3,7 +3,11 @@ import { CommonComponent } from "@/types/typings";
import { View } from "@tarojs/components";
import { Button, SafeArea } from "@nutui/nutui-react-taro";
import { useEffect, useState } from "react";
import { MarketPreview, PurchaseOrderWithdrawReview } from "@/components";
import {
MarketPreview,
PurchaseOrderSubmitReview,
PurchaseOrderWithdrawReview,
} from "@/components";
import { business } from "@/services";
import Taro from "@tarojs/taro";
import { buildUrl } from "@/utils";
@ -73,7 +77,7 @@ export default hocAuth(function Page(props: CommonComponent) {
</Button>
</View>
<View className={"flex-1"}>
<PurchaseOrderWithdrawReview
<PurchaseOrderSubmitReview
purchaseOrderVO={purchaseOrder}
size={"xlarge"}
onFinish={() => {

View File

@ -0,0 +1,67 @@
// @ts-ignore
/* eslint-disable */
import request from "../request";
/** 审核列表 GET /operation/pageAudit */
export async function pageAudit(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: BusinessAPI.pageAuditParams,
options?: { [key: string]: any },
) {
return request<BusinessAPI.PageResponseAuditVO>("/operation/pageAudit", {
method: "GET",
params: {
...params,
auditPageQry: undefined,
...params["auditPageQry"],
},
...(options || {}),
});
}
/** 审核详情 GET /operation/showAudit */
export async function showAudit(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: BusinessAPI.showAuditParams,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseAuditVO>("/operation/showAudit", {
method: "GET",
params: {
...params,
auditShowQry: undefined,
...params["auditShowQry"],
},
...(options || {}),
});
}
/** 审核更新 PUT /operation/updateAudit */
export async function updateAudit(
body: BusinessAPI.AuditUpdateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseAuditVO>("/operation/updateAudit", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 审核更新 PATCH /operation/updateAudit */
export async function updateAudit1(
body: BusinessAPI.AuditUpdateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseAuditVO>("/operation/updateAudit", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}

View File

@ -29,6 +29,7 @@ import * as channel from "./channel";
import * as boxSpec from "./boxSpec";
import * as boxProduct from "./boxProduct";
import * as boxBrand from "./boxBrand";
import * as audit from "./audit";
import * as agreement from "./agreement";
import * as role from "./role";
import * as permission from "./permission";
@ -64,6 +65,7 @@ export default {
boxSpec,
boxProduct,
boxBrand,
audit,
agreement,
role,
permission,

View File

@ -96,6 +96,110 @@ declare namespace BusinessAPI {
notifyUrl?: string;
};
type AuditPageQry = {
pageSize?: number;
pageIndex?: number;
orderBy?: string;
orderDirection?: string;
groupBy?: string;
needTotalCount?: boolean;
/** 自定义字段key */
customFieldKey?: string;
/** 自定义字段value */
customFieldValue?: string;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status?: boolean;
/** 审核ID */
auditId?: string;
/** 资源类型 */
subjectType?: "PURCHASE_ORDER";
/** 审核状态: 1_待审核2_审核中3_审核通过4_审核驳回5_审核撤回 */
state?:
| "WAITING_AUDIT"
| "AUDITING"
| "AUDIT_SUCCESS"
| "AUDIT_REJECTED"
| "AUDIT_CANCEL";
/** 审核类型1_审核员审核2_老板审核 */
type: "REVIEWER_AUDIT" | "BOSS_AUDIT";
offset?: number;
};
type AuditShowQry = {
/** 状态1_启用0_禁用 */
status?: boolean;
/** 审核ID */
auditId?: string;
};
type AuditUpdateCmd = {
/** 创建人ID */
createdBy: string;
/** 创建人姓名 */
createdByName?: string;
/** 审核Id */
auditId: string;
/** 资源Id */
subjectId: string;
/** 资源类型 */
subjectType: "PURCHASE_ORDER";
/** 审核状态: 1_待审核2_审核中3_审核通过4_审核驳回5_审核撤回 */
state?:
| "WAITING_AUDIT"
| "AUDITING"
| "AUDIT_SUCCESS"
| "AUDIT_REJECTED"
| "AUDIT_CANCEL";
/** 审核类型1_审核员审核2_老板审核 */
type: "REVIEWER_AUDIT" | "BOSS_AUDIT";
/** 驳回原因 */
auditReason?: string;
/** 审核时间 */
auditAt?: string;
/** 审核人ID */
auditBy?: string;
/** 审核人名称 */
auditByName?: string;
/** 创建时间 */
createdAt?: string;
};
type AuditVO = {
/** 审核Id */
auditId: string;
/** 资源Id */
subjectId: string;
/** 资源类型 */
subjectType: "PURCHASE_ORDER";
/** 审核状态: 1_待审核2_审核中3_审核通过4_审核驳回5_审核撤回 */
state?:
| "WAITING_AUDIT"
| "AUDITING"
| "AUDIT_SUCCESS"
| "AUDIT_REJECTED"
| "AUDIT_CANCEL";
/** 审核类型1_审核员审核2_老板审核 */
type: "REVIEWER_AUDIT" | "BOSS_AUDIT";
/** 驳回原因 */
auditReason?: string;
/** 审核时间 */
auditAt?: string;
/** 审核人ID */
auditBy?: string;
/** 审核人名称 */
auditByName?: string;
/** 创建人ID */
createdBy: string;
/** 创建人姓名 */
createdByName?: string;
/** 创建时间 */
createdAt?: string;
/** 采购单信息 */
purchaseOrderVO?: PurchaseOrderVO;
};
type BoxBrandCreateCmd = {
/** 品牌ID */
brandId: string;
@ -1957,7 +2061,7 @@ declare namespace BusinessAPI {
/** 费用类型名称 */
costName: string;
/** 花销金额 */
expenseAmount: string;
expenseAmount: number;
/** 备注(可填员工、事由等) */
remark?: string;
};
@ -1975,6 +2079,8 @@ declare namespace BusinessAPI {
provisionAmount: number;
/** 采购单ID */
orderId?: string;
/** 备注 */
remark?: string;
};
type ExpenseRecordCreateCmd = {
@ -3130,10 +3236,6 @@ declare namespace BusinessAPI {
watermelonGrade: string;
/** 瓜农姓名逗号隔开 */
farmerInfo?: string;
/** 发货单据 */
pdfUrl: string;
picUrl: string;
/** 发货单状态0_草稿1_待发货2_待回款3_待改签4_部分回款5_已回款6_拒收完结7_已完结 */
state:
| "DRAFT"
@ -3148,6 +3250,10 @@ declare namespace BusinessAPI {
remark?: string;
/** 发货单明细 */
orderShipItemList?: OrderShipItem[];
/** PDF 文件地址 */
pdfUrl?: string;
/** 图片文件地址 */
picUrl?: string;
};
type OrderShipCreateCmd = {
@ -3201,10 +3307,10 @@ declare namespace BusinessAPI {
remark?: string;
/** 发货单明细 */
orderShipItemList?: OrderShipItem[];
/** 发货单据 */
picUrl?: string;
/** 发货单据 */
/** PDF 文件地址 */
pdfUrl?: string;
/** 图片文件地址 */
picUrl?: string;
};
type OrderShipItem = {
@ -3303,8 +3409,10 @@ declare namespace BusinessAPI {
remark?: string;
/** 发货单明细 */
orderShipItemList?: OrderShipItem[];
/** 发货单据 */
document?: string;
/** PDF 文件地址 */
pdfUrl?: string;
/** 图片文件地址 */
picUrl?: string;
};
type OrderShipVO = {
@ -3338,8 +3446,10 @@ declare namespace BusinessAPI {
watermelonGrade?: string;
/** 瓜农姓名逗号隔开 */
farmerInfo?: string;
/** 发货单据 */
document?: string;
/** PDF 文件地址 */
pdfUrl?: string;
/** 图片文件地址 */
picUrl?: string;
/** 发货单状态0_草稿1_待发货2_待回款3_待改签4_部分回款5_已回款6_拒收完结7_已完结 */
state?:
| "DRAFT"
@ -3385,7 +3495,7 @@ declare namespace BusinessAPI {
phone: string;
/** 微信二维码 */
wechatQr?: string;
/** 供应商类型1_瓜农2_档口; */
/** 供应商类型1_瓜农2_自家档口;3_其他家档口 */
type: "FARMER" | "STALL" | "OTHER_STALL";
/** 是否最后一家 */
isLast: boolean;
@ -3538,8 +3648,8 @@ declare namespace BusinessAPI {
phone: string;
/** 微信二维码 */
wechatQr: string;
/** 供应商类型1_瓜农2_档口; */
type: "FARMER" | "STALL";
/** 供应商类型1_瓜农2_自家档口;3_其他家档口 */
type: "FARMER" | "STALL" | "OTHER_STALL";
/** 是否最后一家 */
isLast: boolean;
/** 空磅是否包含纸箱 */
@ -3640,6 +3750,10 @@ declare namespace BusinessAPI {
agreementPageQry: AgreementPageQry;
};
type pageAuditParams = {
auditPageQry: AuditPageQry;
};
type pageBoxBrandParams = {
boxBrandPageQry: BoxBrandPageQry;
};
@ -3749,6 +3863,19 @@ declare namespace BusinessAPI {
totalPages?: number;
};
type PageResponseAuditVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
totalCount?: number;
pageSize?: number;
pageIndex?: number;
data?: AuditVO[];
empty?: boolean;
notEmpty?: boolean;
totalPages?: number;
};
type PageResponseBoxBrandVO = {
success?: boolean;
errCode?: string;
@ -4579,9 +4706,9 @@ declare namespace BusinessAPI {
/** 报价方式1_按毛重报价2_按净重报价 */
pricingMethod?: "BY_GROSS_WEIGHT" | "BY_NET_WEIGHT";
/** 采购订单状态: 0_草稿1_审核中2_已完成3_已驳回4_已关闭 */
state?: "DRAFT" | "WAITING_AUDIT" | "COMPLETED" | "REJECTED" | "CLOSED";
state: "DRAFT" | "WAITING_AUDIT" | "COMPLETED" | "REJECTED" | "CLOSED";
/** 采购单审核状态: 1_待报价审核2_待老板审批3_老板审批通过4_报价审核驳回5_老板审批驳回 */
auditState?:
auditState:
| "NONE"
| "PENDING_QUOTE_APPROVAL"
| "PENDING_BOSS_APPROVAL"
@ -4845,6 +4972,10 @@ declare namespace BusinessAPI {
agreementShowQry: AgreementShowQry;
};
type showAuditParams = {
auditShowQry: AuditShowQry;
};
type showBoxBrandParams = {
boxBrandShowQry: BoxBrandShowQry;
};
@ -4964,6 +5095,13 @@ declare namespace BusinessAPI {
data?: AgreementVO;
};
type SingleResponseAuditVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
data?: AuditVO;
};
type SingleResponseBoxBrandVO = {
success?: boolean;
errCode?: string;
@ -5254,6 +5392,10 @@ declare namespace BusinessAPI {
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 供应商类型1_瓜农2_自家档口3_其他家档口 */
type: "FARMER" | "STALL" | "OTHER_STALL";
/** 经销商ID */
dealerId?: number;
};
type SupplierDestroyCmd = {
@ -5266,10 +5408,12 @@ declare namespace BusinessAPI {
status?: boolean;
/** 供应商ID */
supplierId?: string;
/** 经销商ID */
dealerId?: string;
/** 供应商名称 */
name?: string;
/** 供应商类型1_瓜农2_档口; */
type?: "FARMER" | "STALL";
/** 供应商类型1_瓜农2_自家档口;3_其他家档口 */
type?: "FARMER" | "STALL" | "OTHER_STALL";
};
type SupplierPackageUsage = {
@ -5296,6 +5440,8 @@ declare namespace BusinessAPI {
status?: boolean;
/** 供应商ID */
supplierId?: string;
/** 经销商ID */
dealerId?: string;
/** 供应商名称 */
name?: string;
/** 身份证号 */
@ -5304,8 +5450,8 @@ declare namespace BusinessAPI {
phone?: string;
/** 银行卡号 */
bankCard?: string;
/** 供应商类型1_瓜农2_档口; */
type?: "FARMER" | "STALL";
/** 供应商类型1_瓜农2_自家档口;3_其他家档口 */
type?: "FARMER" | "STALL" | "OTHER_STALL";
offset?: number;
};
@ -5335,6 +5481,10 @@ declare namespace BusinessAPI {
remark?: string;
/** 状态1_启用0_禁用 */
status: boolean;
/** 供应商类型1_瓜农2_自家档口3_其他家档口 */
type: "FARMER" | "STALL" | "OTHER_STALL";
/** 经销商ID */
dealerId?: number;
};
type SupplierVO = {
@ -5352,8 +5502,10 @@ declare namespace BusinessAPI {
bankCard: string;
/** 微信收款码URL */
wechatQr?: string;
/** 供应商类型1_瓜农2_档口 */
type: "FARMER" | "STALL";
/** 供应商类型1_瓜农2_自家档口3_其他家档口 */
type: "FARMER" | "STALL" | "OTHER_STALL";
/** 经销商ID */
dealerId?: number;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
@ -5364,6 +5516,8 @@ declare namespace BusinessAPI {
createdByName?: string;
/** 创建时间 */
createdAt?: string;
/** 经销商信息 */
dealerVO?: DealerVO;
};
type TencentMapConfigValue =

View File

@ -19,7 +19,6 @@ export async function postApiV1Poster(
type?: string;
/** 编码类型 */
encoding?: string;
format?: 'custom' | 'a4';
},
options?: { [key: string]: any },
) {

View File

@ -0,0 +1,338 @@
import Taro from "@tarojs/taro";
import dayjs from "dayjs";
import { utils, write } from "xlsx";
// 费用统计数据接口
interface ExpenseStatistics {
totalVehicles: number;
totalProvision: number;
totalExpense: number;
dailyProfit: number;
dealerStatistics: Map<string, { vehicleCount: number; totalAmount: number }>;
costCategoryStatistics: Map<string, number>;
}
interface ExpenseRecordVO {
recordDate?: string;
totalProvision?: number;
totalExpense?: number;
expenseProvisionList?: Array<{
dealerName?: string;
provisionAmount?: number;
}>;
expenseCostList?: Array<{ costName?: string; expenseAmount?: number }>;
}
interface ExportExcelOptions {
title?: string;
startDate?: string;
endDate?: string;
statistics?: ExpenseStatistics;
recordList?: ExpenseRecordVO[];
fileName?: string;
sheetName?: string;
}
/**
* Excel文件
* @param options
* @returns Promise<void>
*/
export const exportExpenseStatisticsExcel = async (
options: ExportExcelOptions,
): Promise<void> => {
const {
title = "费用统计报表",
startDate,
endDate,
statistics,
recordList,
fileName,
sheetName = "费用统计",
} = options;
if (!recordList || recordList.length === 0) {
Taro.showToast({
title: "暂无数据可导出",
icon: "none",
});
return;
}
if (!startDate || !endDate || !statistics) {
Taro.showToast({
title: "导出参数不完整",
icon: "none",
});
return;
}
try {
// 创建工作簿
const wb = utils.book_new();
// 创建工作表数据
const wsData: string[][] = [];
// 添加标题信息
wsData.push([title]);
wsData.push([
`统计时间:${dayjs(startDate).format("YYYY年MM月DD日")} ~ ${dayjs(endDate).format("YYYY年MM月DD日")}`,
]);
wsData.push([]); // 空行
// 汇总概览
wsData.push(["汇总概览"]);
wsData.push(["总车次", statistics.totalVehicles + "车"]);
wsData.push(["计提总额", statistics.totalProvision.toLocaleString()]);
wsData.push(["费用总额", statistics.totalExpense.toLocaleString()]);
wsData.push(["日常利润", statistics.dailyProfit.toLocaleString()]);
wsData.push([]); // 空行
// 计提方统计
if (statistics.dealerStatistics.size > 0) {
wsData.push(["计提方统计"]);
wsData.push(["计提方", "车次", "计提金额"]);
statistics.dealerStatistics.forEach((stat, dealerName) => {
wsData.push([
dealerName,
stat.vehicleCount + "车",
stat.totalAmount.toLocaleString(),
]);
});
wsData.push([]); // 空行
}
// 费用分类汇总
if (statistics.costCategoryStatistics.size > 0) {
wsData.push(["费用分类汇总"]);
wsData.push(["费用类型", "金额"]);
statistics.costCategoryStatistics.forEach((amount, costName) => {
wsData.push([costName, amount.toLocaleString()]);
});
wsData.push([]); // 空行
}
// 每日明细
wsData.push(["每日花销明细"]);
wsData.push(["日期", "车次", "计提金额", "费用金额", "当日利润"]);
recordList.forEach((record) => {
const vehicleCount = record.expenseProvisionList?.length || 0;
const dayProfit =
(record.totalProvision || 0) - (record.totalExpense || 0);
wsData.push([
record.recordDate
? dayjs(record.recordDate).format("YYYY年MM月DD日")
: "",
vehicleCount + "车",
(record.totalProvision || 0).toLocaleString(),
(record.totalExpense || 0).toLocaleString(),
dayProfit.toLocaleString(),
]);
});
// 创建工作表
const ws = utils.aoa_to_sheet(wsData);
// 设置列宽
const wscols = [
{ wch: 20 }, // A列宽度
{ wch: 15 }, // B列宽度
{ wch: 15 }, // C列宽度
{ wch: 15 }, // D列宽度
{ wch: 15 }, // E列宽度
];
ws["!cols"] = wscols;
// 设置标题行样式(加粗)
wsData.forEach((row, index) => {
if (
row.length > 0 &&
typeof row[0] === "string" &&
(row[0].includes("统计") ||
row[0].includes("汇总") ||
row[0].includes("明细"))
) {
const cellAddress = utils.encode_cell({ r: index, c: 0 });
if (!ws[cellAddress]) ws[cellAddress] = {};
ws[cellAddress].s = { font: { bold: true } };
}
});
// 将工作表添加到工作簿
utils.book_append_sheet(wb, ws, sheetName);
// 生成默认文件名
const finalFileName =
fileName ||
`费用统计_${dayjs(startDate).format("YYYYMMDD")}_${dayjs(endDate).format("YYYYMMDD")}.xlsx`;
// 根据环境选择不同的导出方式
const processEnv = process.env.TARO_ENV;
if (processEnv === "h5") {
// H5环境创建下载链接
const excelBuffer = write(wb, { type: "array", bookType: "xlsx" });
const blob = new Blob([excelBuffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = finalFileName;
link.style.display = "none";
// 触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理URL对象
window.URL.revokeObjectURL(url);
await Taro.showToast({
title: "导出成功",
icon: "success",
});
} else {
// 小程序环境使用文件系统API
const excelBuffer = write(wb, { type: "array", bookType: "xlsx" });
// 将文件保存到本地
const filePath = `${Taro.env.USER_DATA_PATH}/${finalFileName}`;
Taro.getFileSystemManager().writeFile({
filePath,
data: new Uint8Array(excelBuffer),
encoding: "binary",
success() {
console.log("文件保存成功");
// 分享文件
Taro.shareFileMessage({
filePath,
});
Taro.showToast({
title: "导出成功",
icon: "success",
});
},
fail(error) {
console.error("文件保存失败:", error);
Taro.showToast({
title: "导出失败",
icon: "error",
});
},
});
}
} catch (error) {
console.error("导出失败:", error);
Taro.showToast({
title: "导出失败",
icon: "error",
});
}
};
/**
* Excel导出函数
* @param data
* @param fileName
* @param sheetName
* @param colWidths
*/
export const exportExcel = async (
data: any[][],
fileName: string,
sheetName: string = "Sheet1",
colWidths?: Array<{ wch: number }>,
): Promise<void> => {
try {
// 创建工作簿
const wb = utils.book_new();
// 创建工作表
const ws = utils.aoa_to_sheet(data);
// 设置列宽(如果提供)
if (colWidths) {
ws["!cols"] = colWidths;
}
// 将工作表添加到工作簿
utils.book_append_sheet(wb, ws, sheetName);
// 根据环境选择不同的导出方式
const processEnv = process.env.TARO_ENV;
if (processEnv === "h5") {
// H5环境创建下载链接
const excelBuffer = write(wb, { type: "array", bookType: "xlsx" });
const blob = new Blob([excelBuffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
// 创建下载链接
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName.endsWith(".xlsx")
? fileName
: `${fileName}.xlsx`;
link.style.display = "none";
// 触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理URL对象
window.URL.revokeObjectURL(url);
await Taro.showToast({
title: "导出成功",
icon: "success",
});
} else {
// 小程序环境使用文件系统API
const excelBuffer = write(wb, { type: "array", bookType: "xlsx" });
// 确保文件名有.xlsx后缀
const finalFileName = fileName.endsWith(".xlsx")
? fileName
: `${fileName}.xlsx`;
// 将文件保存到本地
const filePath = `${Taro.env.USER_DATA_PATH}/${finalFileName}`;
Taro.getFileSystemManager().writeFile({
filePath,
data: new Uint8Array(excelBuffer),
encoding: "binary",
success() {
console.log("文件保存成功");
// 分享文件
Taro.shareFileMessage({
filePath,
});
Taro.showToast({
title: "导出成功",
icon: "success",
});
},
fail(error) {
console.error("文件保存失败:", error);
Taro.showToast({
title: "导出失败",
icon: "error",
});
},
});
}
} catch (error) {
console.error("导出失败:", error);
Taro.showToast({
title: "导出失败",
icon: "error",
});
}
};

View File

@ -49,3 +49,6 @@ export * from "./reporter";
// 路由相关
export * from "./routeGuard";
// Excel导出相关
export * from "./exportExcel";

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long