feat(core): notification inbox infinite scroll + command palette fix (+ pending WIP)
消息中心:全量渲染 → 真·后端分页滚动加载 - backend(ops/views): NotificationPagination(10/页,page_size 可覆盖)+ 响应回 type_counts(按收件人绝对计数,不受分页/搜索影响) - frontend(messages): 自管分页,滚到底加载下一批;tab/搜索走服务端并重置到第1页; 代号作废在途旧请求防切换卡空白;乐观标已读;「已加载 X / Y」分母用当前筛选总数 - api/App/types: listNotifications 支持 page/page_size/search;allNotifications 携带 type_counts 命令面板(侧边栏搜索):修复点开后 UI 错位 - app-shell: 遮罩 className 漏了基类 shell-command-bg(只有 .show)致无定位塌到左下; 补回基类 + header 类名对齐 .shell-command-h - messages-page.css: 工作台收进视口高度,收件箱在面板内滚动 本次提交一并带入此前若干未提交 WIP(account/ai-tools/library/pipeline/products/settings + accounts/ai/assets/billing/projects 后端),按用户要求整体推 dev。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -11,11 +11,15 @@ class CreditAccountSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class CreditLedgerSerializer(serializers.ModelSerializer):
|
||||
# 成员展示名:优先真实姓名 → 用户名 → 邮箱;系统流水(无 user)留空
|
||||
user_label = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = CreditLedger
|
||||
fields = [
|
||||
"id",
|
||||
"user",
|
||||
"user_label",
|
||||
"project",
|
||||
"task",
|
||||
"ledger_type",
|
||||
@@ -27,6 +31,12 @@ class CreditLedgerSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_user_label(self, obj):
|
||||
user = obj.user
|
||||
if user is None:
|
||||
return ""
|
||||
return user.first_name or user.username or user.email or ""
|
||||
|
||||
|
||||
class CreditReservationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
|
||||
@@ -29,7 +29,7 @@ def reserve_credit(*, team, user, task, amount: Decimal) -> CreditReservation:
|
||||
ledger_type=CreditLedger.Type.RESERVE,
|
||||
amount=amount,
|
||||
balance_after=account.balance,
|
||||
reason="reserve ai task credit",
|
||||
reason="AI 任务预扣额度",
|
||||
)
|
||||
return reservation
|
||||
|
||||
@@ -52,7 +52,7 @@ def release_credit(*, reservation: CreditReservation, reason: str = "") -> None:
|
||||
ledger_type=CreditLedger.Type.RELEASE,
|
||||
amount=reservation.amount,
|
||||
balance_after=account.balance,
|
||||
reason=reason or "release reserved credit",
|
||||
reason=reason or "释放预留额度",
|
||||
)
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ def charge_reserved_credit(*, reservation: CreditReservation, actual_amount: Dec
|
||||
ledger_type=CreditLedger.Type.CHARGE,
|
||||
amount=actual_amount,
|
||||
balance_after=account.balance,
|
||||
reason="charge ai task credit",
|
||||
reason="AI 任务扣费",
|
||||
)
|
||||
if reservation.amount > actual_amount:
|
||||
CreditLedger.objects.create(
|
||||
@@ -88,6 +88,6 @@ def charge_reserved_credit(*, reservation: CreditReservation, actual_amount: Dec
|
||||
ledger_type=CreditLedger.Type.RELEASE,
|
||||
amount=reservation.amount - actual_amount,
|
||||
balance_after=account.balance,
|
||||
reason="release unused reserved credit",
|
||||
reason="释放未用预留额度",
|
||||
)
|
||||
|
||||
|
||||
@@ -56,7 +56,27 @@ def ledgers(request):
|
||||
queryset = queryset.filter(project_id=project_id)
|
||||
if user_id:
|
||||
queryset = queryset.filter(user_id=user_id)
|
||||
return Response(CreditLedgerSerializer(queryset[:100], many=True).data)
|
||||
# 服务端分页:总数随流水增长(原先写死 [:100] 导致永远 100 条)
|
||||
try:
|
||||
page = max(1, int(request.query_params.get("page", 1)))
|
||||
except (TypeError, ValueError):
|
||||
page = 1
|
||||
try:
|
||||
page_size = int(request.query_params.get("page_size", 10))
|
||||
except (TypeError, ValueError):
|
||||
page_size = 10
|
||||
page_size = max(1, min(page_size, 100))
|
||||
total = queryset.count()
|
||||
start = (page - 1) * page_size
|
||||
rows = queryset[start:start + page_size]
|
||||
return Response(
|
||||
{
|
||||
"count": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"results": CreditLedgerSerializer(rows, many=True).data,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@api_view(["POST"])
|
||||
|
||||
Reference in New Issue
Block a user