fix(admin): 行业选择500修复 + 管理员切换订阅计划
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- fix(industry): list_industries SQL参数编号错位 — count查询和items查询 共用WHERE子句但参数从$3开始,sqlx bind按$1/$2顺序绑定导致500 - feat(billing): 新增 PUT /admin/accounts/:id/subscription 端点 (super_admin) 验证目标计划 → 取消当前订阅 → 创建新订阅(30天) → 同步配额 - feat(admin-v2): Accounts.tsx 编辑弹窗新增「订阅计划」选择区 显示所有活跃计划,保存时调用admin switch plan API
This commit is contained in:
@@ -9,6 +9,7 @@ import type { ProColumns } from '@ant-design/pro-components'
|
||||
import { ProTable } from '@ant-design/pro-components'
|
||||
import { accountService } from '@/services/accounts'
|
||||
import { industryService } from '@/services/industries'
|
||||
import { billingService } from '@/services/billing'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import type { AccountPublic } from '@/types'
|
||||
|
||||
@@ -70,6 +71,12 @@ export default function Accounts() {
|
||||
}
|
||||
}, [accountIndustries, editingId, form])
|
||||
|
||||
// 获取所有活跃计划(用于管理员切换)
|
||||
const { data: plansData } = useQuery({
|
||||
queryKey: ['billing-plans'],
|
||||
queryFn: ({ signal }) => billingService.listPlans(signal),
|
||||
})
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: Partial<AccountPublic> }) =>
|
||||
accountService.update(id, data),
|
||||
@@ -101,6 +108,14 @@ export default function Accounts() {
|
||||
onError: (err: Error) => message.error(err.message || '行业授权更新失败'),
|
||||
})
|
||||
|
||||
// 管理员切换用户计划
|
||||
const switchPlanMutation = useMutation({
|
||||
mutationFn: ({ accountId, planId }: { accountId: string; planId: string }) =>
|
||||
billingService.adminSwitchPlan(accountId, planId),
|
||||
onSuccess: () => message.success('计划切换成功'),
|
||||
onError: (err: Error) => message.error(err.message || '计划切换失败'),
|
||||
})
|
||||
|
||||
const columns: ProColumns<AccountPublic>[] = [
|
||||
{ title: '用户名', dataIndex: 'username', width: 120, tooltip: '搜索用户名、邮箱或显示名' },
|
||||
{ title: '显示名', dataIndex: 'display_name', width: 120, hideInSearch: true },
|
||||
@@ -186,7 +201,7 @@ export default function Accounts() {
|
||||
|
||||
try {
|
||||
// 更新基础信息
|
||||
const { industry_ids, ...accountData } = values
|
||||
const { industry_ids, plan_id, ...accountData } = values
|
||||
await updateMutation.mutateAsync({ id: editingId, data: accountData })
|
||||
|
||||
// 更新行业授权(如果变更了)
|
||||
@@ -201,6 +216,11 @@ export default function Accounts() {
|
||||
queryClient.invalidateQueries({ queryKey: ['account-industries'] })
|
||||
}
|
||||
|
||||
// 切换订阅计划(如果选择了新计划)
|
||||
if (plan_id) {
|
||||
await switchPlanMutation.mutateAsync({ accountId: editingId, planId: plan_id })
|
||||
}
|
||||
|
||||
handleClose()
|
||||
} catch {
|
||||
// Errors handled by mutation onError callbacks
|
||||
@@ -218,6 +238,11 @@ export default function Accounts() {
|
||||
label: `${item.icon} ${item.name}`,
|
||||
}))
|
||||
|
||||
const planOptions = (plansData || []).map((plan) => ({
|
||||
value: plan.id,
|
||||
label: `${plan.display_name} (¥${(plan.price_cents / 100).toFixed(0)}/月)`,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader title="账号管理" description="管理系统用户账号、角色、权限与行业授权" />
|
||||
@@ -256,7 +281,7 @@ export default function Accounts() {
|
||||
open={modalOpen}
|
||||
onOk={handleSave}
|
||||
onCancel={handleClose}
|
||||
confirmLoading={updateMutation.isPending || setIndustriesMutation.isPending}
|
||||
confirmLoading={updateMutation.isPending || setIndustriesMutation.isPending || switchPlanMutation.isPending}
|
||||
width={560}
|
||||
>
|
||||
<Form form={form} layout="vertical" className="mt-4">
|
||||
@@ -280,6 +305,21 @@ export default function Accounts() {
|
||||
]} />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>订阅计划</Divider>
|
||||
|
||||
<Form.Item
|
||||
name="plan_id"
|
||||
label="切换计划"
|
||||
extra="选择新计划后保存将立即切换。留空则不修改当前计划。"
|
||||
>
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="不修改当前计划"
|
||||
options={planOptions}
|
||||
loading={!plansData}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>行业授权</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -90,4 +90,9 @@ export const billingService = {
|
||||
getPaymentStatus: (id: string, signal?: AbortSignal) =>
|
||||
request.get<PaymentStatus>(`/billing/payments/${id}`, withSignal({}, signal))
|
||||
.then((r) => r.data),
|
||||
|
||||
/** 管理员切换用户订阅计划 (super_admin only) */
|
||||
adminSwitchPlan: (accountId: string, planId: string) =>
|
||||
request.put<{ success: boolean; subscription: Subscription }>(`/admin/accounts/${accountId}/subscription`, { plan_id: planId })
|
||||
.then((r) => r.data),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user