Files
proxy-station/web/src/components/NodeFormModal.vue
2026-08-25 11:35:05 +08:00

185 lines
5.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import {
NModal,
NForm,
NFormItem,
NInput,
NInputNumber,
NSelect,
NSwitch,
NButton,
NSpace,
useMessage,
} from 'naive-ui';
import { nodeInputSchema, PROTOCOLS, type NodeDto, type NodeInput } from '@proxy-station/shared';
const props = defineProps<{ show: boolean; node?: NodeDto | null }>();
const emit = defineEmits<{
(e: 'update:show', v: boolean): void;
(e: 'submit', input: NodeInput, id?: string): void;
(e: 'delete', id: string): void;
}>();
const message = useMessage();
const blank = (): NodeInput => ({
name: '',
protocol: 'vmess',
server: '',
port: 443,
network: 'ws',
wsPath: '/',
wsHost: null,
tls: true,
sni: null,
skipCertVerify: false,
uuid: null,
alterId: 0,
security: 'auto',
password: null,
method: 'chacha20-poly1305',
obfsType: null,
obfsPassword: null,
region: null,
tags: [],
enabled: true,
sortOrder: 0,
});
const form = reactive<NodeInput>(blank());
watch(
() => props.show,
(show) => {
if (!show) return;
Object.assign(form, blank(), props.node ?? {});
}
);
const isEdit = computed(() => !!props.node);
const protocolOptions = PROTOCOLS.map((p) => ({ label: p, value: p }));
const tagOptions = [
{ label: 'direct直连出口', value: 'direct' },
{ label: 'warpWARP 出口)', value: 'warp' },
];
const showWs = computed(() => form.protocol !== 'hysteria2');
const showVmess = computed(() => form.protocol === 'vmess');
const showPassword = computed(() => form.protocol !== 'vmess');
const showSs = computed(() => form.protocol === 'shadowsocks');
const showHy2 = computed(() => form.protocol === 'hysteria2');
function submit() {
const result = nodeInputSchema.safeParse({ ...form });
if (!result.success) {
message.error(result.error.issues[0]?.message || '表单校验失败');
return;
}
emit('submit', { ...form }, props.node?.id);
}
</script>
<template>
<NModal
:show="show"
preset="card"
:title="isEdit ? `编辑节点 · ${node?.name}` : '添加节点'"
style="max-width: 560px"
@update:show="emit('update:show', $event)"
>
<NForm label-placement="left" label-width="92">
<NFormItem label="名称" required>
<NInput v-model:value="form.name" placeholder="如 SG-vmess-warp" />
</NFormItem>
<NFormItem label="协议" required>
<NSelect v-model:value="form.protocol" :options="protocolOptions" :disabled="isEdit" />
</NFormItem>
<NFormItem label="服务器" required>
<NInput v-model:value="form.server" placeholder="域名或 IP" class="mono" />
</NFormItem>
<NFormItem label="端口" required>
<NInputNumber v-model:value="form.port" :min="1" :max="65535" style="width: 100%" />
</NFormItem>
<template v-if="showVmess">
<NFormItem label="UUID" required>
<NInput v-model:value="form.uuid" class="mono" />
</NFormItem>
</template>
<template v-if="showPassword">
<NFormItem label="密码" required>
<NInput v-model:value="form.password" type="password" show-password-on="click" class="mono" />
</NFormItem>
</template>
<template v-if="showSs">
<NFormItem label="加密方式" required>
<NInput v-model:value="form.method" placeholder="chacha20-poly1305" class="mono" />
</NFormItem>
</template>
<template v-if="showHy2">
<NFormItem label="混淆">
<NSelect
v-model:value="form.obfsType"
:options="[
{ label: '无', value: null as any },
{ label: 'salamander', value: 'salamander' },
]"
/>
</NFormItem>
<NFormItem v-if="form.obfsType === 'salamander'" label="混淆密码" required>
<NInput v-model:value="form.obfsPassword" type="password" show-password-on="click" class="mono" />
</NFormItem>
</template>
<template v-if="showWs">
<NFormItem label="传输">
<NSelect
v-model:value="form.network"
:options="[
{ label: 'TCP', value: 'tcp' },
{ label: 'WebSocket', value: 'ws' },
]"
/>
</NFormItem>
<template v-if="form.network === 'ws'">
<NFormItem label="WS 路径" required>
<NInput v-model:value="form.wsPath" placeholder="/xxxxxx" class="mono" />
</NFormItem>
<NFormItem label="WS Host">
<NInput v-model:value="form.wsHost" placeholder="默认与服务器相同" class="mono" />
</NFormItem>
</template>
</template>
<NFormItem label="TLS">
<NSwitch v-model:value="form.tls" :disabled="form.protocol === 'hysteria2'" />
</NFormItem>
<NFormItem v-if="form.tls" label="SNI">
<NInput v-model:value="form.sni" placeholder="默认与服务器相同" class="mono" />
</NFormItem>
<NFormItem label="出口标签">
<NSelect v-model:value="form.tags" multiple :options="tagOptions" placeholder="direct / warp用于线路矩阵归位" />
</NFormItem>
<NFormItem label="站点代号">
<NInput v-model:value="form.region" placeholder="如 SGLARN留空则按域名推断" />
</NFormItem>
<NFormItem label="启用">
<NSwitch v-model:value="form.enabled" />
</NFormItem>
</NForm>
<template #footer>
<NSpace justify="space-between">
<NButton v-if="isEdit" type="error" quaternary @click="emit('delete', node!.id)">删除节点</NButton>
<span v-else />
<NSpace>
<NButton @click="emit('update:show', false)">取消</NButton>
<NButton type="primary" @click="submit">{{ isEdit ? '保存' : '添加' }}</NButton>
</NSpace>
</NSpace>
</template>
</NModal>
</template>