城市合伙人端的修改(后台)

This commit is contained in:
2026-07-12 10:19:39 +08:00
parent d0f0fa09af
commit 7f031cc4c2
74 changed files with 5430 additions and 975 deletions
+39 -16
View File
@@ -8,6 +8,7 @@ import {
Form,
Image,
Input,
InputNumber,
Modal,
Select,
Space,
@@ -57,8 +58,7 @@ type CityOption = {
id: string;
name: string;
code: string;
partnerId?: string | null;
partner?: { id: string; companyName: string };
partnerBindings?: Array<{ partnerAccountId: string; partnerCompanyName?: string }>;
};
export default function StoresPage() {
@@ -87,12 +87,12 @@ export default function StoresPage() {
const [cities, setCities] = useState<CityOption[]>([]);
const [optionsLoading, setOptionsLoading] = useState(false);
const selectedPartnerId = Form.useWatch('partnerId', createForm);
const selectedPartnerId = Form.useWatch('partnerAccountId', createForm);
const selectedRegionCodes = Form.useWatch('regionCodes', createForm);
const selectedCityId = Form.useWatch('cityId', createForm);
function bindRegionSelection(codes: string[], partnerId?: string) {
const binding = resolveRegionBinding(codes, cities, partnerId ?? selectedPartnerId);
function bindRegionSelection(codes: string[], partnerAccountId?: string) {
const binding = resolveRegionBinding(codes, cities, partnerAccountId ?? selectedPartnerId);
if (!binding) {
createForm.setFieldsValue({ regionCodes: codes, cityId: undefined });
return;
@@ -114,7 +114,7 @@ export default function StoresPage() {
if (binding.cityId && binding.matchedCity) {
return `已匹配开城城市:${binding.matchedCity.name}(区划 ${binding.cityCode},区县 ${binding.region.districtCode}`;
}
return `区划 ${binding.cityCode} 暂未开城,请先在「开城 → 开城城市」添加`;
return `区划 ${binding.cityCode} 暂未开城,请先在「开城 → 城市」添加`;
}, [selectedRegionCodes, cities, selectedPartnerId]);
async function loadOptions() {
@@ -127,8 +127,8 @@ export default function StoresPage() {
]);
setPartners(p.items);
setCities(c.items);
if (!p.items.length) message.warning('暂无开城合伙人,请先在「开城 → 开城合伙人」中创建');
if (!c.items.length) message.warning('暂无开城城市,请先在「开城 → 开城城市」中创建');
if (!p.items.length) message.warning('暂无开城合伙人,请先在「开城 → 城市」详情中创建合伙人');
if (!c.items.length) message.warning('暂无开城城市,请先在「开城 → 城市」中创建');
} catch (e) {
message.error(e instanceof Error ? e.message : '加载合伙人/城市失败');
} finally {
@@ -147,6 +147,7 @@ export default function StoresPage() {
void loadOptions();
createForm.setFieldsValue({
envPhotoUrls: ['', '', ''],
settlementRate: 60,
});
setCreateStep(0);
setCreateError('');
@@ -162,7 +163,7 @@ export default function StoresPage() {
return;
}
try {
await createForm.validateFields(['partnerId', 'regionCodes', 'cityId', 'name', 'phone', 'address']);
await createForm.validateFields(['partnerAccountId', 'regionCodes', 'cityId', 'name', 'phone', 'address']);
} catch {
return;
}
@@ -184,7 +185,7 @@ export default function StoresPage() {
await request('/admin/stores', {
method: 'POST',
body: JSON.stringify({
partnerId: values.partnerId,
partnerAccountId: values.partnerAccountId,
cityId: values.cityId,
province: values.province,
city: values.city,
@@ -199,6 +200,7 @@ export default function StoresPage() {
bankAccountName: values.bankAccountName.trim(),
bankAccountNo: values.bankAccountNo.replace(/\s/g, ''),
bankBranch: values.bankBranch.trim(),
settlementRate: Number(values.settlementRate ?? 60) / 100,
}),
});
message.success('门店已创建');
@@ -208,7 +210,7 @@ export default function StoresPage() {
if (e && typeof e === 'object' && 'errorFields' in e) {
const fields = e as { errorFields?: Array<{ name: string[] }> };
const first = fields.errorFields?.[0]?.name?.[0];
if (first === 'partnerId' || first === 'cityId' || first === 'regionCodes' || first === 'name' || first === 'phone') {
if (first === 'partnerAccountId' || first === 'cityId' || first === 'regionCodes' || first === 'name' || first === 'phone') {
setCreateStep(0);
}
return;
@@ -239,7 +241,15 @@ export default function StoresPage() {
<Button type="link" size="small" onClick={async () => {
const d = await request<Record<string, unknown>>(`/admin/stores/${row.id}`);
setDetail(d);
editForm.setFieldsValue({ name: d.name, phone: d.phone, intro: d.intro, coverUrl: d.coverUrl, address: d.address, district: d.district });
editForm.setFieldsValue({
name: d.name,
phone: d.phone,
intro: d.intro,
coverUrl: d.coverUrl,
address: d.address,
district: d.district,
settlementRate: d.settlementRate != null ? Number(d.settlementRate) * 100 : 60,
});
setDrawerOpen(true);
}}></Button>
),
@@ -279,7 +289,11 @@ export default function StoresPage() {
}} />
<Button type="primary" onClick={async () => {
const v = await editForm.validateFields();
await request(`/admin/stores/${detail.id}`, { method: 'PUT', body: JSON.stringify(v) });
const payload = {
...v,
settlementRate: v.settlementRate != null ? Number(v.settlementRate) / 100 : undefined,
};
await request(`/admin/stores/${detail.id}`, { method: 'PUT', body: JSON.stringify(payload) });
message.success('已保存');
setDetail({ ...detail, ...v });
void reload();
@@ -291,6 +305,9 @@ export default function StoresPage() {
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
<Descriptions.Item label="ID">{String(detail.id)}</Descriptions.Item>
<Descriptions.Item label="地址">{String(detail.province)}{String(detail.cityName)}{String(detail.district)}{String(detail.address)}</Descriptions.Item>
<Descriptions.Item label="核销结算比例">
{detail.settlementRate != null ? `${Math.round(Number(detail.settlementRate) * 100)}%` : '60%'}
</Descriptions.Item>
<Descriptions.Item label="核销数">{String(detail.redeemCount ?? 0)}</Descriptions.Item>
<Descriptions.Item label="操作">
<Button type="link" size="small" style={{ padding: 0 }} onClick={() => navigate(`/logs/stores?storeId=${detail.id}`)}>
@@ -312,6 +329,9 @@ export default function StoresPage() {
<Form.Item name="intro" label="介绍"><Input.TextArea rows={4} /></Form.Item>
<Form.Item name="district" label="区县"><Input /></Form.Item>
<Form.Item name="address" label="详细地址"><Input /></Form.Item>
<Form.Item name="settlementRate" label="核销结算比例 %" initialValue={60} rules={[{ required: true }]}>
<InputNumber min={0} max={100} precision={2} style={{ width: '100%' }} addonAfter="%" />
</Form.Item>
</Form>
</>
)}
@@ -339,16 +359,16 @@ export default function StoresPage() {
)}
<Form form={createForm} layout="vertical" preserve>
<div style={{ display: createStep === 0 ? 'block' : 'none' }}>
<Form.Item name="partnerId" label="开城合伙人" rules={[{ required: true, message: '请选择开城合伙人' }]}>
<Form.Item name="partnerAccountId" label="开城合伙人" rules={[{ required: true, message: '请选择开城合伙人' }]}>
<Select
showSearch
loading={optionsLoading}
optionFilterProp="label"
placeholder={optionsLoading ? '加载中…' : '请选择开城合伙人'}
options={partners.map((p) => ({ value: p.id, label: p.companyName }))}
onChange={(partnerId) => {
onChange={(partnerAccountId) => {
const codes = createForm.getFieldValue('regionCodes') as string[] | undefined;
if (codes?.length === 3) bindRegionSelection(codes, partnerId);
if (codes?.length === 3) bindRegionSelection(codes, partnerAccountId);
else createForm.setFieldValue('cityId', undefined);
}}
/>
@@ -418,6 +438,9 @@ export default function StoresPage() {
<Form.Item name="bankBranch" label="开户支行" rules={[{ required: true, message: '请填写开户支行' }]}>
<Input placeholder="例如:中国工商银行洛阳分行" />
</Form.Item>
<Form.Item name="settlementRate" label="核销结算比例 %" initialValue={60} rules={[{ required: true, message: '请填写结算比例' }]}>
<InputNumber min={0} max={100} precision={2} style={{ width: '100%' }} addonAfter="%" />
</Form.Item>
<Alert
type="info"
showIcon