feat(dealer): 新增经销商档口管理功能

- 移除独立的 MelonStallList 组件及其导出
- 新增 DealerStallList 组件用于管理经销商档口
- 在 DealerList 中集成档口管理模块,仅对市场类经销商可见
- 更新 DealerSelect 和 DealerSearch 中的标签显示逻辑,去除 ID 前缀
- 调整 BizDetail 过滤逻辑以支持动态显示配置项
- 添加经销商类型枚举国际化支持
- 页面路由从 MelonStall 迁移到 DealerStall
- 补充 dealerStall 相关国际化文案
- 删除 melonStall 相关冗余国际化内容
This commit is contained in:
shenyifei 2025-12-22 10:02:59 +08:00
parent da1200716a
commit a6dc4b85d0
13 changed files with 316 additions and 354 deletions

View File

@ -1,253 +0,0 @@
import {
BizContainer,
BizValueType,
MaterialList,
ModeType,
} from '@/components';
import { business } from '@/services';
import { formatBankCard } from '@/utils/format';
import { formatParam } from '@/utils/formatParam';
import { useIntl } from '@@/exports';
import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons';
import {
ActionType,
ProColumns,
ProFormText,
} from '@ant-design/pro-components';
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
import { ProFormUploadMaterial } from '@chageable/components';
import React, { useRef, useState } from 'react';
interface IMelonStallListProps {
ghost?: boolean;
supplierId?: BusinessAPI.SupplierVO['supplierId'];
search?: boolean;
onValueChange?: () => void;
mode?: ModeType;
trigger?: () => React.ReactNode;
}
export default function MelonStallList(props: IMelonStallListProps) {
const {
ghost = false,
supplierId,
search = true,
mode = 'page',
trigger,
onValueChange,
} = props;
const intl = useIntl();
const intlPrefix = 'melonStall';
const actionRef = useRef<ActionType>();
const [showBankCard, setShowBankCard] = useState<Record<string, boolean>>({});
const columns: ProColumns<BusinessAPI.SupplierVO, BizValueType>[] = [
{
title: intl.formatMessage({ id: intlPrefix + '.column.name' }),
dataIndex: 'name',
key: 'name',
renderText: (text: string) => <span className="font-medium">{text}</span>,
},
{
title: intl.formatMessage({ id: intlPrefix + '.column.payeeName' }),
dataIndex: 'payeeName',
key: 'payeeName',
},
{
title: intl.formatMessage({ id: intlPrefix + '.column.bankCard' }),
dataIndex: 'bankCard',
key: 'bankCard',
render: (_, record) => (
<div className="flex items-center">
<span>
{formatBankCard(record.bankCard, showBankCard[record.supplierId])}
</span>
<span
className="ml-2 cursor-pointer"
onClick={() => {
setShowBankCard((prev) => ({
...prev,
[record.supplierId]: !prev[record.supplierId],
}));
}}
>
{showBankCard[record.supplierId] ? (
<EyeTwoTone />
) : (
<EyeInvisibleOutlined />
)}
</span>
</div>
),
},
{
title: intl.formatMessage({ id: intlPrefix + '.column.wechatQr' }),
dataIndex: 'wechatQr',
valueType: 'image',
key: 'wechatQr',
search: false,
},
];
const formContext = [
<ProFormText key={'type'} name={'type'} hidden={true} />,
<ProFormText key={'payeeName'} name={'payeeName'} hidden={true} />,
<ProFormText
key={'name'}
name={'name'}
label={intl.formatMessage({ id: intlPrefix + '.form.name.label' })}
required={true}
placeholder={intl.formatMessage({
id: intlPrefix + '.form.name.placeholder',
})}
rules={[
{
required: true,
message: intl.formatMessage({
id: intlPrefix + '.form.name.required',
}),
},
]}
/>,
<ProFormText
key={'payeeName'}
name={'payeeName'}
label={intl.formatMessage({ id: intlPrefix + '.form.payeeName.label' })}
required={true}
placeholder={intl.formatMessage({
id: intlPrefix + '.form.payeeName.placeholder',
})}
rules={[
{
required: true,
message: intl.formatMessage({
id: intlPrefix + '.form.payeeName.required',
}),
},
]}
/>,
<ProFormText
key={'bankCard'}
name={'bankCard'}
label={intl.formatMessage({ id: intlPrefix + '.form.bankCard.label' })}
required={true}
placeholder={intl.formatMessage({
id: intlPrefix + '.form.bankCard.placeholder',
})}
rules={[
{
required: true,
message: intl.formatMessage({
id: intlPrefix + '.form.bankCard.required',
}),
},
]}
/>,
<ProFormUploadMaterial
key={'wechatQr'}
label={intl.formatMessage({
id: intlPrefix + '.form.wechatQr.label',
})}
name={'wechatQrList'}
transform={(value) => {
return {
wechatQrList: value,
wechatQr: value[0],
};
}}
fieldProps={{
maxCount: 1,
actionRef: actionRef,
toolBarRender: () => [
<MaterialList
key={'create'}
ghost={true}
mode={'create'}
search={false}
onValueChange={() => actionRef.current?.reload()}
/>,
],
request: async (params, sorter, filter) => {
const { data, success, totalCount } =
await business.material.pageMaterial({
materialPageQry: formatParam<typeof params>(
params,
sorter,
filter,
),
});
return {
data: data || [],
total: totalCount,
success,
};
},
}}
/>,
];
const detailColumns: ProDescriptionsItemProps<
BusinessAPI.SupplierVO,
BizValueType
>[] = columns as ProDescriptionsItemProps<
BusinessAPI.SupplierVO,
BizValueType
>[];
return (
<BizContainer<
typeof business.supplier,
BusinessAPI.SupplierVO,
BusinessAPI.SupplierPageQry,
BusinessAPI.SupplierCreateCmd,
BusinessAPI.SupplierUpdateCmd
>
rowKey={'supplierId'}
permission={'operation-supplier'}
func={business.supplier}
method={'supplier'}
methodUpper={'Supplier'}
intlPrefix={intlPrefix}
modeType={mode}
onValueChange={onValueChange}
container={{}}
remark={{
mode: 'editor',
}}
status
page={{
fieldProps: {
bordered: true,
ghost,
//@ts-ignore
search,
params: {
type: 'STALL',
},
},
columns,
}}
create={{
formType: 'drawer',
formContext,
initValues: {
status: true,
type: 'STALL',
},
}}
update={{
formType: 'drawer',
formContext,
}}
destroy={{}}
detail={{
rowId: supplierId,
formType: 'drawer',
columns: detailColumns,
trigger,
}}
/>
);
}

View File

@ -1,5 +1,4 @@
export { default as MelonFarmerList } from './MelonFarmerList'; export { default as MelonFarmerList } from './MelonFarmerList';
export { default as MelonStallList } from './MelonStallList';
export { default as BoxProductList } from './BoxProductList' export { default as BoxProductList } from './BoxProductList'
export { default as BoxBrandList } from './BoxBrandList'; export { default as BoxBrandList } from './BoxBrandList';
export { default as CostList } from './CostList'; export { default as CostList } from './CostList';

View File

@ -136,7 +136,13 @@ export default function BizDetail<
form.setFieldValue(`${method}VO`, data); form.setFieldValue(`${method}VO`, data);
}, },
), )?.filter((item) => {
//@ts-ignore
return (
!item.display ||
item.display(data?.[`${method}VO`])
);
}),
onChange: (key) => { onChange: (key) => {
form.setFieldValue('activeKey', key); form.setFieldValue('activeKey', key);
}, },
@ -217,7 +223,13 @@ export default function BizDetail<
form.setFieldValue(`${method}VO`, data); form.setFieldValue(`${method}VO`, data);
}, },
), )?.filter((item) => {
//@ts-ignore
return (
!item.display ||
item.display(data?.[`${method}VO`])
);
}),
onChange: (key) => { onChange: (key) => {
form.setFieldValue('activeKey', key); form.setFieldValue('activeKey', key);
}, },

View File

@ -32,7 +32,7 @@ export default function DealerFormItem(props: IDealerFormItemProps) {
options: dealerList?.map((dealerVO?: BusinessAPI.DealerVO) => { options: dealerList?.map((dealerVO?: BusinessAPI.DealerVO) => {
return { return {
value: dealerVO?.dealerId, value: dealerVO?.dealerId,
label: `(ID: ${dealerVO?.dealerId}) ${dealerVO?.shortName || dealerVO?.fullName}`, label: `${dealerVO?.shortName || dealerVO?.fullName}`,
}; };
}), }),
}} }}

View File

@ -4,6 +4,7 @@ import {
DealerPaymentAccountList, DealerPaymentAccountList,
DealerPurchaseSetting, DealerPurchaseSetting,
DealerRebateCustomerList, DealerRebateCustomerList,
DealerStallList,
DeliveryTemplate, DeliveryTemplate,
ModeType, ModeType,
} from '@/components'; } from '@/components';
@ -16,7 +17,7 @@ import {
ProFormText, ProFormText,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions'; import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
import { Tooltip } from 'antd'; import { Space, Tooltip } from 'antd';
import React from 'react'; import React from 'react';
interface IDealerListProps { interface IDealerListProps {
@ -44,7 +45,20 @@ export default function DealerList(props: IDealerListProps) {
title: intl.formatMessage({ id: intlPrefix + '.column.shortName' }), title: intl.formatMessage({ id: intlPrefix + '.column.shortName' }),
dataIndex: 'shortName', dataIndex: 'shortName',
key: 'shortName', key: 'shortName',
renderText: (text: string) => <span className="font-medium">{text}</span>, render: (_, dealerVO) => {
return (
<DealerList
ghost={true}
mode={'detail'}
dealerId={dealerVO.dealerId}
trigger={() => (
<Space>
<a>{dealerVO.shortName}</a>
</Space>
)}
/>
);
},
}, },
{ {
title: intl.formatMessage({ id: intlPrefix + '.column.dealerType' }), title: intl.formatMessage({ id: intlPrefix + '.column.dealerType' }),
@ -377,6 +391,20 @@ export default function DealerList(props: IDealerListProps) {
/> />
), ),
}, },
{
label: `档口管理`,
key: 'dealerStallList',
display: (record: BusinessAPI.DealerVO) =>
record.dealerType === 'MARKET',
children: (
<DealerStallList
dealerVO={dealerVO}
ghost={true}
search={false}
onValueChange={onValueChange}
/>
),
},
]; ];
}; };

View File

@ -10,6 +10,7 @@ import {
import { SelectModal } from '@chageable/components'; import { SelectModal } from '@chageable/components';
import { Alert, ModalProps, Row, Space, Tag } from 'antd'; import { Alert, ModalProps, Row, Space, Tag } from 'antd';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { useIntl } from '@@/exports';
export interface IDealerModalProps extends ModalProps { export interface IDealerModalProps extends ModalProps {
title: string; title: string;
@ -51,11 +52,29 @@ export default function DealerModal(props: IDealerModalProps) {
} }
}, [initParams]); }, [initParams]);
const intl = useIntl();
const intlPrefix = 'dealer';
const columns: ProColumns<BusinessAPI.DealerVO>[] = [ const columns: ProColumns<BusinessAPI.DealerVO>[] = [
{ {
title: '经销商简称', title: intl.formatMessage({ id: intlPrefix + '.column.shortName' }),
dataIndex: 'shortName', dataIndex: 'shortName',
valueType: 'text', key: 'shortName',
renderText: (text: string) => <span className="font-medium">{text}</span>,
},
{
title: intl.formatMessage({ id: intlPrefix + '.column.dealerType' }),
dataIndex: 'dealerType',
key: 'dealerType',
valueType: 'select',
valueEnum: {
MARKET: intl.formatMessage({
id: intlPrefix + '.column.dealerType.enum.market',
}),
SUPERMARKET: intl.formatMessage({
id: intlPrefix + '.column.dealerType.enum.supermarket',
}),
},
}, },
...(initExtraColumns || []), ...(initExtraColumns || []),
]; ];

View File

@ -1,4 +1,4 @@
import { DealerModal, IDealerModalProps, UserModal } from '@/components'; import { DealerModal, IDealerModalProps } from '@/components';
import { import {
FormInstance, FormInstance,
ProFormSelect, ProFormSelect,
@ -10,10 +10,11 @@ export interface IDealerSearchProps extends ProFormSelectProps {
form: FormInstance; form: FormInstance;
selectedList?: IDealerModalProps['selectedList']; selectedList?: IDealerModalProps['selectedList'];
onFinish?: (dealerVOList: BusinessAPI.DealerVO[]) => void; onFinish?: (dealerVOList: BusinessAPI.DealerVO[]) => void;
params: BusinessAPI.DealerPageQry;
} }
export default function DealerSearch(props: IDealerSearchProps) { export default function DealerSearch(props: IDealerSearchProps) {
const { form, selectedList, onFinish, ...rest } = props; const { form, selectedList, onFinish, params, ...rest } = props;
const [showDealerModal, setShowDealerModal] = useState<boolean>(false); const [showDealerModal, setShowDealerModal] = useState<boolean>(false);
const [dealerList, setDealerList] = const [dealerList, setDealerList] =
@ -33,7 +34,7 @@ export default function DealerSearch(props: IDealerSearchProps) {
console.log('dealerVO', dealerVO); console.log('dealerVO', dealerVO);
return { return {
value: dealerVO?.dealerId, value: dealerVO?.dealerId,
label: `(ID: ${dealerVO?.dealerId}) ${dealerVO?.shortName || dealerVO?.fullName}`, label: `${dealerVO?.shortName || dealerVO?.fullName}`,
}; };
}), }),
}} }}
@ -58,6 +59,7 @@ export default function DealerSearch(props: IDealerSearchProps) {
} }
}} }}
type={'radio'} type={'radio'}
params={params}
/> />
</> </>
); );

View File

@ -34,7 +34,9 @@ export default function DealerSelect(props: IUserSelectProps) {
name={'dealerVO'} name={'dealerVO'}
required={true} required={true}
convertValue={(dealerVO) => { convertValue={(dealerVO) => {
return `(ID: ${dealerVO?.dealerId}) ${dealerVO?.shortName || dealerVO?.fullName}`; return dealerVO
? `${dealerVO?.shortName || dealerVO?.fullName}`
: '';
}} }}
placeholder={intl.formatMessage({ placeholder={intl.formatMessage({
id: 'form.dealerId.placeholder', id: 'form.dealerId.placeholder',

View File

@ -0,0 +1,151 @@
import {
BizContainer,
BizValueType,
DealerSelect,
ModeType,
} from '@/components';
import { business } from '@/services';
import { useIntl } from '@@/exports';
import { ProColumns, ProFormText } from '@ant-design/pro-components';
import { ProDescriptionsItemProps } from '@ant-design/pro-descriptions';
import React from 'react';
interface IDealerStallListProps {
ghost?: boolean;
supplierId?: BusinessAPI.SupplierVO['supplierId'];
dealerVO?: BusinessAPI.DealerVO;
search?: boolean;
onValueChange?: () => void;
mode?: ModeType;
trigger?: () => React.ReactNode;
}
export default function DealerStallList(props: IDealerStallListProps) {
const {
ghost = false,
dealerVO,
supplierId,
search = true,
mode = 'page',
trigger,
onValueChange,
} = props;
const intl = useIntl();
const intlPrefix = 'dealerStall';
const columns: ProColumns<BusinessAPI.SupplierVO, BizValueType>[] = [
{
title: intl.formatMessage({ id: intlPrefix + '.column.dealerId' }),
dataIndex: 'dealerVO',
key: 'dealerId',
valueType: 'dealer',
hidden: !!dealerVO,
},
{
title: intl.formatMessage({ id: intlPrefix + '.column.name' }),
dataIndex: 'name',
key: 'name',
renderText: (text: string) => <span className="font-medium">{text}</span>,
},
];
const formContext = [
<DealerSelect
key={'dealerId'}
params={{
dealerType: 'MARKET',
}}
/>,
<ProFormText key={'type'} name={'type'} hidden={true} />,
<ProFormText
key={'name'}
name={'name'}
label={intl.formatMessage({ id: intlPrefix + '.form.name.label' })}
required={true}
placeholder={intl.formatMessage({
id: intlPrefix + '.form.name.placeholder',
})}
rules={[
{
required: true,
message: intl.formatMessage({
id: intlPrefix + '.form.name.required',
}),
},
]}
/>,
];
const detailColumns: ProDescriptionsItemProps<
BusinessAPI.SupplierVO,
BizValueType
>[] = columns as ProDescriptionsItemProps<
BusinessAPI.SupplierVO,
BizValueType
>[];
return (
<BizContainer<
typeof business.supplier,
BusinessAPI.SupplierVO,
BusinessAPI.SupplierPageQry,
BusinessAPI.SupplierCreateCmd,
BusinessAPI.SupplierUpdateCmd
>
rowKey={'supplierId'}
permission={'operation-supplier'}
func={business.supplier}
method={'supplier'}
methodUpper={'Supplier'}
intlPrefix={intlPrefix}
modeType={mode}
onValueChange={onValueChange}
container={{
ghost,
}}
remark={{
mode: 'editor',
}}
status
page={{
fieldProps: {
bordered: true,
ghost,
//@ts-ignore
search,
params: {
...(dealerVO && {
dealerId: dealerVO?.dealerId,
}),
type: 'STALL',
},
},
columns,
}}
create={{
formType: 'drawer',
formContext,
initValues: {
...(dealerVO && {
dealerId: dealerVO?.dealerId,
dealerVO: dealerVO,
canChangeDealer: !dealerVO,
}),
status: true,
type: 'STALL',
},
}}
update={{
formType: 'drawer',
formContext,
}}
destroy={{}}
detail={{
rowId: supplierId,
formType: 'drawer',
columns: detailColumns,
trigger,
}}
/>
);
}

View File

@ -9,3 +9,4 @@ export { default as DealerSelect } from './DealerSelect';
export { default as DealerWarehouseList } from './DealerWarehouseList'; export { default as DealerWarehouseList } from './DealerWarehouseList';
export { default as DeliveryTemplate } from './DeliveryTemplate'; export { default as DeliveryTemplate } from './DeliveryTemplate';
export { default as DealerPurchaseSetting } from './DealerPurchaseSetting'; export { default as DealerPurchaseSetting } from './DealerPurchaseSetting';
export { default as DealerStallList } from './DealerStallList';

View File

@ -828,6 +828,91 @@ export default {
}, },
}, },
}, },
dealerStall: {
column: {
dealerId: '经销商',
name: '档口名称',
payeeName: '收款人姓名',
bankCard: '银行卡号',
wechatQr: '微信收款码',
remark: '备注',
status: '状态',
'status.enum.enabled': '正常',
'status.enum.disabled': '禁用',
'status.placeholder': '请选择状态',
createdAt: '创建时间',
option: '操作',
},
form: {
name: {
label: '档口名称',
placeholder: '请输入档口名称',
required: '档口名称为必填项',
},
payeeName: {
label: '收款人姓名',
placeholder: '请输收款人姓名',
required: '收款人姓名为必填项',
},
bankCard: {
label: '银行卡号',
placeholder: '请输入银行卡号',
required: '银行卡号为必填项',
},
wechatQr: {
label: '微信收款码',
placeholder: '请输入微信收款码',
required: '微信收款码为必填项',
},
remark: {
label: '备注',
placeholder: '请输入备注',
},
status: {
label: '状态',
placeholder: '请选择状态',
required: '状态为必填项',
enum: {
enabled: '正常',
disabled: '禁用',
},
},
},
modal: {
create: {
title: '新增档口',
button: '新增档口',
success: '新增档口成功',
},
update: {
title: '更新档口',
button: '编辑',
success: '更新档口成功',
status: {
success: '修改状态成功',
},
},
delete: {
success: '删除档口成功',
button: '删除',
confirm: {
title: '确认删除',
content: '您确定要删除该档口吗?',
okText: '确定',
cancelText: '取消',
},
},
import: {
title: '导入档口',
button: '导入',
success: '导入档口成功',
},
view: {
title: '查看档口',
button: '查看',
},
},
},
dealerPaymentAccount: { dealerPaymentAccount: {
column: { column: {
dealerId: '经销商', dealerId: '经销商',
@ -1207,90 +1292,6 @@ export default {
}, },
}, },
}, },
melonStall: {
column: {
name: '档口名称',
payeeName: '收款人姓名',
bankCard: '银行卡号',
wechatQr: '微信收款码',
remark: '备注',
status: '状态',
'status.enum.enabled': '正常',
'status.enum.disabled': '禁用',
'status.placeholder': '请选择状态',
createdAt: '创建时间',
option: '操作',
},
form: {
name: {
label: '档口名称',
placeholder: '请输入档口名称',
required: '档口名称为必填项',
},
payeeName: {
label: '收款人姓名',
placeholder: '请输收款人姓名',
required: '收款人姓名为必填项',
},
bankCard: {
label: '银行卡号',
placeholder: '请输入银行卡号',
required: '银行卡号为必填项',
},
wechatQr: {
label: '微信收款码',
placeholder: '请输入微信收款码',
required: '微信收款码为必填项',
},
remark: {
label: '备注',
placeholder: '请输入备注',
},
status: {
label: '状态',
placeholder: '请选择状态',
required: '状态为必填项',
enum: {
enabled: '正常',
disabled: '禁用',
},
},
},
modal: {
create: {
title: '新增档口',
button: '新增档口',
success: '新增档口成功',
},
update: {
title: '更新档口',
button: '编辑',
success: '更新档口成功',
status: {
success: '修改状态成功',
},
},
delete: {
success: '删除档口成功',
button: '删除',
confirm: {
title: '确认删除',
content: '您确定要删除该档口吗?',
okText: '确定',
cancelText: '取消',
},
},
import: {
title: '导入档口',
button: '导入',
success: '导入档口成功',
},
view: {
title: '查看档口',
button: '查看',
},
},
},
boxSpec: { boxSpec: {
column: { column: {
name: '规格名称', name: '规格名称',

View File

@ -0,0 +1,5 @@
import { DealerStallList } from '@/components';
export default function Page() {
return <DealerStallList />;
}

View File

@ -1,5 +0,0 @@
import { MelonStallList } from '@/components';
export default function Page() {
return <MelonStallList />;
}