视频管理

This commit is contained in:
BabyBoy 2025-06-07 16:45:55 +08:00
parent 17e849e638
commit f8ce0b3a3c
5 changed files with 441 additions and 139 deletions

View File

@ -5,36 +5,63 @@ export interface CategoryTreeVO {
weight: number; weight: number;
children: CategoryTreeVO[]; children: CategoryTreeVO[];
} }
export interface Formaget {
vlogId: number | string;
reason: string;
status: number;
}
export interface fansListVO {
bothFriend: number;
createdTime: string;
face: string;
fanId: string;
friend: boolean;
nickname: string;
}
export interface CommentsVO {
comment_user_id: string;
content: string;
create_time: string;
father_comment_id: string;
id: string;
like_counts: number;
userFace: string;
userNickname: string;
vlog_id: string;
vloger_id: string;
}
export interface likeVO {
face: string;
nickname: string;
create_time: string;
}
export interface CategoryVO { export interface CategoryVO {
/** /**
* ID * ID
*/ */
categoryId: string | number; categoryId: string | number;
mediaUrl: string;
/** description: string;
* id updateTime: string;
*/ className: string;
parentId: string | number; type: string;
storageRegion: string;
/** coverUrl: string;
* tagSet: string[];
*/ classId: number;
categoryName: string; storageClass: string;
expireTime: string;
/**
*
*/
orderNum: number;
/**
*
*/
createTime: string; createTime: string;
classPath: string;
/** name: string;
* category: string;
*/ fileId: string;
children: CategoryVO[]; status: string;
commentCounts: number;
likeCounts: number;
fansCounts: number;
} }
export interface CategoryForm extends BaseEntity { export interface CategoryForm extends BaseEntity {
@ -66,4 +93,11 @@ export interface CategoryQuery {
* *
*/ */
categoryName?: string; categoryName?: string;
/**
*
*/
current?: number /**
*
*/;
size?: number;
} }

View File

@ -1,6 +1,7 @@
import request from '@/utils/request'; import { CategoryForm, CategoryQuery, CategoryTreeVO, CategoryVO, Formaget } from '@/api/workflow/category/types';
import { AxiosPromise } from 'axios'; import { AxiosPromise } from 'axios';
import { CategoryVO, CategoryForm, CategoryQuery, CategoryTreeVO } from '@/api/workflow/category/types'; import request from '@/utils/request';
/** /**
* *
@ -10,12 +11,56 @@ import { CategoryVO, CategoryForm, CategoryQuery, CategoryTreeVO } from '@/api/w
export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => { export const listCategory = (query?: CategoryQuery): AxiosPromise<CategoryVO[]> => {
return request({ return request({
url: '/workflow/category/list', // url: '/workflow/category/list',old
url: '/admin/vlog/upload/list',
method: 'get', method: 'get',
params: query params: query
}); });
}; };
/**
*
* @param vlogId id
* @param reason
* @param status 1 2
*/
export const updateaudit = (data?: Formaget) => {
return request({
url: `/admin/vlog/upload/audit?vlogId=${data.vlogId}&reason=${data.reason}&status=${data.status}`,
method: 'post'
});
};
/**
*
* @param vlogId id
*/
export const gerdetail = (data?: string) => {
return request({
url: `/admin/vlog/upload/detail?fileId=${data}`,
method: 'get'
});
};
/**
*
* @param data id
*/
export const deldetail = (data?: string) => {
return request({
url: `/admin/comment/delete?commentId=${data}`,
method: 'post'
});
};
/**
*
* @param vlogId id
*/
export const updateaforbid = (vlogId?: string) => {
return request({
url: `/admin/vlog/upload/forbid`,
method: 'post',
data: { fileIds: vlogId, operation: 'forbid' }
});
};
/** /**
* *
* @param categoryId * @param categoryId

View File

@ -5,6 +5,12 @@ export interface CategoryTreeVO {
weight: number; weight: number;
children: CategoryTreeVO[]; children: CategoryTreeVO[];
} }
export interface Formaget {
vlogId: number | string;
reason: string;
status: number;
}
export interface CategoryVO { export interface CategoryVO {
/** /**
* ID * ID

View File

@ -1,7 +1,10 @@
import { createWebHistory, createRouter, RouteRecordRaw } from 'vue-router'; import { RouteRecordRaw, createRouter, createWebHistory } from 'vue-router';
/* Layout */
import Layout from '@/layout/index.vue'; import Layout from '@/layout/index.vue';
/* Layout */
/** /**
* Note: 路由配置项 * Note: 路由配置项
* *

View File

@ -21,7 +21,7 @@
</transition> </transition>
<el-card shadow="never"> <el-card shadow="never">
<template #header> <!-- <template #header>
<el-row :gutter="10" class="mt30"> <el-row :gutter="10" class="mt30">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflow:video:add']">新增</el-button> <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['workflow:video:add']">新增</el-button>
@ -31,7 +31,7 @@
</el-col> </el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
</template> </template> -->
<el-table <el-table
ref="categoryTableRef" ref="categoryTableRef"
v-loading="loading" v-loading="loading"
@ -41,63 +41,125 @@
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
> >
<el-table-column label="类型" prop="categoryName" width="260" /> <el-table-column label="名称" prop="title" align="center" width="160" />
<el-table-column label="内容" align="center" prop="orderNum" width="200" /> <!-- <el-table-column label="文件类型" prop="type" align="center" width="160" />
<el-table-column label="操作时间" align="center" prop="createTime" width="180" /> <el-table-column label="媒体类型" prop="category" align="center" width="160" /> -->
<el-table-column label="评论数量" prop="commentCounts" align="center" width="160" />
<el-table-column label="点赞数量" prop="likeCounts" align="center" width="160" />
<el-table-column label="粉丝数量" prop="fansCounts" align="center" width="160" />
<el-table-column label="封面图片" align="center" width="200">
<template #default="{ row }">
<img alt="封面图片" style="max-width: 50%; max-height: 50%" :src="row.coverUrl" />
</template>
</el-table-column>
<el-table-column label="文件创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="文件更新时间" align="center" prop="updateTime" width="180" />
<!-- <el-table-column label="文件过期时间" align="center" prop="expireTime" width="180" /> -->
<el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="查看" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['workflow:video:edit']" /> <el-button link type="primary" @click="handleAdd(scope.row)" v-hasPermi="['workflow:video:add']">查看</el-button>
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['workflow:video:add']" />
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['workflow:video:remove']" /> <el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['workflow:video:remove']">删除</el-button>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="block">
<el-pagination @current-change="handleCurrentChange" layout="total, prev, pager, next" :total="total"> </el-pagination>
</div>
</el-card> </el-card>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body> <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="categoryFormRef" :model="form" :rules="rules" label-width="80px"> <div v-if="shouldShowVideo" class="video-container">
<el-form-item label="上级分类" prop="parentId"> <video controls width="80%">
<el-tree-select <source :src="object.mediaUrl" type="video/mp4" />
v-model="form.parentId" 您的浏览器不支持播放该视频
:data="categoryOptions" </video>
:props="{ value: 'categoryId', label: 'categoryName', children: 'children' } as any"
value-key="categoryId" <div class="video-info">
placeholder="请选择上级分类" <div @click="isstates = 1" :style="{ color: isstates == 1 ? 'red' : '' }">评论{{ object.commentCounts }}</div>
check-strictly <div @click="isstates = 2" :style="{ color: isstates == 2 ? 'red' : '' }">点赞{{ object.likeCounts }}</div>
/> <div @click="isstates = 3" :style="{ color: isstates == 3 ? 'red' : '' }">点赞{{ object.fansCounts }}</div>
</el-form-item> </div>
<el-row :gutter="20"> <!-- 评论列表 -->
<el-col :span="12"> <div class="comment-list" v-if="isstates == 1">
<el-form-item label="分类名称" prop="categoryName"> <div v-for="(item, index) in comments" :key="index" class="comment-item">
<el-input v-model="form.categoryName" placeholder="请输入分类名称" /> <div class="comment-avatar">
</el-form-item> <el-avatar :src="item.userFace" size="40" />
</el-col> </div>
<el-col :span="12"> <div class="comment-content">
<el-form-item label="排序" prop="orderNum"> <div class="comment-header">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" /> <div class="comment-name">{{ item.userNickname }}</div>
</el-form-item> <div class="comment-time">{{ item.create_time }}</div>
</el-col> </div>
</el-row> <div class="comment-text">
</el-form> <div>{{ item.content }}</div>
<div><el-button type="danger" width="60" @click="delshow(item.id)">删除</el-button></div>
</div>
</div>
</div>
</div>
<!-- 点赞列表 -->
<div class="comment-list" v-if="isstates == 2" :style="{ width: '80%', margin: '0 auto' }">
<div v-for="(item, index) in likedUsers" :key="index" class="comment-item">
<div class="comment-avatar">
<el-avatar :src="item.face" size="40" />
</div>
<div class="comment-content">
<div class="comment-header">
<div class="comment-name">{{ item.nickname }}</div>
<div class="comment-time">{{ item.created_time }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- 粉丝列表 -->
<div class="comment-list" v-if="isstates == 3">
<div v-for="(item, index) in fansList" :key="index" class="comment-item">
<div class="comment-avatar">
<el-avatar :src="item.face" size="40" />
</div>
<div class="comment-content">
<div class="comment-header">
<div class="comment-name">{{ item.nickname }}</div>
<div class="comment-time">{{ item.createdTime }}</div>
</div>
<div class="comment-text">
<div>{{ item.friend ? '互相关注' : '粉丝' }}</div>
</div>
</div>
</div>
</div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button> <el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button type="danger" @click="showRefuteDialog = true"> </el-button>
<el-button @click="cancel"> </el-button> <el-button @click="cancel"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<el-dialog v-model="showRefuteDialog" title="输入反驳原因">
<el-form>
<el-form-item label="反驳原因">
<el-input v-model="refuteReason" type="textarea" :rows="4"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showRefuteDialog = false">取消</el-button>
<el-button type="primary" @click="submitRefute">确定</el-button>
</span>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup name="Category" lang="ts"> <script setup name="Category" lang="ts">
import { listCategory, getCategory, delCategory, addCategory, updateCategory } from '@/api/workflow/category'; import { listCategory, updateaudit, updateaforbid, gerdetail, deldetail } from '@/api/workflow/category';
import { CategoryVO, CategoryQuery, CategoryForm } from '@/api/contentCenter/types'; import { CategoryVO, CategoryQuery, CategoryForm, Formaget, CommentsVO, likeVO, fansListVO } from '@/api/contentCenter/types';
// import { reactive, ref } from 'vue';
type CategoryOption = { type CategoryOption = {
categoryId: number; categoryId: number;
categoryName: string; categoryName: string;
@ -107,16 +169,38 @@ type CategoryOption = {
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const categoryList = ref<CategoryVO[]>([]); const categoryList = ref<CategoryVO[]>([]);
const object = ref<CategoryVO>();
const categoryOptions = ref<CategoryOption[]>([]); const categoryOptions = ref<CategoryOption[]>([]);
const buttonLoading = ref(false); const buttonLoading = ref(false);
const showSearch = ref(true); const showSearch = ref(true);
const isExpandAll = ref(true); const isExpandAll = ref(true);
const loading = ref(false); const loading = ref(false);
const shouldShowVideo = ref(false);
const queryFormRef = ref<ElFormInstance>(); const queryFormRef = ref<ElFormInstance>();
const categoryFormRef = ref<ElFormInstance>(); const categoryFormRef = ref<ElFormInstance>();
const categoryTableRef = ref<ElTableInstance>(); const categoryTableRef = ref<ElTableInstance>();
const total = ref<number>(0);
const isstates = ref<number>(1);
//
const showRefuteDialog = ref(false);
//
const refuteReason = ref('');
//
const submitRefute = async () => {
// API
console.log('提交的反驳原因:', refuteReason.value);
const data: Formaget = { vlogId: object.value.fileId, status: 2, reason: refuteReason.value };
const res = await updateaudit(data);
if (res.status === 200) {
ElMessage({
message: '操作成功',
type: 'success'
});
showRefuteDialog.value = false;
cancel();
refuteReason.value = '';
}
};
const dialog = reactive<DialogOption>({ const dialog = reactive<DialogOption>({
visible: false, visible: false,
title: '' title: ''
@ -133,7 +217,9 @@ const initFormData: CategoryForm = {
const data = reactive<PageData<CategoryForm, CategoryQuery>>({ const data = reactive<PageData<CategoryForm, CategoryQuery>>({
form: { ...initFormData }, form: { ...initFormData },
queryParams: { queryParams: {
categoryName: undefined categoryName: undefined,
current: 1,
size: 20
}, },
rules: { rules: {
categoryId: [{ required: true, message: '流程分类ID不能为空', trigger: 'blur' }], categoryId: [{ required: true, message: '流程分类ID不能为空', trigger: 'blur' }],
@ -148,23 +234,29 @@ const { queryParams, form, rules } = toRefs(data);
const getList = async () => { const getList = async () => {
loading.value = true; loading.value = true;
const res = await listCategory(queryParams.value); const res = await listCategory(queryParams.value);
const data = proxy?.handleTree<CategoryVO>(res.data, 'categoryId', 'parentId'); // const data = proxy?.handleTree<CategoryVO>(res.data, 'categoryId', 'parentId');
if (data) { if (res.status == 200) {
categoryList.value = data; categoryList.value = res.data.list;
total.value = res.data.totalCount;
loading.value = false; loading.value = false;
} }
}; };
/** 查询流程分类下拉树结构 */ const handleCurrentChange = (val: number) => {
const getTreeselect = async () => { console.log(`当前页: ${val}`);
const res = await listCategory(); queryParams.value.pageNum = val;
categoryOptions.value = []; getList();
//
const data = proxy?.handleTree<CategoryOption>(res.data, 'categoryId', 'parentId');
if (data) {
categoryOptions.value = data; //
}
}; };
/** 查询流程分类下拉树结构 */
// const getTreeselect = async () => {
// const res = await listCategory();
// categoryOptions.value = [];
// //
// const data = proxy?.handleTree<CategoryOption>(res.data, 'categoryId', 'parentId');
// if (data) {
// categoryOptions.value = data; //
// }
// };
// //
const cancel = () => { const cancel = () => {
@ -188,74 +280,196 @@ const resetQuery = () => {
queryFormRef.value?.resetFields(); queryFormRef.value?.resetFields();
handleQuery(); handleQuery();
}; };
//
/** 新增按钮操作 */ const likedUsers = ref<likeVO[]>([]);
const handleAdd = (row?: CategoryVO) => { //
reset(); const fansList = ref<fansListVO[]>([]);
getTreeselect(); //
if (row?.categoryId) { const comments = ref<CommentsVO[]>([]);
form.value.parentId = row.categoryId; /** 查看详情按钮操作 */
} else { const handleAdd = async (row?: CategoryVO) => {
form.value.parentId = undefined; nextTick(() => {
} object.value = JSON.parse(JSON.stringify(row));
dialog.visible = true; shouldShowVideo.value = false;
dialog.title = '添加流程分类'; //
}; nextTick(() => {
shouldShowVideo.value = true;
/** 展开/折叠操作 */ dialog.visible = true;
const handleToggleExpandAll = () => { dialog.title = '视频查看';
isExpandAll.value = !isExpandAll.value; });
toggleExpandAll(categoryList.value, isExpandAll.value);
};
/** 展开/折叠操作 */
const toggleExpandAll = (data: CategoryVO[], status: boolean) => {
data.forEach((item) => {
categoryTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
}); });
}; const res = await gerdetail(row.fileId);
if (res.status == 200) {
/** 修改按钮操作 */ comments.value = res.data.comments;
const handleUpdate = async (row: CategoryVO) => { likedUsers.value = res.data.likedUsers;
await getTreeselect(); fansList.value = res.data.fansList;
if (row != null) {
form.value.parentId = row.parentId;
} }
const res = await getCategory(row.categoryId);
reset();
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改流程分类';
}; };
//
/** 提交按钮 */ const delshow = (id: string) => {
const submitForm = () => { ElMessageBox.confirm('是否确认删除', '警告', {
categoryFormRef.value?.validate(async (valid: boolean) => { confirmButtonText: '确定',
if (valid) { cancelButtonText: '取消',
buttonLoading.value = true; type: 'warning'
if (form.value.categoryId) { }).then(async () => {
await updateCategory(form.value).finally(() => (buttonLoading.value = false)); const res = await deldetail(id);
} else { if (res.status == 200) {
await addCategory(form.value).finally(() => (buttonLoading.value = false)); cancel();
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
getList(); getList();
} }
}); });
}; };
/** 修改按钮操作 */
const handleUpdate = async (row: CategoryVO) => {};
/** 展开/折叠操作 */
// const handleToggleExpandAll = () => {
// isExpandAll.value = !isExpandAll.value;
// toggleExpandAll(categoryList.value, isExpandAll.value);
// };
/** 展开/折叠操作 */
// const toggleExpandAll = (data: CategoryVO[], status: boolean) => {
// data.forEach((item) => {
// categoryTableRef.value?.toggleRowExpansion(item, status);
// if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
// });
// };
// /** */
// const handleUpdate = async (row: CategoryVO) => {
// await getTreeselect();
// if (row != null) {
// form.value.parentId = row.parentId;
// }
// const res = await getCategory(row.categoryId);
// reset();
// Object.assign(form.value, res.data);
// dialog.visible = true;
// dialog.title = '';
// };
/** 提交按钮 */
const submitForm = () => {
ElMessageBox.confirm('是否通过?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const data: Formaget = { vlogId: object.value.fileId, status: 1, reason: refuteReason.value };
const res = await updateaudit(data);
if (res.status == 200) {
ElMessage({
message: '操作成功',
type: 'success'
});
dialog.visible = false;
getList();
} else {
ElMessage({
message: res.msg,
type: 'error'
});
}
})
.catch(() => {
//
console.log('用户取消操作');
});
};
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (row: CategoryVO) => { const handleDelete = async (row: CategoryVO) => {
await proxy?.$modal.confirm('是否确认删除"' + row.categoryName + '"的分类?'); ElMessageBox.confirm('是否确认删除', '警告', {
loading.value = true; confirmButtonText: '确定',
await delCategory(row.categoryId).finally(() => (loading.value = false)); cancelButtonText: '取消',
await getList(); type: 'warning'
proxy?.$modal.msgSuccess('删除成功'); }).then(async () => {
const res = await updateaforbid(row.fileId);
if (res.status == 200) {
ElMessage({
message: '删除成功',
type: 'success'
});
getList();
} else {
ElMessage({
message: res.msg,
type: 'error'
});
}
});
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'contentManage/video/export',
{
...queryParams.value
},
`video_${new Date().getTime()}.xlsx`
);
}; };
onMounted(() => { onMounted(() => {
// getList(); getList();
}); });
</script> </script>
<style scoped lang="scss">
.video-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.video-container .video {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-info {
width: 100%;
display: flex;
justify-content: space-around;
margin-top: 10px;
font-size: 20px;
font-weight: bold;
& > div {
cursor: pointer;
}
}
.comment-list {
width: 100%;
}
.comment-item {
display: flex;
width: 100%;
.comment-avatar {
padding: 10px;
}
.comment-content {
padding: 10px;
width: 100%;
.comment-header {
width: 100%;
display: flex;
justify-content: space-between;
.comment-name {
color: rgb(145, 145, 145);
}
}
.comment-text {
display: flex;
width: 100%;
justify-content: space-between;
.el-button {
width: 50px;
height: 20px;
}
}
}
}
</style>