Add JSON editor for endpoint rules

This commit is contained in:
fawney19
2026-05-06 21:30:13 +08:00
parent 7b11d43466
commit a6b45702e8

View File

@@ -221,7 +221,7 @@
<div class="flex-1" /> <div class="flex-1" />
<div class="flex items-center gap-1 shrink-0"> <div class="flex items-center gap-1 shrink-0">
<Button <Button
v-if="hasRulesChanges(endpoint) || hasBodyRulesChanges(endpoint) || hasResponseHeaderRulesChanges(endpoint)" v-if="hasRulePanelChanges(endpoint)"
variant="ghost" variant="ghost"
size="icon" size="icon"
class="h-7 w-7" class="h-7 w-7"
@@ -232,6 +232,28 @@
<Save class="w-3.5 h-3.5" /> <Save class="w-3.5 h-3.5" />
</Button> </Button>
<Button <Button
variant="ghost"
size="sm"
class="h-7 text-xs px-2"
:title="isEndpointRulesJsonMode(endpoint.id) ? '切回表单视图' : '切到 JSON 视图'"
@click="toggleEndpointRulesJsonMode(endpoint)"
>
<Code2 class="w-3 h-3 mr-1" />
{{ isEndpointRulesJsonMode(endpoint.id) ? '表单' : 'JSON' }}
</Button>
<Button
v-if="isEndpointRulesJsonMode(endpoint.id)"
variant="ghost"
size="sm"
class="h-7 px-2 text-xs"
title="格式化 JSON"
@click="formatEndpointRulesJson(endpoint.id)"
>
<AlignLeft class="w-3 h-3 mr-1" />
格式化
</Button>
<Button
v-if="!isEndpointRulesJsonMode(endpoint.id)"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="h-7 text-xs px-2" class="h-7 text-xs px-2"
@@ -242,6 +264,7 @@
请求头 请求头
</Button> </Button>
<Button <Button
v-if="!isEndpointRulesJsonMode(endpoint.id)"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="h-7 text-xs px-2" class="h-7 text-xs px-2"
@@ -252,6 +275,7 @@
请求体 请求体
</Button> </Button>
<Button <Button
v-if="!isEndpointRulesJsonMode(endpoint.id)"
variant="ghost" variant="ghost"
size="sm" size="sm"
class="h-7 text-xs px-2" class="h-7 text-xs px-2"
@@ -276,7 +300,28 @@
</div> </div>
</div> </div>
<CollapsibleContent class="pt-3"> <CollapsibleContent class="pt-3">
<div class="space-y-2"> <div
v-if="isEndpointRulesJsonMode(endpoint.id)"
class="space-y-2"
>
<Textarea
:model-value="getEndpointRulesJsonDraft(endpoint)"
class="min-h-[360px] font-mono text-xs leading-relaxed"
spellcheck="false"
placeholder="{ &quot;header_rules&quot;: [], &quot;body_rules&quot;: [], &quot;response_header_rules&quot;: [] }"
@update:model-value="(value) => updateEndpointRulesJsonDraft(endpoint.id, value)"
/>
<div
v-if="endpointRulesJsonError[endpoint.id]"
class="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive"
>
{{ endpointRulesJsonError[endpoint.id] }}
</div>
</div>
<div
v-else
class="space-y-2"
>
<div <div
v-if="getEndpointRulesCount(endpoint) > 1 || getEndpointBodyRulesCount(endpoint) > 1 || getEndpointResponseRulesCount(endpoint) > 1" v-if="getEndpointRulesCount(endpoint) > 1 || getEndpointBodyRulesCount(endpoint) > 1 || getEndpointResponseRulesCount(endpoint) > 1"
class="flex items-center gap-1.5 text-xs text-muted-foreground px-2" class="flex items-center gap-1.5 text-xs text-muted-foreground px-2"
@@ -988,6 +1033,7 @@ import {
Dialog, Dialog,
Button, Button,
Input, Input,
Textarea,
Label, Label,
Badge, Badge,
Select, Select,
@@ -1002,7 +1048,7 @@ import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
} from '@/components/ui' } from '@/components/ui'
import { Settings, Trash2, Check, X, Power, ChevronRight, Plus, Shuffle, RotateCcw, Radio, CheckCircle, Save, Filter, HelpCircle, GripVertical, Globe } from 'lucide-vue-next' import { Settings, Trash2, Check, X, Power, ChevronRight, Plus, Shuffle, RotateCcw, Radio, CheckCircle, Save, Filter, HelpCircle, GripVertical, Globe, Code2, AlignLeft } from 'lucide-vue-next'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
import { parseApiError } from '@/utils/errorParser' import { parseApiError } from '@/utils/errorParser'
import { log } from '@/utils/logger' import { log } from '@/utils/logger'
@@ -1072,6 +1118,12 @@ interface EndpointEditState {
bodyRules: EditableBodyRule[] bodyRules: EditableBodyRule[]
} }
interface EndpointRulesJsonPayload {
header_rules: HeaderRule[]
body_rules: BodyRule[]
response_header_rules: HeaderRule[]
}
const props = defineProps<{ const props = defineProps<{
modelValue: boolean modelValue: boolean
provider: ProviderWithEndpointsSummary | null provider: ProviderWithEndpointsSummary | null
@@ -1378,6 +1430,10 @@ const endpointToDelete = ref<ProviderEndpoint | null>(null)
// 请求规则折叠状态 // 请求规则折叠状态
const endpointRulesExpanded = ref<Record<string, boolean>>({}) const endpointRulesExpanded = ref<Record<string, boolean>>({})
const endpointRulesJsonMode = ref<Record<string, boolean>>({})
const endpointRulesJsonDraft = ref<Record<string, string>>({})
const endpointRulesJsonError = ref<Record<string, string | null>>({})
const endpointRulesJsonDirty = ref<Record<string, boolean>>({})
// 请求体规则 Select 的展开状态 // 请求体规则 Select 的展开状态
const bodyRuleSelectOpen = ref<Record<string, boolean>>({}) const bodyRuleSelectOpen = ref<Record<string, boolean>>({})
@@ -1425,6 +1481,220 @@ const RESERVED_BODY_FIELDS = new Set([
'stream', 'stream',
]) ])
const BODY_RULE_JSON_ACTIONS = new Set(['set', 'drop', 'rename', 'append', 'insert', 'regex_replace', 'name_style'])
const BODY_RULE_JSON_STYLES = new Set(['snake_case', 'camelCase', 'PascalCase', 'kebab-case', 'capitalize'])
const CONDITION_JSON_OPS = new Set(['eq', 'neq', 'gt', 'lt', 'gte', 'lte', 'starts_with', 'ends_with', 'contains', 'matches', 'exists', 'not_exists', 'in', 'type_is'])
function isJsonObject(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}
function readJsonRulesArray(root: Record<string, unknown>, key: keyof EndpointRulesJsonPayload, label: string): { value: unknown[]; error: string | null } {
const raw = root[key]
if (raw === undefined || raw === null) return { value: [], error: null }
if (!Array.isArray(raw)) return { value: [], error: `${label} 必须是数组或 null` }
return { value: raw, error: null }
}
function validateJsonCondition(rule: Record<string, unknown>, label: string, index: number): string | null {
const raw = rule.condition
if (raw === undefined || raw === null) return null
if (!isJsonObject(raw)) return `${label}第 ${index + 1} 条condition 必须是对象`
const shapeError = validateJsonConditionShape(raw, `${label}第 ${index + 1} 条condition`)
if (shapeError) return shapeError
const editable = conditionToEditable(raw as BodyRule['condition'])
const err = validateEditableCondition(editable)
return err ? `${label}第 ${index + 1} 条:${err}` : null
}
function validateJsonConditionShape(condition: Record<string, unknown>, label: string): string | null {
if (Object.prototype.hasOwnProperty.call(condition, 'all')) {
if (!Array.isArray(condition.all)) return `${label}.all 必须是数组`
for (let i = 0; i < condition.all.length; i++) {
const child = condition.all[i]
if (!isJsonObject(child)) return `${label}.all[${i}] 必须是对象`
const err = validateJsonConditionShape(child, `${label}.all[${i}]`)
if (err) return err
}
return null
}
if (Object.prototype.hasOwnProperty.call(condition, 'any')) {
if (!Array.isArray(condition.any)) return `${label}.any 必须是数组`
for (let i = 0; i < condition.any.length; i++) {
const child = condition.any[i]
if (!isJsonObject(child)) return `${label}.any[${i}] 必须是对象`
const err = validateJsonConditionShape(child, `${label}.any[${i}]`)
if (err) return err
}
return null
}
if (typeof condition.path !== 'string') return `${label}.path 必须是字符串`
if (typeof condition.op !== 'string' || !CONDITION_JSON_OPS.has(condition.op)) {
return `${label}.op 无效`
}
if (condition.source !== undefined && condition.source !== 'original' && condition.source !== 'current') {
return `${label}.source 只能是 original/current`
}
return null
}
function requireJsonString(rule: Record<string, unknown>, key: string, label: string, index: number): string | null {
return typeof rule[key] === 'string' ? null : `${label}第 ${index + 1} 条:${key} 必须是字符串`
}
function validateHeaderRuleJson(rule: unknown, label: string, index: number): string | null {
if (!isJsonObject(rule)) return `${label}第 ${index + 1} 条必须是对象`
const action = rule.action
if (action !== 'set' && action !== 'drop' && action !== 'rename') {
return `${label}第 ${index + 1} 条action 必须是 set/drop/rename`
}
if (action === 'set') {
return requireJsonString(rule, 'key', label, index)
|| requireJsonString(rule, 'value', label, index)
|| validateJsonCondition(rule, label, index)
}
if (action === 'drop') {
return requireJsonString(rule, 'key', label, index)
|| validateJsonCondition(rule, label, index)
}
return requireJsonString(rule, 'from', label, index)
|| requireJsonString(rule, 'to', label, index)
|| validateJsonCondition(rule, label, index)
}
function validateBodyRuleJson(rule: unknown, label: string, index: number): string | null {
if (!isJsonObject(rule)) return `${label}第 ${index + 1} 条必须是对象`
const action = typeof rule.action === 'string' ? rule.action : ''
if (!BODY_RULE_JSON_ACTIONS.has(action)) {
return `${label}第 ${index + 1} 条action 无效`
}
if (action === 'set' || action === 'append') {
return requireJsonString(rule, 'path', label, index)
|| (Object.prototype.hasOwnProperty.call(rule, 'value') ? null : `${label}第 ${index + 1} 条value 不能为空`)
|| validateJsonCondition(rule, label, index)
}
if (action === 'drop') {
return requireJsonString(rule, 'path', label, index)
|| validateJsonCondition(rule, label, index)
}
if (action === 'rename') {
return requireJsonString(rule, 'from', label, index)
|| requireJsonString(rule, 'to', label, index)
|| validateJsonCondition(rule, label, index)
}
if (action === 'insert') {
if (requireJsonString(rule, 'path', label, index)) return requireJsonString(rule, 'path', label, index)
if (!Number.isInteger(rule.index)) return `${label}第 ${index + 1} 条index 必须是整数`
if (!Object.prototype.hasOwnProperty.call(rule, 'value')) return `${label}第 ${index + 1} 条value 不能为空`
return validateJsonCondition(rule, label, index)
}
if (action === 'regex_replace') {
if (requireJsonString(rule, 'path', label, index)) return requireJsonString(rule, 'path', label, index)
if (requireJsonString(rule, 'pattern', label, index)) return requireJsonString(rule, 'pattern', label, index)
if (typeof rule.replacement !== 'string') return `${label}第 ${index + 1} 条replacement 必须是字符串`
if (rule.flags !== undefined && typeof rule.flags !== 'string') return `${label}第 ${index + 1} 条flags 必须是字符串`
if (rule.count !== undefined && !Number.isInteger(rule.count)) return `${label}第 ${index + 1} 条count 必须是整数`
return validateJsonCondition(rule, label, index)
}
if (requireJsonString(rule, 'path', label, index)) return requireJsonString(rule, 'path', label, index)
if (typeof rule.style !== 'string' || !BODY_RULE_JSON_STYLES.has(rule.style)) {
return `${label}第 ${index + 1} 条style 无效`
}
return validateJsonCondition(rule, label, index)
}
function parseEndpointRulesJsonDraft(draft: string): { value: EndpointRulesJsonPayload | null; error: string | null } {
const raw = draft.trim()
if (!raw) {
return { value: { header_rules: [], body_rules: [], response_header_rules: [] }, error: null }
}
let parsed: unknown
try {
parsed = JSON.parse(raw)
} catch (error: unknown) {
return { value: null, error: error instanceof Error ? error.message : 'JSON 格式无效' }
}
if (!isJsonObject(parsed)) return { value: null, error: '规则 JSON 必须是对象' }
const header = readJsonRulesArray(parsed, 'header_rules', 'header_rules')
if (header.error) return { value: null, error: header.error }
const body = readJsonRulesArray(parsed, 'body_rules', 'body_rules')
if (body.error) return { value: null, error: body.error }
const response = readJsonRulesArray(parsed, 'response_header_rules', 'response_header_rules')
if (response.error) return { value: null, error: response.error }
for (let i = 0; i < header.value.length; i++) {
const error = validateHeaderRuleJson(header.value[i], 'header_rules ', i)
if (error) return { value: null, error }
}
for (let i = 0; i < body.value.length; i++) {
const error = validateBodyRuleJson(body.value[i], 'body_rules ', i)
if (error) return { value: null, error }
}
for (let i = 0; i < response.value.length; i++) {
const error = validateHeaderRuleJson(response.value[i], 'response_header_rules ', i)
if (error) return { value: null, error }
}
return {
value: {
header_rules: header.value as HeaderRule[],
body_rules: body.value as BodyRule[],
response_header_rules: response.value as HeaderRule[],
},
error: null,
}
}
function applyEndpointRulesJsonDraft(
endpointId: string,
options: { notify?: boolean; notifyError?: boolean } = {},
): boolean {
const notifyError = options.notifyError !== false
const parsed = parseEndpointRulesJsonDraft(endpointRulesJsonDraft.value[endpointId] ?? '')
if (!parsed.value) {
endpointRulesJsonError.value[endpointId] = parsed.error
if (notifyError) showError(parsed.error || '规则 JSON 无效')
return false
}
const state = ensureEndpointEditState(endpointId)
if (!state) {
endpointRulesJsonError.value[endpointId] = '端点编辑状态不可用'
if (notifyError) showError('端点编辑状态不可用')
return false
}
state.rules = editableHeaderRulesFromRules(parsed.value.header_rules)
state.bodyRules = editableBodyRulesFromRules(parsed.value.body_rules)
state.responseRules = editableHeaderRulesFromRules(parsed.value.response_header_rules)
clearHeaderRuleSelectOpen(endpointId)
clearResponseRuleSelectOpen(endpointId)
clearBodyRuleSelectOpen(endpointId)
clearHeaderRuleDragState(endpointId)
clearResponseRuleDragState(endpointId)
clearBodyRuleDragState(endpointId)
const validationError = getHeaderValidationErrorForEndpoint(endpointId)
|| getResponseHeaderValidationErrorForEndpoint(endpointId)
|| getBodyValidationErrorForEndpoint(endpointId)
if (validationError) {
endpointRulesJsonError.value[endpointId] = validationError
if (notifyError) showError(validationError)
return false
}
endpointRulesJsonDraft.value[endpointId] = stringifyEndpointRulesJsonPayload(parsed.value)
endpointRulesJsonError.value[endpointId] = null
endpointRulesJsonDirty.value[endpointId] = false
if (options.notify !== false) success('JSON 规则已应用')
return true
}
// {{$original}} 占位符处理 // {{$original}} 占位符处理
const ORIGINAL_PLACEHOLDER = '{{$original}}' const ORIGINAL_PLACEHOLDER = '{{$original}}'
const ORIGINAL_SENTINEL = '__AETHER_ORIGINAL__' const ORIGINAL_SENTINEL = '__AETHER_ORIGINAL__'
@@ -1758,14 +2028,11 @@ function emptyBodyRule(action: BodyRuleAction = 'set'): EditableBodyRule {
} }
} }
// 初始化端点的编辑状态 function editableBodyRulesFromRules(rules: BodyRule[] | null | undefined): EditableBodyRule[] {
function initEndpointEditState(endpoint: ProviderEndpoint): EndpointEditState {
const rules = editableHeaderRulesFromRules(endpoint.header_rules)
const responseRules = editableHeaderRulesFromRules(getEndpointResponseHeaderRules(endpoint))
const bodyRules: EditableBodyRule[] = [] const bodyRules: EditableBodyRule[] = []
if (endpoint.body_rules && endpoint.body_rules.length > 0) { if (!Array.isArray(rules)) return bodyRules
for (const rule of endpoint.body_rules) {
for (const rule of rules) {
if (rule.action === 'set') { if (rule.action === 'set') {
const { value } = initBodyRuleSetValueForEditor(rule.value) const { value } = initBodyRuleSetValueForEditor(rule.value)
bodyRules.push({ ...emptyBodyRule('set'), path: rule.path, value, condition: conditionToEditable(rule.condition) }) bodyRules.push({ ...emptyBodyRule('set'), path: rule.path, value, condition: conditionToEditable(rule.condition) })
@@ -1793,8 +2060,15 @@ function initEndpointEditState(endpoint: ProviderEndpoint): EndpointEditState {
bodyRules.push({ ...emptyBodyRule('name_style'), path: rule.path || '', style: rule.style || 'capitalize', condition: conditionToEditable(rule.condition) }) bodyRules.push({ ...emptyBodyRule('name_style'), path: rule.path || '', style: rule.style || 'capitalize', condition: conditionToEditable(rule.condition) })
} }
} }
return bodyRules
} }
// 初始化端点的编辑状态
function initEndpointEditState(endpoint: ProviderEndpoint): EndpointEditState {
const rules = editableHeaderRulesFromRules(endpoint.header_rules)
const responseRules = editableHeaderRulesFromRules(getEndpointResponseHeaderRules(endpoint))
const bodyRules = editableBodyRulesFromRules(endpoint.body_rules)
return { return {
url: endpoint.base_url, url: endpoint.base_url,
path: endpoint.custom_path || '', path: endpoint.custom_path || '',
@@ -1810,14 +2084,92 @@ function getEndpointEditState(endpointId: string): EndpointEditState | undefined
return endpointEditStates.value[endpointId] return endpointEditStates.value[endpointId]
} }
// 更新端点字段 function ensureEndpointEditState(endpointId: string): EndpointEditState | null {
function updateEndpointField(endpointId: string, field: 'url' | 'path', value: string) {
if (!endpointEditStates.value[endpointId]) { if (!endpointEditStates.value[endpointId]) {
const endpoint = localEndpoints.value.find(e => e.id === endpointId) const endpoint = localEndpoints.value.find(e => e.id === endpointId)
if (endpoint) { if (endpoint) {
endpointEditStates.value[endpointId] = initEndpointEditState(endpoint) endpointEditStates.value[endpointId] = initEndpointEditState(endpoint)
} }
} }
return endpointEditStates.value[endpointId] ?? null
}
function isEndpointRulesJsonMode(endpointId: string): boolean {
return endpointRulesJsonMode.value[endpointId] === true
}
function buildEndpointRulesJsonPayload(endpointId: string): EndpointRulesJsonPayload {
const state = ensureEndpointEditState(endpointId)
return {
header_rules: state ? (rulesToHeaderRules(state.rules) ?? []) : [],
body_rules: state ? (rulesToBodyRules(state.bodyRules) ?? []) : [],
response_header_rules: state ? (rulesToHeaderRules(state.responseRules) ?? []) : [],
}
}
function stringifyEndpointRulesJsonPayload(payload: EndpointRulesJsonPayload): string {
return JSON.stringify(payload, null, 2)
}
function refreshEndpointRulesJsonDraft(endpointId: string) {
endpointRulesJsonDraft.value[endpointId] = stringifyEndpointRulesJsonPayload(
buildEndpointRulesJsonPayload(endpointId),
)
endpointRulesJsonError.value[endpointId] = null
endpointRulesJsonDirty.value[endpointId] = false
}
function enterEndpointRulesJsonMode(endpoint: ProviderEndpoint) {
ensureEndpointEditState(endpoint.id)
refreshEndpointRulesJsonDraft(endpoint.id)
endpointRulesJsonMode.value[endpoint.id] = true
endpointRulesExpanded.value[endpoint.id] = true
}
function toggleEndpointRulesJsonMode(endpoint: ProviderEndpoint) {
if (isEndpointRulesJsonMode(endpoint.id)) {
if (endpointRulesJsonDirty.value[endpoint.id] && !applyEndpointRulesJsonDraft(endpoint.id, { notify: false })) {
return
}
endpointRulesJsonMode.value[endpoint.id] = false
return
}
enterEndpointRulesJsonMode(endpoint)
}
function getEndpointRulesJsonDraft(endpoint: ProviderEndpoint): string {
if (endpointRulesJsonDraft.value[endpoint.id] === undefined) {
endpointRulesJsonDraft.value[endpoint.id] = stringifyEndpointRulesJsonPayload(
buildEndpointRulesJsonPayload(endpoint.id),
)
}
return endpointRulesJsonDraft.value[endpoint.id]
}
function updateEndpointRulesJsonDraft(endpointId: string, value: string) {
endpointRulesJsonDraft.value[endpointId] = value
endpointRulesJsonDirty.value[endpointId] = true
endpointRulesJsonError.value[endpointId] = null
}
function formatEndpointRulesJson(endpointId: string) {
const currentDraft = endpointRulesJsonDraft.value[endpointId] ?? ''
const parsed = parseEndpointRulesJsonDraft(endpointRulesJsonDraft.value[endpointId] ?? '')
if (!parsed.value) {
endpointRulesJsonError.value[endpointId] = parsed.error
return
}
const formattedDraft = stringifyEndpointRulesJsonPayload(parsed.value)
endpointRulesJsonDraft.value[endpointId] = formattedDraft
endpointRulesJsonError.value[endpointId] = null
if (formattedDraft !== currentDraft) {
endpointRulesJsonDirty.value[endpointId] = true
}
}
// 更新端点字段
function updateEndpointField(endpointId: string, field: 'url' | 'path', value: string) {
ensureEndpointEditState(endpointId)
if (endpointEditStates.value[endpointId]) { if (endpointEditStates.value[endpointId]) {
endpointEditStates.value[endpointId][field] = value endpointEditStates.value[endpointId][field] = value
} }
@@ -2610,15 +2962,28 @@ function hasResponseHeaderRulesChanges(endpoint: ProviderEndpoint): boolean {
return editableHeaderRulesChanged(state.responseRules, getEndpointResponseHeaderRules(endpoint)) return editableHeaderRulesChanged(state.responseRules, getEndpointResponseHeaderRules(endpoint))
} }
function hasRulePanelChanges(endpoint: ProviderEndpoint): boolean {
return hasRulesChanges(endpoint)
|| hasBodyRulesChanges(endpoint)
|| hasResponseHeaderRulesChanges(endpoint)
|| (isEndpointRulesJsonMode(endpoint.id) && endpointRulesJsonDirty.value[endpoint.id] === true)
}
// 检查端点是否有修改URL、路径或规则 // 检查端点是否有修改URL、路径或规则
// 注:当前模板直接使用各子函数,此聚合函数保留供未来使用 // 注:当前模板直接使用各子函数,此聚合函数保留供未来使用
function _hasEndpointChanges(endpoint: ProviderEndpoint): boolean { function _hasEndpointChanges(endpoint: ProviderEndpoint): boolean {
return hasUrlChanges(endpoint) || hasRulesChanges(endpoint) || hasBodyRulesChanges(endpoint) || hasResponseHeaderRulesChanges(endpoint) return hasUrlChanges(endpoint) || hasRulePanelChanges(endpoint)
} }
// 重置端点修改 // 重置端点修改
function resetEndpointChanges(endpoint: ProviderEndpoint) { function resetEndpointChanges(endpoint: ProviderEndpoint) {
endpointEditStates.value[endpoint.id] = initEndpointEditState(endpoint) endpointEditStates.value[endpoint.id] = initEndpointEditState(endpoint)
if (isEndpointRulesJsonMode(endpoint.id)) {
refreshEndpointRulesJsonDraft(endpoint.id)
} else {
endpointRulesJsonError.value[endpoint.id] = null
endpointRulesJsonDirty.value[endpoint.id] = false
}
} }
async function handleResetBodyRulesToDefault(endpoint: ProviderEndpoint) { async function handleResetBodyRulesToDefault(endpoint: ProviderEndpoint) {
@@ -2644,6 +3009,9 @@ async function handleResetBodyRulesToDefault(endpoint: ProviderEndpoint) {
endpointRulesExpanded.value[endpoint.id] = (state.rules.length + state.responseRules.length + state.bodyRules.length) > 0 endpointRulesExpanded.value[endpoint.id] = (state.rules.length + state.responseRules.length + state.bodyRules.length) > 0
clearBodyRuleDragState(endpoint.id) clearBodyRuleDragState(endpoint.id)
clearBodyRuleSelectOpen(endpoint.id) clearBodyRuleSelectOpen(endpoint.id)
if (isEndpointRulesJsonMode(endpoint.id)) {
refreshEndpointRulesJsonDraft(endpoint.id)
}
success('已重置请求体为默认规则请点击保存生效') success('已重置请求体为默认规则请点击保存生效')
} catch (error: unknown) { } catch (error: unknown) {
showError(parseApiError(error, '重置失败'), '错误') showError(parseApiError(error, '重置失败'), '错误')
@@ -2777,6 +3145,10 @@ watch(() => props.modelValue, (open) => {
responseRuleDragOverIndex.value = {} responseRuleDragOverIndex.value = {}
bodyRuleDraggedIndex.value = {} bodyRuleDraggedIndex.value = {}
bodyRuleDragOverIndex.value = {} bodyRuleDragOverIndex.value = {}
endpointRulesJsonMode.value = {}
endpointRulesJsonDraft.value = {}
endpointRulesJsonError.value = {}
endpointRulesJsonDirty.value = {}
if (open) { if (open) {
localEndpoints.value = [...(props.endpoints || [])] localEndpoints.value = [...(props.endpoints || [])]
// 清空编辑状态,重新从端点加载 // 清空编辑状态,重新从端点加载
@@ -2804,6 +3176,9 @@ watch(() => props.endpoints, (endpoints) => {
if (!endpointEditStates.value[endpoint.id]) { if (!endpointEditStates.value[endpoint.id]) {
endpointEditStates.value[endpoint.id] = initEndpointEditState(endpoint) endpointEditStates.value[endpoint.id] = initEndpointEditState(endpoint)
} }
if (isEndpointRulesJsonMode(endpoint.id) && !endpointRulesJsonDirty.value[endpoint.id]) {
refreshEndpointRulesJsonDraft(endpoint.id)
}
} }
const newFormats = localEndpoints.value const newFormats = localEndpoints.value
.filter(e => e.api_format && !defaultBodyRulesLoaded.value[defaultBodyRulesCacheKey(e.api_format)]) .filter(e => e.api_format && !defaultBodyRulesLoaded.value[defaultBodyRulesCacheKey(e.api_format)])
@@ -2816,6 +3191,10 @@ watch(() => props.endpoints, (endpoints) => {
// 保存端点 // 保存端点
async function saveEndpoint(endpoint: ProviderEndpoint) { async function saveEndpoint(endpoint: ProviderEndpoint) {
if (isEndpointRulesJsonMode(endpoint.id) && endpointRulesJsonDirty.value[endpoint.id]) {
if (!applyEndpointRulesJsonDraft(endpoint.id, { notify: false })) return
}
const state = endpointEditStates.value[endpoint.id] const state = endpointEditStates.value[endpoint.id]
if (!state || !state.url) return if (!state || !state.url) return