feat(admin): gate user/order/city delete behind assignable HQ permissions
CI / verify (pull_request) Has been cancelled

Add users_delete, orders_delete, cities_delete; default HQ (incl. super admin) has none until explicitly granted.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-28 14:01:18 +08:00
parent f4766d32ff
commit daeb24bbeb
10 changed files with 158 additions and 92 deletions
+18 -10
View File
@@ -18,7 +18,7 @@ import {
message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { request, type AdminOrderItem, type AdminOrderRow, type Paginated } from '../lib/api';
import { request, type AdminOrderItem, type AdminOrderRow, type HqProfile, type Paginated } from '../lib/api';
import {
ADMIN_OPTIONS_PAGE_SIZE,
DELIVERY_PROVIDER_LABELS,
@@ -154,6 +154,7 @@ export default function OrdersPage() {
const [form] = Form.useForm();
const [shipForm] = Form.useForm();
const [logisticsForm] = Form.useForm();
const [profile, setProfile] = useState<HqProfile | null>(null);
const [data, setData] = useState<Paginated<AdminOrderRow> | null>(null);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
@@ -174,12 +175,17 @@ export default function OrdersPage() {
const [shipTarget, setShipTarget] = useState<OrderDetail | null>(null);
const [shipMode, setShipMode] = useState<ShipMode>('EXPRESS');
const [warehouses, setWarehouses] = useState<WarehouseOption[]>([]);
const canDeleteOrders = (profile?.permissionKeys ?? []).includes('orders_delete');
const selectedOrders = useMemo(
() => (data?.items ?? []).filter((row) => selectedRowKeys.includes(row.id)),
[data?.items, selectedRowKeys],
);
useEffect(() => {
request<HqProfile>('/admin/auth/me').then(setProfile).catch(() => {});
}, []);
async function openRedeemDetail(redeemId: string) {
setRedeemDetailLoading(true);
setRedeemDrawerOpen(true);
@@ -454,13 +460,15 @@ export default function OrdersPage() {
<div>
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
<Typography.Title level={4} style={{ margin: 0 }}></Typography.Title>
<Button
danger
disabled={!selectedRowKeys.length}
onClick={() => setBatchDeleteOpen(true)}
>
{selectedRowKeys.length ? ` (${selectedRowKeys.length})` : ''}
</Button>
{canDeleteOrders ? (
<Button
danger
disabled={!selectedRowKeys.length}
onClick={() => setBatchDeleteOpen(true)}
>
{selectedRowKeys.length ? ` (${selectedRowKeys.length})` : ''}
</Button>
) : null}
</Space>
<Form form={form} layout="inline" style={{ marginBottom: 16 }} onFinish={() => { setPage(1); void load(); }}>
<Form.Item name="orderNo" label="订单号">
@@ -504,10 +512,10 @@ export default function OrdersPage() {
columns={columns}
dataSource={data?.items ?? []}
scroll={{ x: 1500 }}
rowSelection={{
rowSelection={canDeleteOrders ? {
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys as string[]),
}}
} : undefined}
pagination={{
current: page,
pageSize,