直播管理 #39

Merged
asd merged 1 commits from zj into master 2020-10-14 09:00:57 +08:00
5 changed files with 628 additions and 414 deletions

View File

@ -1,6 +1,6 @@
import router from '@/router'; import router from '@/router';
import store from '@/store'; import store from '@/store';
import { LoginData, UserInfo } from '@/types'; import { LiveList, LoginData, UserInfo } from '@/types';
import { saveValue } from '@/utils/common'; import { saveValue } from '@/utils/common';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { get, post, setToken } from './base' import { get, post, setToken } from './base'
@ -132,35 +132,12 @@ export async function getvideolist() {
* *
*/ */
interface LiveList {
liveid: number;
memberid: number;
title: string;
img: string;
fileid: string;
fileurl: string;
fileduration: string;
vodurl: string;
vodid: string;
vodduration: string;
dateline: string;
livetime: number;
livenumber: number;
status: number;
livestatus: number;
students: string;
desc: string;
deleted_at: null;
created_at: string;
updated_at: string;
statusname: string;
starttime: string;
begin: number;
}
export async function getlivelist() {
const res = await get<Array<LiveList>>('live'); export async function getlivelist(data?:any) {
console.log(res); const res = await get<Array<LiveList>>('live',data);
// console.log(res);
return res.data
} }
/** /**
@ -182,3 +159,15 @@ export async function getstatisticlist() {
studentInfo:res.data?.studentInfo studentInfo:res.data?.studentInfo
} }
} }
/**
*
*/
interface Liveaddrule{
code:number,
msg:string
}
export async function liveadd(data:any) {
const res = await post<Liveaddrule>('live',data);
console.log(res)
}

View File

@ -1,34 +1,34 @@
<template> <template>
<div class="videoitem" @click="navto()"> <div class="videoitem" @click="navto()">
<img src="" alt="" class="cover"> <img :src="img" alt="" class="cover">
<img src="@/static/images/play.png" alt="" class="play"> <img src="@/static/images/play.png" alt="" class="play">
<div class="title"> <div class="title">
sadghaskghdfjkaghjkfha {{title}}
<span class="lv">7.3</span> <span class="lv">{{score}}</span>
</div> </div>
<div class="info"> <div class="info">
<div class="datetime"> <div class="datetime">
<span>2020-09-11</span> <span>{{date.split(" ")[0]}}</span>
<span class="time">09:30</span> <span class="time">{{date.split(" ")[1]}}</span>
</div> </div>
<div class="feature"> <div class="feature">
<div> <div>
<img src="@/static/images/shijian.png" alt=""> <img src="@/static/images/shijian.png" alt="">
<span>123</span> <span>{{takehour}}</span>
</div> </div>
<div> <div>
<img src="@/static/images/studentnum.png" alt=""> <img src="@/static/images/studentnum.png" alt="">
<span>3</span> <span>{{livenum}}</span>
</div> </div>
</div> </div>
</div> </div>
<div class="state audit" v-if="type==1"> <div class="state audit" v-if="status==0">
还未开始 还未开始
</div> </div>
<div class="state live" v-if="type==2"> <div class="state live" v-if="status==1">
进入直播 进入直播
</div> </div>
<div class="state over" v-if="type==3"> <div class="state over" v-if="status==2">
查看回放 查看回放
</div> </div>
</div> </div>
@ -141,6 +141,27 @@ export default defineComponent({
type: { type: {
type: Number, type: Number,
default:1 default:1
},
img:{
type:String
},
title:{
type:String
},
score:{
type:Number
},
date:{
type:String
},
takehour:{
type:String
},
livenum:{
type:Number
},
status:{
type:Number
} }
}, },
setup(props){ setup(props){

27
src/types/index.d.ts vendored
View File

@ -61,3 +61,30 @@ export interface UserInfo {
desc: string; desc: string;
money: string; money: string;
} }
export interface LiveList {
liveid: number;
memberid: number;
title: string;
img: string;
fileid: string;
fileurl: string;
fileduration: string;
vodurl: string;
vodid: string;
vodduration: string;
dateline: string;
livetime: number;
livenumber: number;
status: number;
livestatus: number;
students: string;
desc: string;
deleted_at: null;
created_at: string;
updated_at: string;
statusname: string;
starttime: string;
begin: number;
}

View File

@ -3,51 +3,108 @@
<a-form :label-col="labelCol" :wrapper-col="wrapperCol"> <a-form :label-col="labelCol" :wrapper-col="wrapperCol">
<div class="title">直播信息</div> <div class="title">直播信息</div>
<a-form-item label="直播标题"> <a-form-item label="直播标题">
<a-input size="small" v-model:value="form.title" placeholder="请输入您的直播标题" /> <a-input
size="small"
v-model:value="form.title"
placeholder="请输入您的直播标题"
/>
</a-form-item> </a-form-item>
<a-form-item label="直播封面" class="item-cover"> <a-form-item label="直播封面" class="item-cover">
<a-upload <a-upload list-type="picture" :customRequest="uploadspic">
list-type="picture" <div class="upload-image" v-if="!viewCover && form.img.length == 0">
@change="coverChange" <PlusOutlined
action="//jsonplaceholder.typicode.com/posts/" style="fontsize: 22px"
> v-if="uploadpicprogress == 0"
<div class="upload-image" v-if="!viewCover"> />
<PlusOutlined style="fontSize: 22px;" /> <a-progress
type="circle"
:percent="uploadpicprogress"
:width="80"
v-else
/>
</div> </div>
<div class="preview-image" v-else> <div class="upload-image" v-else>
<img style="width: 100%" :src="previewImage" /> <img style="width: 100%; height: 100%" :src="form.img" />
</div> </div>
</a-upload> </a-upload>
</a-form-item> </a-form-item>
<a-form-item label="视频介绍" class="video-introduction"> <a-form-item label="视频介绍" class="video-introduction">
<a-upload <a-upload list-type="picture" :customRequest="uploads">
list-type="picture" <div class="upload-image" v-if="form.fileurl.length == 0">
action="//jsonplaceholder.typicode.com/posts/" <PlaySquareOutlined
style="fontsize: 22px"
v-if="uploadprogress == 0"
/>
<a-progress
type="circle"
:percent="uploadprogress"
:width="80"
v-else
/>
<video style="display: none" :src="videofile"></video>
</div>
<div
class="upload-image upload"
style="position: relative"
v-show="form.fileurl.length != 0"
> >
<div class="upload-image"> <video
<PlaySquareOutlined style="fontSize: 22px;" /> :src="form.fileurl"
:ref="
(el) => {
videos[0] = el;
}
"
class="upload"
></video>
<!-- <a-progress type="circle" :percent="100" :width="80" style="position:absolute;right:35%"/> -->
</div> </div>
</a-upload> </a-upload>
<div class="demand"> <div class="demand">
<p class="one-line-hide">视频要求</p> <p class="one-line-hide">视频要求</p>
<p class="one-line-hide">1.上传视频时间要求为30s之内</p> <p class="one-line-hide">1.上传视频时间要求为30s之内</p>
<p class="one-line-hide">2.支持文件大小100M</p> <p class="one-line-hide">2.支持文件大小100M</p>
<p class="one-line-hide">3.文件扩展名fivmp4文件扩展名fivmp4</p> <p class="one-line-hide">
3.文件扩展名fivmp4文件扩展名fivmp4
</p>
</div> </div>
</a-form-item> </a-form-item>
<a-form-item label="开始时间"> <a-form-item label="开始时间">
<a-input size="small" v-model:value="form.startTime" placeholder="请设置您的开始时间" /> <!-- <a-input
size="small"
v-model:value="form.startTime"
placeholder="请设置您的开始时间"
/> -->
<a-date-picker @change="startchange" placeholder="请设置您的开始时间" />
</a-form-item> </a-form-item>
<a-form-item label="直播时长" class="duration" v-bind="validateInfos.duration"> <a-form-item
<a-input size="small" v-model:value="form.duration" placeholder="请输入直播时间" /> label="直播时长"
class="duration"
v-bind="validateInfos.livetime"
>
<a-input
size="small"
v-model:value="form.livetime"
placeholder="请输入直播时间"
/>
<span class="unit">分钟</span> <span class="unit">分钟</span>
</a-form-item> </a-form-item>
<a-form-item label="直播人数" v-bind="validateInfos.number"> <a-form-item label="直播人数" v-bind="validateInfos.livenumber">
<a-input size="small" v-model:value="form.number" placeholder="请输入直播人数" /> <a-input
size="small"
v-model:value="form.livenumber"
placeholder="请输入直播人数"
/>
</a-form-item> </a-form-item>
<a-form-item label="直播简介" class="brief"> <a-form-item label="直播简介" class="brief">
<a-textarea v-model:value="form.brief" :autoSize="true" class="brief-textarea" :maxlength="200" placeholder="请输入您的直播简介" /> <a-textarea
<span class="words-number">{{ form.brief.length }}/200</span> v-model:value="form.desc"
:autoSize="true"
class="brief-textarea"
:maxlength="200"
placeholder="请输入您的直播简介"
/>
<span class="words-number">{{ form.desc.length }}/200</span>
</a-form-item> </a-form-item>
<a-form-item :wrapper-col="{ span: 4, offset: 0 }"> <a-form-item :wrapper-col="{ span: 4, offset: 0 }">
<a-button @click="onSubmit">发布直播</a-button> <a-button @click="onSubmit">发布直播</a-button>
@ -67,12 +124,19 @@
</template> </template>
<div class="notice-container report" v-if="0"> <div class="notice-container report" v-if="0">
<div class="title">您尚未获得直播资格</div> <div class="title">您尚未获得直播资格</div>
<div class="title sub-title">您被 学生XXX<span class="red">举报发生违规行为</span>,如有疑问点击反馈</div> <div class="title sub-title">
您被 学生XXX<span class="red">举报发生违规行为</span
>,如有疑问点击反馈
</div>
<div class="confirm-btn">意见反馈</div> <div class="confirm-btn">意见反馈</div>
</div> </div>
<div class="notice-container" v-else> <div class="notice-container" v-else>
<div class="title">您尚未获得直播资格</div> <div class="title">您尚未获得直播资格</div>
<div class="title sub-title">上一周/月您在平台视频点击量为 <span class="red">第24名</span>要在前 <span class="bule">20</span> 才能获得直播资格</div> <div class="title sub-title">
上一周/月您在平台视频点击量为
<span class="red">第24名</span>要在前
<span class="bule">20</span> 才能获得直播资格
</div>
<rank-list></rank-list> <rank-list></rank-list>
</div> </div>
</a-modal> </a-modal>
@ -81,16 +145,26 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, Ref, ref, toRaw } from 'vue'; import {
import { PlaySquareOutlined, PlusOutlined } from '@ant-design/icons-vue'; defineComponent,
import { useForm } from '@ant-design-vue/use'; onBeforeUpdate,
import NavBottom from '@/components/NavBottom.vue'; onMounted,
import RankList from './RankList.vue'; reactive,
import { previewCover } from '@/utils/common'; Ref,
import { FromSend, ImgInfo } from "@/types/index" ref,
toRaw,
} from "vue";
import { PlaySquareOutlined, PlusOutlined } from "@ant-design/icons-vue";
import { useForm } from "@ant-design-vue/use";
import NavBottom from "@/components/NavBottom.vue";
import RankList from "./RankList.vue";
import { previewCover } from "@/utils/common";
import { FromSend, ImgInfo } from "@/types/index";
import { uploadflie } from "@/utils/vod";
import { liveadd } from "@/api";
export default defineComponent({ export default defineComponent({
name: 'ReleaseWebcast', name: "ReleaseWebcast",
components: { components: {
PlaySquareOutlined, PlaySquareOutlined,
PlusOutlined, PlusOutlined,
@ -100,20 +174,31 @@ export default defineComponent({
setup() { setup() {
// //
const form = reactive({ const form = reactive({
title: '', title: "",
cover: '', img: "",
introduction: '', fileid: "",
startTime: '', fileurl: "",
duration: '', fileduration: 0,
number: '', dateline: "",
brief: '', livetime: "",
livenumber: "",
desc: "",
}); });
const uploadprogress: Ref<number> = ref(0);
const uploadpicprogress: Ref<number> = ref(0);
const videofile = ref<File>();
const videos = ref<Array<any>>([]);
/** /**
* 验证直播时间 * 验证直播时间
*/ */
async function validateDuration (rule: unknown, value: number): Promise<string | void> { async function validateaLivetime(
rule: unknown,
value: number
): Promise<string | void> {
console.log(value);
if (value < 30 || value > 120) { if (value < 30 || value > 120) {
return Promise.reject('*最短30min 最长120min'); return Promise.reject("*最短30min 最长120min");
} else { } else {
return Promise.resolve(); return Promise.resolve();
} }
@ -121,32 +206,36 @@ export default defineComponent({
/** /**
* 验证直播人数 * 验证直播人数
*/ */
const validateNumber = async (rule: unknown, value: number): Promise<string | void> => { const validateLivenumber = async (
rule: unknown,
value: number
): Promise<string | void> => {
console.log(value);
if (value < 1 || value > 4) { if (value < 1 || value > 4) {
return Promise.reject('**最少1人最多4人'); return Promise.reject("**最少1人最多4人");
} else { } else {
return Promise.resolve(); return Promise.resolve();
} }
}; };
// //
const rules = reactive({ const rules = reactive({
duration: [ livetime: [
{ {
validator: validateDuration, validator: validateaLivetime,
trigger: 'change', trigger: "change",
}, },
], ],
number: [ livenumber: [
{ {
validator: validateNumber, validator: validateLivenumber,
trigger: 'change', trigger: "change",
}, },
], ],
}); });
// //
const viewCover: Ref<boolean> = ref(false); const viewCover: Ref<boolean> = ref(false);
const previewImage: Ref<string> = ref(''); const previewImage: Ref<string> = ref("");
/** /**
* 封面改变触发事件 * 封面改变触发事件
@ -155,22 +244,41 @@ export default defineComponent({
function coverChange(info: ImgInfo): void { function coverChange(info: ImgInfo): void {
// console.log(info); // console.log(info);
// //
previewCover(info.file).then(url => previewImage.value = url); previewCover(info.file).then((url) => (previewImage.value = url));
viewCover.value = true; viewCover.value = true;
// form.cover = fileList; // form.cover = fileList;
} }
const { resetFields, validate, validateInfos } = useForm(form, rules); const { resetFields, validate, validateInfos } = useForm(form, rules);
/** /**
* 表单提交 * 表单提交
*/ */
const subdata = reactive({
title: "",
img: "",
fileid: "",
fileurl: "",
fileduration: "",
dateline: "",
livetime: "",
livenumber: "",
desc: "",
});
const onSubmit = (e: FromSend) => { const onSubmit = (e: FromSend) => {
e.preventDefault(); e.preventDefault();
validate().then(() => { validate()
console.log(toRaw(form)); .then(() => {
}).catch((err: unknown) => { // console.log(toRaw(form),111);
console.log('error', err); let subdata = toRaw(form);
// subdata.fileid=picinfo.
console.log(subdata);
liveadd(subdata);
})
.catch((err: unknown) => {
console.log("error", err);
}); });
}; };
const isEntitled: Ref<boolean> = ref(true); const isEntitled: Ref<boolean> = ref(true);
@ -180,10 +288,70 @@ export default defineComponent({
function hideNoticeModal(): void { function hideNoticeModal(): void {
isEntitled.value = false; isEntitled.value = false;
} }
/**
* 开始时间设置
*/
function startchange(e: string): void {
let month=new Date(e).getMonth()+1
console.log(new Date(e).getFullYear()+"-"+month+'-'+new Date(e).getDate())
form.dateline =
new Date(e).getFullYear() +
"-" +
month +
"-" +
new Date(e).getDate();
}
/**
* 上传文件
*/
const videoinfo = reactive({
fileId: "",
url: "",
});
const picinfo = reactive({
fileId: "",
url: "",
});
interface AntUpload {
action: string;
data: unknown;
file: File;
}
async function uploads(file: AntUpload) {
console.log(file);
videofile.value = file.file;
videos.value[0].addEventListener("durationchange", () => {
console.log(videos.value[0].duration);
form.fileduration = videos.value[0].duration;
});
let res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadprogress.value = info.percent.toFixed(2) * 100;
});
console.log(res);
form.fileid = res.fileId;
form.fileurl = res.video.url;
}
async function uploadspic(file: AntUpload) {
let res = await uploadflie(file.file, (info: any) => {
console.log(info);
uploadpicprogress.value = info.percent.toFixed(2) * 100;
});
console.log(res);
// picinfo.fileId=res.fileId
// picinfo.url=res.video.url
form.img = res.video.url;
}
return { return {
labelCol: { span: 4 }, labelCol: { span: 4 },
wrapperCol: { span: 14 }, wrapperCol: { span: 14 },
modalNode: () => document.getElementsByClassName('modal-container')[0], modalNode: () => document.getElementsByClassName("modal-container")[0],
validateInfos, validateInfos,
resetFields, resetFields,
viewCover, viewCover,
@ -193,9 +361,18 @@ export default defineComponent({
onSubmit, onSubmit,
isEntitled, isEntitled,
hideNoticeModal, hideNoticeModal,
} uploads,
} uploadprogress,
}) videoinfo,
uploadspic,
uploadpicprogress,
picinfo,
startchange,
videofile,
videos,
};
},
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.webcast { .webcast {
@ -221,14 +398,18 @@ export default defineComponent({
font-weight: 500; font-weight: 500;
color: #808080; color: #808080;
&::after { &::after {
content: ''; content: "";
} }
} }
} }
.upload {
width: 171px;
height: 96px;
}
.upload-image { .upload-image {
width: 171px; width: 171px;
height: 96px; height: 96px;
border: 1px solid #DCDFE0; border: 1px solid #dcdfe0;
border-radius: 3px; border-radius: 3px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -239,9 +420,9 @@ export default defineComponent({
width: 171px; width: 171px;
padding: 6px 11px; padding: 6px 11px;
border-radius: 3px; border-radius: 3px;
border: 1px solid #DCDFE0; border: 1px solid #dcdfe0;
font-size: 11px; font-size: 11px;
color: #3F3F3F; color: #3f3f3f;
&::-webkit-input-placeholder { &::-webkit-input-placeholder {
font-size: 12px; font-size: 12px;
color: #808080; color: #808080;
@ -256,7 +437,7 @@ export default defineComponent({
.ant-form-explain { .ant-form-explain {
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
color: #D22D2E; color: #d22d2e;
} }
.ant-upload-list { .ant-upload-list {
display: none; display: none;
@ -265,11 +446,11 @@ export default defineComponent({
padding: 0; padding: 0;
width: 63px; width: 63px;
height: 23px; height: 23px;
background: #08AE98; background: #08ae98;
border-radius: 3px; border-radius: 3px;
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
color: #FFFFFF; color: #ffffff;
} }
} }
.item-cover { .item-cover {
@ -319,7 +500,7 @@ export default defineComponent({
bottom: -11px; bottom: -11px;
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
color: #7F7F7F; color: #7f7f7f;
user-select: none; user-select: none;
} }
} }
@ -334,7 +515,7 @@ export default defineComponent({
box-sizing: border-box; box-sizing: border-box;
padding: 25px 30px; padding: 25px 30px;
width: 569px; width: 569px;
background: #FFFFFF; background: #ffffff;
box-shadow: 0px 4px 6px 0px rgba(102, 102, 102, 0.07); box-shadow: 0px 4px 6px 0px rgba(102, 102, 102, 0.07);
border-radius: 28px; border-radius: 28px;
.notice-container { .notice-container {
@ -346,19 +527,19 @@ export default defineComponent({
} }
.sub-title { .sub-title {
.red { .red {
color: #D12C2D; color: #d12c2d;
} }
.bule { .bule {
color: #0DBBA3; color: #0dbba3;
} }
} }
.confirm-btn { .confirm-btn {
display: inline-block; display: inline-block;
background: #07AD97; background: #07ad97;
border-radius: 2px; border-radius: 2px;
font-size: 9px; font-size: 9px;
font-weight: 500; font-weight: 500;
color: #FFFFFF; color: #ffffff;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;

View File

@ -12,16 +12,7 @@
</div> </div>
</div> </div>
<div class="list" v-if="tabindex==1"> <div class="list" v-if="tabindex==1">
<LiveItem :type="2"></LiveItem> <LiveItem :type="2" v-for="(i,j) in livelist" :key="j" :img="i.img" :title="i.title" :score="i.score" :date="i.starttime" :takehour="i.vodduration" :livenum="i.livenumber" :status="i.livestatus"></LiveItem>
<LiveItem :type="2"></LiveItem>
<LiveItem :type="2"></LiveItem>
<LiveItem></LiveItem>
<LiveItem></LiveItem>
<LiveItem></LiveItem>
<LiveItem></LiveItem>
<LiveItem></LiveItem>
<LiveItem></LiveItem>
<LiveItem></LiveItem>
</div> </div>
<div class="list" v-if="tabindex==2"> <div class="list" v-if="tabindex==2">
@ -144,24 +135,29 @@
} }
</style> </style>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from "vue"; import { defineComponent, onMounted, ref } from "vue";
import LiveItem from "@/components/LiveItem.vue"; import LiveItem from "@/components/LiveItem.vue";
import { getlivelist } from '@/api'; import { getlivelist } from '@/api';
import { LiveList } from '@/types';
export default defineComponent({ export default defineComponent({
components: { components: {
LiveItem, LiveItem,
}, },
setup() { setup() {
const page = ref(6); const page = ref(1);
const tabindex = ref(1); const tabindex = ref(1);
getlivelist() const livelist=ref<Array<LiveList>>()
onMounted(async ()=>{
livelist.value= await getlivelist()
})
function tabchange(e: number): void { function tabchange(e: number): void {
tabindex.value=e tabindex.value=e
} }
return { return {
page, page,
tabindex, tabindex,
tabchange tabchange,
livelist
}; };
}, },
}); });