diff --git a/gofaster/README.md b/gofaster/README.md new file mode 100644 index 0000000..3b89127 --- /dev/null +++ b/gofaster/README.md @@ -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后端服务使用,请确保后端服务正常运行。 diff --git a/gofaster/app/DEVELOPMENT.md b/gofaster/app/DEVELOPMENT.md new file mode 100644 index 0000000..6e94d64 --- /dev/null +++ b/gofaster/app/DEVELOPMENT.md @@ -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` 停止开发服务器 + +## 注意事项 + +- 所有控制台输出都已改为英文,避免编码问题 +- 应用界面仍然支持中文显示 +- 字体配置已优化,确保中文正常显示 +- 热加载已优化,包含错误处理和防抖机制 +- 提供多种启动方式,适应不同环境需求 diff --git a/gofaster/app/dev-stable.bat b/gofaster/app/dev-stable.bat new file mode 100644 index 0000000..8f47b3e --- /dev/null +++ b/gofaster/app/dev-stable.bat @@ -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 + ) +) diff --git a/gofaster/app/dev.bat b/gofaster/app/dev.bat new file mode 100644 index 0000000..fa0d79c --- /dev/null +++ b/gofaster/app/dev.bat @@ -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 +) diff --git a/gofaster/app/dev.ps1 b/gofaster/app/dev.ps1 new file mode 100644 index 0000000..1500014 --- /dev/null +++ b/gofaster/app/dev.ps1 @@ -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 +} diff --git a/gofaster/app/dist/renderer/img/background.7c5f6fb1.png b/gofaster/app/dist/renderer/img/background.7c5f6fb1.png deleted file mode 100644 index 173a035..0000000 Binary files a/gofaster/app/dist/renderer/img/background.7c5f6fb1.png and /dev/null differ diff --git a/gofaster/app/dist/renderer/js/index.js b/gofaster/app/dist/renderer/js/index.js index 117cb61..c921dac 100644 --- a/gofaster/app/dist/renderer/js/index.js +++ b/gofaster/app/dist/renderer/js/index.js @@ -16,20 +16,15 @@ __webpack_require__.r(__webpack_exports__); /* 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__); -/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../node_modules/css-loader/dist/runtime/getUrl.js */ "./node_modules/css-loader/dist/runtime/getUrl.js"); -/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__); // Imports - -var ___CSS_LOADER_URL_IMPORT_0___ = new URL(/* asset import */ __webpack_require__(/*! @/assets/background.png */ "./src/renderer/assets/background.png"), __webpack_require__.b); 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())); -var ___CSS_LOADER_URL_REPLACEMENT_0___ = _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default()(___CSS_LOADER_URL_IMPORT_0___); // Module ___CSS_LOADER_EXPORT___.push([module.id, ` /* 全局样式 */ #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; @@ -37,12 +32,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` padding: 0; height: 100vh; width: 100vw; - /* 添加背景图片 */ - background-image: url(${___CSS_LOADER_URL_REPLACEMENT_0___}); - /* 假设图片放在src/assets目录下 */ - background-size: cover; - background-position: center; - background-repeat: no-repeat; + overflow: hidden; } /* 确保html和body元素也填满窗口 */ @@ -53,6 +43,616 @@ 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; +} +`, ""]); +// Exports +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); + + +/***/ }), + +/***/ "./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/MainLayout.vue?vue&type=style&index=0&id=a4b9ee6e&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/MainLayout.vue?vue&type=style&index=0&id=a4b9ee6e&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, ` +.main-layout[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e] { + display: flex; + align-items: center; + gap: 20px; +} +.logo h1[data-v-a4b9ee6e] { + margin: 0; + font-size: 24px; + color: #1976d2; + font-weight: bold; +} +.breadcrumb[data-v-a4b9ee6e] { + display: flex; + align-items: center; + gap: 8px; + color: #666; + font-size: 14px; +} +.breadcrumb-item[data-v-a4b9ee6e] { + color: #333; +} +.separator[data-v-a4b9ee6e] { + color: #ccc; + margin: 0 4px; +} +.header-right[data-v-a4b9ee6e] { + display: flex; + align-items: center; + gap: 20px; +} + +/* 消息中心 */ +.message-center[data-v-a4b9ee6e] { + position: relative; +} +.message-btn[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + background: #f5f5f5; +} +.badge[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e] { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + border-bottom: 1px solid #eee; +} +.message-header h3[data-v-a4b9ee6e] { + margin: 0; + font-size: 16px; +} +.close-btn[data-v-a4b9ee6e] { + background: none; + border: none; + font-size: 20px; + cursor: pointer; + color: #999; +} +.message-list[data-v-a4b9ee6e] { + max-height: 400px; + overflow-y: auto; +} +.message-item[data-v-a4b9ee6e] { + display: flex; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid #f5f5f5; + transition: background-color 0.2s; +} +.message-item[data-v-a4b9ee6e]:hover { + background: #f9f9f9; +} +.message-icon[data-v-a4b9ee6e] { + font-size: 20px; + margin-right: 12px; +} +.message-content[data-v-a4b9ee6e] { + flex: 1; +} +.message-title[data-v-a4b9ee6e] { + font-weight: 500; + margin-bottom: 4px; +} +.message-time[data-v-a4b9ee6e] { + font-size: 12px; + color: #999; +} +.mark-read-btn[data-v-a4b9ee6e] { + background: none; + border: none; + color: #4caf50; + cursor: pointer; + font-size: 16px; + padding: 4px; + border-radius: 4px; +} +.mark-read-btn[data-v-a4b9ee6e]:hover { + background: #f0f8f0; +} + +/* 用户信息 */ +.user-info[data-v-a4b9ee6e] { + position: relative; +} +.user-avatar[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + background: #1565c0; +} +.user-avatar img[data-v-a4b9ee6e] { + width: 100%; + height: 100%; + border-radius: 50%; + -o-object-fit: cover; + object-fit: cover; +} +.avatar-placeholder[data-v-a4b9ee6e] { + font-size: 18px; +} +.user-menu[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e] { + padding: 16px; + border-bottom: 1px solid #eee; +} +.user-name[data-v-a4b9ee6e] { + font-weight: 600; + margin-bottom: 4px; +} +.user-email[data-v-a4b9ee6e] { + font-size: 12px; + color: #666; +} +.user-menu-items[data-v-a4b9ee6e] { + padding: 8px 0; +} +.menu-item[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + background: #f5f5f5; +} + +/* 主要内容区域 */ +.main-content[data-v-a4b9ee6e] { + flex: 1; + display: flex; + overflow: hidden; + /* 高度由flex自动计算 */ +} + +/* 左侧菜单 */ +.sidebar[data-v-a4b9ee6e] { + width: 250px; + background: white; + border-right: 1px solid #e0e0e0; + overflow-y: auto; +} +.sidebar-nav[data-v-a4b9ee6e] { + padding: 20px 0; +} +.nav-section[data-v-a4b9ee6e] { + margin-bottom: 20px; /* 从30px减少到20px,因为删除了标题 */ +} +.nav-section[data-v-a4b9ee6e]:last-child { + margin-bottom: 0; /* 最后一个section不需要底部间距 */ +} +.nav-title[data-v-a4b9ee6e] { + padding: 0 20px; + margin-bottom: 12px; + font-size: 12px; + font-weight: 600; + color: #999; + text-transform: none; /* 取消大写转换 */ + letter-spacing: normal; /* 取消字母间距 */ +} +.nav-list[data-v-a4b9ee6e] { + list-style: none; + padding: 0; + margin: 0; +} +.nav-item[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + background: #f5f5f5; + color: #1976d2; +} +.nav-item.active[data-v-a4b9ee6e] { + background: #e3f2fd; + color: #1976d2; + border-right: 3px solid #1976d2; +} +.nav-icon[data-v-a4b9ee6e] { + font-size: 12px; /* 从18px缩小到12px(缩小三分之一) */ + margin-right: 12px; + width: 16px; /* 从20px缩小到16px */ + text-align: center; +} +.nav-text[data-v-a4b9ee6e] { + flex: 1; +} +.favorite-btn[data-v-a4b9ee6e] { + background: none; + border: none; + font-size: 12px; /* 从14px缩小到12px,与图标保持一致 */ + cursor: pointer; + opacity: 0.3; + transition: opacity 0.2s; + padding: 2px; /* 添加内边距 */ +} +.favorite-btn[data-v-a4b9ee6e]:hover, +.favorite-btn.active[data-v-a4b9ee6e] { + opacity: 1; +} + +/* 右侧内容区域 */ +.content-area[data-v-a4b9ee6e] { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; /* 隐藏所有滚动条 */ + /* 确保高度正确计算 */ + height: 100%; +} + +/* 内容选项卡 */ +.content-tabs[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e] { + display: flex; + align-items: center; + flex: 1; + overflow-x: auto; + overflow-y: hidden; /* 确保垂直方向不滚动 */ + height: 100%; /* 限制高度 */ +} +.tab-item[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + background: #e9ecef; + color: #495057; +} +.tab-item.active[data-v-a4b9ee6e] { + background: white; + color: #007bff; + border-bottom: 2px solid #007bff; + font-weight: 500; +} +.tab-item .tab-close[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + color: #dc3545; + background: transparent; + transform: none; +} +.tab-title[data-v-a4b9ee6e] { + flex: 1; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tab-close[data-v-a4b9ee6e] { + background: none; + border: none; + font-size: 16px; + cursor: pointer; + color: #999; + padding: 2px; + border-radius: 2px; + transition: all 0.2s; +} +.tab-close[data-v-a4b9ee6e]:hover { + background: #f0f0f0; + color: #666; +} +.tab-actions[data-v-a4b9ee6e] { + display: flex; + align-items: center; + justify-content: flex-end; + padding-right: 10px; + flex-shrink: 0; + height: 100%; /* 确保高度一致 */ +} +.close-all-btn[data-v-a4b9ee6e] { + 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[data-v-a4b9ee6e]:hover { + background: #e9ecef; + color: #495057; +} +.close-all-icon[data-v-a4b9ee6e] { + font-size: 14px; + font-weight: bold; + line-height: 1; +} + +/* 功能内容区 */ +.content-body[data-v-a4b9ee6e] { + flex: 1; + overflow-y: auto; /* 只有内容区有垂直滚动条 */ + overflow-x: hidden; /* 隐藏水平滚动条 */ + background: white; + position: relative; + /* 高度由flex自动计算 */ +} + +/* 动态滚动条样式 */ +.content-body[data-v-a4b9ee6e]::-webkit-scrollbar { + width: 8px; + height: 8px; +} +.content-body[data-v-a4b9ee6e]::-webkit-scrollbar-track { + background: transparent; + border-radius: 4px; +} +.content-body[data-v-a4b9ee6e]::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + -webkit-transition: background 0.3s ease; + transition: background 0.3s ease; +} +.content-body[data-v-a4b9ee6e]::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} +.content-body[data-v-a4b9ee6e]::-webkit-scrollbar-corner { + background: transparent; +} + +/* 选项卡区域的滚动条样式 */ +.content-tabs[data-v-a4b9ee6e]::-webkit-scrollbar { + height: 4px; /* 只显示水平滚动条 */ + width: 0; /* 隐藏垂直滚动条 */ +} +.content-tabs[data-v-a4b9ee6e]::-webkit-scrollbar-track { + background: transparent; +} +.content-tabs[data-v-a4b9ee6e]::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.1); + border-radius: 2px; +} +.content-tabs[data-v-a4b9ee6e]::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.2); +} + +/* 确保内容可以滚动 */ +.content-body[data-v-a4b9ee6e] > * { + min-height: 100%; + /* 确保子元素不会创建额外的滚动条 */ + overflow: visible; +} + +/* 响应式设计 */ +@media (max-width: 768px) { +.sidebar[data-v-a4b9ee6e] { + width: 200px; +} +.header[data-v-a4b9ee6e] { + padding: 0 15px; +} +.logo h1[data-v-a4b9ee6e] { + font-size: 20px; +} +.breadcrumb[data-v-a4b9ee6e] { + display: none; +} } `, ""]); // Exports @@ -83,22 +683,77 @@ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBP // Module ___CSS_LOADER_EXPORT___.push([module.id, ` .status-bar[data-v-126429ab] { - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 24px; - background-color: #333; - color: #fff; + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 24px; + background-color: #333; + color: #fff; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10px; + 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[data-v-126429ab] { display: flex; align-items: center; - padding: 0 10px; - font-size: 12px; - border-top: 1px solid #444; - z-index: 1000; +} +.status-right[data-v-126429ab] { + display: flex; + align-items: center; + gap: 10px; /* 使用gap替代margin-right */ + flex-wrap: nowrap; /* 防止换行 */ +} +.status-item[data-v-126429ab] { + margin-right: 15px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: none; /* 移除最大宽度限制 */ + flex-shrink: 0; /* 防止收缩 */ +} + +/* 为服务地址项添加特殊样式 */ +.status-item[data-v-126429ab]:contains("服务地址") { + min-width: 120px; /* 设置最小宽度 */ + max-width: 200px; /* 设置最大宽度 */ +} +.status-item[data-v-126429ab]:last-child { + margin-right: 0; +} + +/* 响应式设计 */ +@media (max-width: 1200px) { +.status-bar[data-v-126429ab] { + padding: 0 8px; + font-size: 10px; +} +.status-item[data-v-126429ab] { + margin-right: 10px; +} +.status-right[data-v-126429ab] { + gap: 8px; +} +} +@media (max-width: 1000px) { +.status-bar[data-v-126429ab] { + padding: 0 6px; + font-size: 9px; } .status-item[data-v-126429ab] { - margin-right: 20px; + margin-right: 8px; +} +.status-right[data-v-126429ab] { + gap: 6px; +} } `, ""]); // Exports @@ -198,75 +853,53 @@ var ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBP // Module ___CSS_LOADER_EXPORT___.push([module.id, ` .home[data-v-ecd3befa] { - 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[data-v-ecd3befa] { - 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[data-v-ecd3befa] { - margin: 0; - font-size: 1.8rem; - font-weight: bold; -} -.nav[data-v-ecd3befa] { - display: flex; - gap: 2rem; -} -.nav-item[data-v-ecd3befa] { - color: white; - text-decoration: none; - padding: 0.5rem 1rem; - border-radius: 20px; - transition: all 0.3s ease; -} -.nav-item[data-v-ecd3befa]:hover, -.nav-item.active[data-v-ecd3befa] { - background: rgba(255, 255, 255, 0.2); -} -.user-info[data-v-ecd3befa] { - display: flex; - align-items: center; - gap: 0.5rem; -} -.avatar[data-v-ecd3befa] { - font-size: 1.5rem; -} + +/* 主要内容区域 */ .main-content[data-v-ecd3befa] { - padding: 2rem; max-width: 1200px; margin: 0 auto; + display: flex; + flex-direction: column; + gap: 2rem; + min-height: 100%; } + +/* 欢迎区域 */ .welcome-section[data-v-ecd3befa] { + text-align: center; margin-bottom: 2rem; } .welcome-card[data-v-ecd3befa] { 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[data-v-ecd3befa] { - margin: 0 0 0.5rem 0; - font-size: 2rem; + font-size: 2.5rem; + margin-bottom: 1rem; + font-weight: 300; } .subtitle[data-v-ecd3befa] { - color: rgba(255, 255, 255, 0.8); + font-size: 1.2rem; margin-bottom: 2rem; + opacity: 0.9; } .quick-stats[data-v-ecd3befa] { display: flex; justify-content: center; gap: 3rem; + margin-top: 2rem; } .stat-item[data-v-ecd3befa] { text-align: center; @@ -279,8 +912,10 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` } .stat-label[data-v-ecd3befa] { font-size: 0.9rem; - color: rgba(255, 255, 255, 0.8); + opacity: 0.8; } + +/* 功能卡片区域 */ .features-section[data-v-ecd3befa] { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); @@ -291,7 +926,7 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` 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; @@ -299,25 +934,28 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` } .feature-card[data-v-ecd3befa]: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[data-v-ecd3befa] { font-size: 3rem; margin-bottom: 1rem; } .feature-card h3[data-v-ecd3befa] { - margin: 0 0 0.5rem 0; + font-size: 1.3rem; + margin-bottom: 0.5rem; } .feature-card p[data-v-ecd3befa] { - color: rgba(255, 255, 255, 0.8); - margin: 0; + opacity: 0.8; + line-height: 1.5; } -.todo-section[data-v-ecd3befa], -.recent-section[data-v-ecd3befa] { + +/* 待办事项区域 */ +.todo-section[data-v-ecd3befa] { 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); } @@ -381,51 +1019,418 @@ ___CSS_LOADER_EXPORT___.push([module.id, ` .delete-btn[data-v-ecd3befa]:hover { opacity: 1; } + +/* 最近活动区域 */ +.recent-section[data-v-ecd3befa] { + 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[data-v-ecd3befa] { + margin-bottom: 1.5rem; +} .activity-list[data-v-ecd3befa] { display: flex; flex-direction: column; - gap: 1rem; + gap: 1rem; +} +.activity-item[data-v-ecd3befa] { + display: flex; + align-items: center; + gap: 1rem; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.05); + border-radius: 10px; +} +.activity-icon[data-v-ecd3befa] { + font-size: 1.5rem; +} +.activity-content[data-v-ecd3befa] { + flex: 1; +} +.activity-title[data-v-ecd3befa] { + font-weight: 500; +} +.activity-time[data-v-ecd3befa] { + color: rgba(255, 255, 255, 0.6); + font-size: 0.9rem; +} + +/* 响应式设计 */ +@media (max-width: 768px) { +.home[data-v-ecd3befa] { + padding: 1rem; +} +.main-content[data-v-ecd3befa] { + gap: 1.5rem; +} +.welcome-card[data-v-ecd3befa] { + padding: 2rem 1rem; +} +.welcome-card h2[data-v-ecd3befa] { + font-size: 2rem; +} +.quick-stats[data-v-ecd3befa] { + flex-direction: column; + gap: 1rem; +} +.features-section[data-v-ecd3befa] { + grid-template-columns: 1fr; +} +.todo-section[data-v-ecd3befa], + .recent-section[data-v-ecd3befa] { + padding: 1.5rem; +} +} +`, ""]); +// Exports +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); + + +/***/ }), + +/***/ "./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/UserManagement.vue?vue&type=style&index=0&id=7338cb92&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/UserManagement.vue?vue&type=style&index=0&id=7338cb92&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, ` +.user-management[data-v-7338cb92] { + padding: 20px; + height: 100%; + overflow-y: auto; +} +.page-header[data-v-7338cb92] { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.page-header h2[data-v-7338cb92] { + margin: 0; + color: #333; +} +.search-bar[data-v-7338cb92] { + display: flex; + gap: 20px; + margin-bottom: 20px; + align-items: center; +} +.search-input[data-v-7338cb92] { + position: relative; + flex: 1; +} +.search-input i[data-v-7338cb92] { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #999; +} +.search-input input[data-v-7338cb92] { + width: 100%; + padding: 10px 10px 10px 35px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; +} +.filters[data-v-7338cb92] { + display: flex; + gap: 10px; +} +.filters select[data-v-7338cb92] { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} +.user-table[data-v-7338cb92] { + background: white; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + overflow: hidden; + margin-bottom: 20px; +} +table[data-v-7338cb92] { + width: 100%; + border-collapse: collapse; +} +th[data-v-7338cb92], td[data-v-7338cb92] { + padding: 12px; + text-align: left; + border-bottom: 1px solid #eee; +} +th[data-v-7338cb92] { + background: #f8f9fa; + font-weight: 600; + color: #333; +} +.status-badge[data-v-7338cb92] { + padding: 4px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} +.status-badge.active[data-v-7338cb92] { + background: #e8f5e8; + color: #2e7d32; +} +.status-badge.inactive[data-v-7338cb92] { + background: #ffebee; + color: #c62828; +} +.role-tags[data-v-7338cb92] { + display: flex; + gap: 4px; + flex-wrap: wrap; +} +.role-tag[data-v-7338cb92] { + background: #e3f2fd; + color: #1976d2; + padding: 2px 6px; + border-radius: 4px; + font-size: 11px; +} +.actions[data-v-7338cb92] { + display: flex; + gap: 8px; +} +.btn[data-v-7338cb92] { + 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[data-v-7338cb92] { + background: #1976d2; + color: white; +} +.btn-primary[data-v-7338cb92]:hover { + background: #1565c0; +} +.btn-info[data-v-7338cb92] { + background: #0288d1; + color: white; +} +.btn-danger[data-v-7338cb92] { + background: #d32f2f; + color: white; +} +.btn-secondary[data-v-7338cb92] { + background: #757575; + color: white; +} +.btn-sm[data-v-7338cb92] { + padding: 6px 12px; + font-size: 12px; +} +.btn[data-v-7338cb92]:hover { + opacity: 0.9; +} +.btn[data-v-7338cb92]:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.pagination[data-v-7338cb92] { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; +} +.page-info[data-v-7338cb92] { + color: #666; + font-size: 14px; +} +.modal-overlay[data-v-7338cb92] { + 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[data-v-7338cb92] { + background: white; + border-radius: 8px; + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; +} +.modal-header[data-v-7338cb92] { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid #eee; +} +.modal-header h3[data-v-7338cb92] { + margin: 0; +} +.close-btn[data-v-7338cb92] { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #999; +} +.modal-body[data-v-7338cb92] { + padding: 20px; +} +.form-group[data-v-7338cb92] { + margin-bottom: 20px; +} +.form-group label[data-v-7338cb92] { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #333; +} +.form-group input[data-v-7338cb92], +.form-group select[data-v-7338cb92] { + width: 100%; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} +.role-checkboxes[data-v-7338cb92] { + display: flex; + flex-direction: column; + gap: 8px; } -.activity-item[data-v-ecd3befa] { +.checkbox-item[data-v-7338cb92] { display: flex; align-items: center; - gap: 1rem; - padding: 0.75rem; - background: rgba(255, 255, 255, 0.05); - border-radius: 10px; + gap: 8px; + cursor: pointer; } -.activity-icon[data-v-ecd3befa] { - font-size: 1.5rem; +.checkbox-item input[type="checkbox"][data-v-7338cb92] { + width: auto; } -.activity-content[data-v-ecd3befa] { - flex: 1; +.form-actions[data-v-7338cb92] { + display: flex; + gap: 12px; + justify-content: flex-end; + margin-top: 30px; } -.activity-title[data-v-ecd3befa] { - font-weight: 500; +.icon[data-v-7338cb92] { + font-size: 14px; } -.activity-time[data-v-ecd3befa] { - color: rgba(255, 255, 255, 0.6); - font-size: 0.9rem; +`, ""]); +// Exports +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___); + + +/***/ }), + +/***/ "./node_modules/css-loader/dist/cjs.js??clonedRuleSet-14.use[1]!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-14.use[2]!./src/renderer/assets/fonts.css": +/*!*************************************************************************************************************************************************************************!*\ + !*** ./node_modules/css-loader/dist/cjs.js??clonedRuleSet-14.use[1]!./node_modules/postcss-loader/dist/cjs.js??clonedRuleSet-14.use[2]!./src/renderer/assets/fonts.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, `/* 中文字体配置文件 */ + +/* 定义字体族变量 */ +: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; } -@media (max-width: 768px) { -.header[data-v-ecd3befa] { - flex-direction: column; - gap: 1rem; - padding: 1rem; + +/* 全局字体设置 */ +* { + font-family: var(--font-family-chinese), var(--font-family-english); } -.nav[data-v-ecd3befa] { - gap: 1rem; + +/* 确保所有文本元素都使用中文字体 */ +body, html, #app { + font-family: var(--font-family-chinese), var(--font-family-english); } -.main-content[data-v-ecd3befa] { - padding: 1rem; + +/* 特定组件的字体设置 */ +.main-layout, +.status-bar, +.home, +.user-management, +.settings { + font-family: var(--font-family-chinese), var(--font-family-english); } -.quick-stats[data-v-ecd3befa] { - flex-direction: column; - gap: 1rem; + +/* 标题和重要文本的字体设置 */ +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-family-chinese), var(--font-family-english); + font-weight: 500; } -.features-section[data-v-ecd3befa] { - grid-template-columns: 1fr; + +/* 按钮和输入框的字体设置 */ +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; + } } `, ""]); // Exports @@ -446,8 +1451,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); /* harmony import */ var _components_StatusBar_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./components/StatusBar.vue */ "./src/renderer/components/StatusBar.vue"); +/* harmony import */ var _assets_fonts_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./assets/fonts.css */ "./src/renderer/assets/fonts.css"); +/* harmony import */ var _assets_fonts_css__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_assets_fonts_css__WEBPACK_IMPORTED_MODULE_1__); + // 引入字体配置文件 /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ name: 'App', @@ -461,6 +1469,239 @@ __webpack_require__.r(__webpack_exports__); }); +/***/ }), + +/***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/MainLayout.vue?vue&type=script&lang=js": +/*!***********************************************************************************************************************************!*\ + !*** ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/MainLayout.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 vue_router__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vue-router */ "./node_modules/vue-router/dist/vue-router.mjs"); +/* harmony import */ var _services_userService__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @/services/userService */ "./src/renderer/services/userService.js"); + + + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ + name: 'MainLayout', + setup() { + const router = (0,vue_router__WEBPACK_IMPORTED_MODULE_1__.useRouter)() + const route = (0,vue_router__WEBPACK_IMPORTED_MODULE_1__.useRoute)() + + // 响应式数据 + const showMessagePanel = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const showUserMenu = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const currentTab = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)('home') + const openTabs = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([ + { id: 'home', title: '欢迎', path: '/', closable: false } + ]) + + const currentUser = (0,vue__WEBPACK_IMPORTED_MODULE_0__.reactive)({ + name: '管理员', + email: 'admin@gofaster.com', + avatar: null + }) + + const messages = (0,vue__WEBPACK_IMPORTED_MODULE_0__.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 = (0,vue__WEBPACK_IMPORTED_MODULE_0__.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 = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + + // 计算属性 + const unreadCount = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => messages.value.filter(m => !m.read).length) + const currentRoute = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => route.path) + const breadcrumbs = (0,vue__WEBPACK_IMPORTED_MODULE_0__.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 _services_userService__WEBPACK_IMPORTED_MODULE_2__.userService.logout() + router.push('/login') + } catch (error) { + console.error('退出登录失败:', error) + } + } + + // 监听路由变化 + ;(0,vue__WEBPACK_IMPORTED_MODULE_0__.watch)(() => route.path, (newPath) => { + const tab = openTabs.value.find(t => t.path === newPath) + if (tab) { + currentTab.value = tab.id + } + }) + + ;(0,vue__WEBPACK_IMPORTED_MODULE_0__.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 + } + } +}); + + /***/ }), /***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/components/StatusBar.vue?vue&type=script&lang=js": @@ -486,21 +1727,63 @@ __webpack_require__.r(__webpack_exports__); const errorCount = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(0); const appVersion = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(({"NODE_ENV":"development","BASE_URL":""}).VUE_APP_VERSION || '1.0.0'); const memoryUsage = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(0); + const systemMemoryUsage = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)('N/A'); - // 更新内存使用情况 + // 更新内存使用情况 - 使用更准确的方法 let intervalId; (0,vue__WEBPACK_IMPORTED_MODULE_0__.onMounted)(() => { intervalId = setInterval(() => { - if (window.performance && window.performance.memory) { - memoryUsage.value = (window.performance.memory.usedJSHeapSize / (1024 * 1024)).toFixed(2); - } - }, 2000); + updateMemoryUsage(); + }, 3000); // 每3秒更新一次 }); (0,vue__WEBPACK_IMPORTED_MODULE_0__.onUnmounted)(() => { - clearInterval(intervalId); + 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; @@ -512,7 +1795,8 @@ __webpack_require__.r(__webpack_exports__); userInfo, errorCount, appVersion, - memoryUsage + memoryUsage, + systemMemoryUsage }; } }); @@ -731,6 +2015,206 @@ __webpack_require__.r(__webpack_exports__); }); +/***/ }), + +/***/ "./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/UserManagement.vue?vue&type=script&lang=js": +/*!**********************************************************************************************************************************!*\ + !*** ./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/views/UserManagement.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 _services_userService__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @/services/userService */ "./src/renderer/services/userService.js"); + + + + +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ + name: 'UserManagement', + setup() { + const users = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + const roles = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)([]) + const loading = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const currentPage = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(1) + const pageSize = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(10) + const total = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(0) + const searchQuery = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)('') + const statusFilter = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)('') + const roleFilter = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)('') + + const showAddUserModal = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const showEditUserModal = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(false) + const editingUser = (0,vue__WEBPACK_IMPORTED_MODULE_0__.ref)(null) + + const userForm = (0,vue__WEBPACK_IMPORTED_MODULE_0__.reactive)({ + username: '', + password: '', + email: '', + phone: '', + status: 1, + roleIds: [] + }) + + const totalPages = (0,vue__WEBPACK_IMPORTED_MODULE_0__.computed)(() => Math.ceil(total.value / pageSize.value)) + + const filteredUsers = (0,vue__WEBPACK_IMPORTED_MODULE_0__.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 _services_userService__WEBPACK_IMPORTED_MODULE_1__.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 _services_userService__WEBPACK_IMPORTED_MODULE_1__.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 _services_userService__WEBPACK_IMPORTED_MODULE_1__.userService.updateUser(editingUser.value.id, userForm) + } else { + await _services_userService__WEBPACK_IMPORTED_MODULE_1__.userService.createUser(userForm) + } + closeModal() + loadUsers() + } catch (error) { + console.error('保存用户失败:', error) + } + } + + const deleteUser = async (userId) => { + if (confirm('确定要删除这个用户吗?')) { + try { + await _services_userService__WEBPACK_IMPORTED_MODULE_1__.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') + } + + ;(0,vue__WEBPACK_IMPORTED_MODULE_0__.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 + } + } +}); + + /***/ }), /***/ "./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[2]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/renderer/App.vue?vue&type=template&id=2fa9d48e": @@ -761,6 +2245,305 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { /***/ }), +/***/ "./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/MainLayout.vue?vue&type=template&id=a4b9ee6e&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/MainLayout.vue?vue&type=template&id=a4b9ee6e&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: "main-layout" } +const _hoisted_2 = { class: "header" } +const _hoisted_3 = { class: "header-left" } +const _hoisted_4 = { class: "breadcrumb" } +const _hoisted_5 = { + key: 0, + class: "separator" +} +const _hoisted_6 = { class: "breadcrumb-item" } +const _hoisted_7 = { class: "header-right" } +const _hoisted_8 = { class: "message-center" } +const _hoisted_9 = { + key: 0, + class: "badge" +} +const _hoisted_10 = { + key: 0, + class: "message-panel" +} +const _hoisted_11 = { class: "message-header" } +const _hoisted_12 = { class: "message-list" } +const _hoisted_13 = { class: "message-icon" } +const _hoisted_14 = { class: "message-content" } +const _hoisted_15 = { class: "message-title" } +const _hoisted_16 = { class: "message-time" } +const _hoisted_17 = ["onClick"] +const _hoisted_18 = { class: "user-info" } +const _hoisted_19 = ["src", "alt"] +const _hoisted_20 = { + key: 1, + class: "avatar-placeholder" +} +const _hoisted_21 = { + key: 0, + class: "user-menu" +} +const _hoisted_22 = { class: "user-menu-header" } +const _hoisted_23 = { class: "user-details" } +const _hoisted_24 = { class: "user-name" } +const _hoisted_25 = { class: "user-email" } +const _hoisted_26 = { class: "user-menu-items" } +const _hoisted_27 = { class: "main-content" } +const _hoisted_28 = { class: "sidebar" } +const _hoisted_29 = { class: "sidebar-nav" } +const _hoisted_30 = { class: "nav-section" } +const _hoisted_31 = { class: "nav-list" } +const _hoisted_32 = { class: "nav-icon" } +const _hoisted_33 = { class: "nav-text" } +const _hoisted_34 = ["onClick"] +const _hoisted_35 = { class: "nav-section" } +const _hoisted_36 = { class: "nav-list" } +const _hoisted_37 = { class: "nav-icon" } +const _hoisted_38 = { class: "nav-text" } +const _hoisted_39 = ["onClick"] +const _hoisted_40 = { class: "content-area" } +const _hoisted_41 = { class: "content-tabs" } +const _hoisted_42 = { class: "tab-list" } +const _hoisted_43 = ["onClick"] +const _hoisted_44 = { class: "tab-title" } +const _hoisted_45 = ["onClick"] +const _hoisted_46 = { class: "tab-actions" } +const _hoisted_47 = { class: "content-body" } + +function render(_ctx, _cache, $props, $setup, $data, $options) { + const _component_router_link = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("router-link") + const _component_router_view = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("router-view") + + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_1, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 顶部导航栏 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("header", _hoisted_2, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, [ + _cache[7] || (_cache[7] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "logo" }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h1", null, "🚀 GoFaster") + ], -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.breadcrumbs, (item, index) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", { key: index }, [ + (index > 0) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", _hoisted_5, "/")) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_6, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item), 1 /* TEXT */) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 消息通知 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "message-btn", + onClick: _cache[0] || (_cache[0] = (...args) => ($setup.toggleMessagePanel && $setup.toggleMessagePanel(...args))) + }, [ + _cache[8] || (_cache[8] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "📢", -1 /* CACHED */)), + ($setup.unreadCount > 0) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", _hoisted_9, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.unreadCount), 1 /* TEXT */)) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 消息面板 "), + ($setup.showMessagePanel) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_10, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_11, [ + _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "消息", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "close-btn", + onClick: _cache[1] || (_cache[1] = $event => ($setup.showMessagePanel = false)) + }, "×") + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_12, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.messages, (message) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { + key: message.id, + class: "message-item" + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_13, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(message.icon), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_14, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_15, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(message.title), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.formatTime(message.time)), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "mark-read-btn", + onClick: $event => ($setup.markAsRead(message.id)) + }, " ✓ ", 8 /* PROPS */, _hoisted_17) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ])) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 用户信息 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { + class: "user-avatar", + onClick: _cache[2] || (_cache[2] = (...args) => ($setup.toggleUserMenu && $setup.toggleUserMenu(...args))) + }, [ + ($setup.currentUser.avatar) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("img", { + key: 0, + src: $setup.currentUser.avatar, + alt: $setup.currentUser.name + }, null, 8 /* PROPS */, _hoisted_19)) + : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", _hoisted_20, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentUser.name?.charAt(0) || 'U'), 1 /* TEXT */)) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 用户菜单 "), + ($setup.showUserMenu) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_21, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_22, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_23, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_24, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentUser.name || '用户'), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_25, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentUser.email || 'user@example.com'), 1 /* TEXT */) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_26, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "menu-item", + onClick: _cache[3] || (_cache[3] = (...args) => ($setup.openProfile && $setup.openProfile(...args))) + }, _cache[10] || (_cache[10] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "👤", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 个人资料 ", -1 /* CACHED */) + ])), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "menu-item", + onClick: _cache[4] || (_cache[4] = (...args) => ($setup.openSettings && $setup.openSettings(...args))) + }, _cache[11] || (_cache[11] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "⚙️", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 设置 ", -1 /* CACHED */) + ])), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "menu-item", + onClick: _cache[5] || (_cache[5] = (...args) => ($setup.logout && $setup.logout(...args))) + }, _cache[12] || (_cache[12] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "🚪", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 退出登录 ", -1 /* CACHED */) + ])) + ]) + ])) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ]) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 主要内容区域 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_27, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 左侧菜单 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("aside", _hoisted_28, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("nav", _hoisted_29, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_30, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("ul", _hoisted_31, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.mainMenuItems, (item) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("li", { + key: item.id + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { + to: item.path, + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["nav-item", { active: $setup.currentRoute === item.path }]), + onClick: $event => ($setup.handleMenuClick(item)) + }, { + default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", _hoisted_32, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item.icon), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_33, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item.name), 1 /* TEXT */), + (item.favorite) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("button", { + key: 0, + class: "favorite-btn", + onClick: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => ($setup.toggleFavorite(item.id)), ["stop"]) + }, " ⭐ ", 8 /* PROPS */, _hoisted_34)) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ]), + _: 2 /* DYNAMIC */ + }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["to", "class", "onClick"]) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_35, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("ul", _hoisted_36, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.favoriteMenuItems, (item) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("li", { + key: item.id + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_link, { + to: item.path, + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(["nav-item", { active: $setup.currentRoute === item.path }]) + }, { + default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", _hoisted_37, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item.icon), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_38, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item.name), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "favorite-btn active", + onClick: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => ($setup.toggleFavorite(item.id)), ["stop"]) + }, " ⭐ ", 8 /* PROPS */, _hoisted_39) + ]), + _: 2 /* DYNAMIC */ + }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["to", "class"]) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ]) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 右侧内容区域 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_40, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 内容选项卡 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_41, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_42, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.openTabs, (tab) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { + key: tab.id, + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['tab-item', { active: tab.id === $setup.currentTab }]), + onClick: $event => ($setup.switchTab(tab.id)) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_44, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(tab.title), 1 /* TEXT */), + ($setup.openTabs.length > 1) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("button", { + key: 0, + class: "tab-close", + onClick: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => ($setup.closeTab(tab.id)), ["stop"]), + title: "关闭标签页" + }, " × ", 8 /* PROPS */, _hoisted_45)) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ], 10 /* CLASS, PROPS */, _hoisted_43)) + }), 128 /* KEYED_FRAGMENT */)) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_46, [ + ($setup.openTabs.length > 1) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("button", { + key: 0, + class: "close-all-btn", + onClick: _cache[6] || (_cache[6] = (...args) => ($setup.closeAllTabs && $setup.closeAllTabs(...args))), + title: "关闭所有标签页" + }, _cache[13] || (_cache[13] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "close-all-icon" }, "⊗", -1 /* CACHED */) + ]))) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 功能内容区 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_47, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_router_view) + ]) + ]) + ]) + ])) +} + +/***/ }), + /***/ "./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/StatusBar.vue?vue&type=template&id=126429ab&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/StatusBar.vue?vue&type=template&id=126429ab&scoped=true ***! @@ -776,19 +2559,29 @@ __webpack_require__.r(__webpack_exports__); const _hoisted_1 = { class: "status-bar" } -const _hoisted_2 = { class: "status-item" } +const _hoisted_2 = { class: "status-left" } const _hoisted_3 = { class: "status-item" } const _hoisted_4 = { class: "status-item" } -const _hoisted_5 = { class: "status-item" } +const _hoisted_5 = { class: "status-right" } const _hoisted_6 = { class: "status-item" } +const _hoisted_7 = { class: "status-item" } +const _hoisted_8 = { class: "status-item" } +const _hoisted_9 = { class: "status-item" } 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, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, "服务地址: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.serverUrl), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, "用户: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.userInfo), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, "最近错误: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errorCount), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_5, "版本: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.appVersion), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_6, "内存: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.memoryUsage) + " MB", 1 /* TEXT */) + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 左侧:用户和版本信息 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, "用户: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.userInfo), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, "版本: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.appVersion), 1 /* TEXT */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 右侧:其他状态信息 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_5, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_6, "服务地址: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.serverUrl), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, "最近错误: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.errorCount), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, "内存: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.memoryUsage) + " MB", 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, "系统内存: " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.systemMemoryUsage), 1 /* TEXT */) + ]) ])) } @@ -863,74 +2656,42 @@ __webpack_require__.r(__webpack_exports__); const _hoisted_1 = { class: "home" } -const _hoisted_2 = { class: "header" } -const _hoisted_3 = { class: "user-info" } -const _hoisted_4 = { class: "username" } -const _hoisted_5 = { class: "main-content" } -const _hoisted_6 = { class: "welcome-section" } -const _hoisted_7 = { class: "welcome-card" } -const _hoisted_8 = { class: "subtitle" } -const _hoisted_9 = { class: "quick-stats" } -const _hoisted_10 = { class: "stat-item" } -const _hoisted_11 = { class: "stat-number" } -const _hoisted_12 = { class: "stat-item" } -const _hoisted_13 = { class: "stat-number" } -const _hoisted_14 = { class: "stat-item" } -const _hoisted_15 = { class: "stat-number" } -const _hoisted_16 = { class: "features-section" } -const _hoisted_17 = { class: "todo-section" } -const _hoisted_18 = { class: "section-header" } -const _hoisted_19 = { class: "todo-list" } -const _hoisted_20 = ["checked", "onChange"] -const _hoisted_21 = { class: "todo-text" } -const _hoisted_22 = { class: "todo-date" } -const _hoisted_23 = ["onClick"] -const _hoisted_24 = { class: "recent-section" } -const _hoisted_25 = { class: "activity-list" } -const _hoisted_26 = { class: "activity-icon" } -const _hoisted_27 = { class: "activity-content" } -const _hoisted_28 = { class: "activity-title" } -const _hoisted_29 = { class: "activity-time" } +const _hoisted_2 = { class: "main-content" } +const _hoisted_3 = { class: "welcome-section" } +const _hoisted_4 = { class: "welcome-card" } +const _hoisted_5 = { class: "subtitle" } +const _hoisted_6 = { class: "features-section" } +const _hoisted_7 = { class: "todo-section" } +const _hoisted_8 = { class: "section-header" } +const _hoisted_9 = { class: "todo-list" } +const _hoisted_10 = ["checked", "onChange"] +const _hoisted_11 = { class: "todo-text" } +const _hoisted_12 = { class: "todo-date" } +const _hoisted_13 = ["onClick"] +const _hoisted_14 = { class: "recent-section" } +const _hoisted_15 = { class: "activity-list" } +const _hoisted_16 = { class: "activity-icon" } +const _hoisted_17 = { class: "activity-content" } +const _hoisted_18 = { class: "activity-title" } +const _hoisted_19 = { class: "activity-time" } 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, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 顶部导航栏 "), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("header", _hoisted_2, [ - _cache[6] || (_cache[6] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createStaticVNode)("

🚀 GoFaster

", 2)), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, [ - _cache[5] || (_cache[5] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "avatar" }, "👤", -1 /* CACHED */)), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_4, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.userInfo.name), 1 /* TEXT */) - ]) - ]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 主要内容区域 "), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("main", _hoisted_5, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("main", _hoisted_2, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 欢迎区域 "), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_6, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_3, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h2", null, "欢迎回来," + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.userInfo.name) + "!", 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", _hoisted_8, "今天是 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.currentDate) + ",您有 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.todoList.length) + " 个待办事项", 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_10, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_11, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.stats.totalTests), 1 /* TEXT */), - _cache[7] || (_cache[7] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "stat-label" }, "总测试次数", -1 /* CACHED */)) - ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_12, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_13, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.stats.avgSpeed), 1 /* TEXT */), - _cache[8] || (_cache[8] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "stat-label" }, "平均速度", -1 /* CACHED */)) - ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_14, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_15, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.stats.bestSpeed), 1 /* TEXT */), - _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { class: "stat-label" }, "最佳速度", -1 /* CACHED */)) - ]) - ]) + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", _hoisted_5, "今天是 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.currentDate) + ",您有 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.todoList.length) + " 个待办事项", 1 /* TEXT */) ]) ]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 功能卡片区域 "), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_16, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_6, [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-card", onClick: _cache[0] || (_cache[0] = (...args) => ($options.startSpeedTest && $options.startSpeedTest(...args))) - }, _cache[10] || (_cache[10] = [ + }, _cache[5] || (_cache[5] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-icon" }, "⚡", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "开始速度测试", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, "测试您的网络连接速度", -1 /* CACHED */) @@ -938,7 +2699,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-card", onClick: _cache[1] || (_cache[1] = (...args) => ($options.viewHistory && $options.viewHistory(...args))) - }, _cache[11] || (_cache[11] = [ + }, _cache[6] || (_cache[6] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-icon" }, "📊", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "查看历史", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, "查看历史测试记录", -1 /* CACHED */) @@ -946,7 +2707,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-card", onClick: _cache[2] || (_cache[2] = (...args) => ($options.openSettings && $options.openSettings(...args))) - }, _cache[12] || (_cache[12] = [ + }, _cache[7] || (_cache[7] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-icon" }, "⚙️", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "设置", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, "配置应用参数", -1 /* CACHED */) @@ -954,22 +2715,22 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-card", onClick: _cache[3] || (_cache[3] = (...args) => ($options.exportData && $options.exportData(...args))) - }, _cache[13] || (_cache[13] = [ + }, _cache[8] || (_cache[8] = [ (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { class: "feature-icon" }, "📤", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "导出数据", -1 /* CACHED */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("p", null, "导出测试数据", -1 /* CACHED */) ])) ]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 待办事项区域 "), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_17, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, [ - _cache[14] || (_cache[14] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "📝 待办事项", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_7, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, [ + _cache[9] || (_cache[9] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "📝 待办事项", -1 /* CACHED */)), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "add-todo-btn", onClick: _cache[4] || (_cache[4] = (...args) => ($options.addTodo && $options.addTodo(...args))) }, "+ 添加") ]), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_19, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, [ ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($data.todoList, (todo) => { return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { key: todo.id, @@ -979,30 +2740,30 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { type: "checkbox", checked: todo.completed, onChange: $event => ($options.toggleTodo(todo.id)) - }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_20), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_21, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(todo.text), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_22, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(todo.date), 1 /* TEXT */), + }, null, 40 /* PROPS, NEED_HYDRATION */, _hoisted_10), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_11, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(todo.text), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_12, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(todo.date), 1 /* TEXT */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { class: "delete-btn", onClick: $event => ($options.deleteTodo(todo.id)) - }, "🗑️", 8 /* PROPS */, _hoisted_23) + }, "🗑️", 8 /* PROPS */, _hoisted_13) ], 2 /* CLASS */)) }), 128 /* KEYED_FRAGMENT */)) ]) ]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 最近活动区域 "), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_24, [ - _cache[15] || (_cache[15] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "🕒 最近活动", -1 /* CACHED */)), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_25, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("section", _hoisted_14, [ + _cache[10] || (_cache[10] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, "🕒 最近活动", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_15, [ ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($data.recentActivities, (activity) => { return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { key: activity.id, class: "activity-item" }, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_26, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(activity.icon), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_27, [ - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_28, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(activity.title), 1 /* TEXT */), - (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_29, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(activity.time), 1 /* TEXT */) + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(activity.icon), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_17, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(activity.title), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_19, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(activity.time), 1 /* TEXT */) ]) ])) }), 128 /* KEYED_FRAGMENT */)) @@ -1014,6 +2775,295 @@ function render(_ctx, _cache, $props, $setup, $data, $options) { /***/ }), +/***/ "./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/UserManagement.vue?vue&type=template&id=7338cb92&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/UserManagement.vue?vue&type=template&id=7338cb92&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: "user-management" } +const _hoisted_2 = { class: "page-header" } +const _hoisted_3 = { class: "search-bar" } +const _hoisted_4 = { class: "search-input" } +const _hoisted_5 = { class: "filters" } +const _hoisted_6 = ["value"] +const _hoisted_7 = { class: "user-table" } +const _hoisted_8 = { class: "role-tags" } +const _hoisted_9 = { class: "actions" } +const _hoisted_10 = ["onClick"] +const _hoisted_11 = ["onClick"] +const _hoisted_12 = { class: "pagination" } +const _hoisted_13 = ["disabled"] +const _hoisted_14 = { class: "page-info" } +const _hoisted_15 = ["disabled"] +const _hoisted_16 = { class: "modal-header" } +const _hoisted_17 = { class: "modal-body" } +const _hoisted_18 = { class: "form-group" } +const _hoisted_19 = ["disabled"] +const _hoisted_20 = { + key: 0, + class: "form-group" +} +const _hoisted_21 = { class: "form-group" } +const _hoisted_22 = { class: "form-group" } +const _hoisted_23 = { class: "form-group" } +const _hoisted_24 = { class: "form-group" } +const _hoisted_25 = { class: "role-checkboxes" } +const _hoisted_26 = ["value"] +const _hoisted_27 = { class: "form-actions" } +const _hoisted_28 = { + type: "submit", + class: "btn btn-primary" +} + +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, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_2, [ + _cache[21] || (_cache[21] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h2", null, "用户管理", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-primary", + onClick: _cache[0] || (_cache[0] = $event => ($setup.showAddUserModal = true)) + }, _cache[20] || (_cache[20] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "+", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" 添加用户 ", -1 /* CACHED */) + ])) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 搜索和筛选 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_3, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_4, [ + _cache[22] || (_cache[22] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "🔍", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", { + "onUpdate:modelValue": _cache[1] || (_cache[1] = $event => (($setup.searchQuery) = $event)), + type: "text", + placeholder: "搜索用户名、邮箱或手机号...", + onInput: _cache[2] || (_cache[2] = (...args) => ($setup.handleSearch && $setup.handleSearch(...args))) + }, null, 544 /* NEED_HYDRATION, NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.searchQuery] + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_5, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("select", { + "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => (($setup.statusFilter) = $event)), + onChange: _cache[4] || (_cache[4] = (...args) => ($setup.handleFilter && $setup.handleFilter(...args))) + }, _cache[23] || (_cache[23] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "" }, "全部状态", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "1" }, "正常", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "2" }, "禁用", -1 /* CACHED */) + ]), 544 /* NEED_HYDRATION, NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelSelect, $setup.statusFilter] + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("select", { + "onUpdate:modelValue": _cache[5] || (_cache[5] = $event => (($setup.roleFilter) = $event)), + onChange: _cache[6] || (_cache[6] = (...args) => ($setup.handleFilter && $setup.handleFilter(...args))) + }, [ + _cache[24] || (_cache[24] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "" }, "全部角色", -1 /* CACHED */)), + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.roles, (role) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("option", { + key: role.id, + value: role.id + }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(role.name), 9 /* TEXT, PROPS */, _hoisted_6)) + }), 128 /* KEYED_FRAGMENT */)) + ], 544 /* NEED_HYDRATION, NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelSelect, $setup.roleFilter] + ]) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 用户列表 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_7, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("table", null, [ + _cache[27] || (_cache[27] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("thead", null, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("tr", null, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "ID"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "用户名"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "邮箱"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "手机号"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "状态"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "角色"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "创建时间"), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("th", null, "操作") + ]) + ], -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("tbody", null, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.filteredUsers, (user) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("tr", { + key: user.id + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(user.id), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(user.username), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(user.email), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(user.phone), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", { + class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['status-badge', user.status === 1 ? 'active' : 'inactive']) + }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(user.status === 1 ? '正常' : '禁用'), 3 /* TEXT, CLASS */) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_8, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)(user.roles, (role) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("span", { + key: role.id, + class: "role-tag" + }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(role.name), 1 /* TEXT */)) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.formatDate(user.created_at)), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("td", null, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_9, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-info", + onClick: $event => ($setup.editUser(user)) + }, [...(_cache[25] || (_cache[25] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "✏️", -1 /* CACHED */) + ]))], 8 /* PROPS */, _hoisted_10), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "btn btn-sm btn-danger", + onClick: $event => ($setup.deleteUser(user.id)) + }, [...(_cache[26] || (_cache[26] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("i", { class: "icon" }, "🗑️", -1 /* CACHED */) + ]))], 8 /* PROPS */, _hoisted_11) + ]) + ]) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 分页 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_12, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + disabled: $setup.currentPage === 1, + onClick: _cache[7] || (_cache[7] = $event => ($setup.changePage($setup.currentPage - 1))), + class: "btn btn-sm" + }, " 上一页 ", 8 /* PROPS */, _hoisted_13), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("span", _hoisted_14, " 第 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.currentPage) + " 页,共 " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.totalPages) + " 页 ", 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + disabled: $setup.currentPage === $setup.totalPages, + onClick: _cache[8] || (_cache[8] = $event => ($setup.changePage($setup.currentPage + 1))), + class: "btn btn-sm" + }, " 下一页 ", 8 /* PROPS */, _hoisted_15) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(" 添加/编辑用户模态框 "), + ($setup.showAddUserModal || $setup.showEditUserModal) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", { + key: 0, + class: "modal-overlay", + onClick: _cache[19] || (_cache[19] = (...args) => ($setup.closeModal && $setup.closeModal(...args))) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", { + class: "modal", + onClick: _cache[18] || (_cache[18] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)(() => {}, ["stop"])) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_16, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("h3", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.showEditUserModal ? '编辑用户' : '添加用户'), 1 /* TEXT */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + class: "close-btn", + onClick: _cache[9] || (_cache[9] = (...args) => ($setup.closeModal && $setup.closeModal(...args))) + }, "×") + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_17, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("form", { + onSubmit: _cache[17] || (_cache[17] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)((...args) => ($setup.submitUser && $setup.submitUser(...args)), ["prevent"])) + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_18, [ + _cache[28] || (_cache[28] = (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[10] || (_cache[10] = $event => (($setup.userForm.username) = $event)), + type: "text", + required: "", + disabled: $setup.showEditUserModal + }, null, 8 /* PROPS */, _hoisted_19), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.userForm.username] + ]) + ]), + (!$setup.showEditUserModal) + ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("div", _hoisted_20, [ + _cache[29] || (_cache[29] = (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.userForm.password) = $event)), + type: "password", + required: "" + }, null, 512 /* NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.userForm.password] + ]) + ])) + : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)("v-if", true), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_21, [ + _cache[30] || (_cache[30] = (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[12] || (_cache[12] = $event => (($setup.userForm.email) = $event)), + type: "email", + required: "" + }, null, 512 /* NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.userForm.email] + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_22, [ + _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[13] || (_cache[13] = $event => (($setup.userForm.phone) = $event)), + type: "tel" + }, null, 512 /* NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelText, $setup.userForm.phone] + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_23, [ + _cache[33] || (_cache[33] = (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.userForm.status) = $event)) + }, _cache[32] || (_cache[32] = [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "1" }, "正常", -1 /* CACHED */), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("option", { value: "2" }, "禁用", -1 /* CACHED */) + ]), 512 /* NEED_PATCH */), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelSelect, $setup.userForm.status] + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_24, [ + _cache[34] || (_cache[34] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("label", null, "角色", -1 /* CACHED */)), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_25, [ + ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($setup.roles, (role) => { + return ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)("label", { + key: role.id, + class: "checkbox-item" + }, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("input", { + type: "checkbox", + value: role.id, + "onUpdate:modelValue": _cache[15] || (_cache[15] = $event => (($setup.userForm.roleIds) = $event)) + }, null, 8 /* PROPS */, _hoisted_26), [ + [vue__WEBPACK_IMPORTED_MODULE_0__.vModelCheckbox, $setup.userForm.roleIds] + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)(" " + (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(role.name), 1 /* TEXT */) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ]), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("div", _hoisted_27, [ + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", { + type: "button", + class: "btn btn-secondary", + onClick: _cache[16] || (_cache[16] = (...args) => ($setup.closeModal && $setup.closeModal(...args))) + }, " 取消 "), + (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("button", _hoisted_28, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($setup.showEditUserModal ? '更新' : '创建'), 1 /* TEXT */) + ]) + ], 32 /* NEED_HYDRATION */) + ]) + ]) + ])) + : (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/App.vue?vue&type=style&index=0&id=2fa9d48e&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/App.vue?vue&type=style&index=0&id=2fa9d48e&lang=css ***! @@ -1047,6 +3097,39 @@ 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/MainLayout.vue?vue&type=style&index=0&id=a4b9ee6e&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/MainLayout.vue?vue&type=style&index=0&id=a4b9ee6e&scoped=true&lang=css ***! + \*************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +// style-loader: Adds some css to the DOM by adding a \ No newline at end of file diff --git a/gofaster/app/src/renderer/assets/fonts.css b/gofaster/app/src/renderer/assets/fonts.css new file mode 100644 index 0000000..ef0086c --- /dev/null +++ b/gofaster/app/src/renderer/assets/fonts.css @@ -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; + } +} diff --git a/gofaster/app/src/renderer/components/MainLayout.vue b/gofaster/app/src/renderer/components/MainLayout.vue new file mode 100644 index 0000000..93375ed --- /dev/null +++ b/gofaster/app/src/renderer/components/MainLayout.vue @@ -0,0 +1,971 @@ + + + + + diff --git a/gofaster/app/src/renderer/components/StatusBar.vue b/gofaster/app/src/renderer/components/StatusBar.vue index f0d0fe3..48d2d63 100644 --- a/gofaster/app/src/renderer/components/StatusBar.vue +++ b/gofaster/app/src/renderer/components/StatusBar.vue @@ -1,11 +1,19 @@ @@ -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(() => { - clearInterval(intervalId); + 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 { userInfo, errorCount, appVersion, - memoryUsage + memoryUsage, + systemMemoryUsage }; } }; @@ -54,22 +105,85 @@ export default { \ No newline at end of file diff --git a/gofaster/app/src/renderer/main.js b/gofaster/app/src/renderer/main.js index f8d01f1..1a3efbc 100644 --- a/gofaster/app/src/renderer/main.js +++ b/gofaster/app/src/renderer/main.js @@ -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对象的只读属性 diff --git a/gofaster/app/src/renderer/router/index.js b/gofaster/app/src/renderer/router/index.js index ea2be54..a4140f9 100644 --- a/gofaster/app/src/renderer/router/index.js +++ b/gofaster/app/src/renderer/router/index.js @@ -1,17 +1,40 @@ 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: '/', - name: 'Home', - component: Home - }, - { - path: '/history', - name: 'History', - component: History + component: MainLayout, + children: [ + { + path: '', + name: 'Home', + component: Home + }, + { + 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') + } + ] } ] diff --git a/gofaster/app/src/renderer/services/userService.js b/gofaster/app/src/renderer/services/userService.js new file mode 100644 index 0000000..f86c0f5 --- /dev/null +++ b/gofaster/app/src/renderer/services/userService.js @@ -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 diff --git a/gofaster/app/src/renderer/views/Home.vue b/gofaster/app/src/renderer/views/Home.vue index 8c7ba9f..d7128a2 100644 --- a/gofaster/app/src/renderer/views/Home.vue +++ b/gofaster/app/src/renderer/views/Home.vue @@ -1,23 +1,6 @@