This commit is contained in:
make
2025-04-06 21:51:01 +08:00
commit 3792acd4c7
408 changed files with 42349 additions and 0 deletions

223
src/views/video/dialog.vue Normal file
View File

@@ -0,0 +1,223 @@
<template>
<el-dialog
v-model="drawerVisible"
:title="drawerProps.title + '--' + drawerProps.name"
:destroy-on-close="true"
width="520px"
draggable
@close="handleClose"
>
<el-form
ref="ruleFormRef"
label-width="80px"
label-suffix=" :"
:rules="rules"
:disabled="drawerProps.isView"
:model="drawerProps.row"
:hide-required-asterisk="drawerProps.isView"
>
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
<template v-if="e.type === 'switch'">
<el-switch v-model="e.value" />
</template>
<template v-if="e.type === 'radio'">
<el-radio-group v-model="e.value" class="ml-4">
<el-radio v-for="item in e.options" :key="item.value" :label="item.value" size="large">{{
item.label
}}</el-radio>
</el-radio-group>
</template>
<template v-if="e.type === 'select'">
<el-select filterable v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
<el-option
v-for="(item, index) in e.options"
:key="index"
:label="getField(item, e.fieldNames?.label)"
:value="getField(item, e.fieldNames?.value)"
/>
</el-select>
</template>
<template v-if="e.type === 'text'">
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'textarea'">
<el-input
type="textarea"
:rows="10"
v-model="e.value"
maxlength="500000"
:placeholder="`请输入${e.label}`"
show-word-limit
/>
</template>
<template v-if="e.type === 'textareaMore'">
<el-input
type="textarea"
v-model="e.value"
maxlength="500000"
:placeholder="`请输入${e.label}`"
show-word-limit
/>
</template>
</el-form-item>
</el-form>
<div id="dplayerDom" style="height: 350px"></div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleClose">确定</el-button>
</template>
<!-- <template #footer>
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary" @click="drawerVisible = false">确定</el-button>
</template> -->
</el-dialog>
</template>
<script setup lang="ts" name="UserDrawer">
import { ref, computed, nextTick, reactive } from "vue";
import { FormInstance } from "element-plus";
import { VideoList } from "@/api/interface/video/list";
import Hls from "hls.js";
import Dplayer from "dplayer";
const videoInfo = reactive({
url: "",
type: "mp4"
});
let dpPlayerJs;
const onInitDplayer = async () => {
dpPlayerJs = new Dplayer({
container: document.getElementById("dplayerDom"),
autoplay: false, //是否自动播放
theme: "#FADFA3", //主题色
loop: true, //视频是否循环播放
lang: "zh-cn",
screenshot: false, //是否开启截图
hotkey: true, //是否开启热键
preload: "auto", //视频是否预加载
volume: 0.7, //默认音量
mutex: true, //阻止多个播放器同时播放,当前播放器播放时暂停其他播放器
video: {
url: videoInfo.url,
type: videoInfo.type,
customType: {
customHls: function (video) {
if (Hls.isSupported()) {
const hls = new Hls();
video.src = videoInfo.url;
hls.loadSource(video.src); // 加载 HLS 视频流
hls.attachMedia(video);
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
// HLS.js 不支持时,直接使用 HTML5 视频播放器播放
video.src = videoInfo.url;
}
}
}
}
});
//暂停播放
dpPlayerJs.on("destroy", () => {
console.log("销毁组件");
});
};
type Options = {
label: string;
value: string | number;
};
const getField = (item: any, fieldName: string | undefined) => {
return fieldName ? item[fieldName] : "";
};
type Fields = {
label: string;
prop: string;
type: string;
value: any;
required: boolean;
options?: Options[];
fieldNames?: { label: string; value: string };
};
const generateRules = (fields: Fields[]) => {
return fields.reduce(
(acc, field) => {
const { type, required, label, prop } = field;
let message = "";
if (type === "select") {
message = `请选择${label}`;
}
if (type === "text") {
message = `请输入${label}`;
}
if (type === "textarea") {
message = `请输入${label}`;
}
if (type === "textareaMore") {
message = `请输入${label}`;
}
if (type === "radio") {
message = `请选择${label}`;
}
acc[prop] = [{ required, message }];
return acc;
},
{} as Record<string, any>
);
};
const formColumns = computed((): Fields[] => {
return [];
});
const rules = ref();
interface DrawerProps {
title: string;
name: any;
isView: boolean;
row: Partial<VideoList.ResList>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
const drawerVisible = ref(false);
const drawerProps = ref<DrawerProps>({
isView: false,
title: "",
name: "",
row: {}
});
// 接收父组件传过来的参数
const acceptParams = (params: DrawerProps) => {
drawerProps.value = params;
(drawerProps.value.name = drawerProps.value.row!.v_name), (rules.value = generateRules(formColumns.value));
drawerVisible.value = true;
if (drawerProps.value.row!.v_play_url_detail && drawerProps.value.row!.v_play_url_detail.length > 0) {
const data = drawerProps.value.row!.v_play_url_detail;
videoInfo.url = data[0].domain;
}
nextTick(() => {
if (videoInfo.url.indexOf("mp4") != -1) {
videoInfo.type = "mp4";
} else {
videoInfo.type = "customHls";
}
onInitDplayer();
});
};
// 提交数据(新增/编辑)
const ruleFormRef = ref<FormInstance>();
function gameEnd() {
dpPlayerJs?.destroy();
}
const handleClose = () => {
drawerVisible.value = false;
gameEnd();
};
defineExpose({
acceptParams
});
</script>

482
src/views/video/drawer.vue Normal file
View File

@@ -0,0 +1,482 @@
<template>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" size="720" :title="`${drawerProps.title}`">
<el-form
ref="ruleFormRef"
label-width="90px"
label-suffix=" :"
:rules="rules"
:disabled="drawerProps.isView"
:model="drawerProps.row"
:hide-required-asterisk="drawerProps.isView"
>
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
<template v-if="e.type === 'switch'">
<el-switch
v-model="e.value"
:active-value="`${1}`"
:inactive-value="`${0}`"
active-text="上架"
inactive-text="下架"
/>
</template>
<template v-if="e.type === 'radio'">
<el-radio-group v-model="e.value" class="ml-4">
<el-radio v-for="item in e.options" :key="item.value" :value="item.value" size="large">{{
item.label
}}</el-radio>
</el-radio-group>
</template>
<template v-if="e.type === 'select'">
<el-select filterable multiple v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
<el-option
v-for="(item, index) in e.options"
:key="index"
:label="getField(item, e.fieldNames?.label)"
:value="getField(item, e.fieldNames?.value)"
/>
</el-select>
</template>
<template v-if="e.type === 'text'">
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'text-i'">
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'text-num'">
<el-input v-model="e.value" type="number" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'textarea'">
<el-input
type="textarea"
v-model="e.value"
:rows="5"
maxlength="3650"
:placeholder="`请输入${e.label}`"
show-word-limit
/>
</template>
<template v-if="e.type === 'text-i'">
<UploadImg disabled v-model:image-url="drawerProps.row!.v_cover" width="135px" height="135px" :file-size="3">
<template #empty>
<el-icon><Avatar /></el-icon>
<span>请上传封面</span>
</template>
</UploadImg>
</template>
<template v-if="e.type === 'tag'">
<div class="tag-more-s">
<el-input
v-model="drawerProps.row.searchText"
placeholder="请输入搜索内容"
clearable
@input="handleSearch"
/>
<div>
<span>选中标签:</span>
<div class="tag-more-h">
<el-checkbox-group v-model="TagList" @change="placeableCheckedCitiesChange">
<el-checkbox-button
class="vip-power"
v-for="item in matchingObjects"
:value="item.t_id"
:key="item.t_id"
>
{{ item.t_name }}
</el-checkbox-button>
</el-checkbox-group>
</div>
</div>
<div>
<span>未选中标签:</span>
<div class="tag-more-h">
<el-checkbox-group v-model="TagList" @change="placeableCheckedCitiesChange">
<el-checkbox-button
class="vip-power"
v-for="item in nonMatchingObjects"
:value="item.t_id"
:key="item.t_id"
>
{{ item.t_name }}
</el-checkbox-button>
</el-checkbox-group>
</div>
</div>
</div>
</template>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="drawerVisible = false">取消</el-button>
<el-button v-show="!drawerProps.isView" type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts" name="UserDrawer">
import { ref, computed, reactive } from "vue";
import { ElMessage, FormInstance } from "element-plus";
import { VideoList } from "@/api/interface/video/list";
import { arrNoticeSwitch } from "@/utils/serviceDict";
import { getTypeList } from "@/api/modules/tag/list";
import UploadImg from "@/components/Upload/Img.vue";
// import { extractURI } from "@/utils/eleValidate";
import { getAllList } from "@/api/modules/site/list";
type Options = {
label: string;
value: string | number;
};
interface Item {
t_id: any;
}
const getField = (item: any, fieldName: string | undefined) => {
return fieldName ? item[fieldName] : "";
};
type Fields = {
label: string;
prop: string;
type: string;
value: any;
required: boolean;
options?: Options[];
fieldNames?: { label: string; value: string };
};
const generateRules = (fields: Fields[]) => {
return fields.reduce(
(acc, field) => {
const { type, required, label, prop } = field;
let message = "";
if (type === "select") {
message = `请选择${label}`;
}
if (type === "tag") {
message = `请选择${label}`;
}
if (type === "radio") {
message = `请选择${label}`;
}
if (type === "text") {
message = `请输入${label}`;
}
if (type === "text-i") {
message = `请输入${label}`;
}
if (type === "text-num") {
message = `请输入${label}`;
}
if (type === "textarea") {
message = `请输入${label}`;
}
if (type === "image") {
message = `请上传${label}`;
}
if (type === "image-more") {
message = `请上传${label}`;
}
acc[prop] = [{ required, message }];
return acc;
},
{} as Record<string, any>
);
};
const TagList = ref([] as any);
const getSiteList = ref([] as any);
getAllList().then(res => {
getSiteList.value = res.data.item;
});
const arrTagList = ref([] as any);
const arrNewTagList = ref([] as any);
const matchingObjects = ref([] as any); //选中相同
const nonMatchingObjects = ref([] as any); //没有选中的
const matchingObjectsA = ref([] as any); //选中相同
const nonMatchingObjectsB = ref([] as any); //没有选中的
const getTagList = async () => {
try {
const initParam = reactive({
t_type: 0,
limit: 3650,
page: 1,
t_hidden: 1
});
const siteRes = await getTypeList(initParam);
arrTagList.value = siteRes.data.item;
arrNewTagList.value = siteRes.data.item;
matchingObjects.value = [];
matchingObjectsA.value = [];
// 遍历第一个数组的每个元素
TagList.value.forEach(index => {
// 遍历第二个数组的每个元素
arrNewTagList.value.forEach(obj => {
// 如果第二个数组中的元素的t_id与第一个数组中的元素相同则将其添加到新数组中
if (obj.t_id === index) {
matchingObjects.value = [...matchingObjects.value, obj];
matchingObjectsA.value = [...matchingObjectsA.value, obj];
const matchingIds = matchingObjectsA.value.map(obj => obj.t_id);
TagList.value = matchingIds;
}
});
});
// 创建一个新数组包含objectArray中id不存在于arrayA的元素
nonMatchingObjects.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
nonMatchingObjectsB.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
} catch (error) {
console.log(error);
}
};
// 处理搜索事件
function handleSearch() {
// 在输入事件中实时更新过滤后的标签列表
filteredTagList();
}
function filteredTagList() {
if (drawerProps.value.row!.searchText.length > 0) {
let arrData = arrTagList.value.filter(tag =>
tag.t_name.toLowerCase().includes(drawerProps.value.row!.searchText.toLowerCase())
);
if (arrData.length > 0) {
matchingObjects.value = matchingObjects.value.filter(objA => arrData.some(objB => objB.t_id === objA.t_id));
nonMatchingObjects.value = nonMatchingObjects.value.filter(objA => arrData.some(objB => objB.t_id === objA.t_id));
} else {
matchingObjects.value = [];
nonMatchingObjects.value = [];
}
} else {
matchingObjects.value = matchingObjectsA.value;
nonMatchingObjects.value = nonMatchingObjectsB.value;
}
}
//标签数组
async function placeableCheckedCitiesChange(value) {
matchingObjects.value = [];
matchingObjectsA.value = [];
drawerProps.value.row.v_tag = value;
// 遍历第一个数组的每个元素
TagList.value.forEach(index => {
// 遍历第二个数组的每个元素
arrNewTagList.value.forEach(obj => {
// 如果第二个数组中的元素的t_id与第一个数组中的元素相同则将其添加到新数组中
if (obj.t_id === index) {
matchingObjects.value = [...matchingObjects.value, obj];
matchingObjectsA.value = [...matchingObjectsA.value, obj];
const matchingIds = matchingObjectsA.value.map(obj => obj.t_id);
TagList.value = matchingIds;
}
});
});
nonMatchingObjects.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
nonMatchingObjectsB.value = arrNewTagList.value.filter(obj => !TagList.value.includes(obj.t_id));
}
const formColumns = computed((): Fields[] => {
return [
{
label: "视频名称",
prop: "v_name",
type: "text",
get value() {
return drawerProps.value.row!.v_name;
},
set value(val) {
drawerProps.value.row!.v_name = val;
},
required: true
},
{
label: "视频状态",
prop: "v_status",
type: "switch",
get value() {
return String(drawerProps.value.row!.v_status);
},
set value(val) {
drawerProps.value.row!.v_status = val;
},
required: true
},
{
label: "是否VIP",
prop: "v_vip",
type: "radio",
get value() {
return drawerProps.value.row!.v_vip;
},
set value(val) {
drawerProps.value.row!.v_vip = val;
},
options: arrNoticeSwitch,
fieldNames: { label: "label", value: "value" },
required: true
},
{
label: "精彩站点",
prop: "v_wonderful_site_id",
type: "select",
get value() {
return drawerProps.value.row!.v_wonderful_site_id;
},
set value(val) {
drawerProps.value.row!.v_wonderful_site_id = val;
},
options: getSiteList.value,
fieldNames: { label: "si_name", value: "si_id" },
required: false
},
{
label: "推荐站点",
prop: "v_recommend_site_id",
type: "select",
get value() {
return drawerProps.value.row!.v_recommend_site_id;
},
set value(val) {
drawerProps.value.row!.v_recommend_site_id = val;
},
options: getSiteList.value,
fieldNames: { label: "si_name", value: "si_id" },
required: false
},
{
label: "播放时长",
prop: "v_play_time",
type: "text",
get value() {
return drawerProps.value.row!.v_play_time;
},
set value(val) {
drawerProps.value.row!.v_play_time = val;
},
required: true
},
{
label: "视频封面",
prop: "v_cover_edit",
type: "text",
get value() {
return drawerProps.value.row!.v_cover_edit;
},
set value(val) {
drawerProps.value.row!.v_cover_edit = val;
},
required: true
},
{
label: "视频简介",
prop: "v_describe",
type: "textarea",
get value() {
return drawerProps.value.row!.v_describe;
},
set value(val) {
drawerProps.value.row!.v_describe = val;
},
required: true
},
{
label: "视频标签",
prop: "v_tag",
type: "tag",
get value() {
return drawerProps.value.row!.v_tag;
},
set value(val) {
drawerProps.value.row!.v_tag = val;
},
options: arrTagList.value,
fieldNames: { label: "label", value: "value" },
required: true
}
];
});
const rules = ref();
interface DrawerProps {
title: string;
isView: boolean;
row: Partial<VideoList.ResList>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
const drawerVisible = ref(false);
const drawerProps = ref<DrawerProps>({
isView: false,
title: "",
row: {}
});
// 接收父组件传过来的参数
const acceptParams = (params: DrawerProps) => {
drawerProps.value = params;
rules.value = generateRules(formColumns.value);
drawerVisible.value = true;
if (drawerProps.value.title === "新增视频") {
drawerProps.value.row!.v_vip = 0;
drawerProps.value.row!.v_wonderful_status = 0;
drawerProps.value.row!.v_recommend_status = 0;
}
TagList.value = [];
if (drawerProps.value.row.v_cover) {
drawerProps.value.row.v_cover_code = drawerProps.value.row.v_cover.code;
drawerProps.value.row.v_cover_edit = drawerProps.value.row.v_cover.uri;
}
if (drawerProps.value.row.v_play_url_detail) {
const data = drawerProps.value.row.v_play_url_detail;
drawerProps.value.row.v_play_url = data[0];
}
if (drawerProps.value.row.v_down_url_detail) {
const data = drawerProps.value.row.v_down_url_detail;
drawerProps.value.row.v_down_url = data[0];
}
if (drawerProps.value.row.v_tag) {
TagList.value = drawerProps.value.row.v_tag.map((obj: Item) => obj.t_id).map(Number);
}
getTagList();
};
// 提交数据(新增/编辑)
const ruleFormRef = ref<FormInstance>();
const handleSubmit = () => {
ruleFormRef.value!.validate(async valid => {
if (!valid) return;
try {
const params = { ...drawerProps.value.row };
let arrCoverData = {
uri: params.v_cover_edit,
code: params.v_cover_code
};
params.v_cover = arrCoverData;
params.v_tag = JSON.stringify(TagList.value);
await drawerProps.value.api!(params);
ElMessage.success({ message: `${drawerProps.value.title}成功!` });
drawerProps.value.getTableList!();
drawerVisible.value = false;
} catch (error) {
console.log(error);
}
});
};
defineExpose({
acceptParams
});
</script>
<style lang="scss">
.el-checkbox-button__inner {
margin-right: 10px;
margin-bottom: 10px;
border-left-color: #dcdfe6 !important;
}
.tag-more-h {
margin-bottom: 10px;
}
.tag-more-s {
width: 100%;
padding: 10px;
border: 1px solid black;
box-shadow: 0 0 4px 2px rgb(0 0 0 / 90%);
}
</style>

View File

@@ -0,0 +1,103 @@
<template>
<div class="table-box">
<ProTable
ref="proTable"
highlight-current-row
:columns="columns"
:init-param="initParam"
:request-api="getTableList"
:data-callback="dataCallback"
row-key="id"
:indent="20"
:search-col="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 3 }"
>
</ProTable>
</div>
</template>
<script setup lang="tsx" name="complexProTable">
import { reactive, ref } from "vue";
import { fenLeiList } from "@/api/interface/video/fenlei";
import ProTable from "@/components/ProTable/index.vue";
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import { getList } from "@/api/modules/video/fenlei";
import { arrPlanSwitch } from "@/utils/serviceDict";
// ProTable 实例
const proTable = ref<ProTableInstance>();
const initParam = reactive({
limit: 15,
page: 1
});
const dataCallback = (data: any) => {
let result = [] as any;
data.items.forEach((item, index) => {
if (item.fl_pid === 0) {
// Create a new first dimension entry
item.id = index + 1;
// item.fl_id = "";
const firstDimension = { ...item, children: [] };
// Find and add second dimension entries
data.items.forEach(subItem => {
if (subItem.fl_pid === item.fl_id) {
firstDimension.children.push(subItem);
}
});
result.push(firstDimension);
}
});
result.forEach(item => {
if (item.fl_pid === 0) {
item.fl_id = "";
}
});
return {
items: result,
total: data.total,
total_page: data.total_pages,
page: initParam.page,
limit: initParam.limit
};
};
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
initParam.page = newParams.page;
initParam.limit = newParams.limit;
return getList(newParams);
};
// 表格配置项
const columns = reactive<ColumnProps<fenLeiList.ResList>[]>([
{ type: "selection", fixed: "left", width: 50 },
{
prop: "key",
label: "搜索内容",
isShow: false,
search: {
el: "input"
}
},
{
prop: "fl_id",
label: "ID",
width: 100
},
{
prop: "fl_mingzi",
label: "分类名称"
},
{
prop: "fl_leixing",
label: "类型",
tag: true,
enum: arrPlanSwitch,
render(scope) {
return (
<el-tag type={scope.row.fl_leixing ? "success" : "info"}>{scope.row.fl_leixing === 1 ? "其他" : "默认"}</el-tag>
);
}
}
]);
</script>

231
src/views/video/index.vue Normal file
View File

@@ -0,0 +1,231 @@
<template>
<div class="table-box">
<ProTable
ref="proTable"
highlight-current-row
:columns="columns"
:request-api="getTableList"
:data-callback="dataCallback"
>
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<el-button
type="primary"
:icon="EditPen"
plain
:disabled="!scope.isSelected"
@click="batchModify(scope.selectedList)"
>
批量设置每日推荐视频
</el-button>
<el-button
type="primary"
:icon="EditPen"
plain
:disabled="!scope.isSelected"
@click="batchVipDataBtn(scope.selectedList)"
>
批量设置vip
</el-button>
</template>
</ProTable>
<ModifyDrawer ref="modifyDrawerRef" />
</div>
</template>
<script setup lang="tsx" name="complexProTable">
import { ElImage } from "element-plus";
import { reactive, ref } from "vue";
import { VideoList } from "@/api/interface/video/list";
import { useHandleData } from "@/hooks/useHandleData";
import ProTable from "@/components/ProTable/index.vue";
import { EditPen } from "@element-plus/icons-vue";
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import { getVideoList } from "@/api/modules/video/list";
import { getAllList } from "@/api/modules/video/fenlei";
import { batchData, batchVipData } from "@/api/modules/video/list";
import { LOSE_STR_KEY, LOGIN_URL } from "@/config";
import router from "@/routers";
import { h } from "vue";
import { AsyncImageComponent } from "@/utils/decrypt";
import { arrNoticeSwitch } from "@/utils/serviceDict";
import ModifyDrawer from "./modifyDrawer.vue";
// ProTable 实例
const proTable = ref<ProTableInstance>();
// 视频分类
const getFenLeiList = ref([] as any);
getAllList().then(res => {
if (res.code === LOSE_STR_KEY) {
router.replace(LOGIN_URL);
return;
}
let arrFenlei = res.data.items;
getFenLeiList.value = arrFenlei;
});
const initParam = reactive({
limit: 15,
page: 1
});
const dataCallback = (data: any) => {
return {
items: data.items,
total: data.total,
total_page: data.total_pages,
page: initParam.page,
limit: initParam.limit
};
};
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
initParam.page = newParams.page;
initParam.limit = newParams.limit;
return getVideoList(newParams);
};
// 表格配置项
const columns = reactive<ColumnProps<VideoList.ResList>[]>([
{ type: "selection", fixed: "left", width: 50 },
{
prop: "key",
label: "搜索内容",
isShow: false,
search: {
el: "input"
}
},
{
prop: "sp_id",
label: "ID",
width: 100
},
{
prop: "sp_name",
label: "名称"
},
{
prop: "sp_fengmian_url",
label: "视频封面",
width: 120,
render(scope) {
return h(ElImage, {
style: "width: 98%; height: 30px; display: flex; align-items: center;",
src: scope.row.sp_fengmian_url,
previewSrcList: [scope.row.sp_fengmian_url],
hideOnClickModal: true,
zIndex: 999999999,
previewTeleported: true,
fit: "cover"
});
return <img src={scope.row.sp_fengmian_url} alt="视频封面" style="width: 100%; height: auto;" />;
return h(AsyncImageComponent, { url: scope.row.sp_fengmian_url });
}
},
{
prop: "sp_is_vip",
label: "是否VIP",
width: 120,
fieldNames: { label: "label", value: "value" },
search: { el: "select", props: { filterable: true } },
enum: arrNoticeSwitch,
render(scope) {
return (
<el-tag type={scope.row.sp_is_vip === 0 ? "danger" : "success"}>{scope.row.sp_is_vip === 0 ? "否" : "是"}</el-tag>
);
}
},
{
prop: "sp_tui_jian_shi_jian",
label: "是否推荐",
width: 120,
fieldNames: { label: "label", value: "value" },
search: { el: "select", props: { filterable: true } },
enum: arrNoticeSwitch,
render(scope) {
return (
<el-tag type={scope.row.sp_tui_jian_shi_jian === 0 ? "danger" : "success"}>
{scope.row.sp_tui_jian_shi_jian === 0 ? "否" : "是"}
</el-tag>
);
}
},
{
prop: "fl_id",
label: "分类",
width: 120,
fieldNames: { label: "fl_mingzi", value: "fl_id" },
enum: getFenLeiList,
search: { el: "select", props: { filterable: true } }
},
{
prop: "sp_biaoqian",
label: "标签",
width: 250,
render(scope) {
if (scope.row.sp_biaoqian) {
if (Array.isArray(scope.row.sp_biaoqian)) {
return <span>{scope.row.sp_biaoqian.join(", ")}</span>;
} else {
const data = JSON.parse(scope.row.sp_biaoqian);
return <span>{data.join(", ")}</span>;
}
}
}
},
{
prop: "sp_fangwen",
label: "访问量",
width: 100
},
{
prop: "sp_shichang",
label: "时长",
width: 100
},
{
prop: "sp_source_code",
label: "源码",
width: 200
},
{ prop: "created_at", label: "添加时间", width: 170 }
]);
// 批量修改视频信息
const batchModify = async (id: any[]) => {
const ids = id.map((item: VideoList.ResList) => item.sp_id);
await useHandleData(batchData, { sp_id: ids.join(",") }, "批量设置所选信息为每日推荐视频");
proTable.value?.clearSelection();
proTable.value?.getTableList();
};
// 批量修改视频vip
const modifyDrawerRef = ref<InstanceType<typeof ModifyDrawer> | null>(null);
const batchVipDataBtn = async (id: any[], row: Partial<VideoList.ResList> = {}) => {
const ids = id.map((item: VideoList.ResList) => item.sp_id);
row.sp_id = ids.join(",");
const params = {
title: "批量编辑VIP视频",
row: { ...row },
api: batchVipData,
getTableList: proTable.value?.getTableList,
clearSelection: proTable.value?.clearSelection
};
modifyDrawerRef.value?.acceptParams(params as any);
};
</script>
<style lang="scss">
.el-table .warning-row,
.el-table .warning-row .el-table-fixed-column--right,
.el-table .warning-row .el-table-fixed-column--left {
background-color: var(--el-color-warning-light-9);
}
.el-table .success-row,
.el-table .success-row .el-table-fixed-column--right,
.el-table .success-row .el-table-fixed-column--left {
background-color: var(--el-color-success-light-9);
}
</style>

View File

@@ -0,0 +1,207 @@
<template>
<el-drawer v-model="drawerVisible" :destroy-on-close="true" size="520" :title="`${drawerProps.title}`">
<el-form
ref="ruleFormRef"
label-width="100px"
label-suffix=" :"
:rules="rules"
:disabled="drawerProps.isView"
:model="drawerProps.row"
:hide-required-asterisk="drawerProps.isView"
>
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
<template v-if="e.type === 'switch'">
<el-switch
v-model="e.value"
:active-value="`${1}`"
:inactive-value="`${0}`"
active-text="上架"
inactive-text="下架"
/>
</template>
<template v-if="e.type === 'radio'">
<el-radio-group v-model="e.value" class="ml-4">
<el-radio v-for="item in e.options" :key="item.value" :value="item.value" size="large">{{
item.label
}}</el-radio>
</el-radio-group>
</template>
<template v-if="e.type === 'select'">
<el-select filterable multiple v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
<el-option
v-for="(item, index) in e.options"
:key="index"
:label="getField(item, e.fieldNames?.label)"
:value="getField(item, e.fieldNames?.value)"
/>
</el-select>
</template>
<template v-if="e.type === 'text'">
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'text-i'">
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'text-num'">
<el-input v-model="e.value" type="number" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'textarea'">
<el-input
type="textarea"
v-model="e.value"
maxlength="3650"
:placeholder="`请输入${e.label}`"
show-word-limit
/>
</template>
<template v-if="e.type === 'text-i'">
<UploadImg disabled v-model:image-url="drawerProps.row!.v_cover" width="135px" height="135px" :file-size="3">
<template #empty>
<el-icon><Avatar /></el-icon>
<span>请上传封面</span>
</template>
</UploadImg>
</template>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="drawerVisible = false">取消</el-button>
<el-button v-show="!drawerProps.isView" type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-drawer>
</template>
<script setup lang="ts" name="UserDrawer">
import { ref, computed } from "vue";
import { ElMessage, FormInstance } from "element-plus";
import { VideoList } from "@/api/interface/video/list";
import UploadImg from "@/components/Upload/Img.vue";
import { arrNoticeSwitch } from "@/utils/serviceDict";
type Options = {
label: string;
value: string | number;
};
const getField = (item: any, fieldName: string | undefined) => {
return fieldName ? item[fieldName] : "";
};
type Fields = {
label: string;
prop: string;
type: string;
value: any;
required: boolean;
options?: Options[];
fieldNames?: { label: string; value: string };
};
const generateRules = (fields: Fields[]) => {
return fields.reduce(
(acc, field) => {
const { type, required, label, prop } = field;
let message = "";
if (type === "select") {
message = `请选择${label}`;
}
if (type === "tag") {
message = `请选择${label}`;
}
if (type === "radio") {
message = `请选择${label}`;
}
if (type === "text") {
message = `请输入${label}`;
}
if (type === "text-i") {
message = `请输入${label}`;
}
if (type === "text-num") {
message = `请输入${label}`;
}
if (type === "textarea") {
message = `请输入${label}`;
}
if (type === "image") {
message = `请上传${label}`;
}
if (type === "image-more") {
message = `请上传${label}`;
}
acc[prop] = [{ required, message }];
return acc;
},
{} as Record<string, any>
);
};
const formColumns = computed((): Fields[] => {
return [
{
label: "是否VIP",
prop: "sp_is_vip",
type: "radio",
get value() {
return drawerProps.value.row!.sp_is_vip;
},
set value(val) {
drawerProps.value.row!.sp_is_vip = val;
},
options: arrNoticeSwitch,
fieldNames: { label: "label", value: "value" },
required: true
}
];
});
const rules = ref();
interface DrawerProps {
title: string;
isView: boolean;
row: Partial<VideoList.ResList>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
clearSelection?: () => void;
}
const drawerVisible = ref(false);
const drawerProps = ref<DrawerProps>({
isView: false,
title: "",
row: {}
});
// 接收父组件传过来的参数
const acceptParams = (params: DrawerProps) => {
drawerProps.value = params;
rules.value = generateRules(formColumns.value);
drawerVisible.value = true;
};
// 提交数据(新增/编辑)
const ruleFormRef = ref<FormInstance>();
const handleSubmit = () => {
ruleFormRef.value!.validate(async valid => {
if (!valid) return;
try {
const params = { ...drawerProps.value.row };
await drawerProps.value.api!(params);
ElMessage.success({ message: `${drawerProps.value.title}成功!` });
drawerProps.value.getTableList!();
drawerProps.value.clearSelection!();
drawerVisible.value = false;
} catch (error) {
console.log(error);
}
});
};
defineExpose({
acceptParams
});
</script>
<style lang="scss">
.el-checkbox-button__inner {
margin-right: 10px;
margin-bottom: 10px;
border-left-color: #dcdfe6 !important;
}
</style>

View File

@@ -0,0 +1,113 @@
<template>
<div class="table-box">
<ProTable
ref="proTable"
highlight-current-row
:columns="columns"
:request-api="getTableList"
:data-callback="dataCallback"
row-key="id"
:indent="20"
:search-col="{ xs: 1, sm: 1, md: 2, lg: 3, xl: 3 }"
>
</ProTable>
</div>
</template>
<script setup lang="tsx" name="complexProTable">
import { reactive, ref } from "vue";
import { nvYouList } from "@/api/interface/video/nvyou";
import ProTable from "@/components/ProTable/index.vue";
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import { getList } from "@/api/modules/video/nvyou";
import { h } from "vue";
import { AsyncImageComponent } from "@/utils/decrypt";
// ProTable 实例
const proTable = ref<ProTableInstance>();
const initParam = reactive({
limit: 15,
page: 1
});
const dataCallback = (data: any) => {
return {
items: data.items,
total: data.total,
total_page: data.total_pages,
page: initParam.page,
limit: initParam.limit
};
};
const getTableList = (params: any) => {
let newParams = JSON.parse(JSON.stringify(params));
initParam.page = newParams.page;
initParam.limit = newParams.limit;
return getList(newParams);
};
// 表格配置项
const columns = reactive<ColumnProps<nvYouList.ResList>[]>([
{ type: "selection", fixed: "left", width: 50 },
{
prop: "key",
label: "搜索内容",
isShow: false,
search: {
el: "input"
}
},
{
prop: "ny_id",
label: "ID",
width: 100
},
{
prop: "ny_name",
label: "名称"
},
{
prop: "ny_pinyin",
label: "拼音"
},
{
prop: "ny_cover_url",
label: "女优封面",
width: 120,
render(scope) {
return h(AsyncImageComponent, { url: scope.row.ny_cover_url });
}
},
{
prop: "ny_zhaobei",
label: "罩杯"
},
{
prop: "ny_yaowei",
label: "腰围"
},
{
prop: "ny_biwei",
label: "臂围"
},
{
prop: "ny_shengao",
label: "身高"
},
{
prop: "ny_chushengriqi",
label: "出生"
},
{
prop: "ny_chudaonianfen",
label: "出道"
},
{
prop: "ny_jieshao",
label: "描述",
width: 250
}
]);
</script>

View File

@@ -0,0 +1,178 @@
<template>
<el-dialog v-model="drawerVisible" :title="drawerProps.title" :destroy-on-close="true" width="520px" draggable>
<el-form
ref="ruleFormRef"
label-width="50px"
label-suffix=" :"
:rules="rules"
:disabled="drawerProps.isView"
:model="drawerProps.row"
:hide-required-asterisk="drawerProps.isView"
>
<el-form-item v-for="e in formColumns" :key="e.label" :label="e.label" :prop="e.prop" :required="e.required">
<template v-if="e.type === 'switch'">
<el-switch v-model="e.value" />
</template>
<template v-if="e.type === 'radio'">
<el-radio-group v-model="e.value" class="ml-4">
<el-radio v-for="item in e.options" :key="item.value" :label="item.value" size="large">{{
item.label
}}</el-radio>
</el-radio-group>
</template>
<template v-if="e.type === 'select'">
<el-select filterable v-model="e.value" :placeholder="`请选择${e.label}`" clearable>
<el-option
v-for="(item, index) in e.options"
:key="index"
:label="getField(item, e.fieldNames?.label)"
:value="getField(item, e.fieldNames?.value)"
/>
</el-select>
</template>
<template v-if="e.type === 'tag'">
<el-tag
style="margin: 7px; margin-right: 10px"
v-for="item in drawerProps.row!.v_tag"
:key="item.t_id"
:type="item.type"
effect="dark"
>
{{ item.t_name }}
</el-tag>
</template>
<template v-if="e.type === 'text'">
<el-input v-model="e.value" :placeholder="`请输入${e.label}`" clearable></el-input>
</template>
<template v-if="e.type === 'textarea'">
<el-input
type="textarea"
:rows="10"
v-model="e.value"
maxlength="500000"
:placeholder="`请输入${e.label}`"
show-word-limit
/>
</template>
<template v-if="e.type === 'textareaMore'">
<el-input
type="textarea"
v-model="e.value"
maxlength="500000"
:placeholder="`请输入${e.label}`"
show-word-limit
/>
</template>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="drawerVisible = false">取消</el-button>
<el-button type="primary" @click="drawerVisible = false">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="UserDrawer">
import { ref, computed } from "vue";
import { FormInstance } from "element-plus";
import { VideoList } from "@/api/interface/video/list";
type Options = {
label: string;
value: string | number;
};
const getField = (item: any, fieldName: string | undefined) => {
return fieldName ? item[fieldName] : "";
};
type Fields = {
label: string;
prop: string;
type: string;
value: any;
required: boolean;
options?: Options[];
fieldNames?: { label: string; value: string };
};
const generateRules = (fields: Fields[]) => {
return fields.reduce(
(acc, field) => {
const { type, required, label, prop } = field;
let message = "";
if (type === "select") {
message = `请选择${label}`;
}
if (type === "text") {
message = `请输入${label}`;
}
if (type === "textarea") {
message = `请输入${label}`;
}
if (type === "textareaMore") {
message = `请输入${label}`;
}
if (type === "radio") {
message = `请选择${label}`;
}
acc[prop] = [{ required, message }];
return acc;
},
{} as Record<string, any>
);
};
const formColumns = computed((): Fields[] => {
return [
{
label: "标签",
prop: "t_name",
type: "tag",
get value() {
return drawerProps.value.row!.t_name;
},
set value(val) {
drawerProps.value.row!.t_name = val;
},
options: drawerProps.value.row!.tag_list,
fieldNames: { label: "t_name", value: "t_id" },
required: false
}
];
});
const rules = ref();
interface DrawerProps {
title: string;
isView: boolean;
row: Partial<VideoList.ResList>;
api?: (params: any) => Promise<any>;
getTableList?: () => void;
}
const drawerVisible = ref(false);
const drawerProps = ref<DrawerProps>({
isView: false,
title: "",
row: {}
});
// 接收父组件传过来的参数
const acceptParams = async (params: DrawerProps) => {
drawerProps.value = params;
rules.value = generateRules(formColumns.value);
drawerVisible.value = true;
};
// 提交数据(新增/编辑)
const ruleFormRef = ref<FormInstance>();
defineExpose({
acceptParams
});
</script>
<style>
.el-dialog .el-dialog__header {
padding: 15px 0;
}
</style>