Outline 本身是谷歌 Jigsaw 团队推出的开源代理工具,通过 Shadowsocks 协议提供加密访问能力,官方的 Outline Manager 已经能完成服务端的创建和密钥管理。但官方界面偏通用,很多个人用户在长期使用时会希望有一个更符合自己习惯的查看入口,比如只看密钥用量、只关心流量趋势。这篇文章就围绕这个需求,讲讲如何用 Vue 3 做一次规范的工程化开发,把 Outline 的管理能力封装成一个私人使用的访问面板。

一、项目初始化与工程化结构设计
工程化的第一步是选好脚手架和依赖管理方式。Vue 3 官方推荐 Vite 作为构建工具,冷启动速度和热更新体验都比老一代的 webpack 方案好很多。创建项目时执行 npm create vite@latest outline-panel -- --template vue 即可生成基础骨架,随后按需补充 vue-router、pinia 和 axios 三个核心依赖,分别负责路由、状态管理和接口请求。
目录结构上建议按功能域划分而不是按文件类型划分。私人面板虽然规模不大,但从一开始就按模块组织,后期加功能会轻松很多。推荐的结构如下:
outline-panel/ ├── src/ │ ├── api/ # 接口封装层 │ │ └── outline.js # Outline API 请求 │ ├── stores/ # pinia 状态模块 │ │ └── server.js │ ├── views/ # 页面组件 │ │ ├── KeyList.vue │ │ └── Dashboard.vue │ ├── components/ # 通用组件 │ ├── router/index.js │ ├── App.vue │ └── main.js ├── vite.config.js └── package.json
这种结构的核心思想是把接口调用和状态逻辑从视图层剥离。比如将来想把流量图表从 ECharts 换成 Chart.js,只需要改组件,api 和 stores 层完全不用动。这就是工程化带来的可维护性收益,对个人项目同样适用。
二、对接 Outline API 与密钥管理模块
Outline 服务端提供了一套本地的 REST API,安装完成后可以在 Outline Manager 的设置里找到 API 端口和证书。这套接口用自签名证书,路径形如 https://127.0.0.1:端口/xxx,常用的几个端点包括获取服务器信息、列出所有密钥、创建和删除密钥等。前端面板要做的第一件事就是把这些能力包装成干净的请求函数。
由于自签名证书在浏览器里会被直接拦截,私人面板通常的做法是在自己的服务器上放一层轻量代理,转发到 Outline 的本地端口。用 Node 写一个十几行的转发服务即可:
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
const agent = new https.Agent({ rejectUnauthorized: false }); // Outline 使用自签名证书
app.use('/outline-api', (req, res) => {
const options = {
hostname: '127.0.0.1',
port: 12345, // 替换为你的 Outline API 端口
path: req.url,
method: req.method,
agent
};
const proxy = https.request(options, r => {
res.status(r.statusCode);
r.pipe(res);
});
req.pipe(proxy);
});
app.listen(3001, () => console.log('代理已启动'));
前端这边用 axios 封装请求,配合 Composition API 组织逻辑。以密钥列表为例,把数据获取、删除操作都收进一个组合式函数,视图层只负责渲染:
import { ref, onMounted } from 'vue';
import axios from 'axios';
const http = axios.create({ baseURL: '/outline-api' });
export function useKeys() {
const keys = ref([]);
const loading = ref(false);
const fetchKeys = async () => {
loading.value = true;
try {
// Outline API 获取访问密钥列表
const res = await http.get('/access-keys');
keys.value = res.data.accessKeys || [];
} finally {
loading.value = false;
}
};
const removeKey = async (id) => {
await http.delete(`/access-keys/${id}`);
await fetchKeys();
};
onMounted(fetchKeys);
return { keys, loading, fetchKeys, removeKey };
}
这种写法的好处是逻辑可复用,如果面板里有两个页面都需要密钥数据,直接调用 useKeys 就行。同时请求失败的处理、loading 状态的切换都集中在一处,排查问题的时候不需要在组件里翻来找去。
三、流量统计展示与权限控制
流量是私人面板最有价值的信息。Outline API 提供按名称维度的流量指标,通过 /metrics/transfer 端点可以拿到每个密钥的上传下载字节数。前端拿到原始字节数后建议统一封装一个格式化函数,把字节数转成 MB 或 GB,展示时用户体验会好很多。
export function formatBytes(bytes) {
if (!bytes) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + units[i];
}
图表部分可以引入 ECharts 的按需加载,只注册折线图和柱状图组件,避免把整个库打进去导致产物膨胀。数据层面,由于 Outline 只提供累计值,如果要做每日趋势图,需要自己在代理层做定时采样存储,比如每小时请求一次累计值存到 SQLite,前端再按天求差值。这个采样逻辑放在服务端比放在浏览器里更可靠,毕竟浏览器窗口不一定会一直开着。
权限控制方面,私人面板不需要复杂的用户体系,但也绝不能裸奔在公网上。最简单有效的方案是在代理层做一个固定 Token 校验:前端登录页让用户输入访问口令,存入 pinia 并通过请求拦截器附加到每个请求头中,代理层校验失败统一返回 401。代码上只需在 axios 拦截器里加几行:
http.interceptors.request.use(config => {
const token = localStorage.getItem('panel_token');
if (token) config.headers['X-Panel-Token'] = token;
return config;
});
代理层校验通过后再转发到 Outline 本地端口。整套机制没有引入任何重量级认证框架,却把面板和公网上的随意访问隔离开了,对私人工具来说这个安全强度已经够用。
四、打包部署与常见问题处理
开发完成后执行 npm run build 产出静态文件,可以直接托管在刚才那层代理服务里,用 express 的静态目录功能一并搞定。部署时有几个点需要留意:第一,如果面板走域名访问,务必配好 HTTPS,可以用免费的 Let's Encrypt 证书,否则部分浏览器功能会受限;第二,Vite 项目默认的资源路径是绝对路径,如果部署在子目录下需要在 vite.config.js 里设置 base 选项,否则会出现白屏加资源 404 的经典问题。
跨域问题也是高频坑。开发阶段在 vite.config.js 中配置代理即可:
export default defineConfig({
server: {
proxy: {
'/outline-api': {
target: 'http://127.0.0.1:3001',
changeOrigin: true
}
}
}
});
生产阶段则建议直接让静态资源和 API 走同一个域名,从根上避免跨域。最后提醒一点,Outline 的 API 地址和证书属于敏感信息,不要提交到公开仓库,可以用 .env 文件配合 .gitignore 管理。整个面板做完,你就有了一个完全由自己掌控的访问工具界面,密钥分配、流量一目了然,后续想加告警、多用户管理都有清晰的扩展路径。
Vue 3Outline VPN前端工程化修改时间:2026-09-12 23:42:40