首次提交
@@ -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 || '' }
|
||||
};
|
||||
}
|
||||
}
|
||||