64850858
3 years ago
34 changed files with 16251 additions and 690 deletions
@ -0,0 +1,60 @@ |
|||||
|
package com.genersoft.iot.vmp.conf; |
||||
|
|
||||
|
import org.apache.http.HttpRequest; |
||||
|
import org.apache.http.HttpResponse; |
||||
|
import org.mitre.dsmiley.httpproxy.ProxyServlet; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.web.servlet.ServletRegistrationBean; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
|
||||
|
import javax.servlet.ServletException; |
||||
|
import java.io.IOException; |
||||
|
import java.net.ConnectException; |
||||
|
import java.util.Locale; |
||||
|
|
||||
|
|
||||
|
@Configuration |
||||
|
public class ProxyServletConfig { |
||||
|
|
||||
|
private final static Logger logger = LoggerFactory.getLogger(ProxyServletConfig.class); |
||||
|
|
||||
|
@Autowired |
||||
|
private MediaConfig mediaConfig; |
||||
|
|
||||
|
@Bean |
||||
|
public ServletRegistrationBean zlmServletRegistrationBean(){ |
||||
|
String ip = StringUtils.isEmpty(mediaConfig.getWanIp())? mediaConfig.getIp(): mediaConfig.getWanIp(); |
||||
|
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new ZLMProxySerlet(),"/zlm/*"); |
||||
|
servletRegistrationBean.setName("zlm_Proxy"); |
||||
|
servletRegistrationBean.addInitParameter("targetUri", String.format("http://%s:%s", ip, mediaConfig.getHttpPort())); |
||||
|
if (logger.isDebugEnabled()) { |
||||
|
servletRegistrationBean.addInitParameter("log", "true"); |
||||
|
} |
||||
|
return servletRegistrationBean; |
||||
|
} |
||||
|
|
||||
|
class ZLMProxySerlet extends ProxyServlet{ |
||||
|
@Override |
||||
|
protected void handleRequestException(HttpRequest proxyRequest, HttpResponse proxyResonse, Exception e){ |
||||
|
System.out.println(e.getMessage()); |
||||
|
try { |
||||
|
super.handleRequestException(proxyRequest, proxyResonse, e); |
||||
|
} catch (ServletException servletException) { |
||||
|
logger.error("zlm 代理失败: ", e); |
||||
|
} catch (IOException ioException) { |
||||
|
if (ioException instanceof ConnectException) { |
||||
|
logger.error("zlm 连接失败"); |
||||
|
}else { |
||||
|
logger.error("zlm 代理失败: ", e); |
||||
|
} |
||||
|
} catch (RuntimeException exception){ |
||||
|
logger.error("zlm 代理失败: ", e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
@ -1,54 +1,54 @@ |
|||||
package com.genersoft.iot.vmp.media.zlm; |
//package com.genersoft.iot.vmp.media.zlm;
|
||||
|
//
|
||||
import com.genersoft.iot.vmp.conf.MediaConfig; |
//import com.genersoft.iot.vmp.conf.MediaConfig;
|
||||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
//import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value; |
//import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*; |
//import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.HttpClientErrorException; |
//import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.client.RestTemplate; |
//import org.springframework.web.client.RestTemplate;
|
||||
|
//
|
||||
import javax.servlet.http.HttpServletRequest; |
//import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse; |
//import javax.servlet.http.HttpServletResponse;
|
||||
|
//
|
||||
@RestController |
//@RestController
|
||||
@RequestMapping("/zlm") |
//@RequestMapping("/zlm")
|
||||
public class ZLMHTTPProxyController { |
//public class ZLMHTTPProxyController {
|
||||
|
//
|
||||
|
//
|
||||
// private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
|
// // private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
|
||||
|
//
|
||||
@Autowired |
// @Autowired
|
||||
private IRedisCatchStorage redisCatchStorage; |
// private IRedisCatchStorage redisCatchStorage;
|
||||
|
//
|
||||
@Autowired |
// @Autowired
|
||||
private MediaConfig mediaConfig; |
// private MediaConfig mediaConfig;
|
||||
|
//
|
||||
@ResponseBody |
// @ResponseBody
|
||||
@RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8") |
// @RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8")
|
||||
public Object proxy(HttpServletRequest request, HttpServletResponse response){ |
// public Object proxy(HttpServletRequest request, HttpServletResponse response){
|
||||
|
//
|
||||
if (redisCatchStorage.getMediaInfo() == null) { |
// if (redisCatchStorage.getMediaInfo() == null) {
|
||||
return "未接入流媒体"; |
// return "未接入流媒体";
|
||||
} |
// }
|
||||
ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
// ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
||||
String requestURI = String.format("http://%s:%s%s?%s&%s", |
// String requestURI = String.format("http://%s:%s%s?%s&%s",
|
||||
mediaInfo.getLocalIP(), |
// mediaInfo.getLocalIP(),
|
||||
mediaConfig.getHttpPort(), |
// mediaConfig.getHttpPort(),
|
||||
request.getRequestURI().replace("/zlm",""), |
// request.getRequestURI().replace("/zlm",""),
|
||||
mediaInfo.getHookAdminParams(), |
// mediaInfo.getHookAdminParams(),
|
||||
request.getQueryString() |
// request.getQueryString()
|
||||
); |
// );
|
||||
// 发送请求
|
// // 发送请求
|
||||
RestTemplate restTemplate = new RestTemplate(); |
// RestTemplate restTemplate = new RestTemplate();
|
||||
//将指定的url返回的参数自动封装到自定义好的对应类对象中
|
// //将指定的url返回的参数自动封装到自定义好的对应类对象中
|
||||
Object result = null; |
// Object result = null;
|
||||
try { |
// try {
|
||||
result = restTemplate.getForObject(requestURI,Object.class); |
// result = restTemplate.getForObject(requestURI,Object.class);
|
||||
|
//
|
||||
}catch (HttpClientErrorException httpClientErrorException) { |
// }catch (HttpClientErrorException httpClientErrorException) {
|
||||
response.setStatus(httpClientErrorException.getStatusCode().value()); |
// response.setStatus(httpClientErrorException.getStatusCode().value());
|
||||
} |
// }
|
||||
return result; |
// return result;
|
||||
} |
// }
|
||||
} |
//}
|
||||
|
@ -0,0 +1,8 @@ |
|||||
|
package com.genersoft.iot.vmp.service; |
||||
|
|
||||
|
import com.genersoft.iot.vmp.storager.dao.dto.RecordInfo; |
||||
|
import com.github.pagehelper.PageInfo; |
||||
|
|
||||
|
public interface IRecordInfoServer { |
||||
|
PageInfo<RecordInfo> getRecordList(int page, int count); |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package com.genersoft.iot.vmp.service.impl; |
||||
|
|
||||
|
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem; |
||||
|
import com.genersoft.iot.vmp.service.IRecordInfoServer; |
||||
|
import com.genersoft.iot.vmp.storager.dao.RecordInfoDao; |
||||
|
import com.genersoft.iot.vmp.storager.dao.dto.RecordInfo; |
||||
|
import com.github.pagehelper.PageHelper; |
||||
|
import com.github.pagehelper.PageInfo; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Service |
||||
|
public class RecordInfoServerImpl implements IRecordInfoServer { |
||||
|
|
||||
|
@Autowired |
||||
|
private RecordInfoDao recordInfoDao; |
||||
|
|
||||
|
@Override |
||||
|
public PageInfo<RecordInfo> getRecordList(int page, int count) { |
||||
|
PageHelper.startPage(page, count); |
||||
|
List<RecordInfo> all = recordInfoDao.selectAll(); |
||||
|
return new PageInfo<>(all); |
||||
|
} |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package com.genersoft.iot.vmp.storager.dao; |
||||
|
|
||||
|
import com.genersoft.iot.vmp.storager.dao.dto.RecordInfo; |
||||
|
import com.genersoft.iot.vmp.storager.dao.dto.User; |
||||
|
import org.apache.ibatis.annotations.Delete; |
||||
|
import org.apache.ibatis.annotations.Insert; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
import org.apache.ibatis.annotations.Select; |
||||
|
import org.springframework.stereotype.Repository; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Mapper |
||||
|
@Repository |
||||
|
public interface RecordInfoDao { |
||||
|
|
||||
|
@Insert("INSERT INTO recordInfo (app, stream, mediaServerId, createTime, type, deviceId, channelId, name) VALUES" + |
||||
|
"('${app}', '${stream}', '${mediaServerId}', datetime('now','localtime')), '${type}', '${deviceId}', '${channelId}', '${name}'") |
||||
|
int add(RecordInfo recordInfo); |
||||
|
|
||||
|
@Delete("DELETE FROM user WHERE createTime < '${beforeTime}'") |
||||
|
int deleteBefore(String beforeTime); |
||||
|
|
||||
|
@Select("select * FROM recordInfo") |
||||
|
List<RecordInfo> selectAll(); |
||||
|
} |
@ -0,0 +1,133 @@ |
|||||
|
package com.genersoft.iot.vmp.storager.dao.dto; |
||||
|
|
||||
|
/** |
||||
|
* 录像记录 |
||||
|
*/ |
||||
|
public class RecordInfo { |
||||
|
|
||||
|
/** |
||||
|
* ID |
||||
|
*/ |
||||
|
private int id; |
||||
|
|
||||
|
/** |
||||
|
* 应用名 |
||||
|
*/ |
||||
|
private String app; |
||||
|
|
||||
|
/** |
||||
|
* 流ID |
||||
|
*/ |
||||
|
private String stream; |
||||
|
|
||||
|
/** |
||||
|
* 对应的zlm流媒体的ID |
||||
|
*/ |
||||
|
private String mediaServerId; |
||||
|
|
||||
|
/** |
||||
|
* 创建时间 |
||||
|
*/ |
||||
|
private String createTime; |
||||
|
|
||||
|
/** |
||||
|
* 类型 对应zlm的 originType |
||||
|
* unknown = 0, |
||||
|
* rtmp_push=1, |
||||
|
* rtsp_push=2, |
||||
|
* rtp_push=3, |
||||
|
* pull=4, |
||||
|
* ffmpeg_pull=5, |
||||
|
* mp4_vod=6, |
||||
|
* device_chn=7, |
||||
|
* rtc_push=8 |
||||
|
*/ |
||||
|
private int type; |
||||
|
|
||||
|
/** |
||||
|
* 国标录像时的设备ID |
||||
|
*/ |
||||
|
private String deviceId; |
||||
|
|
||||
|
/** |
||||
|
* 国标录像时的通道ID |
||||
|
*/ |
||||
|
private String channelId; |
||||
|
|
||||
|
/** |
||||
|
* 拉流代理录像时的名称 |
||||
|
*/ |
||||
|
private String name; |
||||
|
|
||||
|
public int getId() { |
||||
|
return id; |
||||
|
} |
||||
|
|
||||
|
public void setId(int id) { |
||||
|
this.id = id; |
||||
|
} |
||||
|
|
||||
|
public String getApp() { |
||||
|
return app; |
||||
|
} |
||||
|
|
||||
|
public void setApp(String app) { |
||||
|
this.app = app; |
||||
|
} |
||||
|
|
||||
|
public String getStream() { |
||||
|
return stream; |
||||
|
} |
||||
|
|
||||
|
public void setStream(String stream) { |
||||
|
this.stream = stream; |
||||
|
} |
||||
|
|
||||
|
public String getMediaServerId() { |
||||
|
return mediaServerId; |
||||
|
} |
||||
|
|
||||
|
public void setMediaServerId(String mediaServerId) { |
||||
|
this.mediaServerId = mediaServerId; |
||||
|
} |
||||
|
|
||||
|
public String getCreateTime() { |
||||
|
return createTime; |
||||
|
} |
||||
|
|
||||
|
public void setCreateTime(String createTime) { |
||||
|
this.createTime = createTime; |
||||
|
} |
||||
|
|
||||
|
public int getType() { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
public void setType(int type) { |
||||
|
this.type = type; |
||||
|
} |
||||
|
|
||||
|
public String getDeviceId() { |
||||
|
return deviceId; |
||||
|
} |
||||
|
|
||||
|
public void setDeviceId(String deviceId) { |
||||
|
this.deviceId = deviceId; |
||||
|
} |
||||
|
|
||||
|
public String getChannelId() { |
||||
|
return channelId; |
||||
|
} |
||||
|
|
||||
|
public void setChannelId(String channelId) { |
||||
|
this.channelId = channelId; |
||||
|
} |
||||
|
|
||||
|
public String getName() { |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
public void setName(String name) { |
||||
|
this.name = name; |
||||
|
} |
||||
|
} |
@ -0,0 +1,68 @@ |
|||||
|
package com.genersoft.iot.vmp.vmanager.record; |
||||
|
|
||||
|
import com.genersoft.iot.vmp.conf.MediaConfig; |
||||
|
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig; |
||||
|
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.http.HttpStatus; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
import org.springframework.web.client.HttpClientErrorException; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import java.net.URLDecoder; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("/record_proxy") |
||||
|
public class RecoderProxyController { |
||||
|
|
||||
|
|
||||
|
// private final static Logger logger = LoggerFactory.getLogger(ZLMHTTPProxyController.class);
|
||||
|
|
||||
|
@Autowired |
||||
|
private IRedisCatchStorage redisCatchStorage; |
||||
|
|
||||
|
@Autowired |
||||
|
private MediaConfig mediaConfig; |
||||
|
|
||||
|
@ResponseBody |
||||
|
@RequestMapping(value = "/**/**/**", produces = "application/json;charset=UTF-8") |
||||
|
public Object proxy(HttpServletRequest request, HttpServletResponse response){ |
||||
|
|
||||
|
|
||||
|
String baseRequestURI = request.getRequestURI(); |
||||
|
String[] split = baseRequestURI.split("/"); |
||||
|
if (split.length <= 2) { |
||||
|
response.setStatus(HttpStatus.NOT_FOUND.value()); |
||||
|
return null; |
||||
|
} |
||||
|
String mediaId = split[2]; |
||||
|
if (StringUtils.isEmpty(mediaId)){ |
||||
|
response.setStatus(HttpStatus.BAD_REQUEST.value()); |
||||
|
return null; |
||||
|
} |
||||
|
// 后续改为根据Id获取对应的ZLM
|
||||
|
ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
||||
|
String requestURI = String.format("http://%s:%s%s?%s", |
||||
|
mediaInfo.getLocalIP(), |
||||
|
mediaConfig.getRecordAssistPort(), |
||||
|
baseRequestURI.substring(baseRequestURI.indexOf(mediaId) + mediaId.length()), |
||||
|
URLDecoder.decode(request.getQueryString()) |
||||
|
); |
||||
|
// 发送请求
|
||||
|
RestTemplate restTemplate = new RestTemplate(); |
||||
|
//将指定的url返回的参数自动封装到自定义好的对应类对象中
|
||||
|
Object result = null; |
||||
|
try { |
||||
|
result = restTemplate.getForObject(requestURI,Object.class); |
||||
|
|
||||
|
}catch (HttpClientErrorException httpClientErrorException) { |
||||
|
response.setStatus(httpClientErrorException.getStatusCode().value()); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
} |
@ -0,0 +1,51 @@ |
|||||
|
//package com.genersoft.iot.vmp.vmanager.record;
|
||||
|
//
|
||||
|
//import com.alibaba.fastjson.JSONObject;
|
||||
|
//import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
|
||||
|
//import com.genersoft.iot.vmp.service.IRecordInfoServer;
|
||||
|
//import com.genersoft.iot.vmp.storager.dao.dto.RecordInfo;
|
||||
|
//import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
|
||||
|
//import com.github.pagehelper.PageInfo;
|
||||
|
//import io.swagger.annotations.Api;
|
||||
|
//import io.swagger.annotations.ApiImplicitParam;
|
||||
|
//import io.swagger.annotations.ApiImplicitParams;
|
||||
|
//import io.swagger.annotations.ApiOperation;
|
||||
|
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
//import org.springframework.web.bind.annotation.*;
|
||||
|
//
|
||||
|
//@Api(tags = "云端录像")
|
||||
|
//@CrossOrigin
|
||||
|
//@RestController
|
||||
|
//@RequestMapping("/api/record")
|
||||
|
//public class RecordController {
|
||||
|
//
|
||||
|
// @Autowired
|
||||
|
// private IRecordInfoServer recordInfoServer;
|
||||
|
//
|
||||
|
// @ApiOperation("录像列表查询")
|
||||
|
// @ApiImplicitParams({
|
||||
|
// @ApiImplicitParam(name="page", value = "当前页", required = true, dataTypeClass = Integer.class),
|
||||
|
// @ApiImplicitParam(name="count", value = "每页查询数量", required = true, dataTypeClass = Integer.class),
|
||||
|
// @ApiImplicitParam(name="query", value = "查询内容", dataTypeClass = String.class),
|
||||
|
// })
|
||||
|
// @GetMapping(value = "/app/list")
|
||||
|
// @ResponseBody
|
||||
|
// public Object list(@RequestParam(required = false)Integer page,
|
||||
|
// @RequestParam(required = false)Integer count ){
|
||||
|
//
|
||||
|
// PageInfo<RecordInfo> recordList = recordInfoServer.getRecordList(page - 1, page - 1 + count);
|
||||
|
// return recordList;
|
||||
|
// }
|
||||
|
//
|
||||
|
// @ApiOperation("获取录像详情")
|
||||
|
// @ApiImplicitParams({
|
||||
|
// @ApiImplicitParam(name="recordInfo", value = "录像记录", required = true, dataTypeClass = RecordInfo.class)
|
||||
|
// })
|
||||
|
// @GetMapping(value = "/detail")
|
||||
|
// @ResponseBody
|
||||
|
// public JSONObject list(RecordInfo recordInfo, String time ){
|
||||
|
//
|
||||
|
//
|
||||
|
// return null;
|
||||
|
// }
|
||||
|
//}
|
Binary file not shown.
File diff suppressed because it is too large
@ -0,0 +1,200 @@ |
|||||
|
<template> |
||||
|
<div id="app"> |
||||
|
<el-container> |
||||
|
<el-header> |
||||
|
<uiHeader></uiHeader> |
||||
|
</el-header> |
||||
|
<el-main> |
||||
|
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;"> |
||||
|
<span style="font-size: 1rem; font-weight: bold;">云端录像</span> |
||||
|
<div style="position: absolute; right: 1rem; top: 0.3rem;"> |
||||
|
<el-button v-if="!recordDetail" icon="el-icon-refresh-right" circle size="mini" :loading="loading" @click="getRecordList()"></el-button> |
||||
|
<el-button v-if="recordDetail" icon="el-icon-arrow-left" circle size="mini" @click="backToList()"></el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div v-if="!recordDetail"> |
||||
|
<div style="background-color: #FFFFFF; margin-bottom: 1rem; position: relative; padding: 0.5rem; text-align: left;font-size: 14px;"> |
||||
|
|
||||
|
|
||||
|
节点选择: <el-select size="mini" @change="chooseMediaChange" style="width: 16rem; margin-right: 1rem;" v-model="mediaServer" placeholder="请选择" default-first-option> |
||||
|
<el-option |
||||
|
v-for="item in mediaServerList" |
||||
|
:key="item.generalMediaServerId" |
||||
|
:label="item.generalMediaServerId + '( ' + item.wanIp + ' )'" |
||||
|
:value="item"> |
||||
|
</el-option> |
||||
|
</el-select> |
||||
|
</div> |
||||
|
<!--设备列表--> |
||||
|
<el-table :data="recordList" border style="width: 100%" :height="winHeight"> |
||||
|
<el-table-column prop="app" label="应用名" align="center"> |
||||
|
</el-table-column> |
||||
|
<el-table-column prop="stream" label="流ID" align="center"> |
||||
|
</el-table-column> |
||||
|
<el-table-column prop="time" label="时间" align="center"> |
||||
|
</el-table-column> |
||||
|
<el-table-column label="操作" width="360" align="center" fixed="right"> |
||||
|
<template slot-scope="scope"> |
||||
|
<el-button-group> |
||||
|
<el-button size="mini" icon="el-icon-video-camera-solid" type="primary" @click="showRecordDetail(scope.row)">查看</el-button> |
||||
|
<!-- <el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteRecord(scope.row)">删除</el-button>--> |
||||
|
</el-button-group> |
||||
|
</template> |
||||
|
</el-table-column> |
||||
|
</el-table> |
||||
|
<el-pagination |
||||
|
style="float: right" |
||||
|
@size-change="handleSizeChange" |
||||
|
@current-change="currentChange" |
||||
|
:current-page="currentPage" |
||||
|
:page-size="count" |
||||
|
:page-sizes="[15, 25, 35, 50]" |
||||
|
layout="total, sizes, prev, pager, next" |
||||
|
:total="total"> |
||||
|
</el-pagination> |
||||
|
</div> |
||||
|
<cloud-record-detail ref="cloudRecordDetail" v-if="recordDetail" :recordFile="chooseRecord" :mediaServer="mediaServer" ></cloud-record-detail> |
||||
|
</el-main> |
||||
|
</el-container> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import uiHeader from './UiHeader.vue' |
||||
|
import cloudRecordDetail from './CloudRecordDetail.vue' |
||||
|
export default { |
||||
|
name: 'app', |
||||
|
components: { |
||||
|
uiHeader, cloudRecordDetail |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
mediaServerList: [], // 滅体节点列表 |
||||
|
mediaServer: null, // 媒体服务 |
||||
|
recordList: [], // 设备列表 |
||||
|
chooseRecord: null, // 媒体服务 |
||||
|
|
||||
|
updateLooper: 0, //数据刷新轮训标志 |
||||
|
winHeight: window.innerHeight - 250, |
||||
|
currentPage:1, |
||||
|
count:15, |
||||
|
total:0, |
||||
|
loading: false, |
||||
|
recordDetail: false |
||||
|
|
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
|
||||
|
}, |
||||
|
mounted() { |
||||
|
this.initData(); |
||||
|
}, |
||||
|
destroyed() { |
||||
|
// this.$destroy('videojs'); |
||||
|
}, |
||||
|
methods: { |
||||
|
initData: function() { |
||||
|
// 获取媒体节点列表 |
||||
|
this.getMediaServerList(); |
||||
|
// this.getRecordList(); |
||||
|
}, |
||||
|
currentChange: function(val){ |
||||
|
this.currentPage = val; |
||||
|
this.getRecordList(); |
||||
|
}, |
||||
|
handleSizeChange: function(val){ |
||||
|
this.count = val; |
||||
|
this.getRecordList(); |
||||
|
}, |
||||
|
getMediaServerList: function (){ |
||||
|
let that = this; |
||||
|
this.$axios({ |
||||
|
method: 'get', |
||||
|
url:`/api/server/media_server/list`, |
||||
|
}).then(function (res) { |
||||
|
console.log(res) |
||||
|
that.mediaServerList = res.data; |
||||
|
if (that.mediaServerList.length > 0) { |
||||
|
that.mediaServer = that.mediaServerList[0] |
||||
|
that.getRecordList(); |
||||
|
} |
||||
|
|
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
}, |
||||
|
getRecordList: function (){ |
||||
|
let that = this; |
||||
|
this.$axios({ |
||||
|
method: 'get', |
||||
|
url:`/record_proxy/${that.mediaServer.generalMediaServerId}/api/record/list`, |
||||
|
params: { |
||||
|
page: that.currentPage, |
||||
|
count: that.count |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
console.log(res) |
||||
|
that.total = res.data.data.total; |
||||
|
that.recordList = res.data.data.list; |
||||
|
that.loading = false; |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.loading = false; |
||||
|
}); |
||||
|
}, |
||||
|
backToList(){ |
||||
|
this.recordDetail= false; |
||||
|
}, |
||||
|
chooseMediaChange(val){ |
||||
|
console.log(val) |
||||
|
this.mediaServer = val; |
||||
|
this.getRecordList(); |
||||
|
}, |
||||
|
showRecordDetail(row){ |
||||
|
this.recordDetail = true; |
||||
|
this.chooseRecord = row; |
||||
|
// 查询是否存在录像 |
||||
|
// this.$axios({ |
||||
|
// method: 'delete', |
||||
|
// url:`/record_proxy/api/record/delete`, |
||||
|
// params: { |
||||
|
// page: this.currentPage, |
||||
|
// count: this.count |
||||
|
// } |
||||
|
// }).then((res) => { |
||||
|
// console.log(res) |
||||
|
// this.total = res.data.data.total; |
||||
|
// this.recordList = res.data.data.list; |
||||
|
// }).catch(function (error) { |
||||
|
// console.log(error); |
||||
|
// }); |
||||
|
|
||||
|
}, |
||||
|
deleteRecord(){ |
||||
|
// TODO |
||||
|
let that = this; |
||||
|
this.$axios({ |
||||
|
method: 'delete', |
||||
|
url:`/record_proxy/api/record/delete`, |
||||
|
params: { |
||||
|
page: that.currentPage, |
||||
|
count: that.count |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
console.log(res) |
||||
|
that.total = res.data.data.total; |
||||
|
that.recordList = res.data.data.list; |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
</style> |
@ -0,0 +1,569 @@ |
|||||
|
<template> |
||||
|
<div id="recordDetail"> |
||||
|
<el-container> |
||||
|
<el-aside width="300px"> |
||||
|
<div class="record-list-box-box"> |
||||
|
<el-date-picker size="mini" v-model="chooseDate" :picker-options="pickerOptions" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="dateChange()"></el-date-picker> |
||||
|
<div class="record-list-box" :style="recordListStyle"> |
||||
|
<ul v-if="detailFiles.length >0" class="infinite-list record-list" v-infinite-scroll="infiniteScroll" > |
||||
|
<li v-for="item in detailFiles" class="infinite-list-item record-list-item" @click="chooseFile(item)"> |
||||
|
<el-tag v-if="choosedFile != item"> |
||||
|
<i class="el-icon-video-camera" ></i> |
||||
|
{{ item.substring(0,17)}} |
||||
|
</el-tag> |
||||
|
<el-tag type="danger" v-if="choosedFile == item"> |
||||
|
<i class="el-icon-video-camera" ></i> |
||||
|
{{ item.substring(0,17)}} |
||||
|
</el-tag> |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
<div v-if="detailFiles.length ==0" class="record-list-no-val" >暂无数据</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="record-list-option"> |
||||
|
<el-button size="mini" type="primary" icon="fa fa-cloud-download" style="margin: auto; " title="裁剪合并" @click="drawerOpen"></el-button> |
||||
|
</div> |
||||
|
</el-aside> |
||||
|
<el-main style="padding: 22px"> |
||||
|
<div class="playBox" :style="playerStyle"> |
||||
|
<player ref="recordVideoPlayer" :videoUrl="videoUrl" fluent autoplay :height="true" ></player> |
||||
|
</div> |
||||
|
<div class="player-option-box" > |
||||
|
<el-slider |
||||
|
class="playtime-slider" |
||||
|
v-model="playTime" |
||||
|
id="playtimeSlider" |
||||
|
:disabled="detailFiles.length === 0" |
||||
|
:min="sliderMIn" |
||||
|
:max="sliderMax" |
||||
|
:format-tooltip="playTimeFormat" |
||||
|
@change="playTimeChange" |
||||
|
:marks="playTimeSliderMarks"> |
||||
|
</el-slider> |
||||
|
<div class="slider-val-box"> |
||||
|
<div class="slider-val" v-for="item of detailFiles" :style="'width:' + getDataWidth(item) + '%; left:' + getDataLeft(item) + '%'"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</el-main> |
||||
|
</el-container> |
||||
|
<el-drawer |
||||
|
title="录像下载" |
||||
|
:visible.sync="drawer" |
||||
|
:direction="direction" |
||||
|
:before-close="drawerClose"> |
||||
|
<div class="drawer-box"> |
||||
|
<el-button icon="el-icon-plus" size="mini" type="primary" @click="addTask"></el-button> |
||||
|
<el-tabs type="border-card" style="height: 100%" v-model="tabVal" @tab-click="tabClick"> |
||||
|
<el-tab-pane name="running"> |
||||
|
<span slot="label"><i class="el-icon-scissors"></i>进行中</span> |
||||
|
<ul class="task-list"> |
||||
|
<li class="task-list-item" v-for="item in taskListForRuning"> |
||||
|
<div class="task-list-item-box"> |
||||
|
<span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span> |
||||
|
<el-progress :percentage="(parseFloat(item.percentage)*100).toFixed(1)"></el-progress> |
||||
|
</div> |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
</el-tab-pane> |
||||
|
<el-tab-pane name="ended"> |
||||
|
<span slot="label"><i class="el-icon-finished"></i>已完成</span> |
||||
|
<ul class="task-list"> |
||||
|
<li class="task-list-item" v-for="item in taskListEnded"> |
||||
|
<div class="task-list-item-box" style="height: 2rem;line-height: 2rem;"> |
||||
|
<span>{{ item.startTime.substr(10) }}-{{item.endTime.substr(10)}}</span> |
||||
|
<a class="el-icon-download download-btn" :href="basePath + '/' + item.recordFile" download > |
||||
|
</a> |
||||
|
</div> |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
</el-tab-pane> |
||||
|
</el-tabs> |
||||
|
</div> |
||||
|
</el-drawer> |
||||
|
<el-dialog title="选择时间段" :visible.sync="showTaskBox"> |
||||
|
<el-date-picker |
||||
|
type="datetimerange" |
||||
|
v-model="taskTimeRange" |
||||
|
range-separator="至" |
||||
|
start-placeholder="开始时间" |
||||
|
end-placeholder="结束时间" |
||||
|
format="HH:mm:ss" |
||||
|
placeholder="选择时间范围"> |
||||
|
</el-date-picker> |
||||
|
<el-button size="mini" type="primary" @click="addTaskToServer">确认</el-button> |
||||
|
</el-dialog> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
|
||||
|
<script> |
||||
|
// TODO 根据查询的时间列表设置滑轨的最大值与最小值, |
||||
|
import uiHeader from './UiHeader.vue' |
||||
|
import player from './dialog/easyPlayer.vue' |
||||
|
import moment from 'moment' |
||||
|
export default { |
||||
|
name: 'app', |
||||
|
components: { |
||||
|
uiHeader, player |
||||
|
}, |
||||
|
props: ['recordFile', 'mediaServer', 'dateFiles'], |
||||
|
data() { |
||||
|
return { |
||||
|
basePath: process.env.NODE_ENV === 'development'?`${location.origin}/debug/zlm`:`${location.origin}/zlm`, |
||||
|
dateFilesObj: [], |
||||
|
detailFiles: [], |
||||
|
chooseDate: null, |
||||
|
videoUrl: null, |
||||
|
choosedFile: null, |
||||
|
queryDate: new Date(), |
||||
|
currentPage: 1, |
||||
|
count: 1000000, // TODO 分页导致滑轨视频有效值无法获取完全 |
||||
|
total: 0, |
||||
|
direction: "ltr", |
||||
|
drawer: false, |
||||
|
showTaskBox: false, |
||||
|
taskTimeRange: [], |
||||
|
taskListEnded: [], |
||||
|
taskListForRuning: [], |
||||
|
sliderMIn: 0, |
||||
|
sliderMax: 86400, |
||||
|
autoPlay: true, |
||||
|
taskUpdate: null, |
||||
|
tabVal: "running", |
||||
|
recordListStyle: { |
||||
|
height: this.winHeight + "px", |
||||
|
overflow: "auto", |
||||
|
margin: "10px auto 10px auto" |
||||
|
}, |
||||
|
playerStyle: { |
||||
|
"margin": "auto", |
||||
|
"margin-bottom": "20px", |
||||
|
"height": this.winHeight + "px", |
||||
|
}, |
||||
|
winHeight: window.innerHeight - 240, |
||||
|
playTime: 0, |
||||
|
playTimeSliderMarks: { |
||||
|
0: "00:00", |
||||
|
3600: "01:00", |
||||
|
7200: "02:00", |
||||
|
10800: "03:00", |
||||
|
14400: "04:00", |
||||
|
18000: "05:00", |
||||
|
21600: "06:00", |
||||
|
25200: "07:00", |
||||
|
28800: "08:00", |
||||
|
32400: "09:00", |
||||
|
36000: "10:00", |
||||
|
39600: "11:00", |
||||
|
43200: "12:00", |
||||
|
46800: "13:00", |
||||
|
50400: "14:00", |
||||
|
54000: "15:00", |
||||
|
57600: "16:00", |
||||
|
61200: "17:00", |
||||
|
64800: "18:00", |
||||
|
68400: "19:00", |
||||
|
72000: "20:00", |
||||
|
75600: "21:00", |
||||
|
79200: "22:00", |
||||
|
82800: "23:00", |
||||
|
86400: "24:00", |
||||
|
}, |
||||
|
pickerOptions:{ |
||||
|
cellClassName:(date) =>{ |
||||
|
// 通过显示一个点标识这一天有录像 |
||||
|
let time = moment(date).format('YYYY-MM-DD') |
||||
|
if (this.dateFilesObj[time]){ |
||||
|
return "data-picker-true" |
||||
|
}else { |
||||
|
return "data-picker-false" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
}, |
||||
|
computed: { |
||||
|
|
||||
|
}, |
||||
|
mounted() { |
||||
|
this.recordListStyle.height = this.winHeight + "px"; |
||||
|
this.playerStyle["height"] = this.winHeight + "px"; |
||||
|
// 查询当年有视频的日期 |
||||
|
this.getDateInYear(()=>{ |
||||
|
if (Object.values(this.dateFilesObj).length > 0){ |
||||
|
this.chooseDate = Object.values(this.dateFilesObj)[Object.values(this.dateFilesObj).length -1]; |
||||
|
this.dateChange(); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
destroyed() { |
||||
|
this.$destroy('recordVideoPlayer'); |
||||
|
}, |
||||
|
methods: { |
||||
|
dateChange(){ |
||||
|
this.playTime = 0; |
||||
|
this.detailFiles = []; |
||||
|
this.currentPage = 1; |
||||
|
this.sliderMIn= 0; |
||||
|
this.sliderMax= 86400; |
||||
|
let chooseFullDate = new Date(this.chooseDate + " " + "00:00:00"); |
||||
|
if (chooseFullDate.getFullYear() !== this.queryDate.getFullYear() |
||||
|
|| chooseFullDate.getMonth() !== this.queryDate.getMonth()){ |
||||
|
// this.getDateInYear() |
||||
|
} |
||||
|
this.queryRecordDetails(()=>{ |
||||
|
if (this.detailFiles.length > 0){ |
||||
|
let timeForFile = this.getTimeForFile(this.detailFiles[0]); |
||||
|
let lastTimeForFile = this.getTimeForFile(this.detailFiles[this.detailFiles.length - 1]); |
||||
|
let timeNum = timeForFile[0].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime() |
||||
|
let lastTimeNum = lastTimeForFile[1].getTime() - new Date(this.chooseDate + " " + "00:00:00").getTime() |
||||
|
|
||||
|
this.playTime = parseInt(timeNum/1000) |
||||
|
this.sliderMIn = parseInt(timeNum/1000 - timeNum/1000%(60*60)) |
||||
|
this.sliderMax = parseInt(lastTimeNum/1000 - lastTimeNum/1000%(60*60)) + 60*60 |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
infiniteScroll(){ |
||||
|
if (this.total > this.detailFiles.length) { |
||||
|
this.currentPage ++; |
||||
|
this.queryRecordDetails(); |
||||
|
} |
||||
|
}, |
||||
|
queryRecordDetails: function (callback){ |
||||
|
let that = this; |
||||
|
that.$axios({ |
||||
|
method: 'get', |
||||
|
url:`/record_proxy/${that.mediaServer.generalMediaServerId}/api/record/file/list`, |
||||
|
params: { |
||||
|
app: that.recordFile.app, |
||||
|
stream: that.recordFile.stream, |
||||
|
startTime: that.chooseDate + " 00:00:00", |
||||
|
endTime: that.chooseDate + " 23:59:59", |
||||
|
page: that.currentPage, |
||||
|
count: that.count |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
that.total = res.data.data.total; |
||||
|
that.detailFiles = that.detailFiles.concat(res.data.data.list); |
||||
|
that.loading = false; |
||||
|
if (callback) callback(); |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
that.loading = false; |
||||
|
}); |
||||
|
}, |
||||
|
chooseFile(file){ |
||||
|
this.choosedFile = file; |
||||
|
if (file == null) { |
||||
|
this.videoUrl = ""; |
||||
|
}else { |
||||
|
// TODO 控制列表滚动条 |
||||
|
this.videoUrl = `${this.basePath}/${this.mediaServer.recordAppName}/${this.recordFile.app}/${this.recordFile.stream}/${this.chooseDate}/${this.choosedFile}` |
||||
|
console.log(this.videoUrl) |
||||
|
} |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
getDataWidth(item){ |
||||
|
let timeForFile = this.getTimeForFile(item); |
||||
|
let result = (timeForFile[2])/((this.sliderMax - this.sliderMIn)*1000) |
||||
|
return result*100 |
||||
|
}, |
||||
|
getDataLeft(item){ |
||||
|
let timeForFile = this.getTimeForFile(item); |
||||
|
let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " 00:00:00").getTime() |
||||
|
return parseFloat((differenceTime - this.sliderMIn * 1000)/((this.sliderMax - this.sliderMIn)*1000))*100 ; |
||||
|
}, |
||||
|
playTimeChange(val){ |
||||
|
let minTime = this.getTimeForFile(this.detailFiles[0])[0] |
||||
|
let maxTime = this.getTimeForFile(this.detailFiles[this.detailFiles.length - 1])[1]; |
||||
|
this.chooseFile(null); |
||||
|
let timeMilli = new Date(this.chooseDate + " 00:00:00").getTime() + val*1000 |
||||
|
if (timeMilli >= minTime.getTime() && timeMilli <= maxTime.getTime()){ |
||||
|
for (let i = 0; i < this.detailFiles.length; i++) { |
||||
|
let timeForFile = this.getTimeForFile(this.detailFiles[i]); |
||||
|
if (timeMilli >= timeForFile[0].getTime() && timeMilli <= timeForFile[1].getTime()){ |
||||
|
// TODO 当前未按照实际时间偏移,仅仅是找到对应的文静播放 |
||||
|
this.chooseFile(this.detailFiles[i]) |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
getTimeForFile(file){ |
||||
|
let timeStr = file.substring(0,17); |
||||
|
let starTime = new Date(this.chooseDate + " " + timeStr.split("-")[0]); |
||||
|
let endTime = new Date(this.chooseDate + " " + timeStr.split("-")[1]); |
||||
|
return [starTime, endTime, endTime.getTime() - starTime.getTime()]; |
||||
|
}, |
||||
|
playTimeFormat(val){ |
||||
|
let h = parseInt(val/3600); |
||||
|
let m = parseInt((val - h*3600)/60); |
||||
|
let s = parseInt(val - h*3600 - m*60); |
||||
|
return h + ":" + m + ":" + s |
||||
|
}, |
||||
|
deleteRecord(){ |
||||
|
// TODO |
||||
|
let that = this; |
||||
|
this.$axios({ |
||||
|
method: 'delete', |
||||
|
url:`/record_proxy/${that.mediaServer.generalMediaServerId}/api/record/delete`, |
||||
|
params: { |
||||
|
page: that.currentPage, |
||||
|
count: that.count |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
if (res.data.code == 0) { |
||||
|
that.total = res.data.data.total; |
||||
|
that.recordList = res.data.data.list; |
||||
|
} |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
}, |
||||
|
getDateInYear(callback){ |
||||
|
let that = this; |
||||
|
that.dateFilesObj = {}; |
||||
|
this.$axios({ |
||||
|
method: 'get', |
||||
|
url:`/record_proxy/${that.mediaServer.generalMediaServerId}/api/record/date/list`, |
||||
|
params: { |
||||
|
app: that.recordFile.app, |
||||
|
stream: that.recordFile.stream |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
if (res.data.code === 0) { |
||||
|
if (res.data.data.length > 0) { |
||||
|
for (let i = 0; i < res.data.data.length; i++) { |
||||
|
that.dateFilesObj[res.data.data[i]] = res.data.data[i] |
||||
|
} |
||||
|
|
||||
|
console.log(that.dateFilesObj) |
||||
|
} |
||||
|
} |
||||
|
if(callback)callback(); |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
}, |
||||
|
tabClick(){ |
||||
|
this.getTaskList(this.tabVal === "ended") |
||||
|
}, |
||||
|
drawerClose(){ |
||||
|
this.drawer = false; |
||||
|
if (this.taskUpdate != null) { |
||||
|
window.clearInterval(this.taskUpdate) |
||||
|
} |
||||
|
}, |
||||
|
drawerOpen(){ |
||||
|
this.drawer = true; |
||||
|
if (this.taskUpdate != null) { |
||||
|
window.clearInterval(this.taskUpdate) |
||||
|
} |
||||
|
this.taskUpdate = setInterval(()=>{ |
||||
|
this.getTaskList(this.tabVal === "ended") |
||||
|
}, 1000) |
||||
|
}, |
||||
|
addTask(){ |
||||
|
this.showTaskBox = true; |
||||
|
let startTimeStr = this.chooseDate + " " + this.detailFiles[0].substring(0,8); |
||||
|
let endTimeStr = this.chooseDate + " " + this.detailFiles[this.detailFiles.length - 1].substring(9,17); |
||||
|
this.taskTimeRange[0] = new Date(startTimeStr) |
||||
|
this.taskTimeRange[1] = new Date(endTimeStr) |
||||
|
}, |
||||
|
addTaskToServer(){ |
||||
|
let that = this; |
||||
|
this.$axios({ |
||||
|
method: 'get', |
||||
|
url:`/record_proxy/${that.mediaServer.generalMediaServerId}/api/record/file/download/task/add`, |
||||
|
params: { |
||||
|
app: that.recordFile.app, |
||||
|
stream: that.recordFile.stream, |
||||
|
startTime: moment(this.taskTimeRange[0]).format('YYYY-MM-DD HH:mm:ss'), |
||||
|
endTime: moment(this.taskTimeRange[1]).format('YYYY-MM-DD HH:mm:ss'), |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
if (res.data.code === 0 && res.data.msg === "success") { |
||||
|
that.showTaskBox = false |
||||
|
that.getTaskList(false); |
||||
|
}else { |
||||
|
that.$message.error(res.data.msg); |
||||
|
} |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
}, |
||||
|
handleTabClick() { |
||||
|
this.getTaskList(this.tabVal === "ended") |
||||
|
}, |
||||
|
getTaskList(isEnd){ |
||||
|
let that = this; |
||||
|
this.$axios({ |
||||
|
method: 'get', |
||||
|
url:`/record_proxy/${that.mediaServer.generalMediaServerId}/api/record/file/download/task/list`, |
||||
|
params: { |
||||
|
isEnd: isEnd, |
||||
|
} |
||||
|
}).then(function (res) { |
||||
|
if (res.data.code == 0) { |
||||
|
if (isEnd){ |
||||
|
that.taskListEnded = res.data.data; |
||||
|
}else { |
||||
|
that.taskListForRuning = res.data.data; |
||||
|
} |
||||
|
} |
||||
|
}).catch(function (error) { |
||||
|
console.log(error); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.el-slider__runway { |
||||
|
background-color:rgba(206, 206, 206, 0.47) !important; |
||||
|
} |
||||
|
.el-slider__bar { |
||||
|
background-color: rgba(153, 153, 153, 0) !important; |
||||
|
} |
||||
|
.playtime-slider { |
||||
|
position: relative; |
||||
|
z-index: 100; |
||||
|
} |
||||
|
.data-picker-true{ |
||||
|
|
||||
|
} |
||||
|
.data-picker-true:after{ |
||||
|
content: ""; |
||||
|
position: absolute; |
||||
|
width: 4px; |
||||
|
height: 4px; |
||||
|
background-color: #606060; |
||||
|
border-radius: 4px; |
||||
|
left: 45%; |
||||
|
top: 74%; |
||||
|
|
||||
|
} |
||||
|
.data-picker-false{ |
||||
|
|
||||
|
} |
||||
|
.slider-val-box{ |
||||
|
height: 6px; |
||||
|
position: relative; |
||||
|
top: -22px; |
||||
|
} |
||||
|
.slider-val{ |
||||
|
height: 6px; |
||||
|
background-color: #007CFF; |
||||
|
position: absolute; |
||||
|
} |
||||
|
.record-list-box-box{ |
||||
|
width: 250px; |
||||
|
float: left; |
||||
|
} |
||||
|
.record-list-box{ |
||||
|
overflow: auto; |
||||
|
width: 220px; |
||||
|
list-style: none; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
margin-top: 0px; |
||||
|
padding: 1rem 0; |
||||
|
background-color: #FFF; |
||||
|
margin-top: 10px; |
||||
|
} |
||||
|
.record-list{ |
||||
|
list-style: none; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
background-color: #FFF; |
||||
|
|
||||
|
} |
||||
|
.record-list-no-val { |
||||
|
position: absolute; |
||||
|
color: #9f9f9f; |
||||
|
top: 50%; |
||||
|
left: 110px; |
||||
|
} |
||||
|
.record-list-item{ |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
margin: 0.5rem 0; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
.record-list-option { |
||||
|
width: 10px; |
||||
|
float: left; |
||||
|
margin-top: 39px; |
||||
|
|
||||
|
} |
||||
|
.drawer-box{ |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.task-list{ |
||||
|
list-style: none; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
background-color: #FFF; |
||||
|
} |
||||
|
|
||||
|
.task-list-item{ |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
margin: 1.5rem 0; |
||||
|
} |
||||
|
.task-list-item-box{ |
||||
|
text-align: left; |
||||
|
font-size: 13px; |
||||
|
color: #555; |
||||
|
} |
||||
|
.download-btn{ |
||||
|
display: inline-block; |
||||
|
line-height: 1; |
||||
|
white-space: nowrap; |
||||
|
cursor: pointer; |
||||
|
background: #FFF; |
||||
|
background-color: rgb(255, 255, 255); |
||||
|
border: 1px solid #DCDFE6; |
||||
|
border-top-color: rgb(220, 223, 230); |
||||
|
border-right-color: rgb(220, 223, 230); |
||||
|
border-bottom-color: rgb(220, 223, 230); |
||||
|
border-left-color: rgb(220, 223, 230); |
||||
|
border-top-color: rgb(220, 223, 230); |
||||
|
border-right-color: rgb(220, 223, 230); |
||||
|
border-bottom-color: rgb(220, 223, 230); |
||||
|
border-left-color: rgb(220, 223, 230); |
||||
|
-webkit-appearance: none; |
||||
|
text-align: center; |
||||
|
-webkit-box-sizing: border-box; |
||||
|
box-sizing: border-box; |
||||
|
outline: 0; |
||||
|
margin: 0; |
||||
|
-webkit-transition: .1s; |
||||
|
transition: .1s; |
||||
|
font-weight: 500; |
||||
|
padding: 7px 14px; |
||||
|
font-size: 0.875rem; |
||||
|
border-radius: 4px; |
||||
|
font-size: 0.75rem; |
||||
|
border-radius: 3px; |
||||
|
color: #FFF; |
||||
|
background-color: #409EFF; |
||||
|
border-color: #409EFF; |
||||
|
float: right; |
||||
|
} |
||||
|
.download-btn:hover{ |
||||
|
background: #66b1ff; |
||||
|
border-color: #66b1ff; |
||||
|
color: #FFF; |
||||
|
} |
||||
|
.time-box{ |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,190 @@ |
|||||
|
<template> |
||||
|
<div id="test2"> |
||||
|
<div class="timeQuery" style="width: 100%; height: 300px" id="timeQuery"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
|
||||
|
import * as echarts from 'echarts'; |
||||
|
|
||||
|
export default { |
||||
|
name: "test2", |
||||
|
data() { |
||||
|
return { |
||||
|
}; |
||||
|
}, |
||||
|
mounted() { |
||||
|
var base = +new Date("2021-02-02 00:00:00"); |
||||
|
var oneDay = 24 * 3600 * 1000; |
||||
|
|
||||
|
var data = [[base, 10]]; |
||||
|
|
||||
|
for (var i = 1; i < 24; i++) { |
||||
|
var now = new Date(base += oneDay); |
||||
|
data.push([ |
||||
|
new Date("2021-02-02 " + i+":00:00"), 10 |
||||
|
]); |
||||
|
} |
||||
|
// 基于准备好的dom,初始化echarts实例 |
||||
|
var myChart = echarts.init(document.getElementById('timeQuery')); |
||||
|
let option = { |
||||
|
|
||||
|
toolbox: { |
||||
|
feature: { |
||||
|
dataZoom: { |
||||
|
yAxisIndex: 'none' |
||||
|
}, |
||||
|
restore: {}, |
||||
|
saveAsImage: {} |
||||
|
} |
||||
|
}, |
||||
|
xAxis: { |
||||
|
type: 'time', |
||||
|
boundaryGap: false |
||||
|
}, |
||||
|
yAxis: { |
||||
|
type: 'value', |
||||
|
show: false, |
||||
|
splitLine:{show: false}, //去除网格线 |
||||
|
boundaryGap: [0, '100%'] |
||||
|
}, |
||||
|
dataZoom: [{ |
||||
|
type: 'inside', |
||||
|
start: 0, |
||||
|
end: 20 |
||||
|
}, { |
||||
|
start: 0, |
||||
|
end: 20 |
||||
|
}], |
||||
|
series: [ |
||||
|
{ |
||||
|
name: '模拟数据', |
||||
|
type: 'line', |
||||
|
smooth: false, |
||||
|
symbol: 'none', |
||||
|
areaStyle: {}, |
||||
|
data: data |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
// 绘制图表 |
||||
|
myChart.setOption(option); |
||||
|
}, |
||||
|
methods:{ |
||||
|
getTimeNode(){ |
||||
|
let mine = 20 |
||||
|
let width = document.getElementById("timeQuery").offsetWidth |
||||
|
if (width/20 > 24){ |
||||
|
return 24 |
||||
|
}else if (width/20 > 12) { |
||||
|
return 12 |
||||
|
}else if (width/20 > 6) { |
||||
|
return 6 |
||||
|
} |
||||
|
}, |
||||
|
hoveEvent(event){ |
||||
|
console.log(2222222) |
||||
|
console.log(event) |
||||
|
}, |
||||
|
timeChoose(event){ |
||||
|
console.log(event) |
||||
|
}, |
||||
|
getDataWidth(item){ |
||||
|
let startTime = new Date(item.startTime); |
||||
|
let endTime = new Date(item.endTime); |
||||
|
let result = parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10)) |
||||
|
// console.log(result) |
||||
|
return parseFloat((endTime.getTime() - startTime.getTime())/(24*60*60*10)) |
||||
|
}, |
||||
|
getDataLeft(item){ |
||||
|
let startTime = new Date(item.startTime); |
||||
|
let differenceTime = startTime.getTime() - new Date(item.startTime.substr(0,10) + " 00:00:00").getTime() |
||||
|
let result = differenceTime/(24*60*60*10) |
||||
|
console.log(differenceTime) |
||||
|
console.log(result) |
||||
|
return parseFloat(differenceTime/(24*60*60*10)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.timeQuery{ |
||||
|
width: 96%; |
||||
|
margin-left: 2%; |
||||
|
margin-right: 2%; |
||||
|
margin-top: 20%; |
||||
|
position: absolute; |
||||
|
} |
||||
|
.timeQuery-background{ |
||||
|
height: 16px; |
||||
|
width: 100%; |
||||
|
background-color: #ececec; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
box-shadow: #9d9d9d 0px 0px 10px inset; |
||||
|
} |
||||
|
.timeQuery-data{ |
||||
|
height: 16px; |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
z-index: 11; |
||||
|
} |
||||
|
.timeQuery-data-cell{ |
||||
|
height: 10px; |
||||
|
background-color: #888787; |
||||
|
position: absolute; |
||||
|
z-index: 11; |
||||
|
-webkit-box-shadow: #9d9d9d 0px 0px 10px inset; |
||||
|
margin-top: 3px; |
||||
|
} |
||||
|
.timeQuery-label{ |
||||
|
height: 16px; |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
pointer-events: none; |
||||
|
left: 0; |
||||
|
top: 0; |
||||
|
z-index: 11; |
||||
|
} |
||||
|
.timeQuery-label-cell{ |
||||
|
height: 16px; |
||||
|
position: absolute; |
||||
|
z-index: 12; |
||||
|
width: 0px; |
||||
|
border-right: 1px solid #b7b7b7; |
||||
|
} |
||||
|
.timeQuery-label-cell-label { |
||||
|
width: 23px; |
||||
|
text-align: center; |
||||
|
height: 18px; |
||||
|
margin-left: -10px; |
||||
|
margin-top: -30px; |
||||
|
color: #444; |
||||
|
} |
||||
|
.timeQuery-pointer{ |
||||
|
width: 0px; |
||||
|
height: 18px; |
||||
|
position: absolute; |
||||
|
left: 0; |
||||
|
} |
||||
|
.timeQuery-pointer-content{ |
||||
|
width: 0px; |
||||
|
height: 16px; |
||||
|
position: absolute; |
||||
|
border-right: 3px solid #f60303; |
||||
|
z-index: 14; |
||||
|
} |
||||
|
/*.timeQuery-cell:after{*/ |
||||
|
/* content: "";*/ |
||||
|
/* height: 14px;*/ |
||||
|
/* border: 1px solid #e70303;*/ |
||||
|
/* position: absolute;*/ |
||||
|
/*}*/ |
||||
|
</style> |
Loading…
Reference in new issue