Files
ca-lock-client/src/components/device/DeviceCardList.vue
T
2025-07-25 23:03:30 +08:00

333 lines
8.3 KiB
Vue

<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>