城市合伙人县区不做限制

This commit is contained in:
2026-07-22 23:06:51 +08:00
parent 8c4419e04e
commit fb530e2ff5
7 changed files with 47 additions and 37 deletions
@@ -26,7 +26,7 @@ import {
type PartnerPermissionKey, type PartnerPermissionKey,
} from '@dukang/shared-types'; } from '@dukang/shared-types';
import { request, type Paginated } from '../lib/api'; import { request, type Paginated } from '../lib/api';
import { districtCodeLabel } from '../lib/china-region'; import { districtCodeLabel, formatDistrictLabels } from '../lib/china-region';
import { fmtTime } from '../lib/constants'; import { fmtTime } from '../lib/constants';
import CityDistrictMultiSelect from './CityDistrictMultiSelect'; import CityDistrictMultiSelect from './CityDistrictMultiSelect';
import PartnerSubAccountList from './PartnerSubAccountList'; import PartnerSubAccountList from './PartnerSubAccountList';
@@ -37,6 +37,7 @@ type PartnerRow = {
phone: string; phone: string;
name: string; name: string;
scopeType?: string; scopeType?: string;
districtCodes?: string[] | null;
orderCommissionRate?: number; orderCommissionRate?: number;
redeemCommissionRate?: number; redeemCommissionRate?: number;
accountCount: number; accountCount: number;
@@ -211,6 +212,14 @@ export default function CityPartnersPanel({
const columns: ColumnsType<PartnerRow> = [ const columns: ColumnsType<PartnerRow> = [
{ title: '公司名', dataIndex: 'companyName', ellipsis: true }, { title: '公司名', dataIndex: 'companyName', ellipsis: true },
{
title: '区县',
dataIndex: 'districtCodes',
width: 160,
ellipsis: true,
render: (codes: string[] | null | undefined, row) =>
row.scopeType === CityPartnerScopeType.CITY_WIDE ? '全城' : formatDistrictLabels(codes),
},
{ title: '主账号', dataIndex: 'phone', width: 120 }, { title: '主账号', dataIndex: 'phone', width: 120 },
{ {
title: '管辖', title: '管辖',
@@ -287,7 +296,7 @@ export default function CityPartnersPanel({
<Form.Item <Form.Item
name="districtCodes" name="districtCodes"
label="区县" label="区县"
extra="仅选当前开城城市下的区县;不可与其他区域合伙人重合" extra="仅作标识,可多选当前城市下的区县(不做互斥)"
> >
<CityDistrictMultiSelect cityCode={cityCode} /> <CityDistrictMultiSelect cityCode={cityCode} />
</Form.Item> </Form.Item>
@@ -386,7 +395,7 @@ export default function CityPartnersPanel({
name="districtCodes" name="districtCodes"
label="区县" label="区县"
rules={[{ required: true, message: '请选择至少一个区县' }]} rules={[{ required: true, message: '请选择至少一个区县' }]}
extra="仅选当前开城城市下的区县;不可与其他区域合伙人重合" extra="仅作标识,可多选当前城市下的区县(不做互斥)"
> >
<CityDistrictMultiSelect cityCode={cityCode} /> <CityDistrictMultiSelect cityCode={cityCode} />
</Form.Item> </Form.Item>
+6
View File
@@ -106,6 +106,12 @@ export function districtCodeLabel(code: string): string {
return codeToText[code] || code; return codeToText[code] || code;
} }
/** 列表展示:区县码 → 中文名,顿号拼接 */
export function formatDistrictLabels(codes?: string[] | null): string {
if (!codes?.length) return '—';
return codes.map((c) => districtCodeLabel(String(c))).join('、');
}
export function parseRegionCodes(codes?: string[]): ParsedChinaRegion | null { export function parseRegionCodes(codes?: string[]): ParsedChinaRegion | null {
if (!codes || codes.length < 3) return null; if (!codes || codes.length < 3) return null;
const [provinceCode, cityCode, districtCode] = codes; const [provinceCode, cityCode, districtCode] = codes;
+13 -4
View File
@@ -31,7 +31,7 @@ import {
type PartnerPermissionKey, type PartnerPermissionKey,
} from '@dukang/shared-types'; } from '@dukang/shared-types';
import { request, type Paginated } from '../lib/api'; import { request, type Paginated } from '../lib/api';
import { districtCodeLabel } from '../lib/china-region'; import { districtCodeLabel, formatDistrictLabels } from '../lib/china-region';
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants'; import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
import { useAdminList } from '../lib/useAdminList'; import { useAdminList } from '../lib/useAdminList';
import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect'; import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect';
@@ -48,6 +48,7 @@ type Row = {
cityId?: string | null; cityId?: string | null;
cityName?: string | null; cityName?: string | null;
scopeType?: string; scopeType?: string;
districtCodes?: string[] | null;
orderCommissionRate?: number; orderCommissionRate?: number;
redeemCommissionRate?: number; redeemCommissionRate?: number;
bindingStatus?: string; bindingStatus?: string;
@@ -259,7 +260,15 @@ export default function CityPartnersPage() {
} }
const columns: ColumnsType<Row> = [ const columns: ColumnsType<Row> = [
{ title: '公司名', dataIndex: 'companyName', ellipsis: true }, { title: '公司名', dataIndex: 'companyName', ellipsis: true, width: 140 },
{
title: '区县',
dataIndex: 'districtCodes',
width: 160,
ellipsis: true,
render: (codes: string[] | null | undefined, row) =>
row.scopeType === CityPartnerScopeType.CITY_WIDE ? '全城' : formatDistrictLabels(codes),
},
{ title: '城市', dataIndex: 'cityName', width: 90 }, { title: '城市', dataIndex: 'cityName', width: 90 },
{ title: '主账号姓名', dataIndex: 'name', width: 100, ellipsis: true }, { title: '主账号姓名', dataIndex: 'name', width: 100, ellipsis: true },
{ title: '登录手机', dataIndex: 'phone', width: 120 }, { title: '登录手机', dataIndex: 'phone', width: 120 },
@@ -476,7 +485,7 @@ export default function CityPartnersPage() {
<Form.Item <Form.Item
name="districtCodes" name="districtCodes"
label="区县" label="区县"
extra="仅选当前开城城市下的区县;不可与其他区域合伙人重合" extra="仅作标识,可多选当前城市下的区县(不做互斥)"
> >
<CityDistrictMultiSelect cityCode={editCityCode} /> <CityDistrictMultiSelect cityCode={editCityCode} />
</Form.Item> </Form.Item>
@@ -591,7 +600,7 @@ export default function CityPartnersPage() {
name="districtCodes" name="districtCodes"
label="区县" label="区县"
rules={[{ required: true, message: '请选择至少一个区县' }]} rules={[{ required: true, message: '请选择至少一个区县' }]}
extra="仅选当前开城城市下的区县;不可与其他区域合伙人重合" extra="仅作标识,可多选当前城市下的区县(不做互斥)"
> >
<CityDistrictMultiSelect cityCode={createCityCode} /> <CityDistrictMultiSelect cityCode={createCityCode} />
</Form.Item> </Form.Item>
+12 -3
View File
@@ -23,7 +23,7 @@ import {
type PartnerPermissionKey, type PartnerPermissionKey,
} from '@dukang/shared-types'; } from '@dukang/shared-types';
import { request, type Paginated } from '../lib/api'; import { request, type Paginated } from '../lib/api';
import { districtCodeLabel } from '../lib/china-region'; import { districtCodeLabel, formatDistrictLabels } from '../lib/china-region';
import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants'; import { ADMIN_OPTIONS_PAGE_SIZE, fmtTime } from '../lib/constants';
import { useAdminList } from '../lib/useAdminList'; import { useAdminList } from '../lib/useAdminList';
import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect'; import CityDistrictMultiSelect from '../components/CityDistrictMultiSelect';
@@ -37,6 +37,7 @@ type Row = {
cityId?: string | null; cityId?: string | null;
cityName?: string | null; cityName?: string | null;
scopeType?: string; scopeType?: string;
districtCodes?: string[] | null;
orderCommissionRate?: number; orderCommissionRate?: number;
redeemCommissionRate?: number; redeemCommissionRate?: number;
storeCount: number; storeCount: number;
@@ -174,6 +175,14 @@ export default function PartnersPage() {
const columns: ColumnsType<Row> = [ const columns: ColumnsType<Row> = [
{ title: '公司名', dataIndex: 'companyName', ellipsis: true }, { title: '公司名', dataIndex: 'companyName', ellipsis: true },
{
title: '区县',
dataIndex: 'districtCodes',
width: 160,
ellipsis: true,
render: (codes: string[] | null | undefined, row) =>
row.scopeType === CityPartnerScopeType.CITY_WIDE ? '全城' : formatDistrictLabels(codes),
},
{ title: '城市', dataIndex: 'cityName', width: 100 }, { title: '城市', dataIndex: 'cityName', width: 100 },
{ title: '主账号', dataIndex: 'phone', width: 130 }, { title: '主账号', dataIndex: 'phone', width: 130 },
{ {
@@ -251,7 +260,7 @@ export default function PartnersPage() {
<Form.Item <Form.Item
name="districtCodes" name="districtCodes"
label="区县" label="区县"
extra="仅选当前开城城市下的区县;不可与其他区域合伙人重合" extra="仅作标识,可多选当前城市下的区县(不做互斥)"
> >
<CityDistrictMultiSelect cityCode={editCityCode} /> <CityDistrictMultiSelect cityCode={editCityCode} />
</Form.Item> </Form.Item>
@@ -351,7 +360,7 @@ export default function PartnersPage() {
name="districtCodes" name="districtCodes"
label="区县" label="区县"
rules={[{ required: true, message: '请选择至少一个区县' }]} rules={[{ required: true, message: '请选择至少一个区县' }]}
extra="仅选当前开城城市下的区县;不可与其他区域合伙人重合" extra="仅作标识,可多选当前城市下的区县(不做互斥)"
> >
<CityDistrictMultiSelect cityCode={createCityCode} /> <CityDistrictMultiSelect cityCode={createCityCode} />
</Form.Item> </Form.Item>
+2 -5
View File
@@ -61,7 +61,7 @@ describe('validatePartnerCityBinding', () => {
expect(result.ok).toBe(false); expect(result.ok).toBe(false);
}); });
it('rejects overlapping district codes', () => { it('allows overlapping district codes as labels', () => {
const result = validatePartnerCityBinding( const result = validatePartnerCityBinding(
[ [
{ {
@@ -74,10 +74,7 @@ describe('validatePartnerCityBinding', () => {
], ],
{ partnerAccountId: '11', scopeType: 'DISTRICT', districtCodes: ['410105'] }, { partnerAccountId: '11', scopeType: 'DISTRICT', districtCodes: ['410105'] },
); );
expect(result.ok).toBe(false); expect(result.ok).toBe(true);
expect(result.message).toContain('区域重合');
expect(result.message).toContain('甲公司');
expect(result.occupiedCodes).toEqual(['410105']);
}); });
it('allows valid district binding', () => { it('allows valid district binding', () => {
+1 -22
View File
@@ -22,7 +22,6 @@ export interface PartnerCityResolveRef {
export interface PartnerCityValidationResult { export interface PartnerCityValidationResult {
ok: boolean; ok: boolean;
message?: string; message?: string;
occupiedCodes?: string[];
} }
function normalizeDistrictCodes(codes?: string[] | null): string[] { function normalizeDistrictCodes(codes?: string[] | null): string[] {
@@ -101,27 +100,7 @@ export function validatePartnerCityBinding(
return { ok: false, message: '区域合伙人须至少选择一个区县' }; return { ok: false, message: '区域合伙人须至少选择一个区县' };
} }
const occupiedBy = new Map<string, string>(); // 区县仅为标识,允许多个区域合伙人选择相同区县
for (const row of others) {
if (row.scopeType !== 'DISTRICT') continue;
const owner = row.companyName?.trim() || '其他区域合伙人';
for (const code of normalizeDistrictCodes(row.districtCodes)) {
if (!occupiedBy.has(code)) occupiedBy.set(code, owner);
}
}
const overlaps = districts.filter((code) => occupiedBy.has(code));
if (overlaps.length) {
const detail = overlaps
.map((code) => `${code}${occupiedBy.get(code)}`)
.join('、');
return {
ok: false,
message: `区域重合:以下区县已被占用,请改选其他区县 — ${detail}`,
occupiedCodes: overlaps,
};
}
return { ok: true }; return { ok: true };
} }
@@ -80,6 +80,7 @@ export class AdminPartnersService {
maxPartnerCommissionRate: maxPartnerCommissionRate:
p.city?.maxPartnerCommissionRate != null ? Number(p.city.maxPartnerCommissionRate) : null, p.city?.maxPartnerCommissionRate != null ? Number(p.city.maxPartnerCommissionRate) : null,
scopeType: p.scopeType, scopeType: p.scopeType,
districtCodes: this.partnerCityService.parseDistrictCodes(p.districtCodes),
orderCommissionRate: Number(p.orderCommissionRate ?? 0), orderCommissionRate: Number(p.orderCommissionRate ?? 0),
redeemCommissionRate: Number(p.redeemCommissionRate ?? 0.03), redeemCommissionRate: Number(p.redeemCommissionRate ?? 0.03),
bindingStatus: p.bindingStatus, bindingStatus: p.bindingStatus,