通用通道支持看守位控制

This commit is contained in:
panlinlin 2026-06-23 20:38:53 +08:00
parent 391fdf4660
commit b7ba8b9cfa
15 changed files with 271 additions and 12 deletions

View File

@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
@ -599,6 +600,42 @@ public class ChannelFrontEndController {
return result; return result;
} }
@Operation(summary = "看守位设置", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "enabled", description = "是否开启看守位", required = true)
@Parameter(name = "resetTime", description = "自动归位时间间隔(秒)")
@Parameter(name = "presetIndex", description = "调用预置位编号")
@GetMapping("/home_position")
public DeferredResult<WVPResult<String>> homePosition(Integer channelId, Boolean enabled,
@RequestParam(required = false) Integer resetTime,
@RequestParam(required = false) Integer presetIndex){
if (log.isDebugEnabled()) {
log.debug("[通用通道]看守位设置 API调用channelId{} enabled{} resetTime{} presetIndex{}", channelId, enabled, resetTime, presetIndex);
}
CommonGBChannel channel = channelService.getOne(channelId);
Assert.notNull(channel, "通道不存在");
DeferredResult<WVPResult<String>> result = new DeferredResult<>();
result.onTimeout(()->{
WVPResult<String> wvpResult = WVPResult.fail(ErrorCode.ERROR100.getCode(), "请求超时");
result.setResult(wvpResult);
});
ErrorCallback<String> callback = (code, msg, data) -> {
WVPResult<String> wvpResult = new WVPResult<>();
wvpResult.setCode(code);
wvpResult.setMsg(msg);
wvpResult.setData(data);
result.setResult(wvpResult);
};
channelControlService.homePosition(channel, enabled, resetTime, presetIndex, callback);
return result;
}
@Operation(summary = "拉框放大", security = @SecurityRequirement(name = JwtUtils.HEADER)) @Operation(summary = "拉框放大", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "channelId", description = "通道ID", required = true) @Parameter(name = "channelId", description = "通道ID", required = true)
@Parameter(name = "length", description = "播放窗口长度像素值", required = true) @Parameter(name = "length", description = "播放窗口长度像素值", required = true)

View File

@ -17,4 +17,6 @@ public interface IGbChannelControlService {
void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback<String> callback); void auxiliary(CommonGBChannel channel, FrontEndControlCodeForAuxiliary frontEndControlCode, ErrorCallback<String> callback);
void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback); void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback); void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback);
void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback);
} }

View File

@ -22,4 +22,5 @@ public interface IPTZService {
void dragZoomOut(CommonGBChannel channel, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY); void dragZoomOut(CommonGBChannel channel, int length, int width, int midPointX, int midPointY, int lengthX, int lengthY);
void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback);
} }

View File

@ -27,4 +27,6 @@ public interface ISourcePTZService {
void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback); void queryPreset(CommonGBChannel channel, ErrorCallback<List<Preset>> callback);
void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback); void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback);
void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback);
} }

View File

@ -138,4 +138,16 @@ public class GbChannelControlServiceImpl implements IGbChannelControlService {
} }
sourcePTZService.dragZoom(channel, frontEndControlCode, callback); sourcePTZService.dragZoom(channel, frontEndControlCode, callback);
} }
@Override
public void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback) {
log.info("[通用通道] 看守位设置, 类型: {} 编号:{}", channel.getDataType(), channel.getGbDeviceId());
Integer dataType = channel.getDataType();
ISourcePTZService sourcePTZService = sourcePTZServiceMap.get(ChannelDataType.PTZ_SERVICE + dataType);
if (sourcePTZService == null) {
log.error("[通用通道] 类型: {} 不支持看守位设置", dataType);
throw new PlayException(Response.BUSY_HERE, "channel not support");
}
sourcePTZService.homePosition(channel, enabled, resetTime, presetIndex, callback);
}
} }

View File

@ -119,6 +119,23 @@ public class PTZServiceImpl implements IPTZService {
deviceService.dragZoomOut(device, deviceChannel.getDeviceId(), length, width, midPointX, midPointY, lengthX, lengthY); deviceService.dragZoomOut(device, deviceChannel.getDeviceId(), length, width, midPointX, midPointY, lengthX, lengthY);
} }
@Override
public void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback) {
if (channel.getDataType() != ChannelDataType.GB28181) {
log.warn("[INFO 消息] 只有国标通道支持看守位通道ID{}", channel.getGbId());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持");
}
Device device = deviceService.getDevice(channel.getDataDeviceId());
if (device == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备");
}
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
if (deviceChannel == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道");
}
deviceService.homePosition(device, deviceChannel.getDeviceId(), enabled, resetTime, presetIndex, callback);
}
@Override @Override
public void queryPresetList(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) { public void queryPresetList(CommonGBChannel channel, ErrorCallback<List<Preset>> callback) {
if (channel.getDataType() != ChannelDataType.GB28181) { if (channel.getDataType() != ChannelDataType.GB28181) {

View File

@ -349,6 +349,11 @@ public class SourcePTZServiceForGbImpl implements ISourcePTZService {
ptzService.queryPresetList(channel, callback); ptzService.queryPresetList(channel, callback);
} }
@Override
public void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback) {
ptzService.homePosition(channel, enabled, resetTime, presetIndex, callback);
}
@Override @Override
public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom controlCode, ErrorCallback<String> callback) { public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom controlCode, ErrorCallback<String> callback) {
if (controlCode.getCode() == 1) { if (controlCode.getCode() == 1) {

View File

@ -156,6 +156,11 @@ public class SourcePTZServiceForJTImpl implements ISourcePTZService {
callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null);
} }
@Override
public void homePosition(CommonGBChannel channel, Boolean enabled, Integer resetTime, Integer presetIndex, ErrorCallback<String> callback) {
callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null);
}
@Override @Override
public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback) { public void dragZoom(CommonGBChannel channel, FrontEndControlCodeForDragZoom frontEndControlCode, ErrorCallback<String> callback) {
callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null); callback.run(ErrorCode.ERROR486.getCode(), ErrorCode.ERROR486.getMsg(), null);

View File

@ -691,6 +691,14 @@ export function drawThin(data) {
data: data data: data
}) })
} }
export function homePosition(params) {
return request({
method: 'get',
url: '/api/common/channel/front-end/home_position',
params
})
}
export function test() { export function test() {
return request({ return request({
method: 'get', method: 'get',

View File

@ -50,7 +50,8 @@ import {
resumePlayback, resumePlayback,
seekPlayback, speedPlayback, getAllForMap, test, saveLevel, resetLevel, clearThin, thinProgress, drawThin, saveThin, seekPlayback, speedPlayback, getAllForMap, test, saveLevel, resetLevel, clearThin, thinProgress, drawThin, saveThin,
dragZoomIn, dragZoomOut, dragZoomIn, dragZoomOut,
talkStart, talkStop, broadcastStart, broadcastStop talkStart, talkStop, broadcastStart, broadcastStop,
homePosition
} from '@/api/commonChannel' } from '@/api/commonChannel'
const actions = { const actions = {
@ -695,6 +696,17 @@ const actions = {
}) })
}, },
homePosition({ commit }, params) {
return new Promise((resolve, reject) => {
homePosition(params).then(response => {
const { data } = response
resolve(data)
}).catch(error => {
reject(error)
})
})
},
test({ commit }) { test({ commit }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
test().then(response => { test().then(response => {

View File

@ -1,6 +1,6 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 1291092 */ font-family: "iconfont"; /* Project id 1291092 */
src: url('iconfont.woff2?t=1780559263294') format('woff2'); src: url('iconfont.woff2?t=1782216793622') format('woff2');
} }
.iconfont { .iconfont {
@ -11,6 +11,58 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-yuzhidian:before {
content: "\e808";
}
.icon-xunhuan:before {
content: "\e809";
}
.icon-kanshouwei:before {
content: "\e80a";
}
.icon-yushua:before {
content: "\e80b";
}
.icon-mubiaogenzong1:before {
content: "\e80c";
}
.icon-fangkuang:before {
content: "\e807";
}
.icon-jingxiang:before {
content: "\e806";
}
.icon-zuoyoujingxiang:before {
content: "\ea5d";
}
.icon-shangxiajingxiang:before {
content: "\ea5e";
}
.icon-a-18_baojingluxiang:before {
content: "\e805";
}
.icon-mubiaogenzong:before {
content: "\e804";
}
.icon-yuntaijiaodu:before {
content: "\e803";
}
.icon-tubiao_yuntaikongzhi:before {
content: "\e802";
}
.icon-dingshirenwuguanli:before { .icon-dingshirenwuguanli:before {
content: "\e800"; content: "\e800";
} }

Binary file not shown.

View File

@ -0,0 +1,100 @@
<template>
<div>
<el-form label-width="120px" class="guard-form">
<el-form-item label="启用">
<el-switch v-model="enabled" />
</el-form-item>
<el-form-item label="预置位">
<el-select v-model="presetIndex" style="width: 180px" placeholder="选择预置位">
<el-option v-for="p in allPresetList" :key="p.presetId"
:label="p.presetId + '-' + (p.presetName || ('预置点' + p.presetId))"
:value="Number(p.presetId)" />
</el-select>
</el-form-item>
<el-form-item label="自动归位(秒)">
<el-input-number v-model="resetTime" :min="1" :max="999999" controls-position="right" style="width: 180px" />
</el-form-item>
<el-form-item>
<div class="guard-actions">
<el-button @click="loadPresets">刷新</el-button>
<el-button type="primary" :loading="submitting" :disabled="submitting" @click="confirmSave">保存</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'ChPtzGuardConfig',
props: {
channelId: { type: String, default: null }
},
data() {
return {
enabled: false,
presetIndex: null,
resetTime: 10,
allPresetList: [],
submitting: false
}
},
created() {
this.loadPresets()
},
methods: {
loadPresets() {
this.$store.dispatch('commonChanel/queryPreset', this.channelId)
.then(data => {
this.allPresetList = data || []
})
.catch(error => {
console.log('[看守位] 加载预置点列表失败', error)
})
},
confirmSave() {
if (!this.enabled && !this.presetIndex) {
this.$message({ showClose: true, message: '请选择预置位编号', type: 'warning' })
return
}
if (this.resetTime == null || this.resetTime < 1) {
this.$message({ showClose: true, message: '请输入有效的归位时间', type: 'warning' })
return
}
this.submitting = true
const params = {
channelId: this.channelId,
enabled: this.enabled
}
if (this.presetIndex != null) {
params.presetIndex = this.presetIndex
}
if (this.resetTime != null) {
params.resetTime = this.resetTime
}
this.$store.dispatch('commonChanel/homePosition', params)
.then(() => {
this.$message({ showClose: true, message: '保存成功', type: 'success' })
})
.catch(error => {
this.$message({ showClose: true, message: error || '保存失败', type: 'error' })
})
.finally(() => {
this.submitting = false
})
}
}
}
</script>
<style scoped>
.guard-form {
padding: 16px 12px;
max-width: 420px;
}
.guard-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>

View File

@ -5,19 +5,23 @@
<div class="config-sidebar"> <div class="config-sidebar">
<el-menu :default-active="activeTab" @select="handleMenuSelect"> <el-menu :default-active="activeTab" @select="handleMenuSelect">
<el-menu-item index="preset"> <el-menu-item index="preset">
<i class="el-icon-map-location" style="margin-right: 6px" /> <i class="iconfont icon-yuzhidian" style="margin-right: 8px" />
<span>预置点</span> <span>预置点</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="cruise"> <el-menu-item index="cruise">
<i class="el-icon-s-order" style="margin-right: 6px" /> <i class="iconfont icon-xunhuan" style="margin-right: 8px" />
<span>巡航组</span> <span>巡航组</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="scan"> <el-menu-item index="scan">
<i class="iconfont icon-slider-right" style="margin-right: 6px" /> <i class="iconfont icon-slider-right" style="margin-right: 8px" />
<span>线性扫描</span> <span>线性扫描</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="guard">
<i class="iconfont icon-kanshouwei" style="margin-right: 8px" />
<span>看守位</span>
</el-menu-item>
<el-menu-item index="switch"> <el-menu-item index="switch">
<i class="el-icon-s-tools" style="margin-right: 6px" /> <i class="iconfont icon-yushua" style="margin-right: 8px" />
<span>辅助开关</span> <span>辅助开关</span>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
@ -31,6 +35,7 @@
<ptzCruiseConfig v-if="activeTab === 'cruise'" :channel-id="channelId" /> <ptzCruiseConfig v-if="activeTab === 'cruise'" :channel-id="channelId" />
<ptzScanConfig v-if="activeTab === 'scan'" :channel-id="channelId" /> <ptzScanConfig v-if="activeTab === 'scan'" :channel-id="channelId" />
<ptzSwitchConfig v-if="activeTab === 'switch'" :channel-id="channelId" /> <ptzSwitchConfig v-if="activeTab === 'switch'" :channel-id="channelId" />
<ptzGuardConfig v-if="activeTab === 'guard'" :channel-id="channelId" />
</div> </div>
</div> </div>
</div> </div>
@ -43,10 +48,11 @@ import ptzPresetConfig from './common/ptzPresetConfig.vue'
import ptzCruiseConfig from './common/ptzCruiseConfig.vue' import ptzCruiseConfig from './common/ptzCruiseConfig.vue'
import ptzScanConfig from './common/ptzScanConfig.vue' import ptzScanConfig from './common/ptzScanConfig.vue'
import ptzSwitchConfig from './common/ptzSwitchConfig.vue' import ptzSwitchConfig from './common/ptzSwitchConfig.vue'
import ptzGuardConfig from './common/ptzGuardConfig.vue'
export default { export default {
name: 'ChPtzConfig', name: 'ChPtzConfig',
components: { playerPtzPanel, ptzPresetConfig, ptzCruiseConfig, ptzScanConfig, ptzSwitchConfig }, components: { playerPtzPanel, ptzPresetConfig, ptzCruiseConfig, ptzScanConfig, ptzSwitchConfig, ptzGuardConfig },
props: { props: {
channelId: { type: String, default: null } channelId: { type: String, default: null }
}, },

View File

@ -5,23 +5,23 @@
<div class="config-sidebar"> <div class="config-sidebar">
<el-menu :default-active="activeTab" @select="handleMenuSelect"> <el-menu :default-active="activeTab" @select="handleMenuSelect">
<el-menu-item index="preset"> <el-menu-item index="preset">
<i class="el-icon-map-location" style="margin-right: 6px" /> <i class="iconfont icon-yuzhidian" style="margin-right: 8px" />
<span>预置点</span> <span>预置点</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="cruise"> <el-menu-item index="cruise">
<i class="el-icon-s-order" style="margin-right: 6px" /> <i class="iconfont icon-xunhuan" style="margin-right: 8px" />
<span>巡航组</span> <span>巡航组</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="scan"> <el-menu-item index="scan">
<i class="iconfont icon-slider-right" style="margin-right: 6px" /> <i class="iconfont icon-slider-right" style="margin-right: 8px" />
<span>线性扫描</span> <span>线性扫描</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="guard"> <el-menu-item index="guard">
<i class="el-icon-s-home" style="margin-right: 6px" /> <i class="iconfont icon-kanshouwei" style="margin-right: 8px" />
<span>看守位</span> <span>看守位</span>
</el-menu-item> </el-menu-item>
<el-menu-item index="switch"> <el-menu-item index="switch">
<i class="el-icon-s-tools" style="margin-right: 6px" /> <i class="iconfont icon-yushua" style="margin-right: 8px" />
<span>辅助开关</span> <span>辅助开关</span>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>