64850858
4 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; |
|||
|
|||
import com.genersoft.iot.vmp.conf.MediaConfig; |
|||
import com.genersoft.iot.vmp.storager.IRedisCatchStorage; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.client.HttpClientErrorException; |
|||
import org.springframework.web.client.RestTemplate; |
|||
|
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
|
|||
@RestController |
|||
@RequestMapping("/zlm") |
|||
public class ZLMHTTPProxyController { |
|||
|
|||
|
|||
// 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){ |
|||
|
|||
if (redisCatchStorage.getMediaInfo() == null) { |
|||
return "未接入流媒体"; |
|||
} |
|||
ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo(); |
|||
String requestURI = String.format("http://%s:%s%s?%s&%s", |
|||
mediaInfo.getLocalIP(), |
|||
mediaConfig.getHttpPort(), |
|||
request.getRequestURI().replace("/zlm",""), |
|||
mediaInfo.getHookAdminParams(), |
|||
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; |
|||
} |
|||
} |
|||
//package com.genersoft.iot.vmp.media.zlm;
|
|||
//
|
|||
//import com.genersoft.iot.vmp.conf.MediaConfig;
|
|||
//import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
|
|||
//import org.springframework.beans.factory.annotation.Autowired;
|
|||
//import org.springframework.beans.factory.annotation.Value;
|
|||
//import org.springframework.web.bind.annotation.*;
|
|||
//import org.springframework.web.client.HttpClientErrorException;
|
|||
//import org.springframework.web.client.RestTemplate;
|
|||
//
|
|||
//import javax.servlet.http.HttpServletRequest;
|
|||
//import javax.servlet.http.HttpServletResponse;
|
|||
//
|
|||
//@RestController
|
|||
//@RequestMapping("/zlm")
|
|||
//public class ZLMHTTPProxyController {
|
|||
//
|
|||
//
|
|||
// // 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){
|
|||
//
|
|||
// if (redisCatchStorage.getMediaInfo() == null) {
|
|||
// return "未接入流媒体";
|
|||
// }
|
|||
// ZLMServerConfig mediaInfo = redisCatchStorage.getMediaInfo();
|
|||
// String requestURI = String.format("http://%s:%s%s?%s&%s",
|
|||
// mediaInfo.getLocalIP(),
|
|||
// mediaConfig.getHttpPort(),
|
|||
// request.getRequestURI().replace("/zlm",""),
|
|||
// mediaInfo.getHookAdminParams(),
|
|||
// 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,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