首次提交
@@ -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?
|
||||||
@@ -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).
|
||||||
@@ -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>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 |
@@ -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>
|
||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 37 KiB |
@@ -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 |
@@ -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>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<template>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<template>
|
||||||
|
</template>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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'
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 设备新增组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AddDevice',
|
||||||
|
// 设备新增表单,包含设备型号、序列号、名称等字段
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 设备新增样式 */
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 历史记录组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'History',
|
||||||
|
// 展示端口使用历史记录,支持分页查询
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 历史记录样式 */
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Excel导入组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ImportExcel',
|
||||||
|
// 处理Excel文件导入,包含进度反馈和模板下载
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 导入组件样式 */
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 端口卡片列表组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PortCardList',
|
||||||
|
// 实现端口状态管理、连接时长自动计时、权限控制的强制断开按钮等功能
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 端口卡片样式 */
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 端口配置表单组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PortConfigForm',
|
||||||
|
// 处理端口名称、类型、使用方式等字段的表单验证和提交
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 表单样式 */
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 端口列表组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PortList',
|
||||||
|
// 表格展示端口列表,包含状态可视化和操作功能
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 端口列表样式 */
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 端口管理组件 -->
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PortManage',
|
||||||
|
// 实现端口状态分类、WebSocket轮询更新、导入导出功能
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 端口管理样式 */
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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 };
|
||||||
@@ -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';
|
||||||
@@ -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
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// 设备状态管理
|
||||||
|
// 使用Pinia进行状态管理
|
||||||
@@ -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'
|
||||||
|
});
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
// 端口状态管理
|
||||||
|
// 使用Pinia进行状态管理,如myRule(用户权限)、totalPort(端口统计)等
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 };
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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 };
|
||||||
@@ -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
|
||||||
|
};
|
||||||
@@ -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 };
|
||||||
@@ -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 };
|
||||||
@@ -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 };
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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 || '' }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||