首次提交

This commit is contained in:
2025-07-25 23:03:30 +08:00
commit d661fb4752
110 changed files with 39608 additions and 0 deletions
View File
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+5
View File
@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+3648
View File
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
{
"name": "ca-lock-client",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"async-mutex": "^0.5.0",
"asynckit": "^0.4.0",
"axios": "^1.6.8",
"child_process": "^1.0.2",
"combined-stream": "^1.0.8",
"dayjs": "^1.11.13",
"dayjs-plugin-utc": "^0.1.2",
"delayed-stream": "^1.0.0",
"dotenv": "^16.4.5",
"electron-log": "^5.2.0",
"electron-store": "^10.0.0",
"element-plus": "^2.10.4",
"execa": "^8.0.1",
"express": "^4.20.0",
"fs": "^0.0.1-security",
"fs-extra": "^11.2.0",
"iconv-lite": "^0.6.3",
"ini": "^5.0.0",
"keytar": "^7.9.0",
"mitt": "^3.0.1",
"node-shutdown": "^1.0.9",
"pinia": "^2.1.7",
"proxy-from-env": "^1.1.0",
"qs": "^6.12.1",
"vue": "^3.4.24",
"vue-router": "^4.4.2",
"vuex": "^4.0.2",
"vue3-ast-decompiler": "^1.0.4",
"ws": "^8.18.0",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.0.4"
}
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+268
View File
@@ -0,0 +1,268 @@
<template>
<div id="app">
<Header />
<main class="main-content">
<div class="content-wrapper">
<!-- 设备列表区域 -->
<div class="device-section">
<h2>设备管理</h2>
<DeviceList
:devices="deviceList"
:loading="loadingDevices"
@device-select="handleDeviceSelect"
@device-connect="handleDeviceConnect"
/>
</div>
<!-- 设备详情区域 -->
<div class="device-detail" v-if="selectedDevice">
<DeviceDetail
:device="selectedDevice"
@close="selectedDevice = null"
/>
</div>
</div>
</main>
<!-- 超时提示弹窗 -->
<TimeoutPopup
v-model:visible="showTimeoutPopup"
:device-name="timeoutDeviceName"
@continue="handleContinue"
@disconnect="handleDisconnect"
/>
<!-- 设备检测超时管理 -->
<DetectingTimeout
v-if="currentDeviceId"
:device-id="currentDeviceId"
:max-duration="300"
@timeout="handleDeviceTimeout"
/>
</div>
</template>
<script>
import { ref, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import Header from './components/Header.vue';
import DeviceList from './components/DeviceList.vue';
import DeviceDetail from './components/DeviceDetail.vue';
import TimeoutPopup from './components/TimeoutPopup.vue';
import DetectingTimeout from './components/DetectingTimeout.vue';
export default {
name: 'App',
components: {
Header,
DeviceList,
DeviceDetail,
TimeoutPopup,
DetectingTimeout
},
setup() {
const store = useStore();
// 响应式数据
const selectedDevice = ref(null);
const showTimeoutPopup = ref(false);
const timeoutDeviceName = ref('');
const currentDeviceId = ref(null);
// 计算属性
const deviceList = computed(() => store.state.device.deviceList);
const loadingDevices = computed(() => store.state.device.loadingDevices);
// 生命周期
onMounted(async () => {
// 初始化应用数据
await initializeApp();
});
// 初始化应用
const initializeApp = async () => {
try {
// 检查用户登录状态
if (store.getters['user/isLoggedIn']) {
// 获取用户信息
await store.dispatch('user/getUserInfo');
// 获取公司列表
await store.dispatch('user/getCompanyList');
// 获取设备列表
await store.dispatch('device/getDeviceList');
// 获取设备类型
await store.dispatch('device/getDeviceTypes');
}
} catch (error) {
console.error('应用初始化失败:', error);
}
};
// 处理设备选择
const handleDeviceSelect = (device) => {
selectedDevice.value = device;
};
// 处理设备连接
const handleDeviceConnect = async (device) => {
try {
const result = await store.dispatch('device/connectDevice', {
device_id: device.id,
company_id: store.state.user.currentCompany?.id
});
if (result.success) {
// 开始监控超时
currentDeviceId.value = device.id;
// 显示连接成功消息
showNotification('设备连接成功', 'success');
} else {
showNotification(result.message, 'error');
}
} catch (error) {
console.error('连接设备失败:', error);
showNotification('连接设备失败', 'error');
}
};
// 处理设备超时
const handleDeviceTimeout = ({ deviceId }) => {
const device = store.getters['device/getDeviceById'](deviceId);
if (device) {
timeoutDeviceName.value = device.name;
showTimeoutPopup.value = true;
}
};
// 处理继续使用
const handleContinue = () => {
showTimeoutPopup.value = false;
// 重置超时计时器
currentDeviceId.value = null;
setTimeout(() => {
currentDeviceId.value = selectedDevice.value?.id;
}, 100);
};
// 处理断开连接
const handleDisconnect = async () => {
try {
if (currentDeviceId.value) {
// 这里可以调用断开连接的API
await store.dispatch('device/updateDeviceStatus', {
deviceId: currentDeviceId.value,
status: 'disconnected'
});
showNotification('设备已断开', 'info');
}
} catch (error) {
console.error('断开设备失败:', error);
} finally {
showTimeoutPopup.value = false;
currentDeviceId.value = null;
selectedDevice.value = null;
}
};
// 显示通知
const showNotification = (message, type = 'info') => {
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('show-notification', {
title: '云企通',
body: message,
type
});
}
};
return {
selectedDevice,
showTimeoutPopup,
timeoutDeviceName,
currentDeviceId,
deviceList,
loadingDevices,
handleDeviceSelect,
handleDeviceConnect,
handleDeviceTimeout,
handleContinue,
handleDisconnect
};
}
};
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: #f5f5f5;
color: #333;
}
#app {
height: 100vh;
display: flex;
flex-direction: column;
}
.main-content {
flex: 1;
overflow: hidden;
}
.content-wrapper {
height: 100%;
display: flex;
padding: 16px;
gap: 16px;
}
.device-section {
flex: 1;
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow-y: auto;
}
.device-section h2 {
margin-bottom: 16px;
color: #333;
font-size: 18px;
}
.device-detail {
width: 400px;
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow-y: auto;
}
/* 响应式设计 */
@media (max-width: 768px) {
.content-wrapper {
flex-direction: column;
}
.device-detail {
width: 100%;
max-height: 300px;
}
}
</style>
+117
View File
@@ -0,0 +1,117 @@
import request from '@/utils/request'
// 账号密码登录
export function login(data) {
return request({
url: '/dxsz_client_api/auth/login',
method: 'post',
data
})
}
// 手机验证码登录
export function phoneLogin(data) {
return request({
url: '/dxsz_client_api/auth/phone_login',
method: 'post',
data
})
}
export function logout(data) {
return request({
url: '/dxsz_client_api/auth/logout',
method: 'post',
data
})
}
// 获取验证码
export function getVerificationCode(data) {
return request({
url: '/dxsz_client_api/common/verification_code',
method: 'post',
data
})
}
// 获取企业列表
export function getCompanyList() {
return request({
url: '/dxsz_client_api/company/list',
method: 'get'
})
}
// 企业登录
export function loginCompany() {
return request({
url: '/dxsz_client_api/company/login',
method: 'post'
})
}
// 获取企业信息
export function getCompanyInfo() {
return request({
url: '/dxsz_client_api/company/info',
method: 'get'
})
}
// 微信小程序登录相关
export function createWeChatQRCode() {
return request({
url: '/dxsz_client_api/wechat_mini_program/create_qrcode',
method: 'get'
})
}
export function checkWeChatScanStatus() {
return request({
url: '/dxsz_client_api/wechat_mini_program/check_scan_status',
method: 'get'
})
}
// 设备连接审批相关
export function getDeviceApprovalList(params) {
return request({
url: '/dxsz_client_api/connect_ca_relation_device_list_v2',
method: 'get',
params
})
}
export function revokeDeviceApproval(data) {
return request({
url: '/dxsz_client_api/revoke',
method: 'post',
data
})
}
export function getDeviceUseTimeList(params) {
return request({
url: '/dxsz_client_api/device_use_time_list',
method: 'get',
params
})
}
// 错误上报
export function reportErrorByDeviceId(data) {
return request({
url: '/dxsz_client_api/report_error_busid_by_id',
method: 'post',
data
})
}
export function reportErrorByUserId(data) {
return request({
url: '/dxsz_client_api/report_error_busid_by_uid',
method: 'post',
data
})
}
+120
View File
@@ -0,0 +1,120 @@
// 公司管理相关API接口 - 专为SelectCompany组件设计
import request from '@/utils/request'
const API_BASE = '/dxsz_client_api'
/**
* 获取用户公司列表
* 用于SelectCompany组件显示用户可切换的企业列表
* @returns {Promise} 返回公司列表数据
*/
export const getUserCompanyList = () => {
return request({
url: `${API_BASE}/user/company_list`,
method: 'get'
})
}
/**
* 获取公司列表(管理员用)
* 用于获取所有公司列表
* @param {Object} params 查询参数
* @returns {Promise} 返回公司列表数据
*/
export const getCompanyList = (params = {}) => {
return request({
url: `${API_BASE}/company/list`,
method: 'get',
params
})
}
/**
* 公司登录
* 用于切换公司时的登录操作
* @param {Object} data 登录参数 {company_id, user_id}
* @returns {Promise} 返回登录结果
*/
export const companyLogin = (data) => {
return request({
url: `${API_BASE}/auth/company_login`,
method: 'post',
data
})
}
/**
* 获取公司详细信息
* 用于获取当前选中公司的详细信息
* @returns {Promise} 返回公司详细信息
*/
export const getCompanyDetail = () => {
return request({
url: `${API_BASE}/company/info`,
method: 'get'
})
}
/**
* 获取公司用户信息
* 用于获取公司下的用户信息
* @returns {Promise} 返回公司用户信息
*/
export const getCompanyUserInfo = () => {
return request({
url: `${API_BASE}/company_user/info`,
method: 'get'
})
}
/**
* 切换公司
* 完整的切换公司流程,包括断开设备、重新登录等操作
* @param {Object} params 切换参数 {company_id, user_id}
* @returns {Promise} 返回切换结果
*/
export const switchCompany = async (params) => {
try {
// 1. 执行公司登录
const loginResult = await companyLogin(params)
// 2. 获取新公司信息
const companyInfo = await getCompanyDetail()
return {
success: true,
data: {
loginResult,
companyInfo
}
}
} catch (error) {
return {
success: false,
error
}
}
}
/**
* 检查公司状态
* 用于检查公司是否有效
* @param {number} companyId 公司ID
* @returns {Promise} 返回检查结果
*/
export const checkCompanyStatus = (companyId) => {
return request({
url: `${API_BASE}/company/check_status/${companyId}`,
method: 'get'
})
}
export default {
getUserCompanyList,
getCompanyList,
companyLogin,
getCompanyDetail,
getCompanyUserInfo,
switchCompany,
checkCompanyStatus
}
+134
View File
@@ -0,0 +1,134 @@
// API端点常量定义
export const API_BASE_URL = '/dxsz_client_api'
// 认证模块
export const AUTH = {
LOGIN: '/auth/login',
PHONE_LOGIN: '/auth/phone_login',
COMPANY_LOGIN: '/auth/company_login',
LOGOUT: '/auth/logout'
}
// 用户模块
export const USER = {
INFO: '/user/info',
UPDATE: '/user',
CHANGE_PASSWORD: '/user/change_password',
COMPANY_LIST: '/user/company_list',
MY_RULE: '/user/my_rule',
GET_USER_CONNECT_DATA: '/user/get_user_connect_data',
GET_USER_ID_BY_LOGIN: '/user/get_user_id_by_login'
}
// 公司模块
export const COMPANY = {
LIST: '/company/list',
LOGIN: '/company/login',
INFO: '/company/info',
INFO_DETAIL: '/company/info',
USER_LIST: '/user/company_list',
COMPANY_LOGIN: '/auth/company_login',
COMPANY_USER_INFO: '/company_user/info',
CHECK_STATUS: '/company/check_status',
CHECK_SWITCH_PERMISSION: '/company/check_switch_permission'
}
// 验证码模块
export const VERIFICATION = {
CODE: '/common/verification_code'
}
// 微信小程序模块
export const WECHAT = {
CREATE_QRCODE: '/wechat_mini_program/create_qrcode',
CHECK_SCAN_STATUS: '/wechat_mini_program/check_scan_status'
}
// CA关系设备模块
export const CA_DEVICE = {
// 设备管理
LIST: '/ca_relation_device/list',
CONNECT: '/ca_relation_device/connect',
DISCONNECT: '/ca_relation_device/disconnect',
CHANGE: '/ca_relation_device/change',
ADD: '/ca_relation_device/add',
UPDATE: '/ca_relation_device/update',
DELETE: '/ca_relation_device/delete',
INFO: '/ca_relation_device/info',
// 设备审批
APPROVE_LIST: '/ca_use_approve/connect_ca_relation_device_list_v2',
REVOKE: '/ca_use_approve/revoke',
// 设备操作
FORCE_DISCONNECT: '/ca_relation_device/force_disconnect',
REQUEST_CONNECT: '/ca_relation_device/request_connect',
GET_DEVICE_TIME_LIST: '/ca_relation_device/get_device_time_list',
// 设备查询
GET_ALL_HUB_DEVICE_LIST_BY_GROUP: '/ca_device_server/get_all_hub_device_list_by_group_v2',
GET_RELATION_DEVICE_LIST_BY_GROUP: '/ca_device_server/get_relation_device_list_by_group',
TYPE_LIST: '/ca_device_server/type_list',
// 端口管理
GET_PORT_LIST: '/ca_device_server/get_port_list',
GET_PORT_STATUS: '/ca_device_server/get_port_status',
// 设备类型管理
GET_DEVICE_TYPE_LIST: '/ca_device_server/get_device_type_list',
GET_STATUS_LIST: '/ca_device_server/get_status_list',
CREATE_DEVICE: '/ca_device_server/create_device',
UPDATE_DEVICE: '/ca_device_server/update_device',
DELETE_DEVICE: '/ca_device_server/delete_device',
QUERY_RELATED_DEVICES: '/ca_device_server/query_related_devices',
GET_DEVICE_TYPE_TREE: '/ca_device_server/get_device_type_tree'
}
// 设备使用时间模块
export const DEVICE_USE_TIME = {
LIST: '/device_use_time_list'
}
// 错误报告模块
export const ERROR_REPORT = {
BY_ID: '/report_error_busid_by_id',
BY_UID: '/report_error_busid_by_uid'
}
// 授权中心模块
export const AUTHORIZATION_CENTER = {
USER_LIST: '/authorization_center/authorization_center_user_list',
USER_DELETE: '/authorization_center/authorization_center_user_delete',
USER_ADD: '/authorization_center/authorization_center_user_add',
USER_INFO: '/authorization_center/authorization_center_user_info',
USER_EDIT: '/authorization_center/authorization_center_user_edit',
IMPORT: '/authorization_center/import',
DOWNLOAD_TEMPLATE: '/authorization_center/download_template'
}
// 系统模块
export const SYSTEM = {
GET_VERSION: '/get_version',
VERSION_REPORT: '/version_report',
CHECK_UPDATE: '/check_update',
DOWNLOAD_UPDATE: '/download'
}
// API端点汇总
export const API_ENDPOINTS = {
AUTH,
USER,
COMPANY,
VERIFICATION,
WECHAT,
CA_DEVICE,
DEVICE_USE_TIME,
ERROR_REPORT,
AUTHORIZATION_CENTER,
SYSTEM
}
export default {
API_BASE_URL,
API_ENDPOINTS
}
+159
View File
@@ -0,0 +1,159 @@
// 设备管理相关API接口
// 包括设备状态列表、设备类型列表、设备增删改查等接口
import request from '@/utils/request'
const API_BASE = '/dxsz_client_api'
/**
* 获取设备列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getDeviceList = (params) => {
return request({
url: `${API_BASE}/ca_relation_device/list`,
method: 'get',
params
})
}
/**
* 获取设备类型列表
* @returns {Promise}
*/
export const getDeviceTypes = () => {
return request({
url: `${API_BASE}/ca_relation_device/type_list`,
method: 'get',
params: { level: 1 }
})
}
/**
* 获取设备详情
* @param {number} id 设备ID
* @returns {Promise}
*/
export const getDeviceDetail = (id) => {
return request({
url: `${API_BASE}/ca_relation_device/info/${id}`,
method: 'get'
})
}
/**
* 强制断开设备连接
* @param {number} id 设备ID
* @returns {Promise}
*/
export const forceDisconnectDevice = (id) => {
return request({
url: `${API_BASE}/ca_relation_device/force_disconnect/${id}`,
method: 'post'
})
}
/**
* 申请连接设备
* @param {Object} data 申请数据
* @returns {Promise}
*/
export const requestConnectDevice = (data) => {
return request({
url: `${API_BASE}/ca_relation_device/request_connect`,
method: 'post',
data
})
}
/**
* 获取所有设备列表(按组)
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getAllHubDeviceListByGroup = (params) => {
return request({
url: `${API_BASE}/ca_relation_device/get_all_hub_device_list_by_group_v2`,
method: 'get',
params
})
}
/**
* 获取设备连接记录
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getDeviceTimeList = (params) => {
return request({
url: `${API_BASE}/ca_relation_device/get_device_time_list`,
method: 'get',
params
})
}
/**
* 获取连接中的设备列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getConnectCaRelationDeviceList = (params) => {
return request({
url: `${API_BASE}/ca_relation_device/connect_ca_relation_device_list_v2`,
method: 'get',
params
})
}
/**
* 根据组ID获取设备列表
* @param {number} groupId 组ID
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getRelationDeviceListByGroup = (groupId, params = {}) => {
return request({
url: `${API_BASE}/ca_relation_device/get_relation_device_list_by_group/${groupId}`,
method: 'get',
params
})
}
/**
* 更新设备信息
* @param {Object} data 设备数据
* @returns {Promise}
*/
export const updateDevice = (data) => {
return request({
url: `${API_BASE}/ca_relation_device/update`,
method: 'post',
data
})
}
/**
* 删除设备
* @param {number} id 设备ID
* @returns {Promise}
*/
export const deleteDevice = (id) => {
return request({
url: `${API_BASE}/ca_relation_device/delete/${id}`,
method: 'delete'
})
}
/**
* 添加设备
* @param {Object} data 设备数据
* @returns {Promise}
*/
export const addDevice = (data) => {
return request({
url: `${API_BASE}/ca_relation_device/add`,
method: 'post',
data
})
}
+35
View File
@@ -0,0 +1,35 @@
// API统一导出文件
// 所有API模块的入口文件
// 基础API模块
export * from './auth'
export * from './user'
export * from './device'
export * from './port'
export * from './system'
export * from './company'
// 专用组件API
export * from './select-company'
// 常量导出
export { API_BASE_URL, API_ENDPOINTS } from './constants'
// 默认导出
import * as auth from './auth'
import * as user from './user'
import * as device from './device'
import * as port from './port'
import * as system from './system'
import * as company from './company'
import * as selectCompany from './select-company'
export default {
auth,
user,
device,
port,
system,
company,
selectCompany
}
+146
View File
@@ -0,0 +1,146 @@
// 端口管理相关API接口
import request from '@/utils/request'
const API_BASE = '/dxsz_client_api'
/**
* 获取端口列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getPortList = (params) => {
return request({
url: `${API_BASE}/ca_device_server/get_port_list`,
method: 'get',
params
})
}
/**
* 获取端口状态
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getPortStatus = (params) => {
return request({
url: `${API_BASE}/ca_device_server/get_port_status`,
method: 'get',
params
})
}
/**
* 获取所有设备列表(按组)
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getAllHubDeviceListByGroup = (params) => {
return request({
url: `${API_BASE}/ca_device_server/get_all_hub_device_list_by_group_v2`,
method: 'get',
params
})
}
/**
* 获取设备类型列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getDeviceTypeList = (params) => {
return request({
url: `${API_BASE}/ca_device_server/get_device_type_list`,
method: 'get',
params
})
}
/**
* 获取状态列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getStatusList = (params) => {
return request({
url: `${API_BASE}/ca_device_server/get_status_list`,
method: 'get',
params
})
}
/**
* 创建设备
* @param {Object} data 设备数据
* @returns {Promise}
*/
export const createDevice = (data) => {
return request({
url: `${API_BASE}/ca_device_server/create_device`,
method: 'post',
data
})
}
/**
* 更新设备信息
* @param {Object} data 设备数据
* @returns {Promise}
*/
export const updateDeviceInfo = (data) => {
return request({
url: `${API_BASE}/ca_device_server/update_device`,
method: 'put',
data
})
}
/**
* 删除设备
* @param {number} id 设备ID
* @returns {Promise}
*/
export const deleteDevice = (id) => {
return request({
url: `${API_BASE}/ca_device_server/delete_device/${id}`,
method: 'delete'
})
}
/**
* 查询关联设备
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const queryRelatedDevices = (params) => {
return request({
url: `${API_BASE}/ca_device_server/query_related_devices`,
method: 'get',
params
})
}
/**
* 获取设备类型树
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getDeviceTypeTree = (params) => {
return request({
url: `${API_BASE}/ca_device_server/get_device_type_tree`,
method: 'get',
params
})
}
export default {
getPortList,
getPortStatus,
getAllHubDeviceListByGroup,
getDeviceTypeList,
getStatusList,
createDevice,
updateDeviceInfo,
deleteDevice,
queryRelatedDevices,
getDeviceTypeTree
}
+165
View File
@@ -0,0 +1,165 @@
// SelectCompany组件专用API封装
// 基于index-BYhys97C.js中提取的SelectCompany功能实现
import request from '@/utils/request'
import { getUserCompanyList, companyLogin, getCompanyDetail } from './company'
const API_BASE = '/dxsz_client_api'
/**
* SelectCompany组件的核心API集合
* 用于处理企业切换的完整流程
*/
/**
* 获取用户可选择的企业列表
* 对应编译代码中的: e.setCompanyList(o.data)
* @returns {Promise<Array>} 企业列表数据
*/
export const fetchCompanyOptions = () => {
return getUserCompanyList()
}
/**
* 执行企业切换操作
* 对应编译代码中的完整切换流程
* @param {Object} params 切换参数
* @param {number} params.companyId 目标企业ID
* @param {number} params.userId 用户ID
* @param {number} params.selectIndex 选择的企业索引
* @returns {Promise<Object>} 切换结果
*/
export const switchCompany = async ({ companyId, userId, selectIndex }) => {
try {
// 1. 执行企业登录 (对应编译代码: companyLogin)
const loginResult = await companyLogin({
company_id: companyId,
user_id: userId
})
// 2. 获取新企业信息 (对应编译代码: getCompanyDetail)
const companyInfo = await getCompanyDetail()
// 3. 执行设备断开和重新连接 (对应编译代码中的异步操作)
// 这些操作在前端处理,不在API层面
return {
success: true,
data: {
loginResult,
companyInfo,
selectedIndex: selectIndex
}
}
} catch (error) {
console.error('企业切换失败:', error)
return {
success: false,
error: error.response?.data?.message || '企业切换失败'
}
}
}
/**
* 检查企业切换权限
* 对应编译代码中的权限检查逻辑
* @param {number} companyId 企业ID
* @returns {Promise<Object>} 权限检查结果
*/
export const checkSwitchPermission = (companyId) => {
return request({
url: `${API_BASE}/company/check_switch_permission/${companyId}`,
method: 'get'
})
}
/**
* 获取企业切换前的准备数据
* 包括当前企业信息、可切换企业列表等
* @returns {Promise<Object>} 准备数据
*/
export const getSwitchPrepareData = async () => {
try {
const [companyList, currentCompany] = await Promise.all([
fetchCompanyOptions(),
getCompanyDetail()
])
return {
companyList: companyList.data || companyList,
currentCompany: currentCompany.data || currentCompany,
canSwitch: companyList.length > 1
}
} catch (error) {
console.error('获取切换准备数据失败:', error)
throw error
}
}
/**
* 验证企业选择
* 对应编译代码中的企业选择验证逻辑
* @param {number} selectedIndex 选择的企业索引
* @param {Array} companyList 企业列表
* @returns {Object} 验证结果
*/
export const validateCompanySelection = (selectedIndex, companyList) => {
if (!companyList || companyList.length === 0) {
return {
valid: false,
message: '请先入驻企业!'
}
}
if (selectedIndex < 0 || selectedIndex >= companyList.length) {
return {
valid: false,
message: '选择的企业无效'
}
}
return {
valid: true,
selectedCompany: companyList[selectedIndex]
}
}
/**
* 处理企业切换错误
* 对应编译代码中的错误处理逻辑
* @param {Object} error 错误对象
* @returns {Object} 处理后的错误信息
*/
export const handleSwitchError = (error) => {
const errorCode = error.response?.status
const errorMessage = error.response?.data?.message
// 处理402错误码 (对应编译代码: v.code === 402)
if (errorCode === 402) {
return {
type: 'permission_denied',
message: errorMessage || '无权限切换到此企业'
}
}
// 处理网络错误
if (error.code === 'NETWORK_ERROR') {
return {
type: 'network_error',
message: '网络连接失败,请检查网络后重试'
}
}
return {
type: 'unknown_error',
message: errorMessage || '企业切换失败,请稍后重试'
}
}
export default {
fetchCompanyOptions,
switchCompany,
checkSwitchPermission,
getSwitchPrepareData,
validateCompanySelection,
handleSwitchError
}
+231
View File
@@ -0,0 +1,231 @@
import request from '@/utils/request'
// 获取版本信息
export function getVersion() {
return request({
url: '/dxsz_client_api/get_version',
method: 'get',
params: {
is_new: 1,
type: 'dxsz'
}
})
}
// 上报版本信息
export function reportVersionInfo(data) {
return request({
url: '/dxsz_client_api/version_report',
method: 'post',
data
})
}
// 检查更新
export function checkUpdate() {
return request({
url: '/dxsz_client_api/check_update',
method: 'get',
params: {
is_new: 1,
type: 'dxsz'
}
})
}
// 获取更新包
export function downloadUpdate(version) {
return request({
url: `/download/${version}`,
method: 'get',
responseType: 'blob'
})
}
// CA关系设备连接
export const connectCaRelationDevice = (data) => {
const os = require('os')
const networkInterfaces = os.networkInterfaces()
let mac = 'unknown'
for (const name of Object.keys(networkInterfaces)) {
for (const net of networkInterfaces[name]) {
if (net.mac && net.mac !== '00:00:00:00:00:00') {
mac = net.mac
break
}
}
if (mac !== 'unknown') break
}
return request({
url: `/dxsz_client_api/ca_relation_device/connect/${data.id}`,
method: 'post',
data: {
...data,
computer_name: os.hostname(),
ip: Object.values(os.networkInterfaces()).flat().find(net => net.family === 'IPv4' && !net.internal)?.address || 'unknown',
mac: mac
},
loading: false
})
}
// CA关系设备断开连接
export const disconnectCaRelationDevice = (data) => {
return request({
url: `/dxsz_client_api/ca_relation_device/disconnect/${data.id}`,
method: 'post',
data,
loading: false
})
}
// CA关系设备变更
export const changeCaRelationDevice = (data) => {
return request({
url: `/dxsz_client_api/ca_relation_device/change/${data.id}`,
method: 'put',
data,
loading: false
})
}
// 获取用户连接数据
export const getUserConnectData = (params) => {
return request({
url: '/dxsz_client_api/user/get_user_connect_data',
method: 'get',
params,
loading: true
})
}
// 用户登出
export const userLogout = (data) => {
return request({
url: '/dxsz_client_api/auth/logout',
method: 'post',
data,
loading: true
})
}
// 更新用户信息
export const updateUserInfo = (data) => {
return request({
url: '/dxsz_client_api/user',
method: 'put',
data,
loading: true
})
}
// 获取用户规则
export const getUserRule = () => {
return request({
url: '/dxsz_client_api/user/my_rule',
method: 'get',
params: {},
loading: true
})
}
// 授权中心相关API
/**
* 获取授权中心用户列表
* @param {Object} params 查询参数
* @returns {Promise}
*/
export const getAuthorizationCenterUserList = (params) => {
return request({
url: '/dxsz_client_api/authorization_center/authorization_center_user_list',
method: 'get',
params,
loading: false
})
}
/**
* 删除授权中心用户
* @param {Object} data 用户数据
* @returns {Promise}
*/
export const deleteAuthorizationCenterUser = (data) => {
return request({
url: `/dxsz_client_api/authorization_center/authorization_center_user_delete/${data.user_id}`,
method: 'delete',
params: {},
loading: true
})
}
/**
* 添加授权中心用户
* @param {Object} data 用户数据
* @returns {Promise}
*/
export const addAuthorizationCenterUser = (data) => {
return request({
url: '/dxsz_client_api/authorization_center/authorization_center_user_add',
method: 'post',
data,
loading: true
})
}
/**
* 获取授权中心用户信息
* @param {Object} data 用户数据
* @returns {Promise}
*/
export const getAuthorizationCenterUserInfo = (data) => {
return request({
url: `/dxsz_client_api/authorization_center/authorization_center_user_info/${data.user_id}`,
method: 'get',
params: {},
loading: true
})
}
/**
* 编辑授权中心用户
* @param {Object} data 用户数据
* @returns {Promise}
*/
export const editAuthorizationCenterUser = (data) => {
return request({
url: `/dxsz_client_api/authorization_center/authorization_center_user_edit/${data.user_id}`,
method: 'put',
params: data,
loading: true
})
}
/**
* 导入授权中心用户
* @param {Object} data 导入数据
* @returns {Promise}
*/
export const importAuthorizationCenterUsers = (data) => {
return request({
url: '/dxsz_client_api/authorization_center/import',
method: 'post',
data,
loading: true
})
}
/**
* 下载导入模板
* @returns {Promise}
*/
export const downloadImportTemplate = () => {
return request({
url: '/dxsz_client_api/authorization_center/import',
method: 'get',
params: {},
loading: true,
responseType: 'blob'
})
}
+82
View File
@@ -0,0 +1,82 @@
// 用户相关API接口
import request from '@/utils/request'
const API_BASE = '/dxsz_client_api'
/**
* 修改用户密码
* @param {Object} data 密码数据
* @param {string} data.password 新密码
* @param {string} data.repassword 确认密码
* @returns {Promise}
*/
export const changePassword = (data) => {
return request({
url: `${API_BASE}/user/change_password`,
method: 'post',
data,
loading: true
})
}
/**
* 获取用户信息
* @returns {Promise}
*/
export const getUserInfo = () => {
return request({
url: `${API_BASE}/user/info`,
method: 'get'
})
}
/**
* 获取公司列表
* @returns {Promise}
*/
export const getCompanyList = () => {
return request({
url: `${API_BASE}/user/company_list`,
method: 'get',
params: {},
loading: true
})
}
/**
* 根据登录获取用户ID
* @param {Object} data 登录数据
* @returns {Promise}
*/
export const getUserIdByLogin = (data) => {
const os = require('os')
const networkInterfaces = os.networkInterfaces()
let mac = 'unknown'
for (const name of Object.keys(networkInterfaces)) {
for (const net of networkInterfaces[name]) {
if (net.mac && net.mac !== '00:00:00:00:00:00') {
mac = net.mac
break
}
}
if (mac !== 'unknown') break
}
return request({
url: `${API_BASE}/user/get_user_id_by_login`,
method: 'post',
data: {
...data,
mac,
verify_type: 3
}
})
}
export default {
changePassword,
getUserInfo,
getCompanyList,
getUserIdByLogin
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

+92
View File
@@ -0,0 +1,92 @@
<template>
<div class="detecting-timeout">
<!-- 设备检测超时管理组件 -->
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue';
import { useStore } from 'vuex';
export default {
name: 'DetectingTimeout',
props: {
deviceId: {
type: String,
required: true
},
maxDuration: {
type: Number,
default: 300 // 5分钟
}
},
emits: ['timeout', 'update:timeLeft'],
setup(props, { emit }) {
const store = useStore();
const startTime = ref(Date.now());
const timeLeft = ref(props.maxDuration);
let timer = null;
const startMonitoring = () => {
timer = setInterval(() => {
const elapsed = Math.floor((Date.now() - startTime.value) / 1000);
timeLeft.value = Math.max(0, props.maxDuration - elapsed);
emit('update:timeLeft', timeLeft.value);
if (timeLeft.value <= 0) {
handleTimeout();
}
}, 1000);
};
const stopMonitoring = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
};
const handleTimeout = () => {
stopMonitoring();
// 更新设备状态
store.dispatch('device/updateDeviceStatus', {
deviceId: props.deviceId,
status: 'timeout'
});
// 触发超时事件
emit('timeout', {
deviceId: props.deviceId,
duration: props.maxDuration
});
};
const resetTimer = () => {
startTime.value = Date.now();
timeLeft.value = props.maxDuration;
};
onMounted(() => {
startMonitoring();
});
onUnmounted(() => {
stopMonitoring();
});
return {
timeLeft,
resetTimer,
stopMonitoring
};
}
};
</script>
<style scoped>
.detecting-timeout {
/* 样式可以根据需要添加 */
}
</style>
+2
View File
@@ -0,0 +1,2 @@
<template>
</template>
+2
View File
@@ -0,0 +1,2 @@
<template>
</template>
+126
View File
@@ -0,0 +1,126 @@
<template>
<header class="app-header">
<div class="header-left">
<div class="logo">
<img src="@/assets/logo.png" alt="云企通" />
<span>云企通</span>
</div>
</div>
<div class="header-center">
<div class="vpn-status" :class="{ connected: vpnConnected }">
<span class="status-dot"></span>
<span>{{ vpnConnected ? 'VPN已连接' : 'VPN未连接' }}</span>
</div>
</div>
<div class="header-right">
<UserInfo />
<WindowControls />
</div>
</header>
</template>
<script>
import { ref, onMounted } from 'vue';
import UserInfo from './UserInfo.vue';
import WindowControls from './WindowControls.vue';
export default {
name: 'AppHeader',
components: {
UserInfo,
WindowControls
},
setup() {
const vpnConnected = ref(false);
onMounted(() => {
checkVpnStatus();
checkForUpdates();
});
const checkVpnStatus = () => {
// 检查VPN连接状态
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('check-vpn-status');
ipcRenderer.on('vpn-status-response', (event, status) => {
vpnConnected.value = status.connected;
});
}
};
const checkForUpdates = () => {
// 检查版本更新
if (window.require) {
const { ipcRenderer } = window.require('electron');
ipcRenderer.send('check-for-updates');
ipcRenderer.on('update-available', (event, info) => {
console.log('发现新版本:', info.version);
// 这里可以触发更新提示
});
}
};
return {
vpnConnected
};
}
};
</script>
<style scoped>
.app-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: #fff;
border-bottom: 1px solid #e0e0e0;
-webkit-app-region: drag;
}
.header-left,
.header-right {
display: flex;
align-items: center;
gap: 12px;
-webkit-app-region: no-drag;
}
.logo {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: bold;
color: #333;
}
.logo img {
width: 32px;
height: 32px;
}
.vpn-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #666;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #ff4757;
}
.vpn-status.connected .status-dot {
background-color: #2ed573;
}
</style>
+43
View File
@@ -0,0 +1,43 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
+226
View File
@@ -0,0 +1,226 @@
<template>
<div v-if="visible" class="timeout-popup-overlay" @click="handleClose">
<div class="timeout-popup" @click.stop>
<div class="popup-header">
<h3>连接超时</h3>
<button class="close-btn" @click="handleClose">×</button>
</div>
<div class="popup-content">
<div class="timeout-icon">
<svg width="48" height="48" viewBox="0 0 48 48">
<circle cx="24" cy="24" r="20" fill="#ff4757" fill-opacity="0.1" stroke="#ff4757" stroke-width="2"/>
<path d="M24 12v12M24 28v8" stroke="#ff4757" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<div class="timeout-message">
<p>设备连接已超时</p>
<p class="timeout-details">{{ deviceName }} 连接已超时请确认设备状态</p>
</div>
<div class="countdown">
<span>将在 {{ countdown }} 秒后自动断开</span>
</div>
</div>
<div class="popup-actions">
<button class="btn btn-secondary" @click="handleContinue">继续使用</button>
<button class="btn btn-primary" @click="handleDisconnect">立即断开</button>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted } from 'vue';
export default {
name: 'TimeoutPopup',
props: {
visible: {
type: Boolean,
default: false
},
deviceName: {
type: String,
default: '设备'
},
timeout: {
type: Number,
default: 30
}
},
emits: ['close', 'continue', 'disconnect'],
setup(props, { emit }) {
const countdown = ref(props.timeout);
let timer = null;
const startCountdown = () => {
timer = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
handleDisconnect();
}
}, 1000);
};
const stopCountdown = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
};
const handleClose = () => {
stopCountdown();
emit('close');
};
const handleContinue = () => {
stopCountdown();
emit('continue');
};
const handleDisconnect = () => {
stopCountdown();
emit('disconnect');
};
onMounted(() => {
if (props.visible) {
startCountdown();
}
});
onUnmounted(() => {
stopCountdown();
});
return {
countdown,
handleClose,
handleContinue,
handleDisconnect
};
}
};
</script>
<style scoped>
.timeout-popup-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.timeout-popup {
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
max-width: 400px;
width: 90%;
overflow: hidden;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
.popup-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-btn:hover {
color: #333;
}
.popup-content {
padding: 24px 20px;
text-align: center;
}
.timeout-icon {
margin-bottom: 16px;
}
.timeout-message p {
margin: 0 0 8px 0;
color: #333;
font-size: 16px;
}
.timeout-details {
font-size: 14px !important;
color: #666 !important;
}
.countdown {
margin: 16px 0;
color: #ff4757;
font-size: 14px;
}
.popup-actions {
display: flex;
gap: 12px;
padding: 16px 20px;
background: #f9f9f9;
}
.btn {
flex: 1;
padding: 10px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.btn-secondary {
background: white;
color: #333;
border: 1px solid #ddd;
}
.btn-secondary:hover {
background: #f5f5f5;
}
.btn-primary {
background: #ff4757;
color: white;
}
.btn-primary:hover {
background: #ff3838;
}
</style>
+258
View File
@@ -0,0 +1,258 @@
<template>
<div class="user-info">
<div v-if="isLoggedIn" class="user-info">
<el-avatar :src="userInfo.avatar" :size="16" />
<span>{{ displayName }}</span>
<el-icon color="#fff">
<CaretBottom />
</el-icon>
</div>
<div v-else class="user-info" @click="handleLogin">
<img src="@/assets/logo.png" alt="">
<span>未登录</span>
</div>
</div>
<!-- 用户操作弹窗 -->
<el-popover
:virtual-ref="buttonRef"
width="214px"
trigger="click"
virtual-triggering
>
<div class="user-popover">
<div class="user-popover_info">
<el-avatar :src="userInfo.avatar" :size="24" />
<div class="user-popover_info_name">
<span class="user-popover_info_name_txt">{{ displayName }}</span>
<span class="user-popover_info_name_company">公司{{ companyName }}</span>
</div>
</div>
<div v-if="companyList.length > 1" class="user-popover_list" @click="handleSwitchCompany">
<div class="user-popover_list_left">
<el-icon size="12px" color="#A5A5A5">
<Switch />
</el-icon>
<span>切换企业</span>
</div>
<el-icon size="12px" color="#A5A5A5">
<ArrowRightBold />
</el-icon>
</div>
<div class="user-popover_list" @click="handleChangePassword">
<div class="user-popover_list_left">
<el-icon size="12px" color="#A5A5A5">
<SvgIcon icon-name="icon-xiugai" />
</el-icon>
<span>修改密码</span>
</div>
<el-icon size="12px" color="#A5A5A5">
<ArrowRightBold />
</el-icon>
</div>
<div class="user-popover_list user-popover_loginout" @click="handleLogout">
<div class="user-popover_list_left">
<el-icon size="12px" color="#A5A5A5">
<Expand />
</el-icon>
<span>退出登录</span>
</div>
<el-icon size="12px" color="#A5A5A5">
<ArrowRightBold />
</el-icon>
</div>
</div>
</el-popover>
<!-- 确认对话框 -->
<NotificationDialog
ref="notificationDialogRef"
:title="dialogTitle"
:content="dialogContent"
@confirm="handleConfirm"
/>
<!-- 选择企业弹窗 -->
<SelectCompany ref="selectCompanyRef" />
<!-- 修改密码弹窗 -->
<ChangePasswordDialog
v-model:visible="passwordDialogVisible"
/>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from '@/store'
import { ElMessage } from 'element-plus'
import { CaretBottom, Switch, ArrowRightBold, Expand } from '@element-plus/icons-vue'
import NotificationDialog from '@/components/common/NotificationDialog.vue'
import SelectCompany from '@/components/common/SelectCompany.vue'
import ChangePasswordDialog from '@/components/common/ChangePasswordDialog.vue'
import { logout } from '@/api/auth'
const router = useRouter()
const route = useRoute()
const store = useStore()
const buttonRef = ref()
const popoverRef = ref()
const notificationDialogRef = ref()
const selectCompanyRef = ref()
const passwordDialogVisible = ref(false)
const dialogType = ref('loginOut')
const userInfo = computed(() => store.user.userInfo)
const companyInfo = computed(() => store.company.companyInfo)
const companyList = computed(() => store.company.companyList)
const isLoggedIn = computed(() => store.user.isLoggedIn)
const displayName = computed(() => {
return userInfo.value?.certify_name ||
userInfo.value?.company_nickname ||
userInfo.value?.nickname || ''
})
const companyName = computed(() => companyInfo.value?.company_name || '')
const dialogTitle = computed(() => {
return {
loginOut: '退出登录-提示',
switchCopany: '切换企业-提示'
}[dialogType.value]
})
const dialogContent = computed(() => {
return {
loginOut: '退出登录会断开所有已连接的锁,确认退出?',
switchCopany: '切换企业会断开所有已连接的锁,确认切换?'
}[dialogType.value]
})
const routesToSkip = ['caCloudLock']
const handleLogin = () => {
// 处理登录逻辑
}
const handleSwitchCompany = () => {
dialogType.value = 'switchCopany'
showDialog()
}
const handleChangePassword = () => {
passwordDialogVisible.value = true
hidePopover()
}
const handleLogout = () => {
dialogType.value = 'loginOut'
showDialog()
}
const showDialog = () => {
hidePopover()
notificationDialogRef.value?.open()
}
const hidePopover = () => {
popoverRef.value?.hide()
}
const handleConfirm = async () => {
if (dialogType.value === 'switchCopany') {
// 切换企业
selectCompanyRef.value?.open()
} else if (dialogType.value === 'loginOut') {
// 退出登录
try {
await logout()
} catch (error) {
console.error('Logout error:', error)
} finally {
if (!routesToSkip.includes(route.name)) {
router.push({ name: 'caCloudLock' })
}
}
}
}
</script>
<style scoped>
.user-info {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
color: #fff;
}
.user-info img {
width: 16px;
height: 16px;
border-radius: 50%;
}
.user-popover {
padding: 12px;
}
.user-popover_info {
display: flex;
align-items: center;
gap: 8px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.user-popover_info_name {
display: flex;
flex-direction: column;
gap: 2px;
}
.user-popover_info_name_txt {
font-size: 14px;
font-weight: 500;
color: #303133;
}
.user-popover_info_name_company {
font-size: 12px;
color: #909399;
}
.user-popover_list {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
}
.user-popover_list:last-child {
border-bottom: none;
}
.user-popover_list_left {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #606266;
}
.user-popover_loginout {
color: #f56c6c;
}
.user-popover_list:hover {
background-color: #f5f7fa;
}
</style>
+99
View File
@@ -0,0 +1,99 @@
<template>
<div class="window-controls">
<button class="control-btn minimize" @click="handleMinimize" title="最小化">
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="1" y="6" width="10" height="1" fill="currentColor"/>
</svg>
</button>
<button class="control-btn maximize" @click="handleMaximize" title="最大化">
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="2" y="2" width="8" height="8" fill="none" stroke="currentColor" stroke-width="1"/>
</svg>
</button>
<button class="control-btn close" @click="handleClose" title="关闭">
<svg width="12" height="12" viewBox="0 0 12 12">
<line x1="2" y1="2" x2="10" y2="10" stroke="currentColor" stroke-width="1"/>
<line x1="10" y1="2" x2="2" y2="10" stroke="currentColor" stroke-width="1"/>
</svg>
</button>
</div>
</template>
<script>
import { onMounted, onUnmounted } from 'vue';
export default {
name: 'WindowControls',
setup() {
let ipcRenderer = null;
onMounted(() => {
if (window.require) {
const { ipcRenderer: ipc } = window.require('electron');
ipcRenderer = ipc;
}
});
const handleMinimize = () => {
if (ipcRenderer) {
ipcRenderer.send('window-minimize');
}
};
const handleMaximize = () => {
if (ipcRenderer) {
ipcRenderer.send('window-maximize');
}
};
const handleClose = () => {
if (ipcRenderer) {
ipcRenderer.send('window-close');
}
};
return {
handleMinimize,
handleMaximize,
handleClose
};
}
};
</script>
<style scoped>
.window-controls {
display: flex;
align-items: center;
gap: 2px;
}
.control-btn {
width: 30px;
height: 30px;
border: none;
background: transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.control-btn:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.control-btn.close:hover {
background-color: #ff4757;
color: white;
}
.minimize:hover {
background-color: #e0e0e0;
}
.maximize:hover {
background-color: #e0e0e0;
}
</style>
@@ -0,0 +1,135 @@
<template>
<el-dialog
:model-value="visible"
@update:model-value="handleClose"
width="430px"
draggable
:show-close="false"
:modal="true"
class="password-dialog"
@closed="handleClose"
>
<template #header>
<DialogHeader title="修改密码" @close="handleClose" />
</template>
<el-form
ref="ruleFormRef"
:model="formData"
:rules="rules"
label-width="auto"
style="max-width: 360px; margin: 20px auto 0"
>
<el-form-item label="新密码" prop="password" label-position="right">
<el-input
v-model="formData.password"
type="password"
autocomplete="off"
show-password
/>
</el-form-item>
<el-form-item label="再次输入密码" prop="repassword" label-position="right">
<el-input
v-model="formData.repassword"
type="password"
autocomplete="off"
show-password
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleConfirm">确认</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import DialogHeader from './DialogHeader.vue'
import { changePassword } from '@/api/user'
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:visible', 'success'])
const ruleFormRef = ref()
const formData = reactive({
password: '',
repassword: ''
})
const validateRepassword = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
} else if (value !== formData.password) {
callback(new Error('两次输入密码不一致!'))
} else {
callback()
}
}
const rules = {
password: [
{ required: true, message: '请输入密码', trigger: ['change', 'blur'] }
],
repassword: [
{ required: true, validator: validateRepassword, trigger: ['change', 'blur'] }
]
}
const handleClose = () => {
emit('update:visible', false)
ruleFormRef.value?.resetFields()
}
const handleConfirm = async () => {
try {
await ruleFormRef.value?.validate()
await changePassword({
password: formData.password,
repassword: formData.repassword
})
ElMessage.success('修改成功!')
emit('success')
handleClose()
} catch (error) {
console.error('Password change error:', error)
}
}
// 监听visible变化,重置表单
watch(() => props.visible, (newVal) => {
if (newVal) {
formData.password = ''
formData.repassword = ''
}
})
</script>
<style scoped>
.password-dialog {
padding: 0;
--el-dialog-padding-primary: 0;
border-radius: 12px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>
+72
View File
@@ -0,0 +1,72 @@
<template>
<div class="main-head">
<div class="main-head_left">
<img src="@/assets/logo.png" alt="">
<span>{{ title }}</span>
</div>
<img
v-if="isClose"
class="main-head_close"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAQAAAD8fJRsAAAAAXNSR0IArs4c6QAAADtJREFUeNpjwAP+G/03ReGb/jeEMR7/t4QLWwJ5psgcS1QWihSKMIoUkjATAw6AwyicluN0Lk4P4rEVAPV6Q5Fq3plRAAAAAElFTkSuQmCC"
alt=""
@click="handleClose"
>
</div>
</template>
<script setup>
const props = defineProps({
title: {
type: String,
default: '标题'
},
isClose: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['close'])
const handleClose = () => {
emit('close')
}
</script>
<style scoped>
.main-head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
}
.main-head_left {
display: flex;
align-items: center;
gap: 8px;
}
.main-head_left img {
width: 20px;
height: 20px;
}
.main-head_left span {
font-size: 16px;
font-weight: 500;
color: #303133;
}
.main-head_close {
width: 16px;
height: 16px;
cursor: pointer;
transition: opacity 0.3s;
}
.main-head_close:hover {
opacity: 0.7;
}
</style>
+89
View File
@@ -0,0 +1,89 @@
<template>
<div class="main-head">
<div class="main-head-content">
<img
class="main-head-logo"
:src="logo"
alt="企业logo"
/>
<span class="main-head-title">{{ title }}</span>
</div>
<div
v-if="isClose"
class="main-head-close"
@click="handleClose"
>
<img :src="closeIcon" alt="关闭" />
</div>
</div>
</template>
<script setup>
import { defineEmits } from 'vue'
const props = defineProps({
title: {
type: String,
required: true
},
isClose: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['close'])
// 图片资源
const logo = new URL('@/assets/images/enterprise-logo.png', import.meta.url).href
const closeIcon = `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTQiIHZpZXdCb3g9IjAgMCAxNCAxNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEgMUwxMyAxM00xMyAxTDEgMTMiIHN0cm9rZT0iIzk5OTk5OSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4KPC9zdmc+`
const handleClose = () => {
emit('close')
}
</script>
<style scoped>
.main-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #eee;
}
.main-head-content {
display: flex;
align-items: center;
gap: 12px;
}
.main-head-logo {
width: 24px;
height: 24px;
}
.main-head-title {
font-size: 16px;
font-weight: 500;
color: #333;
}
.main-head-close {
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: background-color 0.3s;
}
.main-head-close:hover {
background-color: #f5f5f5;
}
.main-head-close img {
width: 14px;
height: 14px;
display: block;
}
</style>
@@ -0,0 +1,132 @@
<template>
<el-dialog
v-model="visible"
:width="432"
draggable
destroy-on-close
:show-close="false"
:close-on-click-modal="closeOnClickModal"
class="notification-dialog"
:style="{ padding: '0 0 25px 0', 'box-shadow': '0 4px 35.8px 0 #00000026' }"
>
<template #header>
<DialogHeader
:title="title"
:is-close="isClose"
@close="handleClose"
/>
</template>
<div class="notification-dialog_content">
<el-icon :size="21" :color="iconColor">
<WarningFilled />
</el-icon>
<span>{{ content }}</span>
</div>
<slot />
<template #footer>
<div class="dialog-footer">
<el-button
v-if="isCancel"
size="large"
@click="handleClose"
>
{{ cancelText }}
</el-button>
<el-button
type="primary"
size="large"
@click="handleConfirm"
>
{{ confirmText }}
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { WarningFilled } from '@element-plus/icons-vue'
import DialogHeader from './DialogHeader.vue'
const props = defineProps({
title: {
type: String,
default: '标题'
},
content: {
type: String,
default: ''
},
isClose: {
type: Boolean,
default: true
},
isCancel: {
type: Boolean,
default: true
},
cancelText: {
type: String,
default: '取消'
},
confirmText: {
type: String,
default: '确认'
},
iconColor: {
type: String,
default: '#D54941FF'
},
closeOnClickModal: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['confirm', 'close'])
const visible = ref(false)
const open = () => {
visible.value = true
}
const close = () => {
visible.value = false
emit('close')
}
const handleClose = () => {
close()
}
const handleConfirm = () => {
emit('confirm')
close()
}
// 暴露方法给父组件
defineExpose({
open,
close
})
</script>
<style scoped>
.notification-dialog_content {
display: flex;
align-items: center;
gap: 12px;
padding: 20px 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
</style>
+114
View File
@@ -0,0 +1,114 @@
<template>
<div class="select-company">
<NotificationDialog
ref="notificationDialogRef"
title="选择企业"
content="系统检测到您开通了多家企业,请选择"
icon-color="#E37318FF"
:close-on-click-modal="false"
:is-cancel="!!companyToken"
:is-close="!!companyToken"
@confirm="handleConfirm"
>
<el-select
v-model="selectedIndex"
placeholder="请选择企业"
style="width: 400px; margin-left: 16px"
>
<el-option
v-for="(company, index) in companyList"
:key="company.value"
:label="company.company_name"
:value="index"
/>
</el-select>
</NotificationDialog>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from '@/store'
import { ElMessage } from 'element-plus'
import NotificationDialog from './NotificationDialog.vue'
import { switchCompany, getCompanyList } from '@/api/company'
const router = useRouter()
const store = useStore()
const notificationDialogRef = ref()
const selectedIndex = ref(null)
const companyList = computed(() => store.company.companyList)
const companyToken = computed(() => store.company.companyToken)
const open = () => {
selectedIndex.value = null
notificationDialogRef.value?.open()
}
const close = () => {
notificationDialogRef.value?.close()
}
const handleConfirm = async () => {
if (selectedIndex.value === null) {
ElMessage.warning('请选择企业')
return
}
try {
// 切换企业前先确认
await ElMessageBox.confirm('切换企业会断开所有已连接的锁,确认切换?', '切换企业-提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
// 执行切换企业操作
await switchCompany({
companyIndex: selectedIndex.value
})
// 更新本地存储的企业信息
store.company.setCompanySelectIndex(selectedIndex.value)
// 重新初始化数据
await store.device.init()
await store.port.init()
await store.user.getUserInfo()
// 跳转到首页
if (router.currentRoute.value.name !== 'caCloudLock') {
router.push({ name: 'caCloudLock' })
}
ElMessage.success('企业切换成功')
close()
} catch (error) {
if (error.code === 402) {
console.log('权限不足', error)
// 处理权限不足的情况
store.auth.logout()
setTimeout(() => {
close()
}, 800)
} else {
console.error('切换企业失败', error)
}
}
}
// 暴露方法给父组件
defineExpose({
open,
close
})
</script>
<style scoped>
.select-company {
/* 样式可以根据需要添加 */
}
</style>
+62
View File
@@ -0,0 +1,62 @@
<template>
<svg
:class="svgClass"
:style="svgStyle"
aria-hidden="true"
>
<use :xlink:href="iconName" />
</svg>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
iconName: {
type: String,
required: true
},
className: {
type: String,
default: ''
},
size: {
type: [String, Number],
default: '16'
},
color: {
type: String,
default: ''
}
})
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`
}
return 'svg-icon'
})
const svgStyle = computed(() => {
const style = {}
if (props.size) {
style.width = typeof props.size === 'number' ? `${props.size}px` : props.size
style.height = typeof props.size === 'number' ? `${props.size}px` : props.size
}
if (props.color) {
style.color = props.color
style.fill = props.color
}
return style
})
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
+11
View File
@@ -0,0 +1,11 @@
// 用户相关组件
export { default as UserInfo } from './UserInfo.vue'
// 对话框组件
export { default as ChangePasswordDialog } from './ChangePasswordDialog.vue'
export { default as NotificationDialog } from './NotificationDialog.vue'
export { default as SelectCompany } from './SelectCompany.vue'
// 通用组件
export { default as DialogHeader } from './DialogHeader.vue'
export { default as SvgIcon } from './SvgIcon.vue'
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- 设备新增组件 -->
</template>
<script>
export default {
name: 'AddDevice',
// 设备新增表单,包含设备型号、序列号、名称等字段
}
</script>
<style scoped>
/* 设备新增样式 */
</style>
+333
View File
@@ -0,0 +1,333 @@
<template>
<div class="device-card-list">
<el-row :gutter="20">
<el-col :xs="24" :sm="12" :md="8" :lg="6" v-for="device in deviceList" :key="device.id">
<el-card class="device-card" :class="{ 'device-used': device.connect_user_id }">
<div class="device-header">
<div class="device-icon">
<i :class="getDeviceIcon(device.type)" />
</div>
<div class="device-info">
<h3>{{ device.name }}</h3>
<el-tag :type="device.connect_user_id ? 'danger' : 'success'" size="small">
{{ device.connect_user_id ? '占用中' : '空闲' }}
</el-tag>
</div>
</div>
<div class="device-content">
<div class="device-item">
<span class="label">端口:</span>
<span class="value">{{ device.port_name }}</span>
</div>
<div class="device-item">
<span class="label">类型:</span>
<span class="value">{{ getDeviceTypeName(device.type) }}</span>
</div>
<div class="device-item">
<span class="label">使用方式:</span>
<span class="value">{{ device.use_mode === 1 ? '共享' : '审批' }}</span>
</div>
<div class="device-item" v-if="device.connect_user_id">
<span class="label">使用者:</span>
<span class="value">{{ device.connect_user_name || device.connect_user_id }}</span>
</div>
<div class="device-item" v-if="device.connect_user_id">
<span class="label">连接时长:</span>
<span class="value">{{ formatDuration(device.connect_time) }}</span>
</div>
</div>
<div class="device-actions">
<el-button
type="primary"
size="small"
@click="handleViewDetail(device)"
plain>
详情
</el-button>
<el-button
v-if="device.connect_user_id && (device.is_admin || device.connect_user_id === currentUserId)"
type="danger"
size="small"
@click="handleForceDisconnect(device)"
plain>
断开
</el-button>
<el-button
v-else-if="!device.connect_user_id"
type="success"
size="small"
@click="handleRequestConnect(device)"
plain>
连接
</el-button>
</div>
</el-card>
</el-col>
</el-row>
<!-- 设备详情弹窗 -->
<DeviceDetail
v-model:visible="detailVisible"
:device="currentDevice"
@force-disconnect="handleForceDisconnectSuccess"
/>
</div>
</template>
<script>
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getDeviceList,
forceDisconnectDevice,
requestConnectDevice
} from '@/api/device'
import { deviceWebSocket } from '@/utils/websocket'
import DeviceDetail from './DeviceDetail.vue'
export default {
name: 'DeviceCardList',
components: {
DeviceDetail
},
setup() {
const store = useStore()
const deviceList = ref([])
const loading = ref(false)
const detailVisible = ref(false)
const currentDevice = ref({})
const interval = ref(null)
const currentUserId = computed(() => store.state.user.userInfo?.id)
// 设备类型映射
const deviceTypeMap = {
1: '手机',
2: '平板',
3: '路由器',
4: '摄像头',
5: '其他'
}
// 设备图标映射
const deviceIconMap = {
1: 'el-icon-mobile-phone',
2: 'el-icon-monitor',
3: 'el-icon-connection',
4: 'el-icon-video-camera',
5: 'el-icon-cpu'
}
const getDeviceTypeName = (type) => {
return deviceTypeMap[type] || '未知类型'
}
const getDeviceIcon = (type) => {
return deviceIconMap[type] || 'el-icon-cpu'
}
const formatDuration = (timestamp) => {
if (!timestamp) return ''
const now = Date.now()
const duration = Math.floor((now - timestamp) / 1000)
if (duration < 60) return `${duration}`
if (duration < 3600) return `${Math.floor(duration / 60)}分钟`
if (duration < 86400) return `${Math.floor(duration / 3600)}小时`
return `${Math.floor(duration / 86400)}`
}
const loadDeviceList = async () => {
loading.value = true
try {
const params = {
page: 1,
limit: 100,
...searchParams.value
}
const response = await getDeviceList(params)
if (response.code === 200) {
deviceList.value = response.data.list || []
}
} catch (error) {
console.error('获取设备列表失败:', error)
ElMessage.error('获取设备列表失败')
} finally {
loading.value = false
}
}
const handleViewDetail = (device) => {
currentDevice.value = device
detailVisible.value = true
}
const handleForceDisconnect = async (device) => {
try {
await ElMessageBox.confirm(
`确定要强制断开 ${device.name} 的连接吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await forceDisconnectDevice(device.id)
ElMessage.success('断开连接成功')
loadDeviceList()
} catch (error) {
if (error !== 'cancel') {
console.error('断开连接失败:', error)
ElMessage.error('断开连接失败')
}
}
}
const handleRequestConnect = async (device) => {
try {
const response = await requestConnectDevice({
device_id: device.id,
use_mode: device.use_mode
})
if (response.code === 200) {
ElMessage.success('申请连接成功')
loadDeviceList()
} else {
ElMessage.error(response.message || '申请连接失败')
}
} catch (error) {
console.error('申请连接失败:', error)
ElMessage.error('申请连接失败')
}
}
const handleForceDisconnectSuccess = () => {
loadDeviceList()
}
const startInterval = () => {
if (interval.value) clearInterval(interval.value)
interval.value = setInterval(() => {
// 定时更新连接时长
deviceList.value = [...deviceList.value]
}, 1000)
}
const stopInterval = () => {
if (interval.value) {
clearInterval(interval.value)
interval.value = null
}
}
onMounted(() => {
loadDeviceList()
startInterval()
deviceWebSocket.connect()
})
onUnmounted(() => {
stopInterval()
deviceWebSocket.disconnect()
})
return {
deviceList,
loading,
detailVisible,
currentDevice,
currentUserId,
getDeviceTypeName,
getDeviceIcon,
formatDuration,
handleViewDetail,
handleForceDisconnect,
handleRequestConnect,
handleForceDisconnectSuccess
}
}
}
</script>
<style scoped>
.device-card-list {
padding: 20px;
}
.device-card {
margin-bottom: 20px;
transition: all 0.3s ease;
}
.device-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.device-card.device-used {
border-left: 4px solid #f56c6c;
}
.device-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.device-icon {
font-size: 32px;
color: #409eff;
margin-right: 15px;
}
.device-info h3 {
margin: 0 0 5px 0;
font-size: 16px;
color: #303133;
}
.device-content {
margin-bottom: 15px;
}
.device-item {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 14px;
}
.device-item .label {
color: #909399;
margin-right: 10px;
}
.device-item .value {
color: #303133;
font-weight: 500;
}
.device-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
@media (max-width: 768px) {
.device-card-list {
padding: 10px;
}
.device-actions {
flex-direction: column;
}
}
</style>
+133
View File
@@ -0,0 +1,133 @@
<template>
<el-dialog
v-model="visible"
title="设备详情"
width="600px"
:before-close="handleClose"
>
<div v-if="device" class="device-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="设备名称">{{ device.name }}</el-descriptions-item>
<el-descriptions-item label="设备类型">{{ device.type_text }}</el-descriptions-item>
<el-descriptions-item label="端口号">{{ device.index }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="device.connect_user_id ? 'danger' : 'success'">
{{ device.connect_user_id ? '占用' : '空闲' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="连接用户" v-if="device.connect_user_name">
{{ device.connect_user_name }}
</el-descriptions-item>
<el-descriptions-item label="连接时间" v-if="device.connect_time">
{{ formatTime(device.connect_time) }}
</el-descriptions-item>
<el-descriptions-item label="IP地址" v-if="device.connect_ip">
{{ device.connect_ip }}
</el-descriptions-item>
<el-descriptions-item label="使用时长" v-if="device.connect_duration">
{{ formatDuration(device.connect_duration) }}
</el-descriptions-item>
</el-descriptions>
<div class="device-actions" v-if="device.connect_user_id">
<el-button type="danger" @click="handleForceDisconnect" size="small">
强制断开
</el-button>
</div>
</div>
</el-dialog>
</template>
<script>
import { ref, watch } from 'vue'
import { ElMessageBox } from 'element-plus'
export default {
name: 'DeviceDetail',
props: {
modelValue: {
type: Boolean,
default: false
},
device: {
type: Object,
default: null
}
},
emits: ['update:modelValue', 'force-disconnect'],
setup(props, { emit }) {
const visible = ref(false)
watch(() => props.modelValue, (newVal) => {
visible.value = newVal
})
watch(visible, (newVal) => {
emit('update:modelValue', newVal)
})
const handleClose = () => {
visible.value = false
}
const handleForceDisconnect = () => {
ElMessageBox.confirm(
'确定要强制断开此设备的连接吗?',
'确认操作',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
emit('force-disconnect', props.device)
handleClose()
})
}
const formatTime = (timestamp) => {
if (!timestamp) return ''
return new Date(timestamp).toLocaleString()
}
const formatDuration = (seconds) => {
if (!seconds) return ''
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
if (hours > 0) {
return `${hours}小时${minutes}分钟${secs}`
} else if (minutes > 0) {
return `${minutes}分钟${secs}`
} else {
return `${secs}`
}
}
return {
visible,
handleClose,
handleForceDisconnect,
formatTime,
formatDuration
}
}
}
</script>
<style scoped>
.device-detail {
padding: 20px 0;
}
.device-actions {
margin-top: 20px;
text-align: center;
}
:deep(.el-descriptions__label) {
font-weight: bold;
color: #606266;
}
</style>
+396
View File
@@ -0,0 +1,396 @@
<template>
<div class="device-list">
<!-- 筛选条件 -->
<div class="filter-section">
<el-form :inline="true" :model="filterForm" class="filter-form">
<el-form-item label="设备名称">
<el-input v-model="filterForm.device_server_group_name" placeholder="请输入设备名称" clearable />
</el-form-item>
<el-form-item label="端口名称">
<el-input v-model="filterForm.name" placeholder="请输入端口名称" clearable />
</el-form-item>
<el-form-item label="端口类型">
<el-select v-model="filterForm.type_text" placeholder="全部" clearable>
<el-option label="全部" value="" />
<el-option v-for="type in deviceTypes" :key="type.id" :label="type.name" :value="type.name" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="filterForm.status" placeholder="全部" clearable>
<el-option label="全部" value="" />
<el-option label="空闲" value="free" />
<el-option label="占用" value="busy" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleFilter">查询</el-button>
<el-button @click="resetFilter">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 设备列表 -->
<div class="list-section">
<el-table
:data="deviceList"
style="width: 100%"
v-loading="loading"
border
>
<el-table-column prop="index" label="序号" width="80" align="center" />
<el-table-column prop="device_server_group_name" label="设备名称" min-width="150" />
<el-table-column prop="name" label="端口名称" min-width="120" />
<el-table-column prop="type_text" label="端口类型" width="100" />
<el-table-column prop="use_mode" label="使用方式" width="100">
<template #default="{ row }">
<el-tag :type="row.use_mode === 1 ? 'success' : 'warning'">
{{ row.use_mode === 1 ? '共享' : '审批' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.connect_user_id ? 'danger' : 'success'">
{{ row.connect_user_id ? '占用' : '空闲' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="connect_duration" label="连接时长" width="120">
<template #default="{ row }">
<span v-if="row.connect_user_id">{{ formatDuration(row.connect_duration) }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="connect_user_name" label="连接人" width="120" />
<el-table-column prop="connect_ip" label="IP地址" width="120" />
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button
type="primary"
link
size="small"
@click="handleDetail(row)"
>
详情
</el-button>
<el-button
v-if="row.connect_user_id && hasPermission('force_disconnect')"
type="danger"
link
size="small"
@click="handleForceDisconnect(row)"
>
强制断开
</el-button>
<el-button
v-if="!row.connect_user_id && hasPermission('request_connect')"
type="success"
link
size="small"
@click="handleRequestConnect(row)"
>
申请连接
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-section">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<!-- 设备详情弹窗 -->
<device-detail
v-model="detailVisible"
:device="currentDevice"
@force-disconnect="handleForceDisconnectFromDetail"
/>
</div>
</template>
<script>
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import DeviceDetail from './DeviceDetail.vue'
import { getDeviceList, getDeviceTypes, forceDisconnectDevice, requestConnectDevice } from '@/api/device'
import { deviceWebSocket } from '@/utils/websocket'
import { useStore } from 'vuex'
export default {
name: 'DeviceList',
components: {
DeviceDetail
},
setup() {
const store = useStore()
// 状态管理
const loading = ref(false)
const deviceList = ref([])
const deviceTypes = ref([])
const detailVisible = ref(false)
const currentDevice = ref(null)
const updateTimer = ref(null)
// 筛选表单
const filterForm = reactive({
device_server_group_name: '',
name: '',
type_text: '',
status: ''
})
// 分页
const pagination = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
// 权限检查
const hasPermission = (permission) => {
const user = store.state.user
if (!user) return false
switch (permission) {
case 'force_disconnect':
return user.is_super === 1 || user.user_role === 2
case 'request_connect':
return true
default:
return false
}
}
// 获取设备列表
const fetchDeviceList = async () => {
loading.value = true
try {
const params = {
page: pagination.currentPage,
limit: pagination.pageSize,
...filterForm
}
const { data } = await getDeviceList(params)
deviceList.value = data.data || []
pagination.total = data.total || 0
} catch (error) {
ElMessage.error('获取设备列表失败')
console.error('获取设备列表失败:', error)
} finally {
loading.value = false
}
}
// 获取设备类型
const fetchDeviceTypes = async () => {
try {
const { data } = await getDeviceTypes()
deviceTypes.value = data || []
} catch (error) {
console.error('获取设备类型失败:', error)
}
}
// 筛选处理
const handleFilter = () => {
pagination.currentPage = 1
fetchDeviceList()
}
// 重置筛选
const resetFilter = () => {
Object.keys(filterForm).forEach(key => {
filterForm[key] = ''
})
pagination.currentPage = 1
fetchDeviceList()
}
// 分页处理
const handleSizeChange = (val) => {
pagination.pageSize = val
fetchDeviceList()
}
const handleCurrentChange = (val) => {
pagination.currentPage = val
fetchDeviceList()
}
// 查看详情
const handleDetail = (device) => {
currentDevice.value = device
detailVisible.value = true
}
// 强制断开
const handleForceDisconnect = async (device) => {
try {
await ElMessageBox.confirm(
`确定要强制断开 ${device.name} 的连接吗?`,
'确认操作',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
// TODO: 调用强制断开API
ElMessage.success('强制断开成功')
fetchDeviceList()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('操作失败')
}
}
}
// 从详情强制断开
const handleForceDisconnectFromDetail = async (device) => {
await handleForceDisconnect(device)
detailVisible.value = false
}
// 申请连接
const handleRequestConnect = async (device) => {
try {
// TODO: 调用申请连接API
ElMessage.success('申请连接成功')
fetchDeviceList()
} catch (error) {
ElMessage.error('申请连接失败')
}
}
// 格式化时长
const formatDuration = (seconds) => {
if (!seconds) return '0秒'
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
if (hours > 0) {
return `${hours}小时${minutes}分钟${secs}`
} else if (minutes > 0) {
return `${minutes}分钟${secs}`
} else {
return `${secs}`
}
}
// 定时更新
const startUpdateTimer = () => {
updateTimer.value = setInterval(() => {
deviceList.value.forEach(device => {
if (device.connect_user_id && device.connect_duration) {
device.connect_duration += 1
}
})
}, 1000)
}
const stopUpdateTimer = () => {
if (updateTimer.value) {
clearInterval(updateTimer.value)
updateTimer.value = null
}
}
// 生命周期
onMounted(() => {
fetchDeviceTypes()
fetchDeviceList()
startUpdateTimer()
deviceWebSocket.connect()
})
onUnmounted(() => {
stopUpdateTimer()
deviceWebSocket.disconnect()
})
return {
loading,
deviceList,
deviceTypes,
filterForm,
pagination,
detailVisible,
currentDevice,
hasPermission,
fetchDeviceList,
handleFilter,
resetFilter,
handleSizeChange,
handleCurrentChange,
handleDetail,
handleForceDisconnect,
handleRequestConnect,
handleForceDisconnectFromDetail,
formatDuration
}
}
}
</script>
<style scoped>
.device-list {
padding: 20px;
}
.filter-section {
margin-bottom: 20px;
padding: 20px;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.filter-form {
display: flex;
flex-wrap: wrap;
gap: 20px;
align-items: flex-end;
}
.list-section {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.pagination-section {
margin-top: 20px;
display: flex;
justify-content: center;
}
:deep(.el-table) {
border-radius: 4px;
}
:deep(.el-table__header-wrapper) {
background-color: #f5f7fa;
}
:deep(.el-table th) {
background-color: #f5f7fa;
color: #606266;
font-weight: bold;
}
</style>
@@ -0,0 +1,516 @@
<template>
<div class="hardware-management">
<!-- 搜索和筛选区域 -->
<el-card class="search-card">
<el-form :inline="true" :model="searchForm" class="search-form">
<el-form-item label="设备名称">
<el-input
v-model="searchForm.name"
placeholder="请输入设备名称"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="设备类型">
<el-select
v-model="searchForm.type"
placeholder="请选择设备类型"
clearable
@change="handleSearch"
>
<el-option
v-for="item in deviceTypes"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="端口名称">
<el-input
v-model="searchForm.port_name"
placeholder="请输入端口名称"
clearable
@clear="handleSearch"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item label="使用方式">
<el-select
v-model="searchForm.use_mode"
placeholder="请选择使用方式"
clearable
@change="handleSearch"
>
<el-option label="共享" value="1" />
<el-option label="审批" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 操作按钮区域 -->
<el-card class="action-card">
<div class="action-buttons">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增设备
</el-button>
<el-button type="danger" @click="handleBatchDelete" :disabled="selectedDevices.length === 0">
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</div>
</el-card>
<!-- 设备列表 -->
<el-card class="list-card">
<el-table
:data="deviceList"
v-loading="loading"
@selection-change="handleSelectionChange"
stripe
border
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="设备名称" min-width="150" />
<el-table-column prop="port_name" label="端口名称" min-width="120" />
<el-table-column prop="type" label="设备类型" width="100">
<template #default="{ row }">
{{ getDeviceTypeName(row.type) }}
</template>
</el-table-column>
<el-table-column prop="use_mode" label="使用方式" width="100">
<template #default="{ row }">
<el-tag :type="row.use_mode === 1 ? 'success' : 'warning'">
{{ row.use_mode === 1 ? '共享' : '审批' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.connect_user_id ? 'danger' : 'success'">
{{ row.connect_user_id ? '占用中' : '空闲' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="connect_user_name" label="当前使用者" min-width="120" />
<el-table-column prop="connect_time" label="连接时间" min-width="180">
<template #default="{ row }">
{{ row.connect_time ? formatDateTime(row.connect_time) : '-' }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)" plain>
编辑
</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)" plain>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.size"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 新增/编辑设备对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '新增设备' : '编辑设备'"
width="500px"
@close="handleDialogClose"
>
<el-form
ref="deviceFormRef"
:model="deviceForm"
:rules="deviceRules"
label-width="100px"
>
<el-form-item label="设备名称" prop="name">
<el-input v-model="deviceForm.name" placeholder="请输入设备名称" />
</el-form-item>
<el-form-item label="端口名称" prop="port_name">
<el-input v-model="deviceForm.port_name" placeholder="请输入端口名称" />
</el-form-item>
<el-form-item label="设备类型" prop="type">
<el-select v-model="deviceForm.type" placeholder="请选择设备类型" style="width: 100%">
<el-option
v-for="item in deviceTypes"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="使用方式" prop="use_mode">
<el-radio-group v-model="deviceForm.use_mode">
<el-radio :label="1">共享</el-radio>
<el-radio :label="2">审批</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input
v-model="deviceForm.description"
type="textarea"
:rows="3"
placeholder="请输入设备描述信息"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">
确定
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getDeviceList,
getDeviceTypes,
addDevice,
updateDevice,
deleteDevice
} from '@/api/device'
export default {
name: 'HardwareManagement',
setup() {
const store = useStore()
const loading = ref(false)
const deviceList = ref([])
const selectedDevices = ref([])
const dialogVisible = ref(false)
const dialogType = ref('add')
const submitLoading = ref(false)
const deviceFormRef = ref()
const searchForm = reactive({
name: '',
type: '',
port_name: '',
use_mode: ''
})
const deviceForm = reactive({
id: null,
name: '',
port_name: '',
type: 1,
use_mode: 1,
description: ''
})
const pagination = reactive({
current: 1,
size: 20,
total: 0
})
const deviceTypes = ref([])
const deviceRules = {
name: [
{ required: true, message: '请输入设备名称', trigger: 'blur' },
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' }
],
port_name: [
{ required: true, message: '请输入端口名称', trigger: 'blur' },
{ min: 1, max: 30, message: '长度在 1 到 30 个字符', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择设备类型', trigger: 'change' }
],
use_mode: [
{ required: true, message: '请选择使用方式', trigger: 'change' }
]
}
const deviceTypeMap = {
1: '手机',
2: '平板',
3: '路由器',
4: '摄像头',
5: '其他'
}
const getDeviceTypeName = (type) => {
return deviceTypeMap[type] || '未知类型'
}
const formatDateTime = (timestamp) => {
if (!timestamp) return ''
const date = new Date(timestamp)
return date.toLocaleString('zh-CN')
}
const loadDeviceTypes = async () => {
try {
const response = await getDeviceTypes()
if (response.code === 200) {
deviceTypes.value = response.data.map(item => ({
value: item.id,
label: item.name
}))
}
} catch (error) {
console.error('获取设备类型失败:', error)
}
}
const loadDeviceList = async () => {
loading.value = true
try {
const params = {
page: pagination.current,
limit: pagination.size,
...searchForm
}
const response = await getDeviceList(params)
if (response.code === 200) {
deviceList.value = response.data.list || []
pagination.total = response.data.total || 0
}
} catch (error) {
console.error('获取设备列表失败:', error)
ElMessage.error('获取设备列表失败')
} finally {
loading.value = false
}
}
const handleSearch = () => {
pagination.current = 1
loadDeviceList()
}
const handleReset = () => {
Object.keys(searchForm).forEach(key => {
searchForm[key] = ''
})
handleSearch()
}
const handleAdd = () => {
dialogType.value = 'add'
Object.keys(deviceForm).forEach(key => {
deviceForm[key] = key === 'type' ? 1 : key === 'use_mode' ? 1 : ''
})
deviceForm.id = null
dialogVisible.value = true
}
const handleEdit = (row) => {
dialogType.value = 'edit'
Object.keys(deviceForm).forEach(key => {
deviceForm[key] = row[key]
})
deviceForm.id = row.id
dialogVisible.value = true
}
const handleDelete = async (row) => {
try {
await ElMessageBox.confirm(
`确定要删除设备 "${row.name}" 吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
await deleteDevice(row.id)
ElMessage.success('删除成功')
loadDeviceList()
} catch (error) {
if (error !== 'cancel') {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
}
}
const handleBatchDelete = async () => {
if (selectedDevices.value.length === 0) return
try {
await ElMessageBox.confirm(
`确定要删除选中的 ${selectedDevices.value.length} 个设备吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
const promises = selectedDevices.value.map(device => deleteDevice(device.id))
await Promise.all(promises)
ElMessage.success('批量删除成功')
loadDeviceList()
selectedDevices.value = []
} catch (error) {
if (error !== 'cancel') {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
}
}
const handleSubmit = async () => {
try {
await deviceFormRef.value.validate()
submitLoading.value = true
const api = dialogType.value === 'add' ? addDevice : updateDevice
const response = await api(deviceForm)
if (response.code === 200) {
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '更新成功')
dialogVisible.value = false
loadDeviceList()
} else {
ElMessage.error(response.message || '操作失败')
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error('提交失败')
} finally {
submitLoading.value = false
}
}
const handleSelectionChange = (selection) => {
selectedDevices.value = selection
}
const handleSizeChange = (size) => {
pagination.size = size
loadDeviceList()
}
const handleCurrentChange = (current) => {
pagination.current = current
loadDeviceList()
}
const handleDialogClose = () => {
deviceFormRef.value?.resetFields()
}
onMounted(() => {
loadDeviceTypes()
loadDeviceList()
})
return {
loading,
deviceList,
selectedDevices,
searchForm,
deviceForm,
dialogVisible,
dialogType,
submitLoading,
deviceFormRef,
deviceTypes,
pagination,
deviceRules,
getDeviceTypeName,
formatDateTime,
handleSearch,
handleReset,
handleAdd,
handleEdit,
handleDelete,
handleBatchDelete,
handleSubmit,
handleSelectionChange,
handleSizeChange,
handleCurrentChange,
handleDialogClose
}
}
}
</script>
<style scoped>
.hardware-management {
padding: 20px;
}
.search-card {
margin-bottom: 20px;
}
.action-card {
margin-bottom: 20px;
}
.action-buttons {
display: flex;
gap: 10px;
}
.list-card {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
@media (max-width: 768px) {
.hardware-management {
padding: 10px;
}
.search-form {
.el-form-item {
margin-bottom: 10px;
width: 100%;
}
}
}
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- 历史记录组件 -->
</template>
<script>
export default {
name: 'History',
// 展示端口使用历史记录,支持分页查询
}
</script>
<style scoped>
/* 历史记录样式 */
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- Excel导入组件 -->
</template>
<script>
export default {
name: 'ImportExcel',
// 处理Excel文件导入,包含进度反馈和模板下载
}
</script>
<style scoped>
/* 导入组件样式 */
</style>
+85
View File
@@ -0,0 +1,85 @@
<template>
<div class="account-login">
<el-input
v-model="localUsername"
placeholder="请输入用户名"
size="large"
:prefix-icon="User"
clearable
maxlength="20"
@keydown.enter="handleLogin"
/>
<el-input
v-model="localPassword"
type="password"
placeholder="请输入密码"
size="large"
:prefix-icon="Lock"
clearable
show-password
maxlength="20"
@keydown.enter="handleLogin"
style="margin-top: 20px"
/>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { User, Lock } from '@element-plus/icons-vue'
const props = defineProps({
username: {
type: String,
default: ''
},
password: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:username', 'update:password', 'login'])
const localUsername = ref(props.username)
const localPassword = ref(props.password)
watch(() => props.username, (newVal) => {
localUsername.value = newVal
})
watch(() => props.password, (newVal) => {
localPassword.value = newVal
})
watch(localUsername, (newVal) => {
emit('update:username', newVal)
})
watch(localPassword, (newVal) => {
emit('update:password', newVal)
})
const handleLogin = () => {
emit('login')
}
</script>
<style scoped>
.account-login {
width: 100%;
}
:deep(.el-input__wrapper) {
box-shadow: 0 0 0 1px #dcdfe6;
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409EFF;
}
</style>
+108
View File
@@ -0,0 +1,108 @@
<template>
<div class="code-login">
<el-input
v-model="localMobile"
placeholder="请输入手机号"
size="large"
:prefix-icon="Phone"
clearable
type="number"
maxlength="11"
@keydown.enter="handleLogin"
/>
<div class="code-input-container" style="margin-top: 20px">
<el-input
v-model="localCode"
placeholder="请输入验证码"
size="large"
:prefix-icon="Key"
clearable
type="number"
maxlength="6"
@keydown.enter="handleLogin"
style="flex: 1"
/>
<el-button
:disabled="localMobile.length !== 11 || codeText !== '获取验证码'"
@click="handleGetCode"
size="large"
style="margin-left: 10px; min-width: 120px"
>
{{ codeText }}
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { Phone, Key } from '@element-plus/icons-vue'
const props = defineProps({
mobile: {
type: String,
default: ''
},
code: {
type: String,
default: ''
},
codeText: {
type: String,
default: '获取验证码'
}
})
const emit = defineEmits(['update:mobile', 'update:code', 'getCode', 'login'])
const localMobile = ref(props.mobile)
const localCode = ref(props.code)
watch(() => props.mobile, (newVal) => {
localMobile.value = newVal
})
watch(() => props.code, (newVal) => {
localCode.value = newVal
})
watch(localMobile, (newVal) => {
emit('update:mobile', newVal)
})
watch(localCode, (newVal) => {
emit('update:code', newVal)
})
const handleGetCode = () => {
emit('getCode')
}
const handleLogin = () => {
emit('login')
}
</script>
<style scoped>
.code-login {
width: 100%;
}
.code-input-container {
display: flex;
align-items: center;
}
:deep(.el-input__wrapper) {
box-shadow: 0 0 0 1px #dcdfe6;
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #409EFF;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409EFF;
}
</style>
+85
View File
@@ -0,0 +1,85 @@
<template>
<div></div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { ElMessageBox } from 'element-plus'
import { useStore } from '@/store'
import { logout } from '@/utils/auth'
const store = useStore()
// 事件监听函数
const handleLoginConflict = (event, data) => {
if (!data || !data.data) {
handleLogout()
return
}
if (isHandling) return
showLoginConflictDialog()
}
let isHandling = false
// 显示登录冲突对话框
const showLoginConflictDialog = () => {
isHandling = true
store.setIsConnectRequesting(true)
ElMessageBox.confirm(
'账号已在其他设备登录,如不是你本人,请联系管理员并修改密码。',
'提示',
{
confirmButtonText: '确定',
showCancelButton: false,
closeOnClickModal: false,
closeOnPressEscape: false,
showClose: false,
type: 'warning'
}
).then(() => {
// 刷新页面重新登录
window.location.reload()
}).catch(() => {
// 用户点击了取消或其他关闭操作,也刷新页面
window.location.reload()
}).finally(() => {
isHandling = false
})
}
// 处理登出
const handleLogout = () => {
logout()
}
// 事件名称常量
const LOGIN_CONFLICT_EVENT = 'login_conflict'
onMounted(() => {
// 监听登录冲突事件
if (window.electronAPI) {
window.electronAPI.onLoginConflict(handleLoginConflict)
}
// 监听WebSocket消息
if (window.ws) {
window.ws.on('login_conflict', handleLoginConflict)
}
})
onUnmounted(() => {
// 移除事件监听
if (window.electronAPI) {
window.electronAPI.removeLoginConflictListener(handleLoginConflict)
}
if (window.ws) {
window.ws.off('login_conflict', handleLoginConflict)
}
})
</script>
+81
View File
@@ -0,0 +1,81 @@
<template>
<el-dialog
v-model="dialogVisible"
title="选择企业"
width="400px"
center
:show-close="false"
:close-on-click-modal="false"
>
<el-select
v-model="selectedCompanyIndex"
placeholder="请选择企业"
size="large"
style="width: 100%"
>
<el-option
v-for="(company, index) in companyList"
:key="company.id"
:label="company.name"
:value="index"
/>
</el-select>
<template #footer>
<el-button type="primary" size="large" @click="handleConfirm">
确认
</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { getCompanyList as getCompanyListApi } from '@/api/auth'
const dialogVisible = ref(false)
const selectedCompanyIndex = ref(0)
const companyList = reactive([])
const emit = defineEmits(['confirm'])
const open = async () => {
try {
const response = await getCompanyListApi()
companyList.splice(0, companyList.length, ...response.data)
if (companyList.length > 0) {
selectedCompanyIndex.value = companyList.findIndex(item => item.is_default) || 0
}
dialogVisible.value = true
} catch (error) {
ElMessage.error('获取企业列表失败')
}
}
const handleConfirm = () => {
if (selectedCompanyIndex.value === -1) {
ElMessage.warning('请选择企业')
return
}
const selectedCompany = companyList[selectedCompanyIndex.value]
emit('confirm', selectedCompany)
dialogVisible.value = false
}
const close = () => {
dialogVisible.value = false
}
defineExpose({
open,
close
})
</script>
<style scoped>
:deep(.el-dialog) {
border-radius: 8px;
}
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- 端口卡片列表组件 -->
</template>
<script>
export default {
name: 'PortCardList',
// 实现端口状态管理、连接时长自动计时、权限控制的强制断开按钮等功能
}
</script>
<style scoped>
/* 端口卡片样式 */
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- 端口配置表单组件 -->
</template>
<script>
export default {
name: 'PortConfigForm',
// 处理端口名称、类型、使用方式等字段的表单验证和提交
}
</script>
<style scoped>
/* 表单样式 */
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- 端口列表组件 -->
</template>
<script>
export default {
name: 'PortList',
// 表格展示端口列表,包含状态可视化和操作功能
}
</script>
<style scoped>
/* 端口列表样式 */
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<!-- 端口管理组件 -->
</template>
<script>
export default {
name: 'PortManage',
// 实现端口状态分类、WebSocket轮询更新、导入导出功能
}
</script>
<style scoped>
/* 端口管理样式 */
</style>
+105
View File
@@ -0,0 +1,105 @@
<template>
<el-dialog
v-model="dialogVisible"
width="432"
center
:show-close="false"
:close-on-press-escape="false"
modal
draggable
destroy-on-close
:close-on-click-modal="false"
class="notification-dialog"
>
<template #header>
<MainHead
title="更新-提示"
:isClose="isClose"
@close="handleClose"
/>
</template>
<div class="notification-dialog_content">
<el-icon size="21px" color="#D54941FF">
<WarningFilled />
</el-icon>
<span>文件已经下载完成更新程序需要先关闭正在运行的程序是否关闭</span>
</div>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" size="large" @click="handleConfirm">
确认
</el-button>
</div>
</template>
<!-- 插槽内容 -->
<slot name="default"></slot>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { WarningFilled } from '@element-plus/icons-vue'
import MainHead from '@/components/common/MainHead.vue'
const props = defineProps({
isClose: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['confirm', 'close'])
const dialogVisible = ref(false)
const open = () => {
dialogVisible.value = true
}
const close = () => {
dialogVisible.value = false
emit('close')
}
const handleClose = () => {
close()
}
const handleConfirm = () => {
emit('confirm')
close()
}
defineExpose({
open,
close
})
</script>
<style scoped>
.notification-dialog_content {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.notification-dialog_content span {
flex: 1;
font-size: 14px;
color: #333;
}
.dialog-footer {
text-align: center;
}
:deep(.notification-dialog) {
padding: 0 0 25px 0;
box-shadow: 0 4px 35.8px 0 #00000026;
border-radius: 8px;
}
</style>
+317
View File
@@ -0,0 +1,317 @@
<template>
<div>
<el-dialog
v-model="dialogVisible"
title="云企通安全云锁简洁版 升级"
width="690"
:close-on-press-escape="false"
:close-on-click-modal="false"
:show-close="false"
class="upgrade-version"
>
<div class="upgrade-version_head">
<img
class="upgrade-version_title"
:src="upgradeTitleImage"
alt="升级标题"
/>
</div>
<!-- 下载进度 -->
<div v-if="isDownloading" class="upgrade-version_progress">
<div class="upgrade-version_progress_left">
<div class="upgrade-version_progress_left_text">
<span>正在下载最新版本...</span>
</div>
<el-progress
:text-inside="true"
:stroke-width="14"
:percentage="downloadProgress"
/>
</div>
<el-button size="large" @click="cancelDownload">
取消下载
</el-button>
</div>
<!-- 升级提示 -->
<div v-else class="upgrade-version_tips">
<span>检测到新版本请点击"立即升级"安装新版本</span>
<el-button
type="primary"
size="large"
@click="startDownload"
>
立即升级 ({{ newVersion }})
</el-button>
</div>
</el-dialog>
<UpdateDialog
ref="updateDialogRef"
@confirm="confirmUpdate"
:isClose="!isForceUpdate"
/>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElLoading } from 'element-plus'
import UpdateDialog from './UpdateDialog.vue'
import { getVersion, checkUpdate } from '@/api/system'
import { useStore } from '@/store'
const emit = defineEmits(['close'])
// 响应式数据
const dialogVisible = ref(false)
const isDownloading = ref(false)
const downloadProgress = ref(0)
const newVersion = ref('0.0.0')
const isForceUpdate = ref(false)
const updateDialogRef = ref(null)
// 图片资源
const upgradeTitleImage = new URL('@/assets/images/upgrade-title.png', import.meta.url).href
// 全局状态
const store = useStore()
// 监听事件
const handleDownloadProgress = (event, data) => {
if (data.code === 200) {
downloadProgress.value = data.figure
if (data.figure === 100) {
isDownloading.value = false
downloadProgress.value = 0
updateDialogRef.value?.open()
}
}
}
const handleUpdateComplete = () => {
downloadProgress.value = 0
isDownloading.value = false
updateDialogRef.value?.open()
}
const handleCheckVersion = async () => {
await checkForUpdates(0)
}
// 检查更新
const checkForUpdates = async (type = 0) => {
try {
const versionInfo = await getVersion()
if (versionInfo && Object.keys(versionInfo).length > 0) {
handleVersionInfo(versionInfo, type, 0)
} else {
ElMessage.success('当前版本已是最新!')
}
} catch (error) {
ElMessage.error('接口地址不通,请更换')
}
}
// 处理版本信息
const handleVersionInfo = (versionInfo, type, flag) => {
const currentVersion = getCurrentVersion()
const serverVersion = formatVersion(versionInfo.version)
const downloadPath = getDownloadPath()
if (isNewerVersion(serverVersion, currentVersion)) {
if (flag && versionInfo.is_force === 1 || !flag) {
newVersion.value = serverVersion
isForceUpdate.value = versionInfo.is_force !== 1
dialogVisible.value = true
}
} else {
if (type) {
ElMessage.success('当前版本已是最新!')
}
cleanDownloadPath(downloadPath)
}
}
// 获取当前版本
const getCurrentVersion = () => {
// 从package.json或环境变量获取
return process.env.VUE_APP_VERSION || '1.0.0'
}
// 格式化版本号
const formatVersion = (version) => {
return version || '0.0.0'
}
// 获取下载路径
const getDownloadPath = () => {
// 根据实际项目结构调整
return `/downloads`
}
// 判断是否为新版本
const isNewerVersion = (newVersion, currentVersion) => {
const newParts = newVersion.split('.').map(Number)
const currentParts = currentVersion.split('.').map(Number)
for (let i = 0; i < Math.max(newParts.length, currentParts.length); i++) {
const newPart = newParts[i] || 0
const currentPart = currentParts[i] || 0
if (newPart > currentPart) return true
if (newPart < currentPart) return false
}
return false
}
// 清理下载路径
const cleanDownloadPath = (path) => {
// 清理下载目录
console.log('Cleaning download path:', path)
}
// 开始下载
const startDownload = async () => {
try {
const versionInfo = await getVersion()
isDownloading.value = true
// 触发Electron主进程下载
if (window.electronAPI) {
window.electronAPI.checkForUpdate(versionInfo)
}
} catch (error) {
ElMessage.error('下载失败')
isDownloading.value = false
}
}
// 取消下载
const cancelDownload = () => {
if (window.electronAPI) {
window.electronAPI.cancelDownload()
}
downloadProgress.value = 0
isDownloading.value = false
}
// 确认更新
const confirmUpdate = async () => {
try {
await disconnectAllDevices()
const loading = ElLoading.service({
lock: true,
text: '正在退出...',
background: 'rgba(0, 0, 0, 0.7)'
})
if (window.electronAPI) {
window.electronAPI.startUpdate()
}
} catch (error) {
console.error('更新失败:', error)
}
}
// 断开所有设备连接
const disconnectAllDevices = async () => {
try {
await store.disconnectAllDevices()
} catch (error) {
console.error('断开设备连接失败:', error)
}
}
// 公开方法
const open = () => {
dialogVisible.value = true
checkForUpdates()
}
const close = () => {
dialogVisible.value = false
emit('close')
}
// 生命周期
onMounted(() => {
// 监听Electron事件
if (window.electronAPI) {
window.electronAPI.onDownloadProgress(handleDownloadProgress)
window.electronAPI.onUpdateComplete(handleUpdateComplete)
window.electronAPI.onCheckVersion(handleCheckVersion)
}
// 延迟检查版本
setTimeout(async () => {
if (!store.userInfo) {
await checkForUpdates(1)
}
}, 500)
})
onUnmounted(() => {
// 移除事件监听
if (window.electronAPI) {
window.electronAPI.removeDownloadProgressListener(handleDownloadProgress)
window.electronAPI.removeUpdateCompleteListener(handleUpdateComplete)
window.electronAPI.removeCheckVersionListener(handleCheckVersion)
}
})
defineExpose({
open,
close,
checkForUpdates
})
</script>
<style scoped>
.upgrade-version_head {
text-align: center;
margin-bottom: 30px;
}
.upgrade-version_title {
width: 300px;
height: auto;
}
.upgrade-version_progress {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
}
.upgrade-version_progress_left {
flex: 1;
}
.upgrade-version_progress_left_text {
margin-bottom: 10px;
color: #666;
}
.upgrade-version_tips {
text-align: center;
padding: 20px 0;
}
.upgrade-version_tips span {
display: block;
margin-bottom: 20px;
color: #666;
font-size: 16px;
}
:deep(.upgrade-version) {
--el-dialog-padding-primary: 0;
border-radius: 8px;
}
</style>
+52
View File
@@ -0,0 +1,52 @@
// 主应用入口文件
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import { createPinia } from 'pinia';
import App from './App.vue';
import store from './store';
import router from './router';
import SvgIcon from './components/common/SvgIcon.vue';
// 全局样式
import './style.css';
const pinia = createPinia();
// 创建Vue应用
const app = createApp(App);
// 注册全局组件
app.component('SvgIcon', SvgIcon);
app.use(pinia); // 必须先注册 Pinia
// 使用Vuex状态管理
app.use(store);
app.use(router);
// 使用ElementPlus
app.use(ElementPlus);
// 挂载应用
app.mount('#app');
// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
console.error('全局错误:', err);
console.error('错误信息:', info);
};
// 全局属性
app.config.globalProperties.$electron = {
ipcRenderer: window.require ? window.require('electron').ipcRenderer : null
};
// 控制台日志增强
if (process.env.NODE_ENV === 'development') {
const originalLog = console.log;
console.log = (...args) => {
const timestamp = new Date().toLocaleString('zh-CN', {
timeZone: 'Asia/Shanghai'
});
originalLog(`[${timestamp}]`, ...args);
};
}
+83
View File
@@ -0,0 +1,83 @@
import { createRouter, createWebHistory } from 'vue-router'
import { getToken } from '@/utils/auth'
// 路由组件
import Login from '@/views/login/Login.vue'
import PortManage from '@/views/PortManage.vue'
import DeviceManage from '@/views/DeviceManage.vue'
import HelpCenter from '@/views/HelpCenter.vue'
import GuanglianDaDocument from '@/views/GuanglianDaDocument.vue'
const routes = [
{
path: '/login',
name: 'Login',
component: Login,
meta: { title: '登录' }
},
{
path: '/portManage',
name: 'PortManage',
component: PortManage,
meta: { title: '端口管理', requiresAuth: true }
},
{
path: '/deviceManage',
name: 'DeviceManage',
component: DeviceManage,
meta: { title: '设备管理', requiresAuth: true }
},
{
path: '/helpCenter',
name: 'HelpCenter',
component: HelpCenter,
meta: { title: '帮助中心', requiresAuth: true }
},
{
path: '/guanglianDaDocument',
name: 'GuanglianDaDocument',
component: GuanglianDaDocument,
meta: { title: '广联达网络锁配置', requiresAuth: true }
},
{
path: '/',
redirect: '/deviceManage',
meta: { requiresAuth: true }
},
{
path: '/caCloudLock',
redirect: '/login'
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
// 设置页面标题
if (to.meta?.title) {
document.title = `${to.meta.title} - 云企通安全云锁`
}
// 检查是否需要登录
if (to.meta?.requiresAuth) {
const token = getToken()
if (!token) {
next('/login')
} else {
next()
}
} else {
next()
}
})
// 路由错误处理
router.onError((error) => {
console.error('路由错误:', error)
})
export default router
+356
View File
@@ -0,0 +1,356 @@
/**
* API管理器
* 处理网络请求配置、错误处理、文件下载等功能
* 从混淆代码中提取的业务逻辑
*/
import axios from 'axios';
import { ElMessage, ElLoading } from 'element-plus';
import { userStore } from '@/store/user';
import { companyStore } from '@/store/company';
import { ipcRenderer } from 'electron';
// 配置常量
const CONFIG = {
baseURL: '/dxsz_client_api',
timeout: 20000,
maxRetries: 3,
retryDelay: 1000
};
// 创建axios实例
const apiClient = axios.create({
baseURL: CONFIG.baseURL,
timeout: CONFIG.timeout,
withCredentials: false
});
// 全局变量
let loadingInstance = null;
/**
* 显示加载中
*/
function showLoading() {
loadingInstance = ElLoading.service({
lock: true,
text: 'Loading',
background: 'rgba(0, 0, 0, 0.7)'
});
}
/**
* 隐藏加载中
*/
function hideLoading() {
if (loadingInstance) {
loadingInstance.close();
loadingInstance = null;
}
}
/**
* 获取错误消息
*/
function getErrorMessage(error) {
if (!error || !error.response) {
return '网络连接错误';
}
const { status, data } = error.response;
const errorMessages = {
302: '接口重定向了!',
400: '参数不正确!',
401: '您未登录,或者登录已经超时,请先登录!',
402: () => {
reloadPage();
return data.msg || '登录已过期';
},
403: () => {
reloadPage();
return data.msg || '您没有权限操作!';
},
404: `请求地址出错: ${error.response.config?.url}`,
408: '请求超时!',
409: '系统已存在相同数据!',
500: data.msg || '服务器内部错误',
501: '服务未实现!',
502: '网关错误!',
503: '服务不可用!',
504: '服务暂时无法访问,请稍后再试!',
505: 'HTTP 版本不受支持!'
};
const message = errorMessages[status];
if (typeof message === 'function') {
return message();
}
return message || '异常问题,请联系管理员!';
}
/**
* 重新加载页面
*/
function reloadPage() {
if (companyStore().companyToken) {
setTimeout(() => {
window.location.reload();
}, 1500);
}
}
// 请求拦截器
apiClient.interceptors.request.use(
(config) => {
const user = userStore();
const company = companyStore();
// 设置默认值
config.isErrorMessage = config.isErrorMessage ?? true;
config.loading = config.loading ?? false;
if (config.loading) {
showLoading();
}
// 设置token
if (user.token) {
config.headers.token = user.token;
}
if (company.companyToken) {
config.headers['company-token'] = company.companyToken;
}
// 设置默认content-type
if (!config.headers['content-type'] && config.method === 'post') {
config.headers['content-type'] = 'application/json';
}
return config;
},
(error) => {
hideLoading();
return Promise.reject(error);
}
);
// 响应拦截器
apiClient.interceptors.response.use(
(response) => {
hideLoading();
return response.data;
},
(error) => {
hideLoading();
const message = getErrorMessage(error);
if (error.response?.config?.isErrorMessage !== false && message !== 'fail') {
ElMessage.error(message);
}
if (error.response) {
error.response.data.message = message;
}
return Promise.reject(error.response?.data);
}
);
/**
* 设备相关API
*/
export const deviceAPI = {
/**
* 连接设备
*/
async connectDevice(deviceInfo) {
const systemInfo = {
computer_name: await ipcRenderer.invoke('get-hostname'),
ip: await ipcRenderer.invoke('get-ip-address'),
mac: await ipcRenderer.invoke('get-mac-address')
};
return apiClient({
url: `/ca_relation_device/connect/${deviceInfo.id}`,
method: 'post',
data: {
...deviceInfo,
...systemInfo
},
loading: false
});
},
/**
* 断开设备连接
*/
async disconnectDevice(deviceInfo) {
return apiClient({
url: `/ca_relation_device/disconnect/${deviceInfo.id}`,
method: 'post',
data: deviceInfo,
loading: false
});
},
/**
* 修改设备信息
*/
async updateDevice(deviceInfo) {
return apiClient({
url: `/ca_relation_device/change/${deviceInfo.id}`,
method: 'put',
data: deviceInfo,
loading: false
});
}
};
/**
* 文件下载API
*/
export const fileAPI = {
/**
* 下载文件
*/
async downloadFile(url, fileName = null, mkdirPath = 'Downloads') {
try {
const finalFileName = fileName || url.split('/').pop();
const result = await ipcRenderer.invoke('download-file', {
url,
fileName: finalFileName,
mkdirPath
});
if (result.success) {
return {
success: true,
filePath: result.filePath
};
} else {
return {
success: false,
error: result.error
};
}
} catch (error) {
throw new Error(`下载文件失败: ${error.message}`);
}
}
};
/**
* 系统工具API
*/
export const systemAPI = {
/**
* 执行命令
*/
async executeCommand(command, options = {}) {
const { exec } = await import('child_process');
const { promisify } = await import('util');
const execAsync = promisify(exec);
try {
const { stdout, stderr } = await execAsync(command, {
timeout: options.timeout || 5000,
...options
});
return {
success: true,
stdout: stdout.trim(),
stderr: stderr.trim()
};
} catch (error) {
if (error.code === 0 || error.code === null) {
return {
success: true,
stdout: '',
stderr: ''
};
}
return {
success: false,
stdout: error.stdout || '',
stderr: error.stderr || '',
error: error
};
}
},
/**
* 检查进程是否存在
*/
async checkProcess(processName, options = { timeout: 3000 }) {
try {
const result = await this.executeCommand(`tasklist | findstr "${processName}"`, options);
return result.stdout.includes(processName);
} catch {
return false;
}
},
/**
* 安装驱动
*/
async installDriver() {
const driverPath = await ipcRenderer.invoke('get-app-path');
const command = `"${driverPath}\\resources\\driver\\devcon.exe" ins -w`;
return this.executeCommand(command, { timeout: 5000 });
},
/**
* 卸载驱动
*/
async uninstallDriver(mode = 'u') {
const driverPath = await ipcRenderer.invoke('get-app-path');
const command = `"${driverPath}\\resources\\driver\\devcon.exe" unins -${mode}`;
return this.executeCommand(command, { timeout: 5000 });
}
};
/**
* 工具函数
*/
export const utils = {
/**
* 延迟执行
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
/**
* 获取应用路径
*/
async getAppPath() {
return await ipcRenderer.invoke('get-app-path');
}
};
/**
* 重新导出
*/
export default {
apiClient,
deviceAPI,
fileAPI,
systemAPI,
utils
};
// 兼容性导出
export {
apiClient as http,
deviceAPI as device,
fileAPI as file,
systemAPI as system,
utils
};
+274
View File
@@ -0,0 +1,274 @@
// API服务层
import axios from 'axios';
import { API_BASE_URL, API_ENDPOINTS } from '../utils/constants.js';
// 获取设备MAC地址
export function getDeviceMacAddress() {
const os = require('os');
const networkInterfaces = os.networkInterfaces();
for (const name of Object.keys(networkInterfaces)) {
for (const net of networkInterfaces[name]) {
if (net.mac && net.mac !== '00:00:00:00:00:00') {
return net.mac;
}
}
}
return 'unknown';
}
// 创建axios实例
const http = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
http.interceptors.request.use(
config => {
// 可以在这里添加token等认证信息
return config;
},
error => {
return Promise.reject(error);
}
);
// 响应拦截器
http.interceptors.response.use(
response => {
return response;
},
error => {
console.error('API请求错误:', error);
return Promise.reject(error);
}
);
// 认证相关API
export const authAPI = {
// 账号密码登录
login: (data) => {
const mac = getDeviceMacAddress();
return http.post(API_ENDPOINTS.AUTH.LOGIN, {
...data,
mac
});
},
// 手机号验证码登录
phoneLogin: (data) => {
const mac = getDeviceMacAddress();
return http.post(API_ENDPOINTS.AUTH.PHONE_LOGIN, {
...data,
mac
});
},
// 企业登录
companyLogin: (data) => {
return http.post(API_ENDPOINTS.AUTH.COMPANY_LOGIN, data);
},
// 设置登录缓存
setLoginCache: (data) => {
const mac = getDeviceMacAddress();
return http.post(API_ENDPOINTS.AUTH.SET_LOGIN_CACHE, {
...data,
mac
});
}
};
// 用户相关API
export const userAPI = {
// 获取用户信息
getUserInfo: () => {
return http.put(API_ENDPOINTS.USER.INFO, {});
},
// 获取公司列表
getCompanyList: () => {
return http.get(API_ENDPOINTS.USER.COMPANY_LIST, {
params: {},
loading: true
});
},
// 根据登录获取用户ID
getUserIdByLogin: (data) => {
const mac = getDeviceMacAddress();
return http.post(API_ENDPOINTS.USER.GET_USER_ID, {
...data,
mac,
verify_type: 3
});
},
// 修改密码
changePassword: (data) => {
return http.post(API_ENDPOINTS.USER.CHANGE_PASSWORD, data, {
loading: true
});
}
};
// 公司相关API
export const companyAPI = {
// 获取公司用户信息
getCompanyUserInfo: () => {
return http.post(API_ENDPOINTS.COMPANY.INFO, {}, {
loading: true
});
}
};
// 设备相关API
export const deviceAPI = {
// 获取所有设备列表
getDeviceList: (params) => {
return http.get(API_ENDPOINTS.DEVICE.LIST, {
params,
loading: true
});
},
// 获取设备类型列表
getDeviceTypes: (params) => {
return http.get(API_ENDPOINTS.DEVICE.TYPES, {
params,
loading: true
});
},
// 连接设备
connectDevice: (params) => {
return http.get(API_ENDPOINTS.DEVICE.CONNECT, {
params,
loading: params.loading || false,
isErrorMessage: false
});
},
// 获取设备使用日志
getDeviceLogs: (params) => {
return http.get(API_ENDPOINTS.DEVICE.LOGS, {
params,
loading: true
});
},
// 添加使用申请
addUseApprove: (params) => {
return http.post(API_ENDPOINTS.DEVICE.APPROVE, null, {
params,
loading: true
});
},
// 撤销使用申请
revokeUseApprove: (params) => {
return http.post(API_ENDPOINTS.DEVICE.REVOKE, null, {
params,
loading: true
});
},
// 获取用途列表
getPurposeList: (params) => {
return http.get(API_ENDPOINTS.DEVICE.PURPOSE_LIST, {
params,
loading: true
});
},
// 获取设备使用时间列表
getDeviceTimeList: (params) => {
return http.get(API_ENDPOINTS.DEVICE.TIME_LIST, {
params,
loading: true
});
},
// 获取端口列表
getPortList: (params) => {
return http.get(API_ENDPOINTS.DEVICE.PORT_LIST, {
params,
loading: true
});
},
// 获取端口状态
getPortStatus: (params) => {
return http.get(API_ENDPOINTS.DEVICE.PORT_STATUS, {
params,
loading: true
});
}
};
// 通用API
export const commonAPI = {
// 获取验证码
getVerificationCode: (data) => {
return http.post(API_ENDPOINTS.COMMON.VERIFICATION_CODE, data, {
loading: true
});
},
// 获取微信二维码
getWechatQRCode: () => {
return http.get(API_ENDPOINTS.COMMON.WECHAT_QRCODE, {
params: {}
});
},
// 检查扫码状态
checkScanStatus: (params) => {
return http.get(API_ENDPOINTS.COMMON.CHECK_SCAN, {
params
});
}
};
// 统一的API响应处理
export function handleApiResponse(response, successMessage = '操作成功') {
if (response.data && response.data.code === 200) {
return {
success: true,
data: response.data.data,
message: response.data.message || successMessage
};
} else {
return {
success: false,
message: response.data?.message || '操作失败',
code: response.data?.code
};
}
}
// 统一的错误处理
export function handleApiError(error) {
console.error('API调用失败:', error);
let message = '网络请求失败';
if (error.response) {
// 服务器返回了错误状态码
message = error.response.data?.message || `服务器错误: ${error.response.status}`;
} else if (error.request) {
// 请求发出但没有收到响应
message = '网络连接失败,请检查网络';
} else {
// 其他错误
message = error.message || '请求失败';
}
return {
success: false,
message
};
}
+371
View File
@@ -0,0 +1,371 @@
/**
* 驱动管理器
* 处理驱动检测、安装、修复等功能
* 从混淆代码中提取的业务逻辑
*/
import { systemAPI } from './api-manager';
import { ipcRenderer } from 'electron';
import { ElMessage, ElMessageBox } from 'element-plus';
// 驱动配置
const DRIVER_CONFIG = {
driverName: 'CloudLock Driver',
driverFile: 'cloudlock.sys',
serviceName: 'CloudLockService',
installPath: 'C:\\Program Files\\CloudLock\\Driver',
registryPath: 'HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\CloudLockService'
};
// 驱动状态
const DRIVER_STATUS = {
NOT_INSTALLED: 'not_installed',
INSTALLED: 'installed',
RUNNING: 'running',
STOPPED: 'stopped',
ERROR: 'error'
};
/**
* 驱动管理器类
*/
class DriverManager {
constructor() {
this.currentStatus = DRIVER_STATUS.NOT_INSTALLED;
this.isChecking = false;
}
/**
* 检查驱动状态
*/
async checkDriverStatus() {
if (this.isChecking) return this.currentStatus;
this.isChecking = true;
try {
// 检查服务是否存在
const serviceExists = await this.checkServiceExists();
if (!serviceExists) {
this.currentStatus = DRIVER_STATUS.NOT_INSTALLED;
return this.currentStatus;
}
// 检查服务状态
const serviceStatus = await this.getServiceStatus();
this.currentStatus = serviceStatus;
return this.currentStatus;
} catch (error) {
console.error('检查驱动状态失败:', error);
this.currentStatus = DRIVER_STATUS.ERROR;
return this.currentStatus;
} finally {
this.isChecking = false;
}
}
/**
* 检查服务是否存在
*/
async checkServiceExists() {
try {
const result = await systemAPI.executeCommand(
`sc query ${DRIVER_CONFIG.serviceName}`,
{ timeout: 3000 }
);
return result.success;
} catch {
return false;
}
}
/**
* 获取服务状态
*/
async getServiceStatus() {
try {
const result = await systemAPI.executeCommand(
`sc query ${DRIVER_CONFIG.serviceName}`,
{ timeout: 3000 }
);
if (!result.success) {
return DRIVER_STATUS.ERROR;
}
const output = result.stdout;
if (output.includes('RUNNING')) {
return DRIVER_STATUS.RUNNING;
} else if (output.includes('STOPPED')) {
return DRIVER_STATUS.STOPPED;
} else {
return DRIVER_STATUS.INSTALLED;
}
} catch {
return DRIVER_STATUS.ERROR;
}
}
/**
* 安装驱动
*/
async installDriver() {
try {
ElMessage.info('正在安装驱动...');
// 检查管理员权限
const isAdmin = await this.checkAdminRights();
if (!isAdmin) {
ElMessage.error('需要管理员权限才能安装驱动');
return { success: false, error: '需要管理员权限' };
}
// 执行驱动安装
const result = await systemAPI.installDriver();
if (result.success) {
ElMessage.success('驱动安装成功');
this.currentStatus = DRIVER_STATUS.INSTALLED;
return { success: true };
} else {
ElMessage.error('驱动安装失败');
return { success: false, error: result.stderr || '安装失败' };
}
} catch (error) {
console.error('驱动安装失败:', error);
ElMessage.error('驱动安装失败: ' + error.message);
return { success: false, error: error.message };
}
}
/**
* 卸载驱动
*/
async uninstallDriver() {
try {
const confirmed = await ElMessageBox.confirm(
'确定要卸载驱动吗?这将影响所有使用此驱动的应用。',
'确认卸载',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
if (!confirmed) return { success: false, cancelled: true };
ElMessage.info('正在卸载驱动...');
// 停止服务
await this.stopService();
// 卸载驱动
const result = await systemAPI.uninstallDriver();
if (result.success) {
ElMessage.success('驱动卸载成功');
this.currentStatus = DRIVER_STATUS.NOT_INSTALLED;
return { success: true };
} else {
ElMessage.error('驱动卸载失败');
return { success: false, error: result.stderr || '卸载失败' };
}
} catch (error) {
console.error('驱动卸载失败:', error);
ElMessage.error('驱动卸载失败: ' + error.message);
return { success: false, error: error.message };
}
}
/**
* 修复驱动
*/
async repairDriver() {
try {
ElMessage.info('正在修复驱动...');
// 先停止服务
await this.stopService();
// 重新安装驱动
const result = await systemAPI.installDriver();
if (result.success) {
// 启动服务
await this.startService();
ElMessage.success('驱动修复成功');
this.currentStatus = DRIVER_STATUS.RUNNING;
return { success: true };
} else {
ElMessage.error('驱动修复失败');
return { success: false, error: result.stderr || '修复失败' };
}
} catch (error) {
console.error('驱动修复失败:', error);
ElMessage.error('驱动修复失败: ' + error.message);
return { success: false, error: error.message };
}
}
/**
* 启动服务
*/
async startService() {
try {
const result = await systemAPI.executeCommand(
`sc start ${DRIVER_CONFIG.serviceName}`,
{ timeout: 5000 }
);
if (result.success) {
this.currentStatus = DRIVER_STATUS.RUNNING;
return { success: true };
} else {
return { success: false, error: result.stderr || '启动失败' };
}
} catch (error) {
return { success: false, error: error.message };
}
}
/**
* 停止服务
*/
async stopService() {
try {
const result = await systemAPI.executeCommand(
`sc stop ${DRIVER_CONFIG.serviceName}`,
{ timeout: 5000 }
);
if (result.success) {
this.currentStatus = DRIVER_STATUS.STOPPED;
return { success: true };
} else {
return { success: false, error: result.stderr || '停止失败' };
}
} catch (error) {
return { success: false, error: error.message };
}
}
/**
* 检查管理员权限
*/
async checkAdminRights() {
try {
const result = await systemAPI.executeCommand(
'net session',
{ timeout: 2000 }
);
return result.success;
} catch {
return false;
}
}
/**
* 获取驱动信息
*/
async getDriverInfo() {
try {
const result = await systemAPI.executeCommand(
`sc qc ${DRIVER_CONFIG.serviceName}`,
{ timeout: 3000 }
);
if (!result.success) {
return null;
}
const output = result.stdout;
const info = {};
// 解析服务配置
const lines = output.split('\n');
for (const line of lines) {
if (line.includes('BINARY_PATH_NAME')) {
info.path = line.split('=')[1]?.trim();
} else if (line.includes('DISPLAY_NAME')) {
info.displayName = line.split('=')[1]?.trim();
} else if (line.includes('START_TYPE')) {
info.startType = line.split('=')[1]?.trim();
}
}
return info;
} catch (error) {
console.error('获取驱动信息失败:', error);
return null;
}
}
/**
* 检查驱动文件是否存在
*/
async checkDriverFileExists() {
try {
const appPath = await ipcRenderer.invoke('get-app-path');
const driverPath = `${appPath}\\resources\\driver\\${DRIVER_CONFIG.driverFile}`;
const result = await systemAPI.executeCommand(
`if exist "${driverPath}" echo exists`,
{ timeout: 2000 }
);
return result.success && result.stdout.includes('exists');
} catch {
return false;
}
}
/**
* 获取驱动版本
*/
async getDriverVersion() {
try {
const appPath = await ipcRenderer.invoke('get-app-path');
const driverPath = `${appPath}\\resources\\driver\\${DRIVER_CONFIG.driverFile}`;
const result = await systemAPI.executeCommand(
`powershell -Command "(Get-Item '${driverPath}').VersionInfo.FileVersion"`,
{ timeout: 3000 }
);
if (result.success) {
return result.stdout.trim();
}
return '未知版本';
} catch {
return '未知版本';
}
}
/**
* 显示驱动状态
*/
async showDriverStatus() {
const status = await this.checkDriverStatus();
const statusMessages = {
[DRIVER_STATUS.NOT_INSTALLED]: '驱动未安装',
[DRIVER_STATUS.INSTALLED]: '驱动已安装但未运行',
[DRIVER_STATUS.RUNNING]: '驱动运行正常',
[DRIVER_STATUS.STOPPED]: '驱动已停止',
[DRIVER_STATUS.ERROR]: '驱动状态异常'
};
ElMessage.info(statusMessages[status] || '未知状态');
return status;
}
}
// 创建单例实例
const driverManager = new DriverManager();
// 导出实例和类
export default driverManager;
export { DriverManager, DRIVER_STATUS };
+58
View File
@@ -0,0 +1,58 @@
/**
* 服务模块统一导出
* 从混淆代码中提取的业务逻辑模块
*/
// 服务模块
export { default as apiManager } from './api-manager';
export { default as driverManager } from './driver-manager';
export { default as messageManager } from './message-manager';
export { default as fileLogger } from './file-logger';
// 从api-manager中导出具体的API
export {
apiClient,
deviceAPI,
fileAPI,
systemAPI,
utils as apiUtils
} from './api-manager';
// 从driver-manager中导出
export { DriverManager, DRIVER_STATUS } from './driver-manager';
// 从message-manager中导出
export {
MessageManager,
MESSAGE_TYPE,
MESSAGE_CONFIG,
success,
error,
warning,
info,
loading,
confirm,
alert,
prompt
} from './message-manager';
// 工具函数
export {
typeUtils,
objectUtils,
stringUtils,
arrayUtils,
dateUtils,
networkUtils,
fileUtils,
validationUtils,
cacheUtils
} from '../utils/tools';
// 重新导出已有的服务
export * from './api';
export * from './auth';
export * from './logger';
export * from './network-manager';
export * from './request';
export * from './websocket-client';
+346
View File
@@ -0,0 +1,346 @@
/**
* 消息管理器
* 处理自定义消息弹窗、通知等功能
* 从混淆代码中提取的业务逻辑
*/
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus';
import { h } from 'vue';
/**
* 消息类型枚举
*/
const MESSAGE_TYPE = {
SUCCESS: 'success',
ERROR: 'error',
WARNING: 'warning',
INFO: 'info'
};
/**
* 消息配置
*/
const MESSAGE_CONFIG = {
duration: 3000,
showClose: true,
center: false,
offset: 20,
grouping: true
};
/**
* 自定义消息管理器
*/
class MessageManager {
constructor() {
this.messageQueue = [];
this.isProcessing = false;
}
/**
* 显示普通消息
*/
showMessage(message, type = MESSAGE_TYPE.INFO, duration = MESSAGE_CONFIG.duration) {
return ElMessage({
message,
type,
duration,
showClose: MESSAGE_CONFIG.showClose,
center: MESSAGE_CONFIG.center,
offset: MESSAGE_CONFIG.offset,
grouping: MESSAGE_CONFIG.grouping
});
}
/**
* 显示成功消息
*/
success(message, duration = MESSAGE_CONFIG.duration) {
return this.showMessage(message, MESSAGE_TYPE.SUCCESS, duration);
}
/**
* 显示错误消息
*/
error(message, duration = MESSAGE_CONFIG.duration) {
return this.showMessage(message, MESSAGE_TYPE.ERROR, duration);
}
/**
* 显示警告消息
*/
warning(message, duration = MESSAGE_CONFIG.duration) {
return this.showMessage(message, MESSAGE_TYPE.WARNING, duration);
}
/**
* 显示信息消息
*/
info(message, duration = MESSAGE_CONFIG.duration) {
return this.showMessage(message, MESSAGE_TYPE.INFO, duration);
}
/**
* 显示加载中消息
*/
loading(message = '加载中...') {
return ElMessage({
message,
type: MESSAGE_TYPE.INFO,
duration: 0,
showClose: false,
icon: 'el-icon-loading'
});
}
/**
* 关闭所有消息
*/
closeAll() {
ElMessage.closeAll();
}
/**
* 显示确认对话框
*/
async confirm(message, title = '确认', options = {}) {
const defaultOptions = {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
...options
};
try {
await ElMessageBox.confirm(message, title, defaultOptions);
return { confirmed: true, cancelled: false };
} catch (action) {
return { confirmed: false, cancelled: true, action };
}
}
/**
* 显示提示对话框
*/
async alert(message, title = '提示', options = {}) {
const defaultOptions = {
confirmButtonText: '确定',
type: 'info',
...options
};
try {
await ElMessageBox.alert(message, title, defaultOptions);
return { confirmed: true };
} catch (action) {
return { confirmed: false, action };
}
}
/**
* 显示输入对话框
*/
async prompt(message, title = '输入', options = {}) {
const defaultOptions = {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: null,
inputErrorMessage: '输入格式不正确',
...options
};
try {
const value = await ElMessageBox.prompt(message, title, defaultOptions);
return { value, confirmed: true, cancelled: false };
} catch (action) {
return { value: null, confirmed: false, cancelled: true, action };
}
}
/**
* 显示通知
*/
notify(options) {
const defaultOptions = {
title: '通知',
message: '',
type: MESSAGE_TYPE.INFO,
duration: 4500,
position: 'top-right',
offset: 0,
...options
};
return ElNotification(defaultOptions);
}
/**
* 显示成功通知
*/
notifySuccess(message, title = '成功') {
return this.notify({ title, message, type: MESSAGE_TYPE.SUCCESS });
}
/**
* 显示错误通知
*/
notifyError(message, title = '错误') {
return this.notify({ title, message, type: MESSAGE_TYPE.ERROR });
}
/**
* 显示警告通知
*/
notifyWarning(message, title = '警告') {
return this.notify({ title, message, type: MESSAGE_TYPE.WARNING });
}
/**
* 显示信息通知
*/
notifyInfo(message, title = '信息') {
return this.notify({ title, message, type: MESSAGE_TYPE.INFO });
}
/**
* 显示自定义内容通知
*/
notifyCustom(content, options = {}) {
return this.notify({
...options,
message: h('div', content)
});
}
/**
* 队列消息处理
*/
async processMessageQueue() {
if (this.isProcessing || this.messageQueue.length === 0) {
return;
}
this.isProcessing = true;
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
await this.showMessage(message.text, message.type, message.duration);
await this.delay(500);
}
this.isProcessing = false;
}
/**
* 添加到消息队列
*/
enqueueMessage(message, type = MESSAGE_TYPE.INFO, duration = MESSAGE_CONFIG.duration) {
this.messageQueue.push({ text: message, type, duration });
this.processMessageQueue();
}
/**
* 延迟函数
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 格式化错误消息
*/
formatError(error) {
if (typeof error === 'string') {
return error;
}
if (error instanceof Error) {
return error.message;
}
if (error && error.message) {
return error.message;
}
return '未知错误';
}
/**
* 显示网络错误
*/
showNetworkError(error) {
const message = this.formatError(error);
this.error(`网络错误: ${message}`);
}
/**
* 显示系统错误
*/
showSystemError(error) {
const message = this.formatError(error);
this.error(`系统错误: ${message}`);
}
/**
* 显示操作成功
*/
showOperationSuccess(operation) {
this.success(`${operation}成功`);
}
/**
* 显示操作失败
*/
showOperationFailed(operation, error) {
const message = this.formatError(error);
this.error(`${operation}失败: ${message}`);
}
/**
* 显示进度消息
*/
showProgress(current, total, message = '') {
const percent = Math.round((current / total) * 100);
const progressMessage = message ? `${message}: ${percent}%` : `进度: ${percent}%`;
return this.showMessage(progressMessage, MESSAGE_TYPE.INFO, 0);
}
/**
* 显示批量操作结果
*/
showBatchResult(success, failed, total) {
const message = `完成: 成功 ${success} 个, 失败 ${failed} 个, 总计 ${total}`;
if (failed === 0) {
this.success(message);
} else if (success === 0) {
this.error(message);
} else {
this.warning(message);
}
}
}
// 创建单例实例
const messageManager = new MessageManager();
// 快捷方法
const { success, error, warning, info, loading, confirm, alert, prompt } = messageManager;
// 导出实例和方法
export default messageManager;
export {
MessageManager,
MESSAGE_TYPE,
MESSAGE_CONFIG,
messageManager,
success,
error,
warning,
info,
loading,
confirm,
alert,
prompt
};
+2
View File
@@ -0,0 +1,2 @@
// 设备状态管理
// 使用Pinia进行状态管理
+451
View File
@@ -0,0 +1,451 @@
// Vuex状态管理
import { createStore } from 'vuex';
import { authAPI, userAPI, deviceAPI } from '../services/api.js';
// src/store/index.js
import { defineStore } from 'pinia';
export const useStore = defineStore('main', { /* 配置 */ });
// 用户模块
const user = {
namespaced: true,
state: () => ({
userInfo: null,
companyList: [],
currentCompany: null,
token: localStorage.getItem('token') || null,
loginInfo: null
}),
mutations: {
SET_USER_INFO(state, userInfo) {
state.userInfo = userInfo;
},
SET_COMPANY_LIST(state, companyList) {
state.companyList = companyList;
},
SET_CURRENT_COMPANY(state, company) {
state.currentCompany = company;
},
SET_TOKEN(state, token) {
state.token = token;
if (token) {
localStorage.setItem('token', token);
} else {
localStorage.removeItem('token');
}
},
SET_LOGIN_INFO(state, loginInfo) {
state.loginInfo = loginInfo;
},
CLEAR_USER_DATA(state) {
state.userInfo = null;
state.companyList = [];
state.currentCompany = null;
state.token = null;
state.loginInfo = null;
localStorage.removeItem('token');
}
},
actions: {
// 获取用户基本信息
async fetchUserInfo({ commit }) {
try {
const response = await userAPI.getUserInfo();
const result = response.data;
if (result.code === 200) {
commit('SET_USER_INFO', result.data);
return { success: true, data: result.data };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取用户信息失败:', error);
return { success: false, message: '获取用户信息失败' };
}
},
// 获取用户公司列表
async fetchCompanyList({ commit }) {
try {
const response = await userAPI.getCompanyList();
const result = response.data;
if (result.code === 200) {
commit('SET_COMPANY_LIST', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取公司列表失败:', error);
return { success: false, message: '获取公司列表失败' };
}
},
// 设置当前公司
setCurrentCompany({ commit }, company) {
commit('SET_CURRENT_COMPANY', company);
},
// 设置token
setToken({ commit }, token) {
commit('SET_TOKEN', token);
},
// 设置登录信息
setLoginInfo({ commit }, loginInfo) {
commit('SET_LOGIN_INFO', loginInfo);
},
// 用户登出
async userLogout({ commit }) {
commit('CLEAR_USER_DATA');
}
},
getters: {
isLoggedIn: state => !!state.token,
userName: state => state.userInfo?.name || '',
companyName: state => state.currentCompany?.name || ''
}
};
// VPN状态模块
const vpn = {
namespaced: true,
state: () => ({
isConnectVPN: false,
vpnStatus: 'disconnected', // disconnected, connecting, connected, error
vpnConfig: null,
connectionHistory: []
}),
mutations: {
SET_VPN_STATUS(state, status) {
state.isConnectVPN = status
state.vpnStatus = status ? 'connected' : 'disconnected'
},
SET_VPN_CONFIG(state, config) {
state.vpnConfig = config
},
ADD_CONNECTION_HISTORY(state, record) {
state.connectionHistory.push({
timestamp: new Date().toISOString(),
...record
})
},
CLEAR_CONNECTION_HISTORY(state) {
state.connectionHistory = []
}
},
actions: {
// 设置VPN连接状态
setVPNStatus({ commit }, status) {
commit('SET_VPN_STATUS', status)
commit('ADD_CONNECTION_HISTORY', {
action: status ? 'connected' : 'disconnected'
})
},
// 设置VPN配置
setVPNConfig({ commit }, config) {
commit('SET_VPN_CONFIG', config)
},
// 清除连接历史
clearConnectionHistory({ commit }) {
commit('CLEAR_CONNECTION_HISTORY')
}
},
getters: {
vpnStatusText: (state) => {
const statusMap = {
disconnected: '未连接',
connecting: '连接中...',
connected: '已连接',
error: '连接失败'
}
return statusMap[state.vpnStatus] || '未知状态'
},
isVPNConnected: (state) => state.isConnectVPN
}
};
// 设备模块
const device = {
namespaced: true,
state: () => ({
deviceList: [],
deviceTypes: [],
currentDevice: null,
deviceLogs: [],
purposeList: [],
deviceTimeList: [],
connectedDevices: new Map(),
deviceStatus: {}
}),
mutations: {
SET_DEVICE_LIST(state, devices) {
state.deviceList = devices;
},
SET_DEVICE_TYPES(state, types) {
state.deviceTypes = types;
},
SET_CURRENT_DEVICE(state, device) {
state.currentDevice = device;
},
SET_DEVICE_LOGS(state, logs) {
state.deviceLogs = logs;
},
SET_PURPOSE_LIST(state, purposes) {
state.purposeList = purposes;
},
SET_DEVICE_TIME_LIST(state, timeList) {
state.deviceTimeList = timeList;
},
SET_DEVICE_STATUS(state, { deviceId, status }) {
state.deviceStatus[deviceId] = status;
},
ADD_CONNECTED_DEVICE(state, { deviceId, connectionInfo }) {
state.connectedDevices.set(deviceId, connectionInfo);
},
REMOVE_CONNECTED_DEVICE(state, deviceId) {
state.connectedDevices.delete(deviceId);
},
UPDATE_DEVICE_STATUS(state, { deviceId, status }) {
const device = state.deviceList.find(d => d.id === deviceId);
if (device) {
device.status = status;
}
}
},
actions: {
// 获取设备列表数据
async fetchDeviceList({ commit }, params = {}) {
try {
const response = await deviceAPI.getDeviceList(params);
const result = response.data;
if (result.code === 200) {
commit('SET_DEVICE_LIST', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取设备列表失败:', error);
return { success: false, message: '获取设备列表失败' };
}
},
// 获取设备类型列表
async fetchDeviceTypes({ commit }, params = {}) {
try {
const response = await deviceAPI.getDeviceTypes(params);
const result = response.data;
if (result.code === 200) {
commit('SET_DEVICE_TYPES', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取设备类型失败:', error);
return { success: false, message: '获取设备类型失败' };
}
},
// 连接指定设备
async connectToDevice({ commit }, params) {
try {
const response = await deviceAPI.connectDevice(params);
const result = response.data;
if (result.code === 200) {
commit('SET_DEVICE_STATUS', {
deviceId: params.device_id,
status: 'connected'
});
commit('ADD_CONNECTED_DEVICE', {
deviceId: params.device_id,
connectionInfo: result.data
});
return { success: true, data: result.data };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('连接设备失败:', error);
return { success: false, message: '连接设备失败' };
}
},
// 获取设备使用日志
async fetchDeviceLogs({ commit }, params) {
try {
const response = await deviceAPI.getDeviceLogs(params);
const result = response.data;
if (result.code === 200) {
commit('SET_DEVICE_LOGS', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取设备日志失败:', error);
return { success: false, message: '获取设备日志失败' };
}
},
// 获取设备使用用途列表
async fetchPurposeList({ commit }, params) {
try {
const response = await deviceAPI.getPurposeList(params);
const result = response.data;
if (result.code === 200) {
commit('SET_PURPOSE_LIST', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取用途列表失败:', error);
return { success: false, message: '获取用途列表失败' };
}
},
// 获取设备使用时间列表
async fetchDeviceTimeList({ commit }, params) {
try {
const response = await deviceAPI.getDeviceTimeList(params);
const result = response.data;
if (result.code === 200) {
commit('SET_DEVICE_TIME_LIST', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取设备使用时间列表失败:', error);
return { success: false, message: '获取设备使用时间列表失败' };
}
},
// 设置当前设备
setCurrentDevice({ commit }, device) {
commit('SET_CURRENT_DEVICE', device);
},
// 更新设备状态
updateDeviceStatus({ commit }, { deviceId, status }) {
commit('UPDATE_DEVICE_STATUS', { deviceId, status });
}
},
getters: {
availableDevices: state => state.deviceList.filter(d => d.status === 'available'),
connectedDeviceCount: state => state.connectedDevices.size,
getDeviceById: state => id => state.deviceList.find(d => d.id === id)
}
};
// 端口模块
const port = {
namespaced: true,
state: () => ({
portList: [],
currentPort: null,
portStatus: {},
isLoading: false
}),
mutations: {
SET_PORT_LIST(state, ports) {
state.portList = ports;
},
SET_CURRENT_PORT(state, port) {
state.currentPort = port;
},
SET_PORT_STATUS(state, { portId, status }) {
state.portStatus[portId] = status;
},
SET_LOADING(state, loading) {
state.isLoading = loading;
}
},
actions: {
// 获取端口列表
async fetchPortList({ commit }, params = {}) {
try {
const response = await deviceAPI.getPortList(params);
const result = response.data;
if (result.code === 200) {
commit('SET_PORT_LIST', result.data || []);
return { success: true, data: result.data || [] };
} else {
return { success: false, message: result.message };
}
} catch (error) {
console.error('获取端口列表失败:', error);
return { success: false, message: '获取端口列表失败' };
}
},
// 设置当前端口
setCurrentPort({ commit }, port) {
commit('SET_CURRENT_PORT', port);
}
},
getters: {
availablePorts: state => state.portList.filter(p => p.status === 'available'),
occupiedPorts: state => state.portList.filter(p => p.status === 'occupied'),
totalPorts: state => state.portList.length
}
};
// 创建store实例
export default createStore({
modules: {
user,
vpn,
device,
port
},
strict: process.env.NODE_ENV !== 'production'
});
+2
View File
@@ -0,0 +1,2 @@
// 端口状态管理
// 使用Pinia进行状态管理,如myRule(用户权限)、totalPort(端口统计)等
+24158
View File
File diff suppressed because it is too large Load Diff
+115
View File
@@ -0,0 +1,115 @@
/**
* 认证工具函数
*/
const TOKEN_KEY = 'token'
const USER_INFO_KEY = 'user_info'
/**
* 获取token
* @returns {string|null}
*/
export function getToken() {
return localStorage.getItem(TOKEN_KEY)
}
/**
* 设置token
* @param {string} token
*/
export function setToken(token) {
localStorage.setItem(TOKEN_KEY, token)
}
/**
* 移除token
*/
export function removeToken() {
localStorage.removeItem(TOKEN_KEY)
}
/**
* 获取用户信息
* @returns {object|null}
*/
export function getUserInfo() {
const userInfo = localStorage.getItem(USER_INFO_KEY)
return userInfo ? JSON.parse(userInfo) : null
}
/**
* 设置用户信息
* @param {object} userInfo
*/
export function setUserInfo(userInfo) {
localStorage.setItem(USER_INFO_KEY, JSON.stringify(userInfo))
}
/**
* 移除用户信息
*/
export function removeUserInfo() {
localStorage.removeItem(USER_INFO_KEY)
}
/**
* 登出
*/
export function logout() {
removeToken()
removeUserInfo()
// 清理其他相关存储
sessionStorage.clear()
// 重定向到登录页
if (window.router) {
window.router.push('/login')
}
}
/**
* 检查是否已登录
* @returns {boolean}
*/
export function isLoggedIn() {
return !!getToken()
}
/**
* 获取权限列表
* @returns {array}
*/
export function getPermissions() {
const userInfo = getUserInfo()
return userInfo?.permissions || []
}
/**
* 检查是否有权限
* @param {string} permission - 权限标识
* @returns {boolean}
*/
export function hasPermission(permission) {
const permissions = getPermissions()
return permissions.includes(permission)
}
/**
* 获取用户角色
* @returns {array}
*/
export function getRoles() {
const userInfo = getUserInfo()
return userInfo?.roles || []
}
/**
* 检查是否有角色
* @param {string} role - 角色标识
* @returns {boolean}
*/
export function hasRole(role) {
const roles = getRoles()
return roles.includes(role)
}
+121
View File
@@ -0,0 +1,121 @@
// 通用常量定义
export const API_BASE_URL = '/dxsz_client_api';
// 设备相关常量
export const DEVICE_TYPES = {
HUB: 'hub',
CA_DEVICE: 'ca_device'
};
// 连接状态
export const CONNECTION_STATUS = {
CONNECTED: 'connected',
DISCONNECTED: 'disconnected',
CONNECTING: 'connecting'
};
// 错误码
export const ERROR_CODES = {
NETWORK_ERROR: 500,
AUTH_ERROR: 401,
NOT_FOUND: 404,
BUSINESS_ERROR: 402
};
// 时间相关
export const TIMEOUT_SECONDS = 300;
export const RECONNECT_INTERVAL = 2000;
// 系统检测项目
export const SYSTEM_CHECK_ITEMS = {
HOST_NAME: 'host_name',
NETWORK_STATE: 'network_state',
FIREWALL_STATUS: 'firewall_status',
FILES_EXIST: 'files_exist',
CERTIFICATE_EXIST: 'certificate_exist',
TEST_PATTERN: 'test_pattern',
DRIVE_EXIST: 'drive_exist',
OWNERSHIP: 'ownership',
WEB_SERVE: 'web_serve',
SOCKET_SERVE: 'socket_serve',
PROGRAM_INTEGRITY: 'program_integrity'
};
// 文件路径
export const FILE_PATHS = {
CORE_PROGRAM: 'release/1.0.0/win-unpacked/云企通安全云锁客户端.exe',
DRIVER_FILE: 'C:/Windows/System32/drivers/usbip_vhci_ude.sys',
CERTIFICATE: 'USBIP Test'
};
// API端点
export const API_ENDPOINTS = {
AUTH: {
LOGIN: '/auth/login',
PHONE_LOGIN: '/auth/phone_login',
COMPANY_LOGIN: '/auth/company_login',
SET_LOGIN_CACHE: '/auth/set_login_cache',
LOGOUT: '/auth/logout'
},
USER: {
INFO: '/user/info',
COMPANY_LIST: '/user/company_list',
GET_USER_ID: '/user/get_user_id_by_login',
CHANGE_PASSWORD: '/user/change_password',
UPDATE: '/user',
MY_RULE: '/user/my_rule',
GET_USER_CONNECT_DATA: '/user/get_user_connect_data'
},
COMPANY: {
INFO: '/company_user/info',
SWITCH: '/auth/company_login',
LIST: '/company/list',
INFO_DETAIL: '/company/info'
},
DEVICE: {
LIST: '/ca_device_server/get_all_hub_device_list_by_group_v2',
TYPES: '/ca_relation_device/type_list',
CONNECT: '/ca_use_approve/connect_ca_relation_device_list_v2',
LOGS: '/ca_use_logs/get_user_use_device_log_list_v2',
APPROVE: '/ca_use_approve/add',
REVOKE: '/ca_use_approve/revoke',
PURPOSE_LIST: '/ca_use_approve/purpose_list',
TIME_LIST: '/ca_use_approve/device_use_time_list',
PORT_LIST: '/ca_device_server/get_port_list',
PORT_STATUS: '/ca_device_server/get_port_status',
// CA关系设备相关
CA_CONNECT: '/ca_relation_device/connect',
CA_DISCONNECT: '/ca_relation_device/disconnect',
CA_CHANGE: '/ca_relation_device/change',
CA_LIST: '/ca_relation_device/list',
CA_INFO: '/ca_relation_device/info',
CA_ADD: '/ca_relation_device/add',
CA_UPDATE: '/ca_relation_device/update',
CA_DELETE: '/ca_relation_device/delete',
CA_FORCE_DISCONNECT: '/ca_relation_device/force_disconnect',
CA_REQUEST_CONNECT: '/ca_relation_device/request_connect',
CA_GET_ALL_HUB_DEVICE_LIST_BY_GROUP: '/ca_relation_device/get_all_hub_device_list_by_group_v2',
CA_GET_DEVICE_TIME_LIST: '/ca_relation_device/get_device_time_list',
CA_CONNECT_DEVICE_LIST: '/ca_relation_device/connect_ca_relation_device_list_v2',
CA_GET_RELATION_DEVICE_LIST_BY_GROUP: '/ca_relation_device/get_relation_device_list_by_group'
},
COMMON: {
VERIFICATION_CODE: '/common/verification_code',
WECHAT_QRCODE: '/wechat_mini_program/create_qrcode',
CHECK_SCAN: '/wechat_mini_program/check_scan_status'
},
AUTHORIZATION_CENTER: {
USER_LIST: '/authorization_center/authorization_center_user_list',
USER_DELETE: '/authorization_center/authorization_center_user_delete',
USER_ADD: '/authorization_center/authorization_center_user_add',
USER_INFO: '/authorization_center/authorization_center_user_info',
USER_EDIT: '/authorization_center/authorization_center_user_edit',
IMPORT: '/authorization_center/import'
},
SYSTEM: {
GET_VERSION: '/get_version',
VERSION_REPORT: '/version_report',
CHECK_UPDATE: '/check_update',
DOWNLOAD_UPDATE: '/download'
}
};
+74
View File
@@ -0,0 +1,74 @@
/**
* 凭证管理工具
* 用于在Electron环境下保存、获取和删除用户凭证
*/
const { ipcRenderer } = window.require ? window.require('electron') : { ipcRenderer: null }
/**
* 保存用户凭证
* @param {string} username - 用户名
* @param {string} password - 密码
* @returns {Promise<void>}
*/
export async function saveCredentials(username, password) {
if (!ipcRenderer) {
console.warn('Electron环境未找到,使用localStorage替代')
localStorage.setItem('credentials', JSON.stringify({ username, password }))
return
}
try {
await ipcRenderer.invoke('save-credentials', { username, password })
} catch (error) {
console.error('保存凭证失败:', error)
throw error
}
}
/**
* 获取用户凭证
* @returns {Promise<{username: string, password: string}|null>}
*/
export async function getCredentials() {
if (!ipcRenderer) {
console.warn('Electron环境未找到,使用localStorage替代')
const credentials = localStorage.getItem('credentials')
return credentials ? JSON.parse(credentials) : null
}
try {
return await ipcRenderer.invoke('get-credentials')
} catch (error) {
console.error('获取凭证失败:', error)
return null
}
}
/**
* 删除用户凭证
* @returns {Promise<void>}
*/
export async function clearCredentials() {
if (!ipcRenderer) {
console.warn('Electron环境未找到,使用localStorage替代')
localStorage.removeItem('credentials')
return
}
try {
await ipcRenderer.invoke('delete-credentials')
} catch (error) {
console.error('删除凭证失败:', error)
throw error
}
}
/**
* 检查是否有保存的凭证
* @returns {Promise<boolean>}
*/
export async function hasCredentials() {
const credentials = await getCredentials()
return credentials !== null && credentials.username && credentials.password
}
+410
View File
@@ -0,0 +1,410 @@
/**
* 设备连接管理工具类
* 处理设备连接、状态监控、超时处理、重连机制等
*/
import { store } from '../store/index.js';
import networkManager from './network-manager.js';
import wsClient from './websocket-client.js';
class DeviceConnectionManager {
constructor() {
this.activeConnections = new Map();
this.connectionTimeouts = new Map();
this.heartbeatIntervals = new Map();
this.reconnectAttempts = new Map();
this.maxReconnectAttempts = 3;
this.defaultTimeout = 300000; // 5分钟
this.heartbeatInterval = 30000; // 30秒
}
/**
* 连接设备
*/
async connectDevice(deviceId, options = {}) {
const {
timeout = this.defaultTimeout,
autoReconnect = true,
showNotification = true
} = options;
try {
// 检查网络连接
const networkStatus = await networkManager.checkNetworkConnection();
if (networkStatus.status !== 'connected') {
throw new Error('网络连接不可用');
}
// 检查设备是否已连接
if (this.activeConnections.has(deviceId)) {
console.log(`设备 ${deviceId} 已连接`);
return this.activeConnections.get(deviceId);
}
// 更新Vuex状态
store.commit('device/UPDATE_DEVICE_STATUS', {
deviceId,
status: 'connecting'
});
// 调用API连接设备
const response = await store.dispatch('device/connectToDevice', {
deviceId,
timeout
});
if (response.success) {
// 建立连接
this.activeConnections.set(deviceId, {
deviceId,
connectedAt: new Date(),
timeout,
autoReconnect,
...response.data
});
// 设置超时处理
this.setupConnectionTimeout(deviceId, timeout);
// 启动心跳检测
this.startHeartbeat(deviceId);
// 订阅WebSocket消息
this.subscribeToDeviceUpdates(deviceId);
// 更新Vuex状态
store.commit('device/ADD_CONNECTED_DEVICE', {
deviceId,
status: 'connected',
connectedAt: new Date()
});
if (showNotification) {
this.showNotification('设备连接成功', `设备 ${deviceId} 已连接`);
}
return {
success: true,
deviceId,
connection: this.activeConnections.get(deviceId)
};
} else {
throw new Error(response.message || '连接失败');
}
} catch (error) {
console.error('连接设备失败:', error);
// 更新Vuex状态
store.commit('device/UPDATE_DEVICE_STATUS', {
deviceId,
status: 'disconnected',
error: error.message
});
if (autoReconnect && this.reconnectAttempts.get(deviceId) < this.maxReconnectAttempts) {
this.scheduleReconnect(deviceId, options);
}
throw error;
}
}
/**
* 断开设备连接
*/
async disconnectDevice(deviceId, reason = '用户主动断开') {
try {
// 清除超时定时器
this.clearConnectionTimeout(deviceId);
// 清除心跳检测
this.stopHeartbeat(deviceId);
// 取消WebSocket订阅
this.unsubscribeFromDeviceUpdates(deviceId);
// 调用API断开连接
await store.dispatch('device/disconnectDevice', deviceId);
// 移除连接记录
this.activeConnections.delete(deviceId);
this.reconnectAttempts.delete(deviceId);
// 更新Vuex状态
store.commit('device/UPDATE_DEVICE_STATUS', {
deviceId,
status: 'disconnected',
disconnectedAt: new Date(),
reason
});
this.showNotification('设备已断开', `设备 ${deviceId} 已断开连接`);
return {
success: true,
deviceId,
reason
};
} catch (error) {
console.error('断开设备连接失败:', error);
throw error;
}
}
/**
* 设置连接超时
*/
setupConnectionTimeout(deviceId, timeout) {
this.clearConnectionTimeout(deviceId);
const timeoutId = setTimeout(() => {
this.handleConnectionTimeout(deviceId);
}, timeout);
this.connectionTimeouts.set(deviceId, timeoutId);
}
/**
* 清除连接超时
*/
clearConnectionTimeout(deviceId) {
const timeoutId = this.connectionTimeouts.get(deviceId);
if (timeoutId) {
clearTimeout(timeoutId);
this.connectionTimeouts.delete(deviceId);
}
}
/**
* 处理连接超时
*/
async handleConnectionTimeout(deviceId) {
console.warn(`设备 ${deviceId} 连接超时`);
// 更新Vuex状态
store.commit('device/UPDATE_DEVICE_STATUS', {
deviceId,
status: 'timeout',
timeoutAt: new Date()
});
// 显示超时弹窗
this.showTimeoutModal(deviceId);
// 自动重连
const connection = this.activeConnections.get(deviceId);
if (connection && connection.autoReconnect) {
this.scheduleReconnect(deviceId);
}
}
/**
* 启动心跳检测
*/
startHeartbeat(deviceId) {
this.stopHeartbeat(deviceId);
const intervalId = setInterval(async () => {
try {
const isAlive = await this.checkDeviceHeartbeat(deviceId);
if (!isAlive) {
console.warn(`设备 ${deviceId} 心跳检测失败`);
this.handleConnectionLost(deviceId);
}
} catch (error) {
console.error('心跳检测错误:', error);
}
}, this.heartbeatInterval);
this.heartbeatIntervals.set(deviceId, intervalId);
}
/**
* 停止心跳检测
*/
stopHeartbeat(deviceId) {
const intervalId = this.heartbeatIntervals.get(deviceId);
if (intervalId) {
clearInterval(intervalId);
this.heartbeatIntervals.delete(deviceId);
}
}
/**
* 检查设备心跳
*/
async checkDeviceHeartbeat(deviceId) {
try {
const response = await store.dispatch('device/checkDeviceStatus', deviceId);
return response.success && response.data.status === 'connected';
} catch (error) {
return false;
}
}
/**
* 处理连接丢失
*/
async handleConnectionLost(deviceId) {
console.error(`设备 ${deviceId} 连接丢失`);
// 更新状态
store.commit('device/UPDATE_DEVICE_STATUS', {
deviceId,
status: 'lost',
lostAt: new Date()
});
// 尝试重连
const connection = this.activeConnections.get(deviceId);
if (connection && connection.autoReconnect) {
this.scheduleReconnect(deviceId);
}
}
/**
* 计划重连
*/
scheduleReconnect(deviceId, options = {}) {
const attempts = this.reconnectAttempts.get(deviceId) || 0;
if (attempts >= this.maxReconnectAttempts) {
console.warn(`设备 ${deviceId} 重连次数已达上限`);
return;
}
this.reconnectAttempts.set(deviceId, attempts + 1);
const delay = Math.pow(2, attempts) * 1000; // 指数退避
setTimeout(async () => {
try {
console.log(`尝试重连设备 ${deviceId} (第${attempts + 1}次)`);
await this.connectDevice(deviceId, { ...options, autoReconnect: true });
this.reconnectAttempts.delete(deviceId); // 重置重连计数
} catch (error) {
console.error('重连失败:', error);
}
}, delay);
}
/**
* 订阅设备更新
*/
subscribeToDeviceUpdates(deviceId) {
wsClient.on('deviceStatus', (data) => {
if (data.deviceId === deviceId) {
this.handleDeviceStatusUpdate(deviceId, data);
}
});
}
/**
* 取消订阅设备更新
*/
unsubscribeFromDeviceUpdates(deviceId) {
// WebSocket客户端会自动处理事件清理
}
/**
* 处理设备状态更新
*/
handleDeviceStatusUpdate(deviceId, data) {
store.commit('device/UPDATE_DEVICE_STATUS', {
deviceId,
...data
});
}
/**
* 显示通知
*/
showNotification(title, message) {
if (window.electronAPI && window.electronAPI.notification) {
window.electronAPI.notification.show({ title, message });
}
}
/**
* 显示超时弹窗
*/
showTimeoutModal(deviceId) {
store.commit('app/SHOW_TIMEOUT_MODAL', {
deviceId,
visible: true
});
}
/**
* 获取所有活跃连接
*/
getActiveConnections() {
return Array.from(this.activeConnections.values());
}
/**
* 获取设备连接状态
*/
getDeviceConnectionStatus(deviceId) {
const connection = this.activeConnections.get(deviceId);
const vuexDevice = store.getters['device/getDeviceById'](deviceId);
return {
deviceId,
isConnected: !!connection,
connection,
vuexStatus: vuexDevice?.status || 'unknown',
lastHeartbeat: connection?.lastHeartbeat || null
};
}
/**
* 批量检查设备连接状态
*/
async checkDevicesStatus(deviceIds) {
const results = [];
for (const deviceId of deviceIds) {
try {
const status = await this.getDeviceConnectionStatus(deviceId);
results.push(status);
} catch (error) {
results.push({
deviceId,
error: error.message
});
}
}
return results;
}
/**
* 断开所有设备连接
*/
async disconnectAllDevices(reason = '应用关闭') {
const deviceIds = Array.from(this.activeConnections.keys());
const results = [];
for (const deviceId of deviceIds) {
try {
const result = await this.disconnectDevice(deviceId, reason);
results.push(result);
} catch (error) {
results.push({
deviceId,
success: false,
error: error.message
});
}
}
return results;
}
}
// 创建单例实例
const deviceConnectionManager = new DeviceConnectionManager();
export default deviceConnectionManager;
export { DeviceConnectionManager };
+252
View File
@@ -0,0 +1,252 @@
/**
* 设备管理工具函数
* 包含设备状态判断、时间格式化、类型映射等功能
*/
// 设备类型映射
export const DEVICE_TYPES = {
1: { name: '手机', icon: 'el-icon-mobile-phone', color: '#409EFF' },
2: { name: '平板', icon: 'el-icon-monitor', color: '#67C23A' },
3: { name: '路由器', icon: 'el-icon-connection', color: '#E6A23C' },
4: { name: '摄像头', icon: 'el-icon-video-camera', color: '#F56C6C' },
5: { name: '其他', icon: 'el-icon-cpu', color: '#909399' }
}
// 使用方式映射
export const USE_MODES = {
1: { name: '共享', type: 'success', description: '所有用户可连接' },
2: { name: '审批', type: 'warning', description: '需要管理员审批' }
}
// 设备状态映射
export const DEVICE_STATUS = {
IDLE: { name: '空闲', type: 'success', color: '#67C23A' },
OCCUPIED: { name: '占用中', type: 'danger', color: '#F56C6C' },
PENDING: { name: '待审批', type: 'warning', color: '#E6A23C' },
CONNECTING: { name: '连接中', type: 'info', color: '#909399' }
}
/**
* 获取设备类型信息
* @param {number} type 设备类型ID
* @returns {Object} 设备类型信息
*/
export function getDeviceTypeInfo(type) {
return DEVICE_TYPES[type] || DEVICE_TYPES[5]
}
/**
* 获取使用方式信息
* @param {number} mode 使用方式ID
* @returns {Object} 使用方式信息
*/
export function getUseModeInfo(mode) {
return USE_MODES[mode] || USE_MODES[1]
}
/**
* 获取设备状态信息
* @param {Object} device 设备对象
* @returns {Object} 设备状态信息
*/
export function getDeviceStatusInfo(device) {
if (device.connect_user_id) {
return DEVICE_STATUS.OCCUPIED
}
return DEVICE_STATUS.IDLE
}
/**
* 格式化连接时长
* @param {number} startTime 开始时间戳
* @returns {string} 格式化后的时长
*/
export function formatDuration(startTime) {
if (!startTime) return '-'
const now = Date.now()
const duration = Math.floor((now - startTime) / 1000)
if (duration < 60) return `${duration}`
if (duration < 3600) return `${Math.floor(duration / 60)}分钟`
if (duration < 86400) return `${Math.floor(duration / 3600)}小时`
return `${Math.floor(duration / 86400)}`
}
/**
* 格式化日期时间
* @param {number} timestamp 时间戳
* @returns {string} 格式化后的日期时间
*/
export function formatDateTime(timestamp) {
if (!timestamp) return '-'
const date = new Date(timestamp)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
/**
* 判断用户是否有权限操作设备
* @param {Object} device 设备对象
* @param {number} userId 当前用户ID
* @param {boolean} isAdmin 是否为管理员
* @returns {boolean} 是否有权限
*/
export function hasDevicePermission(device, userId, isAdmin = false) {
if (!device || !userId) return false
// 管理员有全部权限
if (isAdmin) return true
// 设备空闲时所有用户可连接
if (!device.connect_user_id) return true
// 设备占用时只有当前连接用户可断开
return device.connect_user_id === userId
}
/**
* 获取设备状态颜色
* @param {Object} device 设备对象
* @returns {string} 状态颜色
*/
export function getDeviceStatusColor(device) {
const status = getDeviceStatusInfo(device)
return status.color
}
/**
* 获取设备状态文本
* @param {Object} device 设备对象
* @returns {string} 状态文本
*/
export function getDeviceStatusText(device) {
const status = getDeviceStatusInfo(device)
return status.name
}
/**
* 获取设备状态类型
* @param {Object} device 设备对象
* @returns {string} 状态类型
*/
export function getDeviceStatusType(device) {
const status = getDeviceStatusInfo(device)
return status.type
}
/**
* 生成设备唯一标识
* @param {Object} device 设备对象
* @returns {string} 设备标识
*/
export function generateDeviceKey(device) {
return `device_${device.id}_${device.port_name}`
}
/**
* 验证设备表单数据
* @param {Object} form 表单数据
* @returns {Object} 验证结果
*/
export function validateDeviceForm(form) {
const errors = []
if (!form.name || form.name.trim().length < 2) {
errors.push('设备名称不能为空且至少2个字符')
}
if (!form.port_name || form.port_name.trim().length < 1) {
errors.push('端口名称不能为空')
}
if (!form.type || !DEVICE_TYPES[form.type]) {
errors.push('请选择有效的设备类型')
}
if (![1, 2].includes(form.use_mode)) {
errors.push('请选择有效的使用方式')
}
return {
valid: errors.length === 0,
errors
}
}
/**
* 创建设备筛选条件
* @param {Object} filters 筛选条件
* @returns {Object} 格式化后的筛选条件
*/
export function createDeviceFilters(filters) {
const result = {}
if (filters.name) result.name = filters.name.trim()
if (filters.port_name) result.port_name = filters.port_name.trim()
if (filters.type) result.type = filters.type
if (filters.use_mode) result.use_mode = filters.use_mode
if (filters.status !== undefined) result.status = filters.status
return result
}
/**
* 计算设备使用统计
* @param {Array} devices 设备列表
* @returns {Object} 统计信息
*/
export function calculateDeviceStats(devices) {
if (!Array.isArray(devices)) return { total: 0, idle: 0, occupied: 0 }
const total = devices.length
const idle = devices.filter(d => !d.connect_user_id).length
const occupied = total - idle
return {
total,
idle,
occupied,
idleRate: total > 0 ? (idle / total * 100).toFixed(1) : 0,
occupiedRate: total > 0 ? (occupied / total * 100).toFixed(1) : 0
}
}
/**
* 设备排序函数
* @param {Array} devices 设备列表
* @param {string} sortBy 排序字段
* @param {string} order 排序顺序(asc/desc
* @returns {Array} 排序后的设备列表
*/
export function sortDevices(devices, sortBy = 'name', order = 'asc') {
if (!Array.isArray(devices)) return []
const sorted = [...devices].sort((a, b) => {
let valueA = a[sortBy]
let valueB = b[sortBy]
if (sortBy === 'connect_time') {
valueA = valueA || 0
valueB = valueB || 0
}
if (typeof valueA === 'string') {
valueA = valueA.toLowerCase()
valueB = valueB.toLowerCase()
}
if (valueA < valueB) return order === 'asc' ? -1 : 1
if (valueA > valueB) return order === 'asc' ? 1 : -1
return 0
})
return sorted
}
+249
View File
@@ -0,0 +1,249 @@
/**
* 文件日志管理工具类
* 处理基于文件系统的日志管理,包括日志文件创建、清理、写入等功能
* 从混淆代码中提取的业务逻辑
*/
import path from 'path';
import fs from 'fs';
import dayjs from 'dayjs';
class FileLogger {
constructor() {
this.logsDir = path.join(process.cwd(), 'logs');
this.logQueue = [];
this.maxLogAge = 7; // 7天
this.isInitialized = false;
}
/**
* 初始化日志目录
*/
async init() {
try {
if (!fs.existsSync(this.logsDir)) {
await fs.promises.mkdir(this.logsDir, { recursive: true });
}
this.isInitialized = true;
} catch (error) {
console.error('初始化日志目录失败:', error);
throw error;
}
}
/**
* 清理7天前的日志文件
*/
async cleanOldLogs() {
try {
await this.init();
const files = await fs.promises.readdir(this.logsDir);
const cutoffDate = dayjs().subtract(this.maxLogAge, 'day');
for (const file of files) {
const filePath = path.join(this.logsDir, file);
const stats = await fs.promises.stat(filePath);
if (dayjs(stats.ctime).isBefore(cutoffDate)) {
await fs.promises.unlink(filePath);
console.log(`已删除旧日志文件: ${file}`);
}
}
} catch (error) {
console.error('清理旧日志失败:', error);
}
}
/**
* 写入日志到文件
*/
async writeToFile() {
if (this.logQueue.length === 0) return;
try {
await this.init();
await this.cleanOldLogs();
const today = dayjs().format('YYYY-MM-DD');
const logFile = path.join(this.logsDir, `${today}.log`);
// 读取现有内容
let existingContent = '';
try {
existingContent = await fs.promises.readFile(logFile, 'utf-8');
} catch (error) {
// 文件不存在,创建新文件
}
// 格式化日志内容
const logContent = this.logQueue.map(log =>
`[${log.timestamp}] [${log.level.toUpperCase()}] ${log.message}`
).join('\n');
// 写入文件
await fs.promises.writeFile(logFile, `${existingContent}\n${logContent}`.trim());
// 清空队列
this.logQueue.length = 0;
} catch (error) {
console.error('写入日志文件失败:', error);
}
}
/**
* 记录日志
*/
async log(message, level = 'debug') {
const logEntry = {
timestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'),
level,
message: typeof message === 'object' ? JSON.stringify(message) : String(message)
};
this.logQueue.push(logEntry);
// 立即写入文件
await this.writeToFile();
}
/**
* 记录错误日志
*/
async error(message, error = null) {
const errorMessage = error ? `${message} - ${error.message}\n${error.stack}` : message;
await this.log(errorMessage, 'error');
}
/**
* 记录警告日志
*/
async warn(message) {
await this.log(message, 'warn');
}
/**
* 记录信息日志
*/
async info(message) {
await this.log(message, 'info');
}
/**
* 记录调试日志
*/
async debug(message) {
await this.log(message, 'debug');
}
/**
* 获取指定日期的日志
*/
async getLogs(date = null) {
try {
await this.init();
const targetDate = date ? dayjs(date).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD');
const logFile = path.join(this.logsDir, `${targetDate}.log`);
if (!fs.existsSync(logFile)) {
return [];
}
const content = await fs.promises.readFile(logFile, 'utf-8');
return content.split('\n').filter(line => line.trim());
} catch (error) {
console.error('读取日志文件失败:', error);
return [];
}
}
/**
* 获取所有日志文件列表
*/
async getLogFiles() {
try {
await this.init();
const files = await fs.promises.readdir(this.logsDir);
return files
.filter(file => file.endsWith('.log'))
.map(file => ({
name: file,
path: path.join(this.logsDir, file),
date: file.replace('.log', ''),
size: fs.statSync(path.join(this.logsDir, file)).size
}))
.sort((a, b) => b.date.localeCompare(a.date));
} catch (error) {
console.error('获取日志文件列表失败:', error);
return [];
}
}
/**
* 导出日志文件
*/
async exportLogs(startDate = null, endDate = null) {
try {
const files = await this.getLogFiles();
let allLogs = [];
for (const file of files) {
const fileDate = dayjs(file.date);
if (startDate && fileDate.isBefore(dayjs(startDate))) continue;
if (endDate && fileDate.isAfter(dayjs(endDate))) continue;
const logs = await this.getLogs(file.date);
allLogs = allLogs.concat(logs);
}
return allLogs;
} catch (error) {
console.error('导出日志失败:', error);
return [];
}
}
/**
* 清空所有日志
*/
async clearAllLogs() {
try {
await this.init();
const files = await fs.promises.readdir(this.logsDir);
for (const file of files) {
if (file.endsWith('.log')) {
await fs.promises.unlink(path.join(this.logsDir, file));
}
}
console.log('已清空所有日志文件');
} catch (error) {
console.error('清空日志失败:', error);
}
}
}
// 创建单例实例
const fileLogger = new FileLogger();
// 快捷方法
const fileLog = {
error: fileLogger.error.bind(fileLogger),
warn: fileLogger.warn.bind(fileLogger),
info: fileLogger.info.bind(fileLogger),
debug: fileLogger.debug.bind(fileLogger),
clean: fileLogger.cleanOldLogs.bind(fileLogger),
export: fileLogger.exportLogs.bind(fileLogger),
clear: fileLogger.clearAllLogs.bind(fileLogger)
};
export default fileLogger;
export { FileLogger, fileLog };
+184
View File
@@ -0,0 +1,184 @@
/**
* 工具类导出文件
* 统一导出所有工具类,方便在项目中使用
*/
// 网络管理
export { default as networkManager } from './network-manager.js';
export { NetworkManager } from './network-manager.js';
// WebSocket客户端
export { default as wsClient } from './websocket-client.js';
export { WebSocketClient } from './websocket-client.js';
// 设备连接管理
export { default as deviceConnectionManager } from './device-connection-manager.js';
export { DeviceConnectionManager } from './device-connection-manager.js';
// 通知管理
export { default as notificationManager } from './notification-manager.js';
export { NotificationManager } from './notification-manager.js';
// 日志管理
export { default as logger, log } from './logger.js';
export { Logger } from './logger.js';
// 系统检测
export {
checkNetworkConnectionState,
checkSystemHostName,
checkSystemFirewallStatus,
checkCoreProgramFiles,
checkSystemCertificate,
checkSystemTestMode,
checkSystemDriver,
checkSystemAdminRights,
checkPortService,
performSystemCheck
} from './system-check.js';
// 重新导出现有的工具
export { default as request } from './request.js';
export { validateForm } from './validation.js';
export { encrypt, decrypt } from './crypto.js';
export { getDeviceMacAddress } from './device.js';
// 工具函数集合
export const utils = {
// 延迟函数
delay: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
// 防抖函数
debounce: (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
// 节流函数
throttle: (func, limit) => {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
},
// 深拷贝
deepClone: (obj) => {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj.getTime());
if (obj instanceof Array) return obj.map(item => utils.deepClone(item));
if (typeof obj === 'object') {
const clonedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = utils.deepClone(obj[key]);
}
}
return clonedObj;
}
},
// 生成唯一ID
generateId: (prefix = '') => {
const timestamp = Date.now().toString(36);
const random = Math.random().toString(36).substr(2, 9);
return prefix ? `${prefix}_${timestamp}_${random}` : `${timestamp}_${random}`;
},
// 格式化文件大小
formatFileSize: (bytes) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
},
// 格式化时间
formatDuration: (ms) => {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}小时${minutes % 60}分钟`;
} else if (minutes > 0) {
return `${minutes}分钟${seconds % 60}`;
} else {
return `${seconds}`;
}
},
// 判断是否为移动设备
isMobile: () => {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
},
// 判断是否为Electron环境
isElectron: () => {
return typeof window !== 'undefined' &&
window.process &&
window.process.type === 'renderer';
},
// 安全地解析JSON
safeParseJSON: (str, defaultValue = {}) => {
try {
return JSON.parse(str);
} catch {
return defaultValue;
}
},
// 获取URL参数
getQueryParam: (name) => {
const url = new URL(window.location.href);
return url.searchParams.get(name);
},
// 设置URL参数
setQueryParam: (name, value) => {
const url = new URL(window.location.href);
url.searchParams.set(name, value);
window.history.replaceState({}, '', url);
},
// 下载文件
downloadFile: (content, filename, type = 'text/plain') => {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
};
// 默认导出所有工具
export default {
networkManager,
wsClient,
deviceConnectionManager,
notificationManager,
logger,
log,
...utils
};
+349
View File
@@ -0,0 +1,349 @@
/**
* 日志管理工具类
* 处理应用日志记录、错误追踪、调试信息等功能
*/
class Logger {
constructor() {
this.levels = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3
};
this.currentLevel = this.levels.INFO;
this.logs = [];
this.maxLogs = 1000;
this.isDevelopment = process.env.NODE_ENV === 'development';
// 初始化日志系统
this.init();
}
/**
* 初始化日志系统
*/
init() {
// 监听全局错误
if (typeof window !== 'undefined') {
window.addEventListener('error', (event) => {
this.error('Global Error', event.error);
});
window.addEventListener('unhandledrejection', (event) => {
this.error('Unhandled Promise Rejection', event.reason);
});
}
// 设置日志级别
this.setLogLevel(
this.isDevelopment ? this.levels.DEBUG : this.levels.INFO
);
}
/**
* 设置日志级别
*/
setLogLevel(level) {
if (typeof level === 'string') {
const levelMap = {
'error': this.levels.ERROR,
'warn': this.levels.WARN,
'info': this.levels.INFO,
'debug': this.levels.DEBUG
};
this.currentLevel = levelMap[level.toLowerCase()] || this.levels.INFO;
} else {
this.currentLevel = level;
}
}
/**
* 记录错误日志
*/
error(message, error = null, context = {}) {
if (this.currentLevel >= this.levels.ERROR) {
const logEntry = this.createLogEntry('ERROR', message, error, context);
this.addLog(logEntry);
this.outputLog('error', logEntry);
}
}
/**
* 记录警告日志
*/
warn(message, data = null, context = {}) {
if (this.currentLevel >= this.levels.WARN) {
const logEntry = this.createLogEntry('WARN', message, data, context);
this.addLog(logEntry);
this.outputLog('warn', logEntry);
}
}
/**
* 记录信息日志
*/
info(message, data = null, context = {}) {
if (this.currentLevel >= this.levels.INFO) {
const logEntry = this.createLogEntry('INFO', message, data, context);
this.addLog(logEntry);
this.outputLog('info', logEntry);
}
}
/**
* 记录调试日志
*/
debug(message, data = null, context = {}) {
if (this.currentLevel >= this.levels.DEBUG) {
const logEntry = this.createLogEntry('DEBUG', message, data, context);
this.addLog(logEntry);
this.outputLog('debug', logEntry);
}
}
/**
* 创建日志条目
*/
createLogEntry(level, message, data, context) {
return {
level,
message,
data: this.serializeData(data),
context: this.serializeData(context),
timestamp: new Date().toISOString(),
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Node.js',
url: typeof window !== 'undefined' ? window.location.href : '',
stack: this.getStackTrace()
};
}
/**
* 添加日志到内存队列
*/
addLog(logEntry) {
this.logs.push(logEntry);
// 限制内存中的日志数量
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
}
/**
* 输出日志到控制台
*/
outputLog(method, logEntry) {
const formattedMessage = this.formatLogMessage(logEntry);
if (typeof console !== 'undefined' && console[method]) {
console[method](formattedMessage, logEntry.data || '');
}
// 发送到远程日志服务(生产环境)
if (!this.isDevelopment) {
this.sendToRemote(logEntry);
}
}
/**
* 格式化日志消息
*/
formatLogMessage(logEntry) {
const { level, timestamp, message, context } = logEntry;
const time = new Date(timestamp).toLocaleTimeString();
const contextStr = Object.keys(context).length > 0 ? ` [${JSON.stringify(context)}]` : '';
return `[${time}] ${level}: ${message}${contextStr}`;
}
/**
* 序列化数据
*/
serializeData(data) {
if (data === null || data === undefined) {
return null;
}
if (typeof data === 'string') {
return data;
}
if (data instanceof Error) {
return {
name: data.name,
message: data.message,
stack: data.stack
};
}
try {
return JSON.parse(JSON.stringify(data));
} catch (error) {
return String(data);
}
}
/**
* 获取堆栈跟踪
*/
getStackTrace() {
try {
const error = new Error();
const stack = error.stack || '';
return stack.split('\n').slice(3).join('\n'); // 移除Logger自身的堆栈
} catch (error) {
return '';
}
}
/**
* 发送到远程日志服务
*/
async sendToRemote(logEntry) {
try {
// 这里可以集成远程日志服务,如Sentry、LogRocket等
// 暂时只发送到Electron主进程
if (window.electronAPI && window.electronAPI.log) {
await window.electronAPI.log.send(logEntry);
}
} catch (error) {
// 远程日志发送失败时不影响应用运行
console.error('发送日志到远程服务失败:', error);
}
}
/**
* 获取日志列表
*/
getLogs(level = null, limit = 100) {
let filteredLogs = this.logs;
if (level) {
const levelValue = typeof level === 'string' ?
this.levels[level.toUpperCase()] : level;
filteredLogs = this.logs.filter(log =>
this.levels[log.level] >= levelValue
);
}
return filteredLogs.slice(-limit);
}
/**
* 清除日志
*/
clearLogs() {
this.logs = [];
}
/**
* 导出日志
*/
exportLogs(format = 'json') {
const logs = this.getLogs();
switch (format) {
case 'json':
return JSON.stringify(logs, null, 2);
case 'csv':
return this.convertToCSV(logs);
case 'text':
return logs.map(log => this.formatLogMessage(log)).join('\n');
default:
return JSON.stringify(logs);
}
}
/**
* 转换为CSV格式
*/
convertToCSV(logs) {
if (logs.length === 0) return '';
const headers = ['timestamp', 'level', 'message', 'data', 'context'];
const csvContent = [
headers.join(','),
...logs.map(log => [
log.timestamp,
log.level,
`"${log.message.replace(/"/g, '""')}"`,
`"${JSON.stringify(log.data || '').replace(/"/g, '""')}"`,
`"${JSON.stringify(log.context || '').replace(/"/g, '""')}"`
].join(','))
].join('\n');
return csvContent;
}
/**
* 下载日志文件
*/
downloadLogs(format = 'json', filename = null) {
const content = this.exportLogs(format);
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || `app-logs-${new Date().toISOString().split('T')[0]}.${format}`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
/**
* 记录性能指标
*/
performance(name, duration, context = {}) {
this.info(`Performance: ${name}`, {
duration,
...context
});
}
/**
* 记录API调用
*/
apiCall(method, url, duration, status, responseSize = null) {
this.info(`API Call: ${method} ${url}`, {
method,
url,
duration,
status,
responseSize
});
}
/**
* 记录用户操作
*/
userAction(action, data = {}) {
this.info(`User Action: ${action}`, data);
}
/**
* 记录异常
*/
exception(error, context = {}) {
this.error('Exception', error, context);
}
}
// 创建单例实例
const logger = new Logger();
// 快捷方法
const log = {
error: logger.error.bind(logger),
warn: logger.warn.bind(logger),
info: logger.info.bind(logger),
debug: logger.debug.bind(logger),
performance: logger.performance.bind(logger),
api: logger.apiCall.bind(logger),
action: logger.userAction.bind(logger),
exception: logger.exception.bind(logger)
};
export default logger;
export { Logger, log };
+259
View File
@@ -0,0 +1,259 @@
/**
* 网络连接管理工具类
* 处理网络状态检测、端口扫描、连接测试等功能
*/
import axios from 'axios';
import { ipcRenderer } from 'electron';
class NetworkManager {
constructor() {
this.timeout = 5000;
this.retryCount = 3;
this.retryDelay = 1000;
}
/**
* 检查网络连接状态
*/
async checkNetworkConnection() {
try {
const startTime = Date.now();
const response = await axios.get('https://www.baidu.com', {
timeout: this.timeout,
validateStatus: (status) => status < 500
});
const responseTime = Date.now() - startTime;
return {
status: response.status === 200 ? 'connected' : 'limited',
responseTime,
message: response.status === 200 ? '网络连接正常' : '网络连接受限'
};
} catch (error) {
return {
status: 'disconnected',
responseTime: -1,
message: '网络连接失败',
error: error.message
};
}
}
/**
* 检查特定端口是否可用
*/
async checkPortAvailability(port, host = 'localhost') {
try {
// 使用Electron主进程检查端口
const isInUse = await ipcRenderer.invoke('check-port-status', port);
return {
port,
host,
available: !isInUse,
message: isInUse ? `端口 ${port} 已被占用` : `端口 ${port} 可用`
};
} catch (error) {
return {
port,
host,
available: false,
message: `检查端口失败: ${error.message}`,
error: error.message
};
}
}
/**
* 批量检查端口
*/
async checkMultiplePorts(ports, host = 'localhost') {
const results = [];
for (const port of ports) {
const result = await this.checkPortAvailability(port, host);
results.push(result);
}
return results;
}
/**
* 测试服务器连接
*/
async testServerConnection(url, timeout = this.timeout) {
try {
const startTime = Date.now();
const response = await axios.get(url, {
timeout,
validateStatus: (status) => true
});
const responseTime = Date.now() - startTime;
return {
status: 'success',
responseTime,
statusCode: response.status,
message: `服务器响应正常 (${response.status})`
};
} catch (error) {
return {
status: 'failed',
responseTime: -1,
message: `连接失败: ${error.message}`,
error: error.message
};
}
}
/**
* 获取网络延迟
*/
async getNetworkLatency(target = 'https://www.baidu.com') {
const results = [];
for (let i = 0; i < 3; i++) {
const startTime = Date.now();
try {
await axios.get(target, {
timeout: this.timeout
});
const latency = Date.now() - startTime;
results.push(latency);
} catch (error) {
results.push(-1);
}
if (i < 2) {
await this.delay(1000);
}
}
const validResults = results.filter(r => r > 0);
const averageLatency = validResults.length > 0
? Math.round(validResults.reduce((a, b) => a + b, 0) / validResults.length)
: -1;
return {
results,
averageLatency,
status: averageLatency > 0 ? 'ok' : 'failed'
};
}
/**
* 检测网络类型
*/
async detectNetworkType() {
try {
// 使用navigator.connection API(如果可用)
if (navigator.connection) {
const connection = navigator.connection;
return {
type: connection.effectiveType || 'unknown',
downlink: connection.downlink,
rtt: connection.rtt,
saveData: connection.saveData,
source: 'navigator'
};
}
// 回退到基本检测
const connection = await this.checkNetworkConnection();
return {
type: connection.status === 'connected' ? 'wifi' : 'none',
downlink: null,
rtt: null,
saveData: false,
source: 'fallback'
};
} catch (error) {
return {
type: 'unknown',
downlink: null,
rtt: null,
saveData: false,
source: 'error',
error: error.message
};
}
}
/**
* 重试机制包装
*/
async retry(operation, maxRetries = this.retryCount) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt < maxRetries) {
await this.delay(this.retryDelay * attempt);
}
}
}
throw lastError;
}
/**
* 延迟工具
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 获取综合网络状态报告
*/
async getNetworkStatusReport() {
const [connection, latency, networkType] = await Promise.all([
this.checkNetworkConnection(),
this.getNetworkLatency(),
this.detectNetworkType()
]);
return {
timestamp: new Date().toISOString(),
connection,
latency,
networkType,
summary: {
overall: connection.status === 'connected' ? 'healthy' : 'unhealthy',
issues: this.generateIssuesReport(connection, latency, networkType)
}
};
}
/**
* 生成问题报告
*/
generateIssuesReport(connection, latency, networkType) {
const issues = [];
if (connection.status !== 'connected') {
issues.push('网络连接失败');
}
if (latency.averageLatency > 1000) {
issues.push('网络延迟过高');
}
if (networkType.type === '2g' || networkType.type === 'slow-2g') {
issues.push('网络速度较慢');
}
return issues;
}
}
// 创建单例实例
const networkManager = new NetworkManager();
export default networkManager;
export { NetworkManager };
+380
View File
@@ -0,0 +1,380 @@
/**
* 通知管理工具类
* 处理系统通知、弹窗提醒、消息推送等功能
*/
class NotificationManager {
constructor() {
this.notificationQueue = [];
this.isProcessing = false;
this.defaultOptions = {
type: 'info',
duration: 3000,
showClose: true,
position: 'top-right'
};
}
/**
* 显示系统通知
*/
async showSystemNotification(options) {
const {
title,
message,
type = 'info',
duration = 5000,
icon = null,
actions = []
} = options;
try {
// 使用Electron原生通知
if (window.electronAPI && window.electronAPI.notification) {
return await window.electronAPI.notification.show({
title,
message,
icon,
actions
});
}
// 回退到Web Notification
if ('Notification' in window && Notification.permission === 'granted') {
const notification = new Notification(title, {
body: message,
icon: icon || '/icon.png',
tag: `notification-${Date.now()}`
});
if (actions.length > 0) {
notification.addEventListener('click', () => {
this.handleNotificationClick(actions[0]);
});
}
return notification;
}
// 使用自定义弹窗
return this.showCustomNotification(options);
} catch (error) {
console.error('显示通知失败:', error);
return this.showCustomNotification(options);
}
}
/**
* 显示自定义通知弹窗
*/
showCustomNotification(options) {
const { title, message, type, duration, showClose, position } = {
...this.defaultOptions,
...options
};
// 创建通知元素
const notification = this.createNotificationElement({
title,
message,
type,
showClose,
position
});
// 添加到DOM
document.body.appendChild(notification);
// 自动关闭
if (duration > 0) {
setTimeout(() => {
this.removeNotification(notification);
}, duration);
}
return notification;
}
/**
* 创建通知DOM元素
*/
createNotificationElement(options) {
const { title, message, type, showClose, position } = options;
const notification = document.createElement('div');
notification.className = `custom-notification notification-${type} notification-${position}`;
notification.style.cssText = `
position: fixed;
z-index: 9999;
padding: 16px 24px;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
background: ${this.getTypeColor(type)};
color: white;
min-width: 300px;
max-width: 500px;
animation: notificationSlideIn 0.3s ease;
`;
const titleEl = document.createElement('div');
titleEl.className = 'notification-title';
titleEl.textContent = title;
titleEl.style.fontWeight = 'bold';
titleEl.style.marginBottom = '4px';
const messageEl = document.createElement('div');
messageEl.className = 'notification-message';
messageEl.textContent = message;
notification.appendChild(titleEl);
notification.appendChild(messageEl);
if (showClose) {
const closeBtn = document.createElement('button');
closeBtn.className = 'notification-close';
closeBtn.innerHTML = '×';
closeBtn.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
`;
closeBtn.onclick = () => this.removeNotification(notification);
notification.appendChild(closeBtn);
}
return notification;
}
/**
* 获取类型对应的颜色
*/
getTypeColor(type) {
const colors = {
success: '#67c23a',
warning: '#e6a23c',
error: '#f56c6c',
info: '#909399'
};
return colors[type] || colors.info;
}
/**
* 移除通知元素
*/
removeNotification(notification) {
if (notification && notification.parentNode) {
notification.style.animation = 'notificationSlideOut 0.3s ease';
setTimeout(() => {
notification.parentNode.removeChild(notification);
}, 300);
}
}
/**
* 显示成功通知
*/
success(title, message, options = {}) {
return this.showSystemNotification({
title,
message,
type: 'success',
...options
});
}
/**
* 显示错误通知
*/
error(title, message, options = {}) {
return this.showSystemNotification({
title,
message,
type: 'error',
...options
});
}
/**
* 显示警告通知
*/
warning(title, message, options = {}) {
return this.showSystemNotification({
title,
message,
type: 'warning',
...options
});
}
/**
* 显示信息通知
*/
info(title, message, options = {}) {
return this.showSystemNotification({
title,
message,
type: 'info',
...options
});
}
/**
* 显示设备连接通知
*/
showDeviceConnected(deviceName, deviceId) {
return this.success('设备已连接', `${deviceName} (${deviceId}) 已成功连接`);
}
/**
* 显示设备断开通知
*/
showDeviceDisconnected(deviceName, deviceId, reason = '') {
const message = reason ? `${deviceName} 已断开连接 (${reason})` : `${deviceName} 已断开连接`;
return this.warning('设备已断开', message);
}
/**
* 显示连接超时通知
*/
showConnectionTimeout(deviceName, deviceId) {
return this.error('连接超时', `${deviceName} (${deviceId}) 连接超时,请检查网络或设备状态`);
}
/**
* 显示系统错误通知
*/
showSystemError(error, context = '') {
const message = context ? `${context}: ${error.message || error}` : error.message || error;
return this.error('系统错误', message, { duration: 0 });
}
/**
* 显示网络状态通知
*/
showNetworkStatus(status) {
const messages = {
connected: '网络连接已恢复',
disconnected: '网络连接已断开',
limited: '网络连接受限'
};
const type = status === 'connected' ? 'success' : 'warning';
return this[type]('网络状态', messages[status] || '网络状态未知');
}
/**
* 批量显示通知
*/
async showBatchNotifications(notifications) {
for (const notification of notifications) {
await this.delay(100); // 避免同时显示多个通知
this.showSystemNotification(notification);
}
}
/**
* 请求通知权限
*/
async requestNotificationPermission() {
if ('Notification' in window && Notification.permission === 'default') {
const permission = await Notification.requestPermission();
return permission === 'granted';
}
return false;
}
/**
* 检查通知权限
*/
checkNotificationPermission() {
if ('Notification' in window) {
return Notification.permission;
}
return 'unsupported';
}
/**
* 延迟工具
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 添加CSS样式
*/
addNotificationStyles() {
if (document.getElementById('notification-styles')) {
return;
}
const style = document.createElement('style');
style.id = 'notification-styles';
style.textContent = `
@keyframes notificationSlideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes notificationSlideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.custom-notification {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 1.4;
}
.notification-top-right {
top: 20px;
right: 20px;
}
.notification-top-left {
top: 20px;
left: 20px;
}
.notification-bottom-right {
bottom: 20px;
right: 20px;
}
.notification-bottom-left {
bottom: 20px;
left: 20px;
}
`;
document.head.appendChild(style);
}
}
// 创建单例实例
const notificationManager = new NotificationManager();
// 自动添加样式
if (typeof window !== 'undefined') {
notificationManager.addNotificationStyles();
}
export default notificationManager;
export { NotificationManager };
+138
View File
@@ -0,0 +1,138 @@
import axios from 'axios'
import { ElMessage, ElLoading } from 'element-plus'
import { getToken, removeToken } from './auth'
// 创建axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API || '/',
timeout: 30000 // 30秒超时
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 添加token
const token = getToken()
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
// 添加请求头
config.headers['Content-Type'] = 'application/json;charset=UTF-8'
return config
},
error => {
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const { data, config } = response
// 处理文件下载
if (config.responseType === 'blob') {
return response
}
// 处理正常响应
if (data.code === 200 || data.success) {
return data
} else {
ElMessage.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || 'Error'))
}
},
error => {
console.error('响应错误:', error)
const { response } = error
if (response) {
const { status, data } = response
switch (status) {
case 400:
ElMessage.error(data.message || '请求参数错误')
break
case 401:
ElMessage.error('登录已过期,请重新登录')
removeToken()
window.location.href = '/login'
break
case 403:
ElMessage.error('没有权限访问')
break
case 404:
ElMessage.error('请求的资源不存在')
break
case 500:
ElMessage.error('服务器内部错误')
break
default:
ElMessage.error(data.message || '请求失败')
}
} else {
ElMessage.error('网络连接错误')
}
return Promise.reject(error)
}
)
// 封装请求方法
export default service
// 导出常用方法
export function get(url, params = {}, config = {}) {
return service.get(url, { params, ...config })
}
export function post(url, data = {}, config = {}) {
return service.post(url, data, config)
}
export function put(url, data = {}, config = {}) {
return service.put(url, data, config)
}
export function del(url, config = {}) {
return service.delete(url, config)
}
// 文件上传
export function upload(url, file, data = {}) {
const formData = new FormData()
formData.append('file', file)
Object.keys(data).forEach(key => {
formData.append(key, data[key])
})
return service.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 文件下载
export function download(url, params = {}, filename = '') {
return service.get(url, {
params,
responseType: 'blob'
}).then(response => {
const blob = new Blob([response.data])
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = filename || 'download'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
})
}
+418
View File
@@ -0,0 +1,418 @@
// 系统检测工具类
import { exec } from 'child_process';
import axios from 'axios';
import fs from 'fs';
import os from 'os';
import path from 'path';
import net from 'net';
import { ipcRenderer } from 'electron';
import iconv from 'iconv-lite';
// 系统检测日志记录
async function logSystemCheck(message) {
console.log(`[System Check] ${message}`);
}
// 发送消息到主进程
function sendMessage(data) {
ipcRenderer.send('send-message', JSON.stringify(data));
}
// 响应系统检测数据
function respondSystemCheckData(data) {
const response = {
status: true,
msg: '请求获取设备信息成功',
data: data
};
process.send(JSON.stringify(response));
}
// 检查系统主机名
export async function checkSystemHostName(params) {
const testItem = '系统主机名';
const description = '获取当前使用计算机的主机名';
let result = '';
let suggestion = '';
try {
const hostname = os.hostname();
await logSystemCheck(`HOSTNAME: ${hostname}`);
result = hostname || '无法获取主机名';
if (!hostname) suggestion = '暂时获取不了主机名';
} catch (error) {
result = '获取失败';
suggestion = error.message;
}
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
};
}
// 检查网络连接状态
export async function checkNetworkConnectionState(params) {
const testItem = '客户端网络状态';
const description = '获取客户端网络是否通畅';
let result = '';
let suggestion = '';
try {
const response = await axios.get('https://www.baidu.com', { timeout: 5000 });
if (response.status === 200) {
result = '网络通畅';
} else {
result = '网络不通';
suggestion = '请检查网线是否正确插拔';
}
await logSystemCheck(response.status === 200 ? '网络通畅' : '网络不通');
} catch (error) {
result = '网络不通';
suggestion = '请检查网络连接';
await logSystemCheck(`networkstate_error: 网络连通 ${error.message}`);
}
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
};
}
// 检查系统防火墙状态
export async function checkSystemFirewallStatus(params) {
const testItem = '检查防火墙设置';
const description = '检查防火墙有没有开启';
let result = '';
let suggestion = '';
return new Promise((resolve) => {
exec('netsh advfirewall show currentprofile', async (error, stdout, stderr) => {
if (error || stderr) {
await logSystemCheck(`error: ${error?.message || stderr}`);
resolve({
status: false,
msg: '未知错误',
data: {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type
}
});
return;
}
const isEnabled = stdout.includes('State ON');
await logSystemCheck(`防火墙状态: ${isEnabled ? '开启' : '关闭'}`);
result = isEnabled ? '开启' : '关闭';
if (!isEnabled) suggestion = '请自行百度开启防火墙的方法';
resolve({
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
});
});
});
}
// 检查核心程序文件
export async function checkCoreProgramFiles(params) {
const testItem = '核心文件';
const description = '检查核心启动文件是否存在';
let result = '';
let suggestion = '';
try {
const corePath = path.join(process.cwd(), 'release', '1.0.0', 'win-unpacked', '云企通安全云锁客户端.exe');
const exists = fs.existsSync(corePath);
await logSystemCheck(`核心文件是否存在: ${exists ? '存在' : '不存在'}`);
result = exists ? '存在' : '不存在';
if (!exists) suggestion = '请前去官网下载程序,然后重新安装';
} catch (error) {
result = '检查失败';
suggestion = error.message;
}
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
};
}
// 检查系统驱动
export async function checkSystemDriver(params) {
const testItem = '检查驱动';
const description = '检查驱动是否存在';
let result = '';
let suggestion = '';
try {
const driverPath = 'C://Windows//System32//drivers//usbip_vhci_ude.sys';
const exists = fs.existsSync(driverPath);
await logSystemCheck(`驱动文件是否存在: ${exists ? '存在' : '不存在'}`);
result = exists ? '存在' : '不存在';
if (!exists) suggestion = '请重新安装程序试试';
} catch (error) {
result = '检查失败';
suggestion = error.message;
}
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
};
}
// 检查系统测试模式
export async function checkSystemTestMode(params) {
const testItem = '测试模式';
const description = '检查测试模式是否开启,如果电脑为win7,则需要开启测试模式';
let result = '';
let suggestion = '';
return new Promise((resolve) => {
exec('bcdedit', async (error, stdout) => {
if (error) {
await logSystemCheck(`执行错误: ${error.message}`);
resolve({
status: false,
msg: '未知错误',
data: {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type
}
});
return;
}
let testMode = '';
const lines = stdout.split('\n');
for (let line of lines) {
if (line.includes('testsigning')) {
testMode = line.split(' ').pop().trim() === 'Yes' ? '是' : '否';
break;
}
}
result = testMode || '否';
if (result === '否') suggestion = '请在命令行执行命令开启';
resolve({
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
});
});
});
}
// 检查系统证书
export async function checkSystemCertificate(params) {
const testItem = '检查证书';
const description = '检查证书是否存在';
let result = '';
let suggestion = '';
return new Promise((resolve) => {
if (os.platform() === 'win32' && os.release().startsWith('7')) {
result = '存在';
resolve({
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
});
return;
}
exec('certutil -store root "USBIP Test"', async (error, stdout, stderr) => {
if (error || stderr) {
await logSystemCheck(`执行错误: ${error?.message || stderr}`);
resolve({
status: false,
msg: '未知错误',
data: {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type
}
});
return;
}
const exists = stdout.includes('USBIP Test');
await logSystemCheck(`证书是否存在: ${exists ? '存在' : '不存在'}`);
result = exists ? '存在' : '不存在';
if (!exists) suggestion = '请重新安装程序试试';
resolve({
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
});
});
});
}
// 检查端口服务
export async function checkPortService(port, host = '127.0.0.1') {
return new Promise((resolve) => {
const socket = new net.Socket();
const timeout = 2000;
socket.setTimeout(timeout);
socket.on('connect', () => {
socket.destroy();
resolve(true);
});
socket.on('timeout', () => {
socket.destroy();
resolve(false);
});
socket.on('error', () => {
resolve(false);
});
socket.connect(port, host);
});
}
// 检查系统管理员权限
export async function checkSystemAdminRights(params) {
const testItem = '管理员权限';
const description = '检查当前用户是否拥有管理员权限';
let result = '';
let suggestion = '';
return new Promise((resolve) => {
exec('net session', async (error) => {
await logSystemCheck(`当前访问是否具有管理员权限: ${error ? '没有' : '有'}`);
result = error ? '没有' : '有';
if (error) suggestion = '请点击右键以管理员权限打开本程序';
resolve({
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: testItem,
project_description: description,
checkout_result: result,
repairing_suggestion: suggestion
});
});
});
}
// 检查程序完整性
export async function checkApplicationIntegrity(params) {
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: '程序完整',
project_description: '检查程序文件是否完整',
checkout_result: '完整',
repairing_suggestion: ''
};
}
// 执行系统检测
export async function performSystemCheck(params) {
await logSystemCheck(`checkout_type: ${params.checkout_type}`);
try {
switch (params.checkout_type) {
case 'host_name':
return await checkSystemHostName(params);
case 'network_state':
return await checkNetworkConnectionState(params);
case 'firewall_status':
return await checkSystemFirewallStatus(params);
case 'files_exist':
return await checkCoreProgramFiles(params);
case 'certificate_exist':
return await checkSystemCertificate(params);
case 'test_pattern':
return await checkSystemTestMode(params);
case 'drive_exist':
return await checkSystemDriver(params);
case 'ownership':
return await checkSystemAdminRights(params);
case 'web_serve':
const webStatus = await checkPortService(25525);
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: 'web服务',
project_description: '检查web服务是否开启成功',
checkout_result: webStatus ? '已开启' : '关闭',
repairing_suggestion: webStatus ? '' : '请重启程序尝试一下'
};
case 'socket_serve':
const socketStatus = await checkPortService(801);
return {
user_id: params.user_id || '',
type: params.type,
checkout_type: params.checkout_type,
test_item: 'socket服务',
project_description: '检查socket服务是否开启成功',
checkout_result: socketStatus ? '已开启' : '关闭',
repairing_suggestion: socketStatus ? '' : '请重启程序尝试一下'
};
case 'program_integrity':
return await checkApplicationIntegrity(params);
default:
return {
status: false,
msg: '不存在的检测类型',
data: { user_id: params.user_id || '' }
};
}
} catch (error) {
return {
status: false,
msg: `检测失败: ${error.message}`,
data: { user_id: params.user_id || '' }
};
}
}

Some files were not shown because too many files have changed in this diff Show More