deotalandAi/apps/frontend/src/views/DeviceList.vue

473 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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('调用deviceListAgentagentId:', 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('调用unbindDeviceAgentdeviceId:', 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>