diff --git a/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysDishesController.java b/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysDishesController.java index af5b66e53..74c1a5e42 100644 --- a/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysDishesController.java +++ b/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysDishesController.java @@ -71,6 +71,7 @@ public class SysDishesController extends BaseController { public AjaxResult getMenuTypes(@PathVariable("id") Long id) { JSONObject object = new JSONObject(); object.put("type", sysDishesService.getDishesMenuTypeById(id)); + object.put("className", sysDishesService.getDishClassNameById(id)); return AjaxResult.success(object); } diff --git a/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysNutritionalVideoController.java b/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysNutritionalVideoController.java index 404dc8bb3..5a55b905c 100644 --- a/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysNutritionalVideoController.java +++ b/stdiet-admin/src/main/java/com/stdiet/web/controller/custom/SysNutritionalVideoController.java @@ -8,6 +8,7 @@ import com.stdiet.common.core.page.TableDataInfo; import com.stdiet.common.utils.AliyunVideoUtils; import com.stdiet.common.utils.StringUtils; import com.stdiet.common.utils.oss.AliyunOSSUtils; +import org.aspectj.weaver.loadtime.Aj; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -54,6 +55,8 @@ public class SysNutritionalVideoController extends BaseController SysNutritionalVideo sysNutritionalVideos = sysNutritionalVideoService.selectSysNutritionalVideoById(id); if(sysNutritionalVideos != null && StringUtils.isNotEmpty(sysNutritionalVideos.getCoverUrl())){ sysNutritionalVideos.setPreviewUrl(AliyunOSSUtils.generatePresignedUrl(sysNutritionalVideos.getCoverUrl())); + }else{ + sysNutritionalVideos.setPreviewUrl(AliyunVideoUtils.getVideoCoverUrl(sysNutritionalVideos.getVideoId())); } return AjaxResult.success(sysNutritionalVideos); } @@ -149,4 +152,47 @@ public class SysNutritionalVideoController extends BaseController return AjaxResult.success(sysNutritionalVideos); } + /** + * 根据视频videoId提交视频截图请求 + */ + @PreAuthorize("@ss.hasPermi('custom:nutritionalVideo:add')") + @GetMapping("/submitVideoSnapshot") + public AjaxResult submitVideoSnapshot(@RequestParam("videoId")String videoId) + { + if(StringUtils.isEmpty(videoId)){ + return AjaxResult.error("视频资源不存在"); + } + AjaxResult result = AjaxResult.error("截图请求失败"); + try { + if(AliyunVideoUtils.submitVideoSnapshot(videoId)){ + result = AjaxResult.success(); + } + }catch (Exception e){ + e.printStackTrace(); + } + return result; + } + + /** + * 根据视频videoId获取视频截图 + */ + @PreAuthorize("@ss.hasPermi('custom:nutritionalVideo:add')") + @GetMapping("/getVideoSnapshot") + public AjaxResult getVideoSnapshot(@RequestParam("videoId")String videoId) + { + if(StringUtils.isEmpty(videoId)){ + return AjaxResult.error("视频资源不存在"); + } + AjaxResult result = AjaxResult.error("截图不存在"); + try { + List<String> videoSnapshotList = AliyunVideoUtils.getVideoSnapshot(videoId); + if(videoSnapshotList != null){ + result = AjaxResult.success(videoSnapshotList); + } + }catch (Exception e){ + e.printStackTrace(); + } + return result; + } + } \ No newline at end of file diff --git a/stdiet-common/src/main/java/com/stdiet/common/utils/AliyunVideoUtils.java b/stdiet-common/src/main/java/com/stdiet/common/utils/AliyunVideoUtils.java index 8a4151073..37e174d34 100644 --- a/stdiet-common/src/main/java/com/stdiet/common/utils/AliyunVideoUtils.java +++ b/stdiet-common/src/main/java/com/stdiet/common/utils/AliyunVideoUtils.java @@ -29,6 +29,9 @@ public class AliyunVideoUtils { public static final String search_field = "VideoId,Title,CoverURL,CateName,Tags,Status,Description,CreationTime"; + //默认截图模板 + public static final String defaultSnapshotTemplateId = "f8ccc3b5113ca8ea356ef9adf8c573b2"; + /** * 初始化视频点播Client * @return @@ -176,7 +179,7 @@ public class AliyunVideoUtils { * @return * @throws Exception */ - public static String updateVideo(String videoId, String title, String tags, String description, Long cateId) throws Exception{ + public static String updateVideo(String videoId, String title, String tags, String description, Long cateId, String coverUrl) throws Exception{ com.aliyun.vod20170321.Client client = AliyunVideoUtils.createClient(); if(StringUtils.isEmpty(videoId)){ return null; @@ -194,6 +197,30 @@ public class AliyunVideoUtils { if(cateId != null && cateId.longValue() > 0){ updateVideoInfoRequest.setCateId(cateId); } + if(StringUtils.isNotEmpty(coverUrl)){ + updateVideoInfoRequest.setCoverURL(coverUrl); + } + UpdateVideoInfoResponse updateVideoInfoResponse = client.updateVideoInfo(updateVideoInfoRequest); + if(updateVideoInfoResponse != null){ + return updateVideoInfoResponse.body.requestId; + } + return null; + } + + /** + * 更新视频封面 + * @param videoId 视频ID必须 + * @param coverUrl 封面 + * @return + * @throws Exception + */ + public static String updateVideoCoverUrl(String videoId, String coverUrl) throws Exception{ + com.aliyun.vod20170321.Client client = AliyunVideoUtils.createClient(); + if(StringUtils.isEmpty(videoId) || StringUtils.isEmpty(coverUrl)){ + return null; + } + UpdateVideoInfoRequest updateVideoInfoRequest = new UpdateVideoInfoRequest().setVideoId(videoId); + updateVideoInfoRequest.setCoverURL(coverUrl); UpdateVideoInfoResponse updateVideoInfoResponse = client.updateVideoInfo(updateVideoInfoRequest); if(updateVideoInfoResponse != null){ return updateVideoInfoResponse.body.requestId; @@ -208,7 +235,7 @@ public class AliyunVideoUtils { * @throws Exception */ public static String delVideo(String videoId) throws Exception{ - return updateVideo(videoId, null,null,null, default_delete_cateId); + return updateVideo(videoId, null,null,null, default_delete_cateId, null); } /** @@ -261,10 +288,52 @@ public class AliyunVideoUtils { return coverUrl; } + /** + * 根据VideoId提交截图请求 + * @param videoId + * @return + */ + public static boolean submitVideoSnapshot(String videoId){ + try{ + com.aliyun.vod20170321.Client client = AliyunVideoUtils.createClient(); + SubmitSnapshotJobRequest submitSnapshotJobRequest = new SubmitSnapshotJobRequest() + .setSnapshotTemplateId(defaultSnapshotTemplateId) + .setVideoId(videoId); + SubmitSnapshotJobResponse response = client.submitSnapshotJob(submitSnapshotJobRequest); + return response != null && response.body != null && response.body.snapshotJob != null; + }catch (Exception e){ + e.printStackTrace(); + } + return false; + } - - + /** + * 根据VideoId获取普通截图信息 + * @param videoId + * @return + */ + public static List<String> getVideoSnapshot(String videoId){ + List<String> snapshotList = new ArrayList<>(); + try{ + com.aliyun.vod20170321.Client client = AliyunVideoUtils.createClient(); + ListSnapshotsRequest listSnapshotsRequest = new ListSnapshotsRequest() + .setVideoId(videoId) + .setSnapshotType("NormalSnapshot"); + ListSnapshotsResponse response = client.listSnapshots(listSnapshotsRequest); + if(response != null && response.body != null){ + List<ListSnapshotsResponseBody.ListSnapshotsResponseBodyMediaSnapshotSnapshotsSnapshot> listSnapshots = response.body.mediaSnapshot.snapshots.snapshot; + if(listSnapshots != null && listSnapshots.size() > 0){ + for (ListSnapshotsResponseBody.ListSnapshotsResponseBodyMediaSnapshotSnapshotsSnapshot snapshot : listSnapshots) { + snapshotList.add(snapshot.url); + } + } + } + }catch (Exception e){ + e.printStackTrace(); + } + return snapshotList; + } } diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysDishesMapper.java b/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysDishesMapper.java index 58cea23e5..6df35fb08 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysDishesMapper.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/mapper/SysDishesMapper.java @@ -3,9 +3,11 @@ package com.stdiet.custom.mapper; import com.stdiet.custom.domain.SysDishes; import com.stdiet.custom.domain.SysDishesIngredient; import com.stdiet.custom.domain.SysPhysicalSignsObj; +import org.apache.ibatis.annotations.Param; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * 菜品Mapper接口 @@ -80,4 +82,12 @@ public interface SysDishesMapper { int deleteDishesNotRecByDishesId(Long dishesId); + + /** + * 根据菜品ID查询大类小类名称 + * @param dishId + * @return + */ + String getDishClassNameById(@Param("dishId")Long dishId); + } \ No newline at end of file diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysDishesService.java b/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysDishesService.java index 03f058e11..765823661 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysDishesService.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/service/ISysDishesService.java @@ -1,6 +1,8 @@ package com.stdiet.custom.service; import java.util.List; +import java.util.Map; + import com.stdiet.custom.domain.SysDishes; import com.stdiet.custom.domain.SysDishesIngredient; import com.stdiet.custom.domain.SysIngredient; @@ -66,4 +68,11 @@ public interface ISysDishesService public String getDishesMenuTypeById(Long id); + /** + * 根据菜品ID查询大类小类名称 + * @param dishId + * @return + */ + String getDishClassNameById(Long dishId); + } \ No newline at end of file diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysDishesServiceImpl.java b/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysDishesServiceImpl.java index 906493262..c3e01cfa5 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysDishesServiceImpl.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysDishesServiceImpl.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * 菜品Service业务层处理 @@ -183,4 +184,15 @@ public class SysDishesServiceImpl implements ISysDishesService { return sysDishesMapper.getDishesMenuTypeById(id); } + + /** + * 根据菜品ID查询大类小类名称 + * @param dishId + * @return + */ + @Override + public String getDishClassNameById(Long dishId){ + return sysDishesMapper.getDishClassNameById(dishId); + } + } \ No newline at end of file diff --git a/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysNutritionalVideoServiceImpl.java b/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysNutritionalVideoServiceImpl.java index 3fa709ac8..237f05cde 100644 --- a/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysNutritionalVideoServiceImpl.java +++ b/stdiet-custom/src/main/java/com/stdiet/custom/service/impl/SysNutritionalVideoServiceImpl.java @@ -106,6 +106,17 @@ public class SysNutritionalVideoServiceImpl implements ISysNutritionalVideoServi public int insertSysNutritionalVideo(SysNutritionalVideo sysNutritionalVideo) { sysNutritionalVideo.setCreateTime(DateUtils.getNowDate()); + //判断封面是上传的还是阿里云视频截图封面 + if(isSnapshot(sysNutritionalVideo.getCoverUrl())){ + //更新阿里云视频封面 + try{ + AliyunVideoUtils.updateVideoCoverUrl(sysNutritionalVideo.getVideoId(), sysNutritionalVideo.getCoverUrl()); + }catch (Exception e){ + e.printStackTrace(); + return 0; + } + sysNutritionalVideo.setCoverUrl(""); + } return sysNutritionalVideoMapper.insertSysNutritionalVideo(sysNutritionalVideo); } @@ -119,19 +130,23 @@ public class SysNutritionalVideoServiceImpl implements ISysNutritionalVideoServi public int updateSysNutritionalVideo(SysNutritionalVideo sysNutritionalVideo) { sysNutritionalVideo.setUpdateTime(DateUtils.getNowDate()); + sysNutritionalVideo.setCoverUrl(sysNutritionalVideo.getCoverUrl() == null ? "" : sysNutritionalVideo.getCoverUrl()); + String coverUrl = sysNutritionalVideo.getCoverUrl(); + //判断封面是上传的还是阿里云视频截图封面 + sysNutritionalVideo.setCoverUrl(isSnapshot(coverUrl) ? "" : coverUrl); int row = sysNutritionalVideoMapper.updateSysNutritionalVideo(sysNutritionalVideo); if(row > 0){ - updateAliyunVideo(sysNutritionalVideo.getId()); + sysNutritionalVideo.setCoverUrl(isSnapshot(coverUrl) ? coverUrl : null); + updateAliyunVideo(sysNutritionalVideo); } return row; } @Async - public void updateAliyunVideo(Long id){ + public void updateAliyunVideo(SysNutritionalVideo sysNutritionalVideo){ try{ - SysNutritionalVideo sysNutritionalVideo = selectSysNutritionalVideoById(id); if(sysNutritionalVideo != null && sysNutritionalVideo.getVideoId() != null){ - AliyunVideoUtils.updateVideo(sysNutritionalVideo.getVideoId(), sysNutritionalVideo.getTitle(), sysNutritionalVideo.getTags(), sysNutritionalVideo.getDescription(), null); + AliyunVideoUtils.updateVideo(sysNutritionalVideo.getVideoId(), sysNutritionalVideo.getTitle(), sysNutritionalVideo.getTags(), sysNutritionalVideo.getDescription(), null, sysNutritionalVideo.getCoverUrl()); } }catch (Exception e){ e.printStackTrace(); @@ -270,4 +285,15 @@ public class SysNutritionalVideoServiceImpl implements ISysNutritionalVideoServi return sysNutritionalVideoMapper.updateVideoPlayNum(videoId); } + /** + * 判断是否为阿里点播的截图 + * @param url + * @return + */ + private boolean isSnapshot(String url){ + return StringUtils.isNotEmpty(url) && url.startsWith("http://outin"); + } + + + } \ No newline at end of file diff --git a/stdiet-custom/src/main/resources/mapper/custom/SysDishesMapper.xml b/stdiet-custom/src/main/resources/mapper/custom/SysDishesMapper.xml index 2ef345d3d..da6d88115 100644 --- a/stdiet-custom/src/main/resources/mapper/custom/SysDishesMapper.xml +++ b/stdiet-custom/src/main/resources/mapper/custom/SysDishesMapper.xml @@ -207,4 +207,12 @@ delete from sys_dishes_not_rec where dishes_id=#{dishesId} </delete> + <!-- 根据菜品ID查询菜品对应大类小类名称 --> + <select id="getDishClassNameById" parameterType="Long" resultType="String"> + select concat(IFNULL(big.dict_label,''),'/',IFNULL(small.dict_label,'')) as className from sys_dishes dish + LEFT JOIN (SELECT dict_label, dict_value FROM sys_dict_data WHERE dict_type = 'dish_class_big') AS big ON big.dict_value = dish.big_class + LEFT JOIN (SELECT dict_label, dict_value FROM sys_dict_data WHERE dict_type = 'dish_class_small') AS small ON small.dict_value = dish.small_class + where dish.id = #{dishId} and dish.del_flag = 0 + </select> + </mapper> \ No newline at end of file diff --git a/stdiet-ui/src/api/custom/nutritionalVideo.js b/stdiet-ui/src/api/custom/nutritionalVideo.js index 649406caa..d4c88087f 100644 --- a/stdiet-ui/src/api/custom/nutritionalVideo.js +++ b/stdiet-ui/src/api/custom/nutritionalVideo.js @@ -78,3 +78,23 @@ export function getVideoPlayUrlById(id) { }) } +// 根据视频videoId提交视频截图请求 +export function submitVideoSnapshot(id) { + return request({ + url: '/custom/nutritionalVideo/submitVideoSnapshot', + method: 'get', + params: {'videoId': id} + }) +} + +//根据视频videoId获取视频截图 +export function getVideoSnapshot(id) { + return request({ + url: '/custom/nutritionalVideo/getVideoSnapshot', + method: 'get', + params: {'videoId': id} + }) +} + + + diff --git a/stdiet-ui/src/api/custom/recipes.js b/stdiet-ui/src/api/custom/recipes.js index 3842cc8d1..4fe01a607 100644 --- a/stdiet-ui/src/api/custom/recipes.js +++ b/stdiet-ui/src/api/custom/recipes.js @@ -45,3 +45,17 @@ export function replaceMenuApi(data) { data }); } + +/** + * 根据菜品ID查询大类小类名称 + * @param dishId + */ +export function getDishClassNameById(dishId) { + return request({ + url: "/custom/recipes/getDishClassNameById", + method: "get", + params: {'dishId':dishId} + }); +} + + diff --git a/stdiet-ui/src/components/FileUpload/UploadFile.vue b/stdiet-ui/src/components/FileUpload/UploadFile.vue index cd1cb78e1..62b4e31b7 100644 --- a/stdiet-ui/src/components/FileUpload/UploadFile.vue +++ b/stdiet-ui/src/components/FileUpload/UploadFile.vue @@ -8,7 +8,7 @@ :accept="'.png,.jpg'" :before-upload="beforeAvatarUpload"> <img v-if="imageUrl || coverUrl" :src="imageUrl || coverUrl" class="avatar"> - <i v-else class="el-icon-plus avatar-uploader-icon"></i> + <i v-else class="el-icon-plus avatar-uploader-icon" title="手动上传图片"></i> <div class="el-upload__tip" slot="tip" style="color:#1890ff"> <el-button v-if="imageUrl || coverUrl" size="small" type="danger" @click="removeFile">移除</el-button> diff --git a/stdiet-ui/src/components/UploadVideo/index.vue b/stdiet-ui/src/components/UploadVideo/index.vue index af0031d96..c8ed8cbfb 100644 --- a/stdiet-ui/src/components/UploadVideo/index.vue +++ b/stdiet-ui/src/components/UploadVideo/index.vue @@ -17,15 +17,11 @@ placeholder="请输入视频描述" v-model.trim="videoFrom.description" maxlength="1000" - rows="3" + rows="2" show-word-limit /> </el-form-item> - - <el-form-item label="视频封面" prop="coverUrl"> - <UploadFile ref="uploadFile" :prefix="'videoCover'" @callbackMethod="handleCoverUrl" :tips="'视频未传封面图片时,会主动截取封面,但会存在延迟,请勿直接发布到小程序'"></UploadFile> - </el-form-item> - <div style="display:flex"> + <div style="display:flex"> <el-form-item label="视频类别" prop="cateId" style="width:300px"> <treeselect v-model="videoFrom.cateId" @@ -46,40 +42,47 @@ </el-select> </el-form-item> </div> - <el-form-item label="视频文件" prop="file"> <div> <input type="file" accept=".mp4" ref="videoFile" id="videoFile" @change="fileChange($event)"> <div > <span>上传状态:{{statusText}}</span><span style="margin-left:100px">进度:{{authProgress}}%</span></div> <div style="color:#1890ff"> - 1、只能上传mp4文件,上传大文件时请使用客户端上传,防止上传超时 + <el-button type="primary" @click="authUpload" :disabled="uploadDisabled" size="small" icon="el-icon-upload2">上传视频</el-button><span style="margin-left:20px">1、只能上传mp4格式视频</span> </div> + </div> </el-form-item> + <el-form-item label="视频封面" prop="coverUrl"> + <UploadFile ref="uploadFile" :prefix="'videoCover'" :coverUrl="videoFrom.previewUrl" @callbackMethod="handleCoverUrl" :tips="''"></UploadFile> + <el-button type="primary" size="small" icon="el-icon-film" @click="selectVideoCover" :disabled="!uploadVideoFlag" title="上传视频之后选择视频截图作为封面">选择封面</el-button> + </el-form-item> <el-form-item label="展示状态" prop="wxShow"> <el-switch v-model="videoFrom.wxShow" - active-text="小程序展示" - inactive-text="小程序不展示"> + active-text="展示" + inactive-text="不展示"> </el-switch> - <div style="color:red">提示:请保证内容正确再展示到小程序</div> + <div style="color:red">提示:开启展示之后客户可看到该视频,请保证内容正确再展示</div> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> - <el-button type="primary" @click="authUpload" :disabled="uploadDisabled">开始上传</el-button> + <el-button type="primary" @click="submitVideoForm">保存</el-button> <el-button @click="cancel">取 消</el-button> </div> + <!-- 手动选择封面 --> + <VideoSelectCover ref="videoSelectCoverRef"></VideoSelectCover> </el-dialog> </template> <script> import axios from 'axios' - import {getUploadVideoAuth,addNutritionalVideo } from "@/api/custom/nutritionalVideo"; + import {getUploadVideoAuth,addNutritionalVideo,getVideoSnapshot,submitVideoSnapshot } from "@/api/custom/nutritionalVideo"; import {getAllClassify } from "@/api/custom/videoClassify"; import UploadFile from "@/components/FileUpload/UploadFile"; import Treeselect from "@riophae/vue-treeselect"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import IconSelect from "@/components/IconSelect"; + import VideoSelectCover from "@/components/VideoSelectCover"; export default { name: "UploadVideo", data () { @@ -122,6 +125,8 @@ statusText: '', fileType:['mp4','MP4'], uploading: false, + //视频是否上传成功标识 + uploadVideoFlag: false } }, created(){ @@ -142,7 +147,7 @@ }, components: { - UploadFile,Treeselect, IconSelect + UploadFile,Treeselect, IconSelect,VideoSelectCover }, methods: { showDialog(classifyList, callback){ @@ -167,7 +172,8 @@ tags: null, payLevel: this.defaultPayLevel ? parseInt(this.defaultPayLevel) : null, videoId: null, - wxShow: false + wxShow: false, + previewUrl: null }; if(this.$refs.uploadFile){ this.$refs.uploadFile.resetUpload(); @@ -180,11 +186,53 @@ this.uploader = null; this.statusText = ''; this.uploading = false; + this.uploadVideoFlag = false; this.resetForm("videoFrom"); }, submitVideoForm(){ + this.$refs["videoFrom"].validate((valid) => { + if (valid) { + //视频分类不能选择主分类 + if(this.videoFrom.cateId == 0){ + this.$message({ + message: "视频分类不能选择主分类", + type: "warning", + }); + return; + } + if(this.uploading){ + this.$message({ + message: "视频正在上传,请勿取消", + type: "warning", + }); + return; + } + if(!this.uploadVideoFlag){ + this.$message({ + message: "请先上传视频再保存", + type: "warning", + }); + return; + } + this.videoFrom.showFlag = this.videoFrom.wxShow ? 1 : 0; + addNutritionalVideo(this.videoFrom).then(response => { + if (response.code === 200) { + this.msgSuccess("视频保存成功"); + this.open = false; + this.callback && this.callback(); + } + }) + } + }); }, + selectVideoCover(){ + this.$refs.videoSelectCoverRef.showDialog(this.videoFrom,(url)=>{ + //console.log(url); + this.videoFrom.previewUrl = url; + this.videoFrom.coverUrl = url; + }); + }, /** 转换菜单数据结构 */ normalizer(node) { if (node.children && !node.children.length) { @@ -205,7 +253,7 @@ cancel(){ if(this.uploading){ this.$message({ - message: "文件正在上传,请勿取消", + message: "视频正在上传,请勿取消", type: "warning", }); return; @@ -219,7 +267,7 @@ if (valid) { if(this.uploading){ this.$message({ - message: "文件正在上传,请勿取消", + message: "视频正在上传,请勿取消", type: "warning", }); return; @@ -242,7 +290,7 @@ this.videoFrom.fileName = this.file.name; if(this.videoFrom.fileName == null || this.videoFrom.fileName.length == 0 || this.videoFrom.fileName.lastIndexOf(".") == -1){ this.$message({ - message: "当前文件名称错误", + message: "当前视频名称错误", type: "warning", }); return; @@ -250,7 +298,7 @@ let fileType = this.videoFrom.fileName.substring(this.videoFrom.fileName.lastIndexOf(".")+1); if(this.fileType.indexOf(fileType) == -1){ this.$message({ - message: "当前文件格式错误", + message: "当前视频格式错误", type: "warning", }); return; @@ -268,8 +316,8 @@ this.resumeDisabled = true } }) - } - }); + } + }); }, authUpload () { // 然后调用 startUpload 方法, 开始上传 @@ -309,7 +357,7 @@ addFileSuccess: function (uploadInfo) { self.uploadDisabled = false self.resumeDisabled = false - self.statusText = '添加文件成功, 等待上传...' + self.statusText = '添加视频成功, 等待上传...' console.log("addFileSuccess: " + uploadInfo.file.name) }, // 开始上传 @@ -322,32 +370,32 @@ // 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html) // 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html) uploader.setUploadAuthAndAddress(uploadInfo, self.uploadAuth.uploadAuth, self.uploadAuth.uploadAddress, self.uploadAuth.videoId) - self.statusText = '文件开始上传...' + self.statusText = '视频开始上传...' console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object) }, // 文件上传成功 onUploadSucceed: function (uploadInfo) { console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object) - self.statusText = '文件上传成功!' + self.statusText = '视频上传成功!' }, // 文件上传失败 onUploadFailed: function (uploadInfo, code, message) { console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message); self.uploading = false; - self.statusText = '文件上传失败!' + self.statusText = '视频上传失败!' }, // 取消文件上传 onUploadCanceled: function (uploadInfo, code, message) { console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message) - self.statusText = '文件已暂停上传' + self.statusText = '视频已暂停上传' }, // 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上 onUploadProgress: function (uploadInfo, totalSize, progress) { console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%") let progressPercent = Math.ceil(progress * 100) self.authProgress = progressPercent - self.statusText = '文件上传中...' + self.statusText = '视频上传中...' }, // 上传凭证超时 onUploadTokenExpired: function (uploadInfo) { @@ -360,23 +408,20 @@ uploader.resumeUploadWithAuth(uploadAuth) console.log('upload expired and resume upload with uploadauth ' + uploadAuth) })*/ - self.statusText = '文件上传超时...'; + self.statusText = '视频上传超时...'; self.uploading = false; }, // 全部文件上传结束 onUploadEnd: function (uploadInfo) { - self.statusText = '文件上传完毕' + self.statusText = '视频上传完毕' self.uploading = false; - //self.msgSuccess("上传成功"); - self.videoFrom.showFlag = self.videoFrom.wxShow ? 1 : 0; - addNutritionalVideo(self.videoFrom).then(response => { - if (response.code === 200) { - self.msgSuccess("视频上传成功"); - self.open = false; - self.callback && self.callback(); + self.uploadVideoFlag = true; + submitVideoSnapshot(self.videoFrom.videoId).then(response => { + if(response.code == 200){ + console.log("-- 截图成功 --"); } - }) + }) } }) return uploader diff --git a/stdiet-ui/src/components/VideoSelectCover/index.vue b/stdiet-ui/src/components/VideoSelectCover/index.vue new file mode 100644 index 000000000..bb2bcbbe1 --- /dev/null +++ b/stdiet-ui/src/components/VideoSelectCover/index.vue @@ -0,0 +1,69 @@ +<template> +<el-dialog title="选择视频封面(每隔两秒一张截图,共十张)" :visible.sync="open" width="650px" style="height: 80%; overflow: auto;" :close-on-click-modal="true" :show-close="true" append-to-body> + <div v-if="!showFlag"><el-divider>{{showStatusText}}</el-divider></div> + <div v-else class="demo-image__lazy" style="margin-left:20px;"> + <div v-for="url in videoSnapshotList" :key="url"> + <el-image :src="url" lazy style="width:550px;height:300px;margin-top:10px;cursor:pointer" title="点击选择" @click="selectVideoSnapshot(url)"></el-image> + <el-divider></el-divider> + </div> + </div> + <div slot="footer" class="dialog-footer"> + <el-button @click="cancel">取 消</el-button> + </div> + +</el-dialog> + +</template> +<script> + import axios from 'axios' + import {getVideoSnapshot } from "@/api/custom/nutritionalVideo"; + export default { + name: "VideoSelectCover", + data () { + return { + open: false, + videoData: null, + callback: undefined, + showFlag: false, + showStatusText:"加载中...", + videoSnapshotList:[] + } + }, + created(){ + + }, + components: { + + }, + methods: { + showDialog(data,callback){ + if(data && data.videoId != null){ + this.videoSnapshotList = []; + this.showFlag = false; + this.showStatusText = "加载中..."; + this.open = true; + this.videoData = data; + this.callback = callback; + this.getVideoSnapshot(); + } + }, + getVideoSnapshot(){ + getVideoSnapshot(this.videoData.videoId).then(response => { + if(response.code == 200 && response.data.length > 0){ + this.videoSnapshotList = response.data; + this.showFlag = true; + }else{ + this.showStatusText = "暂无截图数据!" + } + }) + }, + selectVideoSnapshot(url){ + this.callback && this.callback(url); + this.open = false; + }, + cancel(){ + this.open = false; + } + } + } +</script> \ No newline at end of file diff --git a/stdiet-ui/src/store/modules/recipes.js b/stdiet-ui/src/store/modules/recipes.js index 1fbe644f8..23e4e2a08 100644 --- a/stdiet-ui/src/store/modules/recipes.js +++ b/stdiet-ui/src/store/modules/recipes.js @@ -555,6 +555,7 @@ const actions = { id: new Date().getTime(), name: tarDishes.name, type: response.data.type.split(",").sort(), + className: response.data.className, //大类小类名称 data: tarDishes }).then(() => { window.postMessage( diff --git a/stdiet-ui/src/utils/shortCutUtils.js b/stdiet-ui/src/utils/shortCutUtils.js index 53fe07159..df49202b9 100644 --- a/stdiet-ui/src/utils/shortCutUtils.js +++ b/stdiet-ui/src/utils/shortCutUtils.js @@ -1,8 +1,19 @@ -export function getShortCut() { +export function getShortCut(key) { return new Promise((res, rej) => { try { const data = JSON.parse(localStorage.getItem("shortCut") || "[]"); - res(data); + //关键字检索 + if(key != undefined && key != null && key != ""){ + const resultData = []; + data.forEach((item,index) => { + if(item.name.indexOf(key) != -1 || (item.className != undefined && item.className != null && item.className.indexOf(key) != -1)){ + resultData.push(item); + } + }); + res(resultData); + }else{ + res(data); + } } catch (error) { rej(error); } @@ -35,6 +46,19 @@ export async function removeShortCut(id) { }); } +export async function removeMuchShortCut(ids) { + const shortCutList = await getShortCut(); + return new Promise((res, rej) => { + try { + const newShortCutList = shortCutList.filter(obj => ids.indexOf(obj.id) == -1); + localStorage.setItem("shortCut", JSON.stringify(newShortCutList)); + res(); + } catch (error) { + rej(error); + } + }); +} + export async function editShortCut(data) { const shortCutList = await getShortCut(); return new Promise((res, rej) => { diff --git a/stdiet-ui/src/views/custom/nutritionalVideo/index.vue b/stdiet-ui/src/views/custom/nutritionalVideo/index.vue index dea7f8b97..fa8e6b55c 100644 --- a/stdiet-ui/src/views/custom/nutritionalVideo/index.vue +++ b/stdiet-ui/src/views/custom/nutritionalVideo/index.vue @@ -9,7 +9,7 @@ size="small" /> </el-form-item> - <el-form-item label="小程序展示状态" prop="showFlag" label-width="200"> + <el-form-item label="展示状态" prop="showFlag" label-width="200"> <el-select v-model="queryParams.showFlag" placeholder="请选示状态" @@ -131,7 +131,7 @@ <!--<el-table-column label="标签" align="center" prop="tags" width="100"/>--> <el-table-column label="分类" align="center" prop="cateName" width="100"/> <el-table-column label="权限等级" align="center" prop="payLevelName" width="100"/> - <el-table-column label="小程序展示状态" align="center" prop="showFlag" width="200"> + <el-table-column label="展示状态" align="center" prop="showFlag" width="200"> <template slot-scope="scope" > <el-switch v-model="scope.row.wxShow" @@ -208,20 +208,26 @@ show-word-limit /> </el-form-item> - <el-form-item label="视频封面" prop="coverUrl"> - <UploadFile ref="uploadFile" v-if="open" :prefix="'videoCover'" :coverUrl="form.previewUrl" @callbackMethod="handleCoverUrl" :tips="'视频未传封面图片时,会主动截取封面,但会存在延迟,请勿直接发布到小程序'"></UploadFile> - </el-form-item> - <el-form-item label="视频类别" prop="cateId"> - <el-select v-model="form.cateId" clearable filterable placeholder="请选择类别"> + <div style="display:flex"> + <el-form-item label="视频类别" prop="cateId" style="width:300px"> + <!--<el-select v-model="form.cateId" clearable filterable placeholder="请选择类别"> <el-option v-for="classify in classifyList" :key="classify.id" :label="classify.cateName" :value="classify.id" /> - </el-select> + </el-select>--> + <treeselect + v-model="form.cateId" + :options="classifyList" + :normalizer="normalizer" + :show-count="true" + placeholder="选择分类" + style="width:200px" + /> </el-form-item> - <el-form-item label="视频权限" prop="payLevel"> + <el-form-item label="视频权限" prop="payLevel" style="margin-left:40px"> <el-select v-model="form.payLevel" clearable filterable placeholder="请选择权限"> <el-option v-for="dict in payVideoLevelList" @@ -231,13 +237,18 @@ /> </el-select> </el-form-item> + </div> + <el-form-item label="视频封面" prop="coverUrl"> + <UploadFile ref="uploadFile" v-if="open" :prefix="'videoCover'" :coverUrl="form.previewUrl" @callbackMethod="handleCoverUrl" :tips="''"></UploadFile> + <el-button type="primary" size="small" icon="el-icon-film" @click="selectVideoCover" title="上传视频之后选择视频截图作为封面">选择封面</el-button> + </el-form-item> <el-form-item label="展示状态" prop="wxShow"> <el-switch v-model="form.wxShow" - active-text="小程序展示" - inactive-text="小程序不展示"> + active-text="展示" + inactive-text="不展示"> </el-switch> - <div style="color:red">提示:请保证内容正确再展示到小程序</div> + <div style="color:red">提示:开启展示之后客户可看到该视频,请保证内容正确再展示</div> </el-form-item> </el-form> @@ -255,6 +266,9 @@ <VideoClassify ref="videoClassifyRef"></VideoClassify> </div> </el-dialog> + + <!-- 手动选择封面 --> + <VideoSelectCover ref="videoSelectCoverRef"></VideoSelectCover> </div> </template> @@ -268,6 +282,7 @@ import Treeselect from "@riophae/vue-treeselect"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import IconSelect from "@/components/IconSelect"; + import VideoSelectCover from "@/components/VideoSelectCover"; export default { name: "NutritionalVideo", data() { @@ -316,6 +331,8 @@ coverImageList:[], //分类列表 classifyList:[], + //所有分类 + allClassifyList:[], //权限等级列表 payVideoLevelList:[], //视频分类弹窗显示标识 @@ -331,7 +348,7 @@ }); }, components: { - UploadVideo,UploadFile,VideoClassify,AutoHideMessage,Treeselect, IconSelect + UploadVideo,UploadFile,VideoClassify,AutoHideMessage,Treeselect, IconSelect,VideoSelectCover }, methods: { /** 查询营养视频列表 */ @@ -350,6 +367,7 @@ getAllVideoClassify(){ getAllClassify().then(response => { if(response.code == 200){ + this.allClassifyList = response.data; this.classifyList = []; const classify = { id: 0, cateName: '主分类', children: [] }; classify.children = this.handleTree(response.data, "id"); @@ -366,6 +384,7 @@ reset() { this.form = { id: null, + videoId:null, cateId: null, coverUrl: null, title: null, @@ -373,10 +392,18 @@ tags: null, payLevel:null, showFlag: null, - wxShow: false + wxShow: false, + previewUrl: null }; this.resetForm("form"); }, + selectVideoCover(){ + this.$refs.videoSelectCoverRef.showDialog(this.form,(url)=>{ + //console.log(url); + this.form.previewUrl = url; + this.form.coverUrl = url; + }); + }, /** 搜索按钮操作 */ handleQuery() { this.queryParams.pageNum = 1; @@ -394,7 +421,7 @@ this.multiple = !selection.length }, clickUploadVideo(){ - this.$refs.uploadVideoRef.showDialog(this.classifyList, ()=>{ + this.$refs.uploadVideoRef.showDialog(this.allClassifyList, ()=>{ this.getList(); }); }, @@ -443,6 +470,14 @@ this.$refs["form"].validate(valid => { if (valid) { this.form.showFlag = this.form.wxShow ? 1 : 0; + //视频分类不能选择主分类 + if(this.form.cateId == 0){ + this.$message({ + message: "视频分类不能选择主分类", + type: "warning", + }); + return; + } if (this.form.id != null) { updateNutritionalVideo(this.form).then(response => { if (response.code === 200) { diff --git a/stdiet-ui/src/views/custom/recipesBuild/InfoView/ShortCutCom/index.vue b/stdiet-ui/src/views/custom/recipesBuild/InfoView/ShortCutCom/index.vue index 3693aa495..1499eff0b 100644 --- a/stdiet-ui/src/views/custom/recipesBuild/InfoView/ShortCutCom/index.vue +++ b/stdiet-ui/src/views/custom/recipesBuild/InfoView/ShortCutCom/index.vue @@ -1,14 +1,32 @@ <template> <div class="short_cut_com_wrapper"> - <div class="header"> - <el-button icon="el-icon-refresh" size="mini" @click="getList" circle /> - </div> + <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px"> + <el-form-item label="关键词" prop="key" > + <el-input + v-model.trim="queryParams.key" + placeholder="请输入名称/种类" + clearable + @clear="getList" + @keyup.native="getList" + size="small" + /> + </el-form-item> + <el-form-item> + <el-button type="cyan" icon="el-icon-search" size="mini" @click="getList">搜索</el-button> + </el-form-item> + </el-form> + + <div class="header"> + <el-button icon="el-icon-search" size="mini" @click="showSearch = !showSearch" circle :title="showSearch ? '隐藏搜索' : '显示搜索'"/> + <el-button icon="el-icon-refresh" size="mini" @click="getList" circle title="刷新"/> + <el-button icon="el-icon-delete" size="mini" circle :title="'清空快捷列表'" @click="handleOnMuchDelete"/> + </div> <el-table :data="dataList" ref="shortCutTable" highlight-current-row @current-change="handleOnCurrentChange" - height="800" + height="700" > <el-table-column prop="name" label="对象" align="center"> <template slot-scope="scope"> @@ -95,6 +113,7 @@ import { getShortCut, removeShortCut, editShortCut, + removeMuchShortCut } from "@/utils/shortCutUtils"; import AutoHideInfo from "@/components/AutoHideInfo"; import { createNamespacedHelpers } from "vuex"; @@ -113,6 +132,10 @@ export default { return { dataList: [], modifingId: 0, + showSearch: false, + queryParams:{ + key: null + } }; }, created() { @@ -147,8 +170,12 @@ export default { } }, getList(setCurrent) { - getShortCut().then((data) => { + getShortCut(this.queryParams.key).then((data) => { this.dataList = data; + //超过10个就显示搜索按钮 + if(this.dataList && this.dataList.length > 5 && !this.showSearch){ + this.showSearch = true; + } // console.log(this.dataList); if (setCurrent) { this.$refs.shortCutTable.setCurrentRow(data[0]); @@ -163,6 +190,27 @@ export default { } }); }, + handleOnMuchDelete() { + if(this.dataList && this.dataList.length > 0){ + let ids = []; + this.dataList.forEach((item,index) => { + ids.push(item.id); + }); + this.$confirm("是否确定清除当前 "+ids.length+" 条快捷数据?", "警告", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: "warning", + }).then(function () { + return removeMuchShortCut(ids); + }).then((response) => { + this.getList(); + if (ids.indexOf(this.curShortCutObj.id) != -1) { + this.setCurShortCutObj({}); + } + }) + .catch(function () {}); + } + }, handleOnCurrentChange(data) { this.setCurShortCutObj({ data }); }, diff --git a/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue b/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue index 2eb697373..b5dab009f 100644 --- a/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue +++ b/stdiet-ui/src/views/custom/recipesBuild/RecipesView/RecipesCom/index.vue @@ -342,6 +342,9 @@ </div> </template> <script> +import { + getDishClassNameById +} from "@/api/custom/recipes"; import { createNamespacedHelpers } from "vuex"; const { mapActions,