供应商过坐台地区回显

This commit is contained in:
孙景学
2025-07-24 11:15:37 +08:00
parent eaa5faca63
commit 823da974f9
5 changed files with 209 additions and 46 deletions

View File

@ -3,18 +3,64 @@ import { Cascader, Spin } from 'antd';
import type { DefaultOptionType } from 'antd/es/cascader';
import { getChild } from './services';
// 只用函数式组件的 props不声明泛型
const DictRegionSelect: React.FC<Partial<import('antd').CascaderProps<DefaultOptionType>>> = (props) => {
const [options, setOptions] = useState<DefaultOptionType[]>([]);
const [loading, setLoading] = useState(false);
// 处理回显的所有级联节点
useEffect(() => {
setLoading(true);
fetchRegionOptions('0').then(data => {
setOptions(data);
setLoading(false);
});
}, []);
let destroyed = false;
// 只在有初始值时触发自动懒加载
async function loadAllLevels() {
setLoading(true);
// props.value 是 [省, 市, 区] 的 id 数组
const path = (props.value as string[]) || [];
let currentPid = '0';
let currentOptions: DefaultOptionType[] = [];
let optionLevel = [];
let parentOptions = currentOptions;
for (let i = 0; i < path.length; i++) {
// 拉取本级所有 children
// eslint-disable-next-line no-await-in-loop
const children = await fetchRegionOptions(currentPid);
// 新建 option 节点
const optionNodes = children.map(child => ({
...child,
children: undefined, // 只有下一步才补
}));
// 当前级别节点加入到 options 树
if (i === 0) {
// 顶级直接 set
currentOptions = optionNodes;
} else {
// 找到父节点,挂到父节点 children
let parent = parentOptions.find(opt => opt.value === path[i - 1]);
if (parent) parent.children = optionNodes;
}
// 记录本级 option下一轮用于找 parent
parentOptions = optionNodes;
currentPid = path[i];
}
if (!destroyed) {
setOptions(currentOptions);
setLoading(false);
}
}
// 如果有 value并且是懒加载options 为空或只有顶级)
if (Array.isArray(props.value) && props.value.length > 0) {
loadAllLevels();
} else {
// 首次只拉顶级
setLoading(true);
fetchRegionOptions('0').then(data => {
setOptions(data);
setLoading(false);
});
}
return () => { destroyed = true; };
// eslint-disable-next-line
}, [props.value]);
const loadData = async (selectedOptions: DefaultOptionType[]) => {
const targetOption = selectedOptions[selectedOptions.length - 1];
@ -26,8 +72,6 @@ const DictRegionSelect: React.FC<Partial<import('antd').CascaderProps<DefaultOpt
};
const fetchRegionOptions = async (pId: string | number): Promise<DefaultOptionType[]> => {
console.log(pId,'pId');
const res = await getChild({ pId });
if (res && res.code === 200 && Array.isArray(res.data)) {
return res.data.map((item: any) => ({
@ -39,11 +83,10 @@ const DictRegionSelect: React.FC<Partial<import('antd').CascaderProps<DefaultOpt
return [];
};
// 明确指定 loadData 类型,解决 ts 推断
const cascaderProps = {
changeOnSelect: true,
options,
loadData: loadData as (selectedOptions: DefaultOptionType[]) => void, // 关键类型断言!
loadData: loadData as (selectedOptions: DefaultOptionType[]) => void,
placeholder: "请选择地区",
...props,
};

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { Modal, Form, Input, message, Row, Col, Descriptions, Select } from 'antd';
import { getDictList } from '@/servers/api/dicts';
import { bankView, bankAdd, bankEdit } from '../services';
import { bankView, bankAdd, bankEdit } from '../services';
import type { DictItem } from '@/servers/api/dicts';
import DictRegionSelect from '@/components/CommonSelect/DictRegionSelect'
import { dictRegion } from '@/servers/api/user'
interface props {
visible: boolean;
onOk: () => void;
@ -49,6 +49,11 @@ const InvoiceFormModal: React.FC<props> = ({
//提交防抖
const [submitting, setSubmitting] = useState(false);
const [currency, setCurrency] = useState<DictItem[]>([]);
//币种
const [currencyMap, setCurrencyMap] = useState<{ [code: string]: string }>({});
const [nationName, setNationName] = useState('');
const [provinceName, setProvinceName] = useState('');
const [cityName, setCityName] = useState('');
useEffect(() => {
if (visible) {
@ -60,23 +65,36 @@ const InvoiceFormModal: React.FC<props> = ({
...data,
id: data.id ? data.id : null,
address: [
Number(data.nation),
Number(data.province),
Number(data.city),
]
data.nation ? String(data.nation) : undefined,
data.province ? String(data.province) : undefined,
data.city ? String(data.city) : undefined,
]
};
console.log(fields);
form.setFieldsValue(fields);
setViewData(fields);
if (data.nation) dictRegion(data.nation).then(r => setNationName(r?.data?.name || ''));
if (data.province) dictRegion(data.province).then(r => setProvinceName(r?.data?.name || ''));
if (data.city) dictRegion(data.city).then(r => setCityName(r?.data?.name || ''));
}
});
} else {
form.resetFields();
form.resetFields();
setViewData({});
setNationName('');
setProvinceName('');
setCityName('');
}
getDictList('currency').then((res) => {
if (res.code === 200) {
const map: { [code: string]: string } = {};
res.data.forEach((item: { code: string, dicName: string }) => {
map[item.code] = item.dicName;
});
setCurrencyMap(map);
setCurrency(res.data);
}
});
@ -121,7 +139,13 @@ const InvoiceFormModal: React.FC<props> = ({
setSubmitting(false); // 无论成功失败都解锁
}
};
const fetchRegionNames = async (codes: string) => {
console.log(codes,'codes');
const { data } = await dictRegion(codes);
console.log(data);
};
return (
<Modal
title={readOnly ? '查看' : initialValues ? '修改' : '新增'}
@ -143,17 +167,17 @@ const InvoiceFormModal: React.FC<props> = ({
<Descriptions.Item label="账户名称">{viewData.accountName}</Descriptions.Item>
<Descriptions.Item label="开户银行">{viewData.bank}</Descriptions.Item>
<Descriptions.Item label="联行号">{viewData.interbankNumber}</Descriptions.Item>
<Descriptions.Item label="国家/地区">{viewData.nationName}</Descriptions.Item>
<Descriptions.Item label="省份">{viewData.provinceName}</Descriptions.Item>
<Descriptions.Item label="城市">{viewData.cityName}</Descriptions.Item>
<Descriptions.Item label="币种">{viewData.currency}</Descriptions.Item>
<Descriptions.Item label="国家/地区">{nationName || viewData.nation}</Descriptions.Item>
<Descriptions.Item label="省份">{provinceName || viewData.province}</Descriptions.Item>
<Descriptions.Item label="城市">{cityName || viewData.city}</Descriptions.Item>
<Descriptions.Item label="币种">{currencyMap[viewData.currency as string] || viewData.currency}</Descriptions.Item>
</Descriptions>
) : (
<Form form={form} labelCol={{ flex: '120px' }} wrapperCol={{ flex: 1 }}>
<Row gutter={24}>
<Col span={24}>
<Form.Item name="account" label="开户账号" rules={[{ required: true }]}>
<Input placeholder='请输入开户账号' />
<Input placeholder='请输入开户账号' />
</Form.Item>
</Col>
<Col span={24}>

View File

@ -1,9 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Table, Button, message, Switch } from 'antd';
import { Table, Button, message, Switch, Spin } from 'antd';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { bankGetPage, bankEdit } from '../services';
import { useIntl } from 'umi';
import BankFormModal from './BankFormModal';
import { dictRegion } from '@/servers/api/user'
import { getDictList } from '@/servers/api/dicts';
interface BankInfo {
id: string;
@ -23,6 +25,24 @@ interface Props {
viewType?: boolean;
record?: string;
}
const codeNameCache = new Map<string, string>();
const fetchRegionNames = async (codes: string[]) => {
const waitCodes = codes.filter(code => code && !codeNameCache.has(code));
if (waitCodes.length === 0) return;
// 批量接口推荐你后端支持,单个请求也兼容如下
await Promise.all(waitCodes.map(async (code) => {
try {
const { code: status, data } = await dictRegion(code);
if (status === 200 && data && data.name) {
codeNameCache.set(code, data.name);
}
} catch { /* ignore */ }
}));
};
const BankInfoTab: React.FC<Props> = (props) => {
const userId = sessionStorage.getItem('userId') || '';
const { viewType = false, record = userId } = props;
@ -34,6 +54,8 @@ const BankInfoTab: React.FC<Props> = (props) => {
const [loading, setLoading] = useState(false);
//列表分页
const [pagination, setPagination] = useState<TablePaginationConfig>({ current: 1, pageSize: 10, total: 0 });
//币种
const [currencyMap, setCurrencyMap] = useState<{ [code: string]: string }>({});
//列表方法
const getList = async (pageNo: number = 1, pageSize: number = 10) => {
setLoading(true);
@ -74,10 +96,11 @@ const BankInfoTab: React.FC<Props> = (props) => {
setIsViewMode(true);
setFormVisible(true);
};
//是否作废
const handleObsoleteChange = async (checked: boolean, id:string) => {
const handleObsoleteChange = async (checked: boolean, id: string) => {
// 调用你的作废接口
const res = await bankEdit( { id, delFlag: checked? 'normal':'deleted' } );
const res = await bankEdit({ id, delFlag: checked ? 'normal' : 'deleted' });
if (res.code === 200) {
message.success('操作成功');
getList(pagination.current, pagination.pageSize); // 刷新列表
@ -87,10 +110,48 @@ const BankInfoTab: React.FC<Props> = (props) => {
}
//初始化
useEffect(() => {
if(record) {
if (record) {
getDictList('currency').then((res) => {
if (res.code === 200) {
const map: { [code: string]: string } = {};
res.data.forEach((item: { code:string, dicName: string }) => {
map[item.code] = item.dicName;
});
setCurrencyMap(map);
}
});
getList();
}
}, [record]);
const [regionLoading, setRegionLoading] = useState(false);
const [, forceUpdate] = useState({}); // 用于触发重新渲染
// 新增一个 effect当 data 变化时批量请求
useEffect(() => {
// 收集所有 nation、province、city 的 code
const codes: string[] = [];
data.forEach(item => {
if (item.nation) codes.push(item.nation);
if (item.province) codes.push(item.province);
if (item.city) codes.push(item.city);
});
// 查缓存,没有再查接口
setRegionLoading(true);
fetchRegionNames(codes).then(() => {
setRegionLoading(false);
forceUpdate({}); // 强制刷新
});
}, [data]);
// 通用渲染
const renderRegionName = (code: string) => {
if (!code) return '';
if (codeNameCache.has(code)) {
return codeNameCache.get(code);
}
return <Spin size="small" />;
};
// 表格头部
const columns: ColumnsType<BankInfo> = [
{
@ -123,23 +184,27 @@ const BankInfoTab: React.FC<Props> = (props) => {
},
{
title: 'page.workbench.bank.currency',
dataIndex: 'currencyName',
key: 'currency', ellipsis: true
dataIndex: 'currency',
key: 'currency', ellipsis: true,
render: (code: string) => currencyMap[code] || code
},
{
title: 'page.workbench.bank.nation',
dataIndex: 'nationName',
key: 'nation', ellipsis: true
dataIndex: 'nation',
key: 'nation', ellipsis: true,
render: renderRegionName,
},
{
title: 'page.workbench.bank.province',
dataIndex: 'provinceName',
key: 'province', ellipsis: true
dataIndex: 'province',
key: 'province', ellipsis: true,
render: renderRegionName,
},
{
title: 'page.workbench.bank.city',
dataIndex: 'cityName',
key: 'city', ellipsis: true
dataIndex: 'city',
key: 'city', ellipsis: true,
render: renderRegionName,
},
{
title: 'page.workbench.bank.updateTime',
@ -172,8 +237,8 @@ const BankInfoTab: React.FC<Props> = (props) => {
<>
<a style={{ marginRight: 8 }} onClick={() => handleView(record)}></a>
{record.delFlag === 'normal' && (
<a onClick={() => handleEdit(record)}></a>
)}
<a onClick={() => handleEdit(record)}></a>
)}
</>
),
},

View File

@ -4,7 +4,7 @@ import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { invoiceGetPage, invoiceEdit } from '../services';
import { useIntl } from 'umi';
import InvoiceFormModal from './InvoiceFormModal';
import { getDictList } from '@/servers/api/dicts';
interface InvoiceInfo {
id: string;
taxpayerType: string;
@ -76,10 +76,10 @@ const InvoiceTab: React.FC<InvoiceTabProps> = (props) => {
setIsViewMode(true);
setFormVisible(true);
};
//是否作废
const handleObsoleteChange = async (checked: boolean, id:string) => {
//是否作废
const handleObsoleteChange = async (checked: boolean, id: string) => {
// 调用你的作废接口
const res = await invoiceEdit( { id, delFlag: checked? 'normal':'deleted' } );
const res = await invoiceEdit({ id, delFlag: checked ? 'normal' : 'deleted' });
if (res.code === 200) {
message.success('操作成功');
getList(pagination.current, pagination.pageSize); // 刷新列表
@ -87,16 +87,27 @@ const InvoiceTab: React.FC<InvoiceTabProps> = (props) => {
message.error('操作失败');
}
}
const [taxpayerTypeMap, setTaxpayerTypeMap] = useState<{ [code: string]: string }>({});
//初始化
useEffect(() => {
if(record) {
if (record) {
getDictList('taxpayer_type').then((res) => {
if (res.code === 200) {
const map: { [code: string]: string } = {};
res.data.forEach((item: { code: string, dicName: string }) => {
map[item.code] = item.dicName;
});
setTaxpayerTypeMap(map);
}
});
getList();
}
}, [record]);
const columns: ColumnsType<InvoiceInfo> = [
{ title: 'page.workbench.invoice.index', dataIndex: 'index', width: 80, key: 'index', render: (_: any, __: any, index: number) => index + 1 },
{ title: 'page.workbench.invoice.taxpayerType', dataIndex: 'taxpayerTypeCn', ellipsis: true },
{ title: 'page.workbench.invoice.taxpayerType', dataIndex: 'taxpayerType', ellipsis: true, render: (code: string) => taxpayerTypeMap[code] || code },
{ title: 'page.workbench.invoice.taxpayerCode', dataIndex: 'taxpayerCode', ellipsis: true },
{ title: 'page.workbench.invoice.head', dataIndex: 'head', ellipsis: true },
{ title: 'page.workbench.invoice.address', dataIndex: 'address', ellipsis: true },
@ -136,8 +147,8 @@ const InvoiceTab: React.FC<InvoiceTabProps> = (props) => {
<>
<a style={{ marginRight: 8 }} onClick={() => handleView(record)}></a>
{record.delFlag === 'normal' && (
<a onClick={() => handleEdit(record)}></a>
)}
<a onClick={() => handleEdit(record)}></a>
)}
</>
),
},

View File

@ -11,3 +11,23 @@ export async function getUserList(params: API.UserListRequest) {
params,
});
}
/**
* 获取全国列表
* @param params 查询参数
* @returns 全国列表响应
*/
export async function dictRegion(code:string) {
return request(`/v1/dictRegion/${code}`, {
method: 'GET',
});
}
/**
* 获取全球列表
* @param params 查询参数
* @returns 全球列表响应
*/
export async function dictRegionInternational(code:string) {
return request(`/v1/dictRegionInternational/${code}`, {
method: 'GET',
});
}