feat: 全协议一键安装 + 国内GitHub镜像加速 + 中英文i18n国际化

后端:
- binmgr.go: 新增 detectChina() 国内/海外自动判断(ipinfo.io)
- binmgr.go: 国内自动走 gh-proxy.com 镜像加速 GitHub 下载
- binmgr.go: 新增 InstallViaPackageManager() 包管理器一键安装
- binmgr.go: 新增 GetDeploySpec()/ProtocolAlias() 导出函数
- api.go: installBinary 支持全部7个协议(FRP/WireGuard/OpenVPN/IPSec/IKEv2/L2TP/StrongSwan)
- api.go: 策略: GitHub下载失败 → 自动降级包管理器安装

前端:
- 安装 vue-i18n@9, 支持中英文切换
- 新增 locales/zh-CN.json + en-US.json (548个翻译key, 17个模块)
- 新增 i18n.js 配置, localStorage 持久化语言偏好
- Layout.vue 右上角语言切换下拉菜单
- 所有17个Vue文件中文文本转为 $t() 调用
- Element Plus 组件语言跟随切换
- TunnelWizard: 所有协议显示「一键安装」按钮(带loading)
- 前端构建零错误
This commit is contained in:
opsx-tools committed 2026-07-23 03:01:46 +08:00
1 parent 1e2f8ef24f
commit 021d012478
25 files changed
+2313 -746

No files matched your search

+18 -16
View File
@@ -2,19 +2,19 @@
<el-card class="page-card" shadow="never" v-loading="loading">
<template #header>
<div style="display:flex;justify-content:space-between;align-items:center">
<b>编辑专线</b>
<el-button @click="router.push('/tunnels')">返回列表</el-button>
<b>{{ $t('tunnelEditor.title') }}</b>
<el-button @click="router.push('/tunnels')">{{ $t('tunnelEditor.backToList') }}</el-button>
</div>
</template>
<el-alert type="info" :closable="false" style="margin-bottom:18px"
title="密码、PSK、Token 等敏感字段不会回显;留空表示继续使用原值。" />
:title="$t('tunnelEditor.sensitiveFieldHint')" />
<el-form v-if="form" label-width="170px" label-position="left" style="max-width:720px">
<el-form-item label="专线名称" required>
<el-form-item :label="$t('tunnelEditor.tunnelName')" required>
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="协议类型">
<el-form-item :label="$t('tunnelEditor.protocolType')">
<el-tag :color="protocolColor(protocol)" effect="dark" style="border:none">{{ protocolDisplay(protocol) }}</el-tag>
</el-form-item>
<el-form-item v-for="field in visibleFields" :key="field.key" :label="field.label" :required="field.required">
@@ -24,18 +24,18 @@
<el-option v-for="option in field.options" :key="optionValue(option)" :label="optionLabel(option)" :value="optionValue(option)" />
</el-select>
<el-input v-else-if="field.type === 'password'" v-model="form.config[field.key]" type="password"
show-password :placeholder="field.placeholder || '留空保持原值'" />
show-password :placeholder="field.placeholder || $t('tunnelEditor.keepOriginal')" />
<el-input v-else v-model="form.config[field.key]" :placeholder="field.placeholder || field.default" />
<div v-if="field.tip" class="field-tip">{{ field.tip }}</div>
</el-form-item>
<el-form-item label="到期时间">
<el-date-picker v-model="form.expire_at" type="datetime" clearable placeholder="留空为永久" style="width:100%" />
<el-form-item :label="$t('tunnelEditor.expireTime')">
<el-date-picker v-model="form.expire_at" type="datetime" clearable :placeholder="$t('tunnelEditor.expirePlaceholder')" style="width:100%" />
</el-form-item>
<el-form-item label="自动启动"><el-switch v-model="form.auto_start" /></el-form-item>
<el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" /></el-form-item>
<el-form-item :label="$t('tunnelEditor.autoStart')"><el-switch v-model="form.auto_start" /></el-form-item>
<el-form-item :label="$t('common.remark')"><el-input v-model="form.remark" type="textarea" :rows="3" /></el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="save">保存配置</el-button>
<el-button @click="router.push('/tunnels')">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">{{ $t('tunnelEditor.saveConfig') }}</el-button>
<el-button @click="router.push('/tunnels')">{{ $t('common.cancel') }}</el-button>
</el-form-item>
</el-form>
</el-card>
@@ -45,9 +45,11 @@
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n'
import api from '../api'
import { CUSTOM_FIELDS, PROTOCOL_META, protocolColor, protocolDisplay, optionValue, optionLabel } from '../protocols'
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const loading = ref(true)
@@ -84,7 +86,7 @@ async function load() {
config,
}
} catch (error) {
ElMessage.error(error.response?.data?.error || '加载专线失败')
ElMessage.error(error.response?.data?.error || t('tunnelEditor.loadFailed'))
router.push('/tunnels')
} finally {
loading.value = false
@@ -92,14 +94,14 @@ async function load() {
}
async function save() {
if (!form.value.name) return ElMessage.warning('请填写专线名称')
if (!form.value.name) return ElMessage.warning(t('tunnelEditor.fillName'))
saving.value = true
try {
await api.put(`/tunnels/${route.params.id}`, form.value)
ElMessage.success('配置已保存')
ElMessage.success(t('tunnelEditor.configSaved'))
router.push('/tunnels')
} catch (error) {
ElMessage.error(error.response?.data?.error || '保存失败')
ElMessage.error(error.response?.data?.error || t('common.saveFailed'))
} finally {
saving.value = false
}