diff --git a/apps/admin-web/src/lib/hq-log.ts b/apps/admin-web/src/lib/hq-log.ts
index 760c75e..198485c 100644
--- a/apps/admin-web/src/lib/hq-log.ts
+++ b/apps/admin-web/src/lib/hq-log.ts
@@ -27,6 +27,7 @@ export const HQ_OPERATION_ACTION_OPTIONS = [
{ value: 'STORE_AUDIT', label: '门店审核' },
{ value: 'STORE_ACCOUNT_CREATE', label: '新增门店账户' },
{ value: 'STORE_ACCOUNT_UPDATE', label: '编辑门店账户' },
+ { value: 'STORE_ACCOUNT_STAFF_DELETE', label: '删除门店子账号' },
{ value: 'STORE_CATEGORY_CREATE', label: '新增门店分类' },
{ value: 'STORE_CATEGORY_UPDATE', label: '编辑门店分类' },
{ value: 'STORE_CATEGORY_DELETE', label: '删除门店分类' },
diff --git a/apps/admin-web/src/pages/CitiesPage.tsx b/apps/admin-web/src/pages/CitiesPage.tsx
index fca7551..5c6325e 100644
--- a/apps/admin-web/src/pages/CitiesPage.tsx
+++ b/apps/admin-web/src/pages/CitiesPage.tsx
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useState } from 'react';
+import { Link } from 'react-router-dom';
import {
Button,
Descriptions,
@@ -228,7 +229,16 @@ export default function CitiesPage() {
{ title: '省份', dataIndex: 'province', width: 90 },
{ title: '状态', dataIndex: 'status', width: 90, render: (s) => {CITY_STATUS_LABELS[s] || s} },
{ title: '合伙人', dataIndex: 'partnerBindingCount', width: 90 },
- { title: '门店', dataIndex: 'storeCount', width: 70 },
+ {
+ title: '门店',
+ dataIndex: 'storeCount',
+ width: 70,
+ render: (n: number, row) => (
+
+ {n ?? 0}
+
+ ),
+ },
{ title: '订单', dataIndex: 'orderCount', width: 70 },
{ title: '创建', dataIndex: 'createdAt', width: 160, render: fmtTime },
{
diff --git a/apps/admin-web/src/pages/StoreAccountsPage.tsx b/apps/admin-web/src/pages/StoreAccountsPage.tsx
index 5e4c10e..f7e22fc 100644
--- a/apps/admin-web/src/pages/StoreAccountsPage.tsx
+++ b/apps/admin-web/src/pages/StoreAccountsPage.tsx
@@ -1,6 +1,6 @@
import { useState } from 'react';
import {
- Button, Descriptions, Drawer, Form, Input, Modal, Select, Space, Table, Tag, Typography, message,
+ Button, Descriptions, Drawer, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag, Typography, message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { request, type Paginated } from '../lib/api';
@@ -45,12 +45,33 @@ export default function StoreAccountsPage() {
const [drawerOpen, setDrawerOpen] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const [stores, setStores] = useState([]);
+ const [deletingStaffId, setDeletingStaffId] = useState(null);
async function loadStores() {
const res = await request>(`/admin/stores?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`);
setStores(res.items);
}
+ async function refreshDetail(accountId: string) {
+ const d = await request(`/admin/store-accounts/${accountId}`);
+ setDetail(d);
+ void reload();
+ }
+
+ async function deleteStaff(staffId: string) {
+ if (!detail) return;
+ setDeletingStaffId(staffId);
+ try {
+ await request(`/admin/store-accounts/${detail.id}/staff/${staffId}`, { method: 'DELETE' });
+ message.success('子账号已删除');
+ await refreshDetail(detail.id);
+ } catch (e) {
+ message.error(e instanceof Error ? e.message : '删除失败');
+ } finally {
+ setDeletingStaffId(null);
+ }
+ }
+
const columns: ColumnsType = [
{ title: '姓名', dataIndex: 'name', width: 100 },
{ title: '手机', dataIndex: 'phone', width: 120 },
@@ -218,10 +239,32 @@ export default function StoreAccountsPage() {
dataIndex: 'status',
render: (s) => {ACCOUNT_STATUS_LABELS[s] || s},
},
+ {
+ title: '操作',
+ width: 80,
+ render: (_, staff) => (
+ void deleteStaff(staff.id)}
+ >
+
+
+ ),
+ },
]}
/>
>
- ) : null}
+ ) : (
+
+ 暂无子账号
+
+ )}
>
)}
diff --git a/apps/admin-web/src/pages/StoresPage.tsx b/apps/admin-web/src/pages/StoresPage.tsx
index cca51b8..305da2a 100644
--- a/apps/admin-web/src/pages/StoresPage.tsx
+++ b/apps/admin-web/src/pages/StoresPage.tsx
@@ -1,5 +1,5 @@
-import { useMemo, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useEffect, useMemo, useState } from 'react';
+import { useNavigate, useSearchParams } from 'react-router-dom';
import {
Alert,
Button,
@@ -322,10 +322,14 @@ type CategoryNode = {
export default function StoresPage() {
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+ const initialCityId = searchParams.get('cityId') ?? '';
const [form] = Form.useForm();
const [editForm] = Form.useForm();
const [createForm] = Form.useForm();
- const [filters, setFilters] = useState>({});
+ const [filters, setFilters] = useState>(() =>
+ initialCityId ? { cityId: initialCityId } : {},
+ );
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList(
'/admin/stores',
() => {
@@ -334,10 +338,12 @@ export default function StoresPage() {
if (filters.status) qs.set('status', filters.status);
if (filters.auditStatus) qs.set('auditStatus', filters.auditStatus);
if (filters.phone) qs.set('phone', filters.phone);
+ if (filters.cityId) qs.set('cityId', filters.cityId);
return qs;
},
[filters],
);
+ const [filterCities, setFilterCities] = useState([]);
const [detail, setDetail] = useState | null>(null);
const [drawerOpen, setDrawerOpen] = useState(false);
const [rejectOpen, setRejectOpen] = useState(false);
@@ -356,6 +362,25 @@ export default function StoresPage() {
const [saving, setSaving] = useState(false);
const [phoneMismatch, setPhoneMismatch] = useState(null);
+ useEffect(() => {
+ void request>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`)
+ .then((res) => setFilterCities(res.items))
+ .catch(() => {});
+ }, []);
+
+ useEffect(() => {
+ const cityId = searchParams.get('cityId') ?? '';
+ setFilters((prev) => {
+ if ((prev.cityId ?? '') === cityId) return prev;
+ const next = { ...prev };
+ if (cityId) next.cityId = cityId;
+ else delete next.cityId;
+ return next;
+ });
+ form.setFieldsValue({ cityId: cityId || undefined });
+ if (cityId) setPage(1);
+ }, [searchParams, form, setPage]);
+
const selectedPartnerId = Form.useWatch('partnerAccountId', createForm);
const selectedRegionCodes = Form.useWatch('regionCodes', createForm);
const selectedCityId = Form.useWatch('cityId', createForm);
@@ -726,6 +751,16 @@ export default function StoresPage() {
+
+
@@ -738,7 +773,18 @@ export default function StoresPage() {
/>
-
+
+
+
{ setPage(p); setPageSize(ps); } }} />
diff --git a/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts b/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts
index 4d6eb3c..cd84429 100644
--- a/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts
+++ b/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts
@@ -28,6 +28,7 @@ export const HqOperationAction = {
STORE_AUDIT: 'STORE_AUDIT',
STORE_ACCOUNT_CREATE: 'STORE_ACCOUNT_CREATE',
STORE_ACCOUNT_UPDATE: 'STORE_ACCOUNT_UPDATE',
+ STORE_ACCOUNT_STAFF_DELETE: 'STORE_ACCOUNT_STAFF_DELETE',
STORE_MEDIA_CREATE: 'STORE_MEDIA_CREATE',
STORE_MEDIA_UPDATE: 'STORE_MEDIA_UPDATE',
STORE_MEDIA_DELETE: 'STORE_MEDIA_DELETE',
@@ -127,6 +128,7 @@ export const HQ_OPERATION_ACTION_LABELS: Record = {
[HqOperationAction.STORE_AUDIT]: '门店审核',
[HqOperationAction.STORE_ACCOUNT_CREATE]: '新增门店账户',
[HqOperationAction.STORE_ACCOUNT_UPDATE]: '编辑门店账户',
+ [HqOperationAction.STORE_ACCOUNT_STAFF_DELETE]: '删除门店子账号',
[HqOperationAction.STORE_MEDIA_CREATE]: '新增门店资源',
[HqOperationAction.STORE_MEDIA_UPDATE]: '编辑门店资源',
[HqOperationAction.STORE_MEDIA_DELETE]: '删除门店资源',
diff --git a/server/dukang-api/src/modules/ops/admin-stores.controller.ts b/server/dukang-api/src/modules/ops/admin-stores.controller.ts
index 9c0762d..068c874 100644
--- a/server/dukang-api/src/modules/ops/admin-stores.controller.ts
+++ b/server/dukang-api/src/modules/ops/admin-stores.controller.ts
@@ -94,6 +94,16 @@ export class AdminStoreAccountsController {
update(@Param('id') id: string, @Body() dto: UpdateStoreAccountDto) {
return this.service.updateStoreAccount(BigInt(id), dto);
}
+
+ @Delete(':id/staff/:staffId')
+ @HqOperation({
+ action: HqOperationAction.STORE_ACCOUNT_STAFF_DELETE,
+ refType: 'STORE_ACCOUNT',
+ refIdParam: 'staffId',
+ })
+ deleteStaff(@Param('id') id: string, @Param('staffId') staffId: string) {
+ return this.service.deleteStoreStaff(BigInt(id), BigInt(staffId));
+ }
}
@Controller('admin/store-media')
diff --git a/server/dukang-api/src/modules/ops/admin-stores.service.ts b/server/dukang-api/src/modules/ops/admin-stores.service.ts
index 10603b3..07f0a7c 100644
--- a/server/dukang-api/src/modules/ops/admin-stores.service.ts
+++ b/server/dukang-api/src/modules/ops/admin-stores.service.ts
@@ -717,4 +717,26 @@ export class AdminStoresService {
});
return serializeBigInt(account);
}
+
+ /** HQ 删除门店子账号(非主账号) */
+ async deleteStoreStaff(parentAccountId: bigint, staffId: bigint) {
+ const parent = await this.prisma.storeAccount.findUnique({ where: { id: parentAccountId } });
+ if (!parent || parent.isPrimary !== 1) {
+ throw new BadRequestException('主账号不存在');
+ }
+ const staff = await this.prisma.storeAccount.findFirst({
+ where: { id: staffId, parentAccountId, isPrimary: 0 },
+ });
+ if (!staff) throw new NotFoundException('子账号不存在');
+
+ const pending = await this.prisma.redeemPendingRecord.count({
+ where: { storeAccountId: staffId },
+ });
+ if (pending > 0) {
+ throw new BadRequestException('该子账号仍有待处理核销单,无法删除');
+ }
+
+ await this.prisma.storeAccount.delete({ where: { id: staffId } });
+ return { ok: true };
+ }
}