Browse Source

主窗体基本完成

master
hejl 2 weeks ago
parent
commit
decde488fc
  1. 181
      gofaster/README.md
  2. 115
      gofaster/app/DEVELOPMENT.md
  3. 41
      gofaster/app/dev-stable.bat
  4. 24
      gofaster/app/dev.bat
  5. 25
      gofaster/app/dev.ps1
  6. BIN
      gofaster/app/dist/renderer/img/background.7c5f6fb1.png
  7. 3276
      gofaster/app/dist/renderer/js/index.js
  8. 5223
      gofaster/app/dist/renderer/js/vendors.js
  9. 302
      gofaster/app/dist/renderer/src_renderer_components_SpeedTest_vue.js
  10. 670
      gofaster/app/dist/renderer/src_renderer_views_Settings_vue.js
  11. 30
      gofaster/app/package-lock.json
  12. 7
      gofaster/app/package.json
  13. 184
      gofaster/app/src/main/index.js
  14. 3
      gofaster/app/src/preload.js
  15. 87
      gofaster/app/src/renderer/App.vue
  16. 62
      gofaster/app/src/renderer/assets/fonts.css
  17. 971
      gofaster/app/src/renderer/components/MainLayout.vue
  18. 134
      gofaster/app/src/renderer/components/StatusBar.vue
  19. 69
      gofaster/app/src/renderer/main.js
  20. 23
      gofaster/app/src/renderer/router/index.js
  21. 196
      gofaster/app/src/renderer/services/userService.js
  22. 159
      gofaster/app/src/renderer/views/Home.vue
  23. 399
      gofaster/app/src/renderer/views/Settings.vue
  24. 642
      gofaster/app/src/renderer/views/UserManagement.vue

181
gofaster/README.md

@ -0,0 +1,181 @@ @@ -0,0 +1,181 @@
# GoFaster 前端系统
## 项目概述
GoFaster是一个基于Electron + Vue3的桌面应用程序,包含前端APP和后端Go服务。本仓库包含前端部分的完整实现。
## 功能特性
### 🎯 核心功能
- **用户管理**: 完整的用户CRUD操作,支持角色分配和权限管理
- **速度测试**: 网络连接速度测试功能
- **历史记录**: 测试历史数据查看和管理
- **系统设置**: 可配置的应用参数设置
### 🎨 界面特性
- **现代化设计**: 采用Material Design风格,界面美观易用
- **响应式布局**: 支持不同屏幕尺寸,适配各种设备
- **主题切换**: 支持浅色/深色主题切换
- **多语言支持**: 内置中英文界面
### 🔧 技术特性
- **标签页管理**: 支持多标签页操作,提高工作效率
- **收藏菜单**: 可收藏常用功能,快速访问
- **消息中心**: 实时消息通知和管理
- **面包屑导航**: 清晰的页面层级导航
## 技术架构
### 前端技术栈
- **框架**: Vue 3 + Composition API
- **构建工具**: Vue CLI + Webpack
- **桌面框架**: Electron
- **路由**: Vue Router 4
- **状态管理**: Vuex 4
- **HTTP客户端**: Axios
- **样式**: CSS3 + 响应式设计
### 项目结构
```
app/
├── src/
│ ├── main/ # Electron主进程
│ ├── renderer/ # 渲染进程
│ │ ├── components/ # 组件
│ │ ├── views/ # 页面
│ │ ├── services/ # 服务层
│ │ ├── router/ # 路由配置
│ │ └── store/ # 状态管理
│ └── public/ # 静态资源
├── package.json # 依赖配置
└── vue.config.js # Vue配置
```
## 快速开始
### 环境要求
- Node.js 16+
- npm 8+
### 安装依赖
```bash
cd app
npm install
```
### 开发模式
```bash
npm run dev
```
### 构建生产版本
```bash
npm run build
```
## 主要组件说明
### 1. MainLayout.vue - 主布局组件
- **顶部导航栏**: Logo、面包屑导航、消息中心、用户信息
- **左侧菜单**: 主要功能菜单、收藏菜单
- **内容区域**: 标签页管理、功能内容展示
### 2. UserManagement.vue - 用户管理页面
- **用户列表**: 分页显示、搜索筛选、状态管理
- **用户操作**: 添加、编辑、删除用户
- **角色管理**: 用户角色分配和权限控制
### 3. Settings.vue - 系统设置页面
- **基本设置**: 应用名称、语言、主题
- **网络设置**: API地址、超时配置、重试策略
- **用户设置**: 登录配置、会话管理
- **通知设置**: 桌面通知、声音提醒
- **数据设置**: 缓存管理、备份策略
## 后端API集成
### 用户管理API
- `GET /api/users` - 获取用户列表
- `POST /api/users` - 创建用户
- `PUT /api/users/:id` - 更新用户
- `DELETE /api/users/:id` - 删除用户
- `GET /api/roles` - 获取角色列表
### 认证API
- `POST /api/auth/login` - 用户登录
- `POST /api/auth/logout` - 用户登出
- `GET /api/auth/me` - 获取当前用户信息
## 配置说明
### 环境配置
`src/renderer/services/userService.js` 中配置API基础URL:
```javascript
const API_BASE_URL = 'http://localhost:8080/api'
```
### 主题配置
支持三种主题模式:
- `light`: 浅色主题
- `dark`: 深色主题
- `auto`: 跟随系统设置
## 开发指南
### 添加新页面
1. 在 `src/renderer/views/` 创建新的Vue组件
2. 在 `src/renderer/router/index.js` 添加路由配置
3. 在 `src/renderer/components/MainLayout.vue` 添加菜单项
### 添加新API
1. 在 `src/renderer/services/` 创建新的服务文件
2. 实现相应的API调用方法
3. 在组件中引入并使用
### 样式规范
- 使用CSS3和Flexbox布局
- 遵循Material Design设计规范
- 支持响应式设计,适配移动端
## 部署说明
### 开发环境
- 后端服务运行在 `localhost:8080`
- 前端开发服务器自动构建并启动Electron
### 生产环境
- 使用 `npm run build` 构建生产版本
- 生成的可执行文件在 `dist/` 目录
## 常见问题
### Q: 如何修改API服务器地址?
A: 在设置页面的"网络设置"部分修改API服务器地址,或直接修改 `userService.js` 中的 `API_BASE_URL`
### Q: 如何添加新的菜单项?
A: 在 `MainLayout.vue``mainMenuItems` 数组中添加新的菜单项,包含id、name、path、icon等属性。
### Q: 如何自定义主题颜色?
A: 在CSS中修改CSS变量或直接修改相应的颜色值。
## 贡献指南
1. Fork 本仓库
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 创建Pull Request
## 许可证
本项目采用 ISC 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
## 联系方式
- 项目维护者: GoFaster Team
- 邮箱: support@gofaster.com
- 项目地址: https://github.com/gofaster/gofaster
---
**注意**: 本项目需要配合Go后端服务使用,请确保后端服务正常运行。

115
gofaster/app/DEVELOPMENT.md

@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
# GoFaster 开发指南
## 解决Cursor终端中文乱码问题
由于PowerShell的编码问题,在运行`npm run dev`时可能会出现中文乱码。我们提供了多种解决方案:
### 方案1:使用稳定开发脚本(推荐)
```bash
npm run dev:stable
```
这个命令会:
- 自动设置控制台编码为UTF-8
- 设置正确的环境变量
- 构建Vue应用
- 使用稳定的Electron配置启动
- 包含错误处理和重试机制
### 方案2:使用批处理文件
```bash
npm run dev:bat
```
这个命令会:
- 自动设置控制台编码为UTF-8
- 设置环境变量
- 构建并启动应用
### 方案3:使用PowerShell脚本
```bash
npm run dev:ps
```
这个命令会:
- 设置PowerShell编码为UTF-8
- 设置环境变量
- 构建并启动应用
### 方案4:分步执行
如果上述方案仍有问题,可以分步执行:
```bash
# 第一步:构建Vue应用
npm run build:vue
# 第二步:启动Electron
electron .
```
## 解决热加载问题
### 常见热加载错误
1. **JavaScript注入失败**:`An object could not be cloned`
- 原因:注入的JavaScript包含无法序列化的对象
- 解决:使用`npm run dev:stable`启动
2. **页面重载失败**:频繁的文件变化导致重载问题
- 解决:已添加500ms防抖机制
3. **渲染进程崩溃**:开发环境不稳定
- 解决:使用稳定的Electron启动参数
### 稳定开发配置
`dev:stable`脚本包含以下稳定配置:
- `--disable-gpu`:禁用GPU加速,提高稳定性
- `--disable-software-rasterizer`:禁用软件光栅化
- `--disable-dev-shm-usage`:禁用开发共享内存
## 环境变量说明
- `VUE_CLI_BABEL_TRANSPILE_MODULES=false`: 禁用Babel模块转译
- `VUE_CLI_MODERN_BUILD=false`: 禁用现代构建模式
- `NODE_ENV=development`: 设置开发环境
## 故障排除
### 如果仍然出现乱码:
1. **检查PowerShell版本**:确保使用PowerShell 5.1或更高版本
2. **设置执行策略**:`Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
3. **使用Windows Terminal**:Windows Terminal对UTF-8支持更好
4. **检查系统区域设置**:确保系统支持UTF-8
### 如果热加载失败:
1. **使用稳定脚本**:`npm run dev:stable`
2. **清理构建缓存**:删除`dist`目录后重新构建
3. **检查文件权限**:确保有足够的文件读写权限
4. **重启开发环境**:完全关闭后重新启动
### 如果构建失败:
1. **清理缓存**:`npm run clean` 或删除 `node_modules``dist` 目录
2. **重新安装依赖**:`npm install`
3. **检查Node.js版本**:确保使用Node.js 16或更高版本
## 开发流程
1. 使用 `npm run dev:stable` 启动开发环境(推荐)
2. 修改代码后,应用会自动重新加载
3. 使用 `Ctrl+C` 停止开发服务器
## 注意事项
- 所有控制台输出都已改为英文,避免编码问题
- 应用界面仍然支持中文显示
- 字体配置已优化,确保中文正常显示
- 热加载已优化,包含错误处理和防抖机制
- 提供多种启动方式,适应不同环境需求

41
gofaster/app/dev-stable.bat

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo Starting GoFaster development with stable configuration...
echo Setting environment variables...
set VUE_CLI_BABEL_TRANSPILE_MODULES=false
set VUE_CLI_MODERN_BUILD=false
set NODE_ENV=development
echo VUE_CLI_BABEL_TRANSPILE_MODULES: %VUE_CLI_BABEL_TRANSPILE_MODULES%
echo VUE_CLI_MODERN_BUILD: %VUE_CLI_MODERN_BUILD%
echo NODE_ENV: %NODE_ENV%
echo Building Vue application...
call vue-cli-service build --mode development
if %ERRORLEVEL% EQU 0 (
echo Vue build successful, starting Electron...
echo Starting with stable configuration...
electron . --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
) else (
echo Vue build failed!
echo Attempting to clean and rebuild...
echo Cleaning dist directory...
if exist dist rmdir /s /q dist
echo Rebuilding Vue application...
call vue-cli-service build --mode development
if %ERRORLEVEL% EQU 0 (
echo Rebuild successful, starting Electron...
electron . --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
) else (
echo Rebuild failed! Please check your code for errors.
pause
exit /b 1
)
)

24
gofaster/app/dev.bat

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo Starting GoFaster development with UTF-8 encoding...
echo Setting environment variables...
set VUE_CLI_BABEL_TRANSPILE_MODULES=false
set VUE_CLI_MODERN_BUILD=false
echo VUE_CLI_BABEL_TRANSPILE_MODULES: %VUE_CLI_BABEL_TRANSPILE_MODULES%
echo VUE_CLI_MODERN_BUILD: %VUE_CLI_MODERN_BUILD%
echo Building Vue application...
call vue-cli-service build --mode development
if %ERRORLEVEL% EQU 0 (
echo Vue build successful, starting Electron...
electron .
) else (
echo Vue build failed!
pause
exit /b 1
)

25
gofaster/app/dev.ps1

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
# PowerShell开发脚本 - 设置正确的编码
# 设置控制台编码为UTF-8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
# 设置环境变量
$env:VUE_CLI_BABEL_TRANSPILE_MODULES = "false"
$env:VUE_CLI_MODERN_BUILD = "false"
Write-Host "Starting GoFaster development with UTF-8 encoding..." -ForegroundColor Green
Write-Host "VUE_CLI_BABEL_TRANSPILE_MODULES: $env:VUE_CLI_BABEL_TRANSPILE_MODULES" -ForegroundColor Yellow
Write-Host "VUE_CLI_MODERN_BUILD: $env:VUE_CLI_MODERN_BUILD" -ForegroundColor Yellow
# 构建Vue应用
Write-Host "Building Vue application..." -ForegroundColor Cyan
npm run build:vue
if ($LASTEXITCODE -eq 0) {
Write-Host "Vue build successful, starting Electron..." -ForegroundColor Green
# 启动Electron
electron .
} else {
Write-Host "Vue build failed!" -ForegroundColor Red
exit 1
}

BIN
gofaster/app/dist/renderer/img/background.7c5f6fb1.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

3276
gofaster/app/dist/renderer/js/index.js vendored

File diff suppressed because it is too large Load Diff

5223
gofaster/app/dist/renderer/js/vendors.js vendored

File diff suppressed because it is too large Load Diff

302
gofaster/app/dist/renderer/src_renderer_components_SpeedTest_vue.js vendored

@ -0,0 +1,302 @@ @@ -0,0 +1,302 @@
(globalThis["webpackChunkGoFaster"] = globalThis["webpackChunkGoFaster"] || []).push([["src_renderer_components_SpeedTest_vue"],{
/***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css":
/*!******************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css ***!
\******************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ "./node_modules/css-loader/dist/runtime/noSourceMaps.js");
/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
// Imports
var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
// Module
___CSS_LOADER_EXPORT___.push([module.id, `
.speed-test[data-v-373f173a] {
padding: 20px;
max-width: 600px;
margin: 0 auto;
text-align: center;
}
button[data-v-373f173a] {
padding: 10px 20px;
font-size: 16px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 20px 0;
}
button[data-v-373f173a]:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.results[data-v-373f173a] {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
text-align: left;
}
.result-item[data-v-373f173a] {
margin: 10px 0;
display: flex;
justify-content: space-between;
}
.label[data-v-373f173a] {
font-weight: bold;
}
.value[data-v-373f173a] {
color: #42b983;
}
`, ""]);
// Exports
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
/***/ }),
/***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=script&lang=js":
/*!**********************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=script&lang=js ***!
\**********************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js");
/* harmony import */ var vuex__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vuex */ "./node_modules/vuex/dist/vuex.esm-bundler.js");
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({
name: 'SpeedTest',
setup() {
const store = (0,vuex__WEBPACK_IMPORTED_MODULE_1__.useStore)()
const testing = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false)
const result = (0,vue__WEBPACK_IMPORTED_MODULE_0__.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 }
}
});
/***/ }),
/***/ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true":
/*!**************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true ***!
\**************************************************************************************************************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ render: () => (/* binding */ render)
/* harmony export */ });
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js");
const _hoisted_1 = { class: "speed-test" }
const _hoisted_2 = ["disabled"]
const _hoisted_3 = {
key: 0,
class: "results"
}
const _hoisted_4 = { class: "result-item" }
const _hoisted_5 = { class: "value" }
const _hoisted_6 = { class: "result-item" }
const _hoisted_7 = { class: "value" }
const _hoisted_8 = { class: "result-item" }
const _hoisted_9 = { class: "value" }
function render(_ctx, _cache, $props, $setup, $data, $options) {
return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_1, [
_cache[5] || (_cache[5] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h1", null, "GoFaster Speed Test", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", {
onClick: _cache[0] || (_cache[0] = (...args) => ($setup.startTest && $setup.startTest(...args))),
disabled: $setup.testing
}, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.testing ? 'Testing...' : 'Start Test'), 9 /* TEXT, PROPS */, _hoisted_2),
($setup.result)
? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_3, [
_cache[4] || (_cache[4] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "Test Results:", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, [
_cache[1] || (_cache[1] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "label" }, "Download:", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_5, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.result.download) + " Mbps", 1 /* TEXT */)
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_6, [
_cache[2] || (_cache[2] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "label" }, "Upload:", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_7, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.result.upload) + " Mbps", 1 /* TEXT */)
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, [
_cache[3] || (_cache[3] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "label" }, "Ping:", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_9, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.result.ping) + " ms", 1 /* TEXT */)
])
]))
: (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true)
]))
}
/***/ }),
/***/ "./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css":
/*!************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css ***!
\************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
var content = __webpack_require__(/*! !!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css */ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css");
if(content.__esModule) content = content.default;
if(typeof content === 'string') content = [[module.id, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
var add = (__webpack_require__(/*! !../../../node_modules/vue-style-loader/lib/addStylesClient.js */ "./node_modules/vue-style-loader/lib/addStylesClient.js")["default"])
var update = add("220dc05d", content, false, {"sourceMap":false,"shadowMode":false});
// Hot Module Replacement
if(true) {
// When the styles change, update the <style> tags
if(!content.locals) {
module.hot.accept(/*! !!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css */ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css", function() {
var newContent = __webpack_require__(/*! !!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css */ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css");
if(newContent.__esModule) newContent = newContent.default;
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
}
// When the module is disposed, remove the <style> tags
module.hot.dispose(function() { update(); });
}
/***/ }),
/***/ "./src/renderer/components/SpeedTest.vue":
/*!***********************************************!*\
!*** ./src/renderer/components/SpeedTest.vue ***!
\***********************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _SpeedTest_vue_vue_type_template_id_373f173a_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./SpeedTest.vue?vue&type=template&id=373f173a&scoped=true */ "./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true");
/* harmony import */ var _SpeedTest_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SpeedTest.vue?vue&type=script&lang=js */ "./src/renderer/components/SpeedTest.vue?vue&type=script&lang=js");
/* harmony import */ var _SpeedTest_vue_vue_type_style_index_0_id_373f173a_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css */ "./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css");
/* harmony import */ var _node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../node_modules/vue-loader/dist/exportHelper.js */ "./node_modules/vue-loader/dist/exportHelper.js");
;
const __exports__ = /*#__PURE__*/(0,_node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_3__["default"])(_SpeedTest_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__["default"], [['render',_SpeedTest_vue_vue_type_template_id_373f173a_scoped_true__WEBPACK_IMPORTED_MODULE_0__.render],['__scopeId',"data-v-373f173a"],['__file',"src/renderer/components/SpeedTest.vue"]])
/* hot reload */
if (true) {
__exports__.__hmrId = "373f173a"
const api = __VUE_HMR_RUNTIME__
module.hot.accept()
if (!api.createRecord('373f173a', __exports__)) {
api.reload('373f173a', __exports__)
}
module.hot.accept(/*! ./SpeedTest.vue?vue&type=template&id=373f173a&scoped=true */ "./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true", __WEBPACK_OUTDATED_DEPENDENCIES__ => { /* harmony import */ _SpeedTest_vue_vue_type_template_id_373f173a_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./SpeedTest.vue?vue&type=template&id=373f173a&scoped=true */ "./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true");
return (() => {
api.rerender('373f173a', _SpeedTest_vue_vue_type_template_id_373f173a_scoped_true__WEBPACK_IMPORTED_MODULE_0__.render)
})(__WEBPACK_OUTDATED_DEPENDENCIES__); })
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (__exports__);
/***/ }),
/***/ "./src/renderer/components/SpeedTest.vue?vue&type=script&lang=js":
/*!***********************************************************************!*\
!*** ./src/renderer/components/SpeedTest.vue?vue&type=script&lang=js ***!
\***********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* reexport safe */ _node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__["default"])
/* harmony export */ });
/* harmony import */ var _node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./SpeedTest.vue?vue&type=script&lang=js */ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=script&lang=js");
/***/ }),
/***/ "./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css":
/*!*******************************************************************************************************!*\
!*** ./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css ***!
\*******************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_style_index_0_id_373f173a_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css */ "./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=style&index=0&id=373f173a&scoped=true&lang=css");
/* harmony import */ var _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_style_index_0_id_373f173a_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_style_index_0_id_373f173a_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__);
/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};
/* harmony reexport (unknown) */ for(const __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_style_index_0_id_373f173a_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__) if(__WEBPACK_IMPORT_KEY__ !== "default") __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () => _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_style_index_0_id_373f173a_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__[__WEBPACK_IMPORT_KEY__]
/* harmony reexport (unknown) */ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
/***/ }),
/***/ "./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true":
/*!*****************************************************************************************!*\
!*** ./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true ***!
\*****************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ render: () => (/* reexport safe */ _node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_template_id_373f173a_scoped_true__WEBPACK_IMPORTED_MODULE_0__.render)
/* harmony export */ });
/* harmony import */ var _node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_SpeedTest_vue_vue_type_template_id_373f173a_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./SpeedTest.vue?vue&type=template&id=373f173a&scoped=true */ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/SpeedTest.vue?vue&type=template&id=373f173a&scoped=true");
/***/ })
}]);

670
gofaster/app/dist/renderer/src_renderer_views_Settings_vue.js vendored

@ -0,0 +1,670 @@ @@ -0,0 +1,670 @@
(globalThis["webpackChunkGoFaster"] = globalThis["webpackChunkGoFaster"] || []).push([["src_renderer_views_Settings_vue"],{
/***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css":
/*!************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css ***!
\************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/noSourceMaps.js */ "./node_modules/css-loader/dist/runtime/noSourceMaps.js");
/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/api.js */ "./node_modules/css-loader/dist/runtime/api.js");
/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);
// Imports
var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));
// Module
___CSS_LOADER_EXPORT___.push([module.id, `
.settings[data-v-a5c10072] {
padding: 20px;
height: 100%;
overflow-y: auto;
}
.page-header[data-v-a5c10072] {
margin-bottom: 30px;
}
.page-header h2[data-v-a5c10072] {
margin: 0;
color: #333;
font-size: 24px;
}
.settings-content[data-v-a5c10072] {
max-width: 800px;
}
.settings-section[data-v-a5c10072] {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.settings-section h3[data-v-a5c10072] {
margin: 0 0 20px 0;
color: #333;
font-size: 18px;
border-bottom: 2px solid #e0e0e0;
padding-bottom: 8px;
}
.setting-item[data-v-a5c10072] {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding: 16px 0;
border-bottom: 1px solid #f5f5f5;
}
.setting-item[data-v-a5c10072]:last-child {
border-bottom: none;
margin-bottom: 0;
}
.setting-item label[data-v-a5c10072] {
font-weight: 500;
color: #333;
min-width: 200px;
}
.setting-item input[type="text"][data-v-a5c10072],
.setting-item input[type="number"][data-v-a5c10072],
.setting-item select[data-v-a5c10072] {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
min-width: 200px;
}
.setting-item input[type="text"][data-v-a5c10072]:focus,
.setting-item input[type="number"][data-v-a5c10072]:focus,
.setting-item select[data-v-a5c10072]:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
/* 开关样式 */
.toggle-switch[data-v-a5c10072] {
position: relative;
display: inline-block;
}
.toggle-switch input[type="checkbox"][data-v-a5c10072] {
opacity: 0;
width: 0;
height: 0;
}
.toggle-label[data-v-a5c10072] {
display: block;
width: 50px;
height: 24px;
background: #ccc;
border-radius: 12px;
cursor: pointer;
position: relative;
transition: background-color 0.3s;
}
.toggle-label[data-v-a5c10072]:before {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
top: 2px;
left: 2px;
transition: transform 0.3s;
}
.toggle-switch input[type="checkbox"]:checked + .toggle-label[data-v-a5c10072] {
background: #1976d2;
}
.toggle-switch input[type="checkbox"]:checked + .toggle-label[data-v-a5c10072]:before {
transform: translateX(26px);
}
/* 操作按钮 */
.settings-actions[data-v-a5c10072] {
display: flex;
gap: 16px;
justify-content: flex-end;
margin-top: 30px;
}
.btn[data-v-a5c10072] {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.btn-primary[data-v-a5c10072] {
background: #1976d2;
color: white;
}
.btn-primary[data-v-a5c10072]:hover {
background: #1565c0;
}
.btn-secondary[data-v-a5c10072] {
background: #757575;
color: white;
}
.btn-secondary[data-v-a5c10072]:hover {
background: #616161;
}
/* 响应式设计 */
@media (max-width: 768px) {
.setting-item[data-v-a5c10072] {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.setting-item label[data-v-a5c10072] {
min-width: auto;
}
.setting-item input[type="text"][data-v-a5c10072],
.setting-item input[type="number"][data-v-a5c10072],
.setting-item select[data-v-a5c10072] {
min-width: 100%;
}
.settings-actions[data-v-a5c10072] {
flex-direction: column;
}
}
`, ""]);
// Exports
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);
/***/ }),
/***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=script&lang=js":
/*!****************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=script&lang=js ***!
\****************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js");
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({
name: 'Settings',
setup() {
const settings = (0,vue__WEBPACK_IMPORTED_MODULE_0__.reactive)({
// 基本设置
appName: 'GoFaster',
language: 'zh-CN',
theme: 'light',
// 网络设置
apiUrl: 'http://localhost:8080',
timeout: 10,
retryCount: 3,
// 用户设置
autoLogin: false,
rememberPassword: false,
sessionTimeout: 30,
// 通知设置
desktopNotifications: true,
soundNotifications: true,
notificationInterval: 10,
// 数据设置
cacheSize: 100,
autoCleanCache: true,
backupFrequency: 'weekly'
})
const loadSettings = () => {
const savedSettings = localStorage.getItem('gofaster-settings')
if (savedSettings) {
Object.assign(settings, JSON.parse(savedSettings))
}
}
const saveSettings = () => {
try {
localStorage.setItem('gofaster-settings', JSON.stringify(settings))
// 这里可以添加保存成功的提示
console.log('设置已保存')
} catch (error) {
console.error('保存设置失败:', error)
}
}
const resetSettings = () => {
if (confirm('确定要重置所有设置吗?此操作不可撤销。')) {
localStorage.removeItem('gofaster-settings')
location.reload()
}
}
;(0,vue__WEBPACK_IMPORTED_MODULE_0__.onMounted)(() => {
loadSettings()
})
return {
settings,
saveSettings,
resetSettings
}
}
});
/***/ }),
/***/ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true":
/*!********************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true ***!
\********************************************************************************************************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ render: () => (/* binding */ render)
/* harmony export */ });
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js");
const _hoisted_1 = { class: "settings" }
const _hoisted_2 = { class: "settings-content" }
const _hoisted_3 = { class: "settings-section" }
const _hoisted_4 = { class: "setting-item" }
const _hoisted_5 = { class: "setting-item" }
const _hoisted_6 = { class: "setting-item" }
const _hoisted_7 = { class: "settings-section" }
const _hoisted_8 = { class: "setting-item" }
const _hoisted_9 = { class: "setting-item" }
const _hoisted_10 = { class: "setting-item" }
const _hoisted_11 = { class: "settings-section" }
const _hoisted_12 = { class: "setting-item" }
const _hoisted_13 = { class: "toggle-switch" }
const _hoisted_14 = { class: "setting-item" }
const _hoisted_15 = { class: "toggle-switch" }
const _hoisted_16 = { class: "setting-item" }
const _hoisted_17 = { class: "settings-section" }
const _hoisted_18 = { class: "setting-item" }
const _hoisted_19 = { class: "toggle-switch" }
const _hoisted_20 = { class: "setting-item" }
const _hoisted_21 = { class: "toggle-switch" }
const _hoisted_22 = { class: "setting-item" }
const _hoisted_23 = { class: "settings-section" }
const _hoisted_24 = { class: "setting-item" }
const _hoisted_25 = { class: "setting-item" }
const _hoisted_26 = { class: "toggle-switch" }
const _hoisted_27 = { class: "setting-item" }
const _hoisted_28 = { class: "settings-actions" }
function render(_ctx, _cache, $props, $setup, $data, $options) {
return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_1, [
_cache[45] || (_cache[45] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "page-header" }, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h2", null, "系统设置")
], -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 基本设置 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, [
_cache[22] || (_cache[22] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "基本设置", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, [
_cache[17] || (_cache[17] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "应用名称", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($setup.settings.appName) = $event)),
type: "text",
placeholder: "GoFaster"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.appName]
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_5, [
_cache[19] || (_cache[19] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "语言", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("select", {
"onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (($setup.settings.language) = $event))
}, _cache[18] || (_cache[18] = [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "zh-CN" }, "简体中文", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "en-US" }, "English", -1 /* CACHED */)
]), 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelSelect, $setup.settings.language]
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_6, [
_cache[21] || (_cache[21] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "主题", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("select", {
"onUpdate:modelValue": _cache[2] || (_cache[2] = $event => (($setup.settings.theme) = $event))
}, _cache[20] || (_cache[20] = [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "light" }, "浅色主题", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "dark" }, "深色主题", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "auto" }, "跟随系统", -1 /* CACHED */)
]), 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelSelect, $setup.settings.theme]
])
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 网络设置 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [
_cache[26] || (_cache[26] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "网络设置", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, [
_cache[23] || (_cache[23] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "API服务器地址", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[3] || (_cache[3] = $event => (($setup.settings.apiUrl) = $event)),
type: "text",
placeholder: "http://localhost:8080"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.apiUrl]
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, [
_cache[24] || (_cache[24] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "请求超时时间 (秒)", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[4] || (_cache[4] = $event => (($setup.settings.timeout) = $event)),
type: "number",
min: "5",
max: "60"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.timeout]
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_10, [
_cache[25] || (_cache[25] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "自动重试次数", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($setup.settings.retryCount) = $event)),
type: "number",
min: "0",
max: "5"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.retryCount]
])
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 用户设置 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_11, [
_cache[32] || (_cache[32] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "用户设置", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_12, [
_cache[28] || (_cache[28] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "自动登录", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_13, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[6] || (_cache[6] = $event => (($setup.settings.autoLogin) = $event)),
type: "checkbox",
id: "autoLogin"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelCheckbox, $setup.settings.autoLogin]
]),
_cache[27] || (_cache[27] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", {
for: "autoLogin",
class: "toggle-label"
}, null, -1 /* CACHED */))
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_14, [
_cache[30] || (_cache[30] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "记住密码", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_15, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[7] || (_cache[7] = $event => (($setup.settings.rememberPassword) = $event)),
type: "checkbox",
id: "rememberPassword"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelCheckbox, $setup.settings.rememberPassword]
]),
_cache[29] || (_cache[29] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", {
for: "rememberPassword",
class: "toggle-label"
}, null, -1 /* CACHED */))
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, [
_cache[31] || (_cache[31] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "会话超时时间 (分钟)", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[8] || (_cache[8] = $event => (($setup.settings.sessionTimeout) = $event)),
type: "number",
min: "15",
max: "1440"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.sessionTimeout]
])
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 通知设置 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_17, [
_cache[38] || (_cache[38] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "通知设置", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, [
_cache[34] || (_cache[34] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "桌面通知", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_19, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[9] || (_cache[9] = $event => (($setup.settings.desktopNotifications) = $event)),
type: "checkbox",
id: "desktopNotifications"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelCheckbox, $setup.settings.desktopNotifications]
]),
_cache[33] || (_cache[33] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", {
for: "desktopNotifications",
class: "toggle-label"
}, null, -1 /* CACHED */))
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_20, [
_cache[36] || (_cache[36] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "声音提醒", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_21, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[10] || (_cache[10] = $event => (($setup.settings.soundNotifications) = $event)),
type: "checkbox",
id: "soundNotifications"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelCheckbox, $setup.settings.soundNotifications]
]),
_cache[35] || (_cache[35] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", {
for: "soundNotifications",
class: "toggle-label"
}, null, -1 /* CACHED */))
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_22, [
_cache[37] || (_cache[37] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "消息提醒间隔 (秒)", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[11] || (_cache[11] = $event => (($setup.settings.notificationInterval) = $event)),
type: "number",
min: "5",
max: "300"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.notificationInterval]
])
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 数据设置 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_23, [
_cache[44] || (_cache[44] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "数据设置", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_24, [
_cache[39] || (_cache[39] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "数据缓存大小 (MB)", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[12] || (_cache[12] = $event => (($setup.settings.cacheSize) = $event)),
type: "number",
min: "50",
max: "1000"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.settings.cacheSize]
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_25, [
_cache[41] || (_cache[41] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "自动清理缓存", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_26, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", {
"onUpdate:modelValue": _cache[13] || (_cache[13] = $event => (($setup.settings.autoCleanCache) = $event)),
type: "checkbox",
id: "autoCleanCache"
}, null, 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelCheckbox, $setup.settings.autoCleanCache]
]),
_cache[40] || (_cache[40] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", {
for: "autoCleanCache",
class: "toggle-label"
}, null, -1 /* CACHED */))
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_27, [
_cache[43] || (_cache[43] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "数据备份频率", -1 /* CACHED */)),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("select", {
"onUpdate:modelValue": _cache[14] || (_cache[14] = $event => (($setup.settings.backupFrequency) = $event))
}, _cache[42] || (_cache[42] = [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "daily" }, "每日", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "weekly" }, "每周", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "monthly" }, "每月", -1 /* CACHED */),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "never" }, "从不", -1 /* CACHED */)
]), 512 /* NEED_PATCH */), [
[vue__WEBPACK_IMPORTED_MODULE_0__.vModelSelect, $setup.settings.backupFrequency]
])
])
]),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 操作按钮 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_28, [
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", {
class: "btn btn-secondary",
onClick: _cache[15] || (_cache[15] = (...args) => ($setup.resetSettings && $setup.resetSettings(...args)))
}, " 重置设置 "),
(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", {
class: "btn btn-primary",
onClick: _cache[16] || (_cache[16] = (...args) => ($setup.saveSettings && $setup.saveSettings(...args)))
}, " 保存设置 ")
])
])
]))
}
/***/ }),
/***/ "./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css":
/*!******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css ***!
\******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
// style-loader: Adds some css to the DOM by adding a <style> tag
// load the styles
var content = __webpack_require__(/*! !!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css */ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css");
if(content.__esModule) content = content.default;
if(typeof content === 'string') content = [[module.id, content, '']];
if(content.locals) module.exports = content.locals;
// add the styles to the DOM
var add = (__webpack_require__(/*! !../../../node_modules/vue-style-loader/lib/addStylesClient.js */ "./node_modules/vue-style-loader/lib/addStylesClient.js")["default"])
var update = add("57cdd173", content, false, {"sourceMap":false,"shadowMode":false});
// Hot Module Replacement
if(true) {
// When the styles change, update the <style> tags
if(!content.locals) {
module.hot.accept(/*! !!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css */ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css", function() {
var newContent = __webpack_require__(/*! !!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css */ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css");
if(newContent.__esModule) newContent = newContent.default;
if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
update(newContent);
});
}
// When the module is disposed, remove the <style> tags
module.hot.dispose(function() { update(); });
}
/***/ }),
/***/ "./src/renderer/views/Settings.vue":
/*!*****************************************!*\
!*** ./src/renderer/views/Settings.vue ***!
\*****************************************/
/***/ ((module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Settings_vue_vue_type_template_id_a5c10072_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Settings.vue?vue&type=template&id=a5c10072&scoped=true */ "./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true");
/* harmony import */ var _Settings_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Settings.vue?vue&type=script&lang=js */ "./src/renderer/views/Settings.vue?vue&type=script&lang=js");
/* harmony import */ var _Settings_vue_vue_type_style_index_0_id_a5c10072_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css */ "./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css");
/* harmony import */ var _node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../node_modules/vue-loader/dist/exportHelper.js */ "./node_modules/vue-loader/dist/exportHelper.js");
;
const __exports__ = /*#__PURE__*/(0,_node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_3__["default"])(_Settings_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__["default"], [['render',_Settings_vue_vue_type_template_id_a5c10072_scoped_true__WEBPACK_IMPORTED_MODULE_0__.render],['__scopeId',"data-v-a5c10072"],['__file',"src/renderer/views/Settings.vue"]])
/* hot reload */
if (true) {
__exports__.__hmrId = "a5c10072"
const api = __VUE_HMR_RUNTIME__
module.hot.accept()
if (!api.createRecord('a5c10072', __exports__)) {
api.reload('a5c10072', __exports__)
}
module.hot.accept(/*! ./Settings.vue?vue&type=template&id=a5c10072&scoped=true */ "./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true", __WEBPACK_OUTDATED_DEPENDENCIES__ => { /* harmony import */ _Settings_vue_vue_type_template_id_a5c10072_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Settings.vue?vue&type=template&id=a5c10072&scoped=true */ "./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true");
return (() => {
api.rerender('a5c10072', _Settings_vue_vue_type_template_id_a5c10072_scoped_true__WEBPACK_IMPORTED_MODULE_0__.render)
})(__WEBPACK_OUTDATED_DEPENDENCIES__); })
}
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (__exports__);
/***/ }),
/***/ "./src/renderer/views/Settings.vue?vue&type=script&lang=js":
/*!*****************************************************************!*\
!*** ./src/renderer/views/Settings.vue?vue&type=script&lang=js ***!
\*****************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* reexport safe */ _node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__["default"])
/* harmony export */ });
/* harmony import */ var _node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Settings.vue?vue&type=script&lang=js */ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=script&lang=js");
/***/ }),
/***/ "./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css":
/*!*************************************************************************************************!*\
!*** ./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css ***!
\*************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_style_index_0_id_a5c10072_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!../../../node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!../../../node_modules/vue-loader/dist/stylePostLoader.js!../../../node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css */ "./node_modules/vue-style-loader/index.js??clonedRuleSet-12.use[0]!./node_modules/css-loader/dist/cjs.js??clonedRuleSet-12.use[1]!./node_modules/vue-loader/dist/stylePostLoader.js!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-12.use[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=style&index=0&id=a5c10072&scoped=true&lang=css");
/* harmony import */ var _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_style_index_0_id_a5c10072_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_style_index_0_id_a5c10072_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__);
/* harmony reexport (unknown) */ var __WEBPACK_REEXPORT_OBJECT__ = {};
/* harmony reexport (unknown) */ for(const __WEBPACK_IMPORT_KEY__ in _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_style_index_0_id_a5c10072_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__) if(__WEBPACK_IMPORT_KEY__ !== "default") __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = () => _node_modules_vue_style_loader_index_js_clonedRuleSet_12_use_0_node_modules_css_loader_dist_cjs_js_clonedRuleSet_12_use_1_node_modules_vue_loader_dist_stylePostLoader_js_node_modules_postcss_loader_dist_cjs_js_clonedRuleSet_12_use_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_style_index_0_id_a5c10072_scoped_true_lang_css__WEBPACK_IMPORTED_MODULE_0__[__WEBPACK_IMPORT_KEY__]
/* harmony reexport (unknown) */ __webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
/***/ }),
/***/ "./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true":
/*!***********************************************************************************!*\
!*** ./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true ***!
\***********************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ render: () => (/* reexport safe */ _node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_template_id_a5c10072_scoped_true__WEBPACK_IMPORTED_MODULE_0__.render)
/* harmony export */ });
/* harmony import */ var _node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_2_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_Settings_vue_vue_type_template_id_a5c10072_scoped_true__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!../../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./Settings.vue?vue&type=template&id=a5c10072&scoped=true */ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/Settings.vue?vue&type=template&id=a5c10072&scoped=true");
/***/ })
}]);

30
gofaster/app/package-lock.json generated

@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"axios": "^1.11.0",
"lowdb": "^7.0.1",
"vue-router": "^4.5.1",
"vuex": "^4.0.2"
@ -2482,8 +2483,7 @@ @@ -2482,8 +2483,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/at-least-node": {
"version": "1.0.0",
@ -2533,9 +2533,8 @@ @@ -2533,9 +2533,8 @@
},
"node_modules/axios": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@ -3041,7 +3040,6 @@ @@ -3041,7 +3040,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
@ -3407,7 +3405,6 @@ @@ -3407,7 +3405,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@ -4358,7 +4355,6 @@ @@ -4358,7 +4355,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@ -4614,7 +4610,6 @@ @@ -4614,7 +4610,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
@ -5051,7 +5046,6 @@ @@ -5051,7 +5046,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@ -5060,7 +5054,6 @@ @@ -5060,7 +5054,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@ -5075,7 +5068,6 @@ @@ -5075,7 +5068,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0"
},
@ -5087,7 +5079,6 @@ @@ -5087,7 +5079,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
@ -5551,7 +5542,6 @@ @@ -5551,7 +5542,6 @@
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"dev": true,
"funding": [
{
"type": "individual",
@ -5658,7 +5648,6 @@ @@ -5658,7 +5648,6 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@ -5758,7 +5747,6 @@ @@ -5758,7 +5747,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@ -5776,7 +5764,6 @@ @@ -5776,7 +5764,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
@ -5800,7 +5787,6 @@ @@ -5800,7 +5787,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
@ -5942,7 +5928,6 @@ @@ -5942,7 +5928,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@ -6028,7 +6013,6 @@ @@ -6028,7 +6013,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
@ -6040,7 +6024,6 @@ @@ -6040,7 +6024,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"dependencies": {
"has-symbols": "^1.0.3"
},
@ -6061,7 +6044,6 @@ @@ -6061,7 +6044,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
@ -7321,7 +7303,6 @@ @@ -7321,7 +7303,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"engines": {
"node": ">= 0.4"
}
@ -7424,7 +7405,6 @@ @@ -7424,7 +7405,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@ -7433,7 +7413,6 @@ @@ -7433,7 +7413,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@ -9208,8 +9187,7 @@ @@ -9208,8 +9187,7 @@
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/pseudomap": {
"version": "1.0.2",

7
gofaster/app/package.json

@ -6,7 +6,11 @@ @@ -6,7 +6,11 @@
"dev": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build --mode development && electron .",
"dev:watch": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && concurrently \"vue-cli-service build --mode development --watch\" \"wait-on dist/renderer/index.html && electron .\"",
"build": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build && electron-builder",
"electron:serve": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build --mode development && electron ."
"electron:serve": "set VUE_CLI_BABEL_TRANSPILE_MODULES=false && set VUE_CLI_MODERN_BUILD=false && vue-cli-service build --mode development && electron .",
"build:vue": "vue-cli-service build --mode development",
"dev:ps": "powershell -ExecutionPolicy Bypass -File dev.ps1",
"dev:bat": "dev.bat",
"dev:stable": "dev-stable.bat"
},
"keywords": [],
"author": "",
@ -25,6 +29,7 @@ @@ -25,6 +29,7 @@
"wait-on": "^8.0.4"
},
"dependencies": {
"axios": "^1.11.0",
"lowdb": "^7.0.1",
"vue-router": "^4.5.1",
"vuex": "^4.0.2"

184
gofaster/app/src/main/index.js

@ -2,6 +2,10 @@ const { app, BrowserWindow, ipcMain } = require('electron') @@ -2,6 +2,10 @@ const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
// 设置进程编码,确保中文正常显示
process.env.LANG = 'zh_CN.UTF-8'
process.env.LC_ALL = 'zh_CN.UTF-8'
// 获取项目根目录绝对路径
const appRoot = path.resolve(__dirname, '../..')
console.log('Application root:', appRoot)
@ -10,7 +14,7 @@ let mainWindow @@ -10,7 +14,7 @@ let mainWindow
// 检查是否为开发环境
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged
console.log('开发环境:', isDev)
console.log('Development environment:', isDev)
// 开发环境热重载 - 只在非watch模式下启用
if (isDev && !process.argv.includes('--watch')) {
@ -18,25 +22,45 @@ if (isDev && !process.argv.includes('--watch')) { @@ -18,25 +22,45 @@ if (isDev && !process.argv.includes('--watch')) {
// 修复热重载配置
require('electron-reload')(appRoot, {
electron: path.join(__dirname, '..', '..', 'node_modules', 'electron', 'dist', 'electron.exe'),
hardResetMethod: 'exit'
hardResetMethod: 'exit',
// 添加更稳定的配置
forceHardReset: true,
ignored: [
/node_modules|[\/\\]\./,
/dist|[\/\\]\./,
/\.git|[\/\\]\./
]
});
console.log('热重载已启用');
console.log('Hot reload enabled');
} catch (error) {
console.log('热重载启用失败:', error.message);
console.log('Hot reload failed:', error.message);
}
}
// 监听文件变化(用于watch模式)
if (isDev && process.argv.includes('--watch')) {
console.log('Watch模式已启用,监听文件变化...');
console.log('Watch mode enabled, monitoring file changes...');
// 监听dist目录变化
const distPath = path.join(appRoot, 'dist/renderer');
if (fs.existsSync(distPath)) {
let reloadTimeout = null;
fs.watch(distPath, { recursive: true }, (eventType, filename) => {
if (filename && mainWindow && !mainWindow.isDestroyed()) {
console.log(`文件变化: ${filename}, 重新加载页面...`);
// 添加防抖机制,避免频繁重载
if (reloadTimeout) {
clearTimeout(reloadTimeout);
}
reloadTimeout = setTimeout(() => {
try {
console.log(`File changed: ${filename}, reloading page...`);
mainWindow.reload();
} catch (error) {
console.log('Page reload failed:', error.message);
}
}, 500); // 500ms防抖延迟
}
});
}
@ -65,16 +89,82 @@ function createWindow() { @@ -65,16 +89,82 @@ function createWindow() {
// 禁用eval相关功能
enableBlinkFeatures: '',
disableBlinkFeatures: 'Auxclick'
}
},
// 添加字体配置,确保中文正常显示
titleBarStyle: 'default',
show: false // 先隐藏窗口,等加载完成后再显示
})
console.log('主窗口已创建');
console.log('Main window created');
// 开发环境下抑制安全警告
if (isDev) {
mainWindow.webContents.on('did-frame-finish-load', () => {
// 移除有问题的JavaScript执行,避免IPC通信错误
console.log('页面加载完成,开发者工具已准备就绪');
console.log('Page loaded, DevTools ready');
// 简化JavaScript注入,避免序列化错误
mainWindow.webContents.executeJavaScript(`
// 重写console.warn和console.error来抑制特定警告
const originalWarn = console.warn;
const originalError = console.error;
console.warn = function(...args) {
const message = args.join(' ');
if (message.includes('Electron Security Warning') ||
message.includes('Insecure Content-Security-Policy') ||
message.includes('unsafe-eval')) {
return; // 抑制这些警告
}
originalWarn.apply(console, args);
};
console.error = function(...args) {
const message = args.join(' ');
if (message.includes('Electron Security Warning') ||
message.includes('Insecure Content-Security-Policy') ||
message.includes('unsafe-eval')) {
return; // 抑制这些警告
}
originalError.apply(console, args);
};
console.log('Console warning suppression enabled');
`).catch(err => {
console.log('JavaScript injection failed:', err.message);
// 注入失败不影响应用运行
});
});
// 添加页面加载错误处理
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
console.log('Page load failed:', errorDescription);
// 可以在这里添加重试逻辑
});
// 添加渲染进程崩溃处理
mainWindow.webContents.on('render-process-gone', (event, details) => {
console.log('Render process gone:', details.reason);
// 可以在这里添加恢复逻辑
});
// 抑制Electron安全警告
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
// 抑制Autofill相关警告
if (message.includes('Autofill.enable failed') ||
message.includes('Autofill.setAddresses failed') ||
message.includes('Request Autofill')) {
event.preventDefault();
return false;
}
// 抑制Electron安全警告
if (message.includes('Electron Security Warning') ||
message.includes('Insecure Content-Security-Policy') ||
message.includes('unsafe-eval')) {
event.preventDefault();
return false;
}
});
}
@ -83,12 +173,12 @@ function createWindow() { @@ -83,12 +173,12 @@ function createWindow() {
// 检查文件是否存在
if (!fs.existsSync(loadPath)) {
console.log('文件不存在,等待构建完成...');
console.log('File not found, waiting for build completion...');
// 在watch模式下等待文件构建完成
if (isDev && process.argv.includes('--watch')) {
const checkFile = () => {
if (fs.existsSync(loadPath)) {
console.log('文件已构建完成,开始加载...');
console.log('Build completed, starting to load...');
loadMainWindow();
} else {
setTimeout(checkFile, 1000); // 每秒检查一次
@ -98,8 +188,8 @@ function createWindow() { @@ -98,8 +188,8 @@ function createWindow() {
return; // 重要:如果文件不存在,直接返回,不执行后续代码
} else {
// 非watch模式,直接显示错误
console.error('文件不存在:', loadPath);
mainWindow.loadURL(`data:text/html,<h1>构建文件不存在,请先运行 npm run dev</h1>`);
console.error('File not found:', loadPath);
mainWindow.loadURL(`data:text/html,<h1>Build file not found, please run npm run dev first</h1>`);
return;
}
}
@ -109,14 +199,18 @@ function createWindow() { @@ -109,14 +199,18 @@ function createWindow() {
function loadMainWindow() {
mainWindow.loadFile(loadPath).then(() => {
console.log('页面加载成功');
console.log('Page loaded successfully');
// 开发环境下打开开发者工具
if (isDev) {
mainWindow.webContents.openDevTools()
}
// 页面加载完成后显示窗口
mainWindow.show()
}).catch(err => {
console.error('加载失败:', err)
mainWindow.loadURL(`data:text/html,<h1>加载失败: ${err.toString()}</h1>`)
console.error('Load failed:', err)
mainWindow.loadURL(`data:text/html,<h1>Load failed: ${err.toString()}</h1>`)
// 即使加载失败也要显示窗口
mainWindow.show()
})
}
@ -126,12 +220,12 @@ function createWindow() { @@ -126,12 +220,12 @@ function createWindow() {
})
} catch (error) {
console.error('创建窗口失败:', error)
console.error('Failed to create window:', error)
}
}
app.whenReady().then(() => {
console.log('Electron应用已准备就绪');
console.log('Electron app ready');
createWindow()
// 修复IPC通信问题
@ -143,11 +237,57 @@ app.whenReady().then(() => { @@ -143,11 +237,57 @@ app.whenReady().then(() => {
}
event.sender.send('status-update', statusData)
} catch (error) {
console.error('IPC通信错误:', error);
console.error('IPC communication error:', error);
}
})
// 添加获取进程内存信息的API
ipcMain.handle('get-process-memory-info', async (event) => {
try {
// 使用 process.memoryUsage 获取内存信息
const memoryUsage = process.memoryUsage();
// 获取更详细的系统内存信息
let systemMemoryInfo = {};
try {
// 尝试使用 require('os') 获取系统内存信息
const os = require('os');
systemMemoryInfo = {
totalMemory: os.totalmem(),
freeMemory: os.freemem(),
usedMemory: os.totalmem() - os.freemem()
};
} catch (osError) {
console.warn('Cannot get system memory info:', osError.message);
}
return {
// 进程内存使用情况
privateBytes: memoryUsage.rss, // Resident Set Size - 进程占用的物理内存
sharedBytes: memoryUsage.external, // 外部内存使用
heapUsed: memoryUsage.heapUsed, // JavaScript堆内存使用
heapTotal: memoryUsage.heapTotal, // JavaScript堆内存总量
// 系统内存信息
systemTotal: systemMemoryInfo.totalMemory || 0,
systemFree: systemMemoryInfo.freeMemory || 0,
systemUsed: systemMemoryInfo.usedMemory || 0
};
} catch (error) {
console.error('Failed to get memory info:', error);
// 返回默认值
return {
privateBytes: 0,
sharedBytes: 0,
heapUsed: 0,
heapTotal: 0,
systemTotal: 0,
systemFree: 0,
systemUsed: 0
};
}
});
}).catch(error => {
console.error('应用启动失败:', error)
console.error('App startup failed:', error)
})
app.on('window-all-closed', () => {
@ -160,9 +300,9 @@ app.on('activate', () => { @@ -160,9 +300,9 @@ app.on('activate', () => {
// 添加错误处理
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', error)
console.error('Uncaught exception:', error)
})
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的Promise拒绝:', reason)
console.error('Unhandled promise rejection:', reason)
})

3
gofaster/app/src/preload.js

@ -3,5 +3,6 @@ const { contextBridge, ipcRenderer } = require('electron') @@ -3,5 +3,6 @@ const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
sendStatusRequest: () => ipcRenderer.send('request-status'),
onStatusUpdate: (callback) => ipcRenderer.on('status-update', callback)
onStatusUpdate: (callback) => ipcRenderer.on('status-update', callback),
getProcessMemoryInfo: () => ipcRenderer.invoke('get-process-memory-info')
})

87
gofaster/app/src/renderer/App.vue

@ -8,6 +8,7 @@ @@ -8,6 +8,7 @@
<script>
import StatusBar from './components/StatusBar.vue';
import './assets/fonts.css'; //
export default {
name: 'App',
@ -24,7 +25,7 @@ export default { @@ -24,7 +25,7 @@ export default {
<style>
/* 全局样式 */
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
font-family: 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
@ -32,12 +33,7 @@ export default { @@ -32,12 +33,7 @@ export default {
padding: 0;
height: 100vh;
width: 100vw;
/* 添加背景图片 */
background-image: url('@/assets/background.png');
/* 假设图片放在src/assets目录下 */
background-size: cover;
background-position: center;
background-repeat: no-repeat;
overflow: hidden;
}
/* 确保html和body元素也填满窗口 */
@ -48,5 +44,82 @@ body { @@ -48,5 +44,82 @@ body {
height: 100%;
width: 100%;
overflow: hidden;
/* 添加中文字体支持 */
font-family: 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', Avenir, Helvetica, Arial, sans-serif;
}
/* 全局按钮样式 */
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
text-decoration: none;
}
.btn-primary {
background: #1976d2;
color: white;
}
.btn-primary:hover {
background: #1565c0;
}
.btn-secondary {
background: #757575;
color: white;
}
.btn-secondary:hover {
background: #616161;
}
.btn-danger {
background: #d32f2f;
color: white;
}
.btn-danger:hover {
background: #c62828;
}
.btn-info {
background: #0288d1;
color: white;
}
.btn-info:hover {
background: #0277bd;
}
/* 全局输入框样式 */
input, select, textarea {
font-family: inherit;
}
/* 全局滚动条样式 */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>

62
gofaster/app/src/renderer/assets/fonts.css

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/* 中文字体配置文件 */
/* 定义字体族变量 */
:root {
--font-family-chinese: 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', 'SimHei', 'SimSun', Avenir, Helvetica, Arial, sans-serif;
--font-family-english: 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
}
/* 全局字体设置 */
* {
font-family: var(--font-family-chinese), var(--font-family-english);
}
/* 确保所有文本元素都使用中文字体 */
body, html, #app {
font-family: var(--font-family-chinese), var(--font-family-english);
}
/* 特定组件的字体设置 */
.main-layout,
.status-bar,
.home,
.user-management,
.settings {
font-family: var(--font-family-chinese), var(--font-family-english);
}
/* 标题和重要文本的字体设置 */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-family-chinese), var(--font-family-english);
font-weight: 500;
}
/* 按钮和输入框的字体设置 */
button, input, select, textarea {
font-family: var(--font-family-chinese), var(--font-family-english);
}
/* 表格内容的字体设置 */
table, th, td {
font-family: var(--font-family-chinese), var(--font-family-english);
}
/* 状态栏文本的字体设置 */
.status-item {
font-family: var(--font-family-chinese), var(--font-family-english);
}
/* 响应式字体大小 */
@media (max-width: 768px) {
:root {
--font-size-base: 14px;
--font-size-small: 12px;
}
}
@media (min-width: 769px) {
:root {
--font-size-base: 16px;
--font-size-small: 14px;
}
}

971
gofaster/app/src/renderer/components/MainLayout.vue

@ -0,0 +1,971 @@ @@ -0,0 +1,971 @@
<template>
<div class="main-layout">
<!-- 顶部导航栏 -->
<header class="header">
<div class="header-left">
<div class="logo">
<h1>🚀 GoFaster</h1>
</div>
<div class="breadcrumb">
<span v-for="(item, index) in breadcrumbs" :key="index">
<span v-if="index > 0" class="separator">/</span>
<span class="breadcrumb-item">{{ item }}</span>
</span>
</div>
</div>
<div class="header-right">
<!-- 消息通知 -->
<div class="message-center">
<button class="message-btn" @click="toggleMessagePanel">
<i class="icon">📢</i>
<span v-if="unreadCount > 0" class="badge">{{ unreadCount }}</span>
</button>
<!-- 消息面板 -->
<div v-if="showMessagePanel" class="message-panel">
<div class="message-header">
<h3>消息</h3>
<button class="close-btn" @click="showMessagePanel = false">×</button>
</div>
<div class="message-list">
<div v-for="message in messages" :key="message.id" class="message-item">
<div class="message-icon">{{ message.icon }}</div>
<div class="message-content">
<div class="message-title">{{ message.title }}</div>
<div class="message-time">{{ formatTime(message.time) }}</div>
</div>
<button class="mark-read-btn" @click="markAsRead(message.id)">
</button>
</div>
</div>
</div>
</div>
<!-- 用户信息 -->
<div class="user-info">
<div class="user-avatar" @click="toggleUserMenu">
<img v-if="currentUser.avatar" :src="currentUser.avatar" :alt="currentUser.name" />
<span v-else class="avatar-placeholder">{{ currentUser.name?.charAt(0) || 'U' }}</span>
</div>
<!-- 用户菜单 -->
<div v-if="showUserMenu" class="user-menu">
<div class="user-menu-header">
<div class="user-details">
<div class="user-name">{{ currentUser.name || '用户' }}</div>
<div class="user-email">{{ currentUser.email || 'user@example.com' }}</div>
</div>
</div>
<div class="user-menu-items">
<button class="menu-item" @click="openProfile">
<i class="icon">👤</i> 个人资料
</button>
<button class="menu-item" @click="openSettings">
<i class="icon"></i> 设置
</button>
<button class="menu-item" @click="logout">
<i class="icon">🚪</i> 退出登录
</button>
</div>
</div>
</div>
</div>
</header>
<!-- 主要内容区域 -->
<div class="main-content">
<!-- 左侧菜单 -->
<aside class="sidebar">
<nav class="sidebar-nav">
<div class="nav-section">
<ul class="nav-list">
<li v-for="item in mainMenuItems" :key="item.id">
<router-link
:to="item.path"
class="nav-item"
:class="{ active: currentRoute === item.path }"
@click="handleMenuClick(item)"
>
<i class="nav-icon">{{ item.icon }}</i>
<span class="nav-text">{{ item.name }}</span>
<button
v-if="item.favorite"
class="favorite-btn"
@click.stop="toggleFavorite(item.id)"
>
</button>
</router-link>
</li>
</ul>
</div>
<div class="nav-section">
<ul class="nav-list">
<li v-for="item in favoriteMenuItems" :key="item.id">
<router-link
:to="item.path"
class="nav-item"
:class="{ active: currentRoute === item.path }"
>
<i class="nav-icon">{{ item.icon }}</i>
<span class="nav-text">{{ item.name }}</span>
<button
class="favorite-btn active"
@click.stop="toggleFavorite(item.id)"
>
</button>
</router-link>
</li>
</ul>
</div>
</nav>
</aside>
<!-- 右侧内容区域 -->
<div class="content-area">
<!-- 内容选项卡 -->
<div class="content-tabs">
<div class="tab-list">
<div
v-for="tab in openTabs"
:key="tab.id"
:class="['tab-item', { active: tab.id === currentTab }]"
@click="switchTab(tab.id)"
>
<span class="tab-title">{{ tab.title }}</span>
<button
v-if="openTabs.length > 1"
class="tab-close"
@click.stop="closeTab(tab.id)"
title="关闭标签页"
>
×
</button>
</div>
</div>
<div class="tab-actions">
<button
v-if="openTabs.length > 1"
class="close-all-btn"
@click="closeAllTabs"
title="关闭所有标签页"
>
<span class="close-all-icon"></span>
</button>
</div>
</div>
<!-- 功能内容区 -->
<div class="content-body">
<router-view />
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { userService } from '@/services/userService'
export default {
name: 'MainLayout',
setup() {
const router = useRouter()
const route = useRoute()
//
const showMessagePanel = ref(false)
const showUserMenu = ref(false)
const currentTab = ref('home')
const openTabs = ref([
{ id: 'home', title: '欢迎', path: '/', closable: false }
])
const currentUser = reactive({
name: '管理员',
email: 'admin@gofaster.com',
avatar: null
})
const messages = ref([
{
id: 1,
title: '系统更新完成',
icon: '🔄',
time: new Date(Date.now() - 1000 * 60 * 30),
read: false
},
{
id: 2,
title: '新用户注册',
icon: '👤',
time: new Date(Date.now() - 1000 * 60 * 60),
read: false
}
])
const mainMenuItems = ref([
{ id: 'home', name: '欢迎', path: '/', icon: '🏠', favorite: false },
{ id: 'speed-test', name: '速度测试', path: '/speed-test', icon: '⚡', favorite: false },
{ id: 'user-management', name: '用户管理', path: '/user-management', icon: '👥', favorite: false },
{ id: 'history', name: '历史记录', path: '/history', icon: '📊', favorite: false },
{ id: 'settings', name: '系统设置', path: '/settings', icon: '⚙', favorite: false }
])
const favoriteMenuItems = ref([])
//
const unreadCount = computed(() => messages.value.filter(m => !m.read).length)
const currentRoute = computed(() => route.path)
const breadcrumbs = computed(() => {
const path = route.path
if (path === '/') return ['欢迎']
if (path === '/user-management') return ['欢迎', '用户管理']
if (path === '/speed-test') return ['欢迎', '速度测试']
if (path === '/history') return ['欢迎', '历史记录']
if (path === '/settings') return ['欢迎', '系统设置']
return ['欢迎']
})
//
const toggleMessagePanel = () => {
showMessagePanel.value = !showMessagePanel.value
showUserMenu.value = false
}
const toggleUserMenu = () => {
showUserMenu.value = !showUserMenu.value
showMessagePanel.value = false
}
const handleMenuClick = (item) => {
//
const existingTab = openTabs.value.find(tab => tab.id === item.id)
if (!existingTab) {
openTabs.value.push({
id: item.id,
title: item.name,
path: item.path,
closable: true
})
}
currentTab.value = item.id
}
const switchTab = (tabId) => {
currentTab.value = tabId
const tab = openTabs.value.find(t => t.id === tabId)
if (tab) {
router.push(tab.path)
}
}
const closeTab = (tabId) => {
const index = openTabs.value.findIndex(tab => tab.id === tabId)
if (index > -1) {
openTabs.value.splice(index, 1)
//
if (currentTab.value === tabId) {
const newTab = openTabs.value[index - 1] || openTabs.value[0]
if (newTab) {
currentTab.value = newTab.id
router.push(newTab.path)
}
}
}
}
const closeAllTabs = () => {
openTabs.value = openTabs.value.filter(tab => !tab.closable)
currentTab.value = 'home'
router.push('/')
}
const toggleFavorite = (itemId) => {
const item = mainMenuItems.value.find(i => i.id === itemId)
if (item) {
item.favorite = !item.favorite
updateFavoriteMenu()
}
}
const updateFavoriteMenu = () => {
favoriteMenuItems.value = mainMenuItems.value.filter(item => item.favorite)
}
const markAsRead = (messageId) => {
const message = messages.value.find(m => m.id === messageId)
if (message) {
message.read = true
}
}
const formatTime = (time) => {
const now = new Date()
const diff = now - time
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(diff / (1000 * 60 * 60))
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (minutes < 60) return `${minutes}分钟前`
if (hours < 24) return `${hours}小时前`
return `${days}天前`
}
const openProfile = () => {
showUserMenu.value = false
//
}
const openSettings = () => {
showUserMenu.value = false
router.push('/settings')
}
const logout = async () => {
try {
await userService.logout()
router.push('/login')
} catch (error) {
console.error('退出登录失败:', error)
}
}
//
watch(() => route.path, (newPath) => {
const tab = openTabs.value.find(t => t.path === newPath)
if (tab) {
currentTab.value = tab.id
}
})
onMounted(() => {
//
updateFavoriteMenu()
//
const savedUser = localStorage.getItem('user')
if (savedUser) {
Object.assign(currentUser, JSON.parse(savedUser))
}
})
return {
showMessagePanel,
showUserMenu,
currentTab,
openTabs,
currentUser,
messages,
mainMenuItems,
favoriteMenuItems,
unreadCount,
currentRoute,
breadcrumbs,
toggleMessagePanel,
toggleUserMenu,
handleMenuClick,
switchTab,
closeTab,
closeAllTabs,
toggleFavorite,
markAsRead,
formatTime,
openProfile,
openSettings,
logout
}
}
}
</script>
<style scoped>
.main-layout {
height: calc(100vh - 24px); /* 减去状态条高度 */
display: flex;
flex-direction: column;
background: #f5f5f5;
/* 添加中文字体支持 */
font-family: 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', Avenir, Helvetica, Arial, sans-serif;
/* 确保不会溢出 */
overflow: hidden;
}
/* 顶部导航栏 */
.header {
height: 60px;
background: white;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
z-index: 100;
}
.header-left {
display: flex;
align-items: center;
gap: 20px;
}
.logo h1 {
margin: 0;
font-size: 24px;
color: #1976d2;
font-weight: bold;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
color: #666;
font-size: 14px;
}
.breadcrumb-item {
color: #333;
}
.separator {
color: #ccc;
margin: 0 4px;
}
.header-right {
display: flex;
align-items: center;
gap: 20px;
}
/* 消息中心 */
.message-center {
position: relative;
}
.message-btn {
background: none;
border: none;
font-size: 16px; /* 从20px缩小到16px(缩小一半) */
cursor: pointer;
position: relative;
padding: 8px;
border-radius: 4px;
transition: background-color 0.2s;
}
.message-btn:hover {
background: #f5f5f5;
}
.badge {
position: absolute;
top: 0;
right: 0;
background: #f44336;
color: white;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 11px;
display: flex;
align-items: center;
justify-content: center;
}
.message-panel {
position: absolute;
top: 100%;
right: 0;
width: 350px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
margin-top: 8px;
}
.message-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #eee;
}
.message-header h3 {
margin: 0;
font-size: 16px;
}
.close-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #999;
}
.message-list {
max-height: 400px;
overflow-y: auto;
}
.message-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f5f5f5;
transition: background-color 0.2s;
}
.message-item:hover {
background: #f9f9f9;
}
.message-icon {
font-size: 20px;
margin-right: 12px;
}
.message-content {
flex: 1;
}
.message-title {
font-weight: 500;
margin-bottom: 4px;
}
.message-time {
font-size: 12px;
color: #999;
}
.mark-read-btn {
background: none;
border: none;
color: #4caf50;
cursor: pointer;
font-size: 16px;
padding: 4px;
border-radius: 4px;
}
.mark-read-btn:hover {
background: #f0f8f0;
}
/* 用户信息 */
.user-info {
position: relative;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #1976d2;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s;
}
.user-avatar:hover {
background: #1565c0;
}
.user-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.avatar-placeholder {
font-size: 18px;
}
.user-menu {
position: absolute;
top: 100%;
right: 0;
width: 250px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
margin-top: 8px;
}
.user-menu-header {
padding: 16px;
border-bottom: 1px solid #eee;
}
.user-name {
font-weight: 600;
margin-bottom: 4px;
}
.user-email {
font-size: 12px;
color: #666;
}
.user-menu-items {
padding: 8px 0;
}
.menu-item {
width: 100%;
background: none;
border: none;
padding: 12px 16px;
text-align: left;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
transition: background-color 0.2s;
font-size: 13px; /* 从默认14px缩小到13px */
}
.menu-item:hover {
background: #f5f5f5;
}
/* 主要内容区域 */
.main-content {
flex: 1;
display: flex;
overflow: hidden;
/* 高度由flex自动计算 */
}
/* 左侧菜单 */
.sidebar {
width: 250px;
background: white;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.sidebar-nav {
padding: 20px 0;
}
.nav-section {
margin-bottom: 20px; /* 从30px减少到20px,因为删除了标题 */
}
.nav-section:last-child {
margin-bottom: 0; /* 最后一个section不需要底部间距 */
}
.nav-title {
padding: 0 20px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 600;
color: #999;
text-transform: none; /* 取消大写转换 */
letter-spacing: normal; /* 取消字母间距 */
}
.nav-list {
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
display: flex;
align-items: center;
padding: 10px 20px; /* 从12px减少到10px,因为字体变小了 */
color: #333;
text-decoration: none;
transition: all 0.2s;
position: relative;
font-size: 13px;
}
.nav-item:hover {
background: #f5f5f5;
color: #1976d2;
}
.nav-item.active {
background: #e3f2fd;
color: #1976d2;
border-right: 3px solid #1976d2;
}
.nav-icon {
font-size: 12px; /* 从18px缩小到12px(缩小三分之一) */
margin-right: 12px;
width: 16px; /* 从20px缩小到16px */
text-align: center;
}
.nav-text {
flex: 1;
}
.favorite-btn {
background: none;
border: none;
font-size: 12px; /* 从14px缩小到12px,与图标保持一致 */
cursor: pointer;
opacity: 0.3;
transition: opacity 0.2s;
padding: 2px; /* 添加内边距 */
}
.favorite-btn:hover,
.favorite-btn.active {
opacity: 1;
}
/* 右侧内容区域 */
.content-area {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden; /* 隐藏所有滚动条 */
/* 确保高度正确计算 */
height: 100%;
}
/* 内容选项卡 */
.content-tabs {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 0;
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
overflow-x: auto;
overflow-y: hidden; /* 确保垂直方向不滚动 */
}
.tab-list {
display: flex;
align-items: center;
flex: 1;
overflow-x: auto;
overflow-y: hidden; /* 确保垂直方向不滚动 */
height: 100%; /* 限制高度 */
}
.tab-item {
padding: 6px 16px;
background: transparent;
border: none;
border-right: 1px solid #dee2e6;
cursor: pointer;
font-size: 13px;
color: #6c757d;
transition: all 0.2s;
white-space: nowrap;
display: flex;
align-items: center;
gap: 6px;
height: 100%; /* 确保高度填满容器 */
min-width: 120px;
justify-content: center;
position: relative;
box-sizing: border-box; /* 确保padding不会增加总高度 */
}
.tab-item:hover {
background: #e9ecef;
color: #495057;
}
.tab-item.active {
background: white;
color: #007bff;
border-bottom: 2px solid #007bff;
font-weight: 500;
}
.tab-item .tab-close {
width: 16px;
height: 16px;
border: none;
background: transparent;
color: #6c757d;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
transition: all 0.2s;
margin-left: 4px;
border-radius: 0;
line-height: 1;
}
.tab-item .tab-close:hover {
color: #dc3545;
background: transparent;
transform: none;
}
.tab-title {
flex: 1;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tab-close {
background: none;
border: none;
font-size: 16px;
cursor: pointer;
color: #999;
padding: 2px;
border-radius: 2px;
transition: all 0.2s;
}
.tab-close:hover {
background: #f0f0f0;
color: #666;
}
.tab-actions {
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 10px;
flex-shrink: 0;
height: 100%; /* 确保高度一致 */
}
.close-all-btn {
background: none;
border: none;
font-size: 16px;
cursor: pointer;
color: #999;
padding: 4px;
border-radius: 3px;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px; /* 确保按钮高度合适 */
margin-left: 8px;
box-sizing: border-box; /* 确保padding不会增加总高度 */
}
.close-all-btn:hover {
background: #e9ecef;
color: #495057;
}
.close-all-icon {
font-size: 14px;
font-weight: bold;
line-height: 1;
}
/* 功能内容区 */
.content-body {
flex: 1;
overflow-y: auto; /* 只有内容区有垂直滚动条 */
overflow-x: hidden; /* 隐藏水平滚动条 */
background: white;
position: relative;
/* 高度由flex自动计算 */
}
/* 动态滚动条样式 */
.content-body::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.content-body::-webkit-scrollbar-track {
background: transparent;
border-radius: 4px;
}
.content-body::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 4px;
transition: background 0.3s ease;
}
.content-body::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.4);
}
.content-body::-webkit-scrollbar-corner {
background: transparent;
}
/* 选项卡区域的滚动条样式 */
.content-tabs::-webkit-scrollbar {
height: 4px; /* 只显示水平滚动条 */
width: 0; /* 隐藏垂直滚动条 */
}
.content-tabs::-webkit-scrollbar-track {
background: transparent;
}
.content-tabs::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 2px;
}
.content-tabs::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.2);
}
/* 确保内容可以滚动 */
.content-body > * {
min-height: 100%;
/* 确保子元素不会创建额外的滚动条 */
overflow: visible;
}
/* 响应式设计 */
@media (max-width: 768px) {
.sidebar {
width: 200px;
}
.header {
padding: 0 15px;
}
.logo h1 {
font-size: 20px;
}
.breadcrumb {
display: none;
}
}
</style>

134
gofaster/app/src/renderer/components/StatusBar.vue

@ -1,11 +1,19 @@ @@ -1,11 +1,19 @@
<!-- src/components/StatusBar.vue -->
<template>
<div class="status-bar">
<div class="status-item">服务地址: {{ serverUrl }}</div>
<!-- 左侧用户和版本信息 -->
<div class="status-left">
<div class="status-item">用户: {{ userInfo }}</div>
<div class="status-item">最近错误: {{ errorCount }}</div>
<div class="status-item">版本: {{ appVersion }}</div>
</div>
<!-- 右侧其他状态信息 -->
<div class="status-right">
<div class="status-item">服务地址: {{ serverUrl }}</div>
<div class="status-item">最近错误: {{ errorCount }}</div>
<div class="status-item">内存: {{ memoryUsage }} MB</div>
<div class="status-item">系统内存: {{ systemMemoryUsage }}</div>
</div>
</div>
</template>
@ -20,21 +28,63 @@ export default { @@ -20,21 +28,63 @@ export default {
const errorCount = ref(0);
const appVersion = ref(process.env.VUE_APP_VERSION || '1.0.0');
const memoryUsage = ref(0);
const systemMemoryUsage = ref('N/A');
// 使
// 使 - 使
let intervalId;
onMounted(() => {
intervalId = setInterval(() => {
if (window.performance && window.performance.memory) {
memoryUsage.value = (window.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(2);
}
}, 2000);
updateMemoryUsage();
}, 3000); // 3
});
onUnmounted(() => {
if (intervalId) {
clearInterval(intervalId);
}
});
// 使
const updateMemoryUsage = async () => {
try {
// 使 Electron IPC
if (window.electronAPI && window.electronAPI.getProcessMemoryInfo) {
const memoryInfo = await window.electronAPI.getProcessMemoryInfo();
if (memoryInfo && memoryInfo.privateBytes) {
// privateBytes 使
memoryUsage.value = (memoryInfo.privateBytes / (1024 * 1024)).toFixed(1);
}
// 使
if (memoryInfo.systemTotal && memoryInfo.systemUsed) {
const totalGB = (memoryInfo.systemTotal / (1024 * 1024 * 1024)).toFixed(1);
const usedGB = (memoryInfo.systemUsed / (1024 * 1024 * 1024)).toFixed(1);
const usedPercent = ((memoryInfo.systemUsed / memoryInfo.systemTotal) * 100).toFixed(1);
systemMemoryUsage.value = `${usedGB}/${totalGB} GB (${usedPercent}%)`;
}
} else {
// 退API
fallbackMemoryUsage();
}
} catch (error) {
console.warn('无法获取内存使用情况:', error);
fallbackMemoryUsage();
}
};
// 退
const fallbackMemoryUsage = () => {
if (window.performance && window.performance.memory) {
const memory = window.performance.memory;
// 使 usedJSHeapSize JavaScript使
memoryUsage.value = (memory.usedJSHeapSize / (1024 * 1024)).toFixed(1);
systemMemoryUsage.value = 'N/A (浏览器模式)';
} else {
memoryUsage.value = 'N/A';
systemMemoryUsage.value = 'N/A';
}
};
//
window.electronAPI?.onStatusUpdate((event, data) => {
if (data.userInfo) userInfo.value = data.userInfo;
@ -46,7 +96,8 @@ export default { @@ -46,7 +96,8 @@ export default {
userInfo,
errorCount,
appVersion,
memoryUsage
memoryUsage,
systemMemoryUsage
};
}
};
@ -63,13 +114,76 @@ export default { @@ -63,13 +114,76 @@ export default {
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
font-size: 12px;
font-size: 11px;
border-top: 1px solid #444;
z-index: 1000;
/* 添加中文字体支持 */
font-family: 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', Avenir, Helvetica, Arial, sans-serif;
/* 添加响应式支持 */
min-width: 800px; /* 设置最小宽度 */
}
.status-left {
display: flex;
align-items: center;
}
.status-right {
display: flex;
align-items: center;
gap: 10px; /* 使用gap替代margin-right */
flex-wrap: nowrap; /* 防止换行 */
}
.status-item {
margin-right: 20px;
margin-right: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: none; /* 移除最大宽度限制 */
flex-shrink: 0; /* 防止收缩 */
}
/* 为服务地址项添加特殊样式 */
.status-item:contains("服务地址") {
min-width: 120px; /* 设置最小宽度 */
max-width: 200px; /* 设置最大宽度 */
}
.status-item:last-child {
margin-right: 0;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.status-bar {
padding: 0 8px;
font-size: 10px;
}
.status-item {
margin-right: 10px;
}
.status-right {
gap: 8px;
}
}
@media (max-width: 1000px) {
.status-bar {
padding: 0 6px;
font-size: 9px;
}
.status-item {
margin-right: 8px;
}
.status-right {
gap: 6px;
}
}
</style>

69
gofaster/app/src/renderer/main.js

@ -1,3 +1,72 @@ @@ -1,3 +1,72 @@
// 在最开头抑制Electron安全警告
(function() {
// 抑制Electron安全警告
const originalWarn = console.warn;
const originalError = console.error;
console.warn = function(...args) {
const message = args.join(' ');
if (message.includes('Electron Security Warning') ||
message.includes('Insecure Content-Security-Policy') ||
message.includes('unsafe-eval')) {
return; // 抑制这些警告
}
originalWarn.apply(console, args);
};
console.error = function(...args) {
const message = args.join(' ');
if (message.includes('Electron Security Warning') ||
message.includes('Insecure Content-Security-Policy') ||
message.includes('unsafe-eval')) {
return; // 抑制这些警告
}
originalError.apply(console, args);
};
// 重写warnAboutInsecureCSP函数 - 使用更安全的方法
if (typeof window !== 'undefined') {
try {
// 先检查函数是否已经存在
if (window.warnAboutInsecureCSP) {
// 如果函数已存在,使用Object.defineProperty来重写
Object.defineProperty(window, 'warnAboutInsecureCSP', {
value: function() {
// 什么都不做,完全抑制这个警告
return;
},
writable: false,
configurable: false
});
} else {
// 如果函数不存在,直接定义
window.warnAboutInsecureCSP = function() { return; };
}
} catch (e) {
// 如果重写失败,至少尝试覆盖console.warn
console.log('无法重写warnAboutInsecureCSP,使用console.warn覆盖');
}
}
// 重写eval函数(如果存在)- 使用更安全的方法
try {
if (typeof window.eval !== 'undefined') {
const originalEval = window.eval;
Object.defineProperty(window, 'eval', {
value: function(code) {
// 阻止eval执行
console.log('eval调用被阻止:', code);
return undefined;
},
writable: false,
configurable: false
});
}
} catch (e) {
console.log('无法重写eval函数:', e.message);
}
})();
// 在最开头定义全局变量,解决Electron渲染进程中的global未定义问题
(function() {
// 优先使用globalThis,避免设置window对象的只读属性

23
gofaster/app/src/renderer/router/index.js

@ -1,10 +1,16 @@ @@ -1,10 +1,16 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import MainLayout from '@/components/MainLayout.vue'
import Home from '@/views/Home.vue'
import History from '@/views/History.vue'
import UserManagement from '@/views/UserManagement.vue'
const routes = [
{
path: '/',
component: MainLayout,
children: [
{
path: '',
name: 'Home',
component: Home
},
@ -12,6 +18,23 @@ const routes = [ @@ -12,6 +18,23 @@ const routes = [
path: '/history',
name: 'History',
component: History
},
{
path: '/user-management',
name: 'UserManagement',
component: UserManagement
},
{
path: '/speed-test',
name: 'SpeedTest',
component: () => import('@/components/SpeedTest.vue')
},
{
path: '/settings',
name: 'Settings',
component: () => import('@/views/Settings.vue')
}
]
}
]

196
gofaster/app/src/renderer/services/userService.js

@ -0,0 +1,196 @@ @@ -0,0 +1,196 @@
import axios from 'axios'
// 配置axios基础URL
const API_BASE_URL = 'http://localhost:8080/api'
// 创建axios实例
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
api.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
api.interceptors.response.use(
response => {
return response.data
},
error => {
console.error('API请求错误:', error)
if (error.response) {
// 服务器返回错误状态码
const { status, data } = error.response
switch (status) {
case 400:
throw new Error(data.error || '请求参数错误')
case 401:
throw new Error('未授权,请重新登录')
case 403:
throw new Error('权限不足')
case 404:
throw new Error('请求的资源不存在')
case 500:
throw new Error(data.error || '服务器内部错误')
default:
throw new Error(data.error || `请求失败 (${status})`)
}
} else if (error.request) {
// 请求已发出但没有收到响应
throw new Error('网络连接失败,请检查网络设置')
} else {
// 其他错误
throw new Error(error.message || '请求失败')
}
}
)
export const userService = {
// 获取用户列表
async getUsers(page = 1, pageSize = 10) {
try {
const response = await api.get('/users', {
params: { page, pageSize }
})
return response
} catch (error) {
throw error
}
},
// 获取单个用户
async getUser(id) {
try {
const response = await api.get(`/users/${id}`)
return response
} catch (error) {
throw error
}
},
// 创建用户
async createUser(userData) {
try {
const response = await api.post('/users', userData)
return response
} catch (error) {
throw error
}
},
// 更新用户
async updateUser(id, userData) {
try {
const response = await api.put(`/users/${id}`, userData)
return response
} catch (error) {
throw error
}
},
// 删除用户
async deleteUser(id) {
try {
await api.delete(`/users/${id}`)
return true
} catch (error) {
throw error
}
},
// 获取角色列表
async getRoles() {
try {
const response = await api.get('/roles')
return response
} catch (error) {
throw error
}
},
// 获取权限列表
async getPermissions() {
try {
const response = await api.get('/permissions')
return response
} catch (error) {
throw error
}
},
// 用户登录
async login(credentials) {
try {
const response = await api.post('/auth/login', credentials)
// 保存token到localStorage
if (response.token) {
localStorage.setItem('token', response.token)
localStorage.setItem('user', JSON.stringify(response.user))
}
return response
} catch (error) {
throw error
}
},
// 用户登出
async logout() {
try {
await api.post('/auth/logout')
// 清除本地存储的认证信息
localStorage.removeItem('token')
localStorage.removeItem('user')
return true
} catch (error) {
throw error
}
},
// 获取当前用户信息
async getCurrentUser() {
try {
const response = await api.get('/auth/me')
return response
} catch (error) {
throw error
}
},
// 修改密码
async changePassword(passwordData) {
try {
const response = await api.post('/auth/change-password', passwordData)
return response
} catch (error) {
throw error
}
},
// 重置密码
async resetPassword(email) {
try {
const response = await api.post('/auth/reset-password', { email })
return response
} catch (error) {
throw error
}
}
}
export default userService

159
gofaster/app/src/renderer/views/Home.vue

@ -1,23 +1,6 @@ @@ -1,23 +1,6 @@
<!-- src/renderer/views/Home.vue -->
<template>
<div class="home">
<!-- 顶部导航栏 -->
<header class="header">
<div class="logo">
<h1>🚀 GoFaster</h1>
</div>
<nav class="nav">
<a href="#" class="nav-item active">首页</a>
<a href="#" class="nav-item">速度测试</a>
<a href="#" class="nav-item">历史记录</a>
<a href="#" class="nav-item">设置</a>
</nav>
<div class="user-info">
<div class="avatar">👤</div>
<span class="username">{{ userInfo.name }}</span>
</div>
</header>
<!-- 主要内容区域 -->
<main class="main-content">
<!-- 欢迎区域 -->
@ -25,20 +8,7 @@ @@ -25,20 +8,7 @@
<div class="welcome-card">
<h2>欢迎回来{{ userInfo.name }}</h2>
<p class="subtitle">今天是 {{ currentDate }}您有 {{ todoList.length }} 个待办事项</p>
<div class="quick-stats">
<div class="stat-item">
<span class="stat-number">{{ stats.totalTests }}</span>
<span class="stat-label">总测试次数</span>
</div>
<div class="stat-item">
<span class="stat-number">{{ stats.avgSpeed }}</span>
<span class="stat-label">平均速度</span>
</div>
<div class="stat-item">
<span class="stat-number">{{ stats.bestSpeed }}</span>
<span class="stat-label">最佳速度</span>
</div>
</div>
</div>
</section>
@ -271,62 +241,29 @@ export default { @@ -271,62 +241,29 @@ export default {
<style scoped>
.home {
min-height: 100vh;
min-height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem;
overflow-y: auto;
height: 100%;
/* 添加中文字体支持 */
font-family: 'Microsoft YaHei', 'PingFang SC', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', Avenir, Helvetica, Arial, sans-serif;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.logo h1 {
margin: 0;
font-size: 1.8rem;
font-weight: bold;
}
.nav {
display: flex;
gap: 2rem;
}
.nav-item {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 20px;
transition: all 0.3s ease;
}
.nav-item:hover,
.nav-item.active {
background: rgba(255, 255, 255, 0.2);
}
.user-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.avatar {
font-size: 1.5rem;
}
/* 主要内容区域 */
.main-content {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 2rem;
min-height: 100%;
}
/* 欢迎区域 */
.welcome-section {
text-align: center;
margin-bottom: 2rem;
}
@ -334,25 +271,27 @@ export default { @@ -334,25 +271,27 @@ export default {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 2rem;
text-align: center;
padding: 3rem 2rem;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.welcome-card h2 {
margin: 0 0 0.5rem 0;
font-size: 2rem;
font-size: 2.5rem;
margin-bottom: 1rem;
font-weight: 300;
}
.subtitle {
color: rgba(255, 255, 255, 0.8);
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.quick-stats {
display: flex;
justify-content: center;
gap: 3rem;
margin-top: 2rem;
}
.stat-item {
@ -368,9 +307,10 @@ export default { @@ -368,9 +307,10 @@ export default {
.stat-label {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.8);
opacity: 0.8;
}
/* 功能卡片区域 */
.features-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
@ -382,7 +322,7 @@ export default { @@ -382,7 +322,7 @@ export default {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 1.5rem;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
@ -391,7 +331,8 @@ export default { @@ -391,7 +331,8 @@ export default {
.feature-card:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.15);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.feature-icon {
@ -400,20 +341,21 @@ export default { @@ -400,20 +341,21 @@ export default {
}
.feature-card h3 {
margin: 0 0 0.5rem 0;
font-size: 1.3rem;
margin-bottom: 0.5rem;
}
.feature-card p {
color: rgba(255, 255, 255, 0.8);
margin: 0;
opacity: 0.8;
line-height: 1.5;
}
.todo-section,
.recent-section {
/* 待办事项区域 */
.todo-section {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 1.5rem;
padding: 2rem;
margin-bottom: 2rem;
border: 1px solid rgba(255, 255, 255, 0.2);
}
@ -490,6 +432,19 @@ export default { @@ -490,6 +432,19 @@ export default {
opacity: 1;
}
/* 最近活动区域 */
.recent-section {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 2rem;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.recent-section h3 {
margin-bottom: 1.5rem;
}
.activity-list {
display: flex;
flex-direction: column;
@ -522,19 +477,22 @@ export default { @@ -522,19 +477,22 @@ export default {
font-size: 0.9rem;
}
/* 响应式设计 */
@media (max-width: 768px) {
.header {
flex-direction: column;
gap: 1rem;
.home {
padding: 1rem;
}
.nav {
gap: 1rem;
.main-content {
gap: 1.5rem;
}
.main-content {
padding: 1rem;
.welcome-card {
padding: 2rem 1rem;
}
.welcome-card h2 {
font-size: 2rem;
}
.quick-stats {
@ -545,5 +503,10 @@ export default { @@ -545,5 +503,10 @@ export default {
.features-section {
grid-template-columns: 1fr;
}
.todo-section,
.recent-section {
padding: 1.5rem;
}
}
</style>

399
gofaster/app/src/renderer/views/Settings.vue

@ -0,0 +1,399 @@ @@ -0,0 +1,399 @@
<template>
<div class="settings">
<div class="page-header">
<h2>系统设置</h2>
</div>
<div class="settings-content">
<!-- 基本设置 -->
<div class="settings-section">
<h3>基本设置</h3>
<div class="setting-item">
<label>应用名称</label>
<input v-model="settings.appName" type="text" placeholder="GoFaster" />
</div>
<div class="setting-item">
<label>语言</label>
<select v-model="settings.language">
<option value="zh-CN">简体中文</option>
<option value="en-US">English</option>
</select>
</div>
<div class="setting-item">
<label>主题</label>
<select v-model="settings.theme">
<option value="light">浅色主题</option>
<option value="dark">深色主题</option>
<option value="auto">跟随系统</option>
</select>
</div>
</div>
<!-- 网络设置 -->
<div class="settings-section">
<h3>网络设置</h3>
<div class="setting-item">
<label>API服务器地址</label>
<input v-model="settings.apiUrl" type="text" placeholder="http://localhost:8080" />
</div>
<div class="setting-item">
<label>请求超时时间 ()</label>
<input v-model="settings.timeout" type="number" min="5" max="60" />
</div>
<div class="setting-item">
<label>自动重试次数</label>
<input v-model="settings.retryCount" type="number" min="0" max="5" />
</div>
</div>
<!-- 用户设置 -->
<div class="settings-section">
<h3>用户设置</h3>
<div class="setting-item">
<label>自动登录</label>
<div class="toggle-switch">
<input
v-model="settings.autoLogin"
type="checkbox"
id="autoLogin"
/>
<label for="autoLogin" class="toggle-label"></label>
</div>
</div>
<div class="setting-item">
<label>记住密码</label>
<div class="toggle-switch">
<input
v-model="settings.rememberPassword"
type="checkbox"
id="rememberPassword"
/>
<label for="rememberPassword" class="toggle-label"></label>
</div>
</div>
<div class="setting-item">
<label>会话超时时间 (分钟)</label>
<input v-model="settings.sessionTimeout" type="number" min="15" max="1440" />
</div>
</div>
<!-- 通知设置 -->
<div class="settings-section">
<h3>通知设置</h3>
<div class="setting-item">
<label>桌面通知</label>
<div class="toggle-switch">
<input
v-model="settings.desktopNotifications"
type="checkbox"
id="desktopNotifications"
/>
<label for="desktopNotifications" class="toggle-label"></label>
</div>
</div>
<div class="setting-item">
<label>声音提醒</label>
<div class="toggle-switch">
<input
v-model="settings.soundNotifications"
type="checkbox"
id="soundNotifications"
/>
<label for="soundNotifications" class="toggle-label"></label>
</div>
</div>
<div class="setting-item">
<label>消息提醒间隔 ()</label>
<input v-model="settings.notificationInterval" type="number" min="5" max="300" />
</div>
</div>
<!-- 数据设置 -->
<div class="settings-section">
<h3>数据设置</h3>
<div class="setting-item">
<label>数据缓存大小 (MB)</label>
<input v-model="settings.cacheSize" type="number" min="50" max="1000" />
</div>
<div class="setting-item">
<label>自动清理缓存</label>
<div class="toggle-switch">
<input
v-model="settings.autoCleanCache"
type="checkbox"
id="autoCleanCache"
/>
<label for="autoCleanCache" class="toggle-label"></label>
</div>
</div>
<div class="setting-item">
<label>数据备份频率</label>
<select v-model="settings.backupFrequency">
<option value="daily">每日</option>
<option value="weekly">每周</option>
<option value="monthly">每月</option>
<option value="never">从不</option>
</select>
</div>
</div>
<!-- 操作按钮 -->
<div class="settings-actions">
<button class="btn btn-secondary" @click="resetSettings">
重置设置
</button>
<button class="btn btn-primary" @click="saveSettings">
保存设置
</button>
</div>
</div>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
export default {
name: 'Settings',
setup() {
const settings = reactive({
//
appName: 'GoFaster',
language: 'zh-CN',
theme: 'light',
//
apiUrl: 'http://localhost:8080',
timeout: 10,
retryCount: 3,
//
autoLogin: false,
rememberPassword: false,
sessionTimeout: 30,
//
desktopNotifications: true,
soundNotifications: true,
notificationInterval: 10,
//
cacheSize: 100,
autoCleanCache: true,
backupFrequency: 'weekly'
})
const loadSettings = () => {
const savedSettings = localStorage.getItem('gofaster-settings')
if (savedSettings) {
Object.assign(settings, JSON.parse(savedSettings))
}
}
const saveSettings = () => {
try {
localStorage.setItem('gofaster-settings', JSON.stringify(settings))
//
console.log('设置已保存')
} catch (error) {
console.error('保存设置失败:', error)
}
}
const resetSettings = () => {
if (confirm('确定要重置所有设置吗?此操作不可撤销。')) {
localStorage.removeItem('gofaster-settings')
location.reload()
}
}
onMounted(() => {
loadSettings()
})
return {
settings,
saveSettings,
resetSettings
}
}
}
</script>
<style scoped>
.settings {
padding: 20px;
height: 100%;
overflow-y: auto;
}
.page-header {
margin-bottom: 30px;
}
.page-header h2 {
margin: 0;
color: #333;
font-size: 24px;
}
.settings-content {
max-width: 800px;
}
.settings-section {
background: white;
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.settings-section h3 {
margin: 0 0 20px 0;
color: #333;
font-size: 18px;
border-bottom: 2px solid #e0e0e0;
padding-bottom: 8px;
}
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding: 16px 0;
border-bottom: 1px solid #f5f5f5;
}
.setting-item:last-child {
border-bottom: none;
margin-bottom: 0;
}
.setting-item label {
font-weight: 500;
color: #333;
min-width: 200px;
}
.setting-item input[type="text"],
.setting-item input[type="number"],
.setting-item select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
min-width: 200px;
}
.setting-item input[type="text"]:focus,
.setting-item input[type="number"]:focus,
.setting-item select:focus {
outline: none;
border-color: #1976d2;
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.2);
}
/* 开关样式 */
.toggle-switch {
position: relative;
display: inline-block;
}
.toggle-switch input[type="checkbox"] {
opacity: 0;
width: 0;
height: 0;
}
.toggle-label {
display: block;
width: 50px;
height: 24px;
background: #ccc;
border-radius: 12px;
cursor: pointer;
position: relative;
transition: background-color 0.3s;
}
.toggle-label:before {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background: white;
top: 2px;
left: 2px;
transition: transform 0.3s;
}
.toggle-switch input[type="checkbox"]:checked + .toggle-label {
background: #1976d2;
}
.toggle-switch input[type="checkbox"]:checked + .toggle-label:before {
transform: translateX(26px);
}
/* 操作按钮 */
.settings-actions {
display: flex;
gap: 16px;
justify-content: flex-end;
margin-top: 30px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
}
.btn-primary {
background: #1976d2;
color: white;
}
.btn-primary:hover {
background: #1565c0;
}
.btn-secondary {
background: #757575;
color: white;
}
.btn-secondary:hover {
background: #616161;
}
/* 响应式设计 */
@media (max-width: 768px) {
.setting-item {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.setting-item label {
min-width: auto;
}
.setting-item input[type="text"],
.setting-item input[type="number"],
.setting-item select {
min-width: 100%;
}
.settings-actions {
flex-direction: column;
}
}
</style>

642
gofaster/app/src/renderer/views/UserManagement.vue

@ -0,0 +1,642 @@ @@ -0,0 +1,642 @@
<template>
<div class="user-management">
<div class="page-header">
<h2>用户管理</h2>
<button class="btn btn-primary" @click="showAddUserModal = true">
<i class="icon">+</i> 添加用户
</button>
</div>
<!-- 搜索和筛选 -->
<div class="search-bar">
<div class="search-input">
<i class="icon">🔍</i>
<input
v-model="searchQuery"
type="text"
placeholder="搜索用户名、邮箱或手机号..."
@input="handleSearch"
/>
</div>
<div class="filters">
<select v-model="statusFilter" @change="handleFilter">
<option value="">全部状态</option>
<option value="1">正常</option>
<option value="2">禁用</option>
</select>
<select v-model="roleFilter" @change="handleFilter">
<option value="">全部角色</option>
<option v-for="role in roles" :key="role.id" :value="role.id">
{{ role.name }}
</option>
</select>
</div>
</div>
<!-- 用户列表 -->
<div class="user-table">
<table>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>手机号</th>
<th>状态</th>
<th>角色</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.phone }}</td>
<td>
<span :class="['status-badge', user.status === 1 ? 'active' : 'inactive']">
{{ user.status === 1 ? '正常' : '禁用' }}
</span>
</td>
<td>
<div class="role-tags">
<span v-for="role in user.roles" :key="role.id" class="role-tag">
{{ role.name }}
</span>
</div>
</td>
<td>{{ formatDate(user.created_at) }}</td>
<td>
<div class="actions">
<button class="btn btn-sm btn-info" @click="editUser(user)">
<i class="icon"></i>
</button>
<button class="btn btn-sm btn-danger" @click="deleteUser(user.id)">
<i class="icon">🗑</i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="pagination">
<button
:disabled="currentPage === 1"
@click="changePage(currentPage - 1)"
class="btn btn-sm"
>
上一页
</button>
<span class="page-info">
{{ currentPage }} {{ totalPages }}
</span>
<button
:disabled="currentPage === totalPages"
@click="changePage(currentPage + 1)"
class="btn btn-sm"
>
下一页
</button>
</div>
<!-- 添加/编辑用户模态框 -->
<div v-if="showAddUserModal || showEditUserModal" class="modal-overlay" @click="closeModal">
<div class="modal" @click.stop>
<div class="modal-header">
<h3>{{ showEditUserModal ? '编辑用户' : '添加用户' }}</h3>
<button class="close-btn" @click="closeModal">×</button>
</div>
<div class="modal-body">
<form @submit.prevent="submitUser">
<div class="form-group">
<label>用户名 *</label>
<input
v-model="userForm.username"
type="text"
required
:disabled="showEditUserModal"
/>
</div>
<div class="form-group" v-if="!showEditUserModal">
<label>密码 *</label>
<input
v-model="userForm.password"
type="password"
required
/>
</div>
<div class="form-group">
<label>邮箱 *</label>
<input
v-model="userForm.email"
type="email"
required
/>
</div>
<div class="form-group">
<label>手机号</label>
<input
v-model="userForm.phone"
type="tel"
/>
</div>
<div class="form-group">
<label>状态</label>
<select v-model="userForm.status">
<option value="1">正常</option>
<option value="2">禁用</option>
</select>
</div>
<div class="form-group">
<label>角色</label>
<div class="role-checkboxes">
<label v-for="role in roles" :key="role.id" class="checkbox-item">
<input
type="checkbox"
:value="role.id"
v-model="userForm.roleIds"
/>
{{ role.name }}
</label>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" @click="closeModal">
取消
</button>
<button type="submit" class="btn btn-primary">
{{ showEditUserModal ? '更新' : '创建' }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, reactive, computed, onMounted } from 'vue'
import { userService } from '@/services/userService'
export default {
name: 'UserManagement',
setup() {
const users = ref([])
const roles = ref([])
const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
const searchQuery = ref('')
const statusFilter = ref('')
const roleFilter = ref('')
const showAddUserModal = ref(false)
const showEditUserModal = ref(false)
const editingUser = ref(null)
const userForm = reactive({
username: '',
password: '',
email: '',
phone: '',
status: 1,
roleIds: []
})
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))
const filteredUsers = computed(() => {
let filtered = users.value
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase()
filtered = filtered.filter(user =>
user.username.toLowerCase().includes(query) ||
user.email.toLowerCase().includes(query) ||
user.phone?.toLowerCase().includes(query)
)
}
if (statusFilter.value) {
filtered = filtered.filter(user => user.status === parseInt(statusFilter.value))
}
if (roleFilter.value) {
filtered = filtered.filter(user =>
user.roles.some(role => role.id === parseInt(roleFilter.value))
)
}
return filtered
})
const loadUsers = async () => {
try {
loading.value = true
const response = await userService.getUsers(currentPage.value, pageSize.value)
users.value = response.data
total.value = response.total
} catch (error) {
console.error('加载用户失败:', error)
} finally {
loading.value = false
}
}
const loadRoles = async () => {
try {
const response = await userService.getRoles()
roles.value = response.data
} catch (error) {
console.error('加载角色失败:', error)
}
}
const handleSearch = () => {
currentPage.value = 1
}
const handleFilter = () => {
currentPage.value = 1
}
const changePage = (page) => {
currentPage.value = page
loadUsers()
}
const editUser = (user) => {
editingUser.value = user
Object.assign(userForm, {
username: user.username,
password: '',
email: user.email,
phone: user.phone,
status: user.status,
roleIds: user.roles.map(role => role.id)
})
showEditUserModal.value = true
}
const submitUser = async () => {
try {
if (showEditUserModal.value) {
await userService.updateUser(editingUser.value.id, userForm)
} else {
await userService.createUser(userForm)
}
closeModal()
loadUsers()
} catch (error) {
console.error('保存用户失败:', error)
}
}
const deleteUser = async (userId) => {
if (confirm('确定要删除这个用户吗?')) {
try {
await userService.deleteUser(userId)
loadUsers()
} catch (error) {
console.error('删除用户失败:', error)
}
}
}
const closeModal = () => {
showAddUserModal.value = false
showEditUserModal.value = false
editingUser.value = null
Object.assign(userForm, {
username: '',
password: '',
email: '',
phone: '',
status: 1,
roleIds: []
})
}
const formatDate = (dateString) => {
if (!dateString) return ''
return new Date(dateString).toLocaleDateString('zh-CN')
}
onMounted(() => {
loadUsers()
loadRoles()
})
return {
users,
roles,
loading,
currentPage,
pageSize,
total,
searchQuery,
statusFilter,
roleFilter,
showAddUserModal,
showEditUserModal,
editingUser,
userForm,
totalPages,
filteredUsers,
loadUsers,
loadRoles,
handleSearch,
handleFilter,
changePage,
editUser,
submitUser,
deleteUser,
closeModal,
formatDate
}
}
}
</script>
<style scoped>
.user-management {
padding: 20px;
height: 100%;
overflow-y: auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-header h2 {
margin: 0;
color: #333;
}
.search-bar {
display: flex;
gap: 20px;
margin-bottom: 20px;
align-items: center;
}
.search-input {
position: relative;
flex: 1;
}
.search-input i {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
}
.search-input input {
width: 100%;
padding: 10px 10px 10px 35px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
}
.filters {
display: flex;
gap: 10px;
}
.filters select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.user-table {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background: #f8f9fa;
font-weight: 600;
color: #333;
}
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.status-badge.active {
background: #e8f5e8;
color: #2e7d32;
}
.status-badge.inactive {
background: #ffebee;
color: #c62828;
}
.role-tags {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.role-tag {
background: #e3f2fd;
color: #1976d2;
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
}
.actions {
display: flex;
gap: 8px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}
.btn-primary {
background: #1976d2;
color: white;
}
.btn-primary:hover {
background: #1565c0;
}
.btn-info {
background: #0288d1;
color: white;
}
.btn-danger {
background: #d32f2f;
color: white;
}
.btn-secondary {
background: #757575;
color: white;
}
.btn-sm {
padding: 6px 12px;
font-size: 12px;
}
.btn:hover {
opacity: 0.9;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
}
.page-info {
color: #666;
font-size: 14px;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
}
.modal-header h3 {
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.modal-body {
padding: 20px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.role-checkboxes {
display: flex;
flex-direction: column;
gap: 8px;
}
.checkbox-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.checkbox-item input[type="checkbox"] {
width: auto;
}
.form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 30px;
}
.icon {
font-size: 14px;
}
</style>
Loading…
Cancel
Save