|
@@ -51,6 +51,7 @@
|
|
|
>
|
|
>
|
|
|
<el-table-column prop="lssId" label="LSS ID" min-width="120" sortable="custom" show-overflow-tooltip />
|
|
<el-table-column prop="lssId" label="LSS ID" min-width="120" sortable="custom" show-overflow-tooltip />
|
|
|
<el-table-column prop="lssName" :label="t('名称')" min-width="140" sortable="custom" show-overflow-tooltip />
|
|
<el-table-column prop="lssName" :label="t('名称')" min-width="140" sortable="custom" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="ip" :label="t('IP')" min-width="180" sortable="custom" show-overflow-tooltip />
|
|
|
<el-table-column prop="address" :label="t('地址')" min-width="180" sortable="custom" show-overflow-tooltip />
|
|
<el-table-column prop="address" :label="t('地址')" min-width="180" sortable="custom" show-overflow-tooltip />
|
|
|
<el-table-column prop="status" :label="t('状态')" min-width="100" sortable="custom">
|
|
<el-table-column prop="status" :label="t('状态')" min-width="100" sortable="custom">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
@@ -59,9 +60,9 @@
|
|
|
</el-tag>
|
|
</el-tag>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
- <el-table-column prop="currentTasks" :label="t('当前任务')" min-width="100" sortable="custom" align="center">
|
|
|
|
|
|
|
+ <!-- <el-table-column prop="currentTasks" :label="t('当前任务')" min-width="100" sortable="custom" align="center">
|
|
|
<template #default="{ row }">{{ row.currentTasks }} / {{ row.maxTasks }}</template>
|
|
<template #default="{ row }">{{ row.currentTasks }} / {{ row.maxTasks }}</template>
|
|
|
- </el-table-column>
|
|
|
|
|
|
|
+ </el-table-column> -->
|
|
|
<el-table-column prop="enabled" :label="t('启用')" min-width="80" align="center">
|
|
<el-table-column prop="enabled" :label="t('启用')" min-width="80" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-switch
|
|
<el-switch
|
|
@@ -72,7 +73,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
<el-table-column prop="ffmpegVersion" label="FFmpeg" show-overflow-tooltip />
|
|
<el-table-column prop="ffmpegVersion" label="FFmpeg" show-overflow-tooltip />
|
|
|
- <el-table-column :label="t('摄像头')" min-width="80" align="center">
|
|
|
|
|
|
|
+ <el-table-column :label="t('设备列表')" align="center">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-button type="primary" link :icon="VideoCamera" @click="handleCameraList(row)" />
|
|
<el-button type="primary" link :icon="VideoCamera" @click="handleCameraList(row)" />
|
|
|
</template>
|
|
</template>
|
|
@@ -82,6 +83,16 @@
|
|
|
<el-button type="primary" link :icon="View" @click="handleViewDetail(row)" />
|
|
<el-button type="primary" link :icon="View" @click="handleViewDetail(row)" />
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
|
|
+ <el-table-column label="心跳时间" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatTime(row.lastHeartbeatAt) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="status" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ row.status === 'ONLINE' ? '在线' : '离线' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
<el-table-column :label="t('操作')" align="center" fixed="right">
|
|
<el-table-column :label="t('操作')" align="center" fixed="right">
|
|
|
<template #default="{ row }">
|
|
<template #default="{ row }">
|
|
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)" />
|
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)" />
|
|
@@ -122,43 +133,148 @@
|
|
|
<el-drawer
|
|
<el-drawer
|
|
|
v-model="lssEditDrawerVisible"
|
|
v-model="lssEditDrawerVisible"
|
|
|
direction="rtl"
|
|
direction="rtl"
|
|
|
- size="500px"
|
|
|
|
|
|
|
+ :size="editDrawerSize"
|
|
|
:with-header="false"
|
|
:with-header="false"
|
|
|
destroy-on-close
|
|
destroy-on-close
|
|
|
class="lss-edit-drawer"
|
|
class="lss-edit-drawer"
|
|
|
>
|
|
>
|
|
|
<div class="drawer-content">
|
|
<div class="drawer-content">
|
|
|
- <div class="drawer-header">LSS详情</div>
|
|
|
|
|
|
|
+ <!-- 顶部 Tabs -->
|
|
|
|
|
+ <el-tabs v-model="editActiveTab" class="drawer-tabs">
|
|
|
|
|
+ <el-tab-pane label="LSS详情" name="detail" />
|
|
|
|
|
+ <el-tab-pane label="摄像头列表" name="camera" />
|
|
|
|
|
+ <el-tab-pane label="推币机列表" name="pusher" />
|
|
|
|
|
+ </el-tabs>
|
|
|
|
|
+
|
|
|
<div class="drawer-body">
|
|
<div class="drawer-body">
|
|
|
- <div class="lss-detail-form">
|
|
|
|
|
- <div class="form-item">
|
|
|
|
|
- <label class="form-label">LSS ID:</label>
|
|
|
|
|
- <span class="form-value">{{ currentLss?.lssId }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="form-item">
|
|
|
|
|
- <label class="form-label">名称:</label>
|
|
|
|
|
- <el-input v-model="lssEditForm.lssName" placeholder="请输入名称" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="form-item">
|
|
|
|
|
- <label class="form-label">地址:</label>
|
|
|
|
|
- <el-input v-model="lssEditForm.address" placeholder="请输入地址" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="form-item">
|
|
|
|
|
- <label class="form-label">IP:</label>
|
|
|
|
|
- <span class="form-value">{{ currentLss?.publicIp || '-' }}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="form-item">
|
|
|
|
|
- <label class="form-label">心跳:</label>
|
|
|
|
|
- <span class="form-value" :class="getHeartbeatClass(currentLss?.heartbeat)">
|
|
|
|
|
- {{ formatHeartbeat(currentLss) }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="form-item">
|
|
|
|
|
- <label class="form-label">ably信息:</label>
|
|
|
|
|
- <el-input v-model="lssEditForm.ablyInfo" placeholder="请输入ably信息" />
|
|
|
|
|
|
|
+ <!-- LSS 详情 Tab -->
|
|
|
|
|
+ <div v-show="editActiveTab === 'detail'" class="lss-detail-form">
|
|
|
|
|
+ <el-form ref="lssEditFormRef" :model="lssEditForm" label-width="80px" label-position="left">
|
|
|
|
|
+ <el-form-item label="LSS ID:">
|
|
|
|
|
+ <span class="form-value">{{ currentLss?.lssId }}</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="名称:" prop="lssName">
|
|
|
|
|
+ <el-input v-model="lssEditForm.lssName" placeholder="请输入名称" style="width: 180px" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="地址:" prop="address">
|
|
|
|
|
+ <el-input v-model="lssEditForm.address" placeholder="请输入地址" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="IP:">
|
|
|
|
|
+ <span class="form-value">{{ lssEditForm?.ip }}</span>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="心跳:">
|
|
|
|
|
+ <span class="heartbeat-status" :class="getHeartbeatClass(currentLss?.heartbeat)">
|
|
|
|
|
+ {{ formatHeartbeat(currentLss) }}
|
|
|
|
|
+ <span class="heartbeat-dot" :class="getHeartbeatClass(currentLss?.heartbeat)"></span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <el-tooltip placement="right" effect="light">
|
|
|
|
|
+ <template #content>
|
|
|
|
|
+ <div class="heartbeat-tooltip">
|
|
|
|
|
+ <div class="tooltip-title">心跳状态:</div>
|
|
|
|
|
+ <div>active - 持续返回中,并且频繁</div>
|
|
|
|
|
+ <div>hold - 五分钟内有返回</div>
|
|
|
|
|
+ <div>dead - 五分钟内没有返回</div>
|
|
|
|
|
+ <div class="tooltip-format">表现形式为:</div>
|
|
|
|
|
+ <div class="tooltip-example">Status [yy-mm-dd 00:00:00]</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <el-icon class="heartbeat-info-icon">
|
|
|
|
|
+ <QuestionFilled />
|
|
|
|
|
+ </el-icon>
|
|
|
|
|
+ </el-tooltip>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="ably信息:" prop="ablyInfo">
|
|
|
|
|
+ <div class="textarea-wrapper">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="8"
|
|
|
|
|
+ v-model="lssEditForm.ablyInfo"
|
|
|
|
|
+ placeholder="请输入ably信息"
|
|
|
|
|
+ maxlength="1000"
|
|
|
|
|
+ show-word-limit
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 摄像头列表 Tab -->
|
|
|
|
|
+ <div v-show="editActiveTab === 'camera'" class="tab-content" v-loading="cameraLoading">
|
|
|
|
|
+ <div class="camera-toolbar">
|
|
|
|
|
+ <el-form :model="cameraSearchForm" inline>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model.trim="cameraSearchForm.keyword"
|
|
|
|
|
+ placeholder="IP / 设备ID / 名称"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 200px"
|
|
|
|
|
+ @keyup.enter="handleCameraSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-select v-model="cameraSearchForm.status" placeholder="状态" clearable style="width: 120px">
|
|
|
|
|
+ <el-option label="全部" value="" />
|
|
|
|
|
+ <el-option label="在线" value="ONLINE" />
|
|
|
|
|
+ <el-option label="离线" value="OFFLINE" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :icon="Search" @click="handleCameraSearch">查询</el-button>
|
|
|
|
|
+ <el-button :icon="RefreshRight" @click="handleCameraReset">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-button type="primary" :icon="Plus" @click="handleAddCamera">{{ t('新增') }}</el-button>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <el-empty v-if="!cameraLoading && cameraList.length === 0" description="暂无关联设备" />
|
|
|
|
|
+ <el-table v-else :data="cameraList" stripe size="small" border>
|
|
|
|
|
+ <el-table-column prop="ip" label="本地IP" min-width="110" />
|
|
|
|
|
+ <el-table-column prop="cameraId" label="设备ID" min-width="100" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="name" label="名称" min-width="100" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="状态(心跳)" min-width="140">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span :class="['status-text', row.status === 'ONLINE' ? 'status-active' : 'status-dead']">
|
|
|
|
|
+ {{ formatCameraStatus(row) }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="参数配置" min-width="80" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link @click="handleViewConfig(row)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="运行参数" min-width="80" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link @click="handleViewRunParams(row)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="brand" label="厂商" min-width="90">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatBrand(row.brand) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="model" label="型号" min-width="130" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="添加时间" min-width="140">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatTime(row.createdAt) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="设备控制" min-width="100" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link :icon="Edit" @click="handleEditCamera(row)" />
|
|
|
|
|
+ <el-button type="danger" link :icon="Delete" @click="handleDeleteCamera(row)" />
|
|
|
|
|
+ <el-button type="primary" link :icon="View" @click="handleViewCamera(row)" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div v-if="cameraList.length > 0" class="camera-count">共 {{ cameraList.length }} 个设备</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 推币机列表 Tab -->
|
|
|
|
|
+ <div v-show="editActiveTab === 'pusher'" class="tab-content">
|
|
|
|
|
+ <el-empty description="暂无推币机数据" />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
<div class="drawer-footer">
|
|
<div class="drawer-footer">
|
|
|
<el-button @click="lssEditDrawerVisible = false">{{ t('取消') }}</el-button>
|
|
<el-button @click="lssEditDrawerVisible = false">{{ t('取消') }}</el-button>
|
|
|
<el-button type="primary" :loading="lssUpdating" @click="handleUpdateLss">{{ t('更新') }}</el-button>
|
|
<el-button type="primary" :loading="lssUpdating" @click="handleUpdateLss">{{ t('更新') }}</el-button>
|
|
@@ -173,75 +289,129 @@
|
|
|
direction="rtl"
|
|
direction="rtl"
|
|
|
size="80%"
|
|
size="80%"
|
|
|
destroy-on-close
|
|
destroy-on-close
|
|
|
|
|
+ class="device-drawer"
|
|
|
>
|
|
>
|
|
|
- <div v-loading="cameraLoading">
|
|
|
|
|
- <div class="camera-toolbar">
|
|
|
|
|
- <el-form :model="cameraSearchForm" inline>
|
|
|
|
|
- <el-form-item>
|
|
|
|
|
- <el-input
|
|
|
|
|
- v-model.trim="cameraSearchForm.keyword"
|
|
|
|
|
- placeholder="IP / 设备ID / 名称"
|
|
|
|
|
- clearable
|
|
|
|
|
- style="width: 200px"
|
|
|
|
|
- @keyup.enter="handleCameraSearch"
|
|
|
|
|
- />
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item>
|
|
|
|
|
- <el-select v-model="cameraSearchForm.status" placeholder="状态" clearable style="width: 120px">
|
|
|
|
|
- <el-option label="全部" value="" />
|
|
|
|
|
- <el-option label="在线" value="ONLINE" />
|
|
|
|
|
- <el-option label="离线" value="OFFLINE" />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item>
|
|
|
|
|
- <el-button type="primary" :icon="Search" @click="handleCameraSearch">查询</el-button>
|
|
|
|
|
- <el-button :icon="RefreshRight" @click="handleCameraReset">重置</el-button>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
- <el-button type="primary" :icon="Plus" @click="handleAddCamera">{{ t('新增') }}</el-button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <el-empty v-if="!cameraLoading && cameraList.length === 0" description="暂无关联设备" />
|
|
|
|
|
- <el-table v-else :data="cameraList" stripe size="small" border>
|
|
|
|
|
- <el-table-column prop="ip" label="本地IP" min-width="110" />
|
|
|
|
|
- <el-table-column prop="cameraId" label="设备ID" min-width="100" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column prop="name" label="名称" min-width="100" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column label="状态(心跳)" min-width="140">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <span :class="['status-text', row.status === 'ONLINE' ? 'status-active' : 'status-dead']">
|
|
|
|
|
- {{ formatCameraStatus(row) }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="参数配置" min-width="80" align="center">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-button type="primary" link @click="handleViewConfig(row)">查看</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="运行参数" min-width="80" align="center">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-button type="primary" link @click="handleViewRunParams(row)">查看</el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="brand" label="厂商" min-width="90">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- {{ formatBrand(row.brand) }}
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column prop="model" label="型号" min-width="130" show-overflow-tooltip />
|
|
|
|
|
- <el-table-column label="添加时间" min-width="140">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- {{ formatTime(row.createdAt) }}
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- <el-table-column label="设备控制" min-width="100" align="center" fixed="right">
|
|
|
|
|
- <template #default="{ row }">
|
|
|
|
|
- <el-button type="primary" link :icon="Edit" @click="handleEditCamera(row)" />
|
|
|
|
|
- <el-button type="danger" link :icon="Delete" @click="handleDeleteCamera(row)" />
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-table-column>
|
|
|
|
|
- </el-table>
|
|
|
|
|
- <div v-if="cameraList.length > 0" class="camera-count">共 {{ cameraList.length }} 个设备</div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <el-tabs v-model="deviceActiveTab" class="device-tabs">
|
|
|
|
|
+ <el-tab-pane label="摄像头列表" name="camera">
|
|
|
|
|
+ <div v-loading="cameraLoading" class="tab-content-wrapper">
|
|
|
|
|
+ <div class="camera-toolbar">
|
|
|
|
|
+ <el-form :model="cameraSearchForm" inline>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model.trim="cameraSearchForm.keyword"
|
|
|
|
|
+ placeholder="IP / 设备ID / 名称"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 200px"
|
|
|
|
|
+ @keyup.enter="handleCameraSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-select v-model="cameraSearchForm.status" placeholder="状态" clearable style="width: 120px">
|
|
|
|
|
+ <el-option label="全部" value="" />
|
|
|
|
|
+ <el-option label="在线" value="ONLINE" />
|
|
|
|
|
+ <el-option label="离线" value="OFFLINE" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :icon="Search" @click="handleCameraSearch">查询</el-button>
|
|
|
|
|
+ <el-button :icon="RefreshRight" @click="handleCameraReset">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-button type="primary" :icon="Plus" @click="handleAddCamera">{{ t('新增') }}</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-empty v-if="!cameraLoading && cameraList.length === 0" description="暂无关联设备" />
|
|
|
|
|
+ <el-table v-else :data="cameraList" stripe size="small" border>
|
|
|
|
|
+ <el-table-column prop="ip" label="本地IP" min-width="110" />
|
|
|
|
|
+ <el-table-column prop="cameraId" label="设备ID" min-width="100" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="name" label="名称" min-width="100" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="状态(心跳)" min-width="140">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <span :class="['status-text', row.status === 'ONLINE' ? 'status-active' : 'status-dead']">
|
|
|
|
|
+ {{ formatCameraStatus(row) }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="参数配置" min-width="80" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link @click="handleViewConfig(row)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="运行参数" min-width="80" align="center">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link @click="handleViewRunParams(row)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="brand" label="厂商" min-width="90">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatBrand(row.brand) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="model" label="型号" min-width="130" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="添加时间" min-width="140">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ {{ formatTime(row.createdAt) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="设备控制" min-width="100" align="center" fixed="right">
|
|
|
|
|
+ <template #default="{ row }">
|
|
|
|
|
+ <el-button type="primary" link :icon="Edit" @click="handleEditCamera(row)" />
|
|
|
|
|
+ <el-button type="danger" link :icon="Delete" @click="handleDeleteCamera(row)" />
|
|
|
|
|
+ <el-button type="primary" link :icon="View" @click="handleViewCamera(row)" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ <div v-if="cameraList.length > 0" class="camera-count">共 {{ cameraList.length }} 个设备</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ <el-tab-pane label="推币机列表" name="pusher">
|
|
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
|
|
+ <div class="camera-toolbar">
|
|
|
|
|
+ <el-form inline>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-input placeholder="设备ID / 名称" clearable style="width: 200px" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-select placeholder="状态" clearable style="width: 120px">
|
|
|
|
|
+ <el-option label="全部" value="" />
|
|
|
|
|
+ <el-option label="在线" value="ONLINE" />
|
|
|
|
|
+ <el-option label="离线" value="OFFLINE" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :icon="Search">查询</el-button>
|
|
|
|
|
+ <el-button :icon="RefreshRight">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-button type="primary" :icon="Plus">{{ t('新增') }}</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-empty description="暂无推币机数据" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ <el-tab-pane label="其他设备" name="other">
|
|
|
|
|
+ <div class="tab-content-wrapper">
|
|
|
|
|
+ <div class="camera-toolbar">
|
|
|
|
|
+ <el-form inline>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-input placeholder="设备ID / 名称" clearable style="width: 200px" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-select placeholder="状态" clearable style="width: 120px">
|
|
|
|
|
+ <el-option label="全部" value="" />
|
|
|
|
|
+ <el-option label="在线" value="ONLINE" />
|
|
|
|
|
+ <el-option label="离线" value="OFFLINE" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :icon="Search">查询</el-button>
|
|
|
|
|
+ <el-button :icon="RefreshRight">重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <el-button type="primary" :icon="Plus">{{ t('新增') }}</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <el-empty description="暂无其他设备数据" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-tab-pane>
|
|
|
|
|
+ </el-tabs>
|
|
|
</el-drawer>
|
|
</el-drawer>
|
|
|
|
|
|
|
|
<!-- 摄像头编辑弹窗 -->
|
|
<!-- 摄像头编辑弹窗 -->
|
|
@@ -321,6 +491,20 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
+ <!-- 参数配置/运行参数弹窗 -->
|
|
|
|
|
+ <el-dialog v-model="paramsDialogVisible" :title="paramsDialogTitle" width="600px" :close-on-click-modal="false">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="paramsContent"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="15"
|
|
|
|
|
+ :placeholder="paramsDialogType === 'config' ? '请输入参数配置(JSON 格式)' : '请输入运行参数(JSON 格式)'"
|
|
|
|
|
+ />
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="paramsDialogVisible = false">{{ t('取消') }}</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="paramsSubmitting" @click="handleSaveParams">{{ t('更新') }}</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
<!-- 分页 -->
|
|
<!-- 分页 -->
|
|
|
<div class="pagination-container">
|
|
<div class="pagination-container">
|
|
|
<el-pagination
|
|
<el-pagination
|
|
@@ -338,8 +522,8 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, onMounted, computed } from 'vue'
|
|
|
|
|
-import { Search, RefreshRight, Delete, View, Edit, VideoCamera, Plus } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+import { ref, reactive, onMounted, computed, watch } from 'vue'
|
|
|
|
|
+import { Search, RefreshRight, Delete, View, Edit, VideoCamera, Plus, QuestionFilled } from '@element-plus/icons-vue'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import { listLssNodes, deleteLssNode, setLssNodeEnabled, updateLssNode } from '@/api/lss'
|
|
import { listLssNodes, deleteLssNode, setLssNodeEnabled, updateLssNode } from '@/api/lss'
|
|
|
import { adminListCameras, adminAddCamera, adminUpdateCamera, adminDeleteCamera } from '@/api/camera'
|
|
import { adminListCameras, adminAddCamera, adminUpdateCamera, adminDeleteCamera } from '@/api/camera'
|
|
@@ -407,6 +591,10 @@ function formatCameraStatus(row: CameraInfoDTO): string {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function handleViewCamera(row: CameraInfoDTO) {
|
|
|
|
|
+ console.log(row)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 格式化品牌
|
|
// 格式化品牌
|
|
|
function formatBrand(brand: string | undefined): string {
|
|
function formatBrand(brand: string | undefined): string {
|
|
|
const brandMap: Record<string, string> = {
|
|
const brandMap: Record<string, string> = {
|
|
@@ -447,14 +635,56 @@ function getHeartbeatClass(status: LssHeartbeatStatus | undefined): string {
|
|
|
|
|
|
|
|
// 查看参数配置
|
|
// 查看参数配置
|
|
|
function handleViewConfig(row: CameraInfoDTO) {
|
|
function handleViewConfig(row: CameraInfoDTO) {
|
|
|
- ElMessage.info(`查看 ${row.name} 的参数配置`)
|
|
|
|
|
- // TODO: 打开参数配置弹窗
|
|
|
|
|
|
|
+ paramsCamera.value = row
|
|
|
|
|
+ paramsDialogType.value = 'config'
|
|
|
|
|
+ paramsDialogTitle.value = `参数配置 - ${row.name}`
|
|
|
|
|
+ paramsContent.value = row.configParams || ''
|
|
|
|
|
+ paramsDialogVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 查看运行参数
|
|
// 查看运行参数
|
|
|
function handleViewRunParams(row: CameraInfoDTO) {
|
|
function handleViewRunParams(row: CameraInfoDTO) {
|
|
|
- ElMessage.info(`查看 ${row.name} 的运行参数`)
|
|
|
|
|
- // TODO: 打开运行参数弹窗
|
|
|
|
|
|
|
+ paramsCamera.value = row
|
|
|
|
|
+ paramsDialogType.value = 'run'
|
|
|
|
|
+ paramsDialogTitle.value = `运行参数 - ${row.name}`
|
|
|
|
|
+ paramsContent.value = row.runParams || ''
|
|
|
|
|
+ paramsDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 保存参数配置/运行参数
|
|
|
|
|
+async function handleSaveParams() {
|
|
|
|
|
+ if (!paramsCamera.value) return
|
|
|
|
|
+
|
|
|
|
|
+ paramsSubmitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data: CameraUpdateRequest = {
|
|
|
|
|
+ id: paramsCamera.value.id
|
|
|
|
|
+ }
|
|
|
|
|
+ if (paramsDialogType.value === 'config') {
|
|
|
|
|
+ data.configParams = paramsContent.value
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data.runParams = paramsContent.value
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const res = await adminUpdateCamera(data)
|
|
|
|
|
+ if (res.success) {
|
|
|
|
|
+ ElMessage.success('保存成功')
|
|
|
|
|
+ paramsDialogVisible.value = false
|
|
|
|
|
+ // 更新本地数据
|
|
|
|
|
+ if (paramsDialogType.value === 'config') {
|
|
|
|
|
+ paramsCamera.value.configParams = paramsContent.value
|
|
|
|
|
+ } else {
|
|
|
|
|
+ paramsCamera.value.runParams = paramsContent.value
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.errMessage || '保存失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('保存参数失败', error)
|
|
|
|
|
+ ElMessage.error('保存失败')
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ paramsSubmitting.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
const loading = ref(false)
|
|
@@ -468,15 +698,24 @@ const currentLss = ref<LssNodeDTO | null>(null)
|
|
|
// LSS 编辑抽屉状态
|
|
// LSS 编辑抽屉状态
|
|
|
const lssEditDrawerVisible = ref(false)
|
|
const lssEditDrawerVisible = ref(false)
|
|
|
const lssUpdating = ref(false)
|
|
const lssUpdating = ref(false)
|
|
|
|
|
+const editActiveTab = ref('detail')
|
|
|
|
|
+const lssEditFormRef = ref<FormInstance>()
|
|
|
const lssEditForm = reactive({
|
|
const lssEditForm = reactive({
|
|
|
lssName: '',
|
|
lssName: '',
|
|
|
address: '',
|
|
address: '',
|
|
|
|
|
+ ip: '',
|
|
|
ablyInfo: ''
|
|
ablyInfo: ''
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 根据当前 tab 计算抽屉宽度
|
|
|
|
|
+const editDrawerSize = computed(() => {
|
|
|
|
|
+ return editActiveTab.value === 'detail' ? '500px' : '80%'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
// 设备列表抽屉状态
|
|
// 设备列表抽屉状态
|
|
|
const cameraDrawerVisible = ref(false)
|
|
const cameraDrawerVisible = ref(false)
|
|
|
const cameraLoading = ref(false)
|
|
const cameraLoading = ref(false)
|
|
|
|
|
+const deviceActiveTab = ref('camera')
|
|
|
const cameraList = ref<CameraInfoDTO[]>([])
|
|
const cameraList = ref<CameraInfoDTO[]>([])
|
|
|
|
|
|
|
|
// 摄像头搜索表单
|
|
// 摄像头搜索表单
|
|
@@ -493,6 +732,14 @@ const cameraSubmitting = ref(false)
|
|
|
const currentCamera = ref<CameraInfoDTO | null>(null)
|
|
const currentCamera = ref<CameraInfoDTO | null>(null)
|
|
|
const availableVendors = ref<CameraVendorDTO[]>([])
|
|
const availableVendors = ref<CameraVendorDTO[]>([])
|
|
|
|
|
|
|
|
|
|
+// 参数配置/运行参数弹窗状态
|
|
|
|
|
+const paramsDialogVisible = ref(false)
|
|
|
|
|
+const paramsDialogTitle = ref('')
|
|
|
|
|
+const paramsDialogType = ref<'config' | 'run'>('config')
|
|
|
|
|
+const paramsContent = ref('')
|
|
|
|
|
+const paramsSubmitting = ref(false)
|
|
|
|
|
+const paramsCamera = ref<CameraInfoDTO | null>(null)
|
|
|
|
|
+
|
|
|
// 摄像头表单
|
|
// 摄像头表单
|
|
|
const cameraForm = reactive({
|
|
const cameraForm = reactive({
|
|
|
selectedVendorId: null as number | null,
|
|
selectedVendorId: null as number | null,
|
|
@@ -621,7 +868,9 @@ function handleEdit(row: LssNodeDTO) {
|
|
|
currentLss.value = row
|
|
currentLss.value = row
|
|
|
lssEditForm.lssName = row.lssName || ''
|
|
lssEditForm.lssName = row.lssName || ''
|
|
|
lssEditForm.address = row.address || ''
|
|
lssEditForm.address = row.address || ''
|
|
|
|
|
+ lssEditForm.ip = row.ip || ''
|
|
|
lssEditForm.ablyInfo = row.ablyInfo || ''
|
|
lssEditForm.ablyInfo = row.ablyInfo || ''
|
|
|
|
|
+ editActiveTab.value = 'detail'
|
|
|
lssEditDrawerVisible.value = true
|
|
lssEditDrawerVisible.value = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -629,6 +878,7 @@ async function handleCameraList(row: LssNodeDTO) {
|
|
|
currentLss.value = row
|
|
currentLss.value = row
|
|
|
cameraSearchForm.keyword = ''
|
|
cameraSearchForm.keyword = ''
|
|
|
cameraSearchForm.status = ''
|
|
cameraSearchForm.status = ''
|
|
|
|
|
+ deviceActiveTab.value = 'camera'
|
|
|
cameraDrawerVisible.value = true
|
|
cameraDrawerVisible.value = true
|
|
|
await loadCameraList()
|
|
await loadCameraList()
|
|
|
}
|
|
}
|
|
@@ -901,6 +1151,15 @@ async function handleDelete(row: LssNodeDTO) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 监听 tab 切换,加载对应数据
|
|
|
|
|
+watch(editActiveTab, (newTab) => {
|
|
|
|
|
+ if (newTab === 'camera' && currentLss.value) {
|
|
|
|
|
+ cameraSearchForm.keyword = ''
|
|
|
|
|
+ cameraSearchForm.status = ''
|
|
|
|
|
+ loadCameraList()
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
getList()
|
|
getList()
|
|
|
})
|
|
})
|
|
@@ -1043,13 +1302,38 @@ onMounted(() => {
|
|
|
height: 100%;
|
|
height: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.drawer-header {
|
|
|
|
|
|
|
+.drawer-tabs {
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
- padding: 16px 20px;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- color: #303133;
|
|
|
|
|
border-bottom: 1px solid #e5e7eb;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-tabs__header) {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ padding: 0 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-tabs__nav-wrap::after) {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-tabs__item) {
|
|
|
|
|
+ height: 48px;
|
|
|
|
|
+ line-height: 48px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+
|
|
|
|
|
+ &.is-active {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-tabs__active-bar) {
|
|
|
|
|
+ background-color: #409eff;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.drawer-body {
|
|
.drawer-body {
|
|
@@ -1059,31 +1343,86 @@ onMounted(() => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.lss-detail-form {
|
|
.lss-detail-form {
|
|
|
- .form-item {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: flex-start;
|
|
|
|
|
- margin-bottom: 16px;
|
|
|
|
|
-
|
|
|
|
|
- .form-label {
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
- width: 80px;
|
|
|
|
|
- line-height: 32px;
|
|
|
|
|
- color: #606266;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .form-value {
|
|
|
|
|
- line-height: 32px;
|
|
|
|
|
- color: #303133;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
|
|
+ color: #606266;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- .el-input {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ .form-value {
|
|
|
|
|
+ line-height: 32px;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .textarea-wrapper {
|
|
|
|
|
+ width: 100%;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.heartbeat-status {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ line-height: 32px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.heartbeat-dot {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+
|
|
|
|
|
+ &.status-active {
|
|
|
|
|
+ background-color: #67c23a;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.status-hold {
|
|
|
|
|
+ background-color: #e6a23c;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ &.status-dead {
|
|
|
|
|
+ background-color: #f56c6c;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.heartbeat-info-icon {
|
|
|
|
|
+ margin-left: 8px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+
|
|
|
|
|
+ &:hover {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.heartbeat-tooltip {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+
|
|
|
|
|
+ .tooltip-title {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ margin-bottom: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tooltip-format {
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .tooltip-example {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tab-content {
|
|
|
|
|
+ min-height: 200px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.drawer-footer {
|
|
.drawer-footer {
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
display: flex;
|
|
display: flex;
|