添加国标设备的获取
This commit is contained in:
parent
cf698f4456
commit
500d5f688a
10
src/App.vue
10
src/App.vue
|
|
@ -3,8 +3,9 @@ import { ref } from 'vue'
|
|||
import WebSocketDemo from './components/WebSocketDemo.vue'
|
||||
import Zlm from './components/Zlm.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')
|
||||
|
||||
|
|
@ -34,12 +35,19 @@ const switchTab = (tab: TabType) => {
|
|||
>
|
||||
WVP
|
||||
</button>
|
||||
<button
|
||||
:class="{ active: activeTab === 'gb28181' }"
|
||||
@click="switchTab('gb28181')"
|
||||
>
|
||||
GB28181 Live
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<WebSocketDemo v-if="activeTab === 'websocket'" />
|
||||
<Zlm v-if="activeTab === 'zlm'" />
|
||||
<Wvp v-if="activeTab === 'wvp'" />
|
||||
<GB2818Live v-if="activeTab === 'gb28181'" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue