培训教育

This commit is contained in:
zhaoxinyu
2024-06-26 15:15:32 +08:00
parent ea2670dc15
commit ab39b71127
25 changed files with 3054 additions and 97 deletions
@@ -0,0 +1,234 @@
package io.v.nutz.mobile.trainEducation;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.trainEducation.models.TrainEduVideo;
import io.v.nutz.trainEducation.models.TrainEduVideoWatchRecord;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@At("/mobile/trainEducation")
@Ok("json:full")
public class MTrainEducationController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At
@RequiresAuthentication
@Ok("beetl:/mobile/trainEducation/index.html")
public void index() {
}
@At
@RequiresAuthentication
@Ok("beetl:/mobile/trainEducation/AlbumList.html")
public void AlbumList() {
}
@At
@RequiresAuthentication
@Ok("beetl:/mobile/trainEducation/videoPage.html")
public void videoPage() {
}
/**
* 获取最新的视频
*
* @return
*/
@At
@ViReturn
@RequiresAuthentication
public Object getNewestVideo() {
Sql sql = Sqls.create("""
SELECT
tea.*,
count( tev.id ) AS videoCount,
tet.typeName
FROM
train_edu_album tea
LEFT JOIN train_edu_video tev ON tev.albumId = tea.id
LEFT JOIN train_edu_type tet ON tet.id = tea.typeId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and(new Static("tea.id = (select albumId from train_edu_video order by createdAt desc LIMIT 0,1)"));
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return sql.getResult();
}
/**
* 获取某个类型下的专辑
*
* @param typeId
* @param pageNumber
* @param pageSize
* @return
*/
@At
@ViReturn
@RequiresAuthentication
public Object getAlbumList(@Param(value = "typeId", required = false) String typeId,
@Param("pageNumber") Integer pageNumber,
@Param("pageSize") Integer pageSize) {
Sql sql = Sqls.create("""
SELECT
tea.*,
count( tev.id ) AS videoCount,
tet.typeName
FROM
train_edu_album tea
LEFT JOIN train_edu_video tev ON tev.albumId = tea.id
LEFT JOIN train_edu_type tet ON tet.id = tea.typeId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("tea.typeId", "=", typeId);
cnd.groupBy("tea.id");
sql.setCondition(cnd);
return baseService.listPageMap(pageNumber, pageSize, sql);
}
/**
* 获取某个专辑的详细信息
*
* @param id
* @return
*/
@At
@ViReturn
@RequiresAuthentication
public Object getAlbumInfo(@Param(value = "id", required = false) String id) {
Sql sql = Sqls.create("""
SELECT
tea.*,
count( tev.id ) AS videoCount,
tet.typeName
FROM
train_edu_album tea
LEFT JOIN train_edu_video tev ON tev.albumId = tea.id
LEFT JOIN train_edu_type tet ON tet.id = tea.typeId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("tea.id", "=", id);
cnd.groupBy("tea.id");
sql.setCondition(cnd);
sql.setCallback(Sqls.callback.map());
dao.execute(sql);
return (NutMap) sql.getResult();
}
/**
* 根据专辑获取视频
*
* @param albumId
* @return
*/
@At
@ViReturn
@RequiresAuthentication
public Object getVideoList(@Param(value = "albumId", required = false) String albumId) {
Sql sql = Sqls.create("""
SELECT
tev.*,
tea.title AS albumTitle,
tet.typeName,
tea.cover
FROM
train_edu_video tev
LEFT JOIN train_edu_album tea ON tea.id = tev.albumId
LEFT JOIN train_edu_type tet ON tet.id = tea.typeId
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("tev.albumId", "=", albumId);
cnd.and("tev.disabled", "=", 0);
sql.setCondition(cnd);
return baseService.listMap(sql);
}
/**
* 储存观看记录
*
* @param videoId 视频ID
* @param currentTime 观看时长(秒)
* @return
*/
@At
@ViReturn
@RequiresAuthentication
public Object addWatchRecord(@Param(value = "videoId", required = false) String videoId,
@Param(value = "albumId", required = false) String albumId,
@Param(value = "currentTime", required = false) long currentTime) {
String userId = (String) ShiroUtil.getPrincipalProperty("id");
TrainEduVideo video = dao.fetch(TrainEduVideo.class, Cnd.where("id", "=", videoId));
TrainEduVideoWatchRecord thisVideoRecord = dao.fetch(TrainEduVideoWatchRecord.class, Cnd.where("userId", "=", userId).and("videoId", "=", videoId));
if(Lang.isNotEmpty(thisVideoRecord) && thisVideoRecord.getFinish()){
//已看完的就不要更改了
return null;
}
if (Lang.isEmpty(thisVideoRecord)) {
TrainEduVideoWatchRecord videoWatchRecord = new TrainEduVideoWatchRecord();
videoWatchRecord.setVideoId(videoId);
videoWatchRecord.setWatchDuration(currentTime);
videoWatchRecord.setAlbumId(albumId);
videoWatchRecord.setUserId((String) ShiroUtil.getPrincipalProperty("id"));
videoWatchRecord.setFinish(video.getFileDuration() == currentTime);
dao.insert(videoWatchRecord);
} else {
Chain updateChain = Chain.make("watchDuration", currentTime);
updateChain.add("finish", video.getFileDuration() == currentTime ? 1 : 0);
Cnd cnd = Cnd.where("userId", "=", userId).and("videoId", "=", videoId);
dao.update(TrainEduVideoWatchRecord.class, updateChain, cnd);
}
return null;
}
/**
* 获取我的观看记录
*
* @param albumId
* @return
*/
@At
@ViReturn
@RequiresAuthentication
public Object getMyWatchRecord(@Param(value = "albumId", required = false) String albumId) {
Sql sql = Sqls.create("""
SELECT
tevwr.*
FROM
`train_edu_video_watch_record` tevwr
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.andEX("tevwr.albumId", "=", albumId);
cnd.andEX("tevwr.userId", "=", ShiroUtil.getPrincipalProperty("id"));
sql.setCondition(cnd);
return baseService.listMap(sql);
}
}
@@ -0,0 +1,134 @@
package io.v.nutz.trainEducation.controller;
import cn.hutool.core.util.StrUtil;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.SimpleService;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.trainEducation.models.TrainEduAlbum;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.List;
@IocBean
@At("/platform/trainEdu/album")
@Ok("json:full")
public class TrainEduAlbumController {
@Inject
private Dao dao;
@Inject
private SimpleService simpleService;
@At("")
@Ok("beetl:/platform/trainEdu/album.html")
@RequiresPermissions("trainEdu.album")
public void index() {
}
@At
@ViReturn
@RequiresPermissions("trainEdu.album")
public Object doAdd(TrainEduAlbum trainEduAlbum) {
trainEduAlbum.setUnionId(Vi.getUnionId());
trainEduAlbum.setUnitId(Vi.getUnit().getId());
dao.insert(trainEduAlbum);
return null;
}
@At
@ViReturn
@RequiresPermissions("trainEdu.album")
public Object doEdit(TrainEduAlbum trainEduAlbum) {
dao.update(trainEduAlbum);
return null;
}
@At
@ViReturn
@RequiresPermissions("trainEdu.album")
public Object doDelete(String id) {
dao.clear(TrainEduAlbum.class, Cnd.where("id", "=", id));
return null;
}
@At
@ViReturn
@RequiresPermissions("trainEdu.album")
public Object pageData(PageForm page,
@Param(value = "typeId",required = false) String typeId) {
Sql sql = Sqls.create("""
SELECT
tea.*,
count( tev.id ) AS videoCount,
tet.typeName
FROM
train_edu_album tea
LEFT JOIN train_edu_video tev ON tev.albumId = tea.id
LEFT JOIN train_edu_type tet ON tet.id = tea.typeId
$condition
""");
Cnd cnd = Cnd.NEW();
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and("tea.createType", "=", 1).and("tea.unionId", "=", Vi.getUnionId());
}
if (StrUtil.isNotBlank(page.getSearchName()) && StrUtil.isNotBlank(page.getSearchKeyword())) {
cnd.and(Cnd.likeEX(page.getSearchName(), page.getSearchKeyword()));
}
cnd.andEX("tea.typeId", "=", typeId);
cnd.groupBy("tea.id");
sql.setCondition(cnd);
return simpleService.listPageMap(page.getPageNumber(), page.getPageSize(), sql);
}
@At
@ViReturn
@RequiresAuthentication
public Object getAllAlbum() {
Cnd cnd = Cnd.NEW();
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and(Cnd.exps("createType", "=", 2).or(Cnd.exps("createType", "=", 1).and("unionId", "=", ShiroUtil.getPlatformUid())));
}
List<TrainEduAlbum> list = dao.query(TrainEduAlbum.class, cnd);
for (TrainEduAlbum trainEduAlbum : list) {
dao.fetchLinks(list, "eduType");
}
return list;
}
/**
* 获取专辑根据类型id
*
* @param typeId id类型
* @param pageForm 分页参数
* @return {@link Object}
*/
@At
@ViReturn
@RequiresAuthentication
public Object getAlbumByTypeId(PageForm pageForm,
@Param(value = "typeId", required = false) String typeId) {
Sql sql = Sqls.create("select * from train_edu_album $condition");
Cnd cnd = Cnd.NEW();
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and(Cnd.exps("createType", "=", 2).or(Cnd.exps("createType", "=", 1).and("unionId", "=", ShiroUtil.getPlatformUid())));
}
cnd.andEX("typeId", "=", typeId);
cnd.and(Cnd.likeEX("title", pageForm.getSearchKeyword()));
sql.setCondition(cnd);
return simpleService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
}
@@ -0,0 +1,79 @@
package io.v.nutz.trainEducation.controller;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.SimpleService;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.trainEducation.models.TrainEduType;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
@IocBean
@At("/platform/trainEdu/type")
@Ok("json:full")
public class TrainEduTypeController {
@Inject
private Dao dao;
@Inject
private SimpleService simpleService;
@At("")
@Ok("beetl:/platform/trainEdu/type.html")
@RequiresPermissions("trainEdu.type")
public void index() {
}
@At
@ViReturn
@RequiresPermissions("trainEdu.type")
public Object doAdd(@Param("trainEduType") TrainEduType trainEduType) {
dao.insert(trainEduType);
return null;
}
@At
@ViReturn
@RequiresPermissions("trainEdu.type")
public Object doEdit(@Param("trainEduType") TrainEduType trainEduType) {
dao.update(trainEduType);
return null;
}
@At
@ViReturn
@RequiresPermissions("trainEdu.type")
public Object doDelete(String id) {
dao.clear(TrainEduType.class, Cnd.where("id", "=", id));
return null;
}
/**
* 页面数据
*
* @param pageForm 分页参数
* @return {@link Object}
*/
@At
@ViReturn
@RequiresAuthentication
public Object pageData(PageForm pageForm) {
Sql sql = Sqls.create("""
select
id,
typeName,
files
from train_edu_type
""");
return simpleService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
}
@@ -0,0 +1,187 @@
package io.v.nutz.trainEducation.controller;
import cn.hutool.core.util.StrUtil;
import cn.wizzer.framework.base.Result;
import com.qcloud.vod.model.VodUploadResponse;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.vod.v20180717.models.DeleteMediaResponse;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.base.utils.PageUtil;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.trainEducation.models.TrainEduVideo;
import io.v.nutz.trainEducation.util.TencentVideoService;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.upload.TempFile;
import org.nutz.mvc.upload.UploadAdaptor;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@IocBean
@At("/platform/trainEdu/video")
@Ok("json:full")
public class TrainEduVideoController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("")
@Ok("beetl:/platform/trainEdu/video.html")
@RequiresPermissions("trainEdu.video")
public void index() {
}
@At
@ViReturn
@RequiresAuthentication
@RequiresPermissions("trainEdu.video")
public Object pageData(PageForm page, @Param(value = "albumId", required = false) String albumId) {
Sql sql = Sqls.create("""
SELECT
tev.*,
tea.title AS albumTitle,
u.username AS createUserName,
FROM_UNIXTIME(tev.createdAt/1000,'%Y-%m-%d %H:%i:%s') AS createTime
FROM
train_edu_video tev
LEFT JOIN train_edu_album tea ON tea.id = tev.albumId
LEFT JOIN sys_user u on u.id = tev.createdBy
$condition
""");
Cnd cnd = Cnd.NEW();
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and("tea.createType", "=", 1).and("tea.unionId", "=", Vi.getUnionId());
}
cnd.andEX("tev.albumId", "=", albumId);
if (StrUtil.isNotBlank(page.getSearchName()) && StrUtil.isNotBlank(page.getSearchKeyword())) {
cnd.and(Cnd.likeEX(page.getSearchName(), page.getSearchKeyword()));
}
if (StrUtil.isAllNotBlank(page.getPageOrderName(), page.getPageOrderBy())) {
cnd.orderBy(page.getPageOrderName(), PageUtil.getOrder(page.getPageOrderBy()));
}
sql.setCondition(cnd);
return baseService.listPageMap(page.getPageNumber(), page.getPageSize(), sql);
}
@At
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
@POST
@ViReturn
@RequiresPermissions("trainEdu.video")
public Object doAdd(@Param("file") TempFile file, @Param("data") String data) {
TrainEduVideo trainEduVideo = Json.fromJson(TrainEduVideo.class, data);
try {
VodUploadResponse response = TencentVideoService.uploadFile(file.getFile(), TencentVideoService.trainEducationClassId);
String fileId = response.getFileId();
String mediaUrl = response.getMediaUrl();
trainEduVideo.setFileSize(TencentVideoService.getFileLength(file.getFile()));
trainEduVideo.setFileDuration(TencentVideoService.getFileDuration(file.getFile()));
trainEduVideo.setMediaUrl(mediaUrl);
trainEduVideo.setFileId(fileId);
dao.insert(trainEduVideo);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 编辑文件信息
*
* @param file
* @param data
* @return
*/
@At
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
@POST
@ViReturn
@RequiresPermissions("trainEdu.video")
public Object doEdit(@Param("file") TempFile file, @Param("data") String data) {
TrainEduVideo trainEduVideo = Json.fromJson(TrainEduVideo.class, data);
//有新上传的文件采取删除旧文件
if (file != null) {
try {
DeleteMediaResponse response = TencentVideoService.deleteFile(trainEduVideo.getFileId(), TencentVideoService.subAppId);
VodUploadResponse uploadResponse = TencentVideoService.uploadFile(file.getFile(), TencentVideoService.trainEducationClassId);
String fileId = uploadResponse.getFileId();
String mediaUrl = uploadResponse.getMediaUrl();
trainEduVideo.setFileSize(TencentVideoService.getFileLength(file.getFile()));
trainEduVideo.setFileDuration(TencentVideoService.getFileDuration(file.getFile()));
trainEduVideo.setMediaUrl(mediaUrl);
trainEduVideo.setFileId(fileId);
dao.updateIgnoreNull(trainEduVideo);
} catch (Exception e) {
e.printStackTrace();
}
} else {
dao.updateIgnoreNull(trainEduVideo);
}
return null;
}
@At
@ViReturn
@RequiresPermissions("trainEdu.video")
public Object doDelete(String id) {
TrainEduVideo eduVideo = dao.fetch(TrainEduVideo.class, Cnd.where("id", "=", id));
String fileId = eduVideo.getFileId();
try {
DeleteMediaResponse response = TencentVideoService.deleteFile(fileId, TencentVideoService.subAppId);
System.out.println(response);
dao.delete(eduVideo);
return Result.success();
} catch (TencentCloudSDKException e) {
e.printStackTrace();
return Result.error();
}
}
@At
@ViReturn
@RequiresAuthentication
public Object getVideoList(@Param("albumId") String albumId) {
List<TrainEduVideo> list = dao.query(TrainEduVideo.class, Cnd.where("albumId", "=", albumId));
List<String> fileIdsList = list.stream().map(TrainEduVideo::getFileId).collect(Collectors.toList());
String[] fileIdArray = fileIdsList.toArray(new String[fileIdsList.size()]);
List<HashMap<String, String>> mediaCoverInfos = TencentVideoService.getMediaCoverInfo(fileIdArray);
for (TrainEduVideo trainEduVideo : list) {
Optional<HashMap<String, String>> optionalHashMap = mediaCoverInfos.stream().filter(v -> v.get("fileId").equals(trainEduVideo.getFileId())).findFirst();
optionalHashMap.ifPresent(stringStringHashMap -> trainEduVideo.setCoverUrl(stringStringHashMap.get("coverUrl")));
}
return list;
}
@At
@ViReturn
@RequiresAuthentication
public Object getDescribeMediaInfos(@Param("fileId") String fileId) {
return TencentVideoService.getMediaCoverInfo(new String[]{fileId});
}
}
@@ -0,0 +1,32 @@
package io.v.nutz.trainEducation.controller;
import io.v.nutz.base.service.BaseService;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* @Author JyuHsin
* @Date 2022/8/11
* @Description 视频学习
*/
@IocBean
@At("/platform/trainEdu/study")
@Ok("json:full")
public class TrainStudyController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("")
@Ok("beetl:/platform/trainEdu/study.html")
@RequiresPermissions("trainEdu.study")
public void index() {
}
}
@@ -0,0 +1,103 @@
package io.v.nutz.trainEducation.controller.statistics;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
/**
* 按工会来分析学习参与度
*/
@IocBean
@At("/platform/trainEdu/statistics/union")
@Ok("json:full")
public class TrainEduStatisticsUnionController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("")
@Ok("beetl:/platform/trainEdu/statistics/union.html")
@RequiresPermissions("trainEdu.statistics.union")
public void index() {
}
@At
@ViReturn
@RequiresPermissions("trainEdu.statistics.union")
public Object pageData(@Param(value = "searchType", required = false) String searchType) {
Sql sql = null;
if (searchType.equals("") || searchType.equals("按分工会分析")) {
Cnd cnd = Cnd.NEW();
sql = Sqls.create("""
SELECT
gh.id,
gh.unionname AS unionName,
gh.unioncode AS unionCode,
COUNT(u.id) AS unionSum,
COUNT(DISTINCT ( tevwr.userId )) AS studyNum
FROM
sys_union gh
LEFT JOIN `user` u ON u.unionid = gh.id
LEFT JOIN train_edu_video_watch_record tevwr ON tevwr.userId = u.id
LEFT JOIN train_edu_album tea ON tea.id = tevwr.albumId
$condition
GROUP BY
gh.id
ORDER BY
gh.unioncode ASC
""");
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and("gh.id", "=", Vi.getUnionId());
cnd.and(new Static("IF(tea.createType = 1, u.unionId = tea.unionId , 1=1)"));
}
sql.setCondition(cnd);
} else {
Cnd cnd = Cnd.NEW();
sql = Sqls.create("""
SELECT
dw.id,
dw.`name` AS unitName,
dw.unitcode AS unitCode,
COUNT( u.id ) AS unionSum,
COUNT(DISTINCT ( tevwr.userId )) AS studyNum
FROM
sys_unit dw
LEFT JOIN `user` u ON u.unitid = dw.id
LEFT JOIN train_edu_video_watch_record tevwr ON tevwr.userId = u.id
LEFT JOIN train_edu_album tea ON tea.id = tevwr.albumId
$condition
GROUP BY
dw.id
ORDER BY
dw.unitcode ASC
""");
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and("dw.id", "=", Vi.getUnit().getId());
cnd.and(new Static("IF(tea.createType = 1, u.unitId = tea.unitId , 1=1)"));
}
sql.setCondition(cnd);
}
Pagination pagination = new Pagination();
pagination.setList(baseService.listMap(sql));
return pagination;
}
}
@@ -0,0 +1,103 @@
package io.v.nutz.trainEducation.controller.statistics;
import cn.wizzer.framework.page.Pagination;
import io.v.nutz.annontation.ViReturn;
import io.v.nutz.base.service.BaseService;
import io.v.nutz.base.utils.Vi;
import io.v.nutz.modules.models.PageForm;
import io.v.nutz.web.commons.utils.ShiroUtil;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import java.util.List;
/**
* 统计各视频学习人数
*/
@IocBean
@At("/platform/trainEdu/statistics/video")
@Ok("json:full")
public class TrainEduStatisticsVideoController {
@Inject
private Dao dao;
@Inject
private BaseService baseService;
@At("")
@Ok("beetl:/platform/trainEdu/statistics/video.html")
@RequiresPermissions("trainEdu.statistics.video")
public void index() {
}
@At
@ViReturn
@RequiresPermissions("trainEdu.statistics.video")
public Object pageData() {
Sql sql = Sqls.create("""
SELECT
tea.*,
tet.typeName,
( SELECT sum( fileDuration ) FROM train_edu_video WHERE albumId = tea.id ) AS sumDuration,
( SELECT count( DISTINCT ( userid )) FROM train_edu_video_watch_record WHERE albumId = tea.id ) AS studyNum
FROM
train_edu_album tea
LEFT JOIN train_edu_type tet ON tet.id = tea.typeId\s
$condition
""");
Cnd cnd = Cnd.NEW();
if(!ShiroUtil.hasAnyRoles("sysadmin, A06")) {
cnd.and("tea.createType", "=", 1).and("tea.unionId", "=", Vi.getUnionId());
}
sql.setCondition(cnd);
List list = baseService.listMap(sql);
Pagination pagination = new Pagination();
pagination.setList(list);
return pagination;
}
/**
* 统计每个课程每个人的学习时常
*
* @param pageForm
* @param albumId
* @return
*/
@At
@ViReturn
@RequiresPermissions("trainEdu.statistics.video")
public Object getWatchDuration(PageForm pageForm,
@Param(value = "albumId", required = false) String albumId) {
Sql sql = Sqls.create("""
SELECT
u.username,
u.loginname,
u.unitname,
u.unionname,
sum( tevwr.watchDuration ) AS studyDuration
FROM
`train_edu_video_watch_record` tevwr
LEFT JOIN train_edu_album tea on tea.id = tevwr.albumId
LEFT JOIN `user` u ON u.id = tevwr.userId
WHERE
tevwr.albumId = @albumId
AND IF(tea.createType = 1, u.unionId = tea.unionId , 1=1)
GROUP BY
tevwr.userId
""");
sql.setParam("albumId", albumId);
return baseService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
}
@@ -0,0 +1,60 @@
package io.v.nutz.trainEducation.models;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.List;
@Table
@Data
public class TrainEduAlbum {
@Name
@PrevInsert(uu32 = true)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("视频总标题")
private String title;
@Column
@ColDefine(type = ColType.TEXT)
@Comment("视频简介")
private String description;
@Column
@ColDefine(type = ColType.VARCHAR, width = 200)
@Comment("视频封面")
private String cover;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("视频类型")
private String typeId;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
@Comment("视频标签")
private List<String> tags;
@One(field = "typeId")
private TrainEduType eduType;
@Column
@ColDefine(type = ColType.INT)
@Comment("创建类型,1.分工会, 2.校工会")
private Integer createType;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("工会id")
private String unionId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("单位id")
private String unitId;
}
@@ -0,0 +1,31 @@
package io.v.nutz.trainEducation.models;
import io.v.nutz.sys.models.Sys_file;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.List;
@Table
@Data
public class TrainEduType {
@Name
@PrevInsert(uu32 = true)
@ColDefine(type = ColType.VARCHAR, width = 32)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
private String typeName;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
private String icon;
@Column
@ColDefine(type = ColType.MYSQL_JSON)
private List<Sys_file> files;
}
@@ -0,0 +1,62 @@
package io.v.nutz.trainEducation.models;
import io.v.nutz.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.nutz.dao.entity.annotation.*;
import org.nutz.dao.interceptor.annotation.PrevInsert;
@EqualsAndHashCode(callSuper = true)
@Table
@Data
public class TrainEduVideo extends BaseModel {
@Name
@PrevInsert(uu32 = true)
private String id;
@Column
@ColDefine(type = ColType.VARCHAR, width = 15)
@Comment("小标题")
private String title;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("albumId")
private String albumId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("视频地址")
private String mediaUrl;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("封面地址")
private String coverUrl;
@Column
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("视频Id")
private String fileId;
@Column
@ColDefine(type = ColType.INT)
@Comment("排序编号")
private int sortNo;
@Column
@Comment("是否禁用")
@ColDefine(type = ColType.BOOLEAN)
private boolean disabled;
@Column
@Comment("视频大小")
@ColDefine(type = ColType.VARCHAR)
private String fileSize;
@Column
@Comment("视频时常")
private Long fileDuration;
}
@@ -0,0 +1,37 @@
package io.v.nutz.trainEducation.models;
import lombok.Data;
import org.nutz.dao.entity.annotation.*;
/**
* 视频播放记录
*/
@Table
@Data
public class TrainEduVideoWatchRecord {
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("观看用户")
private String userId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("观看视频Id")
private String videoId;
@Column
@ColDefine(type = ColType.VARCHAR, width = 32)
@Comment("观看视频Id")
private String albumId;
@Column
@Comment("观看时长(秒)")
private Long watchDuration;
@Column
@ColDefine(type = ColType.BOOLEAN)
@Comment("是否看完")
private Boolean finish;
}
@@ -0,0 +1,153 @@
package io.v.nutz.trainEducation.util;
import com.qcloud.vod.VodUploadClient;
import com.qcloud.vod.model.VodUploadRequest;
import com.qcloud.vod.model.VodUploadResponse;
import com.tencentcloudapi.common.Credential;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import com.tencentcloudapi.vod.v20180717.VodClient;
import com.tencentcloudapi.vod.v20180717.models.DeleteMediaRequest;
import com.tencentcloudapi.vod.v20180717.models.DeleteMediaResponse;
import com.tencentcloudapi.vod.v20180717.models.DescribeMediaInfosRequest;
import com.tencentcloudapi.vod.v20180717.models.DescribeMediaInfosResponse;
import org.nutz.lang.Lang;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaInfo;
import ws.schild.jave.MultimediaObject;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
public class TencentVideoService {
/**
* 应用id
*/
public static final Long subAppId = 1500014618L;
/**
* 培训教育类别id
*/
public static final Long trainEducationClassId = 906527L;
private static final String SECRETID = "AKIDsdHpFTTFwdP5czBAB4pdSJEyhVHaIvNc";
private static final String SECRETKEY = "8ELFE5JSRCwKZr5cEuXOUTKlSCwlhgeO";
/**
* 上传文件
*
* @param file
* @param classId
* @return
* @throws Exception
*/
public static VodUploadResponse uploadFile(File file, long classId) throws Exception {
VodUploadClient client = new VodUploadClient(SECRETID, SECRETKEY);
VodUploadRequest request = new VodUploadRequest();
request.setMediaFilePath(file.getPath());
request.setProcedure("captureCover");
request.setSubAppId(TencentVideoService.subAppId);
request.setClassId(classId);
VodUploadResponse response = client.upload("ap-guangzhou", request);
return response;
}
/**
* 删除文件
*
* @param fileId
* @param classId
* @return
* @throws TencentCloudSDKException
*/
public static DeleteMediaResponse deleteFile(String fileId, long classId) throws TencentCloudSDKException {
Credential cred = new Credential(SECRETID, SECRETKEY);
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("vod.tencentcloudapi.com");
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
VodClient client = new VodClient(cred, "", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
DeleteMediaRequest req = new DeleteMediaRequest();
req.setFileId(fileId);
req.setSubAppId(classId);
DeleteMediaResponse resp = client.DeleteMedia(req);
return resp;
}
/**
* 获取文件大小 MB
*
* @param file
* @return
*/
public static String getFileLength(File file) {
long fileLength = file.length();
return Math.round((double) fileLength / 1024 / 1024) + "MB";
}
/**
* 获取视频文件时常
*
* @param file
* @return
*/
public static long getFileDuration(File file) throws EncoderException {
MultimediaObject multimediaObject = new MultimediaObject(file);
MultimediaInfo multimediaInfo = multimediaObject.getInfo();
return multimediaInfo.getDuration()/1000;
}
/**
* 得到描述媒体信息
*
* @param fileIds 文件id
* @return {@link Object}
*/
public static List<HashMap<String, String>> getMediaCoverInfo(String[] fileIds) {
try {
// 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密
// 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取
Credential cred = new Credential(SECRETID, SECRETKEY);
// 实例化一个http选项,可选的,没有特殊需求可以跳过
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint("vod.tencentcloudapi.com");
// 实例化一个client选项,可选的,没有特殊需求可以跳过
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
// 实例化要请求产品的client对象,clientProfile是可选的
VodClient client = new VodClient(cred, "", clientProfile);
// 实例化一个请求对象,每个接口都会对应一个request对象
DescribeMediaInfosRequest req = new DescribeMediaInfosRequest();
req.setSubAppId(TencentVideoService.subAppId);
req.setFileIds(fileIds);
req.setFilters(new String[]{"basicInfo"});
// 返回的resp是一个DescribeMediaInfosResponse的实例,与请求对象对应
DescribeMediaInfosResponse resp = client.DescribeMediaInfos(req);
// 输出json格式的字符串回包
System.out.println(DescribeMediaInfosResponse.toJsonString(resp));
if (Lang.isNotEmpty(resp.getMediaInfoSet())) {
return Arrays.stream(resp.getMediaInfoSet()).map(v -> new HashMap<String, String>(2) {{
put("coverUrl", v.getBasicInfo().getCoverUrl());
put("fileId", v.getFileId());
}}).collect(Collectors.toList());
}
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
@@ -0,0 +1,59 @@
package io.v.nutz.trainEducation.util;
import lombok.Setter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
@Setter
public class TencentVideoSignature {
private String secretId = "1400611918";
private String secretKey = "3563986157db18e7dda93ab7a9b64817a15f9983499558efd592458e39297bbf";
private long currentTime;
private int random;
private int signValidDuration;
private static final String HMAC_ALGORITHM = "HmacSHA1"; //签名算法
private static final String CONTENT_CHARSET = "UTF-8";
private static byte[] byteMerger(byte[] byte1, byte[] byte2) {
byte[] byte3 = new byte[byte1.length + byte2.length];
System.arraycopy(byte1, 0, byte3, 0, byte1.length);
System.arraycopy(byte2, 0, byte3, byte1.length, byte2.length);
return byte3;
}
// 获取签名
public String getUploadSignature() throws Exception {
String strSign = "";
String contextStr = "";
// 生成原始参数字符串
long endTime = (currentTime + signValidDuration);
contextStr += "secretId=" + java.net.URLEncoder.encode(secretId, "utf8");
contextStr += "&currentTimeStamp=" + currentTime;
contextStr += "&expireTime=" + endTime;
contextStr += "&random=" + random;
try {
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
SecretKeySpec secretKey = new SecretKeySpec(this.secretKey.getBytes(CONTENT_CHARSET), mac.getAlgorithm());
mac.init(secretKey);
byte[] hash = mac.doFinal(contextStr.getBytes(CONTENT_CHARSET));
byte[] sigBuf = byteMerger(hash, contextStr.getBytes("utf8"));
strSign = base64Encode(sigBuf);
strSign = strSign.replace(" ", "").replace("\n", "").replace("\r", "");
} catch (Exception e) {
throw e;
}
return strSign;
}
private String base64Encode(byte[] buffer) {
Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(buffer);
}
}
+19
View File
@@ -315,6 +315,7 @@ layout("/mobile/platform.html"){
</div>
<div v-if="paramType === '大病互助'"></div>
<div v-if="paramType === '建言献策'">
<div class="van-sidebar-item van-sidebar-item&#45;&#45;select">
疗休养
@@ -331,6 +332,24 @@ layout("/mobile/platform.html"){
</van-grid-item>
</van-grid>
</div>
<div v-if="paramType === '培训教育'">
<div class="van-sidebar-item van-sidebar-item&#45;&#45;select">
培训教育
</div>
<van-grid :border="false" :column-num="3">
<van-grid-item url="/platform/mobile/theRapyRecuperation/index">
<image src="/assets/mobile/svg/proposal/cx.svg"></image>
<span class="icon_text"> 疗休养</span>
</van-grid-item>
<van-grid-item url="/platform/mobile/theRapyRecuperation/myRecuperation">
<image src="/assets/mobile/svg/proposal/cx.svg"></image>
<span class="icon_text"> 我的疗休养</span>
</van-grid-item>
</van-grid>
</div>
</div>
<script src="https://res.wx.qq.com/open/js/cloudbase/1.1.0/cloud.js"></script>
+12 -2
View File
@@ -55,11 +55,21 @@ layout("/mobile/platform.html"){
<van-grid-item>
<div class="item_div" style="background-color: rgb(155,130,239)">
<div>
<van-image height="32" width="40" src="/assets/platform/img/quickentry/jcbd.png" @click="loginOut"></van-image>
<van-image height="32" width="40" src="/assets/platform/img/quickentry/jcbd.png"
@click="location.href='/mobile/trainEducation/index'"></van-image>
</div>
<span class="icon_text">解除绑定</span>
</div>
</van-grid-item>
<van-grid-item>
<div class="item_div" style="background-color: rgb(155,130,239)">
<div>
<van-image height="32" width="40" src="/assets/platform/img/quickentry/jcbd.png" @click="loginOut"></van-image>
</div>
<span class="icon_text">培训教育</span>
</div>
</van-grid-item>
</van-grid>
<!--<div>
@@ -113,7 +123,7 @@ layout("/mobile/platform.html"){
text: '建言献策',
isOpen: true,
backGroundColor: '#2fbabf'
},
}
]
}
},
@@ -34,6 +34,8 @@ layout("/mobile/platform.html"){
</style>
<div id="app" v-cloak>
<m-page-loading v-if="mLoading"></m-page-loading>
<van-nav-bar
title="视频资料"
left-arrow
@@ -72,7 +74,7 @@ layout("/mobile/platform.html"){
style="margin-right: 10px"
width="90"
height="90"
:src="'${AppFileDomain!}'+o.cover"
:src="FILE_DOMAIN+'/fileStreamPreview?id='+o.cover"
></van-image>
<div class="list-item">
<div style="font-size: 14px">{{o.title}}</div>
@@ -86,11 +88,12 @@ layout("/mobile/platform.html"){
</div>
</div>
</van-list>
<van-empty v-if="mLoading==false&&tableData.length==0" class="custom-image" image="/none.svg"
description="暂无数据">
</van-empty>
</div>
<van-empty v-if="mLoading==false&&tableData.length==0" class="custom-image" image="/none.svg"
description="暂无数据">
</van-empty>
</div>
@@ -112,10 +115,7 @@ layout("/mobile/platform.html"){
},
methods: {
async pageData() {
const toast = vant.Toast.loading({
message: '加载中...',
forbidClick: true,
});
this.mLoading = true
const {data} = await $.get('/mobile/trainEducation/getAlbumList', this.pageForm)
setTimeout(() => {
this.tableData = this.tableData.concat(data.list)
@@ -124,8 +124,7 @@ layout("/mobile/platform.html"){
} else {
this.pageForm.pageNumber++
}
this.loading = false
toast.close()
this.mLoading = false
this.pageForm.totalCount = data.totalCount
}, 300)
},
@@ -147,7 +146,7 @@ layout("/mobile/platform.html"){
async created() {
this.videoTypeList = await getVideoType()
const typeId = this.GetQueryString("id")
const type = this.videoTypeList.find(v => parseInt(v.id) === parseInt(typeId))
const type = this.videoTypeList.find(v => v.id === typeId)
this.typeName = type.typeName
if (typeId) {
this.$set(this.pageForm, 'typeId', typeId)
@@ -159,4 +158,4 @@ layout("/mobile/platform.html"){
<!--#
}
#-->
#-->
@@ -32,6 +32,7 @@ layout("/mobile/platform.html"){
left-arrow
placeholder
fixed
@click-left="history.back()"
></van-nav-bar>
<van-swiper>
@@ -47,7 +48,7 @@ layout("/mobile/platform.html"){
<van-grid-item v-for="item in videoTypeList"
:url="'/mobile/trainEducation/AlbumList?id=' + item.id"
:key="item.id"
:icon="'${AppFileDomain!}'+item.icon"
:icon="FILE_DOMAIN+'/fileStreamPreview?id='+item.icon"
:text="item.typeName"></van-grid-item>
</van-grid>
@@ -63,7 +64,7 @@ layout("/mobile/platform.html"){
style="margin-right: 10px"
width="90"
height="90"
:src="'${AppFileDomain!}'+o.cover"
:src="FILE_DOMAIN+'/fileStreamPreview?id='+o.cover"
></van-image>
<div class="list-item">
<div style="font-size: 14px">{{o.title}}</div>
@@ -101,11 +102,18 @@ layout("/mobile/platform.html"){
this.tableData = []
const {data} = await $.get('/mobile/trainEducation/getNewestVideo')
this.tableData.push(data)
console.log(data)
}
},
async created() {
this.videoTypeList = await getVideoType()
const videoTypeList = await getVideoType()
if (videoTypeList.length > 0) {
videoTypeList.map(v => {
if (v.files) {
v.icon = JSON.parse(v.files)[0].id
}
})
}
this.videoTypeList = videoTypeList
await this.getNewVideo()
}
})
@@ -2,6 +2,23 @@
layout("/mobile/platform.html"){
#-->
<style>
.bottom-wrap {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
z-index: 1;
}
.bottom-wrap button {
border-radius: 10px;
}
</style>
<script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
<link href="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/tcplayer.min.css" rel="stylesheet"/>
<!--如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 HLS 格式的视频,需要在 tcplayer.vx.x.x.min.js 之前引入 hls.min.x.xx.xm.js。-->
@@ -78,7 +95,7 @@ layout("/mobile/platform.html"){
<van-image
v-else
style="width: 100%;height: 100%"
:src="'${AppFileDomain!}'+albumInfo.cover"
:src="FILE_DOMAIN+'/fileStreamPreview?id='+albumInfo.cover"
></van-image>
</div>
@@ -107,7 +124,7 @@ layout("/mobile/platform.html"){
<div style="height: 5px;background-color: #f6f7f9"></div>
<van-cell-group class="video-list">
<van-cell v-for="(o,i) in videoList" @click="playVideo(o)">
<van-cell v-for="(o,i) in videoList" :key="o.id" @click="playVideo(o)">
<template #title>
<van-tag plain type="primary" style="margin-right: 10px">视频</van-tag>
<span style="margin-right: 10px">
@@ -121,28 +138,22 @@ layout("/mobile/platform.html"){
{{getWatchStatus(o)}}
</template>
<template>
<span v-if="o.id === currentVideo.id">
播放中
</span>
</template>
</van-cell>
</van-cell-group>
</van-tab>
<!-- <van-tab title="评价">-->
<!-- <div style="height: 5px;background-color: #f6f7f9"></div>-->
<!-- <van-cell title="我的评价">-->
<!-- <template>-->
<!-- <van-rate v-model="myComment.level" icon="like" void-icon="like-o"></van-rate>-->
<!-- </template>-->
<!-- </van-cell>-->
<!-- </van-tab>-->
</van-tabs>
<div class="bottom-wrap">
<van-button type="danger" block @click="endLearn">结束本次学习</van-button>
</div>
</div>
<script>
let player = null
// let player = null
const vue = new Vue({
el: '#app',
@@ -159,7 +170,9 @@ layout("/mobile/platform.html"){
myComment: {},
watchRecordList: [],
lastTime: 0,
isPlay: false
isPlay: false,
player: null,
}
},
@@ -173,94 +186,100 @@ layout("/mobile/platform.html"){
const {data} = await $.get('/mobile/trainEducation/getAlbumInfo', {id: this.albumId})
this.albumInfo = data
},
//播放视频
async playVideo(o) {
this.isPlay = true
this.currentVideo = o
this.$nextTick(()=>{
if (!player) {
player = TCPlayer('player-container-id', {})
}
player.src(o.mediaUrl)
})
// await this.addWatchRecord()
await this.getMyWatchRecord()
//接着上次继续看
const v = this.watchRecordList.find(v => v.videoId === o.id)
if (v) {
this.lastTime = v.watchDuration
setTimeout(() => {
player.currentTime(v.watchDuration)
})
} else {
player.currentTime(0)
await this.addWatchRecord()
await this.getMyWatchRecord()
//判断当前视频是否看完
if (this.player && this.player.currentTime() < this.currentVideo.fileDuration) {
this.$modal.msg('看完本视频后才能接着看下一个')
return
}
// player.play()
this.playPause()
this.playEnd()
this.playTimeUpdate()
this.playSeeked()
},
//快进 快退
playSeeked() {
player.on('seeked', () => {
this.lastTime = player.currentTime()
this.addWatchRecord()
})
},
playTimeUpdate() {
this.lastTime = player.currentTime()
player.on('timeupdate', () => {
const nowTime = player.currentTime()
if (nowTime - this.lastTime > 5) {
this.lastTime = nowTime
this.addWatchRecord()
}
})
},
playPause() {
player.on('pause', () => {
this.addWatchRecord()
})
this.currentVideo = o
const playVideo_ = (time = 0) => {
this.$nextTick(async () => {
let controlBar = TCPlayer.getComponent('controlBar');
controlBar.prototype.options_ = {
children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'chaptersButton', 'descriptionsButton', 'fullscreenToggle']
}
if (!this.player) {
this.player = TCPlayer('player-container-id', {})
}
this.player.src(o.mediaUrl)
this.player.play()
setTimeout(() => {
this.player.currentTime(time)
}, 100)
this.playEnd()
})
}
const v = this.watchRecordList.find(v => v.videoId === o.id)
if (v && v.finish) {
this.$modal.confirm('本视频您已学习完成,是否重新观看,不会影响到完成的学习记录').then(() => {
playVideo_(0)
})
} else if (v && !v.finish) {
playVideo_(v.watchDuration)
} else {
playVideo_(0)
}
},
playEnd() {
player.on('ended', () => {
this.addWatchRecord()
this.getMyWatchRecord()
const currentIndex = this.videoList.findIndex(v => v.id === this.currentVideo.id)
const nextVideo = this.videoList[currentIndex + 1]
if (nextVideo) {
this.playVideo(nextVideo)
this.player.on('ended', async () => {
const v = this.watchRecordList.find(v => v.videoId === this.currentVideo.id)
if (v.finish) {
return
}
const loading = this.$toast.loading({
duration: 0,
forbidClick: true,
message: '已完整学习本视频,自动上传中'
})
const resp = await this.addWatchRecord()
if (resp.code === 0) {
loading.message = '上传成功'
loading.type = 'success'
}
setTimeout(() => {
loading.clear()
}, 2000)
// this.addWatchRecord()
// this.getMyWatchRecord()
// const currentIndex = this.videoList.findIndex(v => v.id === this.currentVideo.id)
// const nextVideo = this.videoList[currentIndex + 1]
// if (nextVideo) {
// this.playVideo(nextVideo)
// }
})
},
//播放记录
async addWatchRecord() {
// if (player.currentTime() < 2) {
// return
// }
const params = {
videoId: this.currentVideo.id,
currentTime: parseInt(player.currentTime()),
currentTime: parseInt(this.player.currentTime()),
albumId: this.currentVideo.albumId
}
await $.post('/mobile/trainEducation/addWatchRecord', params)
const resp = await $.post('/mobile/trainEducation/addWatchRecord', params)
this.getMyWatchRecord()
return resp
},
//监听页面关闭 隐藏
watchPage() {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
if (player && this.currentVideo) {
if (this.player && this.currentVideo) {
this.addWatchRecord()
}
}
@@ -275,12 +294,45 @@ layout("/mobile/platform.html"){
if (!v) {
return '学习进度:还未开始学习'
}
if (v.finish) {
return '学习进度:已完成'
}
if (v.videoId === this.currentVideo.id) {
return '学习进度:正在学习中'
}
const percentage = (parseInt((v.watchDuration / (video.fileDuration - 1)).toFixed(2) * 100)) + "%"
if (percentage === 0) {
return '学习进度:还未开始学习'
}
return "学习进度:" + percentage
},
endLearn() {
const v = this.watchRecordList.find(v => v.videoId === this.currentVideo.id)
if (v.finish) {
this.$modal.msg('本视频您已学习完成,无需重复上传')
return
}
this.$modal.confirm('您是否要结束本视频学习?').then(() => {
//暂停
this.player.pause()
//上传记录
this.$modal.loading('上传中')
this.addWatchRecord().then(resp => {
console.log(resp)
this.$modal.closeLoading()
if (resp.code === 0) {
this.$modal.msgSuccess('上传成功')
} else {
this.$modal.msgError('上传成功')
}
}).catch(err => {
console.log(err)
})
})
}
},
created() {
this.watchPage()
@@ -291,7 +343,6 @@ layout("/mobile/platform.html"){
this.getVideoInfo()
this.getMyWatchRecord()
}
}
})
</script>
@@ -0,0 +1,331 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
.el-popover {
text-align: center;
}
.descriptionContainer {
}
</style>
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<div class="btn-group tool-button mt5">
<el-input placeholder="请输入内容" v-model="pageForm.searchKeyword" clearable
@keyup.enter.native="doSearch">
<el-select v-model="pageForm.searchName" slot="prepend" placeholder="查询类型"
style="width: 120px;">
<el-option label="标题" value="tea.title"></el-option>
</el-select>
</el-input>
</div>
<div class="btn-group tool-button mt5">
<el-select v-model="pageForm.typeId" clearable>
<el-option v-for="item in videoTypeList" :label="item.typeName"
:value="item.id"></el-option>
</el-select>
</div>
<div class="btn-group tool-button mt5">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
</div>
<div class="pull-right offscreen-right mt5">
<el-button type="primary" @click="openAdd">
<i class="ti-plus"></i>
新增专辑
</el-button>
</div>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="专辑列表" :app="this"></table-tool>
<el-table :data="tableData" :size="tableSize" style="width: 100%">
<el-table-column type="index" label="序号" width="80px"></el-table-column>
<el-table-column
align="center"
header-align="center"
v-for="column in tableColumns"
show-overflow-tooltip
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
>
<template v-if="column.prop==='cover'" scope="{row}">
<el-popover
placement="top-start"
width="100"
trigger="hover">
<el-image
style="height: 100px;width: 100px"
:src="APP_DOMAIN+FILE_STREAM_PREVIEW_ADDRESS+'?id='+row.cover"
fit="cover">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
<el-image
slot="reference"
style="height: 30px;width: 30px"
:src="APP_DOMAIN+FILE_STREAM_PREVIEW_ADDRESS+'?id='+row.cover"
fit="cover">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</el-popover>
</template>
<template v-else-if="column.prop==='createType'" scope="{row}">
<span v-if="row.createType === 1">分工会</span>
<span v-else>校工会</span>
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="操作" width="250">
<template scope="{row}">
<el-button size="small" @click="openView(row)">查看</el-button>
<el-button size="small" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="doDelete(row.id)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<el-form :model="formData" :rules="rules" ref="ruleForm" label-width="100px"
>
<el-form-item label="专辑标题" prop="title">
<el-input v-model="formData.title" maxlength="30" placeholder="请输入专辑标题"></el-input>
</el-form-item>
<el-form-item label="专辑类型" prop="typeId">
<el-select v-model="formData.typeId" style="width: 100%" placeholder="请选择专辑类型">
<el-option v-for="item in videoTypeList" :label="item.typeName"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="创建类型" prop="createType">
<el-radio-group v-model="formData.createType">
<el-radio-button :label="1">分工会</el-radio-button>
<el-radio-button :label="2">校工会</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="专辑简介" prop="description">
<!-- <el-input type="textarea" :rows="3" v-model="formData.description"></el-input>-->
<div id="descriptionEditor"></div>
</el-form-item>
<el-form-item label="封面图片" prop="cover">
<el-upload
class="avatar-uploader"
:action="FILE_UPLOAD_ADDRESS"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="formData.cover"
:src="APP_DOMAIN+FILE_STREAM_PREVIEW_ADDRESS+'?id='+formData.cover"
class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
<el-form-item class="text-right">
<el-button type="primary" @click="doHandle">确定</el-button>
<el-button @click="$refs.guava.index()">取消</el-button>
</el-form-item>
</el-form>
</template>
<template #view>
<el-form label-width="100px" label-suffix=":">
<el-form-item label="专辑标题">
<span>
{{viewData.title}}
</span>
</el-form-item>
<el-form-item label="专辑类型">
<span>{{viewData.typeName}}</span>
</el-form-item>
<el-form-item label="创建类型">
<span v-if="viewData.createType === 1">分工会</span>
<span v-else>校工会</span>
</el-form-item>
<el-form-item label="专辑简介">
<div v-html="viewData.description" class="descriptionContainer"></div>
</el-form-item>
<el-form-item label="封面图片" prop="cover">
<file-upload :files="viewFiles" view></file-upload>
</el-form-item>
</el-form>
</template>
</guava>
</div>
<script>
<!--#include("/platform/trainEdu/include/include.js"){}#-->
let descriptionEditor = null
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
viewFiles: [],
pageForm: {
searchName: 'tea.title'
},
formData: {
cover: null,
title: null,
description: null,
typeId: null,
createType: 1,
},
tableColumns: [
{prop: 'title', label: '标题'},
{prop: 'cover', label: '封面'},
{prop: 'videoCount', label: '视频数量'},
{prop: 'typeName', label: '专辑类型'},
{prop: 'createType', label: '创建类型'},
],
rules: {
title: [{required: true, message: '请输入标题', trigger: ['change', 'blur']}],
description: [{required: true, message: '请输入简介', trigger: ['change', 'blur']}],
cover: [{required: true, message: '请上传封面图片', trigger: ['change', 'blur']}],
typeId: [{required: true, message: '请选择专辑类型', trigger: ['change', 'blur']}],
createType: [{required: true, message: '请选择创建类型', trigger: ['change', 'blur']}],
},
videoTypeList: [],
viewData: {}
}
},
methods: {
openAdd() {
this.$refs['guava'].edit()
this.$refs['ruleForm'].resetFields()
this.formData.id = undefined
this.$nextTick(() => {
$("#descriptionEditor").html("")
descriptionEditor = new wangEditor("#descriptionEditor")
descriptionEditor.config.onchange = (html) => {
this.formData.description = html
}
descriptionEditor.config.uploadImgShowBase64 = true
descriptionEditor.create()
descriptionEditor.txt.html()
})
},
async handleAvatarSuccess(res, file) {
this.$set(this.formData, 'cover', res.data.id)
await this.$refs['ruleForm'].validate()
},
beforeAvatarUpload(file) {
const allow = ['image/jpeg', 'image/png']
if (!allow.includes(file.type)) {
this.$message.error('上传头像图片只能是 JPG、PNG 格式!')
return false
}
return true
},
async doHandle() {
const valid = await this.$refs['ruleForm'].validate()
if (valid) {
const methodName = typeof this.formData.id === 'undefined' ? '/doAdd' : '/doEdit'
const resp = await $.post(loc() + methodName, this.formData)
if (resp.code === 0) {
this.notifySuccess(resp.msg)
this.doSearch()
this.$refs['guava'].index()
} else {
this.notifyError(resp.msg)
}
}
},
openView(row) {
this.viewData = {...row}
this.viewFiles = [{id: row.cover, filename: row.cover + '.jpg'}]
this.$refs.guava.view()
},
openEdit(row) {
this.$refs['guava'].edit()
this.formData = JSON.parse(JSON.stringify(row))
this.$nextTick(() => {
$("#descriptionEditor").html("")
descriptionEditor = new wangEditor("#descriptionEditor")
descriptionEditor.config.onchange = (html) => {
this.formData.description = html
}
descriptionEditor.config.uploadImgShowBase64 = true
descriptionEditor.create()
descriptionEditor.txt.html(row.description)
})
},
async doDelete(id) {
const confirm = await this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
if (confirm === 'confirm') {
const resp = await $.post(loc() + '/doDelete', {id})
if (resp.code === 0) {
this.$notify.success(resp.msg)
this.doSearch()
}
}
},
},
async created() {
this.videoTypeList = await getVideoType()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,9 @@
const getAllAlbum = async () => {
const {data} = await $.get('/platform/trainEdu/album/getAllAlbum')
return data
}
const getVideoType = async () => {
const resp = await $.get('/platform/trainEdu/type/pageData', {pageForm: 1, pageSize: 10000})
return resp.data.list
}
@@ -0,0 +1,144 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<div class="search">
<div class="search-item" v-if="pageForm.searchType==='按分工会分析'">
<div class="search-item-label">分工会:</div>
<div class="search-item-option">
<el-select placeholder="分工会" v-model="pageForm.unionId" style="width: 100%;"
@change="unionChange"
clearable
filterable>
<el-option v-for="item in unions" :label="item.unionname" :value="item.id"></el-option>
</el-select>
</div>
</div>
<div class="search-item" v-if="pageForm.searchType==='按单位分析'">
<div class="search-item-label">单位:</div>
<div class="search-item-option">
<el-select placeholder="单位" v-model="pageForm.unitId" style="width: 100%;"
@change="unitChange"
clearable
filterable>
<el-option v-for="item in units" :label="item.name" :value="item.id"></el-option>
</el-select>
</div>
</div>
<div class="offscreen-right pull-right">
<el-radio-group v-model="pageForm.searchType" @change="searchTypeChange">
<el-radio-button label="按分工会分析"></el-radio-button>
<el-radio-button label="按单位分析"></el-radio-button>
</el-radio-group>
</div>
</div>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="统计" :app="this"></table-tool>
<el-table :data="tableData" :size="tableSize" style="width: 100%">
<el-table-column type="index" label="序号" width="80px"></el-table-column>
<el-table-column
align="center"
header-align="center"
v-for="column in tableColumns"
show-overflow-tooltip
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
>
<template v-if="column.prop==='unionName'" scope="{row}">
{{isDisPlayCode?'('+row.unionCode+')':null}}{{row.unionName}}
</template>
</el-table-column>
</el-table>
</el-card>
</guava>
</div>
<script>
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
tableColumns: [
{prop: 'unionName', label: '分工会名称'},
{prop: 'unionCode', label: '分工会编码', sortable: true},
{prop: 'unionSum', label: '总人数', sortable: true},
{prop: 'studyNum', label: '学习人数', sortable: true},
],
unions: [],
units: [],
tableData: [],
fullTableData: [],
pageForm: {
searchType: '按分工会分析'
}
}
},
methods: {
unionChange(val) {
if (val == null || val == '') {
this.tableData = this.fullTableData
} else {
this.tableData = this.fullTableData.filter(v => v.id === val)
}
},
unitChange(val) {
if (val == null || val == '') {
this.tableData = this.fullTableData
} else {
this.tableData = this.fullTableData.filter(v => v.id === val)
}
},
searchTypeChange() {
const searchType = this.pageForm.searchType
if (searchType === '按分工会分析') {
this.tableColumns = [
{prop: 'unionName', label: '分工会名称'},
{prop: 'unionCode', label: '分工会编码', sortable: true},
{prop: 'unionSum', label: '总人数', sortable: true},
{prop: 'studyNum', label: '学习人数', sortable: true},
]
} else {
this.tableColumns = [
{prop: 'unitName', label: '单位名称'},
{prop: 'unitCode', label: '单位编码', sortable: true},
{prop: 'unionSum', label: '总人数', sortable: true},
{prop: 'studyNum', label: '学习人数', sortable: true},
]
}
this.pageData()
},
async pageData() {
sublime.showLoadingbar()
this.tableLoading = true
const resp = await $.post(loc() + '/pageData', this.pageForm)
sublime.closeLoadingbar()
this.tableLoading = false
if (resp.code === 0) {
this.tableData = resp.data.list
this.fullTableData = resp.data.list
}
}
},
async created() {
this.pageData()
this.unions = await getUnions(null)
this.units = await getUnits(null)
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,122 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never" class="mt10">
<table-tool label="统计" :app="this"></table-tool>
<el-table :data="tableData" :size="tableSize" style="width: 100%">
<el-table-column type="index" label="序号" width="80px"></el-table-column>
<el-table-column
align="center"
header-align="center"
v-for="column in tableColumns"
show-overflow-tooltip
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
>
<template v-if="column.prop==='sumDuration'" scope="{row}">
{{(row.sumDuration / 60 / 60).toFixed(2) + 'h'}}
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="操作" width="200">
<template scope="{row}">
<el-button size="small" type="primary" @click="openView(row)">查看具体学习情况</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<template #view>
<el-table :data="viewTableData" stripe border style="width: 100%">
<el-table-column type="index" label="序号" width="80px">
<template scope="scope">
<span>{{ scope.$index + (viewPageForm.pageNumber - 1) * viewPageForm.pageSize + 1 }} </span>
</template>
</el-table-column>
<el-table-column
align="center"
header-align="center"
v-for="column in viewTableColumns"
show-overflow-tooltip
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
>
<template v-if="column.prop==='studyDuration'" scope="{row}">
{{(row.studyDuration / 60 / 60).toFixed(2) + 'h'}}
</template>
</el-table-column>
</el-table>
<el-row class="el-pagination-container" style="margin-bottom: 0px">
<el-pagination
@current-change="(val)=>{viewPageForm.pageNumber=val;viewPageData()}"
:current-page="viewPageForm.pageNumber"
:page-size="viewPageForm.pageSize"
layout="total, sizes, prev, pager, next"
:total="viewPageForm.totalCount">
</el-pagination>
</el-row>
</template>
</guava>
</div>
<script>
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
tableColumns: [
{prop: 'title', label: '专辑名称'},
{prop: 'typeName', label: '类型'},
{prop: 'studyNum', label: '已看人数', sortable: true},
{prop: 'sumDuration', label: '课程总时长', sortable: true},
],
viewTableColumns: [
{prop: 'username', label: '姓名'},
{prop: 'loginname', label: '工号'},
{prop: 'unitname', label: '单位'},
{prop: 'unionname', label: '分工会'},
{prop: 'studyDuration', label: '观看总时长'},
],
viewPageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
albumId: null
},
viewTableData: []
}
},
methods: {
async openView(row) {
this.viewPageForm.albumId = row.id
this.$refs['guava'].view()
await this.viewPageData()
},
async viewPageData() {
const resp = await $.post(loc() + '/getWatchDuration', this.viewPageForm)
if (resp.code === 0) {
this.viewTableData = resp.data.list
this.viewPageForm.totalCount = resp.data.totalCount
}
},
viewPageNumberChange(val) {
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,442 @@
<!--#
layout("/layouts/platform.html"){
#-->
<script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
<link href="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/tcplayer.min.css" rel="stylesheet"/>
<!--如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 HLS 格式的视频,需要在 tcplayer.vx.x.x.min.js 之前引入 hls.min.x.xx.xm.js。-->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/libs/hls.min.0.13.2m.js"></script>
<!--播放器脚本文件-->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/tcplayer.v4.3.0.min.js"></script>
<style>
.el-tag {
margin-right: 10px;
}
.el-image {
display: inherit;
border-radius: 10px;
cursor: pointer;
}
.hitv_horizontal-txtbox {
height: 52px;
padding-top: 4px;
cursor: pointer;
overflow: hidden;
}
.hitv_horizontal-title {
line-height: 20px;
height: auto;
display: -webkit-box;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
width: 200px;
font-weight: bold;
}
.hitv_horizontal-desc {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
zoom: 1;
color: #999;
width: 200px;
font-size: 13px;
}
p {
margin: 0;
}
div img {
width: 100%;
height: 100%;
transition: all 0.6s;
cursor: pointer;
}
div img:hover {
transform: scale(1.3);
}
.title {
color: #222;
}
.select-video {
color: #1867b0 !important;
}
.info:hover .title {
color: #236eb4;
}
.video {
height: calc(100vh - 50px - 20px - 54px - 40px);
width: 100%;
}
.video_list {
height: calc(100vh - 50px - 20px - 54px - 40px);
overflow: auto;
}
.name {
font-size: 13px;
color: grey;
}
.video_div {
height: calc(100vh - 20px - 85px - 20px - 60px - 40px - 32px - 15px);
overflow: auto;
}
</style>
<div id="app" v-cloak>
<guava ref="guava" v-model="guavaIndex">
<el-card shadow="never">
<div class="btn-group tool-button mt5">
<el-input @keyup.enter.native="doSearch" clearable placeholder="请输入内容"
v-model="pageForm.searchKeyword"></el-input>
</div>
<div class="btn-group tool-button mt5">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
</div>
</el-card>
<el-card shadow="never" class="mt20">
<div>
<el-tag :effect="pageForm.typeId === item.id ? 'dark':'light'" @click="typeClick(item)"
v-for="item in typeList">{{item.typeName}}
</el-tag>
</div>
<div class="demo-image__placeholder mt20 video_div"
:infinite-scroll-disabled="scrollDisabled"
v-infinite-scroll="load">
<div @click="goPlayPage(item.id)" class="info mr25 mb10" style="display: inline-block"
v-for="item in albumList">
<el-image style="width: 200px; height: 130px"
:src="APP_DOMAIN+FILE_STREAM_PREVIEW_ADDRESS+'?id='+item.cover"
lazy>
</el-image>
<div class="hitv_horizontal-txtbox">
<p class="hitv_horizontal-title">
<a class="title">{{item.title}}</a>
</p>
<p class="hitv_horizontal-desc" v-html="item.description">
</p>
</div>
</div>
</div>
</el-card>
<template #view>
<el-row :gutter="20">
<el-col :span="18">
<div id="el-col-video">
<video :id="'player-container-id'+albumId" class="video" height="600" playsinline preload="auto"
webkit-playsinline>
</video>
</div>
<div>
title
description
</div>
</el-col>
<el-col :span="6" class="video_list">
<div @click="playVideo(item)" style="margin-bottom: 20px; display: flex;"
v-for="item in videoList">
<div>
<el-image style="width: 160px; height: 100px; margin-right: 10px"
:src="item.coverUrl"
lazy>
</el-image>
</div>
<div>
<a :class="currentPlayVideo.id === item.id ? 'select-video':''"
class="title">{{item.title}}</a>
<div class="upname">
<div>
<svg height="16" style="position: relative; top: 3px" viewBox="0 0 18 16" width="18"
xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd"
d="M9 2.0625C6.85812 2.0625 4.98983 2.1725 3.67735 2.2798C2.77861 2.35327 2.08174 3.04067 2.00119 3.93221C1.90388 5.00924 1.8125 6.43727 1.8125 8C1.8125 9.56273 1.90388 10.9908 2.00119 12.0678C2.08174 12.9593 2.77861 13.6467 3.67735 13.7202C4.98983 13.8275 6.85812 13.9375 9 13.9375C11.1421 13.9375 13.0105 13.8275 14.323 13.7202C15.2216 13.6467 15.9184 12.9595 15.9989 12.0682C16.0962 10.9916 16.1875 9.56386 16.1875 8C16.1875 6.43614 16.0962 5.00837 15.9989 3.9318C15.9184 3.04049 15.2216 2.3533 14.323 2.27983C13.0105 2.17252 11.1421 2.0625 9 2.0625ZM3.5755 1.03395C4.9136 0.924562 6.81674 0.8125 9 0.8125C11.1835 0.8125 13.0868 0.924583 14.4249 1.03398C15.9228 1.15645 17.108 2.31588 17.2438 3.81931C17.3435 4.92296 17.4375 6.38948 17.4375 8C17.4375 9.61052 17.3435 11.077 17.2438 12.1807C17.108 13.6841 15.9228 14.8436 14.4249 14.966C13.0868 15.0754 11.1835 15.1875 9 15.1875C6.81674 15.1875 4.9136 15.0754 3.5755 14.966C2.07738 14.8436 0.892104 13.6838 0.756256 12.1803C0.656505 11.0762 0.5625 9.60942 0.5625 8C0.5625 6.39058 0.656505 4.92379 0.756257 3.81973C0.892104 2.31616 2.07738 1.15643 3.5755 1.03395ZM4.41663 4.93726C4.72729 4.93726 4.97913 5.1891 4.97913 5.49976V8.62476C4.97913 9.34963 5.56675 9.93726 6.29163 9.93726C7.0165 9.93726 7.60413 9.34963 7.60413 8.62476V5.49976C7.60413 5.1891 7.85597 4.93726 8.16663 4.93726C8.47729 4.93726 8.72913 5.1891 8.72913 5.49976V8.62476C8.72913 9.97095 7.63782 11.0623 6.29163 11.0623C4.94543 11.0623 3.85413 9.97095 3.85413 8.62476V5.49976C3.85413 5.1891 4.10597 4.93726 4.41663 4.93726ZM10.2501 4.93726C9.9394 4.93726 9.68756 5.1891 9.68756 5.49976V10.4998C9.68756 10.8104 9.9394 11.0623 10.2501 11.0623C10.5607 11.0623 10.8126 10.8104 10.8126 10.4998V9.60392H12.2292C13.5179 9.60392 14.5626 8.55925 14.5626 7.27059C14.5626 5.98193 13.5179 4.93726 12.2292 4.93726H10.2501ZM12.2292 8.47892H10.8126V6.06226H12.2292C12.8966 6.06226 13.4376 6.60325 13.4376 7.27059C13.4376 7.93793 12.8966 8.47892 12.2292 8.47892Z"
fill-rule="evenodd"></path>
</svg>
<span :class="currentPlayVideo.id === item.id ? 'select-video':''" class="name">{{item.title}}</span>
</div>
<div>
<svg class="icon" height="16" p-id="2411" style="position: relative; top: 3px"
t="1660188268757" version="1.1" viewBox="0 0 1024 1024"
width="18" xmlns="http://www.w3.org/2000/svg">
<path d="M511.913993 63.989249c-247.012263 0-447.924744 200.912481-447.924744 447.924744s200.912481 447.924744 447.924744 447.924744 447.924744-200.912481 447.924744-447.924744S758.926256 63.989249 511.913993 63.989249zM511.913993 895.677474c-211.577356 0-383.763481-172.186125-383.763481-383.763481 0-211.577356 172.014111-383.763481 383.763481-383.763481s383.763481 172.014111 383.763481 383.763481S723.491349 895.677474 511.913993 895.677474z"
p-id="2412"></path>
<path d="M672.05913 511.913993l-159.973123 0L512.086007 288.123635c0-17.717453-14.277171-32.166639-31.994625-32.166639-17.717453 0-31.994625 14.449185-31.994625 32.166639l0 255.956996c0 17.717453 14.277171 31.994625 31.994625 31.994625l191.967747 0c17.717453 0 32.166639-14.277171 32.166639-31.994625C704.053754 526.191164 689.604569 511.913993 672.05913 511.913993z"
p-id="2413"></path>
</svg>
<span :class="currentPlayVideo.id === item.id ? 'select-video':''" class="name">{{formatSeconds(item.fileDuration)}}</span>
</div>
</div>
</div>
</div>
</el-col>
</el-row>
</template>
</guava>
</div>
<script>
let player = null
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
typeList: [],
albumList: [],
videoList: [],
pageForm: {
typeId: null,
pageSize: 30
},
currentPlayVideo: {},
watchRecordList: [],
guavaIndex: null,
albumId: null,
scrollDisabled: false,
}
},
computed: {
scrollDisabled() {
return this.pageForm.totalCount === this.albumList.length
}
},
watch: {
async guavaIndex(val) {
if (val === 'index' && player) {
console.log('xxx')
player.dispose()
player = null
}
}
},
methods: {
doSearch() {
this.albumList = []
this.pageForm.pageNumber = 1
this.load()
},
async typeClick(item) {
if (this.pageForm.typeId === item.id) return
this.pageForm.typeId = item.id
this.pageForm.pageNumber = 1
this.albumList = []
await this.getAlbum()
},
//获取专辑
async load() {
this.scrollDisabled = true
await this.getAlbum()
},
async getAlbum() {
const resp = await $.get('/platform/trainEdu/album/getAlbumByTypeId', this.pageForm)
if (resp.code === 0) {
if (resp.data.list.length > 0) {
if (this.scrollDisabled === false) {
this.albumList = this.albumList.concat(resp.data.list)
} else {
this.albumList = resp.data.list
}
this.pageForm.totalCount = resp.data.totalCount
this.pageForm.pageNumber = this.pageForm.pageNumber + 1
} else {
this.noMore = true
}
}
},
async goPlayPage(albumId) {
this.$refs.guava.view()
this.albumId = albumId
this.$nextTick(() => {
if (!player) {
const videoTag = document.getElementById('player-container-id' + albumId)
if (!videoTag) {
const videoStr = "<video class='video' id='player-container-id" + albumId + "'" + "height='600' preload='auto' playsinline webkit-playsinline></video>"
document.getElementById('el-col-video').insertAdjacentHTML('beforeend', videoStr)
}
player = TCPlayer('player-container-id' + albumId, {})
}
})
const resp = await $.get('/platform/trainEdu/video/getVideoList', {albumId})
if (resp.code === 0) {
this.videoList = resp.data
const currentVideo = this.videoList[0]
this.currentPlayVideo = currentVideo
if (currentVideo) {
player.src(currentVideo.mediaUrl)
const v = this.watchRecordList.find(v => v.videoId === currentVideo.id)
if (v) {
player.currentTime(v.watchDuration)
}
this.playTimeUpdate()
}
} else {
this.notifyWarning(resp.msg)
}
},
async playVideo(item) {
this.currentPlayVideo = item
player.src(item.mediaUrl)
await this.getMyWatchRecord()
//接着上次继续看
const v = this.watchRecordList.find(v => v.videoId === item.id)
if (v) {
this.lastTime = v.watchDuration
setTimeout(() => {
player.currentTime(v.watchDuration)
})
} else {
player.currentTime(0)
await this.addWatchRecord()
await this.getMyWatchRecord()
}
// player.play()
this.playPause()
this.playEnd()
this.playTimeUpdate()
this.playSeeked()
},
playPlay() {
player.on('play', () => {
this.lastTime = player.currentTime()
this.addWatchRecord()
})
},
//快进 快退
playSeeked() {
player.on('seeked', () => {
this.lastTime = player.currentTime()
this.addWatchRecord()
})
},
playTimeUpdate() {
this.lastTime = player.currentTime()
player.on('timeupdate', () => {
const nowTime = player.currentTime()
if (nowTime - this.lastTime > 5) {
this.lastTime = nowTime
this.addWatchRecord()
}
})
},
playPause() {
player.on('pause', () => {
this.addWatchRecord()
})
},
playEnd() {
player.on('ended', () => {
this.addWatchRecord()
this.getMyWatchRecord()
const currentIndex = this.videoList.findIndex(v => v.id === this.currentVideo.id)
const nextVideo = this.videoList[currentIndex + 1]
if (nextVideo) {
this.playVideo(nextVideo)
}
})
},
async getVideoType() {
const resp = await $.get('/platform/trainEdu/type/pageData', {pageForm: 1, pageSize: 10000})
if (resp.code === 0) {
this.typeList = resp.data.list
}
},
formatSeconds(value) {
let secondTime = parseInt(value);// 秒
let minuteTime = 0;// 分
let hourTime = 0;// 小时
if (secondTime > 60) {//如果秒数大于60,将秒数转换成整数
//获取分钟,除以60取整数,得到整数分钟
minuteTime = parseInt(secondTime / 60);
//获取秒数,秒数取佘,得到整数秒数
secondTime = parseInt(secondTime % 60);
//如果分钟大于60,将分钟转换成小时
if (minuteTime > 60) {
//获取小时,获取分钟除以60,得到整数小时
hourTime = parseInt(minuteTime / 60);
//获取小时后取佘的分,获取分钟除以60取佘的分
minuteTime = parseInt(minuteTime % 60);
}
}
let result = "" + parseInt(secondTime) + "秒";
if (minuteTime > 0) {
result = "" + parseInt(minuteTime) + "分" + result;
}
if (hourTime > 0) {
result = "" + parseInt(hourTime) + "小时" + result;
}
return result;
},
//监听页面关闭 隐藏
watchPage() {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
if (player && this.currentPlayVideo) {
this.addWatchRecord()
}
}
})
},
async addWatchRecord() {
const params = {
videoId: this.currentPlayVideo.id,
currentTime: parseInt(player.currentTime()),
albumId: this.currentPlayVideo.albumId
}
await $.post('/mobile/trainEducation/addWatchRecord', params)
},
async getMyWatchRecord() {
const {data} = await $.get('/mobile/trainEducation/getMyWatchRecord')
this.watchRecordList = data
},
},
async created() {
await this.getVideoType()
await this.getMyWatchRecord()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,188 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
}
.avatar {
width: 50px;
height: 50px;
display: block;
}
</style>
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never" class="text-right">
<div class="btn-group tool-button mt5">
<el-button type="primary" @click="openAdd">
<i class="ti-plus"></i>
新增类型
</el-button>
</div>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="专辑类型" :app="this"></table-tool>
<el-table :data="tableData" :size="tableSize" style="width: 100%">
<el-table-column type="index" label="序号" width="80px"></el-table-column>
<el-table-column
align="center"
header-align="center"
v-for="column in tableColumns"
show-overflow-tooltip
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
>
<template v-if="column.prop==='files'" scope="{row}">
<el-image
v-if="row.files"
style="height: 30px;width: 30px"
:src="FILE_DOMAIN+'/fileStreamPreview?id='+JSON.parse(row.files)[0].id"
fit="cover">
<div slot="error" class="image-slot">
<i class="el-icon-picture-outline"></i>
</div>
</el-image>
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="操作" width="250">
<template scope="{row}">
<el-button size="small" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog :title="formData.id ? '编辑类型':'新增类型'" :visible.sync="dialogVisible" width="30%"
:close-on-click-modal="false">
<el-form :model="formData" :rules="rules" ref="ruleForm" label-width="80px">
<el-form-item label="名称" prop="typeName">
<el-input v-model="formData.typeName"></el-input>
</el-form-item>
<el-form-item label="图标" prop="files">
<file-upload :files.sync="formData.files" :type="['jpg', 'jpeg', 'png', 'JPG', 'JPEG', 'PNG']" :max="1">
<template #el-upload__tip>
<div class="el-upload__tip" slot="tip">
请上传1个类型为jpg/png/jpeg等格式的图片
</div>
</template>
</file-upload>
<!--<el-upload
class="avatar-uploader"
:action="FILE_UPLOAD_ADDRESS"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="formData.icon" :src="FILE_DOMAIN+'/fileStreamPreview?id='+formData.icon" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>-->
</el-form-item>
</el-form>
<template #footer>
<el-row>
<el-button @click="dialogVisible=false">取消</el-button>
<el-button type="primary" @click="doHandle">确定</el-button>
</el-row>
</template>
</el-dialog>
</div>
<script>
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
dialogVisible: false,
formData: {
typeName: null,
files: []
},
tableColumns: [
{prop: 'typeName', label: '类型名称'},
{prop: 'files', label: '图标'},
],
rules: {
typeName: [{required: true, message: '请输入类型名称', trigger: ['change', 'blur']}],
files: [{required: true, message: '请上传图标文件', trigger: ['change', 'blur']}],
}
}
},
methods: {
openAdd() {
this.dialogVisible = true
this.$refs.ruleForm.resetFields()
},
async doHandle() {
const valid = await this.$refs['ruleForm'].validate()
if (valid) {
const methodName = typeof this.formData.id === 'undefined' ? '/doAdd' : '/doEdit'
const resp = await $.post(loc() + methodName, {trainEduType: JSON.stringify(this.formData)})
if (resp.code === 0) {
this.dialogVisible = false
this.notifySuccess(resp.msg)
this.doSearch()
} else {
this.notifyError(resp.msg)
}
}
},
openEdit(row) {
this.dialogVisible = true
this.$nextTick(() => {
this.formData = {...row, files: JSON.parse(row.files)}
})
},
async doDelete(id) {
const confirm = await this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
if (confirm === 'confirm') {
const resp = await $.post(loc() + '/doDelete', {id})
if (resp.code === 0) {
this.$notify.success(resp.msg)
this.doSearch()
}
}
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,360 @@
<!--#
layout("/layouts/platform.html"){
#-->
<script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
<link href="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/tcplayer.min.css" rel="stylesheet"/>
<!--如果需要在 Chrome 和 Firefox 等现代浏览器中通过 H5 播放 HLS 格式的视频,需要在 tcplayer.vx.x.x.min.js 之前引入 hls.min.x.xx.xm.js。-->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/libs/hls.min.0.13.2m.js"></script>
<!--播放器脚本文件-->
<script src="https://web.sdk.qcloud.com/player/tcplayer/release/v4.3.0/tcplayer.v4.3.0.min.js"></script>
<style>
.video-js {
width: 100% !important;
min-height: 500px !important;
}
.upload-form-item .el-form-item__content {
line-height: unset !important;
}
.video-title {
font-size: 18px;
font-weight: 500;
color: #212121;
line-height: 26px;
height: 26px;
margin-top: 0;
margin-bottom: 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
<div id="app" v-cloak>
<guava ref="guava" :v.sync="pageStatus">
<el-card shadow="never">
<div class="btn-group tool-button mt5">
<el-select v-model="pageForm.albumId" placeholder="请选择专辑" filterable clearable @change="doSearch">
<el-option
v-for="item in albumList"
:key="item.id"
:label="item.title"
:value="item.id">
</el-option>
</el-select>
</div>
<div class="btn-group tool-button mt5">
<el-input placeholder="请输入内容" v-model="pageForm.searchKeyword" clearable @keyup.enter.native="doSearch">
<el-select v-model="pageForm.searchName" slot="prepend" placeholder="查询类型" style="width: 120px;">
<el-option label="标题" value="tev.title"></el-option>
</el-select>
</el-input>
</div>
<div class="btn-group tool-button mt5">
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
</div>
<div class="pull-right offscreen-right mt5">
<el-button type="primary" @click="openAdd">
<i class="ti-plus"></i>
上传视频
</el-button>
</div>
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="视频列表" :app="this"></table-tool>
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" label="序号" width="80px"></el-table-column>
<el-table-column
align="center"
header-align="center"
v-for="column in tableColumns"
show-overflow-tooltip
:label="column.label"
:prop="column.prop"
:sortable="column.sortable"
>
<template v-if="column.prop==='disabled'" scope="{row}">
<span v-if="row.disabled"><i class="el-icon-error text-error"></i> 禁用</span>
<span v-else><i class="el-icon-success text-success"></i> 启用</span>
</template>
</el-table-column>
<el-table-column align="center" header-align="center" label="操作" width="250">
<template scope="{row}">
<el-button size="small" @click="openView(row)">查看</el-button>
<el-button size="small" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="doDelete(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<el-form :model="formData" :rules="rules" ref="ruleForm" label-width="100px">
<el-form-item label="所属专辑" prop="albumId">
<el-select v-model="formData.albumId" placeholder="请选择专辑" style="width: 100%">
<el-option
v-for="item in albumList"
:key="item.id"
:label="item.title"
:value="item.id">
</el-option>
</el-select>
<div class="text-primary">{{videoTypeTips}}</div>
</el-form-item>
<el-form-item label="视频标题" prop="title">
<el-input v-model="formData.title"></el-input>
</el-form-item>
<el-form-item label="上传文件" prop="url" class="upload-form-item">
<wp-upload ref="uploadForm"
:upload-auto="false"
:size="1024*100"
@on-select="selectFile"
:allow-files="['mp4']"
:max-file-number="1"
show-model="list"
:file-list="fileList"
type="single"></wp-upload>
<div class="el-upload__tip">请上传格式为mp4的视频文件,且不超过200M</div>
</el-form-item>
<el-form-item label="排序编号" prop="sortNo">
<el-input-number :min="1" :max="128" v-model="formData.sortNo"></el-input-number>
</el-form-item>
<el-form-item label="是否启用" prop="disabled">
<el-switch
v-model="formData.disabled"
:active-value="false"
:inactive-value="true"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</el-form-item>
</el-form>
<el-row class="mt20 text-right">
<el-button @click="$refs.guava.index()">取消</el-button>
<el-button type="primary" @click="doHandle">确定</el-button>
</el-row>
</template>
<template #view>
<!-- <el-row type="flex" :gutter="20">-->
<!-- <el-col :span="18">-->
<!-- <h1 class="video-title">-->
<!-- <span>{{viewData.albumTitle + '-' + viewData.title}}</span>-->
<!-- </h1>-->
<!-- <video id="player-container-id" width="700" height="600" preload="auto" playsinline-->
<!-- webkit-playsinline>-->
<!-- </video>-->
<!-- </el-col>-->
<!-- <el-col :span="6">-->
<!-- <div class="grid-content bg-purple"></div>-->
<!-- </el-col>-->
<!-- </el-row>-->
</template>
</guava>
<el-dialog title="视频" :visible.sync="playDialogVisible" width="60%" close-on-click-modal="false">
<h1 class="video-title">
<span>{{viewData.albumTitle + '-' + viewData.title}}</span>
</h1>
<video id="player-container-id" height="600" preload="auto" playsinline
webkit-playsinline>
</video>
</el-dialog>
</div>
<script>
<!--#include("/platform/trainEdu/include/include.js"){}#-->
let player = null
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
computed: {
videoTypeTips() {
const albumId = this.formData.albumId
const o = this.albumList.find(v => v.id === albumId)
if (o) {
//return '请上传' + o.eduType.typeName + '的视频'
}
return null
}
},
data() {
return {
pageStatus: null,
pageForm: {
albumId: null,
searchName: 'tev.title'
},
formData: {
albumId: null,
cover: null,
title: null,
sortNo: null,
disabled: false,
url: null
},
tableColumns: [
{prop: 'title', label: '标题'},
{prop: 'albumTitle', label: '所属专辑'},
{prop: 'createUserName', label: '上传人'},
{prop: 'createTime', label: '上传时间', sortable: true},
{prop: 'disabled', label: '视频状态', sortable: true},
],
rules: {
albumId: [{required: true, message: '请选择所属专辑', trigger: ['change', 'blur']}],
title: [{required: true, message: '请选择视频标题', trigger: ['change', 'blur']}],
url: [{required: true, message: '请上传视频文件', trigger: ['change', 'blur']}],
sortNo: [{required: true, message: '请输入排序编号', trigger: ['change', 'blur']}]
},
albumList: [],
fileList: [],
viewData: {},
playDialogVisible: false
}
},
watch: {
pageStatus(val) {
if (val !== 'view') {
if (player) {
player.pause()
}
}
}
},
methods: {
openAdd() {
this.$refs['guava'].edit()
if (this.$refs.uploadForm) {
this.$refs.uploadForm.clean()
}
this.$refs['ruleForm'].resetFields()
this.formData.id = undefined
this.formData.albumId = this.pageForm.albumId
},
selectFile(preUploadFiles) {
if (preUploadFiles && preUploadFiles.length > 0) {
this.formData.url = '#'
this.$refs['ruleForm'].validate()
}
},
async doHandle() {
const fileList = this.$refs.uploadForm.fileShowArray
if (fileList && fileList.length > 0) {
this.formData.url = '#'
} else {
this.formData.url = null
}
const valid = await this.$refs['ruleForm'].validate()
if (valid) {
const loading = this.$loading({
lock: true,
text: '上传中,文件过大请耐心等待',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const methodName = typeof this.formData.id === 'undefined' ? '/doAdd' : '/doEdit'
const formData = new FormData()
formData.append("data", JSON.stringify(this.formData))
formData.append("file", this.$refs.uploadForm.fileShowArray[0].file)
const resp = await $.ajax({
type: 'POST',
url: loc() + methodName,
data: formData,
cache: false,
processData: false,
contentType: false
})
loading.close()
if (resp.code === 0) {
this.notifySuccess(resp.msg)
this.doSearch()
this.$refs['guava'].index()
} else {
this.notifyError(resp.msg)
}
}
},
openView(row) {
this.viewData = row
this.playDialogVisible = true
this.$nextTick(() => {
if (!player) {
player = TCPlayer('player-container-id', {})
}
player.src(row.mediaUrl)
})
// player.play()
},
openEdit(row) {
this.$refs['guava'].edit()
this.formData = JSON.parse(JSON.stringify(row))
this.fileList = [{
url: row.mediaUrl
}]
if (this.$refs.uploadForm) {
setTimeout(() => {
this.$refs.uploadForm.initWp()
})
}
},
async doDelete(id) {
const confirm = await this.$confirm('确定删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
if (confirm === 'confirm') {
const resp = await $.post(loc() + '/doDelete', {id})
if (resp.code === 0) {
this.$notify.success(resp.msg)
this.doSearch()
}
}
},
},
async created() {
this.albumList = await getAllAlbum()
if (this.albumList) {
this.pageForm.albumId = this.albumList[0].id
this.pageData()
}
}
})
</script>
<!--#
}
#-->