From e17d5610f587f89611636238f8e14a92e36e7a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=88=90?= Date: Mon, 22 Dec 2025 16:26:59 +0800 Subject: [PATCH] 1 --- .vscode/launch.json | 15 +- app/App.vue | 278 ++++ app/api/apply_records.js | 76 + app/api/config.js | 102 ++ app/api/delivery_config.js | 45 + app/api/feedback.js | 64 + app/api/invite.js | 83 ++ app/components/ConsoleInfoPanel.vue | 464 +++++++ app/components/DeliverySettings.vue | 154 +++ app/components/DeliveryTrendChart.vue | 223 +++ app/components/PrimeVue/index.js | 39 + app/components/QrCodeSection.vue | 67 + app/components/SettingsDialog.vue | 141 ++ app/components/Sidebar.vue | 77 ++ app/components/StatsSection.vue | 45 + app/components/UpdateDialog.vue | 200 +++ app/components/UserInfoDialog.vue | 122 ++ app/components/UserMenu.vue | 270 ++++ app/css/index.less | 353 +++++ app/index.html | 31 + app/main.js | 45 + app/mixins/authMixin.js | 230 ++++ app/mixins/eventListenerMixin.js | 178 +++ app/mixins/logMixin.js | 48 + app/mixins/mqttMixin.js | 97 ++ app/mixins/platformMixin.js | 65 + app/mixins/qrCodeMixin.js | 220 +++ app/mixins/systemInfoMixin.js | 48 + app/mixins/taskMixin.js | 61 + app/mixins/updateMixin.js | 138 ++ app/package-lock.json | 1834 +++++++++++++++++++++++++ app/package.json | 25 + app/router/index.js | 99 ++ app/store/index.js | 49 + app/store/modules/app.js | 28 + app/store/modules/auth.js | 291 ++++ app/store/modules/config.js | 72 + app/store/modules/delivery.js | 165 +++ app/store/modules/log.js | 63 + app/store/modules/mqtt.js | 25 + app/store/modules/platform.js | 31 + app/store/modules/qrCode.js | 42 + app/store/modules/system.js | 41 + app/store/modules/task.js | 81 ++ app/store/modules/update.js | 55 + app/utils/api.js | 409 ++++++ app/views/ConsolePage.vue | 1328 ++++++++++++++++++ app/views/DeliveryPage.vue | 671 +++++++++ app/views/FeedbackPage.vue | 643 +++++++++ app/views/InvitePage.vue | 733 ++++++++++ app/views/LogPage.vue | 163 +++ app/views/LoginPage.vue | 361 +++++ app/views/PurchasePage.vue | 501 +++++++ app/vite.config.js | 49 + 54 files changed, 11735 insertions(+), 3 deletions(-) create mode 100644 app/App.vue create mode 100644 app/api/apply_records.js create mode 100644 app/api/config.js create mode 100644 app/api/delivery_config.js create mode 100644 app/api/feedback.js create mode 100644 app/api/invite.js create mode 100644 app/components/ConsoleInfoPanel.vue create mode 100644 app/components/DeliverySettings.vue create mode 100644 app/components/DeliveryTrendChart.vue create mode 100644 app/components/PrimeVue/index.js create mode 100644 app/components/QrCodeSection.vue create mode 100644 app/components/SettingsDialog.vue create mode 100644 app/components/Sidebar.vue create mode 100644 app/components/StatsSection.vue create mode 100644 app/components/UpdateDialog.vue create mode 100644 app/components/UserInfoDialog.vue create mode 100644 app/components/UserMenu.vue create mode 100644 app/css/index.less create mode 100644 app/index.html create mode 100644 app/main.js create mode 100644 app/mixins/authMixin.js create mode 100644 app/mixins/eventListenerMixin.js create mode 100644 app/mixins/logMixin.js create mode 100644 app/mixins/mqttMixin.js create mode 100644 app/mixins/platformMixin.js create mode 100644 app/mixins/qrCodeMixin.js create mode 100644 app/mixins/systemInfoMixin.js create mode 100644 app/mixins/taskMixin.js create mode 100644 app/mixins/updateMixin.js create mode 100644 app/package-lock.json create mode 100644 app/package.json create mode 100644 app/router/index.js create mode 100644 app/store/index.js create mode 100644 app/store/modules/app.js create mode 100644 app/store/modules/auth.js create mode 100644 app/store/modules/config.js create mode 100644 app/store/modules/delivery.js create mode 100644 app/store/modules/log.js create mode 100644 app/store/modules/mqtt.js create mode 100644 app/store/modules/platform.js create mode 100644 app/store/modules/qrCode.js create mode 100644 app/store/modules/system.js create mode 100644 app/store/modules/task.js create mode 100644 app/store/modules/update.js create mode 100644 app/utils/api.js create mode 100644 app/views/ConsolePage.vue create mode 100644 app/views/DeliveryPage.vue create mode 100644 app/views/FeedbackPage.vue create mode 100644 app/views/InvitePage.vue create mode 100644 app/views/LogPage.vue create mode 100644 app/views/LoginPage.vue create mode 100644 app/views/PurchasePage.vue create mode 100644 app/vite.config.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 00796f7..b369603 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "request": "launch", "name": "后端调试", "skipFiles": [ - "/**", + "/**" ], "resolveSourceMapLocations":[ "${workspaceFolder}/", @@ -28,7 +28,16 @@ "npm run dev" ], "cwd": "${workspaceFolder}/admin" - }, - + } + ], + "compounds": [ + { + "name": "启动全部(后端+前端)", + "configurations": [ + "后端调试", + "后端前端" + ], + "stopAll": true + } ] } \ No newline at end of file diff --git a/app/App.vue b/app/App.vue new file mode 100644 index 0000000..ab99518 --- /dev/null +++ b/app/App.vue @@ -0,0 +1,278 @@ + + + + + + diff --git a/app/api/apply_records.js b/app/api/apply_records.js new file mode 100644 index 0000000..941ec41 --- /dev/null +++ b/app/api/apply_records.js @@ -0,0 +1,76 @@ +/** + * 投递记录 API 服务 + * 封装投递记录相关的API调用 + */ + +import apiClient from '../utils/api.js'; + +class ApplyRecordsAPI { + /** + * 获取投递记录列表 + * @param {Object} params - 查询参数 + * @param {Object} params.seachOption - 搜索条件 + * @param {Object} params.pageOption - 分页选项 + * @returns {Promise} + */ + async getList(params = {}) { + try { + const result = await apiClient.post('/apply/list', params); + return result; + } catch (error) { + console.error('获取投递记录列表失败:', error); + throw error; + } + } + + /** + * 获取投递统计 + * @param {String} snCode - 设备SN码(可选) + * @returns {Promise} + */ + async getStatistics(snCode = null) { + try { + const params = snCode ? { sn_code: snCode } : {}; + const result = await apiClient.get('/apply/statistics', params); + return result; + } catch (error) { + console.error('获取投递统计失败:', error); + throw error; + } + } + + /** + * 获取近7天投递趋势数据 + * @param {String} snCode - 设备SN码(可选) + * @returns {Promise} + */ + async getTrendData(snCode = null) { + try { + const params = snCode ? { sn_code: snCode } : {}; + const result = await apiClient.get('/apply/trend', params); + return result; + } catch (error) { + console.error('获取投递趋势数据失败:', error); + throw error; + } + } + + /** + * 获取投递记录详情 + * @param {String|Number} recordId - 投递记录ID(可以是 id 或 applyId) + * @returns {Promise} + */ + async getDetail(recordId) { + try { + // 使用 id 参数名(数据库主键字段) + const result = await apiClient.get('/apply/detail', { id: recordId }); + return result; + } catch (error) { + console.error('获取投递记录详情失败:', error); + throw error; + } + } +} + +export default new ApplyRecordsAPI(); + diff --git a/app/api/config.js b/app/api/config.js new file mode 100644 index 0000000..6de1675 --- /dev/null +++ b/app/api/config.js @@ -0,0 +1,102 @@ +/** + * 系统配置 API 服务 + * 封装配置相关的API调用 + */ + +import apiClient from '../utils/api.js'; + +// API 基础 URL(用于拼接图片路径,去掉 /api 后缀) +const getBaseURL = () => { + const apiUrl = 'http://localhost:9097/api'; + // 如果包含 /api,去掉它 + return apiUrl.replace(/\/api$/, ''); +}; + +class ConfigAPI { + /** + * 获取配置 + * @param {String|Array} configKeys - 配置键(单个字符串或数组) + * @returns {Promise} + */ + async getConfig(configKeys) { + try { + let params = {}; + + // 支持单个字符串或数组 + if (Array.isArray(configKeys)) { + params.configKeys = configKeys.join(','); + } else if (typeof configKeys === 'string') { + params.configKey = configKeys; + } else { + throw new Error('配置键格式错误'); + } + + const result = await apiClient.get('/config/get', params); + return result; + } catch (error) { + console.error('获取配置失败:', error); + throw error; + } + } + + /** + * 获取微信相关配置 + * @returns {Promise} { wechatNumber, wechatQRCode } + */ + async getWechatConfig() { + try { + const result = await this.getConfig(['wx_num', 'wx_img']); + + if (result && result.code === 0) { + let qrcodeUrl = result.data.wx_img || ''; + + // 如果二维码是相对路径,转换为完整URL + if (qrcodeUrl && !qrcodeUrl.startsWith('http://') && !qrcodeUrl.startsWith('https://') && !qrcodeUrl.startsWith('data:')) { + const baseURL = getBaseURL(); + // 如果以 / 开头,拼接服务器基础URL + if (qrcodeUrl.startsWith('/')) { + qrcodeUrl = baseURL + qrcodeUrl; + } else { + // 如果不是以 / 开头,可能是文件路径,需要拼接 + qrcodeUrl = baseURL + '/' + qrcodeUrl; + } + } + + return { + wechatNumber: result.data.wx_num || '', + wechatQRCode: qrcodeUrl + }; + } + return { + wechatNumber: '', + wechatQRCode: '' + }; + } catch (error) { + console.error('获取微信配置失败:', error); + return { + wechatNumber: '', + wechatQRCode: '' + }; + } + } + + /** + * 获取价格套餐列表 + * @returns {Promise} 价格套餐列表 + */ + async getPricingPlans() { + try { + const result = await apiClient.get('/config/pricing-plans'); + if (result && result.code === 0) { + return result.data || []; + } + return []; + } catch (error) { + console.error('获取价格套餐失败:', error); + return []; + } + } +} + +export default new ConfigAPI(); + diff --git a/app/api/delivery_config.js b/app/api/delivery_config.js new file mode 100644 index 0000000..998138b --- /dev/null +++ b/app/api/delivery_config.js @@ -0,0 +1,45 @@ +/** + * 投递配置 API 服务 + * 封装投递配置相关的API调用 + */ + +import apiClient from '../utils/api.js'; + +class DeliveryConfigAPI { + /** + * 获取投递配置 + * @param {String} snCode - 设备SN码 + * @returns {Promise} + */ + async getConfig(snCode) { + try { + const result = await apiClient.post('/user/delivery-config/get', { sn_code: snCode }); + return result; + } catch (error) { + console.error('获取投递配置失败:', error); + throw error; + } + } + + /** + * 保存投递配置 + * @param {String} snCode - 设备SN码 + * @param {Object} deliverConfig - 投递配置对象 + * @returns {Promise} + */ + async saveConfig(snCode, deliverConfig) { + try { + const result = await apiClient.post('/user/delivery-config/save', { + sn_code: snCode, + deliver_config: deliverConfig + }); + return result; + } catch (error) { + console.error('保存投递配置失败:', error); + throw error; + } + } +} + +export default new DeliveryConfigAPI(); + diff --git a/app/api/feedback.js b/app/api/feedback.js new file mode 100644 index 0000000..6668095 --- /dev/null +++ b/app/api/feedback.js @@ -0,0 +1,64 @@ +/** + * 意见反馈 API 服务 + * 封装意见反馈相关的API调用 + */ + +import apiClient from '../utils/api.js'; +import { mapState } from 'vuex'; + +class FeedbackAPI { + /** + * 提交反馈 + * @param {Object} data - 反馈数据 + * @param {String} data.type - 反馈类型 + * @param {String} data.content - 反馈内容 + * @param {String} data.contact - 联系方式(可选) + * @param {String} data.sn_code - 设备SN码(可选,从 store 获取) + * @returns {Promise} + */ + async submit(data) { + try { + const result = await apiClient.post('/feedback/submit', data); + return result; + } catch (error) { + console.error('提交反馈失败:', error); + throw error; + } + } + + /** + * 获取反馈列表 + * @param {Object} params - 查询参数 + * @param {Number} params.page - 页码 + * @param {Number} params.pageSize - 每页数量 + * @param {String} params.sn_code - 设备SN码(可选,从 store 获取) + * @returns {Promise} + */ + async getList(params = {}) { + try { + const result = await apiClient.post('/feedback/list', params); + return result; + } catch (error) { + console.error('获取反馈列表失败:', error); + throw error; + } + } + + /** + * 获取反馈详情 + * @param {String|Number} feedbackId - 反馈ID + * @returns {Promise} + */ + async getDetail(feedbackId) { + try { + const result = await apiClient.get('/feedback/detail', { id: feedbackId }); + return result; + } catch (error) { + console.error('获取反馈详情失败:', error); + throw error; + } + } +} + +export default new FeedbackAPI(); + diff --git a/app/api/invite.js b/app/api/invite.js new file mode 100644 index 0000000..fd34d34 --- /dev/null +++ b/app/api/invite.js @@ -0,0 +1,83 @@ +/** + * 推广邀请 API 服务 + * 封装推广邀请相关的API调用 + */ + +import apiClient from '../utils/api.js'; + +class InviteAPI { + /** + * 获取邀请信息 + * @param {string} snCode 设备SN码 + * @returns {Promise} + */ + async getInviteInfo(snCode) { + try { + const result = await apiClient.post('/invite/info', { + sn_code: snCode + }); + return result; + } catch (error) { + console.error('获取邀请信息失败:', error); + throw error; + } + } + + /** + * 获取邀请统计 + * @param {string} snCode 设备SN码 + * @returns {Promise} + */ + async getStatistics(snCode) { + try { + const result = await apiClient.post('/invite/statistics', { + sn_code: snCode + }); + return result; + } catch (error) { + console.error('获取邀请统计失败:', error); + throw error; + } + } + + /** + * 生成邀请码 + * @param {string} snCode 设备SN码 + * @returns {Promise} + */ + async generateInviteCode(snCode) { + try { + const result = await apiClient.post('/invite/generate', { + sn_code: snCode + }); + return result; + } catch (error) { + console.error('生成邀请码失败:', error); + throw error; + } + } + + /** + * 获取邀请记录列表 + * @param {string} snCode 设备SN码 + * @param {Object} params 分页参数 + * @param {number} params.page 页码 + * @param {number} params.pageSize 每页数量 + * @returns {Promise} + */ + async getRecords(snCode, params = {}) { + try { + const result = await apiClient.post('/invite/records', { + sn_code: snCode, + page: params.page || 1, + pageSize: params.pageSize || 20 + }); + return result; + } catch (error) { + console.error('获取邀请记录列表失败:', error); + throw error; + } + } +} + +export default new InviteAPI(); diff --git a/app/components/ConsoleInfoPanel.vue b/app/components/ConsoleInfoPanel.vue new file mode 100644 index 0000000..6f7e718 --- /dev/null +++ b/app/components/ConsoleInfoPanel.vue @@ -0,0 +1,464 @@ + + + + + + diff --git a/app/components/DeliverySettings.vue b/app/components/DeliverySettings.vue new file mode 100644 index 0000000..a9b2c3d --- /dev/null +++ b/app/components/DeliverySettings.vue @@ -0,0 +1,154 @@ + + + + + + diff --git a/app/components/DeliveryTrendChart.vue b/app/components/DeliveryTrendChart.vue new file mode 100644 index 0000000..3b8142a --- /dev/null +++ b/app/components/DeliveryTrendChart.vue @@ -0,0 +1,223 @@ + + + + + + diff --git a/app/components/PrimeVue/index.js b/app/components/PrimeVue/index.js new file mode 100644 index 0000000..6e0daa3 --- /dev/null +++ b/app/components/PrimeVue/index.js @@ -0,0 +1,39 @@ +/** + * PrimeVue 组件统一导出 + * 方便统一管理和使用 + */ + +// 表单组件 +export { default as Button } from 'primevue/button'; +export { default as InputText } from 'primevue/inputtext'; +export { default as Password } from 'primevue/password'; +export { default as Textarea } from 'primevue/textarea'; +export { default as InputNumber } from 'primevue/inputnumber'; +export { default as Dropdown } from 'primevue/dropdown'; +export { default as Checkbox } from 'primevue/checkbox'; +export { default as InputSwitch } from 'primevue/inputswitch'; +export { default as Calendar } from 'primevue/calendar'; + +// 数据展示组件 +export { default as DataTable } from 'primevue/datatable'; +export { default as Column } from 'primevue/column'; +export { default as Card } from 'primevue/card'; +export { default as Tag } from 'primevue/tag'; +export { default as Badge } from 'primevue/badge'; + +// 对话框和覆盖层 +export { default as Dialog } from 'primevue/dialog'; +export { default as Toast } from 'primevue/toast'; +export { default as Message } from 'primevue/message'; + +// 分页和导航 +export { default as Paginator } from 'primevue/paginator'; + +// 进度和加载 +export { default as ProgressBar } from 'primevue/progressbar'; +export { default as ProgressSpinner } from 'primevue/progressspinner'; + +// 其他 +export { default as Divider } from 'primevue/divider'; +export { default as Panel } from 'primevue/panel'; + diff --git a/app/components/QrCodeSection.vue b/app/components/QrCodeSection.vue new file mode 100644 index 0000000..5516600 --- /dev/null +++ b/app/components/QrCodeSection.vue @@ -0,0 +1,67 @@ + + + + + + diff --git a/app/components/SettingsDialog.vue b/app/components/SettingsDialog.vue new file mode 100644 index 0000000..594d812 --- /dev/null +++ b/app/components/SettingsDialog.vue @@ -0,0 +1,141 @@ + + + + + + diff --git a/app/components/Sidebar.vue b/app/components/Sidebar.vue new file mode 100644 index 0000000..fb1f5c8 --- /dev/null +++ b/app/components/Sidebar.vue @@ -0,0 +1,77 @@ + + + + + + + diff --git a/app/components/StatsSection.vue b/app/components/StatsSection.vue new file mode 100644 index 0000000..fd3f730 --- /dev/null +++ b/app/components/StatsSection.vue @@ -0,0 +1,45 @@ + + + + + + diff --git a/app/components/UpdateDialog.vue b/app/components/UpdateDialog.vue new file mode 100644 index 0000000..f79896a --- /dev/null +++ b/app/components/UpdateDialog.vue @@ -0,0 +1,200 @@ + + + + + + diff --git a/app/components/UserInfoDialog.vue b/app/components/UserInfoDialog.vue new file mode 100644 index 0000000..da4082c --- /dev/null +++ b/app/components/UserInfoDialog.vue @@ -0,0 +1,122 @@ + + + + + + diff --git a/app/components/UserMenu.vue b/app/components/UserMenu.vue new file mode 100644 index 0000000..d3ed466 --- /dev/null +++ b/app/components/UserMenu.vue @@ -0,0 +1,270 @@ + + + + + + diff --git a/app/css/index.less b/app/css/index.less new file mode 100644 index 0000000..db94b89 --- /dev/null +++ b/app/css/index.less @@ -0,0 +1,353 @@ +// ============================================ +// 全局变量定义 +// ============================================ +@primary-color: #667eea; +@secondary-color: #764ba2; +@text-color: #333; +@text-secondary: #666; +@text-muted: #999; +@border-color: #ddd; +@background-light: #f5f5f5; +@white: #fff; + +@gradient-primary: linear-gradient(135deg, @primary-color 0%, @secondary-color 100%); + +// 边距 +@spacing-xs: 5px; +@spacing-sm: 10px; +@spacing-md: 15px; +@spacing-lg: 20px; +@spacing-xl: 30px; + +// 圆角 +@radius-sm: 5px; +@radius-md: 8px; +@radius-lg: 10px; +@radius-xl: 12px; + +// 阴影 +@shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.1); +@shadow-md: 0 4px 15px rgba(0, 0, 0, 0.1); +@shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.1); + +// ============================================ +// 基础样式重置 +// ============================================ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + overflow: hidden; + font-family: 'Microsoft YaHei', sans-serif; + background: @background-light; + color: @text-color; +} + +// ============================================ +// 全局容器布局(主应用容器) +// ============================================ +.container { + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + padding: @spacing-sm; + background: @gradient-primary; + overflow: hidden; +} + +.main-content { + flex: 1; + display: flex; + gap: @spacing-sm; + min-height: 0; + overflow: hidden; +} + +// ============================================ +// 全局工具类 +// ============================================ +.mt10 { + margin-top: @spacing-sm; +} + +.mt60 { + margin-top: @spacing-xl; +} + +// ============================================ +// 头部样式 +// ============================================ +.header { + text-align: left; + color: @white; + margin-bottom: @spacing-sm; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + h1 { + font-size: 20px; + margin-bottom: 0; + text-align: left; + } +} + +.header-left { + display: flex; + align-items: center; + gap: @spacing-sm; + flex: 1; +} + +.header-right { + display: flex; + align-items: center; + gap: @spacing-sm; +} + +.status-indicator { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 6px 12px; + background: rgba(255, 255, 255, 0.2); + border-radius: 20px; + font-size: 14px; + white-space: nowrap; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #ff4444; + + &.connected { + background: #44ff44; + animation: pulse 2s infinite; + } +} + +// ============================================ +// 侧边栏样式 +// ============================================ +.sidebar { + width: 200px; + background: rgba(255, 255, 255, 0.95); + border-radius: @radius-lg; + padding: @spacing-lg 0; + box-shadow: @shadow-sm; + flex-shrink: 0; +} + +.sidebar-menu { + list-style: none; + padding: 0; + margin: 0; +} + +.menu-item { + padding: 15px 20px; + cursor: pointer; + display: flex; + align-items: center; + gap: 12px; + transition: all 0.3s ease; + border-left: 3px solid transparent; + color: @text-color; + + &:hover { + background: rgba(102, 126, 234, 0.1); + } + + &.active { + background: rgba(102, 126, 234, 0.15); + border-left-color: @primary-color; + color: @primary-color; + font-weight: bold; + + .menu-icon { + color: @primary-color; + } + } +} + +.menu-icon { + font-size: 18px; + width: 20px; + display: inline-block; + text-align: center; + color: @text-secondary; + flex-shrink: 0; +} + +.menu-text { + font-size: 15px; +} + +// ============================================ +// 内容区域 +// ============================================ +.content-area { + flex: 1; + background: rgba(255, 255, 255, 0.95); + border-radius: @radius-lg; + padding: @spacing-xl; + box-shadow: @shadow-sm; + overflow-y: auto; + min-width: 0; + position: relative; + + &.full-width { + padding: 0; + background: transparent; + border-radius: 0; + box-shadow: none; + overflow: hidden; + } +} + +.page-title { + font-size: 24px; + font-weight: bold; + color: @text-color; + margin-bottom: @spacing-xl; + padding-bottom: 15px; + border-bottom: 2px solid @primary-color; +} + +.placeholder-content { + text-align: center; + padding: 40px 20px; + color: @text-muted; + font-size: 16px; +} + +// ============================================ +// 启动加载动画(全局,在 HTML 中使用) +// ============================================ +.loading-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: @gradient-primary; + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + opacity: 1; + transition: opacity 0.5s ease-out; + + &.hidden { + opacity: 0; + pointer-events: none; + } +} + +.loading-content { + text-align: center; + color: @white; +} + +.loading-logo { + margin-bottom: @spacing-xl; +} + +.logo-circle { + width: 80px; + height: 80px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-top-color: @white; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto; +} + +.loading-text { + font-size: 18px; + font-weight: 500; + margin-bottom: @spacing-lg; + animation: pulse 2s ease-in-out infinite; +} + +.loading-progress { + width: 200px; + height: 4px; + background: rgba(255, 255, 255, 0.2); + border-radius: 2px; + overflow: hidden; + margin: 0 auto; +} + +.progress-bar-animated { + height: 100%; + background: @white; + border-radius: 2px; + animation: progress 1.5s ease-in-out infinite; +} + +// ============================================ +// 全局动画 +// ============================================ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideDown { + from { + transform: translateY(-50px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +@keyframes progress { + 0% { + width: 0%; + transform: translateX(0); + } + 50% { + width: 70%; + transform: translateX(0); + } + 100% { + width: 100%; + transform: translateX(0); + } +} diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..7c1ea95 --- /dev/null +++ b/app/index.html @@ -0,0 +1,31 @@ + + + + + + + boss - 远程监听服务 + + + + +
+
+ +
正在启动...
+
+
+
+
+
+ + +
+ + + + + + diff --git a/app/main.js b/app/main.js new file mode 100644 index 0000000..3ca6115 --- /dev/null +++ b/app/main.js @@ -0,0 +1,45 @@ +// Vue 应用入口文件 +import { createApp } from 'vue'; +import App from './App.vue'; +import router from './router'; +import store from './store'; + +// 引入全局样式 +import './css/index.less'; + +// 引入 PrimeVue +import PrimeVue from 'primevue/config'; +import Aura from '@primevue/themes/aura'; +import 'primeicons/primeicons.css'; + +// 创建并挂载 Vue 应用 +const app = createApp(App); + +// 配置 PrimeVue(使用 Aura 主题,扁平化设计) +app.use(PrimeVue, { + theme: { + preset: Aura, + options: { + darkModeSelector: false, // 暂时不使用深色模式 + cssLayer: false + } + } +}); + +// 使用 Vue Router 和 Vuex +app.use(router); +app.use(store); + +// 等待 DOM 加载完成后再挂载应用 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + app.mount('#app'); + window.app = app; + console.log('Vue 应用已挂载'); + }); +} else { + app.mount('#app'); + window.app = app; + console.log('Vue 应用已挂载'); +} + diff --git a/app/mixins/authMixin.js b/app/mixins/authMixin.js new file mode 100644 index 0000000..5c30f56 --- /dev/null +++ b/app/mixins/authMixin.js @@ -0,0 +1,230 @@ +/** + * 用户认证管理 Mixin + */ +import { getToken } from '../utils/api'; + +export default { + data() { + return { + phone: '', + password: '', + isLoggedIn: false, + loginButtonText: '登录', + userName: '', + remainingDays: null, + snCode: '', + deviceId: '-', + listenChannel: '-', + userMenuInfo: { + userName: '', + snCode: '' + } + }; + }, + methods: { + async loadSavedConfig() { + try { + // 从 store 加载保存的手机号 + if (this.$store) { + const savedPhone = this.$store.state.config.phone || this.$store.state.auth.phone; + if (savedPhone) { + this.phone = savedPhone; + } + } + + // 注意:现在数据都在 store 中,通过持久化插件自动恢复 + // 如果需要从主进程同步数据,可以在这里调用,但通常不需要 + // 因为登录成功后已经通过 syncUserInfo 同步到主进程了 + } catch (error) { + console.error('加载配置失败:', error); + if (this.addLog) { + this.addLog('error', `加载配置失败: ${error.message}`); + } + } + }, + + // checkActivationStatus 方法已移除 + // 现在登录状态由 Vuex Store 管理,通过持久化插件自动恢复 + // 不再需要从主进程获取状态 + + // 用户登录(只调用主进程接口,业务逻辑由主进程处理) + async userLogin(password, rememberMe = true) { + // 基本验证 + if (!this.phone) { + if (this.addLog) { + this.addLog('error', '请输入手机号'); + } + return { success: false, error: '请输入手机号' }; + } + + if (!password) { + if (this.addLog) { + this.addLog('error', '请输入密码'); + } + return { success: false, error: '请输入密码' }; + } + + if (!window.electronAPI) { + if (this.addLog) { + this.addLog('error', 'Electron API不可用'); + } + return { success: false, error: 'Electron API不可用' }; + } + + try { + if (this.addLog) { + this.addLog('info', `正在使用手机号 ${this.phone} 登录...`); + } + + const result = await window.electronAPI.invoke('auth:login', { + phone: this.phone, + password: password + }); + + if (result.success && result.data) { + // 登录成功,通过 store 更新状态(业务逻辑由主进程处理) + if (this.$store) { + await this.$store.dispatch('auth/login', { + phone: this.phone, + password: password, + deviceId: result.data.device_id || '' + }); + + if (rememberMe) { + this.$store.dispatch('config/setRememberMe', true); + this.$store.dispatch('config/setPhone', this.phone); + } + } + + // MQTT 连接由主进程自动处理,这里只检查状态 + if (this.checkMQTTStatus) { + setTimeout(() => { + this.checkMQTTStatus(); + }, 1000); + } + + // 开始获取任务状态 + if (this.startTaskStatusUpdate) { + this.startTaskStatusUpdate(); + } + + return { success: true, data: result.data }; + } else { + if (this.addLog) { + this.addLog('error', `登录失败: ${result.error || '未知错误'}`); + } + return { success: false, error: result.error || '未知错误' }; + } + } catch (error) { + if (this.addLog) { + this.addLog('error', `登录过程中发生错误: ${error.message}`); + } + return { success: false, error: error.message }; + } + }, + + async tryAutoLogin() { + try { + if (!this.$store) { + return false; + } + + // 从 store 检查是否有保存的登录信息 + const savedPhone = this.$store.state.config.phone || this.$store.state.auth.phone; + const userLoggedOut = this.$store.state.config.userLoggedOut || this.$store.state.auth.userLoggedOut; + + // 如果用户手动退出,不自动登录 + if (userLoggedOut) { + return false; + } + + if (!savedPhone) { + return false; + } + + + // 检查 store 中是否有有效的登录信息(token 和用户信息) + const token = getToken(); + const storeSnCode = this.$store ? this.$store.state.auth.snCode : ''; + const storeUserName = this.$store ? this.$store.state.auth.userName : ''; + + // 如果有 token 和用户信息,说明已登录(数据已通过持久化插件恢复) + if (token && (storeSnCode || storeUserName)) { + // 更新登录状态 + this.$store.commit('auth/SET_LOGGED_IN', true); + this.$store.commit('auth/SET_LOGIN_BUTTON_TEXT', '注销登录'); + + if (this.addLog) { + this.addLog('info', '自动登录成功'); + } + + // 连接MQTT + // MQTT 连接由主进程自动处理,这里只检查状态 + if (this.checkMQTTStatus) { + setTimeout(() => { + this.checkMQTTStatus(); + }, 1000); + } + + return true; // 自动登录成功 + } + + return false; // 未登录 + } catch (error) { + console.error('自动登录失败:', error); + if (this.addLog) { + this.addLog('error', `自动登录失败: ${error.message}`); + } + return false; + } + }, + + // 注销登录(只调用主进程接口,业务逻辑由主进程处理) + async logoutDevice() { + if (!window.electronAPI) { + if (this.addLog) { + this.addLog('error', 'Electron API不可用'); + } + return; + } + + try { + if (this.addLog) { + this.addLog('info', '正在注销登录...'); + } + + await window.electronAPI.invoke('auth:logout'); + + // 停止任务状态更新 + if (this.stopTaskStatusUpdate) { + this.stopTaskStatusUpdate(); + } + + // 更新 store 状态 + if (this.$store) { + this.$store.dispatch('auth/logout'); + this.$store.dispatch('config/setUserLoggedOut', true); + } + + if (this.addLog) { + this.addLog('success', '注销登录成功'); + } + + // 触发跳转到登录页面 + if (this.$emit) { + this.$emit('logout-success'); + } + } catch (error) { + if (this.addLog) { + this.addLog('error', `注销登录异常: ${error.message}`); + } + } + } + }, + watch: { + snCode(newVal) { + this.userMenuInfo.snCode = newVal; + } + } +}; + diff --git a/app/mixins/eventListenerMixin.js b/app/mixins/eventListenerMixin.js new file mode 100644 index 0000000..214a372 --- /dev/null +++ b/app/mixins/eventListenerMixin.js @@ -0,0 +1,178 @@ +/** + * 事件监听器管理 Mixin + */ +import platformMixin from './platformMixin.js'; + +export default { + mixins: [platformMixin], + + data() { + return { + // 存储已注册的事件监听器,用于清理 + _registeredEventListeners: [] + }; + }, + + methods: { + setupEventListeners() { + console.log('[事件监听] setupEventListeners 开始执行'); + + if (!window.electronAPI || !window.electronEvents) { + console.error('[事件监听] Electron API不可用', { + hasElectronAPI: !!window.electronAPI, + hasElectronEvents: !!window.electronEvents + }); + if (this.addLog) { + this.addLog('error', 'Electron API不可用'); + } + return; + } + + const electronEvents = window.electronEvents; + console.log('[事件监听] electronEvents 对象:', electronEvents); + + // 定义需要注册的事件监听器 + const listeners = [ + // MQTT 事件 + { channel: 'mqtt:connected', handler: (data) => { + console.log('[事件监听] 收到 mqtt:connected 事件,数据:', data); + this.onMQTTConnected(data); + }}, + { channel: 'mqtt:disconnected', handler: (data) => { + console.log('[事件监听] 收到 mqtt:disconnected 事件,数据:', data); + this.onMQTTDisconnected(data); + }}, + { channel: 'mqtt:message', handler: (data) => { + console.log('[事件监听] 收到 mqtt:message 事件'); + this.onMQTTMessage(data); + }}, + { channel: 'mqtt:status', handler: (data) => { + console.log('[事件监听] 收到 mqtt:status 事件,数据:', data); + this.onMQTTStatusChange(data); + }}, + + // 指令执行结果 + { channel: 'command:result', handler: (data) => { + if (this.addLog) { + if (data.success) { + this.addLog('success', `指令执行成功 : ${JSON.stringify(data.data).length}字符`); + } else { + this.addLog('error', `指令执行失败: ${data.error}`); + } + } + }}, + + // 系统信息 + { channel: 'system:info', handler: (data) => this.updateSystemInfo(data) }, + { channel: 'log:message', handler: (data) => { + console.log('[事件监听] 收到 log:message 事件,数据:', data); + if (this.addLog) { + this.addLog(data.level, data.message); + } + }}, + + // 通知 + { channel: 'notification', handler: (data) => { + if (this.showNotification) { + this.showNotification(data.title, data.body); + } + }}, + + // 更新相关 + { channel: 'update:available', handler: (updateInfo) => this.onUpdateAvailable(updateInfo) }, + { channel: 'update:progress', handler: (progressData) => this.onUpdateProgress(progressData) }, + { channel: 'update:downloaded', handler: (data) => this.onUpdateDownloaded(data) }, + { channel: 'update:error', handler: (errorData) => this.onUpdateError(errorData) }, + + // 设备工作状态 + { + channel: 'device:work-status', + handler: (workStatus) => { + this.onDeviceWorkStatus(workStatus); + } + }, + + // 平台登录状态 + { channel: 'platform:login-status-updated', handler: (data) => { + // 优先使用组件中定义的方法 + if (this.onPlatformLoginStatusUpdated) { + this.onPlatformLoginStatusUpdated(data); + } else { + console.warn('[事件监听] 无法更新平台登录状态:组件未定义 onPlatformLoginStatusUpdated 且 store 不可用'); + } + }} + ]; + + // 注册所有事件监听器 + listeners.forEach(({ channel, handler }) => { + electronEvents.on(channel, handler); + // 保存到清理列表 + this._registeredEventListeners.push({ channel, handler }); + }); + + console.log('[事件监听] 所有事件监听器已设置完成'); + // 优先使用 store 的 dispatch 方法添加日志 + if (this.$store && this.$store.dispatch) { + this.$store.dispatch('log/addLog', { level: 'info', message: '事件监听器设置完成' }); + console.log('[事件监听] 已通过 store.dispatch 添加日志'); + } else if (this.addLog) { + this.addLog('info', '事件监听器设置完成'); + console.log('[事件监听] 已通过 addLog 方法添加日志'); + } else { + console.log('[事件监听] 日志系统暂不可用,将在组件完全初始化后记录'); + } + }, + + /** + * 清理所有已注册的事件监听器 + */ + cleanupEventListeners() { + console.log('[事件监听] 开始清理事件监听器'); + + if (!window.electronEvents || !this._registeredEventListeners) { + return; + } + + // 移除所有已注册的监听器 + this._registeredEventListeners.forEach(({ channel, handler }) => { + try { + window.electronEvents.off(channel, handler); + } catch (error) { + console.warn(`[事件监听] 移除监听器失败: ${channel}`, error); + } + }); + + // 清空列表 + this._registeredEventListeners = []; + console.log('[事件监听] 事件监听器清理完成'); + }, + + showNotification(title, body) { + if ('Notification' in window) { + if (Notification.permission === 'granted') { + new Notification(title, { + body: body, + icon: '/assets/icon.png' + }); + } else if (Notification.permission !== 'denied') { + Notification.requestPermission().then(permission => { + if (permission === 'granted') { + new Notification(title, { + body: body, + icon: '/assets/icon.png' + }); + } + }); + } + } + + if (this.addLog) { + this.addLog('info', `[通知] ${title}: ${body}`); + } + } + }, + + + +}; + diff --git a/app/mixins/logMixin.js b/app/mixins/logMixin.js new file mode 100644 index 0000000..cbe5456 --- /dev/null +++ b/app/mixins/logMixin.js @@ -0,0 +1,48 @@ +/** + * 日志管理 Mixin + * 直接使用 store 中的 log 状态 + */ +export default { + methods: { + /** + * 添加日志 + * @param {string} level - 日志级别: 'info', 'success', 'warn', 'error' + * @param {string} message - 日志消息 + */ + addLog(level, message) { + if (this.$store) { + this.$store.dispatch('log/addLog', { level, message }); + } + }, + + /** + * 清空日志 + */ + clearLogs() { + if (this.$store) { + this.$store.dispatch('log/clearLogs'); + } + }, + + /** + * 导出日志 + */ + exportLogs() { + if (this.$store) { + this.$store.dispatch('log/exportLogs'); + } + } + }, + computed: { + /** + * 获取日志条目(从 store 获取) + */ + logEntries() { + if (this.$store) { + return this.$store.getters['log/logEntries'] || []; + } + return []; + } + } +}; + diff --git a/app/mixins/mqttMixin.js b/app/mixins/mqttMixin.js new file mode 100644 index 0000000..c668695 --- /dev/null +++ b/app/mixins/mqttMixin.js @@ -0,0 +1,97 @@ +/** + * MQTT 管理 Mixin + */ +export default { + computed: { + isConnected() { + return this.$store ? this.$store.state.mqtt.isConnected : false; + }, + mqttStatus() { + return this.$store ? this.$store.state.mqtt.mqttStatus : '未连接'; + } + }, + methods: { + // MQTT 连接已由主进程自动处理,客户端不再需要手动连接 + // 只保留状态查询方法 + async checkMQTTStatus() { + try { + + if (!window.electronAPI) { + return; + } + + const statusResult = await window.electronAPI.invoke('mqtt:status'); + if (statusResult && typeof statusResult.isConnected !== 'undefined') { + if (this.$store) { + this.$store.dispatch('mqtt/setConnected', statusResult.isConnected); + } + } + } catch (error) { + console.warn('查询MQTT状态失败:', error); + } + }, + + async disconnectMQTT() { + try { + if (!window.electronAPI) { + return; + } + + await window.electronAPI.invoke('mqtt:disconnect'); + if (this.addLog) { + this.addLog('info', '服务断开连接指令已发送'); + } + } catch (error) { + if (this.addLog) { + this.addLog('error', `断开服务连接异常: ${error.message}`); + } + } + }, + + onMQTTConnected(data) { + console.log('[MQTT] onMQTTConnected 被调用,数据:', data); + if (this.$store) { + this.$store.dispatch('mqtt/setConnected', true); + console.log('[MQTT] 状态已更新为已连接'); + } + if (this.addLog) { + this.addLog('success', 'MQTT 服务已连接'); + } + }, + + onMQTTDisconnected(data) { + if (this.$store) { + this.$store.dispatch('mqtt/setConnected', false); + } + if (this.addLog) { + this.addLog('warn', `服务连接断开: ${data.reason || '未知原因'}`); + } + }, + + onMQTTMessage(data) { + const action = data.payload?.action || 'unknown'; + if (this.addLog) { + this.addLog('info', `收到远程指令: ${action}`); + } + }, + + onMQTTStatusChange(data) { + console.log('[MQTT] onMQTTStatusChange 被调用,数据:', data); + // 根据状态数据更新连接状态 + if (data && typeof data.isConnected !== 'undefined') { + if (this.$store) { + this.$store.dispatch('mqtt/setConnected', data.isConnected); + console.log('[MQTT] 通过 isConnected 更新状态:', data.isConnected); + } + } else if (data && data.status) { + // 如果数据中有 status 字段,根据状态字符串判断 + const isConnected = data.status === 'connected' || data.status === '已连接'; + if (this.$store) { + this.$store.dispatch('mqtt/setConnected', isConnected); + console.log('[MQTT] 通过 status 更新状态:', isConnected); + } + } + } + } +}; + diff --git a/app/mixins/platformMixin.js b/app/mixins/platformMixin.js new file mode 100644 index 0000000..68a3570 --- /dev/null +++ b/app/mixins/platformMixin.js @@ -0,0 +1,65 @@ +/** + * 平台信息管理 Mixin + */ +export default { + computed: { + currentPlatform() { + return this.$store ? this.$store.state.platform.currentPlatform : '-'; + }, + platformLoginStatus() { + return this.$store ? this.$store.state.platform.platformLoginStatus : '-'; + }, + platformLoginStatusColor() { + return this.$store ? this.$store.state.platform.platformLoginStatusColor : '#FF9800'; + }, + isPlatformLoggedIn() { + return this.$store ? this.$store.state.platform.isPlatformLoggedIn : false; + } + }, + methods: { + // 接收主进程推送的平台登录状态(不做处理,直接更新 store) + onPlatformLoginStatusUpdated(data) { + console.log('[PlatformMixin] 收到平台登录状态更新:', data); + if (this.$store && data) { + // 直接使用主进程提供的格式化数据 + if (data.platform !== undefined) { + this.$store.dispatch('platform/updatePlatform', data.platform); + } + // 修复:确保正确处理 isLoggedIn 状态 + const isLoggedIn = data.isLoggedIn !== undefined ? data.isLoggedIn : false; + this.$store.dispatch('platform/updatePlatformLoginStatus', { + status: data.status || (isLoggedIn ? '已登录' : '未登录'), + color: data.color || (isLoggedIn ? '#4CAF50' : '#FF9800'), + isLoggedIn: isLoggedIn + }); + + } + }, + + /** + * 主动检查平台登录状态 + * 调用主进程接口检查状态,主进程会自动更新并通知前端 + */ + async checkPlatformLoginStatus() { + if (!window.electronAPI || !window.electronAPI.invoke) { + console.warn('[PlatformMixin] electronAPI 不可用,无法检查平台登录状态'); + return; + } + + try { + const result = await window.electronAPI.invoke('auth:platform-login-status'); + if (result && result.success) { + // 主进程已经通过 platform:login-status-updated 事件通知前端了 + // 这里不需要再次更新 store,因为事件处理已经更新了 + console.log('[PlatformMixin] 平台登录状态检查完成:', { + platform: result.platformType, + isLoggedIn: result.isLoggedIn + }); + } + } catch (error) { + console.error('[PlatformMixin] 检查平台登录状态失败:', error); + } + } + } +}; + diff --git a/app/mixins/qrCodeMixin.js b/app/mixins/qrCodeMixin.js new file mode 100644 index 0000000..5e3d473 --- /dev/null +++ b/app/mixins/qrCodeMixin.js @@ -0,0 +1,220 @@ +/** + * 二维码管理 Mixin + * 二维码刷新逻辑在渲染层处理,主进程只负责执行获取二维码的操作 + */ +export default { + data() { + return { + qrCodeAutoRefreshInterval: null, // 二维码自动刷新定时器 + qrCodeCountdownInterval: null, // 倒计时定时器 + }; + }, + computed: { + qrCodeUrl() { + return this.$store ? this.$store.state.qrCode.qrCodeUrl : null; + }, + qrCodeCountdown() { + return this.$store ? this.$store.state.qrCode.qrCodeCountdown : 0; + }, + qrCodeCountdownActive() { + return this.$store ? this.$store.state.qrCode.qrCodeCountdownActive : false; + }, + qrCodeExpired() { + return this.$store ? this.$store.state.qrCode.qrCodeExpired : false; + }, + qrCodeRefreshCount() { + return this.$store ? this.$store.state.qrCode.qrCodeRefreshCount : 0; + }, + isPlatformLoggedIn() { + return this.$store ? this.$store.state.platform.isPlatformLoggedIn : false; + } + }, + methods: { + /** + * 获取二维码 + */ + async getQrCode() { + try { + if (!window.electronAPI || !window.electronAPI.invoke) { + console.error('[二维码] electronAPI 不可用'); + return; + } + + const result = await window.electronAPI.invoke('command:execute', { + platform: 'boss', + action: 'get_login_qr_code', + data: { type: 'app' }, + source: 'renderer' + }); + + if (result.success && result.data) { + const qrCodeUrl = result.data.qrCodeUrl || result.data.qr_code_url || result.data.oos_url || result.data.imageData; + const qrCodeOosUrl = result.data.qrCodeOosUrl || result.data.oos_url || null; + + if (qrCodeUrl && this.$store) { + this.$store.dispatch('qrCode/setQrCode', { url: qrCodeUrl, oosUrl: qrCodeOosUrl }); + if (this.addLog) { + this.addLog('success', '二维码已获取'); + } + return true; + } + } else { + console.error('[二维码] 获取失败:', result.error || '未知错误'); + if (this.addLog) { + this.addLog('error', `获取二维码失败: ${result.error || '未知错误'}`); + } + } + } catch (error) { + console.error('[二维码] 获取失败:', error); + if (this.addLog) { + this.addLog('error', `获取二维码失败: ${error.message}`); + } + } + return false; + }, + + /** + * 启动二维码自动刷新 + */ + startQrCodeAutoRefresh() { + // 如果定时器已存在,不重复启动 + if (this.qrCodeAutoRefreshInterval) { + return; + } + + // 如果平台已登录,不启动自动刷新 + if (this.isPlatformLoggedIn) { + return; + } + + const QR_CODE_REFRESH_INTERVAL = 25000; // 25秒 + const MAX_REFRESH_COUNT = 3; // 最大刷新次数 + + // 重置刷新次数 + if (this.$store) { + this.$store.dispatch('qrCode/setQrCodeCountdown', { + countdown: 25, + isActive: true, + refreshCount: 0, + isExpired: false + }); + } + + // 立即获取一次二维码 + this.getQrCode(); + + // 启动定时器,每25秒刷新一次 + this.qrCodeAutoRefreshInterval = setInterval(async () => { + // 检查平台登录状态 + if (this.isPlatformLoggedIn) { + this.stopQrCodeAutoRefresh(); + return; + } + + // 检查刷新次数 + const refreshCount = this.qrCodeRefreshCount; + if (refreshCount >= MAX_REFRESH_COUNT) { + // 已达到最大次数 + if (this.$store) { + this.$store.dispatch('qrCode/setQrCodeCountdown', { + countdown: 0, + isActive: false, + refreshCount: refreshCount, + isExpired: true + }); + } + this.stopQrCodeAutoRefresh(); + return; + } + + // 刷新二维码 + const success = await this.getQrCode(); + if (success) { + // 更新刷新次数 + if (this.$store) { + this.$store.dispatch('qrCode/setQrCodeCountdown', { + countdown: 25, + isActive: true, + refreshCount: refreshCount + 1, + isExpired: false + }); + } + } + }, QR_CODE_REFRESH_INTERVAL); + + // 启动倒计时 + this.startQrCodeCountdown(); + }, + + /** + * 停止二维码自动刷新 + */ + stopQrCodeAutoRefresh() { + if (this.qrCodeAutoRefreshInterval) { + clearInterval(this.qrCodeAutoRefreshInterval); + this.qrCodeAutoRefreshInterval = null; + } + this.stopQrCodeCountdown(); + + if (this.$store) { + this.$store.dispatch('qrCode/setQrCodeCountdown', { + countdown: 0, + isActive: false, + refreshCount: this.qrCodeRefreshCount, + isExpired: this.qrCodeExpired + }); + } + }, + + /** + * 启动二维码倒计时 + */ + startQrCodeCountdown() { + // 如果倒计时定时器已存在,不重复启动 + if (this.qrCodeCountdownInterval) { + return; + } + + // 每秒更新一次倒计时 + this.qrCodeCountdownInterval = setInterval(() => { + if (this.qrCodeCountdownActive && this.qrCodeCountdown > 0) { + const newCountdown = this.qrCodeCountdown - 1; + if (this.$store) { + this.$store.dispatch('qrCode/setQrCodeCountdown', { + countdown: newCountdown, + isActive: true, + refreshCount: this.qrCodeRefreshCount, + isExpired: false + }); + } + } else { + // 倒计时结束 + this.stopQrCodeCountdown(); + } + }, 1000); + }, + + /** + * 停止二维码倒计时 + */ + stopQrCodeCountdown() { + if (this.qrCodeCountdownInterval) { + clearInterval(this.qrCodeCountdownInterval); + this.qrCodeCountdownInterval = null; + } + }, + }, + watch: { + // 监听平台登录状态,如果已登录则停止刷新 + isPlatformLoggedIn(newVal) { + if (newVal) { + this.stopQrCodeAutoRefresh(); + } + } + }, + beforeUnmount() { + // 组件销毁时清理定时器 + this.stopQrCodeAutoRefresh(); + } +}; + diff --git a/app/mixins/systemInfoMixin.js b/app/mixins/systemInfoMixin.js new file mode 100644 index 0000000..7a710fa --- /dev/null +++ b/app/mixins/systemInfoMixin.js @@ -0,0 +1,48 @@ +/** + * 系统信息管理 Mixin + */ +export default { + computed: { + uptime() { + return this.$store ? this.$store.state.system.uptime : '0分钟'; + }, + cpuUsage() { + return this.$store ? this.$store.state.system.cpuUsage : '0%'; + }, + memUsage() { + return this.$store ? this.$store.state.system.memUsage : '0MB'; + }, + deviceId() { + return this.$store ? this.$store.state.system.deviceId : '-'; + } + }, + methods: { + // 更新运行时间(仅计算,不请求) + startSystemInfoUpdate() { + const updateUptime = () => { + if (this.$store && this.startTime) { + const elapsed = Math.floor((Date.now() - this.startTime) / 1000 / 60); + this.$store.dispatch('system/updateUptime', `${elapsed}分钟`); + } + }; + updateUptime(); + setInterval(updateUptime, 5000); + }, + + // 接收主进程推送的系统信息(不做处理,直接更新 store) + updateSystemInfo(data) { + if (this.$store && data) { + if (data.cpu !== undefined) { + this.$store.dispatch('system/updateCpuUsage', data.cpu); + } + if (data.memory !== undefined) { + this.$store.dispatch('system/updateMemUsage', data.memory); + } + if (data.deviceId !== undefined) { + this.$store.dispatch('system/updateDeviceId', data.deviceId); + } + } + } + } +}; + diff --git a/app/mixins/taskMixin.js b/app/mixins/taskMixin.js new file mode 100644 index 0000000..f97aee1 --- /dev/null +++ b/app/mixins/taskMixin.js @@ -0,0 +1,61 @@ +/** + * 设备工作状态管理 Mixin + */ +export default { + computed: { + displayText() { + return this.$store ? this.$store.state.task.displayText : null; + }, + nextExecuteTimeText() { + return this.$store ? this.$store.state.task.nextExecuteTimeText : null; + }, + currentActivity() { + return this.$store ? this.$store.state.task.currentActivity : null; + }, + pendingQueue() { + return this.$store ? this.$store.state.task.pendingQueue : null; + }, + deviceStatus() { + return this.$store ? this.$store.state.task.deviceStatus : null; + } + }, + methods: { + startTaskStatusUpdate() { + // 设备工作状态已通过 MQTT 实时推送,不再使用接口轮询 + // 设备工作状态通过 device_work_status_${snCode} 主题推送,由 mqttService 处理并发送到渲染进程 + // 通过 onDeviceWorkStatus 方法接收并更新状态 + console.log('[TaskMixin] 设备工作状态更新已启动,使用 MQTT 实时推送'); + }, + + stopTaskStatusUpdate() { + if (this.$store) { + this.$store.dispatch('task/clearDeviceWorkStatus'); + } + }, + + /** + * 处理设备工作状态通知 + * 服务端已格式化好显示文本,客户端直接使用,不做复杂处理 + */ + onDeviceWorkStatus(workStatus) { + if (!workStatus || !this.$store) { + console.warn('[Renderer] 收到设备工作状态但数据无效:', workStatus); + return; + } + + + // 直接更新设备工作状态到 store(服务端已处理好所有显示逻辑) + try { + this.$store.dispatch('task/updateDeviceWorkStatus', workStatus); + + // 验证更新是否成功 + this.$nextTick(() => { + const state = this.$store.state.task; + }); + } catch (error) { + console.error('[Renderer] 更新设备工作状态失败:', error); + } + } + } +}; + diff --git a/app/mixins/updateMixin.js b/app/mixins/updateMixin.js new file mode 100644 index 0000000..1f65c45 --- /dev/null +++ b/app/mixins/updateMixin.js @@ -0,0 +1,138 @@ +/** + * 更新管理 Mixin + */ +export default { + computed: { + updateDialogVisible() { + return this.$store ? this.$store.state.update.updateDialogVisible : false; + }, + updateInfo() { + return this.$store ? this.$store.state.update.updateInfo : null; + }, + updateProgress() { + return this.$store ? this.$store.state.update.updateProgress : 0; + }, + isDownloading() { + return this.$store ? this.$store.state.update.isDownloading : false; + }, + downloadState() { + return this.$store ? this.$store.state.update.downloadState : { + progress: 0, + downloadedBytes: 0, + totalBytes: 0 + }; + } + }, + methods: { + // 接收主进程推送的更新信息(不做处理,直接更新 store) + onUpdateAvailable(updateInfo) { + if (!updateInfo) { + return; + } + + if (this.$store) { + this.$store.dispatch('update/setUpdateInfo', updateInfo); + this.$store.dispatch('update/showUpdateDialog'); + } + + if (this.addLog) { + this.addLog('info', `发现新版本: ${updateInfo.version || '未知'}`); + } + }, + + // 接收主进程推送的更新进度(不做处理,直接更新 store) + onUpdateProgress(progressData) { + if (this.$store && progressData) { + this.$store.dispatch('update/setDownloadState', { + progress: progressData.progress || 0, + downloadedBytes: progressData.downloadedBytes || 0, + totalBytes: progressData.totalBytes || 0 + }); + this.$store.dispatch('update/setUpdateProgress', progressData.progress || 0); + this.$store.dispatch('update/setDownloading', true); + } + }, + + // 接收主进程推送的下载完成通知(不做处理,直接更新 store) + onUpdateDownloaded(data) { + if (this.$store) { + this.$store.dispatch('update/setDownloading', false); + this.$store.dispatch('update/setUpdateProgress', 100); + } + if (this.addLog) { + this.addLog('success', '更新包下载完成'); + } + if (this.showNotification) { + this.showNotification('更新下载完成', '更新包已下载完成,是否立即安装?'); + } + }, + + // 接收主进程推送的更新错误(不做处理,直接显示) + onUpdateError(errorData) { + if (this.$store) { + this.$store.dispatch('update/setDownloading', false); + } + const errorMsg = errorData?.error || '更新失败'; + if (this.addLog) { + this.addLog('error', `更新错误: ${errorMsg}`); + } + if (this.showNotification) { + this.showNotification('更新失败', errorMsg); + } + }, + + closeUpdateDialog() { + if (this.$store) { + this.$store.dispatch('update/hideUpdateDialog'); + } + }, + + // 下载更新(调用主进程接口,不做业务逻辑处理) + async startDownload() { + const updateInfoData = this.updateInfo; + if (!updateInfoData || !updateInfoData.downloadUrl) { + if (this.addLog) { + this.addLog('error', '更新信息不存在'); + } + return; + } + + if (!window.electronAPI) { + if (this.addLog) { + this.addLog('error', 'Electron API不可用'); + } + return; + } + + try { + await window.electronAPI.invoke('update:download', updateInfoData.downloadUrl); + } catch (error) { + if (this.addLog) { + this.addLog('error', `下载更新失败: ${error.message}`); + } + } + }, + + // 安装更新(调用主进程接口,不做业务逻辑处理) + async installUpdate() { + if (!window.electronAPI) { + if (this.addLog) { + this.addLog('error', 'Electron API不可用'); + } + return; + } + + try { + await window.electronAPI.invoke('update:install'); + setTimeout(() => { + this.closeUpdateDialog(); + }, 1000); + } catch (error) { + if (this.addLog) { + this.addLog('error', `安装更新失败: ${error.message}`); + } + } + } + } +}; + diff --git a/app/package-lock.json b/app/package-lock.json new file mode 100644 index 0000000..bfe3991 --- /dev/null +++ b/app/package-lock.json @@ -0,0 +1,1834 @@ +{ + "name": "boss-automation-renderer", + "version": "1.0.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "boss-automation-renderer", + "version": "1.0.8", + "dependencies": { + "@primevue/themes": "^4.0.0", + "axios": "^1.13.2", + "primeicons": "^7.0.0", + "primevue": "^4.5.4", + "vue": "^3.3.0", + "vue-router": "^4.6.4", + "vuex": "^4.1.0", + "vuex-persistedstate": "^4.1.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.5.0", + "less": "^4.2.0", + "vite": "^5.4.21" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@primeuix/styled": { + "version": "0.7.4", + "resolved": "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.7.4.tgz", + "integrity": "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==", + "license": "MIT", + "dependencies": { + "@primeuix/utils": "^0.6.1" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primeuix/styles": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/@primeuix/styles/-/styles-2.0.2.tgz", + "integrity": "sha512-LNtkJsTonNHF5ag+9s3+zQzm00+LRmffw68QRIHy6S/dam1JpdrrAnUzNYlWbaY7aE2EkZvQmx7Np7+PyHn+ow==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.4" + } + }, + "node_modules/@primeuix/themes": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/@primeuix/themes/-/themes-2.0.2.tgz", + "integrity": "sha512-prwQvA3tDGBz8yWSUenaJUttEMCEvPvxwOfFhDPmSe1vwsfVKL2Nmh5eZvtPFQnxmIOPsHZS7zc0/L3CzJ83Eg==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.4" + } + }, + "node_modules/@primeuix/utils": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.6.3.tgz", + "integrity": "sha512-/SLNQSKQ73WbBIsflKVqbpVjCfFYvQO3Sf1LMheXyxh8JqxO4M63dzP56wwm9OPGuCQ6MYOd2AHgZXz+g7PZcg==", + "license": "MIT", + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primevue/core": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/@primevue/core/-/core-4.5.4.tgz", + "integrity": "sha512-lYJJB3wTrDJ8MkLctzHfrPZAqXVxoatjIsswSJzupatf6ZogJHVYADUKcn1JAkLLk8dtV1FA2AxDek663fHO5Q==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.4", + "@primeuix/utils": "^0.6.2" + }, + "engines": { + "node": ">=12.11.0" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@primevue/icons": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/@primevue/icons/-/icons-4.5.4.tgz", + "integrity": "sha512-DxgryEc7ZmUqcEhYMcxGBRyFzdtLIoy3jLtlH1zsVSRZaG+iSAcjQ88nvfkZxGUZtZBFL7sRjF6KLq3bJZJwUw==", + "license": "MIT", + "dependencies": { + "@primeuix/utils": "^0.6.2", + "@primevue/core": "4.5.4" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@primevue/themes": { + "version": "4.5.3", + "resolved": "https://registry.npmmirror.com/@primevue/themes/-/themes-4.5.3.tgz", + "integrity": "sha512-h5z4BVQkKkqv9sHjrPc742Oej58v4LOgrTm2btjCMwG+277faAg/o0+YimmQT24/iXbtmc5Lt+yh4d/yOVJtRA==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.4", + "@primeuix/themes": "^2.0.2" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.4.tgz", + "integrity": "sha512-PWU3Y92H4DD0bOqorEPp1Y0tbzwAurFmIYpjcObv5axGVOtcTlB0b2UKMd2echo08MgN7jO8WQZSSysvfisFSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.4.tgz", + "integrity": "sha512-Gw0/DuVm3rGsqhMGYkSOXXIx20cC3kTlivZeuaGt4gEgILivykNyBWxeUV5Cf2tDA2nPLah26vq3emlRrWVbng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.4.tgz", + "integrity": "sha512-+w06QvXsgzKwdVg5qRLZpTHh1bigHZIqoIUPtiqh05ZiJVUQ6ymOxaPkXTvRPRLH88575ZCRSRM3PwIoNma01Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.4.tgz", + "integrity": "sha512-EB4Na9G2GsrRNRNFPuxfwvDRDUwQEzJPpiK1vo2zMVhEeufZ1k7J1bKnT0JYDfnPC7RNZ2H5YNQhW6/p2QKATw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.4.tgz", + "integrity": "sha512-bldA8XEqPcs6OYdknoTMaGhjytnwQ0NClSPpWpmufOuGPN5dDmvIa32FygC2gneKK4A1oSx86V1l55hyUWUYFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.4.tgz", + "integrity": "sha512-3T8GPjH6mixCd0YPn0bXtcuSXi1Lj+15Ujw2CEb7dd24j9thcKscCf88IV7n76WaAdorOzAgSSbuVRg4C8V8Qw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.4.tgz", + "integrity": "sha512-UPMMNeC4LXW7ZSHxeP3Edv09aLsFUMaD1TSVW6n1CWMECnUIJMFFB7+XC2lZTdPtvB36tYC0cJWc86mzSsaviw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.4.tgz", + "integrity": "sha512-H8uwlV0otHs5Q7WAMSoyvjV9DJPiy5nJ/xnHolY0QptLPjaSsuX7tw+SPIfiYH6cnVx3fe4EWFafo6gH6ekZKA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.4.tgz", + "integrity": "sha512-BLRwSRwICXz0TXkbIbqJ1ibK+/dSBpTJqDClF61GWIrxTXZWQE78ROeIhgl5MjVs4B4gSLPCFeD4xML9vbzvCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.4.tgz", + "integrity": "sha512-6bySEjOTbmVcPJAywjpGLckK793A0TJWSbIa0sVwtVGfe/Nz6gOWHOwkshUIAp9j7wg2WKcA4Snu7Y1nUZyQew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.4.tgz", + "integrity": "sha512-U0ow3bXYJZ5MIbchVusxEycBw7bO6C2u5UvD31i5IMTrnt2p4Fh4ZbHSdc/31TScIJQYHwxbj05BpevB3201ug==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.4.tgz", + "integrity": "sha512-iujDk07ZNwGLVn0YIWM80SFN039bHZHCdCCuX9nyx3Jsa2d9V/0Y32F+YadzwbvDxhSeVo9zefkoPnXEImnM5w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.4.tgz", + "integrity": "sha512-MUtAktiOUSu+AXBpx1fkuG/Bi5rhlorGs3lw5QeJ2X3ziEGAq7vFNdWVde6XGaVqi0LGSvugwjoxSNJfHFTC0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.4.tgz", + "integrity": "sha512-btm35eAbDfPtcFEgaXCI5l3c2WXyzwiE8pArhd66SDtoLWmgK5/M7CUxmUglkwtniPzwvWioBKKl6IXLbPf2sQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.4.tgz", + "integrity": "sha512-uJlhKE9ccUTCUlK+HUz/80cVtx2RayadC5ldDrrDUFaJK0SNb8/cCmC9RhBhIWuZ71Nqj4Uoa9+xljKWRogdhA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.4.tgz", + "integrity": "sha512-jjEMkzvASQBbzzlzf4os7nzSBd/cvPrpqXCUOqoeCh1dQ4BP3RZCJk8XBeik4MUln3m+8LeTJcY54C/u8wb3DQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.4.tgz", + "integrity": "sha512-lu90KG06NNH19shC5rBPkrh6mrTpq5kviFylPBXQVpdEu0yzb0mDgyxLr6XdcGdBIQTH/UAhDJnL+APZTBu1aQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.4.tgz", + "integrity": "sha512-dFDcmLwsUzhAm/dn0+dMOQZoONVYBtgik0VuY/d5IJUUb787L3Ko/ibvTvddqhb3RaB7vFEozYevHN4ox22R/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.4.tgz", + "integrity": "sha512-WvUpUAWmUxZKtRnQWpRKnLW2DEO8HB/l8z6oFFMNuHndMzFTJEXzaYJ5ZAmzNw0L21QQJZsUQFt2oPf3ykAD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.4.tgz", + "integrity": "sha512-JGbeF2/FDU0x2OLySw/jgvkwWUo05BSiJK0dtuI4LyuXbz3wKiC1xHhLB1Tqm5VU6ZZDmAorj45r/IgWNWku5g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.4.tgz", + "integrity": "sha512-zuuC7AyxLWLubP+mlUwEyR8M1ixW1ERNPHJfXm8x7eQNP4Pzkd7hS3qBuKBR70VRiQ04Kw8FNfRMF5TNxuZq2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.4.tgz", + "integrity": "sha512-Sbx45u/Lbb5RyptSbX7/3deP+/lzEmZ0BTSHxwxN/IMOZDZf8S0AGo0hJD5n/LQssxb5Z3B4og4P2X6Dd8acCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz", + "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.25", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", + "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.25", + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", + "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.25", + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", + "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.25", + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz", + "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz", + "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.25", + "@vue/shared": "3.5.25" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz", + "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.25", + "@vue/runtime-core": "3.5.25", + "@vue/shared": "3.5.25", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz", + "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25" + }, + "peerDependencies": { + "vue": "3.5.25" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz", + "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT" + }, + "node_modules/less": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/less/-/less-4.5.1.tgz", + "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/primeicons": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/primeicons/-/primeicons-7.0.0.tgz", + "integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==", + "license": "MIT" + }, + "node_modules/primevue": { + "version": "4.5.4", + "resolved": "https://registry.npmmirror.com/primevue/-/primevue-4.5.4.tgz", + "integrity": "sha512-nTyEohZABFJhVIpeUxgP0EJ8vKcJAhD+Z7DYj95e7ie/MNUCjRNcGjqmE1cXtXi4z54qDfTSI9h2uJ51qz2DIw==", + "license": "MIT", + "dependencies": { + "@primeuix/styled": "^0.7.4", + "@primeuix/styles": "^2.0.2", + "@primeuix/utils": "^0.6.2", + "@primevue/core": "4.5.4", + "@primevue/icons": "4.5.4" + }, + "engines": { + "node": ">=12.11.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/rollup": { + "version": "4.53.4", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.4.tgz", + "integrity": "sha512-YpXaaArg0MvrnJpvduEDYIp7uGOqKXbH9NsHGQ6SxKCOsNAjZF018MmxefFUulVP2KLtiGw1UvZbr+/ekjvlDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.4", + "@rollup/rollup-android-arm64": "4.53.4", + "@rollup/rollup-darwin-arm64": "4.53.4", + "@rollup/rollup-darwin-x64": "4.53.4", + "@rollup/rollup-freebsd-arm64": "4.53.4", + "@rollup/rollup-freebsd-x64": "4.53.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.4", + "@rollup/rollup-linux-arm-musleabihf": "4.53.4", + "@rollup/rollup-linux-arm64-gnu": "4.53.4", + "@rollup/rollup-linux-arm64-musl": "4.53.4", + "@rollup/rollup-linux-loong64-gnu": "4.53.4", + "@rollup/rollup-linux-ppc64-gnu": "4.53.4", + "@rollup/rollup-linux-riscv64-gnu": "4.53.4", + "@rollup/rollup-linux-riscv64-musl": "4.53.4", + "@rollup/rollup-linux-s390x-gnu": "4.53.4", + "@rollup/rollup-linux-x64-gnu": "4.53.4", + "@rollup/rollup-linux-x64-musl": "4.53.4", + "@rollup/rollup-openharmony-arm64": "4.53.4", + "@rollup/rollup-win32-arm64-msvc": "4.53.4", + "@rollup/rollup-win32-ia32-msvc": "4.53.4", + "@rollup/rollup-win32-x64-gnu": "4.53.4", + "@rollup/rollup-win32-x64-msvc": "4.53.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/shvl": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/shvl/-/shvl-2.0.3.tgz", + "integrity": "sha512-V7C6S9Hlol6SzOJPnQ7qzOVEWUQImt3BNmmzh40wObhla3XOYMe4gGiYzLrJd5TFa+cI2f9LKIRJTTKZSTbWgw==", + "deprecated": "older versions vulnerable to prototype pollution", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.25", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz", + "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-sfc": "3.5.25", + "@vue/runtime-dom": "3.5.25", + "@vue/server-renderer": "3.5.25", + "@vue/shared": "3.5.25" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vuex": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.1.0.tgz", + "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vuex-persistedstate": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/vuex-persistedstate/-/vuex-persistedstate-4.1.0.tgz", + "integrity": "sha512-3SkEj4NqwM69ikJdFVw6gObeB0NHyspRYMYkR/EbhR0hbvAKyR5gksVhtAfY1UYuWUOCCA0QNGwv9pOwdj+XUQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "shvl": "^2.0.3" + }, + "peerDependencies": { + "vuex": "^3.0 || ^4.0.0-rc" + } + } + } +} diff --git a/app/package.json b/app/package.json new file mode 100644 index 0000000..930c7e7 --- /dev/null +++ b/app/package.json @@ -0,0 +1,25 @@ +{ + "name": "boss-automation-renderer", + "version": "1.0.8", + "description": "BOSS自动化工具 - 前端渲染进程", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@primevue/themes": "^4.0.0", + "axios": "^1.13.2", + "primeicons": "^7.0.0", + "primevue": "^4.5.4", + "vue": "^3.3.0", + "vue-router": "^4.6.4", + "vuex": "^4.1.0", + "vuex-persistedstate": "^4.1.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.5.0", + "less": "^4.2.0", + "vite": "^5.4.21" + } +} diff --git a/app/router/index.js b/app/router/index.js new file mode 100644 index 0000000..9e1934f --- /dev/null +++ b/app/router/index.js @@ -0,0 +1,99 @@ +/** + * Vue Router 路由配置 + */ +import { createRouter, createWebHashHistory } from 'vue-router'; +import LoginPage from '../views/LoginPage.vue'; +import ConsolePage from '../views/ConsolePage.vue'; +import DeliveryPage from '../views/DeliveryPage.vue'; +import InvitePage from '../views/InvitePage.vue'; +import FeedbackPage from '../views/FeedbackPage.vue'; +import PurchasePage from '../views/PurchasePage.vue'; +import LogPage from '../views/LogPage.vue'; +import store from '../store'; + +const routes = [ + { + path: '/', + redirect: '/console' + }, + { + path: '/login', + name: 'Login', + component: LoginPage, + meta: { + requiresAuth: false, + showSidebar: false + } + }, + { + path: '/console', + name: 'Console', + component: ConsolePage, + meta: { + requiresAuth: true + } + }, + { + path: '/delivery', + name: 'Delivery', + component: DeliveryPage, + meta: { + requiresAuth: true + } + }, + { + path: '/invite', + name: 'Invite', + component: InvitePage, + meta: { + requiresAuth: true + } + }, + { + path: '/feedback', + name: 'Feedback', + component: FeedbackPage, + meta: { + requiresAuth: true + } + }, + { + path: '/log', + name: 'Log', + component: LogPage, + meta: { + requiresAuth: true + } + }, + { + path: '/purchase', + name: 'Purchase', + component: PurchasePage, + meta: { + requiresAuth: true + } + } +]; + +const router = createRouter({ + history: createWebHashHistory(), + routes +}); + +// 路由守卫 +router.beforeEach((to, from, next) => { + const isLoggedIn = store.state.auth.isLoggedIn; + + if (to.meta.requiresAuth && !isLoggedIn) { + // 需要登录但未登录,跳转到登录页 + next('/login'); + } else if (to.path === '/login' && isLoggedIn) { + // 已登录访问登录页,跳转到控制台 + next('/console'); + } else { + next(); + } +}); + +export default router; + diff --git a/app/store/index.js b/app/store/index.js new file mode 100644 index 0000000..92bdc16 --- /dev/null +++ b/app/store/index.js @@ -0,0 +1,49 @@ +/** + * Vuex Store 主入口 + */ +import { createStore } from 'vuex'; +import app from './modules/app'; +import auth from './modules/auth'; +import mqtt from './modules/mqtt'; +import task from './modules/task'; +import system from './modules/system'; +import platform from './modules/platform'; +import qrCode from './modules/qrCode'; +import update from './modules/update'; +import delivery from './modules/delivery'; +import log from './modules/log'; +import config from './modules/config'; +import createPersistedState from 'vuex-persistedstate' + +const store = createStore({ + modules: { + app, + auth, + mqtt, + task, + system, + platform, + qrCode, + update, + delivery, + log, + config + }, + plugins: [createPersistedState({ + key: 'boss-auto-app', + storage: window.localStorage, // 或 sessionStorage + paths: ['auth', 'config'] // 只持久化这些 + })] +}); + +// 调试:输出localStorage中保存的持久化数据 +console.log('[Store] localStorage中保存的数据:', { + 'boss-auto-app': localStorage.getItem('boss-auto-app'), + 'api_token': localStorage.getItem('api_token') +}); + +// 应用启动时,从 store 恢复登录状态 +store.dispatch('auth/restoreLoginStatus'); + +export default store; + diff --git a/app/store/modules/app.js b/app/store/modules/app.js new file mode 100644 index 0000000..e3c8458 --- /dev/null +++ b/app/store/modules/app.js @@ -0,0 +1,28 @@ +/** + * 应用全局状态管理 + */ +export default { + namespaced: true, + state: { + currentVersion: '1.0.0', + isLoading: true, + startTime: Date.now() + }, + mutations: { + SET_VERSION(state, version) { + state.currentVersion = version; + }, + SET_LOADING(state, loading) { + state.isLoading = loading; + } + }, + actions: { + setVersion({ commit }, version) { + commit('SET_VERSION', version); + }, + setLoading({ commit }, loading) { + commit('SET_LOADING', loading); + } + } +}; + diff --git a/app/store/modules/auth.js b/app/store/modules/auth.js new file mode 100644 index 0000000..ba9f0ef --- /dev/null +++ b/app/store/modules/auth.js @@ -0,0 +1,291 @@ +/** + * 用户认证状态管理 + */ +import { login as apiLogin, setToken, clearToken, getToken } from '../../utils/api'; + +export default { + namespaced: true, + state: { + email: '', + password: '', + isLoggedIn: false, + loginButtonText: '登录', + userName: '', + remainingDays: null, + snCode: '', + deviceId: null, + platformType: null, + userId: null, + listenChannel: '-', + userLoggedOut: false, + rememberMe: false, + userMenuInfo: { + userName: '', + snCode: '' + } + }, + mutations: { + SET_EMAIL(state, email) { + state.email = email; + }, + SET_PASSWORD(state, password) { + state.password = password; + }, + SET_LOGGED_IN(state, isLoggedIn) { + state.isLoggedIn = isLoggedIn; + }, + SET_LOGIN_BUTTON_TEXT(state, text) { + state.loginButtonText = text; + }, + SET_USER_NAME(state, userName) { + state.userName = userName; + state.userMenuInfo.userName = userName; + }, + SET_REMAINING_DAYS(state, days) { + state.remainingDays = days; + }, + SET_SN_CODE(state, snCode) { + state.snCode = snCode; + state.userMenuInfo.snCode = snCode; + state.listenChannel = snCode ? `request_${snCode}` : '-'; + }, + SET_DEVICE_ID(state, deviceId) { + state.deviceId = deviceId; + }, + SET_PLATFORM_TYPE(state, platformType) { + state.platformType = platformType; + }, + SET_USER_ID(state, userId) { + state.userId = userId; + }, + SET_USER_LOGGED_OUT(state, value) { + state.userLoggedOut = value; + }, + SET_REMEMBER_ME(state, value) { + state.rememberMe = value; + }, + SET_USER_MENU_INFO(state, info) { + state.userMenuInfo = { ...state.userMenuInfo, ...info }; + }, + CLEAR_AUTH(state) { + state.isLoggedIn = false; + state.loginButtonText = '登录'; + state.listenChannel = '-'; + state.snCode = ''; + state.deviceId = null; + state.platformType = null; + state.userId = null; + state.userName = ''; + state.remainingDays = null; + state.userMenuInfo = { + userName: '', + snCode: '' + }; + state.password = ''; + state.userLoggedOut = true; + } + }, + actions: { + /** + * 从 store 恢复登录状态 + * 状态已通过持久化插件从 localStorage 恢复 + */ + async restoreLoginStatus({ commit, state }) { + try { + const token = getToken(); + + console.log('[Auth Store] 尝试恢复登录状态:', { + hasToken: !!token, + userLoggedOut: state.userLoggedOut, + snCode: state.snCode, + userName: state.userName, + isLoggedIn: state.isLoggedIn, + persistedState: { + email: state.email, + platformType: state.platformType, + userId: state.userId, + deviceId: state.deviceId + } + }); + + // 如果有 token 且用户没有手动退出,且有用户信息,则恢复登录状态 + if (token && !state.userLoggedOut && (state.snCode || state.userName)) { + console.log('[Auth Store] 满足恢复登录条件,开始恢复...'); + + commit('SET_LOGGED_IN', true); + commit('SET_LOGIN_BUTTON_TEXT', '注销登录'); + + // 恢复登录状态后,同步用户信息到主进程(确保 platform_type 等数据同步) + if (window.electronAPI && window.electronAPI.invoke) { + try { + await window.electronAPI.invoke('auth:sync-user-info', { + platform_type: state.platformType || null, + sn_code: state.snCode || '', + user_id: state.userId || null, + user_name: state.userName || '', + device_id: state.deviceId || null + }); + console.log('[Auth Store] 用户信息已同步到主进程'); + + // 恢复登录状态后,手动连接 MQTT + if (state.snCode) { + try { + const mqttResult = await window.electronAPI.invoke('mqtt:connect', state.snCode); + console.log('[Auth Store] 恢复登录后 MQTT 连接结果:', mqttResult); + + // 如果连接成功,立即更新MQTT状态到store + if (mqttResult && mqttResult.success && mqttResult.isConnected) { + commit('mqtt/SET_CONNECTED', true, { root: true }); + commit('mqtt/SET_STATUS', '已连接', { root: true }); + console.log('[Auth Store] 恢复登录后 MQTT 状态已更新到 store'); + } + } catch (mqttError) { + console.warn('[Auth Store] 恢复登录后 MQTT 连接失败:', mqttError); + } + } + } catch (error) { + console.warn('[Auth Store] 恢复登录状态时同步用户信息到主进程失败:', error); + } + } else { + console.warn('[Auth Store] electronAPI 不可用,无法同步用户信息到主进程'); + } + + console.log('[Auth Store] 登录状态恢复完成'); + } else { + console.log('[Auth Store] 不满足恢复登录条件,跳过恢复'); + } + } catch (error) { + console.error('[Auth Store] 恢复登录状态失败:', error); + } + }, + + /** + * 用户登录 + * @param {Object} context Vuex context + * @param {Object} payload 登录参数 + * @param {string} payload.email 邮箱 + * @param {string} payload.password 密码 + * @param {string} payload.deviceId 设备ID(可选) + * @returns {Promise} 登录结果 + */ + async login({ commit }, { email, password, deviceId = null }) { + try { + // 调用登录接口 + const response = await apiLogin(email, password, deviceId); + + if (response.code === 0 && response.data) { + // 登录成功,更新状态 + const { token, user, device_id } = response.data; + + // token 已在 apiLogin 中设置,这里不需要重复设置 + + + + commit('SET_EMAIL', email); + commit('SET_PASSWORD', password); + commit('SET_LOGGED_IN', true); + commit('SET_LOGIN_BUTTON_TEXT', '注销登录'); + commit('SET_USER_NAME', user?.name || email); + commit('SET_REMAINING_DAYS', user?.remaining_days || null); + commit('SET_SN_CODE', user?.sn_code || ''); + commit('SET_DEVICE_ID', device_id || deviceId); + commit('SET_PLATFORM_TYPE', user?.platform_type || null); + commit('SET_USER_ID', user?.id || null); + commit('SET_USER_LOGGED_OUT', false); + + // 登录成功后,同步更新 platform store 的显示名称 + if (user?.platform_type) { + const platformNames = { + 'boss': 'BOSS直聘', + 'liepin': '猎聘', + 'zhilian': '智联招聘', + '1': 'BOSS直聘' + }; + const displayName = platformNames[user.platform_type] || user.platform_type; + commit('platform/SET_CURRENT_PLATFORM', displayName, { root: true }); + + // 初始化平台登录状态为未登录(需要用户通过平台登录) + commit('platform/SET_PLATFORM_LOGIN_STATUS', { + status: '未登录', + color: '#FF9800', + isLoggedIn: false + }, { root: true }); + console.log('[Auth Store] 平台信息已初始化'); + } + + // 同步用户信息到主进程的 authService(确保主进程也能获取到 platform_type 和 token) + if (window.electronAPI && window.electronAPI.invoke) { + try { + await window.electronAPI.invoke('auth:sync-user-info', { + token: token, // 同步 token 到主进程 + platform_type: user?.platform_type || null, + sn_code: user?.sn_code || '', + user_id: user?.id || null, + user_name: user?.name || email, // 修复:使用 email 而不是 phone + device_id: device_id || deviceId + }); + + // 登录成功后,手动连接 MQTT + if (user?.sn_code) { + try { + const mqttResult = await window.electronAPI.invoke('mqtt:connect', user.sn_code); + console.log('[Auth Store] MQTT 连接结果:', mqttResult); + + // 如果连接成功,立即更新MQTT状态到store + if (mqttResult && mqttResult.success && mqttResult.isConnected) { + commit('mqtt/SET_CONNECTED', true, { root: true }); + commit('mqtt/SET_STATUS', '已连接', { root: true }); + console.log('[Auth Store] MQTT 状态已更新到 store'); + } + } catch (mqttError) { + console.warn('[Auth Store] MQTT 连接失败:', mqttError); + } + } + } catch (error) { + console.warn('[Auth Store] 同步用户信息到主进程失败:', error); + } + } + + return { + success: true, + data: response.data + }; + } else { + // 登录失败 + return { + success: false, + error: response.message || '登录失败' + }; + } + } catch (error) { + console.error('[Auth Store] 登录失败:', error); + return { + success: false, + error: error.message || '登录过程中发生错误' + }; + } + }, + logout({ commit }) { + commit('CLEAR_AUTH'); + clearToken(); + }, + updateUserInfo({ commit }, userInfo) { + if (userInfo.name) commit('SET_USER_NAME', userInfo.name); + if (userInfo.sn_code) commit('SET_SN_CODE', userInfo.sn_code); + if (userInfo.device_id) commit('SET_DEVICE_ID', userInfo.device_id); + if (userInfo.remaining_days !== undefined) { + commit('SET_REMAINING_DAYS', userInfo.remaining_days); + } + } + }, + getters: { + isLoggedIn: state => state.isLoggedIn, + userInfo: state => ({ + email: state.email, + userName: state.userName, + snCode: state.snCode, + deviceId: state.deviceId, + remainingDays: state.remainingDays + }) + } +}; \ No newline at end of file diff --git a/app/store/modules/config.js b/app/store/modules/config.js new file mode 100644 index 0000000..f5bf23d --- /dev/null +++ b/app/store/modules/config.js @@ -0,0 +1,72 @@ +/** + * 应用配置状态管理 + * 统一管理所有配置,不再使用 localStorage + */ +export default { + namespaced: true, + state: { + // 用户相关配置 + email: '', + userLoggedOut: false, + rememberMe: false, + + // 应用设置 + appSettings: { + autoStart: false, + startOnBoot: false, + enableNotifications: true, + soundAlert: true + }, + + // 设备ID(客户端生成,需要持久化) + deviceId: null + }, + mutations: { + SET_EMAIL(state, email) { + state.email = email; + }, + SET_USER_LOGGED_OUT(state, value) { + state.userLoggedOut = value; + }, + SET_REMEMBER_ME(state, value) { + state.rememberMe = value; + }, + SET_APP_SETTINGS(state, settings) { + state.appSettings = { ...state.appSettings, ...settings }; + }, + UPDATE_APP_SETTING(state, { key, value }) { + state.appSettings[key] = value; + }, + SET_DEVICE_ID(state, deviceId) { + state.deviceId = deviceId; + } + }, + actions: { + setEmail({ commit }, email) { + commit('SET_EMAIL', email); + }, + setUserLoggedOut({ commit }, value) { + commit('SET_USER_LOGGED_OUT', value); + }, + setRememberMe({ commit }, value) { + commit('SET_REMEMBER_ME', value); + }, + updateAppSettings({ commit }, settings) { + commit('SET_APP_SETTINGS', settings); + }, + updateAppSetting({ commit }, { key, value }) { + commit('UPDATE_APP_SETTING', { key, value }); + }, + setDeviceId({ commit }, deviceId) { + commit('SET_DEVICE_ID', deviceId); + } + }, + getters: { + email: state => state.email, + userLoggedOut: state => state.userLoggedOut, + rememberMe: state => state.rememberMe, + appSettings: state => state.appSettings, + deviceId: state => state.deviceId + } +}; + diff --git a/app/store/modules/delivery.js b/app/store/modules/delivery.js new file mode 100644 index 0000000..4350ac4 --- /dev/null +++ b/app/store/modules/delivery.js @@ -0,0 +1,165 @@ +/** + * 投递配置状态管理 + */ +export default { + namespaced: true, + state: { + deliveryStats: { + todayCount: 0, + weekCount: 0, + monthCount: 0, + totalCount: 0, + successCount: 0, + failedCount: 0, + pendingCount: 0, + interviewCount: 0, + successRate: 0, + interviewRate: 0 + }, + deliveryConfig: { + autoDelivery: false, + interval: 30, + minSalary: 15000, + maxSalary: 30000, + scrollPages: 3, + maxPerBatch: 10, + filterKeywords: '', + excludeKeywords: '', + startTime: '09:00', + endTime: '18:00', + workdaysOnly: true + } + }, + mutations: { + SET_DELIVERY_STATS(state, stats) { + state.deliveryStats = { ...state.deliveryStats, ...stats }; + }, + SET_DELIVERY_CONFIG(state, config) { + state.deliveryConfig = { ...state.deliveryConfig, ...config }; + }, + UPDATE_DELIVERY_CONFIG(state, { key, value }) { + state.deliveryConfig[key] = value; + } + }, + actions: { + updateDeliveryStats({ commit }, stats) { + commit('SET_DELIVERY_STATS', stats); + }, + async loadDeliveryStats({ commit, rootState }) { + try { + const snCode = rootState.auth.snCode; + if (!snCode) { + console.warn('[Delivery Store] 没有 snCode,无法加载统计数据'); + return { success: false, error: '请先登录' }; + } + + // 动态导入 API + const applyRecordsAPI = (await import('../../api/apply_records.js')).default; + const result = await applyRecordsAPI.getStatistics(snCode); + + // 后端返回格式:{ code: 0, data: {...} } + if (result && result.code === 0 && result.data) { + commit('SET_DELIVERY_STATS', result.data); + return { success: true }; + } else { + const errorMsg = result?.message || '加载统计失败'; + console.error('[Delivery Store] 加载统计失败:', result); + return { success: false, error: errorMsg }; + } + } catch (error) { + console.error('[Delivery Store] 加载统计失败:', error); + return { success: false, error: error.message || '加载统计失败' }; + } + }, + updateDeliveryConfig({ commit }, { key, value }) { + commit('UPDATE_DELIVERY_CONFIG', { key, value }); + }, + setDeliveryConfig({ commit }, config) { + commit('SET_DELIVERY_CONFIG', config); + }, + async saveDeliveryConfig({ state, rootState }) { + try { + const snCode = rootState.auth.snCode; + if (!snCode) { + return { success: false, error: '请先登录' }; + } + + // 将前端配置格式转换为后端格式 + const deliverConfig = { + auto_delivery: state.deliveryConfig.autoDelivery || false, + interval: state.deliveryConfig.interval || 30, + min_salary: state.deliveryConfig.minSalary || 0, + max_salary: state.deliveryConfig.maxSalary || 0, + scroll_pages: state.deliveryConfig.scrollPages || 3, + max_per_batch: state.deliveryConfig.maxPerBatch || 10, + filter_keywords: state.deliveryConfig.filterKeywords || '', + exclude_keywords: state.deliveryConfig.excludeKeywords || '', + start_time: state.deliveryConfig.startTime || '09:00', + end_time: state.deliveryConfig.endTime || '18:00', + // 将布尔值转换为数字 1/0,后端期望数字类型 + workdays_only: state.deliveryConfig.workdaysOnly ? 1 : 0 + }; + + // 动态导入 API + const deliveryConfigAPI = (await import('../../api/delivery_config.js')).default; + const result = await deliveryConfigAPI.saveConfig(snCode, deliverConfig); + + // 后端返回格式:{ code: 0, message: 'success', data: {...} } + // 或者:{ success: true, ... } + if (result && (result.code === 0 || result.success === true)) { + return { success: true }; + } else { + const errorMsg = result?.message || result?.error || '保存失败'; + console.error('[Delivery Store] 保存配置失败:', result); + return { success: false, error: errorMsg }; + } + } catch (error) { + console.error('[Delivery Store] 保存配置失败:', error); + return { success: false, error: error.message || '保存配置失败' }; + } + }, + async loadDeliveryConfig({ commit, rootState }) { + try { + const snCode = rootState.auth.snCode; + if (!snCode) { + // 如果没有登录,使用默认配置 + return { success: true }; + } + + // 动态导入 API + const deliveryConfigAPI = (await import('../../api/delivery_config.js')).default; + const result = await deliveryConfigAPI.getConfig(snCode); + + // 后端返回格式:{ code: 0, data: { deliver_config: {...} } } + if (result && result.data && result.data.deliver_config) { + const deliverConfig = result.data.deliver_config; + // 将后端格式转换为前端格式 + const frontendConfig = { + autoDelivery: deliverConfig.auto_delivery || false, + interval: deliverConfig.interval || 30, + minSalary: deliverConfig.min_salary || 0, + maxSalary: deliverConfig.max_salary || 0, + scrollPages: deliverConfig.scroll_pages || 3, + maxPerBatch: deliverConfig.max_per_batch || 10, + filterKeywords: deliverConfig.filter_keywords || '', + excludeKeywords: deliverConfig.exclude_keywords || '', + startTime: deliverConfig.start_time || '09:00', + endTime: deliverConfig.end_time || '18:00', + // 将数字 1/0 转换为布尔值,前端使用布尔值 + workdaysOnly: deliverConfig.workdays_only === 1 || deliverConfig.workdays_only === true + }; + commit('SET_DELIVERY_CONFIG', frontendConfig); + return { success: true }; + } else { + // 如果获取失败,使用默认配置 + return { success: true }; + } + } catch (error) { + console.error('[Delivery Store] 加载配置失败:', error); + // 如果加载失败,使用默认配置 + return { success: true }; + } + } + } +}; + diff --git a/app/store/modules/log.js b/app/store/modules/log.js new file mode 100644 index 0000000..4986da5 --- /dev/null +++ b/app/store/modules/log.js @@ -0,0 +1,63 @@ +/** + * 日志状态管理 + */ +export default { + namespaced: true, + state: { + logs: [], + maxLogs: 1000 + }, + mutations: { + ADD_LOG(state, logEntry) { + state.logs.push(logEntry); + if (state.logs.length > state.maxLogs) { + state.logs.shift(); + } + }, + CLEAR_LOGS(state) { + state.logs = []; + } + }, + actions: { + addLog({ commit }, { level, message }) { + const timestamp = new Date().toLocaleString(); + const logEntry = { + time: timestamp, + level: level.toUpperCase(), + message: message + }; + commit('ADD_LOG', logEntry); + }, + clearLogs({ commit }) { + commit('CLEAR_LOGS'); + commit('ADD_LOG', { + time: new Date().toLocaleString(), + level: 'INFO', + message: '日志已清空' + }); + }, + exportLogs({ state, commit }) { + const logText = state.logs.map(log => + `[${log.time}] [${log.level}] ${log.message}` + ).join('\n'); + + const blob = new Blob([logText], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `logs_${new Date().toISOString().replace(/[:.]/g, '-')}.txt`; + a.click(); + URL.revokeObjectURL(url); + + commit('ADD_LOG', { + time: new Date().toLocaleString(), + level: 'INFO', + message: '日志已导出' + }); + } + }, + getters: { + logEntries: state => state.logs + } +}; + diff --git a/app/store/modules/mqtt.js b/app/store/modules/mqtt.js new file mode 100644 index 0000000..4d6544a --- /dev/null +++ b/app/store/modules/mqtt.js @@ -0,0 +1,25 @@ +/** + * MQTT 连接状态管理 + */ +export default { + namespaced: true, + state: { + isConnected: false, + mqttStatus: '未连接' + }, + mutations: { + SET_CONNECTED(state, isConnected) { + state.isConnected = isConnected; + }, + SET_STATUS(state, status) { + state.mqttStatus = status; + } + }, + actions: { + setConnected({ commit }, isConnected) { + commit('SET_CONNECTED', isConnected); + commit('SET_STATUS', isConnected ? '已连接' : '未连接'); + } + } +}; + diff --git a/app/store/modules/platform.js b/app/store/modules/platform.js new file mode 100644 index 0000000..8d6530b --- /dev/null +++ b/app/store/modules/platform.js @@ -0,0 +1,31 @@ +/** + * 平台信息状态管理 + */ +export default { + namespaced: true, + state: { + currentPlatform: '-', + platformLoginStatus: '-', + platformLoginStatusColor: '#FF9800', + isPlatformLoggedIn: false + }, + mutations: { + SET_CURRENT_PLATFORM(state, platform) { + state.currentPlatform = platform; + }, + SET_PLATFORM_LOGIN_STATUS(state, { status, color, isLoggedIn }) { + state.platformLoginStatus = status; + state.platformLoginStatusColor = color || '#FF9800'; + state.isPlatformLoggedIn = isLoggedIn || false; + } + }, + actions: { + updatePlatform({ commit }, platform) { + commit('SET_CURRENT_PLATFORM', platform); + }, + updatePlatformLoginStatus({ commit }, { status, color, isLoggedIn }) { + commit('SET_PLATFORM_LOGIN_STATUS', { status, color, isLoggedIn }); + } + } +}; + diff --git a/app/store/modules/qrCode.js b/app/store/modules/qrCode.js new file mode 100644 index 0000000..574bfe5 --- /dev/null +++ b/app/store/modules/qrCode.js @@ -0,0 +1,42 @@ +/** + * 二维码状态管理 + */ +export default { + namespaced: true, + state: { + qrCodeUrl: null, + qrCodeOosUrl: null, + qrCodeCountdown: 0, + qrCodeCountdownActive: false, + qrCodeRefreshCount: 0, + qrCodeExpired: false + }, + mutations: { + SET_QR_CODE_URL(state, { url, oosUrl }) { + state.qrCodeUrl = url; + state.qrCodeOosUrl = oosUrl || null; + }, + SET_QR_CODE_COUNTDOWN(state, { countdown, isActive, refreshCount, isExpired }) { + state.qrCodeCountdown = countdown || 0; + state.qrCodeCountdownActive = isActive || false; + state.qrCodeRefreshCount = refreshCount || 0; + state.qrCodeExpired = isExpired || false; + }, + CLEAR_QR_CODE(state) { + state.qrCodeUrl = null; + state.qrCodeOosUrl = null; + } + }, + actions: { + setQrCode({ commit }, { url, oosUrl }) { + commit('SET_QR_CODE_URL', { url, oosUrl }); + }, + setQrCodeCountdown({ commit }, data) { + commit('SET_QR_CODE_COUNTDOWN', data); + }, + clearQrCode({ commit }) { + commit('CLEAR_QR_CODE'); + } + } +}; + diff --git a/app/store/modules/system.js b/app/store/modules/system.js new file mode 100644 index 0000000..853381c --- /dev/null +++ b/app/store/modules/system.js @@ -0,0 +1,41 @@ +/** + * 系统信息状态管理 + */ +export default { + namespaced: true, + state: { + uptime: '0分钟', + cpuUsage: '0%', + memUsage: '0MB', + deviceId: '-' + }, + mutations: { + SET_UPTIME(state, uptime) { + state.uptime = uptime; + }, + SET_CPU_USAGE(state, usage) { + state.cpuUsage = usage; + }, + SET_MEM_USAGE(state, usage) { + state.memUsage = usage; + }, + SET_DEVICE_ID(state, deviceId) { + state.deviceId = deviceId || '-'; + } + }, + actions: { + updateUptime({ commit }, uptime) { + commit('SET_UPTIME', uptime); + }, + updateCpuUsage({ commit }, usage) { + commit('SET_CPU_USAGE', `${usage.toFixed(1)}%`); + }, + updateMemUsage({ commit }, usage) { + commit('SET_MEM_USAGE', `${usage}MB`); + }, + updateDeviceId({ commit }, deviceId) { + commit('SET_DEVICE_ID', deviceId); + } + } +}; + diff --git a/app/store/modules/task.js b/app/store/modules/task.js new file mode 100644 index 0000000..280babc --- /dev/null +++ b/app/store/modules/task.js @@ -0,0 +1,81 @@ +/** + * 设备工作状态和任务统计管理 + */ +export default { + namespaced: true, + state: { + // 设备工作状态 + displayText: null, // 服务端格式化好的显示文本 + nextExecuteTimeText: null, // 服务端格式化好的下次执行时间文本 + currentActivity: null, // 当前活动(任务或指令) + pendingQueue: null, // 待执行队列信息 + deviceStatus: null, // 设备状态 + + // 任务统计 + taskStats: { + todayCount: 0, + weekCount: 0, + monthCount: 0, + totalCount: 0, + completedCount: 0, + runningCount: 0, + pendingCount: 0, + failedCount: 0, + completionRate: 0 + } + }, + mutations: { + SET_DEVICE_WORK_STATUS(state, workStatus) { + state.displayText = workStatus.displayText || null; + state.nextExecuteTimeText = workStatus.pendingQueue?.nextExecuteTimeText || null; + state.currentActivity = workStatus.currentActivity || null; + state.pendingQueue = workStatus.pendingQueue || null; + state.deviceStatus = workStatus.deviceStatus || null; + + }, + CLEAR_DEVICE_WORK_STATUS(state) { + state.displayText = null; + state.nextExecuteTimeText = null; + state.currentActivity = null; + state.pendingQueue = null; + state.deviceStatus = null; + }, + SET_TASK_STATS(state, stats) { + state.taskStats = { ...state.taskStats, ...stats }; + } + }, + actions: { + updateDeviceWorkStatus({ commit }, workStatus) { + commit('SET_DEVICE_WORK_STATUS', workStatus); + }, + clearDeviceWorkStatus({ commit }) { + commit('CLEAR_DEVICE_WORK_STATUS'); + }, + async loadTaskStats({ commit, rootState }) { + try { + const snCode = rootState.auth.snCode; + if (!snCode) { + console.warn('[Task Store] 没有 snCode,无法加载任务统计'); + return { success: false, error: '请先登录' }; + } + + // 调用任务统计接口 + const result = await window.electronAPI.invoke('http:get', '/task/statistics', { sn_code: snCode }); + + // 后端返回格式:{ code: 0, data: {...} } + if (result && result.code === 0 && result.data) { + commit('SET_TASK_STATS', result.data); + return { success: true }; + } else { + const errorMsg = result?.message || '加载任务统计失败'; + console.error('[Task Store] 加载任务统计失败:', result); + return { success: false, error: errorMsg }; + } + } catch (error) { + console.error('[Task Store] 加载任务统计失败:', error); + return { success: false, error: error.message || '加载任务统计失败' }; + } + } + } +}; + diff --git a/app/store/modules/update.js b/app/store/modules/update.js new file mode 100644 index 0000000..c531801 --- /dev/null +++ b/app/store/modules/update.js @@ -0,0 +1,55 @@ +/** + * 更新状态管理 + */ +export default { + namespaced: true, + state: { + updateDialogVisible: false, + updateInfo: null, + updateProgress: 0, + isDownloading: false, + downloadState: { + progress: 0, + downloadedBytes: 0, + totalBytes: 0 + } + }, + mutations: { + SET_UPDATE_DIALOG_VISIBLE(state, visible) { + state.updateDialogVisible = visible; + }, + SET_UPDATE_INFO(state, info) { + state.updateInfo = info; + }, + SET_UPDATE_PROGRESS(state, progress) { + state.updateProgress = progress; + }, + SET_DOWNLOADING(state, downloading) { + state.isDownloading = downloading; + }, + SET_DOWNLOAD_STATE(state, downloadState) { + state.downloadState = { ...state.downloadState, ...downloadState }; + } + }, + actions: { + showUpdateDialog({ commit }) { + commit('SET_UPDATE_DIALOG_VISIBLE', true); + }, + hideUpdateDialog({ commit }) { + commit('SET_UPDATE_DIALOG_VISIBLE', false); + }, + setUpdateInfo({ commit }, info) { + commit('SET_UPDATE_INFO', info); + }, + setUpdateProgress({ commit }, progress) { + commit('SET_UPDATE_PROGRESS', progress); + }, + setDownloading({ commit }, downloading) { + commit('SET_DOWNLOADING', downloading); + }, + setDownloadState({ commit }, state) { + commit('SET_DOWNLOAD_STATE', state); + } + } +}; + diff --git a/app/utils/api.js b/app/utils/api.js new file mode 100644 index 0000000..1b83d7d --- /dev/null +++ b/app/utils/api.js @@ -0,0 +1,409 @@ +/** + * 前端 API 工具类 + * 用于在渲染进程(Vue)中直接调用后端 API + */ + +// API 基础 URL(从环境变量或配置中获取) + +/** + * HTTP 请求工具 + */ +class ApiClient { + constructor() { + + this.token = this.getToken(); + // 缓存 electronAPI 可用状态,避免每次检查 + this._electronAPIChecked = false; + this._electronAPIAvailable = false; + } + + /** + * 检查 window.electronAPI 是否可用(快速检查,不等待) + * @param {boolean} forceCheck 强制重新检查(默认 false) + * @returns {boolean} 是否可用 + */ + isElectronAPIAvailable(forceCheck = false) { + // 如果已经检查过且不需要强制检查,直接返回缓存结果 + if (this._electronAPIChecked && !forceCheck) { + return this._electronAPIAvailable; + } + + // 快速检查 + const available = !!(window.electronAPI && window.electronAPI.http); + + // 缓存结果 + this._electronAPIChecked = true; + this._electronAPIAvailable = available; + + return available; + } + + /** + * 等待 window.electronAPI 可用(仅在首次检查时等待) + * @param {number} maxWaitTime 最大等待时间(毫秒),默认500ms(减少等待时间) + * @param {number} checkInterval 检查间隔(毫秒),默认50ms(加快检查频率) + * @returns {Promise} 是否可用 + */ + async waitForElectronAPI(maxWaitTime = 500, checkInterval = 50) { + // 如果已经确认可用,直接返回 + if (this.isElectronAPIAvailable()) { + return true; + } + + // 如果已经检查过但不可用,直接返回 false(不重复等待) + if (this._electronAPIChecked && !this._electronAPIAvailable) { + return false; + } + + // 首次检查时,短暂等待(preload 脚本是同步的,通常立即可用) + const startTime = Date.now(); + return new Promise((resolve) => { + const check = () => { + if (window.electronAPI && window.electronAPI.http) { + this._electronAPIChecked = true; + this._electronAPIAvailable = true; + resolve(true); + return; + } + + const elapsed = Date.now() - startTime; + if (elapsed >= maxWaitTime) { + // 超时后标记为不可用 + this._electronAPIChecked = true; + this._electronAPIAvailable = false; + resolve(false); + return; + } + + setTimeout(check, checkInterval); + }; + check(); + }); + } + + /** + * 使用 electronAPI.http 发送请求 + * @param {string} method HTTP 方法 + * @param {string} endpoint API 端点 + * @param {Object} data 请求数据 + * @returns {Promise} + */ + /** + * 将响应式对象(Proxy)转换为普通对象,以便通过 IPC 传递 + * @param {any} obj 要转换的对象 + * @returns {any} 普通对象 + */ + toPlainObject(obj) { + if (obj === null || obj === undefined) { + return obj; + } + + // 如果是基本类型,直接返回 + if (typeof obj !== 'object') { + return obj; + } + + // 如果是 Date 对象,转换为 ISO 字符串 + if (obj instanceof Date) { + return obj.toISOString(); + } + + // 如果是 RegExp 对象,转换为字符串 + if (obj instanceof RegExp) { + return obj.toString(); + } + + // 尝试使用 JSON 序列化/反序列化(最可靠的方法) + try { + return JSON.parse(JSON.stringify(obj)); + } catch (e) { + // 如果 JSON 序列化失败,使用递归方法 + console.warn('[API] JSON 序列化失败,使用递归方法:', e); + + // 如果是数组,递归处理每个元素 + if (Array.isArray(obj)) { + return obj.map(item => this.toPlainObject(item)); + } + + // 如果是对象,递归处理每个属性 + const plainObj = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + try { + plainObj[key] = this.toPlainObject(obj[key]); + } catch (err) { + // 如果无法访问属性,跳过 + console.warn(`[API] 无法访问属性 ${key}:`, err); + } + } + } + return plainObj; + } + } + + async requestWithElectronAPI(method, endpoint, data = null) { + const fullEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`; + + // 将响应式对象转换为普通对象(避免 Proxy 无法序列化的问题) + const plainData = data ? this.toPlainObject(data) : null; + + // 主进程不再缓存 token,每次请求都从浏览器端实时获取,这里不需要同步 + + try { + let result; + + if (method === 'GET') { + result = await window.electronAPI.http.get(fullEndpoint, plainData || {}); + } else { + result = await window.electronAPI.http.post(fullEndpoint, plainData || {}); + } + // 输出详细的响应日志 + console.log('requestWithElectronAPI result', method, fullEndpoint ,plainData, result); + + + return result; + } catch (error) { + // 输出详细的错误日志 + console.error('========================================'); + console.error(`[API错误] ${method}`); + console.error(`[错误信息]`, error.message || error); + console.error(`[错误堆栈]`, error.stack); + console.error('========================================'); + throw error; + } + } + + /** + * 设置认证 token + * @param {string} token + */ + setToken(token) { + this.token = token; + if (token) { + localStorage.setItem('api_token', token); + // 主进程不再缓存 token,每次请求都从浏览器端实时获取 + } else { + localStorage.removeItem('api_token'); + } + } + + /** + * 获取认证 token + * @returns {string|null} + */ + getToken() { + if (!this.token) { + this.token = localStorage.getItem('api_token'); + } + return this.token; + } + + /** + * 构建请求头 + * @returns {Object} + */ + buildHeaders() { + const headers = { + 'Content-Type': 'application/json' + }; + + const token = this.getToken(); + if (token) { + headers['applet-token'] = `${token}`; + } + + return headers; + } + + /** + * 处理错误 + * @param {Error|Response} error + * @returns {Promise} + */ + async handleError(error) { + if (error.response) { + // 服务器返回了错误响应 + const { status, data } = error.response; + return { + code: status, + message: data?.message || `请求失败: ${status}`, + data: null + }; + } else if (error.request) { + // 请求已发出但没有收到响应 + return { + code: -1, + message: '网络错误,请检查网络连接', + data: null + }; + } else { + // 其他错误 + return { + code: -1, + message: error.message || '未知错误', + data: null + }; + } + } + + /** + * 显示错误提示 + * @param {string} message 错误消息 + */ + showError(message) { + // 优先使用 electronAPI 的日志功能 + if (window.electronAPI && typeof window.electronAPI.log === 'function') { + try { + window.electronAPI.log('error', `[API错误] ${message}`); + } catch (error) { + console.error('调用 electronAPI.log 失败:', error); + } + } + + console.error('[API错误]', message); + + } + + /** + * 确保 electronAPI 可用并发送请求 + * @param {string} method HTTP 方法 + * @param {string} endpoint API 端点 + * @param {Object} data 请求数据 + * @returns {Promise} + */ + async _ensureElectronAPIAndRequest(method, endpoint, data = null) { + // 检查 electronAPI 是否可用 + if (!this.isElectronAPIAvailable()) { + // 首次检查时,短暂等待一次 + const electronAPIAvailable = await this.waitForElectronAPI(); + if (!electronAPIAvailable) { + const errorMsg = 'electronAPI 不可用,无法发送请求'; + this.showError(errorMsg); + throw new Error(errorMsg); + } + } + + // 发送请求 + const result = await this.requestWithElectronAPI(method, endpoint, data); + + // 检查返回结果,如果 code 不为 0,显示错误提示 + if (result && result.code !== undefined && result.code !== 0) { + const errorMsg = result.message || '请求失败'; + this.showError(errorMsg); + } + + return result; + } + + /** + * 发送 HTTP 请求 + * @param {string} method HTTP 方法 + * @param {string} endpoint API 端点 + * @param {Object} data 请求数据 + * @returns {Promise} + */ + async request(method, endpoint, data = null) { + try { + const res = await this._ensureElectronAPIAndRequest(method, endpoint, data); + return res + } catch (error) { + const errorMsg = error.message || '请求失败'; + this.showError(errorMsg); + throw error; + } + } + + /** + * GET 请求 + * @param {string} endpoint API 端点 + * @param {Object} params 查询参数 + * @returns {Promise} + */ + async get(endpoint, params = {}) { + try { + return await this._ensureElectronAPIAndRequest('GET', endpoint, params); + } catch (error) { + const errorMsg = error.message || '请求失败'; + this.showError(errorMsg); + throw error; + } + } + + /** + * POST 请求 + * @param {string} endpoint API 端点 + * @param {Object} data 请求数据 + * @returns {Promise} + */ + async post(endpoint, data = {}) { + try { + const result = await this.request('POST', endpoint, data); + return result; + } catch (error) { + console.error(`[ApiClient] POST 请求失败:`, { error: error.message, endpoint, data }); + const errorResult = await this.handleError(error); + // 如果 handleError 返回了错误结果,显示错误提示 + if (errorResult && errorResult.code !== 0) { + this.showError(errorResult.message || '请求失败'); + } + return errorResult; + } + } + +} + +// 创建默认实例 +const apiClient = new ApiClient(); + +/** + * 用户登录 + * @param {string} email 邮箱 + * @param {string} password 密码 + * @param {string} deviceId 设备ID(可选) + * @returns {Promise} + */ +export async function login(email, password, deviceId = null) { + const requestData = { + login_name: email, // 后端使用 login_name 字段 + password + }; + + if (deviceId) { + requestData.device_id = deviceId; + } + + const response = await apiClient.post('/user/login', requestData); + + // 如果登录成功,保存 token + if (response.code === 0 && response.data && response.data.token) { + apiClient.setToken(response.data.token); + } + + return response; +} + +/** + * 设置 API token(用于其他地方设置的 token) + * @param {string} token + */ +export function setToken(token) { + apiClient.setToken(token); +} + +/** + * 获取 API token + * @returns {string|null} + */ +export function getToken() { + return apiClient.getToken(); +} + +/** + * 清除 API token + */ +export function clearToken() { + apiClient.setToken(null); +} + +// 导出默认实例,方便直接使用 +export default apiClient; diff --git a/app/views/ConsolePage.vue b/app/views/ConsolePage.vue new file mode 100644 index 0000000..497871e --- /dev/null +++ b/app/views/ConsolePage.vue @@ -0,0 +1,1328 @@ + + + + + diff --git a/app/views/DeliveryPage.vue b/app/views/DeliveryPage.vue new file mode 100644 index 0000000..fd92457 --- /dev/null +++ b/app/views/DeliveryPage.vue @@ -0,0 +1,671 @@ + + + + + diff --git a/app/views/FeedbackPage.vue b/app/views/FeedbackPage.vue new file mode 100644 index 0000000..d15237d --- /dev/null +++ b/app/views/FeedbackPage.vue @@ -0,0 +1,643 @@ +