feat(tenant): per-tenant brand and question limit overrides
Add nullable brand_limit / question_limit columns on tenants so ops admins can override the plan-derived brand-library quotas per account. When set, these take precedence over config.yml plan defaults across the brand-library summary, question materialization, and the daily monitoring worker's primary-client plan resolution. Ops side exposes them on admin-user create plus a new PUT /admin-users/:id/limits endpoint, invalidating the brand-library summary cache on change. The ops-web AdminUsers view gains matching inputs on the create and edit modals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -201,6 +201,24 @@
|
||||
<a-form-item label="初始套餐" required>
|
||||
<a-select v-model:value="createForm.plan_code" :options="planOptions" />
|
||||
</a-form-item>
|
||||
<a-form-item label="品牌数量上限">
|
||||
<a-input-number
|
||||
v-model:value="createForm.brand_limit"
|
||||
class="admin-users-limit-input"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
placeholder="留空使用 config.yml 默认值"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="问题数量上限">
|
||||
<a-input-number
|
||||
v-model:value="createForm.question_limit"
|
||||
class="admin-users-limit-input"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
placeholder="留空使用 config.yml 默认值"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
@@ -240,6 +258,24 @@
|
||||
:allow-clear="false"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="品牌数量上限">
|
||||
<a-input-number
|
||||
v-model:value="editForm.brand_limit"
|
||||
class="admin-users-limit-input"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
placeholder="留空使用 config.yml 默认值"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="问题数量上限">
|
||||
<a-input-number
|
||||
v-model:value="editForm.question_limit"
|
||||
class="admin-users-limit-input"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
placeholder="留空使用 config.yml 默认值"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
@@ -320,6 +356,8 @@ interface AdminUserRow {
|
||||
plan_name: string | null
|
||||
subscription_status: string | null
|
||||
subscription_end_at: string | null
|
||||
brand_limit: number | null
|
||||
question_limit: number | null
|
||||
kol_profile_id: number | null
|
||||
kol_display_name: string | null
|
||||
kol_status: string | null
|
||||
@@ -481,6 +519,8 @@ const createForm = reactive({
|
||||
password: '',
|
||||
role: 'tenant_admin',
|
||||
plan_code: '',
|
||||
brand_limit: null as number | null,
|
||||
question_limit: null as number | null,
|
||||
})
|
||||
|
||||
function resetCreate() {
|
||||
@@ -490,6 +530,8 @@ function resetCreate() {
|
||||
createForm.password = ''
|
||||
createForm.role = 'tenant_admin'
|
||||
createForm.plan_code = defaultPlanCode()
|
||||
createForm.brand_limit = null
|
||||
createForm.question_limit = null
|
||||
createOpen.value = false
|
||||
}
|
||||
|
||||
@@ -514,13 +556,15 @@ async function submitCreate() {
|
||||
}
|
||||
createLoading.value = true
|
||||
try {
|
||||
await http.post('/admin-users', {
|
||||
await http.post<AdminUserRow>('/admin-users', {
|
||||
phone,
|
||||
name: createForm.name.trim(),
|
||||
email: createForm.email.trim(),
|
||||
password: createForm.password,
|
||||
role: createForm.role,
|
||||
plan_code: createForm.plan_code,
|
||||
brand_limit: createForm.brand_limit,
|
||||
question_limit: createForm.question_limit,
|
||||
})
|
||||
message.success('新建成功')
|
||||
resetCreate()
|
||||
@@ -542,6 +586,8 @@ const editForm = reactive({
|
||||
role: 'tenant_admin',
|
||||
plan_code: '',
|
||||
subscription_end_date: '',
|
||||
brand_limit: null as number | null,
|
||||
question_limit: null as number | null,
|
||||
})
|
||||
|
||||
function openEdit(row: AdminUserRow) {
|
||||
@@ -551,6 +597,8 @@ function openEdit(row: AdminUserRow) {
|
||||
editForm.email = row.email ?? ''
|
||||
editForm.role = row.tenant_role ?? 'tenant_admin'
|
||||
editForm.plan_code = row.plan_code ?? defaultPlanCode()
|
||||
editForm.brand_limit = row.brand_limit
|
||||
editForm.question_limit = row.question_limit
|
||||
editForm.subscription_end_date = row.subscription_end_at
|
||||
? dayjs(row.subscription_end_at).format('YYYY-MM-DD')
|
||||
: estimatePlanEndDate(editForm.plan_code)
|
||||
@@ -604,6 +652,10 @@ async function submitEdit() {
|
||||
end_at: subscriptionEndAt,
|
||||
})
|
||||
}
|
||||
await http.put(`/admin-users/${editTarget.value.id}/limits`, {
|
||||
brand_limit: editForm.brand_limit,
|
||||
question_limit: editForm.question_limit,
|
||||
})
|
||||
message.success('用户设置已更新')
|
||||
editOpen.value = false
|
||||
void reload()
|
||||
@@ -818,6 +870,10 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-users-limit-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-users-toolbar__spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user