diff --git a/backend/internal/database/migrations/013_rock_tokens_remark.sql b/backend/internal/database/migrations/013_rock_tokens_remark.sql new file mode 100644 index 0000000..5960c96 --- /dev/null +++ b/backend/internal/database/migrations/013_rock_tokens_remark.sql @@ -0,0 +1,5 @@ +-- ============================================================================ +-- 013_rock_tokens_remark.sql: 为 rock_tokens 表新增 remark 备注字段 +-- ============================================================================ + +ALTER TABLE rock_tokens ADD COLUMN IF NOT EXISTS remark TEXT NOT NULL DEFAULT ''; diff --git a/backend/internal/handler/rock.go b/backend/internal/handler/rock.go index ebd489f..e7b6ef8 100644 --- a/backend/internal/handler/rock.go +++ b/backend/internal/handler/rock.go @@ -169,3 +169,55 @@ func (h *RockHandler) ReleaseToken(c *gin.Context) { } response.OK(c, gin.H{"message": "已释放回可用库存,该提货链接可以再次分配"}) } + +type updateRemarkReq struct { + Remark string `json:"remark"` + Status string `json:"status"` // 可选:比如 abnormal +} + +// UpdateRemark 更新 Token 备注并可联动更新状态 +func (h *RockHandler) UpdateRemark(c *gin.Context) { + idUint, err := strconv.ParseUint(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "无效的 Token ID") + return + } + var req updateRemarkReq + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "参数解析失败") + return + } + token, err := h.rockSvc.UpdateTokenRemark(c.Request.Context(), uint(idUint), req.Remark, req.Status) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + response.OK(c, gin.H{ + "message": "备注更新成功", + "token": token, + }) +} + +type markAbnormalReq struct { + Remark string `json:"remark"` +} + +// MarkAbnormal 快捷标记为上报失效(作废,不可再用) +func (h *RockHandler) MarkAbnormal(c *gin.Context) { + idUint, err := strconv.ParseUint(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "无效的 Token ID") + return + } + var req markAbnormalReq + _ = c.ShouldBindJSON(&req) + token, err := h.rockSvc.MarkTokenAbnormal(c.Request.Context(), uint(idUint), req.Remark) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + response.OK(c, gin.H{ + "message": "已标记为上报失效,该提货链接已作废", + "token": token, + }) +} diff --git a/backend/internal/model/rock.go b/backend/internal/model/rock.go index b02d905..82a1feb 100644 --- a/backend/internal/model/rock.go +++ b/backend/internal/model/rock.go @@ -77,6 +77,7 @@ type RockToken struct { TargetCurrencyQuantity int `gorm:"not null;default:0" json:"target_currency_quantity"` MismatchAt *time.Time `gorm:"index" json:"mismatch_at"` MismatchReason string `gorm:"size:255" json:"mismatch_reason"` + Remark string `gorm:"type:text;not null;default:''" json:"remark"` ClaimedAt *time.Time `json:"claimed_at"` BoundAt *time.Time `json:"bound_at"` RedeemedAt *time.Time `json:"redeemed_at"` diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index c171d3c..73c9155 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -279,6 +279,8 @@ func Setup(h *Handlers) *gin.Engine { platform.POST("/platform/rock/tokens/:id/expected-uid", h.Rock.CorrectTokenUID) platform.POST("/platform/rock/tokens/:id/reset", h.Rock.ResetToken) platform.POST("/platform/rock/tokens/:id/release", h.Rock.ReleaseToken) + platform.POST("/platform/rock/tokens/:id/remark", h.Rock.UpdateRemark) + platform.POST("/platform/rock/tokens/:id/abnormal", h.Rock.MarkAbnormal) } } diff --git a/backend/internal/service/provider_rock_test.go b/backend/internal/service/provider_rock_test.go index e7175b4..3060a25 100644 --- a/backend/internal/service/provider_rock_test.go +++ b/backend/internal/service/provider_rock_test.go @@ -996,3 +996,71 @@ func TestRockProvider_CheckBindRefreshesSameTokenLink(t *testing.T) { t.Fatalf("其他订单不能拿到这个提货号的二维码: %s", checkOther.BindURL) } } + +func TestRockService_TokenRemarkAndAbnormal(t *testing.T) { + db := testdb.New(t) + client := NewRockClient("https://0666.pro") + rockSvc := NewRockService(db, client) + + now := time.Now() + token := model.RockToken{ + UpstreamTokenID: "TEST_REMARK_1", + Token: "jwt-remark-1", + RedeemCode: "CODE_REMARK_1", + Status: model.RockStatusMismatch, + AssignedOrderNo: "ORDER_REMARK_1", + ExpectedUID: "123456", + ActualRoleID: "654321", + ActualRoleName: "路人甲", + MismatchAt: &now, + MismatchReason: "预期UID不符", + } + if err := db.Create(&token).Error; err != nil { + t.Fatalf("create test token failed: %v", err) + } + + // 1. 测试单纯更新备注 + updated, err := rockSvc.UpdateTokenRemark(context.Background(), token.ID, "买家联系客服核对中", "") + if err != nil { + t.Fatalf("update remark failed: %v", err) + } + if updated.Remark != "买家联系客服核对中" || updated.Status != model.RockStatusMismatch { + t.Fatalf("更新备注不符: remark=%s status=%s", updated.Remark, updated.Status) + } + + // 2. 测试快捷标记为已上报失效 (MarkTokenAbnormal) + abnormalToken, err := rockSvc.MarkTokenAbnormal(context.Background(), token.ID, "已上报0868客服 (卡密绑定错误废弃)") + if err != nil { + t.Fatalf("mark abnormal failed: %v", err) + } + if abnormalToken.Status != model.RockStatusAbnormal { + t.Fatalf("标记异常后状态应为 abnormal,实际: %s", abnormalToken.Status) + } + if abnormalToken.Remark != "已上报0868客服 (卡密绑定错误废弃)" { + t.Fatalf("标记异常后备注不符: %s", abnormalToken.Remark) + } + + // 3. 搜索备注测试 + list, total, err := rockSvc.ListTokens(context.Background(), 1, 10, "", "卡密绑定错误废弃") + if err != nil { + t.Fatalf("list by remark search error: %v", err) + } + if total != 1 || len(list) != 1 || list[0].ID != token.ID { + t.Fatalf("通过备注搜索未检索到目标 token, total=%d", total) + } + + // 4. 从 abnormal 状态解冻重置回 available + if err := rockSvc.ResetMismatchToken(context.Background(), token.ID); err != nil { + t.Fatalf("reset abnormal token error: %v", err) + } + var resetToken model.RockToken + if err := db.First(&resetToken, token.ID).Error; err != nil { + t.Fatalf("find reset token error: %v", err) + } + if resetToken.Status != model.RockStatusAvailable { + t.Fatalf("重置后应为 available,实际: %s", resetToken.Status) + } + if resetToken.AssignedOrderNo != "" || resetToken.ExpectedUID != "" { + t.Fatalf("重置后应清空订单关联, assigned_order_no=%s", resetToken.AssignedOrderNo) + } +} diff --git a/backend/internal/service/rock_recover.go b/backend/internal/service/rock_recover.go index c6216cf..949ab0a 100644 --- a/backend/internal/service/rock_recover.go +++ b/backend/internal/service/rock_recover.go @@ -98,20 +98,20 @@ func isDigits(value string) bool { return true } -// ResetMismatchToken 客服人工重置解冻绑定错误的 Token +// ResetMismatchToken 客服人工重置解冻绑定错误或作废的 Token func (s *RockService) ResetMismatchToken(ctx context.Context, tokenID uint) error { var token model.RockToken if err := s.db.WithContext(ctx).First(&token, tokenID).Error; err != nil { return err } - if token.Status != model.RockStatusMismatch { - return fmt.Errorf("仅 mismatch 状态的 Token 允许人工解冻重置") + if token.Status != model.RockStatusMismatch && token.Status != model.RockStatusAbnormal { + return fmt.Errorf("仅 mismatch 或 abnormal 状态的 Token 允许人工解冻重置") } s.refreshCache.Delete(token.UpstreamTokenID) - // 重置回 available,清空订单关联 + // 重置回 available,清空订单关联与异常标记 return s.db.WithContext(ctx).Model(&token).Updates(map[string]interface{}{ "status": model.RockStatusAvailable, "assigned_order_no": "", @@ -125,3 +125,51 @@ func (s *RockService) ResetMismatchToken(ctx context.Context, tokenID uint) erro "updated_at": time.Now(), }).Error } + +// UpdateTokenRemark 更新 Token 备注,并支持联动流转状态(例如上报后置为 abnormal 失效) +func (s *RockService) UpdateTokenRemark(ctx context.Context, tokenID uint, remark string, nextStatus string) (*model.RockToken, error) { + var token model.RockToken + if err := s.db.WithContext(ctx).First(&token, tokenID).Error; err != nil { + return nil, err + } + + updates := map[string]interface{}{ + "remark": strings.TrimSpace(remark), + "updated_at": time.Now(), + } + + trimmedStatus := strings.TrimSpace(nextStatus) + if trimmedStatus != "" { + switch trimmedStatus { + case model.RockStatusAbnormal, model.RockStatusAvailable, model.RockStatusMismatch: + updates["status"] = trimmedStatus + token.Status = trimmedStatus + if trimmedStatus == model.RockStatusAbnormal { + s.refreshCache.Delete(token.UpstreamTokenID) + } + default: + return nil, fmt.Errorf("不支持的目标状态: %s", trimmedStatus) + } + } + + if err := s.db.WithContext(ctx).Model(&token).Updates(updates).Error; err != nil { + return nil, err + } + token.Remark = updates["remark"].(string) + + _ = writeAudit(s.db.WithContext(ctx), nil, nil, nil, "rock_token.update_remark", "rock_token", fmt.Sprintf("%d", token.ID), map[string]interface{}{ + "remark": token.Remark, + "status": token.Status, + }) + + return &token, nil +} + +// MarkTokenAbnormal 快捷标记为上报失效(算作废,不可再用) +func (s *RockService) MarkTokenAbnormal(ctx context.Context, tokenID uint, reason string) (*model.RockToken, error) { + remark := strings.TrimSpace(reason) + if remark == "" { + remark = "已上报上游 (失效作废)" + } + return s.UpdateTokenRemark(ctx, tokenID, remark, model.RockStatusAbnormal) +} diff --git a/backend/internal/service/rock_stock.go b/backend/internal/service/rock_stock.go index c447fa4..5c7c7a7 100644 --- a/backend/internal/service/rock_stock.go +++ b/backend/internal/service/rock_stock.go @@ -127,8 +127,8 @@ func (s *RockService) ListTokens(ctx context.Context, page, pageSize int, status } if strings.TrimSpace(search) != "" { term := "%" + strings.TrimSpace(search) + "%" - db = db.Where("CAST(id AS TEXT) = ? OR upstream_token_id = ? OR upstream_token_id LIKE ? OR assigned_order_no LIKE ? OR expected_uid LIKE ? OR actual_role_name LIKE ? OR actual_role_id LIKE ? OR redeem_code LIKE ? OR assigned_order_no IN (SELECT order_no FROM fulfillment_orders WHERE client_order_no LIKE ?)", - strings.TrimSpace(search), strings.TrimSpace(search), term, term, term, term, term, term, term) + db = db.Where("CAST(id AS TEXT) = ? OR upstream_token_id = ? OR upstream_token_id LIKE ? OR assigned_order_no LIKE ? OR expected_uid LIKE ? OR actual_role_name LIKE ? OR actual_role_id LIKE ? OR redeem_code LIKE ? OR remark LIKE ? OR assigned_order_no IN (SELECT order_no FROM fulfillment_orders WHERE client_order_no LIKE ?)", + strings.TrimSpace(search), strings.TrimSpace(search), term, term, term, term, term, term, term, term) } var total int64 diff --git a/docs/Rock发货记录时间与备注及失效状态优化方案.md b/docs/Rock发货记录时间与备注及失效状态优化方案.md new file mode 100644 index 0000000..c97dbda --- /dev/null +++ b/docs/Rock发货记录时间与备注及失效状态优化方案.md @@ -0,0 +1,89 @@ +# Rock发货记录时间与备注及失效状态优化方案 + +> **日期**:2026-10-08 +> **任务背景**:Rock (0666.pro) 发货平台看板中,提货记录缺少时间追踪与运营备注;当发生 UID 绑定错误(mismatch)并上报给上游(0868/0666 客服)后,该链接实际已废弃无法使用,但原有仅有的「解冻重置」会将坏卡重新释放回可用库存池,存在严重业务安全隐患。 + +--- + +## 一、进度与证据分类登记 + +### 1.1 已知事实 (Known Facts) +1. **现有数据结构与表定义**: + - 源码位置:`backend/internal/model/rock.go`、`backend/internal/database/migrations/012_rock_fulfillment.sql` + - `rock_tokens` 表已有字段:`id`, `upstream_token_id`, `redeem_code`, `status`, `assigned_order_no`, `expected_uid`, `actual_role_id`, `actual_role_name`, `mismatch_at`, `mismatch_reason`, `claimed_at`, `bound_at`, `redeemed_at`, `created_at`, `updated_at`。 + - 确信度:**100%(源码与迁移文件已确认)**。 +2. **状态机支持情况**: + - `model.RockStatusAbnormal = "abnormal"` 已经定义(注释为“异常/作废”),并在 `rock_assign.go:50-62`、`rock_service.go:215` 等处被处理(当 status 为 abnormal 时,领号逻辑会直接拦截阻断该订单再次分配此号)。 + - 当前缺少从前端直接将 Token 标记为 `abnormal` / 已上报失效的流转入口。 + - 确信度:**100%(源码已确认)**。 +3. **表格现有列**: + - 源码位置:`frontend/src/pages/platformRock/tokenColumns.tsx` + - 现有列包含:`0666 编号 / 提取码`, `规格与适用范围`, `库存状态`, `关联业务工单`, `预期 UID vs 扫码角色`, `发货结果图片`, `操作`。 + - 缺乏:时间列(入库时间、绑错时间、绑定/发货时间)以及备注列。 + - 确信度:**100%(源码与截图已确认)**。 +4. **原有「解冻重置」的逻辑与缺陷**: + - 源码位置:`backend/internal/service/rock_recover.go:101-127`(`ResetMismatchToken`) + - 现有逻辑:只要是 mismatch 状态,调用 resetToken 会直接无条件将 `status` 更新为 `available`,清空 `assigned_order_no` 和 `expected_uid`。 + - 业务缺陷:若运营人员已向 0868 上游反馈坏卡/上报问题,该链接在上游已被处理或作废,一旦客服误点“解冻重置”,该废卡被重新分给后续新订单,导致下一单继续报错。 + - 确信度:**100%(源码与业务逻辑已确认)**。 + +### 1.2 目标 (Goals) +1. **数据库与数据模型升级**: + - 在 `rock_tokens` 表增加 `remark` 字段(TEXT / VARCHAR(255))。 + - 新增安全数据库迁移脚本(`013_rock_tokens_remark.sql`),自动为现有表增列。 + - 在 `model.RockToken` 补充 `Remark string json:"remark"` 字段。 +2. **后端 API 扩展**: + - 提供 Token 备注更新与状态联动接口:`POST /api/platform/rock/tokens/:id/remark`。 + - 支持设置备注(例如“已上报上游”、“卡密绑定错误已上报”、“买家填错UID”等),并支持可选传参 `status`(如置为 `abnormal` / 已失效作废)。 + - 提供针对已失效/作废的专门操作或优化现有 Reset 行为(若已上报失效,明确区分是作废还是强制重置)。 + - 搜索功能支持对备注 `remark` 进行模糊查询。 +3. **前端表格重构 (`tokenColumns.tsx` & `RockTokenTable.tsx`)**: + - **新增「时间信息」列**: + - 入库时间(`created_at`); + - 关键节点流转时间(如 `mismatch_at` 绑错时间、`bound_at` 授权时间、`redeemed_at` 发货时间),带醒目标识。 + - **新增「备注」列**: + - 支持快捷选择标签(下拉/气泡快捷选填): + - 🔴 `已上报上游 (作废失效)`(自动设置备注并将状态置为失效 `abnormal`,阻断再次使用); + - ⚠️ `绑定错误 (已上报)`(设置备注并将状态置为失效 `abnormal`); + - ✏️ `买家UID填错 (待核对)`; + - ⏳ `上报处理中`; + - 📝 `自定义输入备注`; + - 🧹 `清空备注`。 + - 行内直观展示当前备注内容。 + - **优化「操作」列**: + - 对于 `mismatch` 状态: + - 保留「修改 UID」(适用于买家扫对了但填错了); + - 新增一键「上报失效」(快捷将链接标记为已上报并作废,不再入库存池); + - 优化「解冻重置」的提示文案与门禁(警告不可将已上报上游的卡重置回库存)。 + - 对于 `abnormal`(已上报失效 / 作废)状态: + - 展示「已作废 (不可用)」徽章,不再显示常规解冻重置; + - 如确需误操作恢复,提供带高危确认的「强制恢复为可用」。 + - **优化筛选栏 (`RockTokenFilterBar.tsx`)**: + - 增加「已失效/已上报」(`abnormal`)筛选 Tab,方便运营检索所有上报废弃卡。 + +### 1.3 下一步执行计划 (Next Steps) +1. [x] 现状分析与数据库/代码核查完成。 +2. [x] 编写数据库迁移 `013_rock_tokens_remark.sql` 并更新 Go 结构体 `model.RockToken`。 +3. [x] 扩展后端 service 与 handler:实现 Token 备注与状态流转接口,编写对应单元测试并通过验证。 +4. [x] 前端类型与 API 定义补充 (`frontend/src/api/index.ts`)。 +5. [x] 前端组件实现: + - `tokenColumns.tsx` 增加流转时间列、备注列(含快速设置交互)、优化操作列。 + - `TokenRemarkCell.tsx` 实现一键快捷选择(上报错误失效等)与自定义备注输入。 + - `RockTokenFilterBar.tsx` 增加 `abnormal` 已上报失效状态筛选。 +6. [x] 完整回归验证: + - 前端构建 `npm run build`、代码规范 `oxlint` 全部通过 (0 errors, 0 warnings)。 + - 后端代码格式化 `make fmt`、类型检查 `go vet` 全部通过。 + - 单元测试 `go test -count=1 ./internal/service -run TestRock` 全部通过。 + +--- + +## 二、验收与验证结果清单 + +| 检查项 | 验证命令 / 方式 | 结果 | +| :--- | :--- | :--- | +| 数据库增列与迁移 | 检查 `013_rock_tokens_remark.sql` 与 `model.RockToken` | 通过,新增 `remark` 字段 | +| 接口联通与流转逻辑 | 运行单测 `TestRockService_TokenRemarkAndAbnormal` | 通过,支持备注更新、一键标记 abnormal、模糊搜索备注及异常重置 | +| 前端 TypeScript 编译 | `npm run build` (`tsc -b && vite build`) | 通过 (0 errors) | +| 前端代码质量检查 | `npm run lint` (`oxlint`) | 通过 (0 errors, 0 warnings) | +| 后端代码格式与单测 | `make fmt` && `go vet ./...` && `go test -count=1 ./internal/service -run TestRock` | 全部通过 | + diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index e82dc0b..3a180e9 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -330,6 +330,7 @@ export interface RockTokenItem { target_currency_quantity?: number mismatch_at?: string mismatch_reason?: string + remark?: string claimed_at?: string bound_at?: string redeemed_at?: string @@ -348,6 +349,10 @@ export const rockApi = { request.post(`/platform/rock/tokens/${id}/expected-uid`, { expected_uid: expectedUid }).then((r) => r.data.data as { message?: string; status?: string }), resetToken: (id: number) => request.post(`/platform/rock/tokens/${id}/reset`).then((r) => r.data.data), releaseToken: (id: number) => request.post(`/platform/rock/tokens/${id}/release`).then((r) => r.data.data), + updateRemark: (id: number, data: { remark: string; status?: string }) => + request.post(`/platform/rock/tokens/${id}/remark`, data).then((r) => r.data.data as { message: string; token: RockTokenItem }), + markAbnormal: (id: number, remark?: string) => + request.post(`/platform/rock/tokens/${id}/abnormal`, { remark }).then((r) => r.data.data as { message: string; token: RockTokenItem }), } diff --git a/frontend/src/pages/platformRock/RockTokenFilterBar.tsx b/frontend/src/pages/platformRock/RockTokenFilterBar.tsx index e7fce49..1137690 100644 --- a/frontend/src/pages/platformRock/RockTokenFilterBar.tsx +++ b/frontend/src/pages/platformRock/RockTokenFilterBar.tsx @@ -59,6 +59,11 @@ export function RockTokenFilterBar({ 绑定错误 + + + 已上报失效 + + + + + + + + + + + + +
+
+ + 自定义备注内容: + +
+ + setInputValue(e.target.value)} + onPressEnter={handleSaveCustom} + /> + + +
+ + {record.remark ? ( +
+ +
+ ) : null} + + ) + + const isAbnormalOrReported = + record.status === 'abnormal' || (record.remark && record.remark.includes('上报')) + + if (!record.remark) { + return ( + { + setOpen(v) + if (v) setInputValue('') + }} + placement="bottomLeft" + > + + + 添加备注 + + + ) + } + + return ( + { + setOpen(v) + if (v) setInputValue(record.remark || '') + }} + placement="bottomLeft" + > +
+ : } + > + {record.remark} + +
+
+ ) +} diff --git a/frontend/src/pages/platformRock/tokenColumns.tsx b/frontend/src/pages/platformRock/tokenColumns.tsx index 0679646..a8768b2 100644 --- a/frontend/src/pages/platformRock/tokenColumns.tsx +++ b/frontend/src/pages/platformRock/tokenColumns.tsx @@ -1,7 +1,9 @@ import { Button, Popconfirm, Space, Tag, Tooltip, Typography } from 'antd' -import { CopyOutlined, EyeOutlined, UndoOutlined } from '@ant-design/icons' +import { CopyOutlined, EyeOutlined, StopOutlined, UndoOutlined } from '@ant-design/icons' import type { ColumnsType } from 'antd/es/table' import type { RockTokenItem } from '../../api' +import { formatDateTime } from '../../utils/time' +import { TokenRemarkCell } from './TokenRemarkCell' import type { UidEditState } from './types' const { Text } = Typography @@ -13,6 +15,8 @@ export interface TokenColumnHandlers { onEditUid: (state: UidEditState) => void onResetToken: (id: number) => void onReleaseToken: (id: number) => void + onUpdateRemark: (id: number, remark: string, status?: string) => void + onMarkAbnormal: (id: number, remark?: string) => void } export function buildTokenColumns({ @@ -22,6 +26,8 @@ export function buildTokenColumns({ onEditUid, onResetToken, onReleaseToken, + onUpdateRemark, + onMarkAbnormal, }: TokenColumnHandlers): ColumnsType { return [ { @@ -98,11 +104,62 @@ export function buildTokenColumns({ ⚠️ 绑定错误 (阻断) ) + case 'abnormal': + return ( + + 🚫 已上报失效 + + ) default: return {status} } }, }, + { + title: '流转时间', + width: 175, + render: (_, record) => ( + +
+ 入库: + {formatDateTime(record.created_at)} +
+ {record.mismatch_at && ( +
+ ⚠️ 绑错: + {formatDateTime(record.mismatch_at)} +
+ )} + {record.redeemed_at && ( +
+ ✅ 发货: + {formatDateTime(record.redeemed_at)} +
+ )} + {!record.redeemed_at && record.bound_at && ( +
+ 🔗 绑定: + {formatDateTime(record.bound_at)} +
+ )} + {!record.bound_at && !record.mismatch_at && record.claimed_at && ( +
+ 📦 提货: + {formatDateTime(record.claimed_at)} +
+ )} +
+ ), + }, { title: '关联业务工单', dataIndex: 'assigned_order_no', @@ -179,6 +236,17 @@ export function buildTokenColumns({ ) }, }, + { + title: '备注', + width: 200, + render: (_, record) => ( + + ), + }, { title: '发货结果图片', key: 'voucher', @@ -273,7 +341,7 @@ export function buildTokenColumns({ { title: '操作', key: 'action', - width: 168, + width: 180, fixed: 'right', render: (_, record) => { if (record.status === 'mismatch') { @@ -294,19 +362,50 @@ export function buildTokenColumns({ 修改 UID onMarkAbnormal(record.id, '绑定错误-已上报上游 (已失效)')} + > + + + onResetToken(record.id)} > - ) } + if (record.status === 'abnormal') { + return ( + + 已失效作废 + onResetToken(record.id)} + > + + + + ) + } if (record.status === 'using' && !record.bound_at && !record.actual_role_id) { return (