web-admin/src/view/content/live/index.vue

438 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" label-width="90px" class="demo-form-inline"
@keyup.enter="handleSubmitSearch">
<el-form-item label="关键词" style="width:300px">
<template #label>
<span>
<el-tooltip content="从标题和简介中搜索" placement="top-start">
<el-icon>
<QuestionFilled />
</el-icon>
</el-tooltip>
关键词
</span>
</template>
<el-input v-model="searchInfo.keyword" class="keyword" placeholder="请输入" clearable style="width:100%" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="search" @click="handleSubmitSearch">查询</el-button>
<el-button icon="refresh" @click="handleResetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button type="primary" icon="plus" @click="handleAdd('0')">新增直播</el-button>
</div>
<el-table :data="tableData" row-key="ID">
<el-table-column type="expand">
<template #default="scope">
<div>
<div class="gva-table-expand" style="max-width: 600px;">
<el-text tag="b">简介</el-text>
</div>
<div class="gva-table-expand" style="max-width: 600px;">
{{ scope.row.brief }}
</div>
<div class="gva-table-expand" style="max-width: 600px;">
<el-text tag="b">时间线</el-text>
</div>
<div class="gva-table-expand" style="max-width: 600px;">
<el-timeline style="max-width: 600px;">
<el-timeline-item v-for="(item, index) in JSON.parse(scope.row.liveTimeline)" :key="index"
hide-timestamp>
<div>
<div>{{ item.title }}</div>
<el-text size="small">{{ item.content }}</el-text>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</template>
</el-table-column>
<el-table-column align="left" label="ID" min-width="50" prop="ID" />
<el-table-column align="left" label="封面" min-width="180" prop="cover">
<template #default="scope">
<el-image :src="scope.row.cover" class="gva-image" fit="contain" :preview-src-list="[scope.row.cover]"
preview-teleported hide-on-click-modal close-on-press-escape style="width: 160px; height: 90px;" />
</template>
</el-table-column>
<el-table-column align="left" label="标题" min-width="220" prop="title" />
<el-table-column align="left" label="直播/回放地址" min-width="450" prop="liveUrl">
<template #default="scope">
<p>直播:<el-text>{{ scope.row.liveUrl }}</el-text></p>
<p>回放:<el-text>{{ scope.row.replayUrl }}</el-text></p>
</template>
</el-table-column>
<el-table-column align="left" label="直播时间" min-width="220" prop="liveTime">
<template #default="scope">
<p>开始:<el-text>{{ formatDate(scope.row.startTime) }}</el-text></p>
<p>结束:<el-text>{{ formatDate(scope.row.endTime) }}</el-text></p>
</template>
</el-table-column>
<el-table-column align="left" fixed="right" label="操作" width="160">
<template #default="scope">
<el-button type="primary" link icon="edit" @click="handleEdit(scope.row.ID)">编辑</el-button>
<el-button type="danger" link icon="delete" @click="handleDelete(scope.row.ID)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
@size-change="handleSizeChange" />
</div>
</div>
<el-drawer v-model="dialogFormVisible" size="900" :show-close="false" :before-close="handleCloseDialog"
:close-on-click-modal="false" :close-on-press-escape="false">
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">{{ !isEdit ? '添加' : '修改' }}直播</span>
<div>
<el-button type="primary" @click="handleFormSubmit">确 定</el-button>
<el-button @click="handleCloseDialog">取 消</el-button>
</div>
</div>
</template>
<el-form v-if="dialogFormVisible" ref="editFormRef" label-position="top" label-width="auto" :model="editForm"
:rules="rules" style="width: 100%">
<el-row :gutter="15">
<el-col :span="16">
<el-form-item label="标题" prop="title" style="width: 100%">
<el-input v-model="editForm.title" autocomplete="off" />
</el-form-item>
<el-form-item label="简介" prop="brief" style="width: 100%">
<el-input type="textarea" rows="4" v-model="editForm.brief" autocomplete="off" />
</el-form-item>
<el-form-item label="直播地址" prop="liveUrl" style="width: 100%">
<el-input v-model="editForm.liveUrl" autocomplete="off" placeholder="url地址" />
</el-form-item>
<el-form-item label="回放地址" prop="replayUrl" style="width: 100%">
<el-input v-model="editForm.replayUrl" autocomplete="off" placeholder="url地址" />
</el-form-item>
<el-form-item label="时间线" prop="liveTimeline" style="width: 100%">
<template #default="scope">
<el-timeline hide-timestamp>
<el-timeline-item placement="top" v-for="(item, index) in liveTimelineArr">
<el-row :gutter="10" style="width: 100%">
<el-col :span="18">
<div class="gva-multi-input">
<el-input v-model="item.title" style="width: 100%; margin-bottom: 10px;" placeholder="时间线标题"
@blur="timelineBlur" />
<el-input v-model="item.content" type="textarea" rows="3" style="width: 100%"
placeholder="时间线内容" />
</div>
</el-col>
<el-col :span="6">
<el-button type="primary" plain size="small" @click="timelineAdd(index)">
<el-icon>
<Plus />
</el-icon>
</el-button>
<el-button type="danger" plain size="small" v-if="index > 0" @click="timelineDel(index)">
<el-icon>
<Minus />
</el-icon>
</el-button>
</el-col>
</el-row>
</el-timeline-item>
</el-timeline>
</template>
</el-form-item>
</el-col>
<el-col :span="1"><el-divider direction="vertical" style="height: 100%" /></el-col>
<el-col :span="7">
<el-form-item label="封面" prop="cover" style="width: 100%">
<el-upload class="avatar-uploader" :action="`${imgUploadPath}/cms/mediaFile/upload?category=live_cover`"
:show-file-list="false" :on-success="uploadSuccess" :on-error="uploadFailure"
:before-upload="beforeUpload">
<img v-if="editForm.cover" :src="editForm.cover" class="avatar" style="object-fit: cover;">
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="开始时间" prop="startTime" style="width: 100%">
<el-date-picker v-model="editForm.startTime" type="datetime" placeholder="请选择" style="width:100%" />
</el-form-item>
<el-form-item label="结束时间" prop="endTime" style="width: 100%">
<el-date-picker v-model="editForm.endTime" type="datetime" placeholder="请选择" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-drawer>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getLiveList,
addLive,
updateLive,
deleteLive,
getLiveById
} from '@/api/live'
import { isImageMime } from '@/utils/image'
import { isUrl } from '@/utils/validator'
import { formatDate } from '@/utils/format'
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const searchInfo = ref({})
const elSearchFormRef = ref(null)
const isDateEndGtStart = (rule, value, callback) => {
if (value < editForm.value.startTime) {
callback(new Error("结束时间必须大于开始时间"))
} else {
callback()
}
}
const rules = reactive({
cover: [
{ required: true, message: '请选择封面', trigger: 'blur' }
],
title: [
{ required: true, message: '请输入标题', trigger: 'blur' }
],
liveTimeline: [
{ required: true, message: '请输入时间线', trigger: 'blur' }
],
liveUrl: [
{ required: true, message: '请输入url', trigger: 'blur' },
{ validator: isUrl, message: '请输入正确的url', trigger: 'blur' }
],
startTime: [
{ required: true, message: '请输入开始时间', trigger: 'blur' }
],
endTime: [
{ required: true, message: '请输入结束时间', trigger: 'blur' },
{ validator: isDateEndGtStart, message: '结束时间必须大于开始时间', trigger: 'blur' }
],
replayUrl: [
{ validator: isUrl, message: '请输入正确的url', trigger: 'blur' }
],
})
const initSearchInfo = () => {
searchInfo.value.keyword = ''
page.value = 1
}
initSearchInfo()
// 重置
const handleResetSearch = () => {
initSearchInfo()
getTableData()
}
// 搜索
const handleSubmitSearch = () => {
elSearchFormRef.value?.validate(async valid => {
if (!valid) return
getTableData()
})
}
// 查询
const tableData = ref([])
const getTableData = async () => {
const res = await getLiveList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
if (res.code === 0) {
tableData.value = res.data.list
}
}
getTableData()
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
// 修改页面容量
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// ----- 以下为 editForm 操作 -----
// 提交表单
const handleFormSubmit = async () => {
copyTimelineData()
editFormRef.value.validate(async valid => {
if (!valid) {
return false
}
let res
if (isEdit.value) {
res = await updateLive(editForm.value)
} else {
res = await addLive(editForm.value)
}
if (res.code === 0) {
ElMessage({
type: 'success',
message: isEdit.value ? '编辑成功' : '添加成功!'
})
getTableData()
initForm()
dialogFormVisible.value = false
}
})
}
// 初始化弹窗内表格方法
const editFormRef = ref(null)
const editForm = ref({})
const checkFlag = ref(false)
const liveTimelineArr = ref([])
const copyTimelineData = () => {
const arr = liveTimelineArr.value.filter(item => item.title.trim() != '')
if (arr.length > 0) {
editForm.value.liveTimeline = JSON.stringify(arr)
}
}
const timelineAdd = (index) => {
liveTimelineArr.value.splice(index + 1, 0, { title: '', content: '' })
}
const timelineDel = (index) => {
liveTimelineArr.value.splice(index, 1)
}
const timelineBlur = (index) => {
copyTimelineData()
}
const initForm = () => {
checkFlag.value = false
liveTimelineArr.value = [{
title: "",
content: "",
}]
editForm.value = {
ID: 0,
title: '',
brief: '',
cover: '',
liveTimeline: '',
liveUrl: '',
startTime: '',
endTime: '',
replayUrl: '',
}
}
// 关闭弹窗
const dialogFormVisible = ref(false)
const handleCloseDialog = () => {
initForm()
dialogFormVisible.value = false
}
// 删除
const handleDelete = (ID) => {
ElMessageBox.confirm('此操作将永久删除直播, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await deleteLive({ id: ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
const isEdit = ref(false)
const dialogTitle = ref('新增直播')
const handleAdd = (id) => {
dialogTitle.value = '新增直播'
initForm()
isEdit.value = false
dialogFormVisible.value = true
}
// 修改菜单方法
const handleEdit = async (ID) => {
dialogTitle.value = '编辑直播'
const res = await getLiveById({ id: ID })
editForm.value = res.data.live
if (editForm.value.liveTimeline != '') {
liveTimelineArr.value = JSON.parse(editForm.value.liveTimeline)
} else {
liveTimelineArr.value = [{
title: "",
content: ""
}]
}
isEdit.value = true
dialogFormVisible.value = true
}
// ------- 图片操作 -------
const imgUploadPath = ref(import.meta.env.VITE_BASE_API)
const beforeUpload = (file) => {
const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo 应支持在项目中设置
const isImage = isImageMime(file.type)
if (!isImage) {
ElMessage.error('上传图片只能是 jpg,png,svg,webp 格式!')
return false
}
if (!isLt500K && isImage) {
ElMessage.error('未压缩的上传图片大小不能超过 500KB请使用压缩上传')
return false
}
return true
}
const uploadSuccess = (res) => {
const { code, data, msg } = res
if (code !== 0) {
ElMessage({ type: 'error', message: msg })
return
}
if (!data.mediaFile) {
ElMessage({ type: 'error', message: '返回错误,上传失败' })
return
}
editForm.value.cover = data.mediaFile.url
}
const uploadFailure = () => {
ElMessage({
type: 'error',
message: '上传失败'
})
}
</script>
<style scoped>
.gva-multi-input {
margin-bottom: 15px;
}
</style>