添加国标设备的获取

This commit is contained in:
孙小云 2025-12-10 17:21:37 +08:00
parent cf698f4456
commit 500d5f688a
2 changed files with 651 additions and 1 deletions

View File

@ -3,8 +3,9 @@ import { ref } from 'vue'
import WebSocketDemo from './components/WebSocketDemo.vue' import WebSocketDemo from './components/WebSocketDemo.vue'
import Zlm from './components/Zlm.vue' import Zlm from './components/Zlm.vue'
import Wvp from './components/Wvp.vue' import Wvp from './components/Wvp.vue'
import GB2818Live from './components/GB2818Live.vue'
type TabType = 'websocket' | 'zlm' | 'wvp' type TabType = 'websocket' | 'zlm' | 'wvp' | 'gb28181'
const activeTab = ref<TabType>('websocket') const activeTab = ref<TabType>('websocket')
@ -34,12 +35,19 @@ const switchTab = (tab: TabType) => {
> >
WVP WVP
</button> </button>
<button
:class="{ active: activeTab === 'gb28181' }"
@click="switchTab('gb28181')"
>
GB28181 Live
</button>
</div> </div>
<div class="tab-content"> <div class="tab-content">
<WebSocketDemo v-if="activeTab === 'websocket'" /> <WebSocketDemo v-if="activeTab === 'websocket'" />
<Zlm v-if="activeTab === 'zlm'" /> <Zlm v-if="activeTab === 'zlm'" />
<Wvp v-if="activeTab === 'wvp'" /> <Wvp v-if="activeTab === 'wvp'" />
<GB2818Live v-if="activeTab === 'gb28181'" />
</div> </div>
</div> </div>
</template> </template>

View File

@ -0,0 +1,642 @@
<script setup lang="ts">
import { ref } from 'vue'
const GATEWAY_URL = 'http://localhost:8080'
//
const channelPage = ref(1)
const channelCount = ref(15)
const channelType = ref('')
const channelQuery = ref('')
const channelOnline = ref('')
const channelLoading = ref(false)
const channelError = ref('')
const channelList = ref<any[]>([])
const selectedChannel = ref<any>(null)
//
const getChannelList = async () => {
channelLoading.value = true
channelError.value = ''
channelList.value = []
try {
const params = new URLSearchParams({
page: channelPage.value.toString(),
count: channelCount.value.toString()
})
//
if (channelType.value) {
params.append('channelType', channelType.value)
}
if (channelQuery.value) {
params.append('query', channelQuery.value)
}
if (channelOnline.value) {
params.append('online', channelOnline.value)
}
const response = await fetch(`${GATEWAY_URL}/wvp/api/common/channel/list?${params}`)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
if (data.code === 0 && data.data && data.data.list) {
channelList.value = data.data.list
} else {
channelError.value = data.msg || '获取通道列表失败'
}
} catch (error) {
channelError.value = error instanceof Error ? error.message : '获取通道列表失败'
} finally {
channelLoading.value = false
}
}
//
const playLoading = ref(false)
const playError = ref('')
const playUrl = ref('')
const channelStatus = ref('未播放')
//
const playChannel = async (channelId: string) => {
if (!channelId) {
playError.value = '请先选择一个通道'
return
}
playLoading.value = true
playError.value = ''
playUrl.value = ''
channelStatus.value = '正在启动播放...'
try {
const params = new URLSearchParams({
channelId: channelId
})
const response = await fetch(`${GATEWAY_URL}/wvp/api/common/channel/play?${params}`)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
if (data.code === 0 && data.data) {
playUrl.value = JSON.stringify(data.data, null, 2)
channelStatus.value = '播放中'
playError.value = ''
} else {
playError.value = data.msg || '获取播放地址失败'
channelStatus.value = '播放失败'
}
} catch (error) {
playError.value = error instanceof Error ? error.message : '获取播放地址失败'
channelStatus.value = '播放失败'
} finally {
playLoading.value = false
}
}
//
const stopLoading = ref(false)
const stopError = ref('')
const stopChannel = async (channelId: string) => {
if (!channelId) {
stopError.value = '请先选择一个通道'
return
}
stopLoading.value = true
stopError.value = ''
channelStatus.value = '正在停止播放...'
try {
const params = new URLSearchParams({
channelId: channelId
})
const response = await fetch(`${GATEWAY_URL}/wvp/api/common/channel/play/stop?${params}`)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
if (data.code === 0) {
playUrl.value = ''
channelStatus.value = '已停止'
stopError.value = ''
} else {
stopError.value = data.msg || '停止播放失败'
channelStatus.value = '停止失败'
}
} catch (error) {
stopError.value = error instanceof Error ? error.message : '停止播放失败'
channelStatus.value = '停止失败'
} finally {
stopLoading.value = false
}
}
//
const selectChannel = (channel: any) => {
selectedChannel.value = channel
playUrl.value = ''
playError.value = ''
stopError.value = ''
channelStatus.value = '已选择通道'
}
//
const getStatusClass = () => {
const status = channelStatus.value
if (status === '播放中') return 'status-playing'
if (status === '已停止' || status === '未播放') return 'status-stopped'
if (status.includes('失败')) return 'status-error'
return 'status-loading'
}
</script>
<template>
<div class="gb28181-container">
<h2>GB28181 国标通道管理</h2>
<div class="api-section">
<h3>1. 获取通道列表</h3>
<div class="api-description">
<p>通过网关调用: <code>GET /wvp/api/common/channel/list</code></p>
<p>实际转发到: <code>http://114.67.89.4:9090/api/common/channel/list</code></p>
<p>网关会自动注入 access-token 请求头</p>
</div>
<div class="form-row">
<div class="form-group">
<label>当前页 (page):</label>
<input
v-model.number="channelPage"
type="number"
min="1"
placeholder="1"
/>
</div>
<div class="form-group">
<label>每页数量 (count):</label>
<input
v-model.number="channelCount"
type="number"
min="1"
placeholder="15"
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>通道类型 (channelType) - 可选:</label>
<input
v-model="channelType"
type="text"
placeholder="留空表示不过滤"
/>
</div>
<div class="form-group">
<label>查询内容 (query) - 可选:</label>
<input
v-model="channelQuery"
type="text"
placeholder="留空表示不过滤"
/>
</div>
</div>
<div class="form-group">
<label>在线状态 (online) - 可选:</label>
<input
v-model="channelOnline"
type="text"
placeholder="留空表示不过滤"
/>
</div>
<div class="button-group">
<button @click="getChannelList" :disabled="channelLoading">
{{ channelLoading ? '查询中...' : '获取通道列表' }}
</button>
</div>
<div v-if="channelError" class="error">
错误: {{ channelError }}
</div>
<div v-if="channelList.length > 0" class="channel-list">
<h4>通道列表 ( {{ channelList.length }} ):</h4>
<table class="channel-table">
<thead>
<tr>
<th>通道ID</th>
<th>通道名称</th>
<th>在线状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="channel in channelList"
:key="channel.gbDeviceId"
:class="{ 'selected': selectedChannel?.gbDeviceId === channel.gbDeviceId }"
>
<td>{{ channel.gbDeviceId }}</td>
<td>{{ channel.gbName || '-' }}</td>
<td>
<span :class="channel.gbStatus === 'ON' ? 'status-online' : 'status-offline'">
{{ channel.gbStatus === 'ON' ? '在线' : '离线' }}
</span>
</td>
<td>
<button
@click="selectChannel(channel)"
class="select-button"
:disabled="selectedChannel?.gbDeviceId === channel.gbDeviceId"
>
{{ selectedChannel?.gbDeviceId === channel.gbDeviceId ? '已选择' : '选择' }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="api-section" v-if="selectedChannel">
<h3>2. 通道控制</h3>
<div class="api-description">
<p>播放接口: <code>GET /wvp/api/common/channel/play?channelId={channelId}</code></p>
<p>停止接口: <code>GET /wvp/api/common/channel/play/stop?channelId={channelId}</code></p>
</div>
<div class="selected-channel-info">
<h4>当前选择的通道:</h4>
<p><strong>通道ID:</strong> {{ selectedChannel.gbDeviceId }}</p>
<p><strong>通道名称:</strong> {{ selectedChannel.gbName || '-' }}</p>
<p><strong>在线状态:</strong>
<span :class="selectedChannel.gbStatus === 'ON' ? 'status-online' : 'status-offline'">
{{ selectedChannel.gbStatus === 'ON' ? '在线' : '离线' }}
</span>
</p>
</div>
<div class="status-display">
<h4>通道状态:</h4>
<div class="status-box" :class="getStatusClass()">
{{ channelStatus }}
</div>
</div>
<div class="button-group">
<button
@click="playChannel(selectedChannel.gbId)"
:disabled="playLoading || channelStatus === '播放中'"
>
{{ playLoading ? '启动中...' : '播放' }}
</button>
<button
@click="stopChannel(selectedChannel.gbId)"
:disabled="stopLoading || channelStatus === '已停止' || channelStatus === '未播放'"
class="stop-button"
>
{{ stopLoading ? '停止中...' : '停止' }}
</button>
</div>
<div v-if="playError" class="error">
播放错误: {{ playError }}
</div>
<div v-if="stopError" class="error">
停止错误: {{ stopError }}
</div>
<div v-if="playUrl" class="result-preview">
<h4>播放地址:</h4>
<pre>{{ playUrl }}</pre>
</div>
</div>
<div class="info-box">
<h4>使用说明:</h4>
<p>1. 首先点击"获取通道列表"按钮获取所有可用的国标通道</p>
<p>2. 在通道列表中点击"选择"按钮选择要操作的通道</p>
<p>3. 点击"播放"按钮获取该通道的播放地址播放地址会显示在下方</p>
<p>4. 点击"停止"按钮停止该通道的播放</p>
<p>5. 通道状态会实时显示当前通道的播放状态</p>
<p><strong>注意:</strong> 本页面不直接播放视频流仅提供播放地址的获取和停止功能</p>
</div>
</div>
</template>
<style scoped>
.gb28181-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
h2 {
margin-bottom: 30px;
color: #333;
}
.api-section {
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.api-section h3 {
margin-top: 0;
margin-bottom: 15px;
color: #2196f3;
font-size: 18px;
}
.api-description {
background-color: #f5f5f5;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
}
.api-description p {
margin: 5px 0;
font-size: 14px;
color: #666;
}
.api-description code {
background-color: #e0e0e0;
padding: 2px 6px;
border-radius: 3px;
font-family: 'Courier New', monospace;
font-size: 13px;
color: #d32f2f;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
font-size: 14px;
}
.form-group input[type="text"],
.form-group input[type="number"] {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-group input:focus {
outline: none;
border-color: #2196f3;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 20px;
}
button {
padding: 10px 20px;
background-color: #2196f3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.3s ease;
}
button:hover:not(:disabled) {
background-color: #1976d2;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.stop-button {
background-color: #f44336;
}
.stop-button:hover:not(:disabled) {
background-color: #d32f2f;
}
.select-button {
padding: 5px 15px;
font-size: 12px;
}
.error {
margin-top: 15px;
padding: 12px;
background-color: #ffebee;
color: #c62828;
border-radius: 4px;
font-size: 14px;
}
.channel-list {
margin-top: 20px;
}
.channel-list h4 {
margin-bottom: 10px;
color: #333;
}
.channel-table {
width: 100%;
border-collapse: collapse;
background-color: white;
border-radius: 4px;
overflow: hidden;
border: 1px solid #e0e0e0;
}
.channel-table thead {
background-color: #2196f3;
color: white;
}
.channel-table th,
.channel-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.channel-table th {
font-weight: 600;
font-size: 14px;
}
.channel-table td {
font-size: 13px;
color: #333;
}
.channel-table tbody tr:hover {
background-color: #f5f5f5;
}
.channel-table tbody tr.selected {
background-color: #e3f2fd;
}
.channel-table tbody tr:last-child td {
border-bottom: none;
}
.status-online {
color: #4caf50;
font-weight: 600;
}
.status-offline {
color: #f44336;
font-weight: 600;
}
.selected-channel-info {
background-color: #e3f2fd;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.selected-channel-info h4 {
margin-top: 0;
margin-bottom: 10px;
color: #1976d2;
}
.selected-channel-info p {
margin: 5px 0;
font-size: 14px;
color: #333;
}
.status-display {
margin-bottom: 20px;
}
.status-display h4 {
margin-bottom: 10px;
color: #333;
}
.status-box {
padding: 15px;
border-radius: 4px;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.status-playing {
background-color: #e8f5e9;
color: #2e7d32;
border: 2px solid #4caf50;
}
.status-stopped {
background-color: #f5f5f5;
color: #666;
border: 2px solid #ccc;
}
.status-error {
background-color: #ffebee;
color: #c62828;
border: 2px solid #f44336;
}
.status-loading {
background-color: #fff3e0;
color: #e65100;
border: 2px solid #ff9800;
}
.result-preview {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
}
.result-preview h4 {
margin-top: 0;
margin-bottom: 10px;
color: #333;
}
.result-preview pre {
background-color: #2d2d2d;
color: #f8f8f2;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.5;
margin: 0;
}
.info-box {
margin-top: 20px;
padding: 15px;
background-color: #e3f2fd;
border-radius: 4px;
border-left: 4px solid #2196f3;
}
.info-box h4 {
margin-top: 0;
margin-bottom: 10px;
color: #1976d2;
}
.info-box p {
margin: 8px 0;
font-size: 14px;
color: #333;
}
</style>