merge: 合并远程分支并修复代码冲突

合并了远程分支 cb72262 的功能更新,同时保留了 ESLint 修复:

**冲突解决详情:**

1. AccountTableFilters.vue
   -  保留 emit 模式修复(避免 vue/no-mutating-props 错误)
   -  添加第三个筛选器 type(账户类型)
   -  新增 antigravity 平台和 inactive 状态选项

2. UserBalanceModal.vue
   -  保留 console.error 错误日志
   -  添加输入验证(金额校验、余额不足检查)
   -  使用 appStore.showError 向用户显示友好错误

3. AccountsView.vue
   -  保留所有 console.error 错误日志(避免 no-empty 错误)
   -  使用新 API:clearRateLimit 和 setSchedulable

4. UsageView.vue
   -  添加 console.error 错误日志
   -  添加图表功能(模型分布、使用趋势)
   -  添加粒度选择(按天/按小时)
   -  保留 XLSX 动态导入优化

**测试结果:**
-  Go tests: PASS
-  golangci-lint: 0 issues
-  ESLint: 0 errors
-  TypeScript: PASS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yangjianbo
2026-01-06 12:50:51 +08:00
121 changed files with 4244 additions and 3233 deletions

View File

@@ -52,7 +52,7 @@ const load = async () => { loading.value = true; try { const res = await adminAP
const handleSave = async () => {
if (!props.user) return; submitting.value = true
try {
await adminAPI.users.update(props.user.id, { allowed_groups: selectedIds.value.length > 0 ? selectedIds.value : null })
await adminAPI.users.update(props.user.id, { allowed_groups: selectedIds.value })
appStore.showSuccess(t('admin.users.allowedGroupsUpdated')); emit('success'); emit('close')
} catch (error) { console.error('Failed to update allowed groups:', error) } finally { submitting.value = false }
}

View File

@@ -37,10 +37,22 @@ watch(() => props.show, (v) => { if(v) { form.amount = 0; form.notes = '' } })
const calculateNewBalance = () => (props.user ? (props.operation === 'add' ? props.user.balance + form.amount : props.user.balance - form.amount) : 0)
const handleBalanceSubmit = async () => {
if (!props.user) return; submitting.value = true
if (!props.user) return
if (!form.amount || form.amount <= 0) {
appStore.showError(t('admin.users.amountRequired'))
return
}
if (props.operation === 'subtract' && form.amount > props.user.balance) {
appStore.showError(t('admin.users.insufficientBalance'))
return
}
submitting.value = true
try {
await adminAPI.users.updateBalance(props.user.id, form.amount, props.operation, form.notes)
appStore.showSuccess(t('common.success')); emit('success'); emit('close')
} catch (error) { console.error('Failed to update balance:', error) } finally { submitting.value = false }
} catch (e: any) {
console.error('Failed to update balance:', e)
appStore.showError(e.response?.data?.detail || t('common.error'))
} finally { submitting.value = false }
}
</script>
</script>

View File

@@ -17,7 +17,7 @@
<input v-model="form.password" type="text" required class="input pr-10" :placeholder="t('admin.users.enterPassword')" />
</div>
<button type="button" @click="generateRandomPassword" class="btn btn-secondary px-3">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" /></svg>
<Icon name="refresh" size="md" />
</button>
</div>
</div>
@@ -52,6 +52,7 @@ import { reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'; import { adminAPI } from '@/api/admin'
import { useForm } from '@/composables/useForm'
import BaseDialog from '@/components/common/BaseDialog.vue'
import Icon from '@/components/icons/Icon.vue'
const props = defineProps<{ show: boolean }>()
const emit = defineEmits(['close', 'success']); const { t } = useI18n()

View File

@@ -21,7 +21,7 @@
</button>
</div>
<button type="button" @click="generatePassword" class="btn btn-secondary px-3">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" /></svg>
<Icon name="refresh" size="md" />
</button>
</div>
</div>
@@ -59,6 +59,7 @@ import { adminAPI } from '@/api/admin'
import type { User, UserAttributeValuesMap } from '@/types'
import BaseDialog from '@/components/common/BaseDialog.vue'
import UserAttributeForm from '@/components/user/UserAttributeForm.vue'
import Icon from '@/components/icons/Icon.vue'
const props = defineProps<{ show: boolean, user: User | null }>()
const emit = defineEmits(['close', 'success'])
@@ -86,6 +87,14 @@ const copyPassword = async () => {
}
const handleUpdateUser = async () => {
if (!props.user) return
if (!form.email.trim()) {
appStore.showError(t('admin.users.emailRequired'))
return
}
if (form.concurrency < 1) {
appStore.showError(t('admin.users.concurrencyMin'))
return
}
submitting.value = true
try {
const data: any = { email: form.email, username: form.username, notes: form.notes, concurrency: form.concurrency }
@@ -98,4 +107,4 @@ const handleUpdateUser = async () => {
appStore.showError(e.response?.data?.detail || t('admin.users.failedToUpdate'))
} finally { submitting.value = false }
}
</script>
</script>