feat(ops): v4.0.4 开发任务多行与版本状态同步、合伙人/门店删除、账单合伙人列
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
@@ -42,6 +44,7 @@ type PartnerRow = {
|
||||
districtCodes?: string[] | null;
|
||||
orderCommissionRate?: number;
|
||||
redeemCommissionRate?: number;
|
||||
storeCount?: number;
|
||||
accountCount: number;
|
||||
createdAt: string;
|
||||
};
|
||||
@@ -121,6 +124,7 @@ export default function CityPartnersPanel({
|
||||
maxPartnerCommissionRate = 0.05,
|
||||
onChanged,
|
||||
}: Props) {
|
||||
const navigate = useNavigate();
|
||||
const [editForm] = Form.useForm();
|
||||
const [createForm] = Form.useForm();
|
||||
const [subForm] = Form.useForm();
|
||||
@@ -204,6 +208,31 @@ export default function CityPartnersPanel({
|
||||
}
|
||||
}
|
||||
|
||||
async function deletePartner(row: { id: string; storeCount?: number }) {
|
||||
if ((row.storeCount ?? 0) > 0) {
|
||||
Modal.warning({
|
||||
title: '请先删除门店',
|
||||
content: `该城市合伙人下还有 ${row.storeCount} 家门店,请先删除门店后再删除合伙人。`,
|
||||
okText: '查看门店',
|
||||
onOk: () => navigate(`/stores?partnerId=${row.id}`),
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await request(`/admin/partners/${row.id}`, { method: 'DELETE' });
|
||||
message.success('城市合伙人已删除');
|
||||
if (detail?.id === row.id) {
|
||||
setDrawerOpen(false);
|
||||
setDetail(null);
|
||||
}
|
||||
void loadPartners();
|
||||
onChanged?.();
|
||||
} catch (e) {
|
||||
const msg = formatApiError(e);
|
||||
if (msg) message.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSubAccount(subId: string) {
|
||||
await request(`/admin/partner-accounts/${subId}`, { method: 'DELETE' });
|
||||
message.success('子账号已删除');
|
||||
@@ -237,11 +266,24 @@ export default function CityPartnersPanel({
|
||||
{ title: '创建', dataIndex: 'createdAt', width: 150, render: fmtTime },
|
||||
{
|
||||
title: '操作',
|
||||
width: 80,
|
||||
width: 140,
|
||||
render: (_, row) => (
|
||||
<Button type="link" size="small" onClick={() => void openPartner(row.id)}>
|
||||
管理
|
||||
</Button>
|
||||
<Space size={0}>
|
||||
<Button type="link" size="small" onClick={() => void openPartner(row.id)}>
|
||||
管理
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认删除该城市合伙人?"
|
||||
description="若有门店需先删除门店;账号下有订单或核销单将禁止删除。"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void deletePartner(row)}
|
||||
>
|
||||
<Button type="link" size="small" danger>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -272,7 +314,22 @@ export default function CityPartnersPanel({
|
||||
width={640}
|
||||
open={drawerOpen}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
extra={<Button type="primary" onClick={() => void savePartner()}>保存</Button>}
|
||||
extra={
|
||||
<Space>
|
||||
{detail ? (
|
||||
<Popconfirm
|
||||
title="确认删除该城市合伙人?"
|
||||
description="若有门店需先删除门店;账号下有订单或核销单将禁止删除。"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void deletePartner(detail)}
|
||||
>
|
||||
<Button danger>删除</Button>
|
||||
</Popconfirm>
|
||||
) : null}
|
||||
<Button type="primary" onClick={() => void savePartner()}>保存</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{detail && (
|
||||
<Tabs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@@ -127,6 +127,7 @@ function formatApiError(err: unknown): string | null {
|
||||
}
|
||||
|
||||
export default function CityPartnersPage() {
|
||||
const navigate = useNavigate();
|
||||
const [filterForm] = Form.useForm();
|
||||
const [editForm] = Form.useForm();
|
||||
const [createForm] = Form.useForm();
|
||||
@@ -234,6 +235,30 @@ export default function CityPartnersPage() {
|
||||
void reload();
|
||||
}
|
||||
|
||||
async function deletePartner(row: { id: string; storeCount?: number }) {
|
||||
if ((row.storeCount ?? 0) > 0) {
|
||||
Modal.warning({
|
||||
title: '请先删除门店',
|
||||
content: `该城市合伙人下还有 ${row.storeCount} 家门店,请先删除门店后再删除合伙人。`,
|
||||
okText: '查看门店',
|
||||
onOk: () => navigate(`/stores?partnerId=${row.id}`),
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await request(`/admin/partners/${row.id}`, { method: 'DELETE' });
|
||||
message.success('城市合伙人已删除');
|
||||
if (detail?.id === row.id) {
|
||||
setDrawerOpen(false);
|
||||
setDetail(null);
|
||||
}
|
||||
void reload();
|
||||
} catch (e) {
|
||||
const msg = formatApiError(e);
|
||||
if (msg) message.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSubAccount(subId: string, parentId: string) {
|
||||
await request(`/admin/partner-accounts/${subId}`, { method: 'DELETE' });
|
||||
message.success('子账号已删除');
|
||||
@@ -352,12 +377,25 @@ export default function CityPartnersPage() {
|
||||
{ title: '创建', dataIndex: 'createdAt', width: 150, render: fmtTime },
|
||||
{
|
||||
title: '操作',
|
||||
width: 80,
|
||||
width: 140,
|
||||
fixed: 'right',
|
||||
render: (_, row) => (
|
||||
<Button type="link" size="small" onClick={() => void openPartner(row.id)}>
|
||||
管理
|
||||
</Button>
|
||||
<Space size={0}>
|
||||
<Button type="link" size="small" onClick={() => void openPartner(row.id)}>
|
||||
管理
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认删除该城市合伙人?"
|
||||
description="若有门店需先删除门店;账号下有订单或核销单将禁止删除。"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void deletePartner(row)}
|
||||
>
|
||||
<Button type="link" size="small" danger>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
@@ -491,9 +529,22 @@ export default function CityPartnersPage() {
|
||||
open={drawerOpen}
|
||||
onClose={() => setDrawerOpen(false)}
|
||||
extra={
|
||||
<Button type="primary" onClick={() => void savePartner()}>
|
||||
保存
|
||||
</Button>
|
||||
<Space>
|
||||
{detail ? (
|
||||
<Popconfirm
|
||||
title="确认删除该城市合伙人?"
|
||||
description="若有门店需先删除门店;账号下有订单或核销单将禁止删除。"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void deletePartner(detail)}
|
||||
>
|
||||
<Button danger>删除</Button>
|
||||
</Popconfirm>
|
||||
) : null}
|
||||
<Button type="primary" onClick={() => void savePartner()}>
|
||||
保存
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{detail && (
|
||||
|
||||
@@ -274,7 +274,14 @@ export default function DevPlanTasksPage() {
|
||||
<Tag color={STATUS_COLOR[s]}>{DEV_PLAN_TASK_STATUS_LABELS[s]}</Tag>
|
||||
),
|
||||
},
|
||||
{ title: '内容', dataIndex: 'content' },
|
||||
{
|
||||
title: '内容',
|
||||
dataIndex: 'content',
|
||||
width: 360,
|
||||
render: (v: string) => (
|
||||
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', maxWidth: 480 }}>{v}</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '附件',
|
||||
width: 100,
|
||||
|
||||
@@ -57,6 +57,7 @@ type Row = {
|
||||
redeemItems?: BillItemRow[];
|
||||
partner?: {
|
||||
companyName?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
bankAccountName?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
@@ -64,6 +65,7 @@ type Row = {
|
||||
};
|
||||
partnerAccount?: {
|
||||
companyName?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
bankAccountName?: string | null;
|
||||
bankAccountNo?: string | null;
|
||||
@@ -71,7 +73,7 @@ type Row = {
|
||||
};
|
||||
};
|
||||
|
||||
type PartnerOption = { id: string; companyName: string; phone?: string };
|
||||
type PartnerOption = { id: string; companyName?: string | null; name?: string | null; phone?: string };
|
||||
|
||||
const STATUS_LABELS: Record<string, string> = {
|
||||
PENDING_REVIEW: '待审核',
|
||||
@@ -89,6 +91,18 @@ const STATUS_COLORS: Record<string, string> = {
|
||||
REJECTED: 'red',
|
||||
};
|
||||
|
||||
function partnerDashLabel(p?: {
|
||||
companyName?: string | null;
|
||||
name?: string | null;
|
||||
phone?: string | null;
|
||||
} | null): string {
|
||||
if (!p) return '—';
|
||||
const company = (p.companyName || '').trim();
|
||||
const person = (p.name || '').trim();
|
||||
if (company && person) return `${company}-${person}`;
|
||||
return company || person || (p.phone || '').trim() || '—';
|
||||
}
|
||||
|
||||
export default function PartnerBillsPage() {
|
||||
const [form] = Form.useForm();
|
||||
const [filters, setFilters] = useState<Record<string, string>>({});
|
||||
@@ -257,7 +271,7 @@ export default function PartnerBillsPage() {
|
||||
}
|
||||
|
||||
const summary = data?.summary;
|
||||
const partnerName = (r: Row) => r.partner?.companyName || r.partnerAccount?.companyName || '—';
|
||||
const partnerName = (r: Row) => partnerDashLabel(r.partnerAccount ?? r.partner);
|
||||
const partnerBank = (r: Row) => r.partnerAccount ?? r.partner;
|
||||
const selectedRows = (data?.items ?? []).filter((r) => selectedKeys.includes(r.id));
|
||||
const canSend = selectedRows.filter((r) => r.status === 'PENDING_REVIEW' || r.status === 'REJECTED');
|
||||
@@ -275,7 +289,7 @@ export default function PartnerBillsPage() {
|
||||
},
|
||||
{
|
||||
title: '合伙人',
|
||||
width: 160,
|
||||
width: 200,
|
||||
render: (_, r) => partnerName(r),
|
||||
},
|
||||
{
|
||||
@@ -406,7 +420,7 @@ export default function PartnerBillsPage() {
|
||||
optionFilterProp="label"
|
||||
options={partners.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.companyName || p.phone || p.id,
|
||||
label: partnerDashLabel(p),
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -503,7 +517,7 @@ export default function PartnerBillsPage() {
|
||||
placeholder="全部主合伙人"
|
||||
options={partners.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.companyName || p.phone || p.id,
|
||||
label: partnerDashLabel(p),
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Steps,
|
||||
@@ -302,6 +303,7 @@ export default function StoresPage() {
|
||||
const [categoryTree, setCategoryTree] = useState<CategoryNode[]>([]);
|
||||
const [optionsLoading, setOptionsLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [phoneMismatch, setPhoneMismatch] = useState<string | null>(null);
|
||||
const deepLinkStoreOpenedRef = useRef(false);
|
||||
|
||||
@@ -372,6 +374,23 @@ export default function StoresPage() {
|
||||
.map((n) => ({ value: n.id, label: n.name }));
|
||||
}, [categoryTree, editCategoryParentId]);
|
||||
|
||||
async function deleteStore(id: string) {
|
||||
setDeleting(true);
|
||||
try {
|
||||
await request(`/admin/stores/${id}`, { method: 'DELETE' });
|
||||
message.success('门店及门店账号已删除');
|
||||
if (detail && String(detail.id) === id) {
|
||||
setDrawerOpen(false);
|
||||
setDetail(null);
|
||||
}
|
||||
void reload();
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '删除失败');
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function openStoreDetail(row: StoreRow, opts?: { tab?: string }) {
|
||||
const [d, cats] = await Promise.all([
|
||||
request<Record<string, unknown>>(`/admin/stores/${row.id}`),
|
||||
@@ -821,7 +840,7 @@ export default function StoresPage() {
|
||||
{
|
||||
key: 'actions',
|
||||
title: '操作',
|
||||
width: 280,
|
||||
width: 320,
|
||||
fixed: 'right',
|
||||
render: (_, row) => (
|
||||
<Space size={0} wrap>
|
||||
@@ -861,6 +880,17 @@ export default function StoresPage() {
|
||||
>
|
||||
提现
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title="确认删除该门店?"
|
||||
description="将同时删除门店账号;若有关联订单或核销单将禁止删除。"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void deleteStore(row.id)}
|
||||
>
|
||||
<Button type="link" size="small" danger>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@@ -1034,6 +1064,17 @@ export default function StoresPage() {
|
||||
void reload();
|
||||
}} />
|
||||
<Button type="primary" loading={saving} onClick={() => void saveStoreDetail()}>保存修改</Button>
|
||||
{detail?.id ? (
|
||||
<Popconfirm
|
||||
title="确认删除该门店?"
|
||||
description="将同时删除门店账号;若有关联订单或核销单将禁止删除。"
|
||||
okText="删除"
|
||||
okButtonProps={{ danger: true }}
|
||||
onConfirm={() => void deleteStore(String(detail.id))}
|
||||
>
|
||||
<Button danger loading={deleting}>删除门店</Button>
|
||||
</Popconfirm>
|
||||
) : null}
|
||||
</Space>
|
||||
)}>
|
||||
{detail && (
|
||||
|
||||
Reference in New Issue
Block a user