Skip to content

Commit 8b5401b

Browse files
committed
refactor(frontend): modularize SessionHeadersConfig component - Extract dialog components: TextImportDialog, JsonImportDialog, FileImportDialog, EditDialog - Add constants.ts for shared constants and templates - Add session-headers.scss for component styles - Add useSessionHeaders composable for business logic - Register Select and ToggleSwitch components globally - Fix dialog styling and padding issues - Support ||| separator for import with replace_strategy in content
1 parent 15c2d92 commit 8b5401b

11 files changed

Lines changed: 3092 additions & 2150 deletions

File tree

src/frontEnd/src/components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@ declare module 'vue' {
2929
InlineMessage: typeof import('primevue/inlinemessage')['default']
3030
InputIcon: typeof import('primevue/inputicon')['default']
3131
InputNumber: typeof import('primevue/inputnumber')['default']
32+
InputSwitch: typeof import('primevue/inputswitch')['default']
3233
InputText: typeof import('primevue/inputtext')['default']
3334
Message: typeof import('primevue/message')['default']
3435
OfflineBanner: typeof import('./components/OfflineBanner.vue')['default']
3536
Password: typeof import('primevue/password')['default']
3637
ProgressSpinner: typeof import('primevue/progressspinner')['default']
3738
RouterLink: typeof import('vue-router')['RouterLink']
3839
RouterView: typeof import('vue-router')['RouterView']
40+
Select: typeof import('primevue/select')['default']
3941
SelectButton: typeof import('primevue/selectbutton')['default']
4042
Slider: typeof import('primevue/slider')['default']
4143
Tag: typeof import('primevue/tag')['default']
4244
TaskFilter: typeof import('./components/TaskFilter.vue')['default']
4345
TaskSummary: typeof import('./components/TaskSummary.vue')['default']
4446
Textarea: typeof import('primevue/textarea')['default']
4547
ToggleButton: typeof import('primevue/togglebutton')['default']
48+
ToggleSwitch: typeof import('primevue/toggleswitch')['default']
4649
}
4750
}

src/frontEnd/src/primevue.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Lara from '@primevue/themes/lara'
44
import Tooltip from 'primevue/tooltip'
55
import ToastService from 'primevue/toastservice'
66
import ConfirmationService from 'primevue/confirmationservice'
7+
import Select from 'primevue/select'
8+
import ToggleSwitch from 'primevue/toggleswitch'
79

810
// 导入PrimeIcons图标库CSS(使用直接路径)
911
import 'primeicons/primeicons.css'
@@ -32,4 +34,8 @@ export function setupPrimeVue(app: App) {
3234

3335
// 注册Tooltip指令
3436
app.directive('tooltip', Tooltip)
37+
38+
// 显式注册Select组件(解决PrimeVueResolver未自动识别问题)
39+
app.component('Select', Select)
40+
app.component('ToggleSwitch', ToggleSwitch)
3541
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<template>
2+
<Dialog
3+
v-model:visible="visible"
4+
:header="isAdd ? '添加Session Header' : '编辑Session Header'"
5+
:style="{
6+
width: '90vw',
7+
maxWidth: '700px',
8+
maxHeight: '85vh'
9+
}"
10+
modal
11+
class="session-dialog"
12+
>
13+
<div class="dialog-content">
14+
<!-- 基本信息 -->
15+
<Card class="mb-4">
16+
<template #title>
17+
<div class="flex align-items-center gap-2">
18+
<i class="pi pi-info-circle text-primary"></i>
19+
<span>基本信息</span>
20+
</div>
21+
</template>
22+
<template #content>
23+
<div class="form-grid">
24+
<div class="field">
25+
<label for="header_name" class="required-field">Header名称</label>
26+
<InputText
27+
id="header_name"
28+
v-model="localForm.header_name"
29+
class="w-full"
30+
placeholder="如: Authorization, X-Custom-Header"
31+
/>
32+
</div>
33+
34+
<div class="field">
35+
<label for="header_value" class="required-field">Header值</label>
36+
<Textarea
37+
id="header_value"
38+
v-model="localForm.header_value"
39+
rows="3"
40+
class="w-full"
41+
placeholder="Header的值"
42+
/>
43+
</div>
44+
45+
<div class="field-row">
46+
<div class="field">
47+
<label for="replace_strategy">替换策略</label>
48+
<Select
49+
id="replace_strategy"
50+
v-model="localForm.replace_strategy"
51+
:options="replaceStrategyOptions"
52+
optionLabel="label"
53+
optionValue="value"
54+
class="w-full"
55+
/>
56+
</div>
57+
58+
<div class="field">
59+
<label for="priority">优先级</label>
60+
<InputNumber
61+
id="priority"
62+
v-model="localForm.priority"
63+
:min="0"
64+
:max="100"
65+
class="w-full"
66+
/>
67+
</div>
68+
</div>
69+
70+
<div class="field-row">
71+
<div class="field">
72+
<label for="ttl">生存时间(秒)</label>
73+
<InputNumber
74+
id="ttl"
75+
v-model="localForm.ttl"
76+
:min="0"
77+
class="w-full"
78+
placeholder="0表示不过期"
79+
/>
80+
</div>
81+
82+
<div class="field flex align-items-center">
83+
<label for="is_active" class="mr-2">启用状态</label>
84+
<ToggleSwitch id="is_active" v-model="localForm.is_active" />
85+
</div>
86+
</div>
87+
</div>
88+
</template>
89+
</Card>
90+
91+
<!-- 作用域配置 -->
92+
<ScopeConfigPanel
93+
v-model="localScope"
94+
:title="'作用域配置(可选)'"
95+
:description="'设置此Header的生效范围,留空表示全局生效'"
96+
:show-templates="true"
97+
:show-info="true"
98+
:show-advanced="false"
99+
/>
100+
</div>
101+
102+
<template #footer>
103+
<Button
104+
label="取消"
105+
icon="pi pi-times"
106+
severity="secondary"
107+
@click="visible = false"
108+
/>
109+
<Button
110+
:label="isAdd ? '添加' : '保存'"
111+
icon="pi pi-check"
112+
@click="handleSubmit"
113+
:loading="loading"
114+
:disabled="!isFormValid"
115+
/>
116+
</template>
117+
</Dialog>
118+
</template>
119+
120+
<script setup lang="ts">
121+
import { ref, computed, watch } from 'vue'
122+
import ScopeConfigPanel from '../ScopeConfigPanel.vue'
123+
import type { HeaderScope } from '@/types/headerRule'
124+
import { ReplaceStrategy } from '@/types/headerRule'
125+
import { REPLACE_STRATEGY_OPTIONS, DEFAULT_PRIORITY, DEFAULT_TTL } from './constants'
126+
127+
interface FormData {
128+
header_name: string
129+
header_value: string
130+
replace_strategy: ReplaceStrategy
131+
priority: number
132+
ttl: number
133+
is_active: boolean
134+
}
135+
136+
interface EditData {
137+
header_name?: string
138+
header_value?: string
139+
replace_strategy?: ReplaceStrategy
140+
priority?: number
141+
ttl?: number
142+
is_active?: boolean
143+
scope?: HeaderScope | null
144+
}
145+
146+
const props = defineProps<{
147+
modelValue: boolean
148+
editData?: EditData | null
149+
loading?: boolean
150+
}>()
151+
152+
const emit = defineEmits<{
153+
(e: 'update:modelValue', value: boolean): void
154+
(e: 'submit', form: FormData, scope: HeaderScope | null): void
155+
}>()
156+
157+
const visible = computed({
158+
get: () => props.modelValue,
159+
set: (value) => emit('update:modelValue', value)
160+
})
161+
162+
const isAdd = computed(() => !props.editData)
163+
164+
const replaceStrategyOptions = [...REPLACE_STRATEGY_OPTIONS]
165+
166+
const defaultForm: FormData = {
167+
header_name: '',
168+
header_value: '',
169+
replace_strategy: ReplaceStrategy.REPLACE,
170+
priority: DEFAULT_PRIORITY,
171+
ttl: DEFAULT_TTL,
172+
is_active: true
173+
}
174+
175+
const localForm = ref<FormData>({ ...defaultForm })
176+
const localScope = ref<HeaderScope | null>(null)
177+
178+
const isFormValid = computed(() => {
179+
return localForm.value.header_name.trim() !== '' &&
180+
localForm.value.header_value.trim() !== ''
181+
})
182+
183+
watch(visible, (newVal) => {
184+
if (newVal) {
185+
if (props.editData) {
186+
localForm.value = {
187+
header_name: props.editData.header_name || '',
188+
header_value: props.editData.header_value || '',
189+
replace_strategy: props.editData.replace_strategy || ReplaceStrategy.REPLACE,
190+
priority: props.editData.priority ?? DEFAULT_PRIORITY,
191+
ttl: props.editData.ttl ?? DEFAULT_TTL,
192+
is_active: props.editData.is_active ?? true
193+
}
194+
localScope.value = props.editData.scope || null
195+
} else {
196+
localForm.value = { ...defaultForm }
197+
localScope.value = null
198+
}
199+
}
200+
})
201+
202+
function handleSubmit() {
203+
emit('submit', { ...localForm.value }, localScope.value)
204+
}
205+
</script>
206+
207+
<style scoped lang="scss">
208+
.form-grid {
209+
display: flex;
210+
flex-direction: column;
211+
gap: 1rem;
212+
}
213+
214+
.field {
215+
display: flex;
216+
flex-direction: column;
217+
gap: 0.5rem;
218+
}
219+
220+
.field-row {
221+
display: flex;
222+
gap: 1rem;
223+
224+
.field {
225+
flex: 1;
226+
}
227+
}
228+
229+
.required-field::after {
230+
content: '*';
231+
color: var(--red-500);
232+
margin-left: 0.25rem;
233+
}
234+
235+
@media (max-width: 576px) {
236+
.field-row {
237+
flex-direction: column;
238+
}
239+
}
240+
241+
.dialog-content {
242+
padding: 1.5rem;
243+
244+
:deep(.p-card) {
245+
margin-bottom: 1.5rem;
246+
247+
.p-card-content {
248+
padding: 1rem 1.25rem;
249+
}
250+
}
251+
}
252+
</style>

0 commit comments

Comments
 (0)