From 0cb2b2cebb2d48cb937f720a8d5d835e579db3e5 Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Tue, 28 Jul 2026 15:10:14 +0800 Subject: [PATCH] feat(admin): store detail edit with login phone sync; tighten HQ UI HQ store update now syncs store_account.phone for shop login; expand editable store fields. System version only for SUPER_ADMIN; permissions checklist layout polish. Co-authored-by: Cursor --- apps/admin-web/src/pages/DashboardPage.tsx | 34 +- .../admin-web/src/pages/HqPermissionsPage.tsx | 123 +++- apps/admin-web/src/pages/StoresPage.tsx | 580 +++++++++++------- .../modules/ops/admin-dashboard.controller.ts | 2 + .../src/modules/ops/admin-stores.service.ts | 183 ++++-- .../src/modules/ops/dto/admin-mutate.dto.ts | 24 + 6 files changed, 638 insertions(+), 308 deletions(-) diff --git a/apps/admin-web/src/pages/DashboardPage.tsx b/apps/admin-web/src/pages/DashboardPage.tsx index c9e6637..6bf200f 100644 --- a/apps/admin-web/src/pages/DashboardPage.tsx +++ b/apps/admin-web/src/pages/DashboardPage.tsx @@ -52,8 +52,18 @@ export default function DashboardPage() { request('/admin/dashboard/stats') .then(setStats) .finally(() => setLoading(false)); - void loadVersion(); - request('/admin/auth/me').then(setProfile).catch(() => {}); + request('/admin/auth/me') + .then((p) => { + setProfile(p); + if (p.adminRole === 'SUPER_ADMIN') { + void loadVersion(); + } else { + setVersionLoading(false); + } + }) + .catch(() => { + setVersionLoading(false); + }); }, [loadVersion]); function handleDeploy() { @@ -83,6 +93,7 @@ export default function DashboardPage() {
数据概览 + {isSuperAdmin ? ( } onClick={() => void loadVersion()}> 刷新版本 - {isSuperAdmin && ( - - )} + } > @@ -122,6 +131,7 @@ export default function DashboardPage() { 尚未记录发版信息 )} + ) : null} diff --git a/apps/admin-web/src/pages/HqPermissionsPage.tsx b/apps/admin-web/src/pages/HqPermissionsPage.tsx index 084a5cf..15fd956 100644 --- a/apps/admin-web/src/pages/HqPermissionsPage.tsx +++ b/apps/admin-web/src/pages/HqPermissionsPage.tsx @@ -5,7 +5,6 @@ import { Card, Checkbox, Col, - Divider, Form, Row, Select, @@ -36,6 +35,13 @@ const ROLE_LABELS = Object.fromEntries(HQ_ADMIN_ROLES.map((r) => [r.value, r.lab const CATALOG_GROUPS = [...new Set(HQ_PERMISSION_CATALOG.map((p) => p.group ?? '其他'))]; +function groupColSpan(itemCount: number): number { + if (itemCount <= 2) return 12; + if (itemCount === 3) return 8; + if (itemCount === 4) return 6; + return 8; +} + function PermissionChecklist({ value, onChange, @@ -54,19 +60,39 @@ function PermissionChecklist({ > {CATALOG_GROUPS.map((group) => { const items = HQ_PERMISSION_CATALOG.filter((p) => (p.group ?? '其他') === group); + const span = groupColSpan(items.length); + const compact = items.length <= 3; return ( -
- +
+ {group} - - {items.map((item) => ( - - {item.label} - - ))} - - + {compact ? ( + + {items.map((item) => ( + + {item.label} + + ))} + + ) : ( + + {items.map((item) => ( + + {item.label} + + ))} + + )}
); })} @@ -88,6 +114,12 @@ export default function HqPermissionsPage() { const [accountLoading, setAccountLoading] = useState(false); const [accountSaving, setAccountSaving] = useState(false); + const selectedAccount = useMemo( + () => accounts.find((a) => a.id === accountId), + [accounts, accountId], + ); + const selectedIsSuperAdmin = selectedAccount?.adminRole === 'SUPER_ADMIN'; + const previewEffectiveKeys = useMemo( () => [...new Set([...roleInheritedKeys, ...accountKeys])], [roleInheritedKeys, accountKeys], @@ -243,32 +275,57 @@ export default function HqPermissionsPage() { ) : ( <> - - 角色继承: - {roleInheritedKeys.map((key) => { - const item = HQ_PERMISSION_CATALOG.find((p) => p.key === key); - return ( - - {item?.group ? `${item.group}·${item.label}` : item?.label || key} - - ); - })} - +
+ 角色继承: + {selectedIsSuperAdmin ? ( + 超级管理员基础权限(不含危险操作) + ) : ( + + {roleInheritedKeys.map((key) => { + const item = HQ_PERMISSION_CATALOG.find((p) => p.key === key); + return ( + + {item?.label || key} + + ); + })} + {!roleInheritedKeys.length ? ( + + ) : null} + + )} +
下方勾选为用户专属追加权限(保存后与角色权限合并生效)。危险操作(删除用户/订单/城市)默认不授予,需在此勾选。 - - 合并生效: - {previewEffectiveKeys.map((key) => { - const item = HQ_PERMISSION_CATALOG.find((p) => p.key === (key as HqPermissionKey)); - return ( - - {item?.group ? `${item.group}·${item.label}` : item?.label || key} - - ); - })} - +
+ 合并生效: + {selectedIsSuperAdmin ? ( + + 基础权限(全部) + {accountKeys.map((key) => { + const item = HQ_PERMISSION_CATALOG.find((p) => p.key === (key as HqPermissionKey)); + return ( + + {item?.label || key} + + ); + })} + + ) : ( + + {previewEffectiveKeys.map((key) => { + const item = HQ_PERMISSION_CATALOG.find((p) => p.key === (key as HqPermissionKey)); + return ( + + {item?.label || key} + + ); + })} + + )} +
+ ), }, ]; @@ -670,7 +779,7 @@ export default function StoresPage() { ) : null} - - - - - - - - - - - - - - - - - - - - - - - - 搜索或拖图确认位置后自动填入经纬度 - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ + + {String(detail.id)} + + + {STORE_AUDIT_STATUS_LABELS[String(detail.auditStatus || 'APPROVED')] || String(detail.auditStatus)} + + + {String(detail.auditStatus) === 'REJECTED' ? ( + {String(detail.rejectReason || '—')} + ) : null} + {String(detail.redeemCount ?? 0)} + + + + {Array.isArray(detail.audits) && (detail.audits as Array>).length > 0 ? ( + + + {(detail.audits as Array>).map((a) => ( + + {fmtTime(String(a.createdAt))} · {String(a.status)} · {String(a.remark || '—')} + + ))} + + + ) : null} + + 编辑门店信息 + {phoneMismatch ? ( + + ) : null} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ), + }, + { + key: 'settlement', + label: '结算资质', + forceRender: true, + children: ( + <> + + + + + + + + + + { + const v = String(value || '').trim(); + if (!v) return; + if (!/^\d{16,19}$/.test(v)) { + throw new Error('银行卡号须为 16–19 位数字'); + } + }, + }, + ]} + > + + + + 修改后点右上角「保存修改」一并提交。 + + + ), + }, + { + key: 'media', + label: '审核材料', + children: , + }, + ]} + /> + )} { + await tx.store.update({ + where: { id }, + data: { + ...(dto.name !== undefined ? { name: dto.name.trim() } : {}), + ...(normalizedPhone !== undefined ? { phone: normalizedPhone } : {}), + ...(dto.intro !== undefined ? { intro: dto.intro } : {}), + ...(dto.benefitUsageRule !== undefined + ? { benefitUsageRule: String(dto.benefitUsageRule).trim() || null } + : {}), + ...(dto.address !== undefined ? { address: dto.address.trim() } : {}), + ...(dto.province !== undefined ? { province: dto.province.trim() } : {}), + ...(dto.city !== undefined ? { cityName: dto.city.trim() } : {}), + ...(dto.district !== undefined ? { district: dto.district.trim() } : {}), + ...(categoryId !== undefined ? { categoryId } : {}), + ...(dto.settlementRate !== undefined ? { settlementRate: dto.settlementRate } : {}), + ...(dto.openTime !== undefined ? { openTime: dto.openTime } : {}), + ...(dto.closeTime !== undefined ? { closeTime: dto.closeTime } : {}), + ...(dto.openTime2 !== undefined ? { openTime2: dto.openTime2 || null } : {}), + ...(dto.closeTime2 !== undefined ? { closeTime2: dto.closeTime2 || null } : {}), + ...(dto.avgPrice !== undefined ? { avgPrice: dto.avgPrice } : {}), + ...(latitude != null && longitude != null ? { latitude, longitude } : {}), + }, + }); + + if (dto.coverUrl) { + if (current.coverResourceId) { + await tx.commonResource.update({ + where: { id: current.coverResourceId }, + data: { url: dto.coverUrl, ossKey: dto.coverUrl }, + }); + } else { + const cover = await tx.commonResource.create({ + data: { + ownerType: 'STORE', + ownerId: id, + bizType: 'COVER', + mediaType: 'IMAGE', + ossBucket: 'legacy', + ossKey: dto.coverUrl, + url: dto.coverUrl, + }, + }); + await tx.store.update({ where: { id }, data: { coverResourceId: cover.id } }); + } + } + + if (primaryBinding?.storeAccount) { + const account = primaryBinding.storeAccount; + const accountData: { + name?: string; + phone?: string; + bankAccountName?: string | null; + bankAccountNo?: string | null; + bankBranch?: string | null; + } = {}; + if (dto.name !== undefined) accountData.name = dto.name.trim(); + if (normalizedPhone !== undefined && normalizedPhone !== account.phone) { + accountData.phone = normalizedPhone; + } + if (dto.bankAccountName !== undefined) { + accountData.bankAccountName = dto.bankAccountName?.trim() || null; + } + if (dto.bankAccountNo !== undefined) { + accountData.bankAccountNo = dto.bankAccountNo?.trim() || null; + } + if (dto.bankBranch !== undefined) { + accountData.bankBranch = dto.bankBranch?.trim() || null; + } + if (Object.keys(accountData).length) { + await tx.storeAccount.update({ + where: { id: account.id }, + data: accountData, + }); + } + } + }); + return this.detailStore(id); } diff --git a/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts b/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts index 8b6c17b..7f05bab 100644 --- a/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts +++ b/server/dukang-api/src/modules/ops/dto/admin-mutate.dto.ts @@ -162,10 +162,22 @@ export class UpdateStoreDto { @IsNumber() longitude?: number; + @IsOptional() + @IsString() + province?: string; + + @IsOptional() + @IsString() + city?: string; + @IsOptional() @IsString() district?: string; + @IsOptional() + @IsString() + categoryId?: string; + @IsOptional() @IsNumber() @Min(0) @@ -191,6 +203,18 @@ export class UpdateStoreDto { @IsNumber() @Min(0) avgPrice?: number | null; + + @IsOptional() + @IsString() + bankAccountName?: string | null; + + @IsOptional() + @IsString() + bankAccountNo?: string | null; + + @IsOptional() + @IsString() + bankBranch?: string | null; } export class CreateStoreAccountDto {