fix(ui):修复多个页面vue语法告警

This commit is contained in:
qian committed 2026-05-17 09:40:56 +08:00
1 parent 29e07facb4
commit bdca11903d
20 files changed
+57 -241

No files matched your search

+2 -2
View File
@@ -672,7 +672,7 @@ function formatHeaders(headers: Record<string, string>): string {
<!-- Update Key Modal -->
<AModal v-model:visible="showUpdateKeyModal" title="更新 API Key" :width="450" :on-before-ok="handleUpdateKey" :ok-loading="updateKeyLoading">
<AForm layout="vertical">
<AForm :model="{ key: newApiKey }" layout="vertical">
<AFormItem label="新 API Key" required>
<AInput v-model="newApiKey" type="textarea" :auto-size="{ minRows: 3, maxRows: 5 }" placeholder="输入新的 API Key" />
</AFormItem>
@@ -681,7 +681,7 @@ function formatHeaders(headers: Record<string, string>): string {
<!-- Clone Channel Modal -->
<AModal v-model:visible="showCloneModal" title="克隆渠道" :width="500" :on-before-ok="handleClone" :ok-loading="cloneLoading">
<AForm layout="vertical">
<AForm :model="cloneForm" layout="vertical">
<AFormItem label="新渠道名称">
<AInput v-model="cloneForm.name" :placeholder="(detail?.name || '') + ' (副本)'" />
</AFormItem>
+2 -2
View File
@@ -327,7 +327,7 @@ const { exporting, exportFile } = useExport({
<AModal v-model:visible="showOAuthDialog" title="官方渠道 OAuth 授权" :width="560" :footer="false" :mask-closable="false">
<!-- Step 1: Choose platform & mode -->
<div v-if="!oauthStep2">
<AForm layout="vertical">
<AForm :model="oauthForm" layout="vertical">
<AFormItem label="选择平台" required>
<ARadioGroup v-model="oauthForm.platform">
<Radio value="claude">Claude</Radio>
@@ -358,7 +358,7 @@ const { exporting, exportFile } = useExport({
<!-- Step 2: Enter code -->
<div v-else>
<AAlert type="info" class="mb-4">请在弹出窗口中完成授权,将回调 URL 中的 code 参数粘贴到下方。</AAlert>
<AForm layout="vertical">
<AForm :model="oauthForm" layout="vertical">
<AFormItem label="Authorization Code" required>
<AInput v-model="oauthForm.code" placeholder="粘贴 code 参数值" />
</AFormItem>
@@ -388,8 +388,8 @@ onMounted(() => {
<PageHeader title="支付设置" description="管理支付渠道和全局支付配置" />
<ATabs v-model:active-key="activeTab" type="rounded" class="mb-4">
<ATab key="channels" title="支付渠道" />
<ATab key="settings" title="全局设置" />
<ATabPane key="channels" title="支付渠道" />
<ATabPane key="settings" title="全局设置" />
</ATabs>
<!-- 支付渠道管理 -->
@@ -181,7 +181,7 @@ onUnmounted(() => {
</a-card>
<a-modal v-model:visible="showResolveModal" title="解决告警" :on-before-ok="handleResolve" :ok-loading="resolveLoading">
<a-form layout="vertical">
<a-form :model="{ notes: resolveNotes }" layout="vertical">
<a-form-item label="处理备注">
<a-textarea v-model="resolveNotes" placeholder="请输入处理说明..." :auto-size="{ minRows: 3, maxRows: 6 }" />
</a-form-item>
@@ -11,7 +11,7 @@ const total = ref(0)
const pagination = reactive({ current: 1, pageSize: 20 })
const filterSource = ref('')
const filterErrorCode = ref('')
const filterErrorCode = ref<number | undefined>(undefined)
const filterResolved = ref('')
const filterDateRange = ref<string[]>([])
const filterKeyword = ref('')
@@ -118,7 +118,7 @@ async function fetchData() {
page_size: pagination.pageSize,
}
if (filterSource.value) params.source = filterSource.value
if (filterErrorCode.value) params.error_code = parseInt(filterErrorCode.value)
if (filterErrorCode.value) params.error_code = filterErrorCode.value
if (filterResolved.value) params.resolved = filterResolved.value
if (filterKeyword.value) params.keyword = filterKeyword.value
if (filterDateRange.value?.length === 2) {
@@ -194,7 +194,7 @@ function handleFilter() {
function resetFilter() {
filterSource.value = ''
filterErrorCode.value = ''
filterErrorCode.value = undefined
filterResolved.value = ''
filterDateRange.value = []
filterKeyword.value = ''
@@ -176,7 +176,7 @@ onMounted(fetchTemplates)
<!-- Test Modal -->
<AModal v-model:visible="showTestModal" title="模板测试" :width="650" :footer="false">
<AForm layout="vertical">
<AForm :model="{ code: testingCode, variables: testVariables }" layout="vertical">
<AFormItem label="模板编码">
<AInput :model-value="testingCode" disabled />
</AFormItem>
@@ -358,7 +358,7 @@ onMounted(fetchTickets)
<!-- Reply Modal -->
<AModal v-model:visible="showReplyModal" title="回复工单" :width="500" :mask-closable="false" :on-before-ok="handleReply" :ok-loading="replyLoading">
<AForm layout="vertical">
<AForm :model="replyForm" layout="vertical">
<AFormItem label="回复内容" required>
<ATextarea v-model="replyForm.content" placeholder="请输入回复内容" :auto-size="{ minRows: 4, maxRows: 8 }" />
</AFormItem>
+1 -1
View File
@@ -81,7 +81,7 @@ onMounted(() => {
<div class="settings-content">
<ACard :bordered="false">
<ASpin :loading="loading">
<component :is="categoryMap[activeTab]" v-if="categoryMap[activeTab]" v-model="formValues" />
<component :is="categoryMap[activeTab]" v-if="categoryMap[activeTab]" />
<div class="settings-footer">
<AButton type="primary" :loading="saving" @click="save">保存设置</AButton>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,11 +1,12 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
function num(key: string) {
return Number(values.value[key]) || 0
return Number(values[key]) || 0
}
function set(key: string, v: number | undefined): void {
values.value[key] = String(v ?? 0)
values[key] = String(v ?? 0)
}
</script>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,16 +1,17 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
function syncRate(direction: 'cny_to_usd' | 'usd_to_cny') {
if (direction === 'cny_to_usd') {
const usdToCny = parseFloat(values.value['payment_exchange_rate_usd_to_cny'])
const usdToCny = parseFloat(values['payment_exchange_rate_usd_to_cny'])
if (usdToCny > 0) {
values.value['payment_exchange_rate_cny_to_usd'] = String(Math.round((1 / usdToCny) * 10000) / 10000)
values['payment_exchange_rate_cny_to_usd'] = String(Math.round((1 / usdToCny) * 10000) / 10000)
}
} else {
const cnyToUsd = parseFloat(values.value['payment_exchange_rate_cny_to_usd'])
const cnyToUsd = parseFloat(values['payment_exchange_rate_cny_to_usd'])
if (cnyToUsd > 0) {
values.value['payment_exchange_rate_usd_to_cny'] = String(Math.round((1 / cnyToUsd) * 10000) / 10000)
values['payment_exchange_rate_usd_to_cny'] = String(Math.round((1 / cnyToUsd) * 10000) / 10000)
}
}
}
@@ -1,11 +1,12 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
function num(key: string) {
return Number(values.value[key]) || 0
return Number(values[key]) || 0
}
function set(key: string, v: number | undefined): void {
values.value[key] = String(v ?? 0)
values[key] = String(v ?? 0)
}
</script>
@@ -1,105 +1,6 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Message } from '@arco-design/web-vue'
import request from '@/utils/request'
const values = defineModel<Record<string, any>>({ required: true })
// 2FA state
const totpEnabled = ref(false)
const setupLoading = ref(false)
const enableLoading = ref(false)
const showSetupModal = ref(false)
const showBackupCodesModal = ref(false)
const showDisableModal = ref(false)
const showRegenerateModal = ref(false)
const setupStep = ref(1)
const setupSecret = ref('')
const qrCodeUrl = ref('')
const backupCodes = ref<string[]>([])
const enableForm = reactive({ code: '', password: '' })
const disableForm = reactive({ code: '' })
const regenerateCode = ref('')
onMounted(() => {
check2FAStatus()
})
async function check2FAStatus() {
try {
const profileRes = await request.get('/api/admin/users/profile')
if (profileRes.data?.code === 0) {
totpEnabled.value = profileRes.data.data?.totp_enabled || false
}
} catch {
// ignore
}
}
async function handleSetup() {
setupLoading.value = true
try {
const res = await request.post('/api/admin/security/2fa/setup')
setupSecret.value = res.data.data.secret
qrCodeUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(res.data.data.uri)}`
setupStep.value = 1
showSetupModal.value = true
} catch {
// error toast already shown by interceptor
} finally {
setupLoading.value = false
}
}
async function handleEnable() {
enableLoading.value = true
try {
const res = await request.post('/api/admin/security/2fa/enable', enableForm)
totpEnabled.value = true
showSetupModal.value = false
backupCodes.value = res.data.data.backup_codes || []
showBackupCodesModal.value = true
Message.success('双因素认证已启用')
} catch {
// error toast already shown by interceptor
} finally {
enableLoading.value = false
}
}
async function handleDisable() {
if (!disableForm.code) {
Message.warning('请输入验证码')
return
}
try {
await request.post('/api/admin/security/2fa/disable', disableForm)
totpEnabled.value = false
showDisableModal.value = false
disableForm.code = ''
Message.success('双因素认证已禁用')
} catch {
// error toast already shown by interceptor
}
}
async function handleRegenerate() {
if (!regenerateCode.value) {
Message.warning('请输入验证码')
return
}
try {
const res = await request.post('/api/admin/security/2fa/backup-codes', { code: regenerateCode.value })
regenerateCode.value = ''
showRegenerateModal.value = false
backupCodes.value = res.data.data.backup_codes || []
showBackupCodesModal.value = true
Message.success('恢复码已重新生成')
} catch {
// error toast already shown by interceptor
}
}
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -210,77 +111,6 @@ async function handleRegenerate() {
</AFormItem>
</div>
</div>
<!-- 双因素认证 -->
<div class="section">
<div class="section-title">双因素认证(2FA)</div>
<template v-if="!totpEnabled">
<div class="section-desc">启用双因素认证后,登录时需要输入动态验证码,大幅提升账号安全性。</div>
<AButton type="primary" @click="handleSetup" :loading="setupLoading">开启 2FA</AButton>
</template>
<template v-else>
<AAlert type="success" class="mb-4">已启用双因素认证</AAlert>
<ASpace>
<AButton @click="showRegenerateModal = true">重新生成恢复码</AButton>
<AButton status="danger" @click="showDisableModal = true">禁用 2FA</AButton>
</ASpace>
</template>
</div>
<!-- 2FA Setup Modal -->
<AModal v-model:visible="showSetupModal" title="设置双因素认证" :maskClosable="false" :footer="false" width="480px">
<div v-if="setupStep === 1">
<AAlert type="info" class="mb-4">请使用身份验证器 App(如 Google Authenticator、Microsoft Authenticator)扫描下方二维码。</AAlert>
<div class="qr-container">
<img :src="qrCodeUrl" alt="QR Code" v-if="qrCodeUrl" class="qr-image" />
</div>
<div class="secret-key">
<span class="label">手动输入密钥:</span>
<ATypographyText copyable code>{{ setupSecret }}</ATypographyText>
</div>
<AButton type="primary" long @click="setupStep = 2" class="mt-4">我已扫描,下一步</AButton>
</div>
<div v-if="setupStep === 2">
<AForm :model="enableForm" @submit-success="handleEnable" layout="vertical">
<AFormItem label="输入验证码" field="code" :rules="[{ required: true, message: '请输入6位验证码' }, { length: 6, message: '验证码为6位' }]">
<AInput v-model="enableForm.code" placeholder="6位数字验证码" maxlength="6" />
</AFormItem>
<AFormItem label="当前密码" field="password" :rules="[{ required: true, message: '请输入密码' }]">
<AInputPassword v-model="enableForm.password" placeholder="请输入当前密码以确认" />
</AFormItem>
<AButton type="primary" html-type="submit" long :loading="enableLoading">确认启用</AButton>
</AForm>
</div>
</AModal>
<!-- Backup Codes Modal -->
<AModal v-model:visible="showBackupCodesModal" title="备用恢复码" :maskClosable="false" width="400px">
<AAlert type="warning" class="mb-4">请妥善保存以下恢复码。每个恢复码只能使用一次。关闭后无法再次查看。</AAlert>
<div class="backup-codes-grid">
<ATag v-for="code in backupCodes" :key="code" size="large" class="backup-code">{{ code }}</ATag>
</div>
<template #footer>
<AButton type="primary" @click="showBackupCodesModal = false">我已保存</AButton>
</template>
</AModal>
<!-- Disable Modal -->
<AModal v-model:visible="showDisableModal" title="禁用双因素认证" @ok="handleDisable" :maskClosable="false">
<AForm :model="disableForm" layout="vertical">
<AFormItem label="输入验证码或恢复码">
<AInput v-model="disableForm.code" placeholder="6位验证码或8位恢复码" />
</AFormItem>
</AForm>
</AModal>
<!-- Regenerate Backup Codes Modal -->
<AModal v-model:visible="showRegenerateModal" title="重新生成恢复码" @ok="handleRegenerate" :maskClosable="false">
<AForm layout="vertical">
<AFormItem label="输入验证码">
<AInput v-model="regenerateCode" placeholder="6位数字验证码" maxlength="6" />
</AFormItem>
</AForm>
</AModal>
</div>
</template>
@@ -291,37 +121,4 @@ async function handleRegenerate() {
color: var(--color-text-3);
margin-bottom: 12px;
}
.mb-4 {
margin-bottom: 16px;
}
.mt-4 {
margin-top: 16px;
}
.qr-container {
display: flex;
justify-content: center;
padding: 16px;
}
.qr-image {
width: 200px;
height: 200px;
}
.secret-key {
text-align: center;
padding: 8px 0;
}
.secret-key .label {
color: var(--color-text-2);
margin-right: 8px;
}
.backup-codes-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.backup-code {
font-family: monospace;
font-size: 16px;
text-align: center;
}
</style>
@@ -1,5 +1,6 @@
<script setup lang="ts">
const values = defineModel<Record<string, any>>({ required: true })
import { useFormValues } from './useSettings'
const values = useFormValues()
</script>
<template>
@@ -1,12 +1,16 @@
import { ref } from 'vue'
import { ref, reactive, provide, inject, type InjectionKey } from 'vue'
import { Message } from '@arco-design/web-vue'
import request from '@/utils/request'
const settingsFormKey: InjectionKey<Record<string, any>> = Symbol('settingsFormValues')
export function useSettings(category: () => string) {
const formValues = ref<Record<string, any>>({})
const formValues = reactive<Record<string, any>>({})
const loading = ref(false)
const saving = ref(false)
provide(settingsFormKey, formValues)
async function refresh() {
if (!category()) return
loading.value = true
@@ -22,9 +26,10 @@ export function useSettings(category: () => string) {
}
vals[item.key] = val
}
formValues.value = vals
Object.keys(formValues).forEach(k => delete formValues[k])
Object.assign(formValues, vals)
} catch {
formValues.value = {}
Object.keys(formValues).forEach(k => delete formValues[k])
} finally {
loading.value = false
}
@@ -33,7 +38,7 @@ export function useSettings(category: () => string) {
async function save() {
saving.value = true
try {
await request.put(`/admin/settings/${category()}`, { settings: formValues.value })
await request.put(`/admin/settings/${category()}`, { settings: formValues })
Message.success('保存成功')
await refresh()
} catch {
@@ -45,3 +50,7 @@ export function useSettings(category: () => string) {
return { formValues, loading, saving, refresh, save }
}
export function useFormValues() {
return inject(settingsFormKey)!
}