refactor(frontend): 改进故障转移规则对话框的状态码输入与校验

状态码输入从即时双向解析改为原始字符串绑定,保存时统一校验,
增加明确的错误提示;提取正则验证函数;优化小屏布局防止按钮挤压
This commit is contained in:
fawney19
2026-03-01 12:37:08 +08:00
parent 8b0e92e408
commit dc2bc83c17

View File

@@ -10,8 +10,8 @@
<div class="space-y-5 max-h-[60vh] overflow-y-auto px-0.5 py-0.5 -mx-0.5"> <div class="space-y-5 max-h-[60vh] overflow-y-auto px-0.5 py-0.5 -mx-0.5">
<!-- 成功转移规则 --> <!-- 成功转移规则 -->
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-center justify-between"> <div class="flex items-start justify-between gap-3">
<div> <div class="min-w-0">
<h3 class="text-sm font-medium"> <h3 class="text-sm font-medium">
成功转移规则 成功转移规则
</h3> </h3>
@@ -23,6 +23,7 @@
type="button" type="button"
variant="outline" variant="outline"
size="sm" size="sm"
class="shrink-0"
@click="addRule('success')" @click="addRule('success')"
> >
<Plus class="w-4 h-4 mr-1" /> <Plus class="w-4 h-4 mr-1" />
@@ -45,6 +46,7 @@
<Input <Input
v-model="rule.pattern" v-model="rule.pattern"
placeholder="例如: relay:.*格式错误" placeholder="例如: relay:.*格式错误"
size="sm"
class="font-mono text-xs flex-1" class="font-mono text-xs flex-1"
/> />
<Button <Button
@@ -60,8 +62,8 @@
<!-- 错误终止规则 --> <!-- 错误终止规则 -->
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-center justify-between"> <div class="flex items-start justify-between gap-3">
<div> <div class="min-w-0">
<h3 class="text-sm font-medium"> <h3 class="text-sm font-medium">
错误终止规则 错误终止规则
</h3> </h3>
@@ -73,6 +75,7 @@
type="button" type="button"
variant="outline" variant="outline"
size="sm" size="sm"
class="shrink-0"
@click="addRule('error')" @click="addRule('error')"
> >
<Plus class="w-4 h-4 mr-1" /> <Plus class="w-4 h-4 mr-1" />
@@ -93,15 +96,15 @@
class="flex items-center gap-1" class="flex items-center gap-1"
> >
<Input <Input
:model-value="formatStatusCodes(rule.status_codes)" v-model="statusCodeInputs[index]"
placeholder="状态码" placeholder="状态码 (可选)"
size="sm" size="sm"
class="font-mono text-xs w-24 shrink-0" class="font-mono text-xs w-28 shrink-0"
@update:model-value="(v: string | number) => rule.status_codes = parseStatusCodes(String(v))"
/> />
<Input <Input
v-model="rule.pattern" v-model="rule.pattern"
placeholder="例如: content_policy_violation" placeholder="例如: content_policy_violation"
size="sm"
class="font-mono text-xs flex-1" class="font-mono text-xs flex-1"
/> />
<Button <Button
@@ -162,12 +165,16 @@ const saving = ref(false)
const successPatterns = ref<FailoverRuleItem[]>([]) const successPatterns = ref<FailoverRuleItem[]>([])
const errorPatterns = ref<FailoverRuleItem[]>([]) const errorPatterns = ref<FailoverRuleItem[]>([])
const statusCodeInputs = ref<string[]>([])
watch(() => [props.open, props.provider], () => { watch(() => [props.open, props.provider], () => {
if (props.open && props.provider) { if (props.open && props.provider) {
const rules = props.provider.failover_rules const rules = props.provider.failover_rules
successPatterns.value = (rules?.success_failover_patterns || []).map(r => ({ ...r })) successPatterns.value = (rules?.success_failover_patterns || []).map(r => ({ ...r }))
errorPatterns.value = (rules?.error_stop_patterns || []).map(r => ({ ...r })) errorPatterns.value = (rules?.error_stop_patterns || []).map(r => ({ ...r }))
statusCodeInputs.value = errorPatterns.value.map(r =>
r.status_codes?.length ? r.status_codes.join(',') : ''
)
} }
}, { immediate: true }) }, { immediate: true })
@@ -177,6 +184,7 @@ function addRule(type: 'success' | 'error') {
successPatterns.value.push(rule) successPatterns.value.push(rule)
} else { } else {
errorPatterns.value.push(rule) errorPatterns.value.push(rule)
statusCodeInputs.value.push('')
} }
} }
@@ -185,6 +193,7 @@ function removeRule(type: 'success' | 'error', index: number) {
successPatterns.value.splice(index, 1) successPatterns.value.splice(index, 1)
} else { } else {
errorPatterns.value.splice(index, 1) errorPatterns.value.splice(index, 1)
statusCodeInputs.value.splice(index, 1)
} }
} }
@@ -192,19 +201,29 @@ function handleClose() {
emit('update:open', false) emit('update:open', false)
} }
function formatStatusCodes(codes: number[] | undefined): string { function parseStatusCodes(input: string): { valid: true; codes?: number[] } | { valid: false; reason: string } {
if (!codes || codes.length === 0) return '' const trimmed = input.trim()
return codes.join(',') if (!trimmed) return { valid: true }
const parts = trimmed.split(/[,\s]+/)
const codes: number[] = []
for (const part of parts) {
if (!part) continue
if (!/^\d+$/.test(part)) return { valid: false, reason: `"${part}" 不是有效数字` }
const n = parseInt(part, 10)
if (n < 100 || n > 599) return { valid: false, reason: `${n} 不在 100-599 范围内` }
codes.push(n)
}
return { valid: true, codes: codes.length > 0 ? codes : undefined }
} }
function parseStatusCodes(input: string): number[] | undefined { function validatePattern(pattern: string): string | null {
const trimmed = input.trim() if (!pattern.trim()) return '正则表达式不能为空'
if (!trimmed) return undefined try {
const codes = trimmed new RegExp(pattern)
.split(/[,\s]+/) return null
.map(s => parseInt(s.trim(), 10)) } catch {
.filter(n => !isNaN(n) && n >= 100 && n <= 599) return `无效的正则表达式: ${pattern}`
return codes.length > 0 ? codes : undefined }
} }
async function handleSave() { async function handleSave() {
@@ -213,16 +232,22 @@ async function handleSave() {
// Validate patterns // Validate patterns
const allPatterns = [...successPatterns.value, ...errorPatterns.value] const allPatterns = [...successPatterns.value, ...errorPatterns.value]
for (const rule of allPatterns) { for (const rule of allPatterns) {
if (!rule.pattern.trim()) { const err = validatePattern(rule.pattern)
showError('正则表达式不能为空', '验证失败') if (err) {
showError(err, '验证失败')
return return
} }
try { }
new RegExp(rule.pattern)
} catch { // Parse and validate status codes from raw inputs
showError(`无效的正则表达式: ${rule.pattern}`, '验证失败') for (let i = 0; i < errorPatterns.value.length; i++) {
const raw = statusCodeInputs.value[i]?.trim() || ''
const result = parseStatusCodes(raw)
if (!result.valid) {
showError(`状态码格式错误: ${result.reason},请输入 100-599 之间的整数,多个用逗号分隔`, '验证失败')
return return
} }
errorPatterns.value[i].status_codes = result.codes
} }
saving.value = true saving.value = true