473 lines
11 KiB
Vue
473 lines
11 KiB
Vue
<template>
|
||
<div class="device-list">
|
||
<!-- 页面头部 -->
|
||
<div class="page-header">
|
||
<div class="header-content">
|
||
<el-button type="default" plain @click="handleBack">
|
||
{{ t('common.back') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<div style="height: 10px;width: 100%"></div>
|
||
<el-scrollbar height="83vh" @end-reached="loadMore">
|
||
<div class="devices-section">
|
||
<!-- 设备列表 -->
|
||
<div class="devices-grid">
|
||
<!-- 动态设备卡片 -->
|
||
<div v-if="devicesList.length > 0" class="device-card" v-for="device in devicesList" :key="device.id">
|
||
<div class="device-card-body">
|
||
<div class="device-info-item">
|
||
<span class="label">{{ t('deviceList.macAddress') }}:</span>
|
||
<span class="value">{{ device.mac_address || t('deviceList.notSet') }}</span>
|
||
</div>
|
||
<div class="device-info-item">
|
||
<span class="label">{{ t('deviceList.createdAt') }}:</span>
|
||
<span class="value">{{ device.created_at || t('deviceList.notSet') }}</span>
|
||
</div>
|
||
<div class="device-info-item">
|
||
<span class="label">{{ t('deviceList.updatedAt') }}:</span>
|
||
<span class="value">{{ device.updated_at || t('deviceList.notSet') }}</span>
|
||
</div>
|
||
<div class="device-actions">
|
||
<el-button type="danger" size="small" @click="handleUnbindDevice(device.id)">
|
||
{{ t('deviceList.unbindDevice') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 初始加载状态 -->
|
||
<div v-if="loading && devicesList.length === 0" class="loading-container">
|
||
<div class="loading-text">{{ t('deviceList.loading') }}</div>
|
||
</div>
|
||
|
||
<!-- 加载更多状态 -->
|
||
<div v-if="loading && devicesList.length > 0" class="loading-more-container">
|
||
<div class="loading-more-text">{{ t('deviceList.loadingMore') }}</div>
|
||
</div>
|
||
|
||
<!-- 没有更多数据状态 -->
|
||
<div v-if="!loading && !hasMore.value && devicesList.length > 0" class="no-more-container">
|
||
<div class="no-more-text">{{ t('deviceList.noMoreDevices') }}</div>
|
||
</div>
|
||
|
||
<!-- 空状态 -->
|
||
<div v-if="!loading && devicesList.length === 0" class="empty-container">
|
||
<div class="empty-content">
|
||
<el-icon size="64"><Monitor /></el-icon>
|
||
<p class="empty-text">{{ t('deviceList.noDevices') }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-scrollbar>
|
||
|
||
<!-- 解除绑定确认弹窗 -->
|
||
<el-dialog
|
||
v-model="showUnbindDialog"
|
||
:title="t('deviceList.unbindConfirmTitle')"
|
||
width="400px"
|
||
center
|
||
>
|
||
<div class="unbind-dialog-content">
|
||
<p>{{ t('deviceList.unbindConfirmContent') }}</p>
|
||
</div>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="cancelUnbind">{{ t('common.cancel') }}</el-button>
|
||
<el-button type="danger" @click="confirmUnbind">
|
||
{{ t('deviceList.confirmUnbind') }}
|
||
</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { Monitor } from '@element-plus/icons-vue'
|
||
import { ref, onMounted, computed } from 'vue'
|
||
import { useRoute,useRouter } from 'vue-router'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { XiaozhiServer } from '@deotaland/utils'
|
||
import { ElMessage } from 'element-plus'
|
||
const router = useRouter()
|
||
// 国际化
|
||
const { t } = useI18n()
|
||
const route = useRoute()
|
||
|
||
// 创建XiaozhiServer实例
|
||
const xiaozhiServer = new XiaozhiServer()
|
||
|
||
// 响应式状态
|
||
const devicesList = ref([])
|
||
const loading = ref(false)
|
||
const hasMore = ref(true) // 是否还有更多数据
|
||
const agentId = computed(() => route.params.agentId)
|
||
const currentPage = ref(1)
|
||
const pageSize = ref(10)
|
||
const total = ref(0)
|
||
const showUnbindDialog = ref(false) // 解除绑定确认弹窗
|
||
const currentDeviceId = ref('') // 当前要解除绑定的设备ID
|
||
|
||
// 获取设备列表
|
||
const getDevicesList = async () => {
|
||
if (!hasMore.value || loading.value) return
|
||
|
||
try {
|
||
loading.value = true
|
||
console.log('调用deviceListAgent,agentId:', agentId.value, 'currentPage:', currentPage.value, 'pageSize:', pageSize.value)
|
||
// 调用真实API获取设备列表
|
||
const response = await xiaozhiServer.deviceListAgent({
|
||
agent_id: agentId.value,
|
||
page: currentPage.value,
|
||
page_size: pageSize.value
|
||
})
|
||
console.log('deviceListAgent响应结果:', response)
|
||
|
||
let newDevices = []
|
||
if (response.code === 0 && response.data) {
|
||
newDevices = response.data.items || []
|
||
total.value = response.data.total || 0
|
||
}
|
||
// 累加数据而不是替换数据
|
||
if (currentPage.value === 1) {
|
||
devicesList.value = newDevices
|
||
} else {
|
||
devicesList.value = [...devicesList.value, ...newDevices]
|
||
}
|
||
|
||
// 判断是否还有更多数据
|
||
hasMore.value = devicesList.value.length < total.value
|
||
currentPage.value++
|
||
} catch (error) {
|
||
console.error('获取设备列表出错:', error)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 加载更多数据
|
||
const loadMore = () => {
|
||
getDevicesList()
|
||
}
|
||
|
||
// 处理解除绑定按钮点击
|
||
const handleUnbindDevice = (deviceId) => {
|
||
currentDeviceId.value = deviceId
|
||
showUnbindDialog.value = true
|
||
}
|
||
|
||
// 取消解除绑定
|
||
const cancelUnbind = () => {
|
||
showUnbindDialog.value = false
|
||
currentDeviceId.value = ''
|
||
}
|
||
|
||
// 确认解除绑定
|
||
const confirmUnbind = async () => {
|
||
try {
|
||
loading.value = true
|
||
console.log('调用unbindDeviceAgent,deviceId:', currentDeviceId.value)
|
||
// 调用真实API解除绑定设备
|
||
const response = await xiaozhiServer.unbindDeviceAgent({
|
||
device_id: currentDeviceId.value
|
||
})
|
||
console.log('unbindDeviceAgent响应结果:', response)
|
||
|
||
if (response.code === 0) {
|
||
ElMessage.success(t('deviceList.unbindSuccess'))
|
||
init() // 刷新页面数据
|
||
} else {
|
||
ElMessage.error(response.msg || t('deviceList.unbindFailed'))
|
||
}
|
||
} catch (error) {
|
||
console.error('解除绑定设备出错:', error)
|
||
ElMessage.error(t('deviceList.unbindFailed'))
|
||
} finally {
|
||
loading.value = false
|
||
showUnbindDialog.value = false
|
||
currentDeviceId.value = ''
|
||
}
|
||
}
|
||
|
||
const init = ()=>{
|
||
currentPage.value = 1;
|
||
loading.value = false;
|
||
hasMore.value = true;
|
||
devicesList.value = [];
|
||
total.value = 0;
|
||
getDevicesList()
|
||
}
|
||
// 处理返回按钮点击
|
||
const handleBack = () => {
|
||
router.back()
|
||
}
|
||
|
||
// 组件挂载时获取设备列表
|
||
onMounted(() => {
|
||
init();
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.device-list {
|
||
min-height: 100vh;
|
||
background: var(--bg-color, #F3F4F6);
|
||
}
|
||
|
||
/* 页面头部样式 */
|
||
.page-header {
|
||
padding: 20px 24px;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.header-content {
|
||
margin: 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 16px;
|
||
}
|
||
|
||
.page-title {
|
||
margin: 0;
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: var(--text-color, #1F2937);
|
||
}
|
||
|
||
.devices-section {
|
||
margin: 0 auto;
|
||
padding: 24px;
|
||
}
|
||
|
||
/* 设备列表 */
|
||
.devices-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 32px;
|
||
}
|
||
|
||
/* 设备卡片 */
|
||
.device-card {
|
||
background: #fff;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--sidebar-border, #e5e7eb);
|
||
padding: 16px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.device-card:hover {
|
||
border-color: var(--primary-color, #409EFF);
|
||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.15);
|
||
}
|
||
|
||
.device-card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.device-name {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: var(--text-color, #1F2937);
|
||
line-height: 1.2;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.device-card-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.device-info-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.device-info-item .label {
|
||
font-size: 14px;
|
||
color: var(--sidebar-text-secondary, #6b7280);
|
||
min-width: 80px;
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.device-info-item .value {
|
||
font-size: 14px;
|
||
color: var(--text-color, #1F2937);
|
||
font-weight: 500;
|
||
width: 100%;
|
||
word-break: break-word;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* 设备操作按钮样式 */
|
||
.device-actions {
|
||
margin-top: 16px;
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* 解除绑定弹窗样式 */
|
||
.unbind-dialog-content {
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.unbind-dialog-content p {
|
||
margin-bottom: 16px;
|
||
color: var(--text-color, #1F2937);
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
:root.dark .unbind-dialog-content p {
|
||
color: var(--text-color, #F3F4F6);
|
||
}
|
||
|
||
/* 加载状态和空状态样式 */
|
||
.loading-container {
|
||
grid-column: 1 / -1;
|
||
padding: 40px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 16px;
|
||
color: var(--sidebar-text-secondary, #6b7280);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 加载更多状态样式 */
|
||
.loading-more-container {
|
||
grid-column: 1 / -1;
|
||
padding: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.loading-more-text {
|
||
font-size: 14px;
|
||
color: var(--sidebar-text-secondary, #6b7280);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 没有更多数据状态样式 */
|
||
.no-more-container {
|
||
grid-column: 1 / -1;
|
||
padding: 20px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.no-more-text {
|
||
font-size: 14px;
|
||
color: var(--sidebar-text-secondary, #6b7280);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.empty-container {
|
||
grid-column: 1 / -1;
|
||
padding: 60px 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16px;
|
||
color: var(--sidebar-text-secondary, #6b7280);
|
||
}
|
||
|
||
.empty-text {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 暗色主题样式 */
|
||
:root.dark .device-list {
|
||
background: linear-gradient(135deg, #1F2937 0%, #111827 50%, #030712 100%);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
:root.dark .page-title {
|
||
color: var(--text-color, #F3F4F6);
|
||
}
|
||
|
||
:root.dark .device-card {
|
||
background: #111827;
|
||
border-color: var(--sidebar-border, #374151);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
:root.dark .device-name {
|
||
color: var(--text-color, #F3F4F6);
|
||
}
|
||
|
||
:root.dark .device-info-item .label {
|
||
color: var(--sidebar-text-secondary, #9ca3af);
|
||
}
|
||
|
||
:root.dark .device-info-item .value {
|
||
color: var(--text-color, #F3F4F6);
|
||
}
|
||
|
||
:root.dark .loading-text {
|
||
color: var(--sidebar-text-secondary, #9ca3af);
|
||
}
|
||
|
||
:root.dark .empty-text {
|
||
color: var(--sidebar-text-secondary, #9ca3af);
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 1024px) {
|
||
.page-header {
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
.devices-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 20px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.devices-section {
|
||
padding: 16px;
|
||
}
|
||
|
||
.devices-grid {
|
||
grid-template-columns: 1fr;
|
||
gap: 16px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.devices-section {
|
||
padding: 12px;
|
||
}
|
||
|
||
.devices-grid {
|
||
gap: 12px;
|
||
}
|
||
|
||
.device-card {
|
||
padding: 12px;
|
||
}
|
||
|
||
.device-name {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
</style> |