批量政策标签替换功能集成指南
本文档说明如何在广告账户列表页面中集成批量政策标签替换功能。
功能概述
批量政策标签替换功能允许用户: - 在表格中选择多个广告账户 - 批量设置政策标签(折扣、后返、扣点、奖励) - 查看所有选中账户的共同可用标签 - 一次性为所有账户应用相同的政策标签配置
集成步骤
1. 安装依赖
确保项目中已安装所需组件和工具:
import { BatchAccountPolicyLabelDialog, AccountInfo } from '@/components/policy-label';
import { toast } from 'sonner';
2. 在页面中添加状态管理
'use client';
import { useState, useCallback } from 'react';
import { Button } from '@/components/ui/button';
import { BatchAccountPolicyLabelDialog, AccountInfo } from '@/components/policy-label';
import { Tags } from 'lucide-react';
export default function AdAccountPage() {
// 用于存储选中的账户
const [selectedAccounts, setSelectedAccounts] = useState<AccountInfo[]>([]);
// 控制批量对话框显示
const [showBatchPolicyDialog, setShowBatchPolicyDialog] = useState(false);
// 从表格获取选中的行数据
const handleSelectionChanged = useCallback(() => {
const selectedRows = gridRef.current?.api?.getSelectedRows() || [];
// 转换为 AccountInfo 格式
const accounts: AccountInfo[] = selectedRows.map(row => ({
accountId: row.accountId,
accountName: row.accountName || row.accountId.toString(),
customerId: row.customerId,
customerName: row.customer?.name,
appliedLabels: row.policyLabels || [],
}));
setSelectedAccounts(accounts);
}, []);
// 打开批量设置对话框
const handleBatchPolicyClick = () => {
if (selectedAccounts.length === 0) {
toast.error('请先选择要操作的账户');
return;
}
setShowBatchPolicyDialog(true);
};
// 批量操作成功后的回调
const handleBatchSuccess = () => {
// 刷新表格数据
gridRef.current?.api?.refreshServerSide();
// 清空选择
gridRef.current?.api?.deselectAll();
setSelectedAccounts([]);
toast.success('批量政策标签设置成功');
};
return (
<>
{/* 工具栏按钮 */}
<div className="flex gap-2">
<Button
onClick={handleBatchPolicyClick}
disabled={selectedAccounts.length === 0}
variant="outline"
>
<Tags className="mr-2 h-4 w-4" />
批量设置政策标签
{selectedAccounts.length > 0 && ` (${selectedAccounts.length})`}
</Button>
</div>
{/* AG Grid */}
<AgGridReact
ref={gridRef}
rowSelection="multiple"
onSelectionChanged={handleSelectionChanged}
// ... 其他配置
/>
{/* 批量政策标签对话框 */}
<BatchAccountPolicyLabelDialog
open={showBatchPolicyDialog}
onClose={() => setShowBatchPolicyDialog(false)}
accounts={selectedAccounts}
onSuccess={handleBatchSuccess}
/>
</>
);
}
3. AG Grid 配置
确保 AG Grid 配置了行选择功能:
<AgGridReact
ref={gridRef}
rowSelection="multiple" // 启用多选
rowMultiSelectWithClick={true} // 点击行即可选择
suppressRowClickSelection={false} // 允许点击行选择
onSelectionChanged={handleSelectionChanged} // 选择变化回调
// ... 其他配置
/>
4. 在底部固定行中添加批量操作按钮(可选)
const [pinnedBottomRowData, setPinnedBottomRowData] = useState<any[]>([{
accountId: '选择上方行项目进行批量操作',
batchActions: true, // 标记为批量操作行
}]);
// 在列定义中添加批量操作渲染器
{
field: 'policyLabels',
cellRenderer: (params: any) => {
if (params.data?.batchActions) {
return (
<Button
size="sm"
onClick={handleBatchPolicyClick}
disabled={selectedAccounts.length === 0}
>
批量设置政策标签 ({selectedAccounts.length})
</Button>
);
}
// 正常的单行政策标签渲染
return <ProductTagsRenderer {...params} />;
}
}
5. 完整示例代码
'use client';
import { useRef, useState, useCallback } from 'react';
import { AgGridReact } from 'ag-grid-react';
import { Button } from '@/components/ui/button';
import { BatchAccountPolicyLabelDialog, AccountInfo } from '@/components/policy-label';
import { toast } from 'sonner';
import { Tags } from 'lucide-react';
export default function AdAccountPage() {
const gridRef = useRef<AgGridReact>(null);
const [selectedAccounts, setSelectedAccounts] = useState<AccountInfo[]>([]);
const [showBatchPolicyDialog, setShowBatchPolicyDialog] = useState(false);
// 监听选择变化
const handleSelectionChanged = useCallback(() => {
const selectedRows = gridRef.current?.api?.getSelectedRows() || [];
const accounts: AccountInfo[] = selectedRows
.filter(row => row.customerId) // 过滤掉没有客户的账户
.map(row => ({
accountId: row.accountId,
accountName: row.accountName || `账户${row.accountId}`,
customerId: row.customerId,
customerName: row.customer?.name || `客户${row.customerId}`,
appliedLabels: row.policyLabels || [],
}));
setSelectedAccounts(accounts);
}, []);
// 打开批量设置对话框
const handleBatchPolicyClick = () => {
if (selectedAccounts.length === 0) {
toast.error('请先选择要操作的账户');
return;
}
// 检查是否所有账户都属于同一个客户
const customerIds = new Set(selectedAccounts.map(a => a.customerId));
if (customerIds.size > 1) {
toast.warning('所选账户属于不同客户,只会显示共同的可用标签');
}
setShowBatchPolicyDialog(true);
};
// 批量操作成功回调
const handleBatchSuccess = () => {
// 刷新表格
gridRef.current?.api?.refreshServerSide();
// 清空选择
gridRef.current?.api?.deselectAll();
setSelectedAccounts([]);
};
return (
<div className="space-y-4">
{/* 工具栏 */}
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Button
onClick={handleBatchPolicyClick}
disabled={selectedAccounts.length === 0}
variant="outline"
size="sm"
>
<Tags className="mr-2 h-4 w-4" />
批量设置政策标签
{selectedAccounts.length > 0 && (
<span className="ml-1 text-muted-foreground">
({selectedAccounts.length})
</span>
)}
</Button>
</div>
</div>
{/* 表格 */}
<AgGridReact
ref={gridRef}
rowSelection="multiple"
rowMultiSelectWithClick={true}
onSelectionChanged={handleSelectionChanged}
// ... 其他配置
/>
{/* 批量政策标签对话框 */}
<BatchAccountPolicyLabelDialog
open={showBatchPolicyDialog}
onClose={() => setShowBatchPolicyDialog(false)}
accounts={selectedAccounts}
onSuccess={handleBatchSuccess}
/>
</div>
);
}
注意事项
1. 客户限制
- 如果选中的账户属于不同客户,对话框会自动显示所有客户共同拥有的政策标签(取交集)
- 建议在UI中提示用户选择相同客户的账户以获得最佳体验
2. 权限控制
确保用户有相应的操作权限:
import { useCan } from '@/lib/hooks/useCasbin';
const canBatchApply = useCan('AdvertiserService.BatchApplyPolicyLabels');
<Button
onClick={handleBatchPolicyClick}
disabled={!canBatchApply || selectedAccounts.length === 0}
>
批量设置政策标签
</Button>
3. 数据刷新
批量操作成功后,建议: - 刷新表格数据以显示最新的政策标签 - 清空选择状态 - 显示成功提示
4. 错误处理
组件内部已经处理了常见错误,但你可以在 onSuccess 回调中添加额外的错误处理逻辑:
const handleBatchSuccess = () => {
try {
gridRef.current?.api?.refreshServerSide();
gridRef.current?.api?.deselectAll();
toast.success('批量设置完成');
} catch (error) {
console.error('刷新失败:', error);
toast.error('操作成功,但刷新数据失败,请手动刷新页面');
}
};
5. 审批流程
如果政策标签变更需要审批: - 对话框会自动显示审批提示 - 用户可以点击"查看审批"跳转到审批页面 - 审批通过后,相关账户的政策标签才会生效
API 要求
确保后端支持以下接口:
获取客户政策标签
POST /api/v1/customers/policy_labels
Body: { "customer_id": number }
Response: { "policyLabels": PolicyLabel[] }
批量应用政策标签
POST /api/v1/advertiser/batch_apply_policy_labels
Body: {
"operations": [
{
"account_id": number,
"policy_label_type": string,
"new_policy_label_id"?: number,
"delete_policy_label_id"?: number
}
]
}
Response: {
"successCount": number,
"failedCount": number,
"review"?: boolean,
"results": [
{
"accountId": number,
"success": boolean,
"message"?: string,
"appliedPolicyLabels": PolicyLabel[]
}
]
}
样式定制
如果需要自定义对话框样式,可以通过 CSS 覆盖:
/* 自定义对话框宽度 */
.batch-policy-dialog .dialog-content {
max-width: 900px;
}
/* 自定义账户卡片样式 */
.batch-policy-dialog .account-card {
background: var(--muted);
border-radius: 8px;
}
测试建议
- 单客户多账户: 选择属于同一客户的多个账户
- 多客户多账户: 选择属于不同客户的账户,验证交集逻辑
- 空标签账户: 选择没有应用任何标签的账户
- 混合状态: 选择有不同标签状态的账户
- 审批流程: 测试需要审批的场景
- 错误处理: 测试网络错误、权限错误等场景
故障排查
问题:对话框中没有可选标签
可能原因: - 选中的账户属于不同客户,且没有共同标签 - 客户没有配置政策标签 - API 返回数据为空
解决方案: - 检查选中账户的客户ID - 在客户管理页面为客户配置政策标签 - 检查 API 响应数据
问题:应用后标签没有更新
可能原因: - 表格没有刷新 - 需要审批,标签还未生效 - API 返回成功但实际失败
解决方案:
- 确保调用了 refreshServerSide()
- 检查是否进入审批流程
- 查看网络请求和响应
扩展功能
1. 添加预设模板
// 定义常用的标签组合
const POLICY_TEMPLATES = [
{
name: '标准折扣',
labels: { TAG_DISCOUNT: 1, TAG_REBATE: null }
},
{
name: 'VIP客户',
labels: { TAG_DISCOUNT: 2, TAG_REWARD: 5 }
}
];
// 在对话框中添加快速选择按钮
<Select onValueChange={applyTemplate}>
<SelectTrigger>快速选择模板</SelectTrigger>
<SelectContent>
{POLICY_TEMPLATES.map(t => (
<SelectItem key={t.name} value={t.name}>{t.name}</SelectItem>
))}
</SelectContent>
</Select>
2. 添加变更预览
在确认对话框中显示将要发生的变更:
// 计算变更摘要
const changes = accounts.map(account => {
const changes = [];
Object.entries(selectedLabels).forEach(([type, labelId]) => {
const current = account.appliedLabels.find(l => l.type === type);
if (current?.id !== labelId) {
changes.push({
account: account.accountName,
type: getPolicyTypeLabel(type),
from: current?.name || '无',
to: labelId ? '新标签' : '移除'
});
}
});
return changes;
}).flat();
// 在确认对话框中展示
<AlertDialogDescription>
将要修改 {changes.length} 处配置:
<ul className="mt-2 space-y-1">
{changes.slice(0, 5).map((c, i) => (
<li key={i} className="text-sm">
{c.account} - {c.type}: {c.from} → {c.to}
</li>
))}
</ul>
{changes.length > 5 && <p className="mt-1">...还有 {changes.length - 5} 处变更</p>}
</AlertDialogDescription>