diff --git a/package.json b/package.json index e12f43d..20e074a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "dependencies": { "axios": "0.24.0", "core-js": "^3.8.3", + "@easydarwin/easywasmplayer": "^4.0.10", + "emittery": "^0.8.1", + "logt": "^1.4.1", "dayjs": "^1.11.9", "element-ui": "^2.15.13", "file-saver": "2.0.5", @@ -17,7 +20,6 @@ "less": "^4.1.3", "less-loader": "^11.1.3", "vue": "^2.6.14", - "vue-h265-player": "^0.0.24", "vue-router": "^4.2.2" }, "devDependencies": { @@ -27,7 +29,7 @@ "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", - "eslint-plugin-vue": "^8.0.3", + "eslint-plugin-vue": "^8.0.3", "vue-template-compiler": "^2.6.14" }, "eslintConfig": { diff --git a/public/css/largeScreenStyle.css b/public/css/largeScreenStyle.css index 16bc603..17bf225 100644 --- a/public/css/largeScreenStyle.css +++ b/public/css/largeScreenStyle.css @@ -3291,4 +3291,8 @@ table{ .scroll::-webkit-scrollbar-thumb { background-color: rgb(1, 169, 255); border-radius: 4px; +} +.bg-date-picker-pop{ + background-image: url('../images/list_bgd.png'); + border:solid 1px rgb(1, 169, 255); } \ No newline at end of file diff --git a/src/components/h265-player/assets/play-button.svg b/src/components/h265-player/assets/play-button.svg new file mode 100644 index 0000000..b20de78 --- /dev/null +++ b/src/components/h265-player/assets/play-button.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/h265-player/index.js b/src/components/h265-player/index.js new file mode 100644 index 0000000..924b3e4 --- /dev/null +++ b/src/components/h265-player/index.js @@ -0,0 +1,7 @@ +import H265PlayerComponent from './index.vue' +export { H265Player, H265PlayerConstants } from './utils/player' + +H265PlayerComponent.install = Vue => + Vue.component('H265Player', H265PlayerComponent) + +export default H265PlayerComponent diff --git a/src/components/h265-player/index.vue b/src/components/h265-player/index.vue new file mode 100644 index 0000000..edf8e1c --- /dev/null +++ b/src/components/h265-player/index.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/src/components/h265-player/lib/libDecoder.wasm b/src/components/h265-player/lib/libDecoder.wasm new file mode 100644 index 0000000..a45028d Binary files /dev/null and b/src/components/h265-player/lib/libDecoder.wasm differ diff --git a/src/components/h265-player/utils/logger.js b/src/components/h265-player/utils/logger.js new file mode 100644 index 0000000..60f2ba1 --- /dev/null +++ b/src/components/h265-player/utils/logger.js @@ -0,0 +1,20 @@ +import LogT from 'logt'; +const TAG = 'h265-player'; +const log = new LogT(5); +export const logger = { + ok: (...args) => { + log.silly(TAG, '', ...args); + }, + info: (...args) => { + log.info(TAG, '', ...args); + }, + warn: (...args) => { + log.warn(TAG, '', ...args); + }, + fatal: (...args) => { + log.error(TAG, '', ...args); + }, + verbose: (...args) => { + log.verbose(TAG, '', ...args); + } +}; diff --git a/src/components/h265-player/utils/player.js b/src/components/h265-player/utils/player.js new file mode 100644 index 0000000..ffeb89c --- /dev/null +++ b/src/components/h265-player/utils/player.js @@ -0,0 +1,204 @@ +import WasmPlayer from '@easydarwin/easywasmplayer/EasyWasmPlayer.js'; +import Emittery from 'emittery'; +import { v4 as uuidv4 } from 'uuid'; +import axios from 'axios'; +import { logger } from './logger'; +/** + * 获取一个随机的 元素id,用作播放器的元素id + */ +export const getRandomElemId = () => { + return uuidv4(); +}; +export var H265PlayerConstants; +(function (H265PlayerConstants) { + H265PlayerConstants["on_play"] = "on_play"; + H265PlayerConstants["on_stop"] = "on_stop"; + H265PlayerConstants["on_endLoading"] = "on_endLoading"; + H265PlayerConstants["on_pause"] = "on_pause"; + H265PlayerConstants["on_resume"] = "on_resume"; + H265PlayerConstants["on_dispose"] = "on_dispose"; + H265PlayerConstants["on_error"] = "on_error"; + H265PlayerConstants["on_player_cb"] = "on_player_cb"; +})(H265PlayerConstants || (H265PlayerConstants = {})); +export class H265Player extends Emittery { + /** + * @param elemId 播放器元素id + * @param maxRetryCount 最大尝试重连次数 + */ + constructor(elemId, maxRetryCount = 3) { + super(); + this.timer = 0; + this.currentUrl = ''; + this.elemId = elemId; + this.maxRetryCount = maxRetryCount <= 0 ? 3 : maxRetryCount; + this.retryCount = this.maxRetryCount; + this.init(); + } + init() { + if (this.player) { + this.dispose(); + } + logger.verbose('init player ~'); + this.player = new WasmPlayer(null, this.elemId, this.playerCbFunc.bind(this), { + Height: true, + HideKbs: true + }); + } + play(url) { + var _a; + if (!url) { + logger.fatal('url is not valid'); + this.dispose(); + return; + } + logger.ok('start to play ~'); + // eslint-disable-next-line no-unused-expressions + (_a = this.player) === null || _a === void 0 ? void 0 : _a.play(url, 1); + this.currentUrl = url; + this.startUrlHeartBeat(); + } + pause() { + var _a; + logger.warn('player pause'); + this.emit(H265PlayerConstants.on_pause); + // eslint-disable-next-line no-unused-expressions + (_a = this.player) === null || _a === void 0 ? void 0 : _a.destroy(); + this.player = null; + this.stopUrlHeartBeat(); + } + resume() { + logger.info('player resume'); + this.emit(H265PlayerConstants.on_resume); + this.init(); + this.play(this.currentUrl); + } + playerCbFunc(type) { + logger.info('player cb function:', type); + this.emit(H265PlayerConstants.on_player_cb, type); + switch (type) { + case 'play': + this.emit(H265PlayerConstants.on_play); + this.player.endLoading(); + break; + case 'stop': + this.emit(H265PlayerConstants.on_stop); + break; + case 'endLoading': + this.emit(H265PlayerConstants.on_endLoading); + break; + case 'pause': + this.emit(H265PlayerConstants.on_pause); + break; + default: + break; + } + } + changeUrl(newUrl) { + logger.info('change url'); + this.dispose(); + this.init(); + this.play(newUrl); + } + dispose() { + var _a; + logger.warn('dispose player'); + this.stopUrlHeartBeat(); + // eslint-disable-next-line no-unused-expressions + (_a = this.player) === null || _a === void 0 ? void 0 : _a.destroy(); + this.player = null; + this.currentUrl = ''; + this.retryCount = this.maxRetryCount; + this.emit(H265PlayerConstants.on_dispose); + } + stopUrlHeartBeat() { + logger.warn('stop url alive heartbeat'); + if (this.timer) { + window.clearTimeout(this.timer); + } + } + startUrlHeartBeat() { + this.stopUrlHeartBeat(); + const url = this.currentUrl; + if (!url) { + logger.fatal('start url heart beat failed , because of url is not ok, url is:', url); + return; + } + const HEART_BEAT_TIMEOUT = 6 * 1000; // 每隔多少秒进行一次心跳检测 + logger.verbose(`start url alive heartbeat, every ${HEART_BEAT_TIMEOUT} seconds`); + checkUrlIsValid(url) + .then(() => { + logger.ok('url heartbeat ok, prepare for next heartbeat ~'); + // 如果正常,开始下一次检测 + this.timer = window.setTimeout(() => { + this.startUrlHeartBeat(); + }, HEART_BEAT_TIMEOUT); + }) + .catch((e) => { + if (e.status === 501) { + logger.fatal(e.statusText); + this.dispose(); + return; + } + this.retryCount--; + logger.fatal('url heartbeat failed with', e); + if (this.retryCount <= 0) { + logger.warn('reach max retry count, will dispose player '); + this.emit(H265PlayerConstants.on_error, e); + this.dispose(); + } + else { + logger.info('left retry count is: ', `${this.retryCount} / ${this.maxRetryCount} (left / total)`); + logger.info('retry heartbeat ...'); + this.timer = window.setTimeout(() => { + this.startUrlHeartBeat(); + }, HEART_BEAT_TIMEOUT); + } + }); + } +} +/** + * 检测一个 URL 是否能够正常访问 + * @param url 要测试的 url + * @param timeout 超时时间 + * @returns 是否能够正常访问 + */ +const checkUrlIsValid = (url, timeout = 5 * 1000) => { + return new Promise((resolve, reject) => { + if (!isValidURL(url) && !url.includes('m3u8')) { + // eslint-disable-next-line prefer-promise-reject-errors + return reject({ + status: 501, + statusText: 'url is not valid' + }); + } + axios + .get(`${url}&__time=${Date.now()}`, { + responseType: 'blob', + timeout + }) + .then(resolve) + .catch(e => { + if (e.response) { + const { status, statusText } = e.response || {}; + // eslint-disable-next-line prefer-promise-reject-errors + reject({ status, statusText }); + } + else { + // eslint-disable-next-line prefer-promise-reject-errors + reject({ + status: 500, + statusText: (e === null || e === void 0 ? void 0 : e.message) || 'network error' + }); + } + }); + }); +}; +/** + * 检查一个url是否合法 + * @param url 待检查的 url + * @returns 是否合法 + */ +export const isValidURL = (url) => { + const res = url.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/g); + return res !== null; +}; diff --git a/src/pages/components/ProblemmodifyDetail.vue b/src/pages/components/ProblemmodifyDetail.vue index 0b75f53..dd03881 100644 --- a/src/pages/components/ProblemmodifyDetail.vue +++ b/src/pages/components/ProblemmodifyDetail.vue @@ -46,7 +46,7 @@
-
@@ -334,7 +334,8 @@ export default { .item-left { display: inline-flex; - width: 200px; + width: 400px; + min-height: 220px; align-items: center; } @@ -353,9 +354,9 @@ export default { .item-right { display: inline-block; margin-left: 10px; - width: calc(100% - 214px); + width: calc(100% - 414px); position: relative; - + .sp-lbl { display: inline-block; color: aquamarine; @@ -375,7 +376,7 @@ export default { display: flex; align-items: center; border-bottom: solid 1px rgba(255, 255, 255, 0.1); - line-height: 32px; + line-height: 36px; .div-text { display: inline-block; diff --git a/src/pages/init.js b/src/pages/init.js index 8fcdc3b..d0a2476 100644 --- a/src/pages/init.js +++ b/src/pages/init.js @@ -4,6 +4,8 @@ import dayjs from 'dayjs' import './style/index.less' import dayfilter from '@/utils/dayfilter' import {tryToJson} from '../utils/tools' +import H265Player from '../components/h265-player/index' +H265Player.install(Vue); dayfilter(Vue); Vue.prototype.$api=Api; Vue.prototype.$bus=new Vue(); diff --git a/src/pages/projectQuality.vue b/src/pages/projectQuality.vue index 4424d5b..fa47508 100644 --- a/src/pages/projectQuality.vue +++ b/src/pages/projectQuality.vue @@ -166,11 +166,11 @@ %
-
+
-
-
+
+
验收合格
验收不合格
@@ -186,7 +186,7 @@
-
+
验收描述:{{it.intro}}
@@ -196,6 +196,7 @@
+ @@ -210,9 +211,10 @@ import '../components/rank-chart' import '../components/idle-list-chart' import '../components/people-number' import ProblemmodifyDetail from './components/ProblemmodifyDetail.vue' +import showCheckingDlg from './quality/showCheckingDlg.vue' export default { components:{ - ProblemmodifyDetail + ProblemmodifyDetail,showCheckingDlg }, data() { return { @@ -296,7 +298,7 @@ export default { }, methods: { doShowChecking(it){ - + this.$refs.checkDlg.showDialog(it); }, checkDetection(n){ this.samplingNav=n; @@ -446,11 +448,19 @@ export default { } return url; }, - getBigProfileImage(url) { - if (url) { - return '/jhapi' + url; + getBigProfileImage(it) { + if(it.imageUrls){ + let tmps= it.imageUrls.split(",").filter(d=>d).map(it=>{ + return '/jhapi' + it; + }) + if(tmps.length>0){ + return tmps; + } } - return url; + if (it.mainImage && it.mainImage.trim()) { + return ['/jhapi' + it.mainImage]; + } + return []; }, }, }; diff --git a/src/pages/projectVideo.vue b/src/pages/projectVideo.vue index 1e1f612..7a9258b 100644 --- a/src/pages/projectVideo.vue +++ b/src/pages/projectVideo.vue @@ -181,10 +181,10 @@ import '../components/staff-survey-chart' import '../components/classify-bar' import '../components/list-menu' import '../components/amplify/shipinguanli/amplify-spjk' -import H265Player from "vue-h265-player"; + export default { components: { - H265Player, + }, data() { return { @@ -1364,8 +1364,8 @@ export default { /** 查询视频配置列表 */ initVideoMenu() { let param = { - deptId:this.dept.id||0, - projectId:this.proojectInfo.projectId||0 + deptId:this.dept?.id||0, + projectId:this.proojectInfo?.projectId||0 }; listVideoView(param).then((response) => { this.videoListData = response.data; diff --git a/src/pages/quality/showCheckingDlg.vue b/src/pages/quality/showCheckingDlg.vue index 0407860..a83245a 100644 --- a/src/pages/quality/showCheckingDlg.vue +++ b/src/pages/quality/showCheckingDlg.vue @@ -1,11 +1,11 @@