This commit is contained in:
caoqianming 2022-12-05 14:08:20 +08:00
commit 4d352a1773
20 changed files with 1484 additions and 97 deletions

View File

@ -3,4 +3,5 @@ ENV = 'production'
# base api
VUE_APP_BASE_API = 'https://testsearch.ctc.ac.cn/api'
#VUE_APP_BASE_API = 'http://47.95.0.242:2222/api'

View File

@ -17,6 +17,7 @@
"dependencies": {
"@riophae/vue-treeselect": "^0.4.0",
"axios": "0.18.1",
"echarts": "^5.4.0",
"element-china-area-data": "^5.0.2",
"element-ui": "2.13.0",
"file-saver": "^2.0.2",

View File

@ -155,6 +155,13 @@ export function updateQtask(id,data) {
})
}
export function deleteQtask(id) {
return request({
url:`/ability/qtask/${id}/`,
method:'delete'
})
}
export function qtaskStart(id) {
return request({
url:`/ability/qtask/${id}/start/`,

View File

@ -49,7 +49,7 @@ export function getVideoPlayCode(id) {
method: 'get'
})
}
//已弃用
export function getMyView(id, data) {
return request({
url: `/vod/video/${id}/myview/`,
@ -57,7 +57,7 @@ export function getMyView(id, data) {
data
})
}
//已弃用
export function refreshMyView(id, data) {
return request({
url: `/vod/video/${id}/myview/`,
@ -65,3 +65,92 @@ export function refreshMyView(id, data) {
data
})
}
//开始播放
export function videoStart(id) {
return request({
url: `/vod/video/${id}/start/`,
method: 'get'
})
}
//观看统计
export function videoView2(data) {
return request({
url: `/vod/view2/`,
method: 'get',
data
})
}
//我的观看统计
export function myVideoView2(data) {
return request({
url: `/vod/view2/my/`,
method: 'get',
data
})
}
//观看记录
export function viewItem(data) {
return request({
url: `/vod/viewitem/`,
method: 'get',
data
})
}
//我的观看记录
export function myViewItem(data) {
return request({
url: `/vod/viewitem/my/`,
method: 'get',
data
})
}
//更新观看记录
export function refreshViewItem(id, data) {
return request({
url: `/vod/viewitem/${id}/`,
method: 'put',
data
})
}
//本视频的我的观看统计
export function myView(id) {
return request({
url: `/vod/video/${id}/my/`,
method: 'get'
})
}
//播放完成
export function viewItemComplete(id) {
return request({
url: `/vod/viewitem/${id}/complete/`,
method: 'get'
})
}
//单位观看量统计
export function groupByOrgView(data) {
return request({
url: '/vod/analyse/group_by_org_view/',
method: 'post',
data
})
}
//个人观看量统计
export function groupByUserView(data) {
return request({
url: '/vod/analyse/group_by_user_view/',
method: 'post',
data
})
}
//视频大类播放量统计
export function groupByCategoryView(data) {
return request({
url: '/vod/analyse/group_by_video_category_big/',
method: 'post',
data
})
}

View File

@ -300,12 +300,19 @@ export const asyncRoutes = [
meta: { title: '上传视频', perms: ['video_create'] }
},
{
path: 'index/:id',
name: 'Index',
path: 'index',
name: 'index',
component: () => import('@/views/testvideo/index.vue'),
meta: { title: '视频播放', perms: ['video_view'] },
hidden: true
},
{
path: 'videoStatistics',
name: 'videoStatistics',
component: () => import('@/views/testvideo/videoStatistics.vue'),
meta: { title: '视频播放统计', perms: ['video_view'] },
// hidden: true
},
]
},
{

View File

@ -289,11 +289,11 @@
label-position="right"
:rules="rule"
>
<el-form-item label="资质认定" prop="type">
<el-form-item label="资质名称" prop="type">
<el-select
style="width: 100%;"
v-model="qualiForm.type"
placeholder="资质认定"
placeholder="资质名称"
@change="qualiTypeChange"
>
<el-option
@ -304,8 +304,8 @@
/>
</el-select>
</el-form-item>
<el-form-item label="资质名称" prop="name" v-if="qualiForm.type==='OTHER'">
<el-input v-model="qualiForm.name" placeholder="资质名称"/>
<el-form-item label="具体名称" prop="name" v-if="qualiForm.type==='OTHER'">
<el-input v-model="qualiForm.name" placeholder="具体名称"/>
</el-form-item>
<el-form-item label="资质类型" v-if="qualiForm.type==='CMA'">
<el-select
@ -494,11 +494,11 @@
label-position="right"
:rules="rule"
>
<el-form-item label="资质认定" prop="type">
<el-form-item label="资质名称" prop="type">
<el-select
style="width: 100%;"
v-model="qualiForm.type"
placeholder="资质认定"
placeholder="资质名称"
@change="qualiTypeChange"
>
<el-option
@ -509,8 +509,8 @@
/>
</el-select>
</el-form-item>
<el-form-item label="资质名称" prop="name" v-if="qualiForm.type==='OTHER'">
<el-input v-model="qualiForm.name" placeholder="资质名称"/>
<el-form-item label="具体名称" prop="name" v-if="qualiForm.type==='OTHER'">
<el-input v-model="qualiForm.name" placeholder="具体名称"/>
</el-form-item>
<el-form-item label="资质类型" v-if="qualiForm.type==='CMA'">
<el-select
@ -734,7 +734,7 @@
cityLists,
qactionItem,
qualiNoChange,
abilityNoChange
abilityNoChange,
} from "@/api/ability";
import {getDictList} from "@/api/dict";
import {genTree} from "@/utils";

View File

@ -65,18 +65,23 @@
size="small"
@click="handleClick(scope)"
>执行</el-link>
<!-- <el-link
v-if="scope.row.state==='待发布'"
<el-link
v-if="scope.row.state!='已关闭'"
type="primary"
size="small"
@click="handleEdit(scope)"
>编辑</el-link>-->
>编辑</el-link>
<el-link
v-if="scope.row.state==='待发布'"
type="warning"
size="small"
@click="handleStart(scope)"
>发布</el-link>
<el-link
type="danger"
size="small"
@click="handleDelete(scope)"
>删除</el-link>
</template>
</el-table-column>
</el-table>
@ -135,7 +140,7 @@
<script>
import { getOrgList } from "@/api/org";
import {getQtask,createQtask,updateQtask,qtaskStart} from "@/api/ability";
import {getQtask,createQtask,updateQtask,qtaskStart, deleteQtask} from "@/api/ability";
import {genTree} from "@/utils";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
@ -280,6 +285,22 @@
resetFilter() {
this.pageForm.search = '';
this.getQtaskList();
},
//删除操作
handleDelete(scope) {
let that = this;
that.$confirm("确认删除吗?", "提示")
.then(async () => {
await deleteQtask(scope.row.id).then(res => {
if (res.code >= 200 && res.code < 400) {
that.getQtaskList();
that.$message.success("成功");
}
});
})
.catch((err) => {
console.error(err);
});
},
},

View File

@ -364,7 +364,8 @@
atypeOptions: {},
afieldOptions: {},
atype_name:'',
afield_name:''
afield_name:'',
item:{}
};
},
created() {
@ -422,6 +423,7 @@
},
//操作处理
handleRecord(type, item) {
this.item = item
this.showData.id = item.id;
this.showData.data = item;
this.showData.type = item.action;

View File

@ -1,6 +1,6 @@
<template>
<el-container>
<el-header style="height: 80px;padding: 0">
<el-header style="height: 80px;padding: 0;position: absolute;">
<el-row class="biaotou">
<el-col :span="20" style="text-align:center;color:seashell;font-size:32px;line-height: 70px;">
{{ video.name }}
@ -8,7 +8,9 @@
</el-row>
</el-header>
<el-main style="margin-top: 70px;">
<div style="margin:20px 12%;">
<el-row>
<el-col :sm="24" :lg="15" :xl="13">
<div style="margin:20px;">
<div class="content">
<video
:id="tcPlayerId"
@ -26,8 +28,8 @@
<el-col class="firstLineDetail">
<div class="firstLineText">{{ video.name }}</div>
<div>
<el-button class="firstLineBtn" type="error" icon="el-icon-view">{{video.views}}</el-button>
<el-button class="firstLineBtn" type="error" icon="el-icon-s-custom">{{video.viewsp}}</el-button>
<el-button class="firstLineBtn" type="error" icon="el-icon-view">{{video.views_n}}</el-button>
<el-button class="firstLineBtn" type="error" icon="el-icon-s-custom">{{video.viewsp_n}}</el-button>
</div>
</el-col>
<div style="font-size: 15px">
@ -35,11 +37,27 @@
</div>
</div>
</div>
</el-col>
<el-col :sm="24" :lg="9" :xl="11">
<div style="margin:20px;height:calc(100% - 140px);">
<p class="firstLineText">观看记录</p>
<div class="viewRecordList">
<div class="viewRecordItemWrap" v-for="item in recordList" :key="item.id"
@click="recordItemPlay(item.video)">
<div class="recordName" v-if="item.video_"><span>{{item.video_.name}}</span></div>
<div class="viewInfo">上次观看时间<span class="viewInfo_tiem">{{item.update_time}}</span></div>
<div class="viewInfo">上次观看进度<span class="viewInfo_current">{{item.current}}</span></div>
</div>
</div>
</div></el-col>
</el-row>
</el-main>
</el-container>
</template>
<script>
import {getVideo, getMyView,refreshMyView, getVideoPlayCode} from "@/api/video";
import {getVideo,videoStart,myViewItem, myView,getMyView,refreshViewItem, getVideoPlayCode,viewItemComplete} from "@/api/video";
export default {
name: 'TencentPlayer',
props: {
@ -60,9 +78,12 @@
video: {id: 0},
description: '',
name: '',
videoFileid:false,
userName:"",
playTimer:null,
isFirstView:true,
id:'',
videoViId:'',
recordList:[],
};
},
mounted(){
@ -70,44 +91,52 @@
if(this.player!==null){
this.player.null;
}
if(this.$route.params.id){
this.id = this.$route.params.id;
this.videoFileId = this.$route.params.fileid;
if(this.$route.query.id){
this.id = this.$route.query.id;
let videoId = sessionStorage.getItem('videoId');
let videoFileId = sessionStorage.getItem('videoFileId');
if(videoId){
if(videoId!==undefined&&videoId!==null&&videoId!==''){
sessionStorage.removeItem('videoId');
sessionStorage.setItem('videoId',this.$route.params.id);
}else{
sessionStorage.setItem('videoId',this.$route.params.id);
}
if(videoFileId){
sessionStorage.removeItem('videoFileId');
sessionStorage.setItem('videoFileId',this.$route.params.fileid);
}else{
sessionStorage.setItem('videoFileId',this.$route.params.fileid);
}
}else{
this.id = sessionStorage.getItem('videoId');
this.videoFileId = sessionStorage.getItem('videoFileId');
}
this.getPlayCode(this.videoFileId);
this.getVideo();
this.clicknub();
this.getMyVideoView();
},
methods: {
//获取视频详情
getVideo() {
getVideo(this.id).then((response) => {
let that = this;
getVideo(that.id).then((response) => {
if (response.data) {
this.video = response.data;
that.video = response.data;
that.videoFileId = response.data.fileid;
that.getPlayCode(response.data.fileid);
}
this.listLoading = false;
that.listLoading = false;
});
},
//获取饿哦的观看记录
getMyVideoView() {
let that = this;
myViewItem().then((response) => {
if (response.data) {
that.recordList = response.data.results;
// debugger;
console.log(that.recordList)
}
});
},
//获取播放码
getPlayCode(id){
let that = this;
getVideoPlayCode(id).then(res=>{
if(res.data){
that.videoFileid = true;
that.videoPsign = res.data.psign;
that.initVideo();
}
@ -115,13 +144,17 @@
},
//视频观看次数
clicknub() {
getMyView(this.id).then((response) => {});
myView(this.id).then((response) => {
// debugger;
console.log(response.data);
});
},
//初始化视频播放器
initVideo() {
let that = this;
getMyView(that.id,{}).then((response) => {
debugger;
console.log(response.data);
// debugger;
// console.log(response.data);
let current = response.data.current;
let playerParm = {
fileID: that.videoFileId,
@ -132,7 +165,10 @@
speed: 0.1,
content: "用户:"+that.userName
},
}
},
controlBar:{
playbackRateMenuButton:false
},
};
if (!that.player) {
that.player = window.TCPlayer(that.tcPlayerId, playerParm);
@ -143,39 +179,72 @@
that.player.currentTime(current);
//视频播放
that.player.on('play', function(error) {
// debugger;
if(that.playTimer!==null){
clearInterval(that.playTimer);
that.playTimer = null;
}
if(that.isFirstView){
that.isFirstView = false;
videoStart(that.id).then(ews=>{
// debugger;
that.videoViId = ews.data.vi;
})
}
that.playTimer = setInterval(function(){
let currentTimeNum = 0;
currentTimeNum = that.player.currentTime();
console.log(currentTimeNum);
refreshMyView(that.id,{current:currentTimeNum}).then((response) => {});
refreshViewItem(that.videoViId,{current:currentTimeNum,seconds:10})
.then((response) => {})
.catch(response=>{
debugger;
clearInterval(that.playTimer);
that.playTimer = null;
that.player.pause();
});
},10000)
});
//视频暂停
that.player.on('pause', function(error) {
let currentTimeNum = 0;
currentTimeNum = that.player.currentTime();
console.log(currentTimeNum);
refreshMyView(that.id,{current:currentTimeNum}).then((response) => {});
refreshViewItem(that.videoViId,{current:currentTimeNum,seconds:0}).then((response) => {
});
clearInterval(that.playTimer);
that.playTimer = null;
});
//视频观看中
that.player.on('playing', function(error) {
});
//视频播放已结束时触发,此时 currentTime 值等于媒体资源最大值
that.player.on('ended', function(error) {
viewItemComplete(that.videoViId).then(res=>{
})
});
});
},
},
recordItemPlay(id){
debugger;
let routeData = this.$router.resolve({
path: "/test/index",
query: {
id: id
}
});
//必要操作否则不会打开新页面
window.open(routeData.href, '_blank');
},
},
beforeDestroy () {
let that = this;
if(that.playTimer!==null){
clearInterval(that.playTimer);
that.playTimer = null;
}
this.player.dispose()
that.player.dispose()
}
};
</script>
@ -188,7 +257,7 @@
color: #333;
text-align: center;
line-height: 40px;
position: fixed;
position: absolute;
width: 100%;
z-index: 1010;
}
@ -239,4 +308,30 @@
border: 1px solid #e74e4e;
border-radius: 15px;
}
.viewRecordList{
display: flex;
flex-direction: column;
}
.viewRecordItemWrap{
margin: 10px 0;
padding: 5px 0;
border-bottom: 1px solid #dddddd;
}
.recordName{
font-size: 15px;
font-weight: 600;
color: #333333;
margin-bottom: 10px;
}
.viewInfo{
font-size: 14px;
color: #a2a2a2;
padding-bottom: 5px;
}
.viewInfo_tiem{
color:#6090e6
}
.viewInfo_current{
color:#86c793
}
</style>

View File

@ -0,0 +1,610 @@
<template>
<div class="app-container">
<el-card>
<div style="margin-top:10px">
<el-date-picker
v-model="dataValue"
type="daterange"
align="right"
unlink-panels
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
:picker-options="pickerOptions"
value-format="yyyy-MM-dd HH:mm:ss"
:default-time="['00:00:00', '23:59:59']">
</el-date-picker>
<el-button type="primary" icon="el-icon-search" @click="handleFilter">搜索</el-button>
<div style="margin-top:10px"></div>
</div>
</el-card>
<el-card style="margin-top:10px">
<div id="main" style="width:1000px;height:600px;margin-top: 50px;"></div>
<div id="userMain" style="width:1000px;height:600px;margin-top: 50px;"></div>
<div id="orgMain" style="width:1000px;height:600px;margin-top: 50px;"></div>
</el-card>
</div>
</template>
<script>
import * as echarts from 'echarts'
import FileSaver from "file-saver";
import XLSX from "xlsx";
import { groupByCategoryView,groupByUserView,groupByOrgView } from '@/api/video'
export default{
data () {
return {
cateChart:null,
userChart:null,
orgChart:null,
dataValue:'',
dataQuery:{
start_time:'',
end_time:'',
limit:500
},
pickerOptions: {
shortcuts: [{
text: '最近一天',
onClick(picker) {
let end = new Date();
let start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一周',
onClick(picker) {
let end = new Date();
let start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
let end = new Date();
let start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
let end = new Date();
let start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
};
},
mounted(){
let dateNow = new Date();
let yeear = dateNow.getFullYear();
let month = dateNow.getMonth()+1;
let day = dateNow.getDate();
this.dataQuery.start_time=yeear+'-'+month+'-'+'01 00:00:00';
this.dataQuery.end_time=yeear+'-'+month+'-'+day+' 23:59:59';
this.dataValue = [this.dataQuery.start_time,this.dataQuery.end_time];
this.getCategoryView();
this.getUserView();
this.getGroupView();
},
methods: {
getCategoryView(){
groupByCategoryView(this.dataQuery).then(res=>{
let data = res.data;
let xAxisOptions = [],data1 = [],data2 = [],data3 = [];
data.forEach(item=>{
xAxisOptions.push(item.视频大类);
data1.push(item.视频数量);
data2.push(item.观看总次数);
data3.push(item.观看总人数);
})
let chartDom = document.getElementById('main');
this.cateChart = echarts.init(chartDom);
let labelOption = {
show: true,
position: 'insideBottom',
distance: 5,
align: 'left',
verticalAlign: 'middle',
rotate: 90,
formatter: '{c}',
fontSize: 12,
rich: {
name: {}
}
};
let option = {
title: { text: '视频大类观看统计' },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['视频数量', '观看总次数', '观看总人数']
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: {
show: true,
title: '数据视图',
lang: ['视频大类观看统计', '关闭', '导出Excel'], // 按钮
contentToOption: function (opts) {
$('#tableExcel').table2excel({
exclude: '.noExl', //过滤位置的 css 类名 有class = noExl 的行不被导出
filename: '最大需量', // 文件名称
name: 'Excel Document Name.xls',
exclude_img: true,
exclude_links: true,
exclude_inputs: true
})
},
optionToContent: function (opt) {
var axisData = opt.xAxis[0].data //坐标轴
var series = opt.series //折线图的数据
console.log(series);
var tdHeads =
'<td style="margin-top:10px; padding: 0 15px">视频大类</td>' //表头
var tdBodys = ''
series.forEach(function (item) {
tdHeads += `<td style="padding:5px 15px">${item.name}</td>`
})
var table = `<table id='table-content' border="1" style="margin-left:20px;border-collapse:collapse;font-size:14px;text-align:center;"><tbody><tr>${tdHeads} </tr>`
for (var i = 0, l = axisData.length; i < l; i++) {
for (var j = 0; j < series.length; j++) {
if (series[j].data[i] == undefined) {
tdBodys += `<td>${'-'}</td>`
} else {
tdBodys += `<td>${series[j].data[i]}</td>`
}
}
table += `<tr><td style="padding: 0 15px">${axisData[i]}</td>${tdBodys}</tr>`
tdBodys = ''
}
table += '</tbody></table>'
return table
},
contentToOption: function (HTMLDomElement, opt) {
let et = XLSX.utils.table_to_book(
document.getElementById("table-content")
); //此处传入table的DOM节点
let etout = XLSX.write(et, {
bookType: "xlsx",
bookSST: true,
type: "array",
});
try {
FileSaver.saveAs(
new Blob([etout], {
type: "application/octet-stream",
}),
"统计数据.xlsx"
); //trade-publish.xlsx 为导出的文件名
} catch (e) {
}
return etout;
},
},
magicType: { show: true, type: ['line', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
xAxis: [
{
type: 'category',
axisTick: { show: false },
data: xAxisOptions,
axisLabel: {
interval:0,
rotate:60//角度顺时针计算的
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '视频数量',
type: 'bar',
barGap: 0,
label: labelOption,
emphasis: {
focus: 'series'
},
data: data1
},
{
name: '观看总次数',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data2
},
{
name: '观看总人数',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data3
}
]
};
this.cateChart.setOption(option);
})
},
getGroupView(){
groupByOrgView(this.dataQuery).then(res=>{
debugger;console.log(res.data)
let data = res.data;
let xAxisOptions = [],data1 = [],data2 = [],data3 = [],data4=[];
data.forEach(item=>{
xAxisOptions.push(item.单位名称);
data1.push(item.观看完成视频总数);
data2.push(item.观看视频总数);
data3.push(item.观看总次数);
data4.push(item.观看总时间);
})
let chartDom = document.getElementById('orgMain');
this.orgChart = echarts.init(chartDom);
let labelOption = {
show: true,
position: 'insideBottom',
distance: 5,
align: 'left',
verticalAlign: 'middle',
rotate: 90,
formatter: '{c}',
fontSize: 12,
rich: {
name: {}
}
};
let option = {
title: { text: '单位观看量统计' },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['观看完成视频总数','观看视频总数', '观看总次数', '观看总时间']
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: {
show: true,
title: '数据视图',
lang: ['单位观看量统计', '关闭', '导出Excel'], // 按钮
contentToOption: function (opts) {
$('#tableExcel').table2excel({
exclude: '.noExl', //过滤位置的 css 类名 有class = noExl 的行不被导出
filename: '最大需量', // 文件名称
name: 'Excel Document Name.xls',
exclude_img: true,
exclude_links: true,
exclude_inputs: true
})
},
optionToContent: function (opt) {
var axisData = opt.yAxis[0].data //坐标轴
var series = opt.series //折线图的数据
console.log(series);
var tdHeads =
'<td style="margin-top:10px; padding: 0 15px">视频大类</td>' //表头
var tdBodys = ''
series.forEach(function (item) {
tdHeads += `<td style="padding:5px 15px">${item.name}</td>`
})
var table = `<table id='table-content' border="1" style="margin-left:20px;border-collapse:collapse;font-size:14px;text-align:center;"><tbody><tr>${tdHeads} </tr>`
for (var i = 0, l = axisData.length; i < l; i++) {
for (var j = 0; j < series.length; j++) {
if (series[j].data[i] == undefined) {
tdBodys += `<td>${'-'}</td>`
} else {
tdBodys += `<td>${series[j].data[i]}</td>`
}
}
table += `<tr><td style="padding: 0 15px">${axisData[i]}</td>${tdBodys}</tr>`
tdBodys = ''
}
table += '</tbody></table>'
return table
},
contentToOption: function (HTMLDomElement, opt) {
let et = XLSX.utils.table_to_book(
document.getElementById("table-content")
); //此处传入table的DOM节点
let etout = XLSX.write(et, {
bookType: "xlsx",
bookSST: true,
type: "array",
});
try {
FileSaver.saveAs(
new Blob([etout], {
type: "application/octet-stream",
}),
"统计数据.xlsx"
); //trade-publish.xlsx 为导出的文件名
} catch (e) {
}
return etout;
},
},
magicType: { show: true, type: [ 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: xAxisOptions,
// axisLabel: {
// interval:0,
// rotate:60//角度顺时针计算的
// }
}
],
xAxis: [
{
type: 'value'
}
],
series: [
{
name: '观看完成视频总数',
type: 'bar',
barGap: 0,
label: labelOption,
emphasis: {
focus: 'series'
},
data: data1
},
{
name: '观看视频总数',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data2
},
{
name: '观看总次数',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data3
}
,
{
name: '观看总时间',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data4
}
]
};
this.orgChart.setOption(option);
})
},
getUserView(){
groupByUserView(this.dataQuery).then(res=>{
debugger;
console.log(res.data);
let data = res.data;
let xAxisOptions = [],data1 = [],data2 = [],data3 = [],data4=[];
data.forEach(item=>{
xAxisOptions.push(item.姓名);
data1.push(item.观看完成视频总数);
data2.push(item.观看视频总数);
data3.push(item.观看总次数);
data4.push(item.观看总时间);
})
let chartDom = document.getElementById('userMain');
this.userChart = echarts.init(chartDom);
let labelOption = {
show: true,
position: 'insideBottom',
distance: 5,
align: 'left',
verticalAlign: 'middle',
rotate: 90,
formatter: '{c}',
fontSize: 12,
rich: {
name: {}
}
};
let option = {
title: { text: '个人观看量统计' },
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['观看完成视频总数','观看视频总数', '观看总次数', '观看总时间']
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: {
show: true,
title: '数据视图',
lang: ['个人观看量统计', '关闭', '导出Excel'], // 按钮
contentToOption: function (opts) {
$('#tableExcel').table2excel({
exclude: '.noExl', //过滤位置的 css 类名 有class = noExl 的行不被导出
filename: '最大需量', // 文件名称
name: 'Excel Document Name.xls',
exclude_img: true,
exclude_links: true,
exclude_inputs: true
})
},
optionToContent: function (opt) {
var axisData = opt.yAxis[0].data //坐标轴
var series = opt.series //折线图的数据
console.log(series);
var tdHeads =
'<td style="margin-top:10px; padding: 0 15px">视频大类</td>' //表头
var tdBodys = ''
series.forEach(function (item) {
tdHeads += `<td style="padding:5px 15px">${item.name}</td>`
})
var table = `<table id='table-content' border="1" style="margin-left:20px;border-collapse:collapse;font-size:14px;text-align:center;"><tbody><tr>${tdHeads} </tr>`
for (var i = 0, l = axisData.length; i < l; i++) {
for (var j = 0; j < series.length; j++) {
if (series[j].data[i] == undefined) {
tdBodys += `<td>${'-'}</td>`
} else {
tdBodys += `<td>${series[j].data[i]}</td>`
}
}
table += `<tr><td style="padding: 0 15px">${axisData[i]}</td>${tdBodys}</tr>`
tdBodys = ''
}
table += '</tbody></table>'
return table
},
contentToOption: function (HTMLDomElement, opt) {
let et = XLSX.utils.table_to_book(
document.getElementById("table-content")
); //此处传入table的DOM节点
let etout = XLSX.write(et, {
bookType: "xlsx",
bookSST: true,
type: "array",
});
try {
FileSaver.saveAs(
new Blob([etout], {
type: "application/octet-stream",
}),
"统计数据.xlsx"
); //trade-publish.xlsx 为导出的文件名
} catch (e) {
}
return etout;
},
},
magicType: { show: true, type: [ 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: xAxisOptions,
// axisLabel: {
// interval:0,
// rotate:60//角度顺时针计算的
// }
}
],
xAxis: [
{
type: 'value'
}
],
series: [
{
name: '观看完成视频总数',
type: 'bar',
barGap: 0,
label: labelOption,
emphasis: {
focus: 'series'
},
data: data1
},
{
name: '观看视频总数',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data2
},
{
name: '观看总次数',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data3
}
,
{
name: '观看总时间',
type: 'bar',
label: labelOption,
emphasis: {
focus: 'series'
},
data: data4
}
]
};
this.userChart.setOption(option);
})
},
handleFilter(){
this.cateChart.resize();
this.userChart.resize();
this.orgChart.resize();
this.dataQuery.start_time = this.dataValue[0];
this.dataQuery.end_time = this.dataValue[1];
this.getCategoryView();
this.getGroupView();
this.getUserView();
},
},
}
</script>

View File

@ -54,12 +54,12 @@
<div class="bottom clearfix">
<el-row>
<el-col :span="6">
<el-button type="warning" style="border: none;float: left;padding: 2px; color:#dcae07;background-color: white;" icon="el-icon-view">{{o.views}}
<el-button type="warning" style="border: none;float: left;padding: 2px; color:#dcae07;background-color: white;" icon="el-icon-view">{{o.views_n}}
</el-button>
</el-col>
<el-col :span="6">
<el-button type="warning" style="border: none;float: left;padding: 2px; color:#dcae07;
background-color: white;" icon="el-icon-s-custom">{{o.viewsp}}
background-color: white;" icon="el-icon-s-custom">{{o.viewsp_n}}
</el-button>
</el-col>
<el-col :span="12">
@ -182,7 +182,16 @@
}else{
sessionStorage.removeItem('videoType');
}
this.$router.push({name: "Index", params: {fileid: a.fileid, id: a.id}})
let routeData = this.$router.resolve({
path: "/test/index",
query: {
id: a.id
}
});
//必要操作否则不会打开新页面
window.open(routeData.href, '_blank');
// this.$router.push({path: "index", query: {fileid: a.fileid, id: a.id}})
}
},
};

View File

@ -52,6 +52,8 @@ const install = (Vue, vm) => {
let submitExam = (id,params={})=>vm.$u.post(`/exam/examrecord/${id}/submit/`,params);//开始考试
let examRecord = (id,params={})=>vm.$u.get(`/exam/examrecord/self/`,params);//我的考试记录
let examRecordDetail = (id,params={})=>vm.$u.get(`/exam/examrecord/${id}/`,params);//我的考试记录
let paperDetail = (id,params={})=>vm.$u.get(`/exam/paper/${id}/`,params);//测试
let questionList = (params={})=>vm.$u.get(`/exam/question/`,params);//题目
vm.$u.api = {getUserInfo,
getCode,
@ -79,6 +81,8 @@ const install = (Vue, vm) => {
startExam,
submitExam,
examRecord,
paperDetail,
questionList,
examRecordDetail
};
}

View File

@ -131,6 +131,42 @@
,{
"path" : "pages/exam/index",
"style" :
{
"navigationBarTitleText": "考试",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/test",
"style" :
{
"navigationBarTitleText": "练习",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/question",
"style" :
{
"navigationBarTitleText": "专题练习",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/testDetail",
"style" :
{
"navigationBarTitleText": "练习题目",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/exam/exam",
"style" :
{
"navigationBarTitleText": "考试列表",
"enablePullDownRefresh": false

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@ -1,6 +1,27 @@
<template>
<view>
<uni-list>
<!-- 垂直排列无略缩图主标题+副标题显示 -->
<uni-list-item direction="column" :key="item.id" v-for="(item, index) in examList">
<template v-slot:header>
<view class="uni-title">{{item.name}}</view>
</template>
<template v-slot:body>
<view class="uni-list-box">
<view class="uni-content">
<view class="uni-title-sub uni-ellipsis-2">开启时间: {{item.open_time}}{{item.close_time}}</view>
<view class="uni-note">考试机会: {{item.chance}}</view>
</view>
</view>
</template>
<template v-slot:footer>
<view class="uni-footer">
<u-button size="mini" type="primary" @click="attendExam(item)">我要参加</u-button>
<u-button size="mini" type="info">成绩排名</u-button>
</view>
</template>
</uni-list-item>
</uni-list>
</view>
</template>
@ -8,11 +29,28 @@
export default {
data() {
return {
query:{
page: 1
},
examList: []
}
},
onLoad() {
this.getExamList();
},
methods: {
getExamList(){
this.$u.api.getExamList(this.query).then(res=>{
this.examList = res.data.results
})
},
attendExam(val){
console.log(val)
uni.setStorageSync('currentExam', val)
uni.navigateTo({
url:"/pages/exam/preview"
})
}
}
}
</script>

View File

@ -1,27 +1,11 @@
<template>
<view>
<uni-list>
<!-- 垂直排列无略缩图主标题+副标题显示 -->
<uni-list-item direction="column" :key="item.id" v-for="(item, index) in examList">
<template v-slot:header>
<view class="uni-title">{{item.name}}</view>
</template>
<template v-slot:body>
<view class="uni-list-box">
<view class="uni-content">
<view class="uni-title-sub uni-ellipsis-2">开启时间: {{item.open_time}}{{item.close_time}}</view>
<view class="uni-note">考试机会: {{item.chance}}</view>
</view>
</view>
</template>
<template v-slot:footer>
<view class="uni-footer">
<u-button size="mini" type="primary" @click="attendExam(item)">我要参加</u-button>
<u-button size="mini" type="info">成绩排名</u-button>
</view>
</template>
</uni-list-item>
</uni-list>
<view class="cellWrap">
<view class="cellItem" v-for="item in cellList" :index="item.id" :key="item.id" @click="intoPage(item)">
<image class="cellImg" :src="item.img"></image>
<text class="cellText">{{item.title}}</text>
</view>
</view>
</view>
</template>
@ -32,29 +16,79 @@
query:{
page: 1
},
examList: []
cellList: [
{img:'../../static/exam/exam.png',title:'正式考试',id:'exam'},
{img:'../../static/exam/test.png',title:'模拟测试',id:'test'},
{img:'../../static/exam/question.png',title:'专题练习',id:'question'},
{img:'../../static/exam/errorIcon.png',title:'错题记录',id:'record'},
]
}
},
onLoad() {
this.getExamList();
},
methods: {
getExamList(){
this.$u.api.getExamList(this.query).then(res=>{
this.examList = res.data.results
})
},
attendExam(val){
console.log(val)
uni.setStorageSync('currentExam', val)
uni.navigateTo({
url:"/pages/exam/preview"
})
intoPage(item){
switch (item.id){
case 'exam':
uni.navigateTo({
url:"/pages/exam/exam"
})
break;
case 'test':
uni.navigateTo({
url:"/pages/exam/test"
})
break;
case 'question':
uni.navigateTo({
url:"/pages/exam/question"
})
break;
case 'erroRrecord':
uni.navigateTo({
url:"/pages/exam/erroRrecord"
})
break;
}
}
}
}
</script>
<style>
.cellWrap{
display: flex;
flex-flow: wrap;
margin: auto;
margin-top: 30upx;
margin-bottom: 30upx;
justify-content: space-evenly;
}
.cellItem{
width: 35%;
display: flex;
flex-direction: column;
text-align: center;
margin-top: 50upx;
}
.cellImg{
margin: auto;
width: 120upx;
height: 120upx;
}
.cellText{
height: 60upx;
line-height: 60upx;
text-align: center;
color: #339900;
}
.cellItem:nth-of-type(2) .cellText{
color: #efb336;
}
.cellItem:nth-of-type(3) .cellText{
color: #1296db;
}
.cellItem:nth-of-type(4) .cellText{
color: #d81e06;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<view>
<view class="cellWrap">
<view class="cellItem" v-for="item in typeList" :key="item" @click="intoPage(item)">
<view class="cellText">{{item}}</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
query:{
page: 1,
page_size:999,
type:'单选题'
},
typeList: ['单选','多选','判断']
}
},
methods: {
intoPage(item){
this.query.type=item;
this.$u.api.questionList(this.query).then(res=>{
let currentExam={};
currentExam.questions_ = res.data.results;
uni.setStorageSync('currentExam', currentExam);
uni.navigateTo({
url:"/pages/exam/testDetail"
})
})
}
}
}
</script>
<style>
.cellWrap{
display: flex;
flex-direction: column;
margin: auto;
margin-top: 30upx;
margin-bottom: 30upx;
justify-content: space-evenly;
}
.cellItem{
text-align: center;
margin-top: 50upx;
width: 60%;
margin-left: 20%;
}
.cellText{
height: 60upx;
line-height: 60upx;
text-align: center;
color: #339900;
border: 1px solid #339900;
border-radius: 15rpx;
}
.cellItem:nth-of-type(2) .cellText{
color: #efb336;
border: 1px solid #efb336;
}
.cellItem:nth-of-type(3) .cellText{
color: #1296db;
border: 1px solid #1296db;
}
</style>

View File

@ -0,0 +1,56 @@
<template>
<view>
<uni-list>
<!-- 垂直排列无略缩图主标题+副标题显示 -->
<uni-list-item direction="column" :key="item.id" v-for="(item, index) in examList">
<template v-slot:header>
<view class="uni-title">{{item.name}}</view>
</template>
<template v-slot:footer>
<view class="uni-footer">
<u-button size="mini" type="primary" @click="attendTest(item)">开始练习</u-button>
</view>
</template>
</uni-list-item>
</uni-list>
</view>
</template>
<script>
export default {
data() {
return {
query:{
page: 1
},
examList: []
}
},
onLoad() {
this.getExamList();
},
methods: {
getExamList(){
this.$u.api.getExamList(this.query).then(res=>{
this.examList = res.data.results
})
},
attendTest(val){
console.log(val)
let currentExam = val;
this.$u.api.paperDetail(val.paper_.id).then(res=>{
currentExam = res.data;
uni.setStorageSync('currentExam', currentExam)
uni.navigateTo({
url:"/pages/exam/testDetail"
})
})
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,294 @@
<template>
<view>
<!-- 大标题 -->
<view class="header" id="header">
<span class="header-button">{{currentIndex+1}}/{{currentExam.questions_.length}} </span>
</view>
<!-- 小标题栏 -->
<view class="sub-header">
<u-row>
<u-col span="4">
<view style="text-align: center;"><span class="header-card">{{currentQuestion.type}}</span></view>
</u-col>
<u-col span="4">
<view style="text-align: right;"><span class="submitButton" @click='handleSubmit()' >显示答案</span></view>
</u-col>
</u-row>
</view>
<scroll-view class="content" scroll-y="true" v-bind:style="{height:scollHeight+'rpx'}">
<view class="name">
<view>{{currentIndex}}·{{currentQuestion.name}}</view>
<!-- <rich-text :nodes="currentQuestion.name"></rich-text> -->
<view v-if="currentQuestion.img">
{{currentQuestion.img}}
</view>
</view>
<view class="options">
<checkbox-group @change="checkboxGroupChange" v-if="currentQuestion.type=='多选'">
<label class="option" v-for="item in currentOptions" :key="item.id" >
<view class="option-item1">
<checkbox :value="item.value" :checked="item.checked" color="#2979ff"/>
</view >
<view class="option-item2">{{item.value}}.{{item.text}}</view>
</label>
</checkbox-group>
<radio-group v-else @change="checkboxGroupChange">
<label class="option" v-for="item in currentOptions" :key="item.id">
<view class="option-item1">
<radio :value="item.value" :checked="item.checked" color="#2979ff"></radio>
</view>
<view class="option-item2">
{{item.value}}.{{item.text}}
</view>
</label>
</radio-group>
</view>
<view v-if="showAns">正确答案{{currentQuestion.right}}</view>
<view style="height:20rpx"></view>
</scroll-view>
<u-popup v-model="showM" mode="bottom" height="40%">
<view class="questionArea" style="display:flex">
<block v-for="(item, index) in currentExam.questions_" :key="index">
<view class="questionItem questionItem-select" v-if="item.user_answer" @click="jumpQuestion(index)">{{index+1}}</view>
<view class="questionItem questionItem-unselect" v-else @click="jumpQuestion(index)">{{index+1}}</view>
</block>
</view>
</u-popup>
<!-- 底部栏 -->
<view class="footer" id="footer">
<u-button @click='previousQ()' throttle-time="200" :plain="true" type="primary">上一题</u-button>
<u-button @click="showM = !showM" type="primary">答题卡</u-button>
<u-button @click='nextQ()' throttle-time="200" :plain="true" type="primary">下一题</u-button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentExam:{questions_:[]},
currentIndex:0,
currentOptions:[],
currentQuestion:{type:'单选'},
showM:false,
keyid:0,
start_time:null,
scollHeight:0,
showAns:false,
}
},
onLoad() {
//#ifdef MP-WEIXIN
uni.hideHomeButton()
//#endif
let res = uni.getSystemInfoSync();
let ratio = 750 / res.windowWidth;
this.scollHeight = res.windowHeight*ratio - 230;
this.currentExam = uni.getStorageSync('currentExam');
this.initQuestion();
},
methods: {
handleSubmit(){
this.showAns = true;
},
panTi(tm_current) {
// 返回当前题目是否正确,得分多少
let is_right = false, score = 0
if (tm_current.type == '多选') {
if (tm_current.user_answer) {
if (tm_current.user_answer.sort().toString() == tm_current.right.sort().toString()) {
is_right = true
score = tm_current.total_score
}
}
} else {
if(tm_current.right == tm_current.user_answer){
is_right = true
score = tm_current.total_score
}
}
return {'is_right':is_right,'score':score}
},
initQuestion(){
var currentQuestion = this.currentExam.questions_[this.currentIndex];
this.currentQuestion = currentQuestion;
let options_ = [];
let origin = currentQuestion.options;
this.currentOptions = [];
for (let key in origin) {
let option = {
value:key,
text:origin[key],
id: this.keyid++,
checked:false
}
if (currentQuestion.user_answer) {
if (key == currentQuestion.user_answer || currentQuestion.user_answer.indexOf(key) != -1) {
option.checked = true
}
} else {
option.checked = false
}
options_.push(option)
}
this.currentOptions = options_
},
nextQ(){
this.showAns = false;
let index = this.currentIndex + 1
if(index<this.currentExam.questions_.length){
this.currentIndex = index
this.initQuestion()
}
},
previousQ(){
this.showAns = false;
let index = this.currentIndex - 1;
if(index >= 0){
this.currentIndex = index
this.initQuestion()
}
},
checkboxGroupChange(e){
// debugger;
console.log(e)
this.currentExam.questions_[this.currentIndex].user_answer = e.detail.value
},
jumpQuestion(index){
this.currentIndex = index
this.initQuestion()
this.showM = false
}
}
}
</script>
<style lang="scss">
page {
background-color: $u-bg-color;
}
.content{
margin-top:8rpx;
.name {
font-size:34rpx;
padding: 25rpx 30rpx;
color:$u-content-color;
line-height:130%;
background-color: #FFFFFF;
}
.options {
margin-top:8rpx;
background-color: #FFFFFF;
padding: 6rpx 30rpx;
.option {
padding: 10rpx 0rpx;
display: flex;
font-size: 36rpx;
.option-item1{
justify-content: flex-start
}
.option-item2{
justify-content: flex-start;
color:$u-main-color;
}
}
}
}
.header {
width: 750rpx;
text-align: center;
height: 60rpx;
line-height: 60rpx;
font-size: 36rpx;
font-weight: 600;
color: $theme-color;
background-color: #FFFFFF;
&-button {
position: absolute;
right: 10rpx;
font-size:34rpx;
font-weight: bold;
color: #000;
}
.scoreText {
color: #00b060;
font-size: 35rpx;
}
}
.sub-header {
padding: 4rpx 20rpx;
color: #000;
font-size: 33rpx;
font-weight: bold;
background-color: #FFFFFF;
}
.submitButton{
padding: 6rpx 20rpx;
border-radius: 15rpx;
font-weight: bold;
color: #ffffff;
background-color: $u-type-error;
}
.header-card {
padding: 6rpx 20rpx;
border-radius: 15rpx;
color: #FFFFFF;
background-color: $u-type-primary-dark;
}
.footer {
width: 750rpx;
height: 100rpx;
padding: 30rpx 60rpx;
position: fixed;
bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 32rpx;
box-sizing: border-box;
color: #4c8af3;
box-shadow: 0 0 5px 1px #eee;
background-color: #FFFFFF;
&-card {
padding: 10rpx 20rpx;
border: 1px solid $theme-color;
border-radius: 15rpx;
color: #FFFFFF;
background-color: $theme-color;
}
}
.questionArea {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 20rpx;
.questionItem {
width: 80rpx;
height: 80rpx;
margin: 10rpx 22rpx;
line-height: 80rpx;
font-size: 35rpx;
text-align: center;
border-radius: 50%;
color: #ffffff;
}
.questionItem-select {
background-color: $theme-color;
}
.questionItem-unselect {
background-color: #bbbbbb;
}
}
</style>

Binary file not shown.