16 changed files with 12212 additions and 1 deletions
@ -1 +1,3 @@ |
|||||||
{} |
{ |
||||||
|
"git.ignoreLimitWarning": true |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@ |
|||||||
|
{ |
||||||
|
"name": "GoFaster", |
||||||
|
"version": "1.0.0", |
||||||
|
"main": "src/main/index.js", |
||||||
|
"scripts": { |
||||||
|
"serve": "vue-cli-service serve", |
||||||
|
"build": "vue-cli-service build && electron-builder", |
||||||
|
"start": "electron .", |
||||||
|
"dev": "concurrently \"npm run serve\" \"wait-on http://localhost:8080 && npm run start\"", |
||||||
|
"postinstall": "electron-builder install-app-deps" |
||||||
|
}, |
||||||
|
"keywords": [], |
||||||
|
"author": "", |
||||||
|
"license": "ISC", |
||||||
|
"description": "", |
||||||
|
"devDependencies": { |
||||||
|
"@electron/remote": "^2.1.3", |
||||||
|
"@vue/cli-service": "^5.0.8", |
||||||
|
"concurrently": "^9.2.0", |
||||||
|
"electron": "^37.2.6", |
||||||
|
"electron-builder": "^26.0.12", |
||||||
|
"vue": "^3.5.18", |
||||||
|
"wait-on": "^8.0.4" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"lowdb": "^7.0.1", |
||||||
|
"vue-router": "^4.5.1", |
||||||
|
"vuex": "^4.0.2" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>GoFaster</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,43 @@ |
|||||||
|
const { app, BrowserWindow } = require('electron') |
||||||
|
const path = require('path') |
||||||
|
|
||||||
|
let mainWindow |
||||||
|
|
||||||
|
function createWindow() { |
||||||
|
mainWindow = new BrowserWindow({ |
||||||
|
width: 1200, |
||||||
|
height: 800, |
||||||
|
webPreferences: { |
||||||
|
nodeIntegration: false, |
||||||
|
contextIsolation: true, |
||||||
|
enableRemoteModule: false, |
||||||
|
preload: path.join(__dirname, '../preload.js') |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
// 开发模式下加载开发服务器
|
||||||
|
if (process.env.NODE_ENV === 'development') { |
||||||
|
mainWindow.loadURL('http://localhost:8080') |
||||||
|
mainWindow.webContents.openDevTools() |
||||||
|
} else { |
||||||
|
mainWindow.loadFile(path.join(__dirname, '../public/index.html')) |
||||||
|
} |
||||||
|
|
||||||
|
mainWindow.on('closed', () => { |
||||||
|
mainWindow = null |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
app.whenReady().then(createWindow) |
||||||
|
|
||||||
|
app.on('window-all-closed', () => { |
||||||
|
if (process.platform !== 'darwin') { |
||||||
|
app.quit() |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
app.on('activate', () => { |
||||||
|
if (mainWindow === null) { |
||||||
|
createWindow() |
||||||
|
} |
||||||
|
}) |
@ -0,0 +1,7 @@ |
|||||||
|
const { contextBridge } = require('electron') |
||||||
|
|
||||||
|
// 安全地暴露API给渲染进程
|
||||||
|
contextBridge.exposeInMainWorld('electron', { |
||||||
|
platform: process.platform |
||||||
|
// 可以添加更多安全的API
|
||||||
|
}) |
@ -0,0 +1,23 @@ |
|||||||
|
<template> |
||||||
|
<div id="app"> |
||||||
|
<router-view /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'App' |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style> |
||||||
|
/* 全局样式 */ |
||||||
|
#app { |
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif; |
||||||
|
-webkit-font-smoothing: antialiased; |
||||||
|
-moz-osx-font-smoothing: grayscale; |
||||||
|
color: #2c3e50; |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,107 @@ |
|||||||
|
<template> |
||||||
|
<div class="speed-test"> |
||||||
|
<h1>GoFaster Speed Test</h1> |
||||||
|
<button @click="startTest" :disabled="testing"> |
||||||
|
{{ testing ? 'Testing...' : 'Start Test' }} |
||||||
|
</button> |
||||||
|
|
||||||
|
<div v-if="result" class="results"> |
||||||
|
<h3>Test Results:</h3> |
||||||
|
<div class="result-item"> |
||||||
|
<span class="label">Download:</span> |
||||||
|
<span class="value">{{ result.download }} Mbps</span> |
||||||
|
</div> |
||||||
|
<div class="result-item"> |
||||||
|
<span class="label">Upload:</span> |
||||||
|
<span class="value">{{ result.upload }} Mbps</span> |
||||||
|
</div> |
||||||
|
<div class="result-item"> |
||||||
|
<span class="label">Ping:</span> |
||||||
|
<span class="value">{{ result.ping }} ms</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { useStore } from 'vuex' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'SpeedTest', |
||||||
|
setup() { |
||||||
|
const store = useStore() |
||||||
|
const testing = ref(false) |
||||||
|
const result = ref(null) |
||||||
|
|
||||||
|
const startTest = async () => { |
||||||
|
testing.value = true |
||||||
|
result.value = null |
||||||
|
|
||||||
|
// 模拟网络测试 (替换为真实测试逻辑) |
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000)) |
||||||
|
|
||||||
|
const testResult = { |
||||||
|
download: (Math.random() * 100).toFixed(2), |
||||||
|
upload: (Math.random() * 50).toFixed(2), |
||||||
|
ping: (Math.random() * 100).toFixed(2), |
||||||
|
timestamp: new Date().toISOString() |
||||||
|
} |
||||||
|
|
||||||
|
// 保存结果 |
||||||
|
await store.dispatch('saveTestResult', testResult) |
||||||
|
result.value = testResult |
||||||
|
testing.value = false |
||||||
|
} |
||||||
|
|
||||||
|
return { testing, result, startTest } |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.speed-test { |
||||||
|
padding: 20px; |
||||||
|
max-width: 600px; |
||||||
|
margin: 0 auto; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
padding: 10px 20px; |
||||||
|
font-size: 16px; |
||||||
|
background-color: #42b983; |
||||||
|
color: white; |
||||||
|
border: none; |
||||||
|
border-radius: 4px; |
||||||
|
cursor: pointer; |
||||||
|
margin: 20px 0; |
||||||
|
} |
||||||
|
|
||||||
|
button:disabled { |
||||||
|
background-color: #cccccc; |
||||||
|
cursor: not-allowed; |
||||||
|
} |
||||||
|
|
||||||
|
.results { |
||||||
|
margin-top: 20px; |
||||||
|
padding: 15px; |
||||||
|
background-color: #f5f5f5; |
||||||
|
border-radius: 4px; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
|
||||||
|
.result-item { |
||||||
|
margin: 10px 0; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
} |
||||||
|
|
||||||
|
.label { |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.value { |
||||||
|
color: #42b983; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,13 @@ |
|||||||
|
import { createApp } from 'vue' |
||||||
|
import App from './App.vue' |
||||||
|
import router from './router' |
||||||
|
import store from './store' |
||||||
|
|
||||||
|
// 初始化数据库
|
||||||
|
import { initDB } from './services/db' |
||||||
|
initDB().then(() => { |
||||||
|
createApp(App) |
||||||
|
.use(store) |
||||||
|
.use(router) |
||||||
|
.mount('#app') |
||||||
|
}) |
@ -0,0 +1,23 @@ |
|||||||
|
import { createRouter, createWebHashHistory } from 'vue-router' |
||||||
|
import Home from '@/views/Home.vue' |
||||||
|
import History from '@/views/History.vue' |
||||||
|
|
||||||
|
const routes = [ |
||||||
|
{ |
||||||
|
path: '/', |
||||||
|
name: 'Home', |
||||||
|
component: Home |
||||||
|
}, |
||||||
|
{ |
||||||
|
path: '/history', |
||||||
|
name: 'History', |
||||||
|
component: History |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
const router = createRouter({ |
||||||
|
history: createWebHashHistory(), |
||||||
|
routes |
||||||
|
}) |
||||||
|
|
||||||
|
export default router |
@ -0,0 +1,20 @@ |
|||||||
|
import { Low, JSONFile } from 'lowdb' |
||||||
|
import { join } from 'path' |
||||||
|
import { app } from '@electron/remote' |
||||||
|
|
||||||
|
const file = join(app.getPath('userData'), 'gofaster-db.json') |
||||||
|
const adapter = new JSONFile(file) |
||||||
|
const db = new Low(adapter) |
||||||
|
|
||||||
|
// 初始化数据库
|
||||||
|
async function initDB() { |
||||||
|
await db.read() |
||||||
|
db.data ||= {
|
||||||
|
settings: {},
|
||||||
|
activities: [],
|
||||||
|
stats: {}
|
||||||
|
} |
||||||
|
await db.write() |
||||||
|
} |
||||||
|
|
||||||
|
export { db, initDB } |
@ -0,0 +1,28 @@ |
|||||||
|
import { createStore } from 'vuex' |
||||||
|
import { db } from '../services/db' |
||||||
|
|
||||||
|
export default createStore({ |
||||||
|
state: { |
||||||
|
testResults: [] |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
setTestResults(state, results) { |
||||||
|
state.testResults = results |
||||||
|
}, |
||||||
|
addTestResult(state, result) { |
||||||
|
state.testResults.unshift(result) |
||||||
|
} |
||||||
|
}, |
||||||
|
actions: { |
||||||
|
async loadTestResults({ commit }) { |
||||||
|
await db.read() |
||||||
|
commit('setTestResults', db.data.activities || []) |
||||||
|
}, |
||||||
|
async saveTestResult({ commit }, result) { |
||||||
|
await db.read() |
||||||
|
db.data.activities.unshift(result) |
||||||
|
await db.write() |
||||||
|
commit('addTestResult', result) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
@ -0,0 +1,99 @@ |
|||||||
|
<template> |
||||||
|
<div class="history-view"> |
||||||
|
<h1>Test History</h1> |
||||||
|
|
||||||
|
<div v-if="testResults.length === 0" class="empty-state"> |
||||||
|
No speed tests recorded yet. |
||||||
|
</div> |
||||||
|
|
||||||
|
<div v-else class="history-list"> |
||||||
|
<div v-for="(result, index) in testResults" :key="index" class="history-item"> |
||||||
|
<div class="history-date"> |
||||||
|
{{ formatDate(result.timestamp) }} |
||||||
|
</div> |
||||||
|
<div class="history-stats"> |
||||||
|
<span class="stat download">↓ {{ result.download }} Mbps</span> |
||||||
|
<span class="stat upload">↑ {{ result.upload }} Mbps</span> |
||||||
|
<span class="stat ping">↔ {{ result.ping }} ms</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { computed, onMounted } from 'vue' |
||||||
|
import { useStore } from 'vuex' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'HistoryView', |
||||||
|
setup() { |
||||||
|
const store = useStore() |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
store.dispatch('loadTestResults') |
||||||
|
}) |
||||||
|
|
||||||
|
const testResults = computed(() => store.state.testResults) |
||||||
|
|
||||||
|
const formatDate = (timestamp) => { |
||||||
|
return new Date(timestamp).toLocaleString() |
||||||
|
} |
||||||
|
|
||||||
|
return { testResults, formatDate } |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.history-view { |
||||||
|
padding: 20px; |
||||||
|
max-width: 800px; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
.empty-state { |
||||||
|
text-align: center; |
||||||
|
padding: 40px; |
||||||
|
color: #888; |
||||||
|
} |
||||||
|
|
||||||
|
.history-list { |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
.history-item { |
||||||
|
padding: 15px; |
||||||
|
margin-bottom: 10px; |
||||||
|
background-color: #f9f9f9; |
||||||
|
border-radius: 4px; |
||||||
|
display: flex; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.history-date { |
||||||
|
color: #666; |
||||||
|
} |
||||||
|
|
||||||
|
.history-stats { |
||||||
|
display: flex; |
||||||
|
gap: 15px; |
||||||
|
} |
||||||
|
|
||||||
|
.stat { |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.download { |
||||||
|
color: #42b983; |
||||||
|
} |
||||||
|
|
||||||
|
.upload { |
||||||
|
color: #3498db; |
||||||
|
} |
||||||
|
|
||||||
|
.ping { |
||||||
|
color: #e74c3c; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,21 @@ |
|||||||
|
<!-- src/renderer/views/Home.vue --> |
||||||
|
<template> |
||||||
|
<div class="home"> |
||||||
|
<h1>Welcome to Your Electron App</h1> |
||||||
|
<!-- 这里可以放你的首页内容 --> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'HomeView' |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
/* 可以添加一些基础样式 */ |
||||||
|
.home { |
||||||
|
padding: 20px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,55 @@ |
|||||||
|
const path = require('path') |
||||||
|
const { defineConfig } = require('@vue/cli-service') |
||||||
|
|
||||||
|
module.exports = defineConfig({ |
||||||
|
outputDir: path.resolve(__dirname, 'dist/renderer'), |
||||||
|
publicPath: './', |
||||||
|
|
||||||
|
// 关键修改:配置 Electron 构建目标和排除原生模块
|
||||||
|
configureWebpack: { |
||||||
|
target: 'electron-renderer', // 指定为 Electron 渲染进程
|
||||||
|
externals: { |
||||||
|
electron: 'require("electron")', // 防止 Webpack 处理 electron 模块
|
||||||
|
fs: 'require("fs")', // 排除 Node.js 原生模块
|
||||||
|
path: 'require("path")', |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
alias: { |
||||||
|
'@': path.resolve(__dirname, 'src/renderer'), |
||||||
|
// 确保 Vue 单文件组件引用时自动补全扩展名
|
||||||
|
'views': path.resolve(__dirname, 'src/renderer/views') |
||||||
|
}, |
||||||
|
// 添加扩展名自动解析
|
||||||
|
extensions: ['.js', '.vue', '.json'] |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// 保留原有页面配置
|
||||||
|
pages: { |
||||||
|
index: { |
||||||
|
entry: 'src/renderer/main.js', |
||||||
|
template: 'public/index.html', |
||||||
|
filename: 'index.html' |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// 保留 Electron Builder 配置
|
||||||
|
pluginOptions: { |
||||||
|
electronBuilder: { |
||||||
|
preload: 'src/preload.js', |
||||||
|
nodeIntegration: false, |
||||||
|
contextIsolation: true, |
||||||
|
// 可选:明确指定主进程文件路径
|
||||||
|
mainProcessFile: 'src/main/index.js' |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// 开发服务器配置(与 Electron 主进程配合)
|
||||||
|
devServer: { |
||||||
|
port: 8080, // 确保与 wait-on 端口一致
|
||||||
|
hot: true, |
||||||
|
headers: { |
||||||
|
'Access-Control-Allow-Origin': '*' |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
Loading…
Reference in new issue