From b53d26ec65b355c35267cffb8d550c1073573ef5 Mon Sep 17 00:00:00 2001 From: shenyifei Date: Tue, 6 Jan 2026 11:10:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(components):=20=E6=B7=BB=E5=8A=A0=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=95=86=E7=93=9C=E5=86=9C=E7=BB=84=E4=BB=B6=E5=92=8C?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E4=BB=BB=E5=8A=A1=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 SupplierFarmerList 和 SupplierStallList 组件 - 添加 PaymentTaskList、PaymentTaskCreate、InvoicerStatisticCard 等支付任务相关组件 - 将 MelonFarmerList 重命名为 SupplierFarmerList 并移至 Supplier 目录 - 在 BizContainer 中添加 supplier 类型渲染支持 - 为 BizDetail 组件添加 formContextMode 属性和渲染逻辑 - 添加供应商类型到 BizValueType 类型定义中 - 添加开票方统计卡片和订单供应商发票列表组件 - 更新组件导出配置和本地化文件 --- .../src/components/BasicData/index.ts | 7 +- .../src/components/Biz/BizContainer.tsx | 25 +- .../src/components/Biz/BizDetail.tsx | 130 +- .../src/components/Biz/typing.ts | 8 +- .../components/Order/OrderSupplierModal.tsx | 402 + .../PaymentTask/InvoicerStatisticCard.tsx | 38 + .../PaymentTask/OrderSupplierInvoiceList.tsx | 84 + .../PaymentTask/PaymentTaskCreate.tsx | 459 + .../PaymentTask/PaymentTaskList.tsx | 505 + .../src/components/PaymentTask/index.ts | 4 + .../SupplierFarmerList.tsx} | 35 +- .../components/Supplier/SupplierStallList.tsx | 309 + .../src/components/Supplier/index.ts | 2 + .../app-operation/src/components/index.ts | 6 +- packages/app-operation/src/locales/zh-CN.ts | 373 +- .../{PaymentCost.tsx => CostExpenses.tsx} | 0 ...{PaymentSupplier.tsx => FarmerPayment.tsx} | 0 .../src/pages/FarmerPaymentTask.tsx | 5 + .../app-operation/src/pages/MelonFarmer.tsx | 5 - .../{PaymentRebate.tsx => RebateExpenses.tsx} | 0 .../{PaymentStall.tsx => StallPayment.tsx} | 0 .../src/pages/SupplierFarmer.tsx | 5 + .../app-operation/src/pages/SupplierStall.tsx | 5 + .../src/services/business/index.ts | 4 + .../src/services/business/orderSupplier.ts | 15 - .../src/services/business/paymentTask.ts | 152 + .../src/services/business/supplierInvoice.ts | 132 + .../src/services/business/typings.d.ts | 498 +- .../utils/calculateOrderSupplierStatistics.ts | 135 + pnpm-lock.yaml | 32107 +++++++++------- swagger/business.json | 2 +- 31 files changed, 21342 insertions(+), 14110 deletions(-) create mode 100644 packages/app-operation/src/components/Order/OrderSupplierModal.tsx create mode 100644 packages/app-operation/src/components/PaymentTask/InvoicerStatisticCard.tsx create mode 100644 packages/app-operation/src/components/PaymentTask/OrderSupplierInvoiceList.tsx create mode 100644 packages/app-operation/src/components/PaymentTask/PaymentTaskCreate.tsx create mode 100644 packages/app-operation/src/components/PaymentTask/PaymentTaskList.tsx create mode 100644 packages/app-operation/src/components/PaymentTask/index.ts rename packages/app-operation/src/components/{BasicData/MelonFarmerList.tsx => Supplier/SupplierFarmerList.tsx} (90%) create mode 100644 packages/app-operation/src/components/Supplier/SupplierStallList.tsx create mode 100644 packages/app-operation/src/components/Supplier/index.ts rename packages/app-operation/src/pages/{PaymentCost.tsx => CostExpenses.tsx} (100%) rename packages/app-operation/src/pages/{PaymentSupplier.tsx => FarmerPayment.tsx} (100%) create mode 100644 packages/app-operation/src/pages/FarmerPaymentTask.tsx delete mode 100644 packages/app-operation/src/pages/MelonFarmer.tsx rename packages/app-operation/src/pages/{PaymentRebate.tsx => RebateExpenses.tsx} (100%) rename packages/app-operation/src/pages/{PaymentStall.tsx => StallPayment.tsx} (100%) create mode 100644 packages/app-operation/src/pages/SupplierFarmer.tsx create mode 100644 packages/app-operation/src/pages/SupplierStall.tsx create mode 100644 packages/app-operation/src/services/business/paymentTask.ts create mode 100644 packages/app-operation/src/services/business/supplierInvoice.ts create mode 100644 packages/app-operation/src/utils/calculateOrderSupplierStatistics.ts diff --git a/packages/app-operation/src/components/BasicData/index.ts b/packages/app-operation/src/components/BasicData/index.ts index 3bf8a65..e440c31 100644 --- a/packages/app-operation/src/components/BasicData/index.ts +++ b/packages/app-operation/src/components/BasicData/index.ts @@ -1,7 +1,6 @@ -export { default as MelonFarmerList } from './MelonFarmerList'; -export { default as BoxProductList } from './BoxProductList' export { default as BoxBrandList } from './BoxBrandList'; -export { default as CostList } from './CostList'; -export { default as CostItemList } from './CostItemList'; +export { default as BoxProductList } from './BoxProductList'; export { default as BoxSpecList } from './BoxSpecList'; +export { default as CostItemList } from './CostItemList'; +export { default as CostList } from './CostList'; export { default as ProductDataList } from './ProductDataList'; diff --git a/packages/app-operation/src/components/Biz/BizContainer.tsx b/packages/app-operation/src/components/Biz/BizContainer.tsx index 796c920..c81dc44 100644 --- a/packages/app-operation/src/components/Biz/BizContainer.tsx +++ b/packages/app-operation/src/components/Biz/BizContainer.tsx @@ -15,12 +15,13 @@ import { BizValueType, DealerFormItem, DealerList, - PageContainer, OrderFormItem, OrderList, + PageContainer, Remark, RemarkFormItem, SmartActionBar, + SupplierFarmerList, UserFormItem, UserList, } from '@/components'; @@ -847,11 +848,27 @@ export default function BizContainer< ); }, }, + supplier: { + render: (supplierVO: BusinessAPI.SupplierVO) => { + return ( + supplierVO && ( + ( + + {supplierVO.name} + + )} + /> + ) + ); + }, + }, order: { renderFormItem: (_, props) => { - return ( - - ); + return ; }, render: (orderVO: BusinessAPI.OrderVO) => { return ( diff --git a/packages/app-operation/src/components/Biz/BizDetail.tsx b/packages/app-operation/src/components/Biz/BizDetail.tsx index c38e740..bfb84bc 100644 --- a/packages/app-operation/src/components/Biz/BizDetail.tsx +++ b/packages/app-operation/src/components/Biz/BizDetail.tsx @@ -27,6 +27,7 @@ export default function BizDetail< methodUpper, func, formContext, + formContextMode = 'tab', trigger, fieldProps, initValues, @@ -40,6 +41,63 @@ export default function BizDetail< const intl = useIntl(); + /** 渲染 formContext 内容 */ + const renderFormContext = (data: any, form: any, colSpan: number) => { + if (!formContext) return null; + + const contextItems = formContext(data?.[`${method}VO`], async () => { + const { data } = await func?.[`show${methodUpper}`]({ + [`${method}ShowQry`]: { + [`${rowKey}`]: rowId, + }, + }); + form.setFieldValue(`${method}VO`, data); + })?.filter((item) => { + // @ts-ignore + return !item.display || item.display(data?.[`${method}VO`]); + }); + + // 平铺模式:直接垂直堆叠所有内容 + if (formContextMode === 'flat') { + return ( + + {contextItems?.map((item, index) => ( + + {item.children} + + ))} + + ); + } + + // Tab 模式:使用 tabs 渲染 + return ( + { + form.setFieldValue('activeKey', key); + }, + }} + /> + ); + }; + let rest = { title: intl.formatMessage({ id: intlPrefix + '.modal.view.title', @@ -115,40 +173,7 @@ export default function BizDetail< column={column(data.isMobile, formContext)} /> - {formContext && ( - { - const { data } = await func?.[ - `show${methodUpper}` - ]({ - [`${method}ShowQry`]: { - [`${rowKey}`]: rowId, - }, - }); - - form.setFieldValue(`${method}VO`, data); - }, - )?.filter((item) => { - //@ts-ignore - return ( - !item.display || - item.display(data?.[`${method}VO`]) - ); - }), - onChange: (key) => { - form.setFieldValue('activeKey', key); - }, - }} - /> - )} + {renderFormContext(data, form, data.isMobile ? 24 : 18)} ); }} @@ -179,7 +204,7 @@ export default function BizDetail< {...rest} > {columns && ( - + {(data, form) => { return ( @@ -202,40 +227,7 @@ export default function BizDetail< column={column(data.isMobile, formContext)} /> - {formContext && ( - { - const { data } = await func?.[ - `show${methodUpper}` - ]({ - [`${method}ShowQry`]: { - [`${rowKey}`]: rowId, - }, - }); - - form.setFieldValue(`${method}VO`, data); - }, - )?.filter((item) => { - //@ts-ignore - return ( - !item.display || - item.display(data?.[`${method}VO`]) - ); - }), - onChange: (key) => { - form.setFieldValue('activeKey', key); - }, - }} - /> - )} + {renderFormContext(data, form, data.isMobile ? 24 : 18)} ); }} diff --git a/packages/app-operation/src/components/Biz/typing.ts b/packages/app-operation/src/components/Biz/typing.ts index e2024f0..de42523 100644 --- a/packages/app-operation/src/components/Biz/typing.ts +++ b/packages/app-operation/src/components/Biz/typing.ts @@ -9,11 +9,11 @@ import { ProDescriptionsItemProps, ProTableProps, } from '@ant-design/pro-components'; +import { ProListMetas, ProListProps } from '@ant-design/pro-list'; import { TourStepProps } from 'antd'; import { TabsProps } from 'antd/es/tabs'; import { Dayjs } from 'dayjs'; import React, { MutableRefObject } from 'react'; -import { ProListMetas, ProListProps } from '@ant-design/pro-list'; export type BizValueType = | 'user' @@ -21,7 +21,8 @@ export type BizValueType = | 'status' | 'remark' | 'order' - | 'dealer'; + | 'dealer' + | 'supplier'; export type FormType = 'modal' | 'drawer' | 'step'; export type ModeType = | 'tree' @@ -176,6 +177,8 @@ export interface BizCalenderProps< trigger?: () => React.ReactNode; } +export type FormContextMode = 'tab' | 'flat'; + export type BizDetailProps< BizVO extends Record, Func extends Record, @@ -187,6 +190,7 @@ export type BizDetailProps< record: BizVO, onValueChange: () => void, ) => TabsProps['items']; + formContextMode?: FormContextMode; initValues?: (rowId: string) => Record; onFinish?: (record: BizVO) => void; trigger?: (record?: BizVO) => React.ReactNode; diff --git a/packages/app-operation/src/components/Order/OrderSupplierModal.tsx b/packages/app-operation/src/components/Order/OrderSupplierModal.tsx new file mode 100644 index 0000000..1c77d45 --- /dev/null +++ b/packages/app-operation/src/components/Order/OrderSupplierModal.tsx @@ -0,0 +1,402 @@ +import { OrderList } from '@/components'; +import { business } from '@/services'; +import { formatParam } from '@/utils/formatParam'; +import { pagination } from '@/utils/pagination'; +import { useIntl } from '@@/exports'; +import { + ActionType, + LightFilter, + ProColumns, + ProFormSelect, +} from '@ant-design/pro-components'; +import { SelectModal } from '@chageable/components'; +import { Alert, ModalProps, Row, Space, Tag } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; + +export interface IOrderSupplierModalProps extends ModalProps { + title: string; + selectedList?: BusinessAPI.OrderSupplierVO[]; + onFinish: (orderSupplierVOList: BusinessAPI.OrderSupplierVO[]) => void; + type: 'checkbox' | 'radio' | undefined; + params?: BusinessAPI.OrderSupplierPageQry; + num?: number; + tips?: string; + extraFilter?: React.ReactNode[]; + extraColumns?: ProColumns[]; +} + +export default function OrderSupplierModal(props: IOrderSupplierModalProps) { + const { + title, + onFinish, + type, + selectedList, + params: initParams, + num = 10, + tips, + extraFilter = [], + extraColumns: initExtraColumns = [], + ...rest + } = props; + const actionRef = useRef(); + const sessionKey = `orderSupplierList`; + const intl = useIntl(); + const intlPrefix = 'orderSupplier'; + const [params, setParams] = useState( + initParams || {}, + ); + + useEffect(() => { + if (initParams) { + setParams({ + ...params, + ...initParams, + }); + } + }, [initParams]); + + const columns: ProColumns[] = [ + { + title: intl.formatMessage({ id: intlPrefix + '.column.order' }), + dataIndex: ['orderVO', 'orderSn'], + key: 'orderSn', + search: false, + render: (_, orderSupplierVO: BusinessAPI.OrderSupplierVO) => { + const orderVO = orderSupplierVO.orderVO; + return ( + orderVO && ( + ( + + + {`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`} + + + )} + /> + ) + ); + }, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.name' }), + dataIndex: 'name', + key: 'name', + renderText: (text: string) => {text}, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.idCard' }), + dataIndex: 'idCard', + key: 'idCard', + search: false, + ellipsis: true, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.phone' }), + dataIndex: 'phone', + key: 'phone', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.bankName' }), + dataIndex: 'bankName', + key: 'bankName', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.bankCard' }), + dataIndex: 'bankCard', + key: 'bankCard', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.invoiceAmount' }), + dataIndex: 'invoiceAmount', + key: 'invoiceAmount', + valueType: 'money', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.depositAmount' }), + dataIndex: 'depositAmount', + key: 'depositAmount', + valueType: 'money', + search: false, + }, + { + title: intl.formatMessage({ + id: intlPrefix + '.column.remainingAmount', + }), + dataIndex: 'remainingAmount', + key: 'remainingAmount', + valueType: 'money', + search: false, + renderText: (_, record) => { + return record.invoiceAmount - record.depositAmount || 0; + }, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.orderCompany' }), + dataIndex: ['orderCompany', 'shortName'], + key: 'orderCompany', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.isPaid' }), + dataIndex: 'isPaid', + key: 'isPaid', + valueType: 'select', + valueEnum: { + true: { + text: intl.formatMessage({ id: intlPrefix + '.column.isPaid.paid' }), + status: 'Success', + }, + false: { + text: intl.formatMessage({ + id: intlPrefix + '.column.isPaid.unpaid', + }), + status: 'Default', + }, + }, + }, + // { + // title: intl.formatMessage({ id: intlPrefix + '.column.type' }), + // dataIndex: 'type', + // key: 'type', + // valueType: 'select', + // valueEnum: { + // FARMER: { text: intl.formatMessage({ id: intlPrefix + '.column.type.farmer' }), status: 'Default' }, + // STALL: { text: intl.formatMessage({ id: intlPrefix + '.column.type.stall' }), status: 'Processing' }, + // OTHER_STALL: { text: intl.formatMessage({ id: intlPrefix + '.column.type.otherStall' }), status: 'Warning' }, + // }, + // }, + ...(initExtraColumns || []), + ]; + + function setOrderSupplierVOStorage( + orderSupplierVO: BusinessAPI.OrderSupplierVO, + ) { + const localOrderSupplierList = localStorage.getItem(sessionKey); + const orderSupplierList = localOrderSupplierList + ? JSON.parse(localOrderSupplierList) + : []; + orderSupplierList.forEach( + (item: BusinessAPI.OrderSupplierVO, index: number) => { + if (item.orderSupplierId === orderSupplierVO.orderSupplierId) { + orderSupplierList.splice(index, 1); + } + }, + ); + if (orderSupplierList.length < 5) { + orderSupplierList.unshift(orderSupplierVO); + localStorage.setItem(sessionKey, JSON.stringify(orderSupplierList)); + } else { + orderSupplierList.pop(); + orderSupplierList.unshift(orderSupplierVO); + localStorage.setItem(sessionKey, JSON.stringify(orderSupplierList)); + } + } + + return ( + + rowKey={'orderSupplierId'} + modalProps={{ + title: title || '选择瓜农订单', + ...rest, + destroyOnHidden: true, + afterOpenChange: (open) => { + if (!open) { + setParams({ + ...initParams, + }); + } + }, + }} + selectedList={selectedList} + tableProps={{ + rowKey: 'orderSupplierId', + columns: columns, + columnsState: { + persistenceType: 'sessionStorage', + persistenceKey: 'orderSupplierModalColumnStateKey', + }, + params: { + ...params, + }, + request: async (params, sorter, filter) => { + const { data, success, totalCount } = + await business.orderSupplier.pageOrderSupplier({ + orderSupplierPageQry: formatParam( + params, + sorter, + filter, + ), + }); + + return { + data: data || [], + total: totalCount, + success, + }; + }, + pagination: { + ...pagination(), + position: ['bottomRight'], + }, + tableAlertRender: ({ selectedRowKeys, selectedRows }) => { + const selectedRowsMap = new Map< + string, + BusinessAPI.OrderSupplierVO + >(); + selectedRows.forEach((item: BusinessAPI.OrderSupplierVO) => { + if (item) { + if (!selectedRowsMap.has(item.orderSupplierId)) { + selectedRowsMap.set(item.orderSupplierId, item); + } + } + }); + selectedList?.forEach((item: BusinessAPI.OrderSupplierVO) => { + if (!selectedRowsMap.has(item.orderSupplierId)) { + selectedRowsMap.set(item.orderSupplierId, item); + } + }); + let selectedTempList: BusinessAPI.OrderSupplierVO[] = []; + selectedRowsMap.forEach((item: BusinessAPI.OrderSupplierVO) => { + if (selectedRowKeys.includes(item.orderSupplierId)) { + selectedTempList.push(item); + } + }); + return ( + + 已选 {selectedRowKeys.length} 项 + + {selectedTempList?.map((item: BusinessAPI.OrderSupplierVO) => { + return ( + item && {item.name} + ); + })} + + + ); + }, + ...(tips && { + tableExtraRender: () => { + return tips && ; + }, + }), + ...(type === 'radio' && { + tableExtraRender: () => { + const localOrderSupplierList = localStorage.getItem(sessionKey); + if (localOrderSupplierList) { + const orderSupplierList = JSON.parse(localOrderSupplierList); + return ( + <> + {tips && } + + {orderSupplierList.map( + (item: BusinessAPI.OrderSupplierVO) => { + return ( + { + // 直接使用 localStorage 中保存的数据 + onFinish([item]); + setOrderSupplierVOStorage(item); + }} + key={item.orderSupplierId} + > + {item.name} + + ); + }, + )} + + + ); + } + }, + }), + actionRef: actionRef, + toolbar: { + filter: ( + { + setParams({ + ...initParams, + ...values, + }); + }} + > + {extraFilter} + {/**/} + + + ), + search: { + placeholder: '请输入供应商姓名', + onSearch: async (value: string) => { + setParams({ + ...params, + //@ts-ignore + name: value, + }); + }, + }, + }, + }} + onFinish={(orderSupplierVOList) => { + if (type === 'radio') { + if (orderSupplierVOList.length > 0) { + setOrderSupplierVOStorage(orderSupplierVOList[0]); + } + } + + onFinish(orderSupplierVOList); + }} + num={num} + type={type} + /> + ); +} diff --git a/packages/app-operation/src/components/PaymentTask/InvoicerStatisticCard.tsx b/packages/app-operation/src/components/PaymentTask/InvoicerStatisticCard.tsx new file mode 100644 index 0000000..4583f2f --- /dev/null +++ b/packages/app-operation/src/components/PaymentTask/InvoicerStatisticCard.tsx @@ -0,0 +1,38 @@ +import { formatCurrency } from '@/utils/format'; +import { ProCard } from '@ant-design/pro-components'; + +type IInvoicerStatisticCardProps = { + /** 开票方统计 */ + title: string; + /** 车次 */ + count: number; + /** 金额 */ + amount: number; + /** 颜色 */ + color: string; +}; +/** 开票方统计卡片组件 */ +export default function InvoicerStatisticCard( + props: IInvoicerStatisticCardProps, +) { + const { title, count, amount, color } = props; + return ( + +
+ {title} +
+
+
+
车次
+
{count}
+
+
+
开票金额
+
+ {formatCurrency(amount)} +
+
+
+
+ ); +} diff --git a/packages/app-operation/src/components/PaymentTask/OrderSupplierInvoiceList.tsx b/packages/app-operation/src/components/PaymentTask/OrderSupplierInvoiceList.tsx new file mode 100644 index 0000000..526faf3 --- /dev/null +++ b/packages/app-operation/src/components/PaymentTask/OrderSupplierInvoiceList.tsx @@ -0,0 +1,84 @@ +import { OrderList, SupplierFarmerList } from '@/components'; +import { Space, Table, TableProps } from 'antd'; + +type IOrderSupplierInvoiceListProps = TableProps; + +export default function OrderSupplierInvoiceList( + props: IOrderSupplierInvoiceListProps, +) { + const { ...tableProps } = props; + // 订单列表列定义 + const orderColumns = [ + { + title: '采购单', + dataIndex: 'orderVO', + key: 'orderId', + render: (orderVO: BusinessAPI.OrderVO) => { + return ( + orderVO && ( + ( + + + {`${orderVO.orderVehicle?.dealerName} - 第 ${orderVO.orderVehicle?.vehicleNo || '暂无'} 车 - ${orderVO.orderSn || '暂无'}`} + + + )} + /> + ) + ); + }, + }, + { + title: '瓜农名称', + key: 'name', + render: (orderSupplierVO: BusinessAPI.OrderSupplierVO) => { + return ( + orderSupplierVO && ( + {orderSupplierVO.name}} + /> + ) + ); + }, + }, + { + title: '货款金额(元)', + dataIndex: 'invoiceAmount', + key: 'invoiceAmount', + valueType: 'money', + }, + { + title: '定金金额(元)', + dataIndex: 'depositAmount', + key: 'depositAmount', + valueType: 'money', + }, + { + title: '应付金额(元)', + key: 'remainingAmount', + valueType: 'money', + search: false, + render: (record: BusinessAPI.OrderSupplierVO) => { + return record.invoiceAmount - record.depositAmount || 0; + }, + }, + ...(tableProps.columns || []), + ]; + + return ( + + ); +} diff --git a/packages/app-operation/src/components/PaymentTask/PaymentTaskCreate.tsx b/packages/app-operation/src/components/PaymentTask/PaymentTaskCreate.tsx new file mode 100644 index 0000000..7437eda --- /dev/null +++ b/packages/app-operation/src/components/PaymentTask/PaymentTaskCreate.tsx @@ -0,0 +1,459 @@ +import { + BizEditor, + ButtonAccess, + InsertPosition, + OrderSupplierInvoiceList, +} from '@/components'; +import OrderSupplierModal from '@/components/Order/OrderSupplierModal'; +import { business } from '@/services'; +import { formatCurrency } from '@/utils/format'; +import { formLayout } from '@/utils/formLayout'; +import { useIntl } from '@@/exports'; +import { PlusOutlined } from '@ant-design/icons'; +import { + DrawerForm, + ProCard, + ProFormDependency, + ProFormItem, + ProFormSelect, + ProFormText, + RouteContext, + RouteContextType, +} from '@ant-design/pro-components'; +import { Button, Col, Image, message, Row, Table, Tag } from 'antd'; +import { useState } from 'react'; + +export interface IPaymentTaskCreateProps { + onFinish: () => void; + insertPosition?: InsertPosition; +} + +export default function PaymentTaskCreate(props: IPaymentTaskCreateProps) { + const { onFinish } = props; + const intl = useIntl(); + const intlPrefix = 'paymentTask'; + const [selectedSupplier, setSelectedSupplier] = + useState(null); + const [selectedOrderSupplierList, setSelectedOrderSupplierList] = useState< + BusinessAPI.OrderSupplierVO[] + >([]); + const [orderSupplierModalOpen, setOrderSupplierModalOpen] = useState(false); + + // 计算总金额和订单数量 + const totalAmount = selectedOrderSupplierList.reduce( + (sum, order) => + sum + (order.invoiceAmount || 0) - (order.depositAmount || 0), + 0, + ); + const orderCount = selectedOrderSupplierList.length; + + const handleReset = () => { + setSelectedSupplier(null); + setSelectedOrderSupplierList([]); + }; + console.log(selectedOrderSupplierList); + + const handleSubmit = async (formData: any) => { + if (!selectedSupplier) { + message.error('请选择付款瓜农'); + return false; + } + + if (selectedOrderSupplierList.length === 0) { + message.error('请选择付款车次'); + return false; + } + + // 构建付款任务数据 + const paymentTaskData: BusinessAPI.PaymentTaskCreateCmd = { + taskName: formData.taskName, + taskType: 'MELON_FARMER', // 瓜农付款任务 + paymentCode: `PAY-${Date.now()}`, // 生成付款编码 + targetId: selectedSupplier.supplierId, + totalAmount: totalAmount, + paidAmount: 0, + state: 'PENDING', // 待付款任务 + orderCount: orderCount, + remark: formData.remark, + orderSupplierVOList: selectedOrderSupplierList, + }; + + const { success } = + await business.paymentTask.createPaymentTask(paymentTaskData); + + if (success) { + message.success( + intl.formatMessage({ + id: intlPrefix + '.modal.create.success', + }), + ); + handleReset(); + onFinish(); + } + + return success; + }; + + // 获取瓜农列表 + const loadSupplierList = async () => { + const { data } = await business.supplier.listSupplier({ + supplierListQry: { + type: 'FARMER', + status: true, + }, + }); + return (data || []).map((supplier: BusinessAPI.SupplierVO) => ({ + label: supplier.name, + value: supplier.supplierId, + ...supplier, + })); + }; + + return ( + <> + + {(value: RouteContextType) => { + const { isMobile } = value; + return ( + + title={intl.formatMessage({ + id: intlPrefix + '.modal.create.title', + })} + {...formLayout(isMobile)} + width={isMobile ? '100%' : '85%'} + drawerProps={{ + destroyOnHidden: true, + onClose: handleReset, + }} + trigger={ + } + > + {intl.formatMessage({ + id: intlPrefix + '.modal.create.button', + })} + + } + onFinish={handleSubmit} + > + {/* 选择瓜农 */} + { + setSelectedSupplier(value); + // 切换瓜农时清空已选订单 + setSelectedOrderSupplierList([]); + }} + /> + + {/* 显示瓜农信息 */} + {selectedSupplier && ( + + + + +
+
+ 姓名 +
+
+ {selectedSupplier.name} +
+
+ + +
+
+ 手机号 +
+
+ {selectedSupplier.phone} +
+
+ + +
+
+ 银行卡号 +
+
+ {selectedSupplier.bankCard} +
+
+ + +
+
+ 微信收款码 +
+
+ {selectedSupplier.wechatQr ? ( + + ) : ( +
暂无
+ )} +
+
+ + + + + )} + + {/* 选择车次 */} + {selectedSupplier && ( + + setOrderSupplierModalOpen(true)} + > + 添加车次 + + } + style={{ marginBottom: 16 }} + > + {selectedOrderSupplierList.length > 0 ? ( + ( + + ), + }, + ]} + pagination={false} + size="small" + summary={(pageData) => { + let totalAmount = 0; + pageData.forEach( + ({ invoiceAmount, depositAmount }) => { + totalAmount += + (invoiceAmount || 0) - (depositAmount || 0); + }, + ); + return ( + + + + 合计 + + + + {formatCurrency(totalAmount)} + + + + + + ); + }} + /> + ) : ( +
+ 暂无数据,请点击「添加车次」按钮添加 +
+ )} +
+
+ )} + + {/* 任务信息 */} + {selectedOrderSupplierList.length > 0 && ( + <> + + {/* 任务摘要 */} + + + +
+
+ 付款瓜农 +
+
+ {selectedSupplier?.name} +
+
+ + +
+
+ 车次数量 +
+
+ {orderCount} 车 +
+
+ + +
+
+ 付款总额 +
+
+ {formatCurrency(totalAmount)} +
+
+ + +
+
+ 付款状态 +
+
+ 待付款 +
+
+ + + + + + {/* 任务名称和备注 */} + + + {({ remark }, form) => { + return ( + { + form.setFieldValue('remark', value); + }} + label={intl.formatMessage({ + id: intlPrefix + '.form.remark.label', + })} + name={'remark'} + placeholder={intl.formatMessage({ + id: intlPrefix + '.form.remark.placeholder', + })} + /> + ); + }} + + + )} + + ); + }} + + + {/* 选择车次模态框 */} + { + // 过滤掉已经选择的订单 + const newOrders = orderSupplierList.filter( + (item) => + !selectedOrderSupplierList.some( + (selected) => selected.orderSupplierId === item.orderSupplierId, + ), + ); + setSelectedOrderSupplierList([ + ...selectedOrderSupplierList, + ...newOrders, + ]); + setOrderSupplierModalOpen(false); + }} + onCancel={() => setOrderSupplierModalOpen(false)} + params={{ + type: 'FARMER', + isPaid: false, + }} + tips="请选择该瓜农未付款的车次" + num={999} + /> + + ); +} diff --git a/packages/app-operation/src/components/PaymentTask/PaymentTaskList.tsx b/packages/app-operation/src/components/PaymentTask/PaymentTaskList.tsx new file mode 100644 index 0000000..3984082 --- /dev/null +++ b/packages/app-operation/src/components/PaymentTask/PaymentTaskList.tsx @@ -0,0 +1,505 @@ +import { + BizContainer, + BizValueType, + InvoicerStatisticCard, + ModeType, + OrderSupplierInvoiceList, + PaymentTaskCreate, +} from '@/components'; +import { business } from '@/services'; +import { calculateOrderSupplierStatistics } from '@/utils/calculateOrderSupplierStatistics'; +import { formatCurrency } from '@/utils/format'; +import { useIntl } from '@@/exports'; +import { + ActionType, + ProColumns, + StatisticCard, +} from '@ant-design/pro-components'; +import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions'; +import { Image } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; + +interface IPaymentTaskListProps { + ghost?: boolean; + paymentTaskId?: BusinessAPI.PaymentTaskVO['paymentTaskId']; + search?: boolean; + onValueChange?: () => void; + mode?: ModeType; + trigger?: () => React.ReactNode; +} + +export default function PaymentTaskList(props: IPaymentTaskListProps) { + const { + ghost = false, + paymentTaskId, + search = true, + mode = 'page', + trigger, + onValueChange, + } = props; + const intl = useIntl(); + const intlPrefix = 'paymentTask'; + const actionRef = useRef(); + + const [activeKey, setActiveKey] = useState('PENDING'); + const [statisticsPaymentTask, setStatisticsPaymentTask] = + useState(); + const [loading, setLoading] = useState(true); + + const initStatistic = async () => { + const { data } = await business.paymentTask.statisticsPaymentTask({ + statisticsQry: {}, + }); + setStatisticsPaymentTask(data); + setLoading(false); + }; + useEffect(() => { + initStatistic().then(); + }, []); + + /** 状态文本映射 */ + const stateMap: Record = { + PENDING: { + text: intl.formatMessage({ id: intlPrefix + '.state.pending' }), + color: 'orange', + }, + PARTIAL: { + text: intl.formatMessage({ id: intlPrefix + '.state.partial' }), + color: 'blue', + }, + COMPLETED: { + text: intl.formatMessage({ id: intlPrefix + '.state.completed' }), + color: 'green', + }, + CANCELLED: { + text: intl.formatMessage({ id: intlPrefix + '.state.cancelled' }), + color: 'red', + }, + }; + + const columns: ProColumns[] = [ + { + title: intl.formatMessage({ id: intlPrefix + '.column.paymentCode' }), + dataIndex: 'paymentCode', + key: 'paymentCode', + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.taskName' }), + dataIndex: 'taskName', + key: 'taskName', + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.supplier' }), + dataIndex: 'supplierVO', + key: 'supplier', + valueType: 'supplier', + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.totalAmount' }), + dataIndex: 'totalAmount', + key: 'totalAmount', + valueType: 'money', + search: false, + renderText: (value: number) => formatCurrency(value), + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.paidAmount' }), + dataIndex: 'paidAmount', + key: 'paidAmount', + valueType: 'money', + search: false, + renderText: (value: number) => formatCurrency(value), + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.unpaidAmount' }), + key: 'unpaidAmount', + search: false, + render: (_, record) => { + const unpaid = (record.totalAmount || 0) - (record.paidAmount || 0); + return {formatCurrency(unpaid)}; + }, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.orderCount' }), + dataIndex: 'orderCount', + key: 'orderCount', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.state' }), + dataIndex: 'state', + key: 'state', + valueType: 'select', + valueEnum: { + PENDING: { text: stateMap.PENDING.text, status: 'Warning' }, + PARTIAL: { text: stateMap.PARTIAL.text, status: 'Processing' }, + COMPLETED: { text: stateMap.COMPLETED.text, status: 'Success' }, + CANCELLED: { text: stateMap.CANCELLED.text, status: 'Error' }, + }, + search: false, + render: (_, record) => { + const stateInfo = stateMap[record.state] || stateMap.PENDING; + return {stateInfo.text}; + }, + }, + ]; + + const detailColumns: ProDescriptionsItemProps< + BusinessAPI.PaymentTaskVO, + BizValueType + >[] = columns as ProDescriptionsItemProps< + BusinessAPI.PaymentTaskVO, + BizValueType + >[]; + + const detailContext = (paymentTaskVO: BusinessAPI.PaymentTaskVO) => { + // 统计车次数据 + const statistics = calculateOrderSupplierStatistics( + paymentTaskVO.orderSupplierVOList, + ); + + return [ + // 统计卡片 Tab + { + label: intl.formatMessage({ + id: intlPrefix + '.detail.tab.statistics', + }), + key: 'statistics', + children: ( +
+ + + +
+ ), + }, + // 开票方统计 Tab + ...(statistics.invoicerStatistics.length > 0 + ? [ + { + label: intl.formatMessage({ + id: intlPrefix + '.detail.tab.invoicerStatistics', + }), + key: 'invoicerStatistics', + children: ( +
+ {statistics.invoicerStatistics.map((stat, index) => ( + + ))} +
+ ), + }, + ] + : []), + // 自开车次列表 Tab + ...(statistics.selfInvoiceList.length > 0 + ? [ + { + label: intl.formatMessage( + { id: intlPrefix + '.detail.tab.selfInvoiceList' }, + { count: statistics.selfInvoiceCount }, + ), + key: 'selfInvoiceList', + subTitle: intl.formatMessage( + { id: intlPrefix + '.detail.tab.selfInvoiceList.subTitle' }, + { count: statistics.selfInvoiceCount }, + ), + children: ( + { + return {supplierInvoiceVO.invoiceSn}; + }, + }, + ]} + pagination={false} + size="small" + /> + ), + }, + ] + : []), + // 代开车次列表 Tab + ...(statistics.proxyInvoiceList.length > 0 + ? [ + { + label: intl.formatMessage( + { id: intlPrefix + '.detail.tab.proxyInvoiceList' }, + { count: statistics.proxyInvoiceCount }, + ), + key: 'proxyInvoiceList', + subTitle: intl.formatMessage( + { id: intlPrefix + '.detail.tab.proxyInvoiceList.subTitle' }, + { count: statistics.proxyInvoiceCount }, + ), + children: ( + + ), + }, + ] + : []), + // 无发票车次列表 Tab + ...(statistics.noInvoiceList.length > 0 + ? [ + { + label: intl.formatMessage( + { id: intlPrefix + '.detail.tab.noInvoiceList' }, + { count: statistics.noInvoiceCount }, + ), + key: 'noInvoiceList', + subTitle: intl.formatMessage( + { id: intlPrefix + '.detail.tab.noInvoiceList.subTitle' }, + { count: statistics.noInvoiceCount }, + ), + children: ( + { + if (!wechatQr) { + return '暂无微信收款码'; + } + return ( + + ); + }, + }, + ]} + dataSource={statistics.noInvoiceList} + pagination={false} + size="small" + /> + ), + }, + ] + : []), + ]; + }; + + return ( + + rowKey={'paymentTaskId'} + permission={'operation-payment-task'} + func={business.paymentTask} + method={'paymentTask'} + methodUpper={'PaymentTask'} + intlPrefix={intlPrefix} + modeType={mode} + onValueChange={onValueChange} + container={{ + fieldProps: { + ghost, + loading, + }, + }} + remark={{ + mode: 'editor', + }} + page={{ + fieldProps: { + bordered: true, + ...(!ghost && { + tableExtraRender: () => ( + + + = + + + + + + + + + + + + ), + }), + ghost, + //@ts-ignore + search, + params: { + ...(activeKey !== 'ALL' && { + state: activeKey as BusinessAPI.PaymentTaskVO['state'], + }), + taskType: 'MELON_FARMER', + }, + toolbar: { + menu: { + type: 'tab', + activeKey: activeKey, + items: [ + { + key: 'ALL', + label: intl.formatMessage({ + id: intlPrefix + '.tab.all', + }), + }, + { + key: 'PENDING', + label: intl.formatMessage({ + id: intlPrefix + '.tab.pending', + }), + }, + { + key: 'PARTIAL', + label: intl.formatMessage({ + id: intlPrefix + '.tab.partial', + }), + }, + { + key: 'COMPLETED', + label: intl.formatMessage({ + id: intlPrefix + '.tab.completed', + }), + }, + { + key: 'CANCELLED', + label: intl.formatMessage({ + id: intlPrefix + '.tab.cancelled', + }), + }, + ], + onChange: (key) => { + setActiveKey(key as string); + }, + }, + actions: [ + { + actionRef.current?.reload(); + onValueChange?.(); + }} + />, + ], + }, + }, + columns, + actionRef: actionRef, + }} + create={false} + update={{ + formType: 'drawer', + formContext: [], + }} + destroy={{}} + detail={{ + rowId: paymentTaskId, + formType: 'drawer', + columns: detailColumns, + formContext: detailContext, + formContextMode: 'flat', + trigger, + }} + /> + ); +} diff --git a/packages/app-operation/src/components/PaymentTask/index.ts b/packages/app-operation/src/components/PaymentTask/index.ts new file mode 100644 index 0000000..06fd9c4 --- /dev/null +++ b/packages/app-operation/src/components/PaymentTask/index.ts @@ -0,0 +1,4 @@ +export { default as InvoicerStatisticCard } from './InvoicerStatisticCard'; +export { default as OrderSupplierInvoiceList } from './OrderSupplierInvoiceList'; +export { default as PaymentTaskCreate } from './PaymentTaskCreate'; +export { default as PaymentTaskList } from './PaymentTaskList'; diff --git a/packages/app-operation/src/components/BasicData/MelonFarmerList.tsx b/packages/app-operation/src/components/Supplier/SupplierFarmerList.tsx similarity index 90% rename from packages/app-operation/src/components/BasicData/MelonFarmerList.tsx rename to packages/app-operation/src/components/Supplier/SupplierFarmerList.tsx index 7a1d9f2..7f4a84a 100644 --- a/packages/app-operation/src/components/BasicData/MelonFarmerList.tsx +++ b/packages/app-operation/src/components/Supplier/SupplierFarmerList.tsx @@ -18,7 +18,7 @@ import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions'; import { ProFormUploadMaterial } from '@chageable/components'; import React, { useRef, useState } from 'react'; -interface IMelonFarmerListProps { +interface ISupplierFarmerListProps { ghost?: boolean; supplierId?: BusinessAPI.SupplierVO['supplierId']; search?: boolean; @@ -27,7 +27,7 @@ interface IMelonFarmerListProps { trigger?: () => React.ReactNode; } -export default function MelonFarmerList(props: IMelonFarmerListProps) { +export default function SupplierFarmerList(props: ISupplierFarmerListProps) { const { ghost = false, supplierId, @@ -37,7 +37,7 @@ export default function MelonFarmerList(props: IMelonFarmerListProps) { onValueChange, } = props; const intl = useIntl(); - const intlPrefix = 'melonFarmer'; + const intlPrefix = 'supplierFarmer'; const actionRef = useRef(); const [showIdCard, setShowIdCard] = useState>({}); @@ -60,7 +60,7 @@ export default function MelonFarmerList(props: IMelonFarmerListProps) { render: (_, record) => (
- {formatIdCard(record.idCard, showIdCard[record.supplierId])} + {formatIdCard(record.idCard!, showIdCard[record.supplierId])} ), }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.bankName' }), + dataIndex: 'bankName', + key: 'bankName', + search: false, + }, { title: intl.formatMessage({ id: intlPrefix + '.column.bankCard' }), dataIndex: 'bankCard', @@ -201,6 +207,23 @@ export default function MelonFarmerList(props: IMelonFarmerListProps) { }, ]} />, + , void; + mode?: ModeType; + trigger?: () => React.ReactNode; +} + +export default function SupplierStallList(props: ISupplierStallListProps) { + const { + ghost = false, + supplierId, + search = true, + mode = 'page', + trigger, + onValueChange, + } = props; + const intl = useIntl(); + const intlPrefix = 'supplierStall'; + const actionRef = useRef(); + + const [showBankCard, setShowBankCard] = useState>({}); + const [showPhone, setShowPhone] = useState>({}); + + const columns: ProColumns[] = [ + { + title: intl.formatMessage({ id: intlPrefix + '.column.name' }), + dataIndex: 'name', + key: 'name', + renderText: (text: string) => {text}, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.payeeName' }), + dataIndex: 'payeeName', + key: 'payeeName', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.phone' }), + dataIndex: 'phone', + key: 'phone', + render: (_, record) => ( +
+ {formatPhone(record.phone, showPhone[record.supplierId])} + { + setShowPhone((prev) => ({ + ...prev, + [record.supplierId]: !prev[record.supplierId], + })); + }} + > + {showPhone[record.supplierId] ? ( + + ) : ( + + )} + +
+ ), + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.bankName' }), + dataIndex: 'bankName', + key: 'bankName', + search: false, + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.bankCard' }), + dataIndex: 'bankCard', + key: 'bankCard', + render: (_, record) => ( +
+ + {formatBankCard(record.bankCard, showBankCard[record.supplierId])} + + { + setShowBankCard((prev) => ({ + ...prev, + [record.supplierId]: !prev[record.supplierId], + })); + }} + > + {showBankCard[record.supplierId] ? ( + + ) : ( + + )} + +
+ ), + }, + { + title: intl.formatMessage({ id: intlPrefix + '.column.wechatQr' }), + dataIndex: 'wechatQr', + valueType: 'image', + key: 'wechatQr', + search: false, + }, + ]; + + const formContext = [ +