Browse Source

视频库等

pansl 2 years ago
parent
commit
3ece0dfed9

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "nprogress": "^0.2.0",
     "pinia": "^2.0.32",
     "postcss": "^8.4.21",
+    "qiniu-js": "^3.4.1",
     "sortablejs": "^1.15.0",
     "tailwindcss": "^3.2.2",
     "terser": "^5.16.5",

+ 14 - 0
src/api/qiniu/index.ts

@@ -0,0 +1,14 @@
+import http from '@/api/http';
+
+/**
+ * 上传图片
+ */
+export function qiniuUploadImage(params: object) {
+  return http.post('/qiniu/upload/image', params);
+}
+/**
+ * 视频-上传token
+ */
+export function qiniuUploadToken(params?: object) {
+  return http.get(`/qiniu/upload/token`, params);
+}

+ 38 - 0
src/api/video/index.ts

@@ -0,0 +1,38 @@
+import http from '@/api/http';
+/**
+ * 视频剧集-分页列表
+ */
+export function videoStockEpisodeList(params: object) {
+  return http.get('/videoStock/episode/list', params);
+}
+/**
+ * 视频-分页列表
+ */
+export function videoStockVideoList(params: object) {
+  return http.get('/videoStock/video/list', params);
+}
+
+/**
+ * 视频-添加
+ */
+export function videoStockVideoAdd(params: object) {
+  return http.post('/videoStock/video/add', params);
+}
+/**
+ * 视频-更新
+ */
+export function videoStockVideoUpdate(params: object) {
+  return http.post(`/videoStock/video/update`, params);
+}
+/**
+ * 视频-订阅设置
+ */
+export function videoStockVideoSetChargeConfig(params: object) {
+  return http.post(`/videoStock/video/setChargeConfig`, params);
+}
+/**
+ * 视频-保存视频
+ */
+export function videoStockEpisodeAdd(params: object) {
+  return http.post(`/videoStock/episode/add`, params);
+}

+ 227 - 0
src/components/Upload/VideoUploader.vue

@@ -0,0 +1,227 @@
+<template>
+  <el-upload class="w-auto" :action="uploadUrl" :headers="headers" list-type="text" :file-list="props.fileList"
+    :data="extraParams" :multiple="props.isMultiple" :accept="props.acceptType" :auto-upload="true"
+    :handleRemove="handleRemove" :show-file-list="true" :before-upload="beforeUpload" :on-progress="onProgress"
+    :on-success="onSuccess">
+    <div class="el-upload__text">
+      <el-icon class="avatar-uploader-icon">
+        <Upload />
+      </el-icon>
+      <span>{{ props.buttonText }}</span>
+    </div>
+    <template #tip v-if="!props.isDisableUpload && isShowTips">
+      <div class="el-upload__tip">
+        <span>支持{{ acceptTypeDesc }};</span> <span v-if="isLimitSize">文件大小不能超过{{
+          props.maxFileSize }}M</span>
+      </div>
+    </template>
+  </el-upload>
+</template>
+
+<script lang="ts" setup>
+import { Close, Upload, Download, Plus, Link, DeleteFilled } from "@element-plus/icons-vue";
+import { defineComponent, ref } from 'vue'
+import * as qiniu from 'qiniu-js'
+import { qiniuUploadToken } from '@/api/qiniu/index'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import type { Action, UploadProps, UploadUserFile } from 'element-plus'
+const emits = defineEmits(["success", "fileRemove"]);
+interface Props {
+  acceptType?: string; // 上传文件类型
+  acceptTypeDesc?: string; // 描述 - 上传文件类型
+  isMultiple?: boolean; //   是否可批量上传
+  isLimitSize?: boolean;//是否限制大小
+  isShowTips?: boolean;
+  maxFileSize?: number; // 文件大小
+  isCheckName?: boolean; //   是否检查文件名
+  limitNum?: number; // 允许上传文件的最大数量
+  formType?: string;//上传的文件字段名称
+  buttonText?: string;//上传的文件字段名称
+  fileList?: any; // 回显的文件
+  public_video_url?: string; // 封面
+}
+// 接收父组件传递过来的参数
+const props = withDefaults(defineProps<Props>(), {
+  acceptType: ".mp4",
+  acceptTypeDesc: ".mp4",
+  formType: "file",
+  maxFileSize: 1000,
+  isMultiple: true,
+  isLimitSize: false,
+  isShowTips: false,
+  isCheckName: true,
+  limitNum: 10,
+  fileList: [],
+  buttonText: '上传文件',
+  public_video_url: ''
+});
+const uploadUrl = ref('')
+const extraParams = ref({})
+const token = ref('')
+const headers = ref({})
+const progress = ref(0)
+const uploading = ref(false)
+// 校验上传文件格式
+const getType = (acceptType: string) => {
+  let val = "";
+  switch (acceptType) {
+    case ".mp4":
+      val = "mp4";
+      break;
+    case ".avi":
+      val = "avi";
+      break;
+    case ".wmv":
+      val = "wmv";
+      break;
+  }
+  return val
+};
+const getToken = async () => {
+  try {
+    const res = await qiniuUploadToken()
+    token.value = await res.data
+    if (token.value) {
+      headers.value = {
+        Authorization: `${token.value}`,
+      }
+    }
+    var config = {
+      checkByServer: true,
+      checkByMD5: true,
+      forceDirect: false,
+      useCdnDomain: true,
+      disableStatisticsReport: false,
+      retryCount: 6,
+      region: qiniu.region.z0,
+      debugLogLevel: 'INFO'
+    };
+    qiniu.getUploadUrl(config, token.value).then(res => {
+      console.log(res, 'getUploadUrlgetUploadUrl');
+      uploadUrl.value = res
+    })
+  } catch (error) {
+    console.error(error)
+    ElMessage.error('获取上传凭证失败')
+  }
+}
+
+const beforeUpload = (file: File) => {
+  if (!/^[0-9]+_/.test(file.name) && props.isCheckName) {
+    ElMessage.error(`文件上传格式错误,请以数字和下划线开头,例如:01_xxx`);
+    return false
+  }
+  const filetype = file.type;
+  const list = props.acceptTypeDesc.split("/");
+  let acceptTypeList = list.map((its: string) => {
+    console.log(its, 'itsits', filetype, file);
+    return getType(its)
+  })
+  // 如果要检索的字符串值没有出现,则该方法返回 -1
+  const ars = acceptTypeList.filter((q: string) => {
+    return filetype.indexOf(q) > -1
+  })
+  // 用于校验是否符合上传条件
+  const type = props.acceptTypeDesc.replace("/", ", ");
+  console.log(type, 'typetype');
+  if (ars.length < 1) {
+    ElMessage.error(`仅支持格式为${type}的图片`);
+    return false;
+  }
+  if (file.size / 1024 / 1024 > props.maxFileSize && props.isLimitSize) {
+    ElMessage.error(`文件大小不能超过${props.maxFileSize}MB!`);
+    return false;
+  }
+  console.log(file, '$(prefix)-$(year)-$(fname)$(ext)');
+  uploading.value = true
+  const fname = `${Date.now()}_${file.name}`
+  extraParams.value = {
+    key: `${fname}`, token: token.value
+  }
+  return checkFileName(file)
+
+}
+
+const onProgress = (event: ProgressEvent) => {
+  progress.value = Math.floor((event.loaded / event.total) * 100)
+}
+
+const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
+  console.log(file, uploadFiles)
+  emits('fileRemove', file)
+}
+
+const onSuccess = (response: any) => {
+  uploading.value = false
+  ElMessage.success('上传成功')
+  emits('success', response)
+  console.log(props.public_video_url);
+  props.fileList.push({ name: response.fname, url: props.public_video_url, ...response })
+}
+
+const onError = (error: any) => {
+  uploading.value = false
+  ElMessage.error('上传失败')
+  console.error(error)
+}
+
+const upload = (file: File) => {
+  const key = `${Date.now()}_${file.name}`
+  extraParams.value = {
+    key, token: token.value
+  }
+  setTimeout(() => {
+    const observable = qiniu.upload(file, key, token.value)
+    observable.subscribe({
+      next: (res) => {
+        progress.value = res.total.percent
+      },
+      error: (err) => {
+        onError(err)
+      },
+      complete: (res) => {
+        onSuccess(res)
+      },
+    })
+  }, 50);
+
+}
+
+const checkFileName = (file: File) => {
+  const fileName = file.name
+  if (fileNameList.value.includes(fileName)) {
+    console.log(fileName);
+    ElMessageBox.alert(`上传失败,《${fileName}》文件名已存在,请修改后重新上传`)
+    return false
+  } else {
+    fileNameList.value.push(fileName)
+    return true
+  }
+}
+
+const fileNameList = ref<string[]>([])
+
+onMounted(() => {
+  getToken()
+})
+</script>
+
+<style lang="scss" scoped>
+:deep(.el-upload__text) {
+  width: 106px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #ffffff;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+
+  span {
+    font-size: 14px;
+    padding-left: 6px;
+    font-family: PingFangSC-Regular, PingFang SC;
+    font-weight: 400;
+    color: rgba(0, 0, 0, 0.65);
+  }
+}
+</style>

+ 91 - 54
src/components/Upload/myUpload.vue

@@ -1,43 +1,68 @@
 <template>
   <div class="upload_wrap">
-    <el-upload v-if="!props.isDisableUpload" class="w-auto upload" ref="uploadRef" :file-list="waitFileList"
-      :multiple="props.isMultiple" :limit="props.limitNum" :list-type="listType" :accept="props.acceptType"
-      :auto-upload="false" :show-file-list="false" :disabled="props.isDisableUpload" :on-change="handleChange">
-      <div class="el-upload__text" v-if="listType == 'text'">
-        <el-icon>
-          <Upload />
-        </el-icon>
-        <span>上传文件</span>
+    <block v-if="waitFileList.length > 0">
+      <div class="el-upload-list el-upload-list--picture-card" v-if="listType == 'picture-card' || listType == 'picture'">
+        <div class="el-upload-list__item is-success" v-for="( item, index ) in  waitFileList " :key="index">
+          <el-image style="width:148px;height:148px;" :src="item.url" fit="fill" :lazy="true"></el-image>
+          <div v-if="!props.isDisableUpload" class="el-upload-list__item-actions">
+            <el-icon class="cursor-pointer el-upload-list__item-delete" @click="removeFile(item)">
+              <DeleteFilled />
+            </el-icon>
+          </div>
+          <span v-if="isDownLoad" style="paddingleft: 5px">
+            <el-icon @click="handleDownLoad(item)">
+              <Download />
+            </el-icon>
+          </span>
+        </div>
       </div>
-      <div v-else>
-        <el-icon>
-          <Plus />
-        </el-icon>
+      <div class="template_list" v-if="listType == 'text'">
+        <div class="template" v-for="(item, index) in waitFileList" :key="index">
+          <span>
+            <el-icon>
+              <Link />
+            </el-icon>
+          </span>
+          <span class="documentName">{{ item.name }}</span>
+          <span v-if="!props.isDisableUpload">
+            <el-icon color="#000000a6" size="16" @click="removeFile(item)">
+              <Close />
+            </el-icon>
+          </span>
+          <span v-if="isDownLoad" style="paddingleft: 5px">
+            <el-icon @click="handleDownLoad(item)">
+              <Download />
+            </el-icon>
+          </span>
+        </div>
       </div>
-    </el-upload>
-    <div class="template_list">
-      <div class="template" v-for="(item, index) in waitFileList" :key="index">
-        <span>
+    </block>
+    <el-upload v-else class="w-auto upload" ref="uploadRef" :file-list="waitFileList" :multiple="props.isMultiple"
+      :limit="props.limitNum" :list-type="listType" :accept="props.acceptType" :auto-upload="false"
+      :show-file-list="false" :disabled="props.isDisableUpload" :on-change="handleChange" :on-remove="handleRemove">
+      <block>
+        <div class="el-upload__text" v-if="listType == 'text'">
           <el-icon>
-            <Link />
-          </el-icon>
-        </span>
-        <span class="documentName">{{ item.name }}</span>
-        <span v-if="!props.isDisableUpload">
-          <el-icon color="#000000a6" size="16" @click="removeFile(item)">
-            <Close />
+            <Upload />
           </el-icon>
-        </span>
-        <span v-if="isDownLoad" style="paddingleft: 5px">
-          <el-icon @click="handleDownLoad(item)">
-            <Download />
+          <span>上传文件</span>
+        </div>
+        <div v-else>
+          <el-icon>
+            <Plus />
           </el-icon>
-        </span>
-      </div>
-    </div>
-    <div class="tips" v-if="!props.isDisableUpload && isShowTips"><span>支持{{ acceptTypeDesc }};</span> <span
-        v-if="isLimitSize">文件大小不能超过{{
-          props.maxFileSize }}M</span> </div>
+        </div>
+      </block>
+      <template #tip v-if="!props.isDisableUpload && isShowTips">
+        <div class="el-upload__tip">
+          <span>支持{{ acceptTypeDesc }};</span> <span v-if="isLimitSize">文件大小不能超过{{
+            props.maxFileSize }}M</span>
+        </div>
+      </template>
+    </el-upload>
+    <el-dialog v-model="dialogVisible">
+      <img w-full :src="dialogImageUrl" alt="Preview Image" />
+    </el-dialog>
   </div>
 </template>
 
@@ -45,18 +70,21 @@
 import { ref, watch } from "vue";
 import { ElLoading, ElMessage } from "element-plus";
 import http from '@/api/http';
-import { Close, Upload, Download, Plus, Link } from "@element-plus/icons-vue";
+import { Close, Upload, Download, Plus, Link, DeleteFilled } from "@element-plus/icons-vue";
+import type { UploadProps, UploadUserFile } from 'element-plus'
 const emits = defineEmits(["fileSuccess", "fileRemove"]);
 interface Props {
   acceptType?: string; // 上传文件类型
   acceptTypeDesc?: string; // 描述 - 上传文件类型
   isMultiple?: boolean; //   是否可批量上传
+  isCheckName?: boolean; //   是否检查文件名
   limitNum?: number; // 允许上传文件的最大数量
   isDisableUpload?: boolean; // 是否禁用上传
   maxFileSize?: number; // 文件大小
   isLimitSize?: boolean;//是否限制大小
   isShowTips?: boolean;
   action?: string;
+  formType?: string;//上传的文件字段名称
   fileList?: any; // 回显的文件
   isDownLoad?: boolean; // 是否可以下载
   listType?: string;//文件上传样式类型
@@ -65,13 +93,15 @@ interface Props {
 const props = withDefaults(defineProps<Props>(), {
   acceptType: ".jpeg,.png",
   acceptTypeDesc: ".png/.jpeg",
+  formType: "photo",
   isMultiple: false,
+  isCheckName: false,
   limitNum: 10,
   isDisableUpload: false,
   maxFileSize: 0.3,
   isShowTips: true,
   isLimitSize: true,
-  action: "/activity/resource/uploadFile",
+  action: "/qiniu/upload/image",
   fileList: [],
   isDownLoad: false,
   listType: 'picture-card'
@@ -79,18 +109,26 @@ const props = withDefaults(defineProps<Props>(), {
 let waitFileList = ref<any[]>([]);
 
 waitFileList.value = props.fileList;
-waitFileList.value?.forEach((item: any) => {
-  item.name = item.original;
-});
-
+// waitFileList.value?.forEach((item: any) => {
+//   item.name = item.original;
+// });
+const dialogImageUrl = ref('')
+const dialogVisible = ref(false)
+const handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {
+  dialogImageUrl.value = uploadFile.url!
+  dialogVisible.value = true
+}
+const handleRemove: UploadProps['onRemove'] = (uploadFile, uploadFiles) => {
+  console.log(uploadFile, uploadFiles)
+}
 watch(
   () => props.fileList,
   () => {
     console.log("props.fileList====>", props.fileList);
     waitFileList.value = props.fileList;
-    waitFileList.value?.forEach((item: any) => {
-      item.name = item.original;
-    });
+    // waitFileList.value?.forEach((item: any) => {
+    //   item.name = item.original;
+    // });
   }
 );
 
@@ -98,7 +136,7 @@ watch(
 const handleChange = async (file: any, fileList: any[]) => {
   console.log(fileList, 'fileListfileList');
   console.log(file, 'filefilefile', /^[0-9_][0-9_]*$/.test(file.name));
-  if (!/^[0-9]+_/.test(file.name)) {
+  if (!/^[0-9]+_/.test(file.name) && props.isCheckName) {
     ElMessage.error(`文件上传格式错误`);
     return false
   }
@@ -117,7 +155,7 @@ const handleChange = async (file: any, fileList: any[]) => {
   if (ars.length < 1) {
     ElMessage.error(`仅支持格式为${type}的图片`);
     return false;
-  } else if (rawFile.size / 1024 / 1024 > props.maxFileSize && isLimitSize) {
+  } else if (rawFile.size / 1024 / 1024 > props.maxFileSize && props.isLimitSize) {
     ElMessage.error(`文件大小不能超过${props.maxFileSize}MB!`);
     const arr = [...waitFileList.value];
     waitFileList.value = arr.filter((item: any) => {
@@ -126,8 +164,7 @@ const handleChange = async (file: any, fileList: any[]) => {
     return false;
   } else {
     let formData = new FormData();
-    formData.append("file", rawFile);
-    formData.append("fileType", "2");
+    formData.append(props.formType, rawFile);
     const loadingInstance = ElLoading.service({
       text: "正在上传",
       background: "rgba(0,0,0,.2)",
@@ -136,13 +173,10 @@ const handleChange = async (file: any, fileList: any[]) => {
     const requestURL: string = props.action;
     http.post(requestURL, formData)
       .then(async (res: any) => {
-        if (res.code == 0) {
+        if (res.code == 10000) {
           loadingInstance.close();
-          let obj = {
-            ...res.data,
-            name: res.data.original,
-          };
-          emits("fileSuccess", obj);
+          waitFileList.value.push({ uid: file.uid, url: res.data })
+          emits("fileSuccess", res.data);
         } else {
           loadingInstance.close();
           ElMessage.warning(`文件上传失败`);
@@ -207,9 +241,12 @@ const getType = (acceptType: string) => {
 const removeFile = (file: any) => {
   const arr: any[] = [...waitFileList.value];
   waitFileList.value = arr.filter((its: any) => {
-    return its.id != file.id;
+    console.log(its, file);
+    return its.uid != file.uid;
   });
-  emits("fileRemove", waitFileList.value);
+  
+  console.log(file, 'file', arr, waitFileList.value);
+  emits("fileRemove", file);
 };
 
 const handleDownLoad = (row: { ossFile: string }) => {

+ 56 - 0
src/components/download/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <div>
+    <el-button type="primary" @click="download">下载</el-button>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, ref } from 'vue';
+import { ElButton, ElMessage } from 'element-plus';
+import axios from 'axios';
+
+export default defineComponent({
+  components: { ElButton },
+  props: {
+    urls: { type: Array, required: true },
+    fileName: { type: String, default: '' },
+  },
+  setup(props) {
+    const urls = ref(props.urls);
+
+    const download = () => {
+      if (!urls.value.length) {
+        ElMessage.error('没有可下载的文件');
+        return;
+      }
+      const anchor = document.createElement('a');
+      anchor.style.display = 'none';
+      anchor.href = '#';
+      document.body.appendChild(anchor);
+
+      const formData = new FormData();
+      urls.value.forEach(el => {
+        axios
+          .get(el.video_url, {
+            responseType: 'blob',
+          })
+          .then((response) => {
+            anchor.download = `《${props.fileName}》${el.series_name}.mp4`;
+            const blob = new Blob([response.data]);
+            const objectUrl = URL.createObjectURL(blob);
+            anchor.href = objectUrl;
+            anchor.click();
+            URL.revokeObjectURL(objectUrl);
+          })
+          .catch((error) => {
+            console.error(error);
+            ElMessage.error('下载失败');
+          });
+      })
+
+    };
+
+    return { download };
+  },
+});
+</script>

+ 57 - 0
src/components/mytabs/index.vue

@@ -0,0 +1,57 @@
+<template>
+  <el-tabs @tab-change="handChange">
+    <el-tab-pane :label="item.title" v-for="(item, i) in labelList" :key="i">
+      <slot name="content" />
+    </el-tab-pane>
+  </el-tabs>
+</template>
+<script lang="ts" setup>
+import { ref } from 'vue'
+import { videoStockEpisodeList } from '@/api/video/index'
+const labelList = ref([])
+const videoChoose = ref([])
+const changeForm = ref({ page: 1, limit: 5 })
+const props = defineProps({
+  meta: Object,
+});
+const emits = defineEmits(['change']);
+if (props.meta) {
+  console.log(props.meta, 'metameta');
+}
+const initLabelList = () => {
+  const pageSize = props.meta.limit;
+  const total = props.meta.total;
+  const pageCount = Math.ceil(total / pageSize);
+  for (let i = 0; i < pageCount; i++) {
+    const start = i * pageSize + 1;
+    const end = i === pageCount - 1 ? total : (i + 1) * pageSize;
+    let label;
+    if (i === pageCount - 1) {
+      if (start === total) {
+        label = `${total}`
+      } else {
+        label = `${start}-${total}`
+      }
+    } else {
+      label = `${start}-${end}`
+    }
+    labelList.value[i] = { title: label, page: pageCount }
+    console.log(labelList.value, 'test.value');
+  }
+
+}
+
+
+const handChange = (e) => {
+  console.log(e, 'handChange');
+  changeForm.value.page = Number(e) + 1;
+  emits('change', changeForm.value)
+}
+
+onMounted(() => {
+  initLabelList()
+})
+</script>
+
+
+<style lang="scss" scoped></style>

+ 19 - 19
src/router/modules/video.ts

@@ -1,20 +1,20 @@
-// import { RouteRecordRaw } from 'vue-router';
-// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// // @ts-ignore
-// const router: RouteRecordRaw[] = [
-//   {
-//     path: '/video',
-//     component: () => import('@/layout/index.vue'),
-//     meta: { title: '视频库管理', icon: 'user' },
-//     children: [
-//       {
-//         path: 'video',
-//         name: 'video-account',
-//         meta: { title: '视频库管理', icon: 'home' },
-//         component: () => import('@/views/videoManage/index.vue')
-//       }
-//     ]
-//   }
-// ];
+import { RouteRecordRaw } from 'vue-router';
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+const router: RouteRecordRaw[] = [
+  {
+    path: '/video',
+    component: () => import('@/layout/index.vue'),
+    meta: { title: '视频库管理', icon: 'user' },
+    children: [
+      {
+        path: 'video',
+        name: 'video-account',
+        meta: { title: '视频库管理', icon: 'home' },
+        component: () => import('@/views/videoManage/index.vue')
+      }
+    ]
+  }
+];
 
-// export default router;
+export default router;

+ 21 - 0
src/utils/index.ts

@@ -54,3 +54,24 @@ export function isExternal(path: string) {
   const isExternal = /^(https?:|http?:|mailto:|tel:)/.test(path);
   return isExternal;
 }
+
+export const downloadImage = async (imgsrc: any, name: any) => {
+  // 下载图片地址和图片名
+  const image = new Image();
+  image.setAttribute('crossOrigin', 'anonymous');
+  image.onload = () => {
+    const canvas = document.createElement('canvas');
+    canvas.width = image.width;
+    canvas.height = image.height;
+    const context = canvas.getContext('2d');
+    // @ts-ignore
+    context.drawImage(image, 0, 0, image.width, image.height);
+    const url = canvas.toDataURL('image/png');
+    const a = document.createElement('a');
+    const event = new MouseEvent('click');
+    a.download = name || 'photo';
+    a.href = url;
+    a.dispatchEvent(event);
+  };
+  image.src = imgsrc;
+};

+ 1 - 1
src/views/appletManage/form/create.vue

@@ -61,7 +61,7 @@ const rules = reactive({
   ],
   remark: [
     {
-      required: true,
+      required: false,
       message: '请输入备注',
       trigger: 'blur'
     }

+ 1 - 1
src/views/appletManage/index.vue

@@ -34,7 +34,7 @@
           <el-table-column label="操作" width="200">
             <template #default="scope">
               <el-button link type="primary" size="small" @click="opendepots(scope.row)">分配</el-button>
-              <el-button link type="primary" size="small" @click="open(scope.row.id)">更新</el-button>
+              <el-button link type="primary" size="small" @click="open(scope.row.id)">编辑</el-button>
             </template>
           </el-table-column>
         </el-table>

+ 37 - 19
src/views/videoManage/detail.vue

@@ -1,44 +1,62 @@
 <template>
-  <div class="flex flex-col justify-between w-full sm:flex-row">
-    <div class="table-default">
-      <el-table :data="tableData" class="mt-3" v-loading="loading">
-        <el-table-column prop="id" label="章节名称" />
-        <el-table-column prop="email" label="是否付费">
+  <div class="flex flex-col justify-between w-full sm:flex-row" style="width:100%;">
+    <div class="table-default" style="width:100%;">
+      <el-table :data="tableData" class="w-full mt-3" v-loading="loading" style="width:100%;">
+        <el-table-column prop="series_name" label="章节名称" />
+        <el-table-column prop="is_charge" label="是否付费">
           <template #default="scope">
-            <el-tooltip placement="top" v-if="scope.row.remark">
-              <template #content> {{ scope.row.remark }}<br /> </template>
-              <span>{{ scope.row.email }}</span>
-            </el-tooltip>
-            <span v-else>{{ scope.row.email }}</span>
+            <span :class="scope.row.is_charge ? 'text-red-600' : 'text-green-300'">
+              {{ scope.row.is_charge ? '【付费】' : '【免费】' }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="username" label="章节时长" />
+        <el-table-column prop="duration_str" label="章节时长" />
         <el-table-column label="操作" width="200">
           <template #default="scope">
-            <el-button link type="primary" size="small">播放</el-button>
+            <el-button link type="primary" size="small" @click="play(scope.row)">播放</el-button>
           </template>
         </el-table-column>
       </el-table>
       <Paginate />
     </div>
   </div>
+  <Dialog v-model="playVisible" title="视频" destroy-on-close height="100%">
+    <video id="my-player" class="video-js" controls autoplay preload="auto" ref="myVideo">
+      <source :src="current.video_url" type="video/mp4">
+    </video>
+  </Dialog>
 </template>
 
 <script lang="ts" setup>
 import { computed, onMounted, ref } from 'vue';
-import Create from './create.vue';
 import { useGetList } from '@/hook/curd/useGetList';
 import { useOpen } from '@/hook/curd/useOpen';
-
-const statusapi = 'users';
-const applet = ref([{ id: 1, name: '微信', value: 'wx' }, { id: 2, name: '抖音', value: 'dy' }])
-const api = 'channel/advertiser/listAdvertiser';
+const api = 'videoStock/episode/list';
+const props = defineProps({
+  primary: String | Number,
+});
+const current = ref({})
+const playVisible = ref(false)
 const { data, query, search, reset, loading } = useGetList(api);
 const { open, close, title, visible, id } = useOpen();
-
+if (props.primary) {
+  query.value.video_id = props.primary.id
+}
 const tableData = computed(() => data.value?.data);
-
+const play = (e: object) => {
+  current.value = e;
+  playVisible.value = true;
+}
 onMounted(() => {
+  console.log(props.primary, 'props.primaryprops.primary');
   search();
 });
 </script>
+
+<style lang="scss" scoped>
+//播放器样式
+video#my-player.video-js {
+  width: 80%;
+  height: 500px;
+  background-color: #000;
+}
+</style>

+ 102 - 88
src/views/videoManage/form/create.vue

@@ -2,37 +2,38 @@
   <el-form :model="formData" label-width="120px" ref="ruleForm" :rules="rules" v-loading="loading" class="pr-4">
     <div class="flex flex-row justify-between">
       <div class="w-full">
-        <el-form-item label="短剧名称" prop="email">
-          <el-input v-model="formData.email" placeholder="请输入短剧名称" />
+        <el-form-item label="短剧名称" prop="name">
+          <el-input v-model="formData.name" placeholder="请输入短剧名称" />
         </el-form-item>
-        <el-form-item label="集数" prop="username">
-          <el-input v-model="formData.username" placeholder="请输入集数" />
+        <el-form-item label="集数" prop="total_episode_num">
+          <el-input-number style="width:200px;" v-model="formData.total_episode_num" :step="1" step-strictly :min="1"
+            @change="handleChange" placeholder="请输入集数" />
         </el-form-item>
-        <el-form-item label="完结状态" prop="miniProgramIds">
-          <el-select class="w-full" v-model="formData.miniProgramIds" remote filterable multiple
-            :remote-method="remoteMethod" clearable placeholder="选择类型">
-            <el-option v-for="item in cpList" :key="item.cp_id" :label="item.cp_name" :value="item.cp_name" />
-          </el-select>
+        <el-form-item label="完结状态" prop="update_type">
+          <el-radio-group v-model="formData.update_type">
+            <el-radio :label="1">连载中</el-radio>
+            <el-radio :label="2">完结</el-radio>
+          </el-radio-group>
         </el-form-item>
-        <el-form-item label="频道" prop="miniProgramIds">
-          <el-select class="w-full" v-model="formData.miniProgramIds" remote filterable multiple
-            :remote-method="remoteMethod" clearable placeholder="选择类型">
+        <el-form-item label="频道" prop="category_id">
+          <el-select class="w-full" v-model="formData.category_id" filterable clearable placeholder="选择频道">
             <el-option v-for="item in cpList" :key="item.cp_id" :label="item.cp_name" :value="item.cp_name" />
           </el-select>
         </el-form-item>
-        <el-form-item label="上架" prop="status">
-          <el-radio-group v-model="formData.radio">
-            <el-radio :label="3">Option A</el-radio>
-            <el-radio :label="6">Option B</el-radio>
+        <el-form-item label="上架" prop="shelf_type">
+          <el-radio-group v-model="formData.shelf_type">
+            <el-radio :label="1">未上架</el-radio>
+            <el-radio :label="2">上架</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="起始集" prop="password">
-          <el-input v-model="formData.password" clearable type="password" autocomplete="new-password" size="large"
-            placeholder="请输入起始集" show-password class="h-12 text-base" />
+        <el-form-item label="起始集" prop="d_charge_sequence">
+          <el-input-number style="width:200px;" v-model="formData.d_charge_sequence" :step="1" step-strictly :min="1"
+            @change="handleChange" placeholder="请输入起始集" />
         </el-form-item>
-        <el-form-item label="默认定价" prop="repassword">
+        <el-form-item label="默认定价" prop="d_charge_coin">
           <div class="flex items-center w-full">
-            <el-input v-model="formData.username" placeholder="请输入集数" />
+            <el-input-number style="width:200px;" v-model="formData.d_charge_coin" :precision="2" :step="1" :min="1"
+              @change="handleChange" placeholder="请输入默认定价" />
             <el-tooltip placement="top">
               <template #content> multiple lines<br />second line </template>
               <el-icon>
@@ -41,21 +42,23 @@
             </el-tooltip>
           </div>
         </el-form-item>
-        <el-form-item label="所属CP方" prop="miniProgramIds">
-          <el-select class="w-full" v-model="formData.miniProgramIds" remote filterable multiple
-            :remote-method="remoteMethod" clearable placeholder="选择类型">
+        <el-form-item label="所属CP方" prop="cp_name">
+          <el-select class="w-full" v-model="formData.cp_name" remote filterable :remote-method="remoteMethod" clearable
+            placeholder="选择所属CP方">
             <el-option v-for="item in cpList" :key="item.cp_id" :label="item.cp_name" :value="item.cp_name" />
           </el-select>
         </el-form-item>
-        <el-form-item label="分成模式" prop="miniProgramIds">
-          <el-select class="w-full" v-model="formData.miniProgramIds" remote filterable multiple
-            :remote-method="remoteMethod" clearable placeholder="选择类型">
-            <el-option v-for="item in cpList" :key="item.cp_id" :label="item.cp_name" :value="item.cp_name" />
-          </el-select>
+        <el-form-item label="分成模式" prop="cp_share_type">
+          <el-radio-group v-model="formData.cp_share_type">
+            <el-radio :label="1">分成</el-radio>
+            <el-radio :label="2">保底</el-radio>
+            <el-radio :label="3">买断</el-radio>
+          </el-radio-group>
         </el-form-item>
-        <el-form-item label="备注" prop="remark">
-          <my-upload></my-upload>
-          <el-input v-model="formData.remark" placeholder="请填写备注" type="textarea" />
+        <el-form-item label="封面" prop="cover_image">
+          <my-upload :isMultiple="true" @fileRemove="fileRemove" @fileSuccess="fileSuccess" :fileList="formData.fileList"
+            action="/qiniu/upload/image"></my-upload>
+          <el-input v-model="formData.cover_image" placeholder="请填写备注" type="textarea" />
         </el-form-item>
       </div>
     </div>
@@ -71,6 +74,7 @@ import { useCreate } from '@/hook/curd/useCreate';
 import { useShow } from '@/hook/curd/useShow';
 import { cpManageCpList, cpOptions } from '@/api/cp/index'
 import { advertiserGetAdvertiser, advertiserAdd, advertiserUpdateAdvertiser } from '@/api/advertiser/index'
+import { videoStockVideoUpdate, videoStockVideoAdd } from '@/api/video/index'
 import type { FormInstance, FormRules } from 'element-plus'
 const ruleForm = ref<FormInstance>()
 import { onMounted, ref } from 'vue';
@@ -78,68 +82,70 @@ const props = defineProps({
   primary: String | Number,
 });
 const formData = ref({ status: 2, miniProgramIds: [] })
-//自定义校验规则
-const validatePasswordConfirmation = (
-  rule: any,
-  value: any,
-  callback: any
-) => {
-  if (value === '') {
-    callback(new Error('请再次输入密码'));
-  } else if (value !== ruleForm.password) {
-    callback(new Error('两次密码不匹配'));
-  } else {
-    callback();
-  }
-};
 const rules = reactive({
-  miniProgramIds: [{ required: true, message: '请选择小程序' }],
-  email: [
+  name: [{ required: true, message: '请输入短剧名称', trigger: 'blur' }],
+  total_episode_num: [
     {
       required: true,
-      message: '请输入登录账号',
+      message: '请输入集数',
       trigger: 'blur'
-    },
+    }
+  ],
+  update_type: [
     {
-      type: 'email',
-      message: '邮箱格式不正确',
-      trigger: 'blur'
+      required: true,
+      message: '请选择完结状态',
+      trigger: 'change'
     }
   ],
-  remark: [
+  category_id: [
     {
       required: true,
-      message: '请输入备注',
-      trigger: 'blur'
+      message: '请选择频道',
+      trigger: 'change'
+    }
+  ],
+  shelf_type: [
+    {
+      required: true,
+      message: '请选择上架状态',
+      trigger: 'change'
     }
   ],
-  username: [
+  d_charge_sequence: [
     {
       required: true,
-      message: '请输入用户名',
+      message: '请输入起始集',
       trigger: 'blur'
     }
   ],
-  password: [
+  d_charge_coin: [
     {
       required: true,
-      message: '请输入密码',
+      message: '请输入默认定价',
       trigger: 'blur'
-    },
+    }
+  ],
+  cp_name: [
     {
-      pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/,
-      message:
-        '必须包含大小写字母和数字的组合,可以使用特殊字符,长度在6-20之间'
+      required: true,
+      message: '请选择所属CP方',
+      trigger: 'change'
     }
   ],
-  repassword: [
+  cp_share_type: [
     {
       required: true,
-      message: '请再次输入密码',
-      trigger: 'blur'
-    },
-    { validator: validatePasswordConfirmation, trigger: 'blur' }
-  ]
+      message: '请选择分成模式',
+      trigger: 'change'
+    }
+  ],
+  cover_image: [
+    {
+      required: true,
+      message: '请上传封面',
+    }
+  ],
 });
 const loading = ref(false)
 
@@ -150,23 +156,20 @@ const remoteMethod = (query: string) => {
     initCpOtion({})
   }
 }
-
+const fileSuccess = (e) => {
+  console.log(e);
+  formData.value.cover_image = e;
+}
+const fileRemove = (e) => {
+  console.log(e);
+  formData.value.cover_image = '';
+}
 const initCpOtion = (params: object) => {
   cpOptions(params).then(res => {
     cpList.value = res.data;
   })
 }
 
-const passwordRules = [
-  {
-    required: true,
-    message: '密码必须填写'
-  },
-  {
-    pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/,
-    message: '必须包含大小写字母和数字的组合,可以使用特殊字符,长度在6-20之间'
-  }
-];
 const submitForm = (formEl: FormInstance | undefined) => {
   console.log(formData.value, 'formData.valueformData.value');
   if (!formEl) return;
@@ -174,12 +177,25 @@ const submitForm = (formEl: FormInstance | undefined) => {
     .validate(valid => {
       if (valid) {
         let api;
+        let params = {
+          "name": formData.value.name,
+          "total_episode_num": formData.value.total_episode_num,
+          "update_type": formData.value.update_type,
+          "category_id": formData.value.category_id,
+          "shelf_type": formData.value.shelf_type,
+          "d_charge_sequence": formData.value.d_charge_sequence,
+          "d_charge_coin": formData.value.d_charge_coin,
+          "cp_name": formData.value.cp_name,
+          "cp_share_type": formData.value.cp_share_type,
+          "cover_image": formData.value.cover_image,
+        };
         if (props.primary) {
-          api = advertiserUpdateAdvertiser
+          api = videoStockVideoUpdate
+          params.id = formData.value.id
         } else {
-          api = advertiserAdd
+          api = videoStockVideoAdd
         }
-        api({ ...formData.value }).then(res => {
+        api(params).then(res => {
           console.log(res);
           ElMessage.success(res.message)
           emit('close')
@@ -192,10 +208,8 @@ const submitForm = (formEl: FormInstance | undefined) => {
 }
 
 if (props.primary) {
-  advertiserGetAdvertiser({ id: props.primary }).then(res => {
-    console.log(res, 'props.primary');
-    formData.value = res.data
-  })
+  formData.value = JSON.parse(JSON.stringify(props.primary))
+  formData.value.fileList = [{ uid: props.primary.id, url: props.primary.cover_image }]
 }
 
 const emit = defineEmits(['close']);

+ 24 - 109
src/views/videoManage/form/subscribeSet.vue

@@ -2,28 +2,21 @@
   <el-form :model="formData" label-width="120px" ref="ruleForm" :rules="rules" v-loading="loading" class="pr-4">
     <div class="flex flex-row justify-between">
       <div class="w-full">
-        <el-form-item label="收费模式" prop="email">
-          <el-radio-group v-model="formData.radio">
-            <el-radio :label="3">Option A</el-radio>
-            <el-radio :label="6">Option B</el-radio>
-            <el-radio :label="9">Option C</el-radio>
+        <el-form-item label="收费模式" prop="chargeType">
+          <el-radio-group v-model="formData.chargeType">
+            <el-radio :label="1">单集固定收费</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="起始集" prop="miniProgramIds">
-          <el-select class="w-full" v-model="formData.miniProgramIds" remote filterable multiple
-            :remote-method="remoteMethod" clearable placeholder="选择类型">
-            <el-option v-for="item in cpList" :key="item.cp_id" :label="item.cp_name" :value="item.cp_name" />
-          </el-select>
+        <el-form-item label="起始集" prop="chargeSequence">
+          <el-input-number style="width:200px;" v-model="formData.chargeSequence" :step="1" step-strictly :min="1"
+            :max="30" @change="handleChange" placeholder="请输入起始集" />
+          <span class="ml-3 text-gray-400">范围:1-30</span>
         </el-form-item>
-        <el-form-item label="单机定价" prop="repassword">
+        <el-form-item label="单集定价" prop="chargeCoin">
           <div class="flex items-center w-full">
-            <el-input v-model="formData.username" placeholder="请输入集数" />
-            <el-tooltip placement="top">
-              <template #content> multiple lines<br />second line </template>
-              <el-icon>
-                <InfoFilled />
-              </el-icon>
-            </el-tooltip>
+            <el-input-number style="width:200px;" v-model="formData.chargeCoin" :step="1" step-strictly :min="50"
+              :max="300" @change="handleChange" placeholder="请输入单集定价" />
+            <span class="ml-3 text-gray-400">范围:50-300</span>
           </div>
         </el-form-item>
       </div>
@@ -38,8 +31,7 @@
 import { InfoFilled } from '@element-plus/icons-vue';
 import { useCreate } from '@/hook/curd/useCreate';
 import { useShow } from '@/hook/curd/useShow';
-import { cpManageCpList, cpOptions } from '@/api/cp/index'
-import { advertiserGetAdvertiser, advertiserAdd, advertiserUpdateAdvertiser } from '@/api/advertiser/index'
+import { videoStockVideoSetChargeConfig } from '@/api/video/index'
 import type { FormInstance, FormRules } from 'element-plus'
 const ruleForm = ref<FormInstance>()
 import { onMounted, ref } from 'vue';
@@ -47,108 +39,33 @@ const props = defineProps({
   primary: String | Number,
 });
 const formData = ref({ status: 2, miniProgramIds: [] })
-//自定义校验规则
-const validatePasswordConfirmation = (
-  rule: any,
-  value: any,
-  callback: any
-) => {
-  if (value === '') {
-    callback(new Error('请再次输入密码'));
-  } else if (value !== ruleForm.password) {
-    callback(new Error('两次密码不匹配'));
-  } else {
-    callback();
-  }
-};
 const rules = reactive({
-  miniProgramIds: [{ required: true, message: '请选择小程序' }],
-  email: [
+  chargeType: [{ required: true, message: '请选择收费模式', trigger: 'change' }],
+  chargeSequence: [
     {
       required: true,
-      message: '请输入登录账号',
-      trigger: 'blur'
-    },
-    {
-      type: 'email',
-      message: '邮箱格式不正确',
-      trigger: 'blur'
-    }
-  ],
-  remark: [
-    {
-      required: true,
-      message: '请输入备注',
-      trigger: 'blur'
-    }
-  ],
-  username: [
-    {
-      required: true,
-      message: '请输入用户名',
+      message: '请输入起始集',
       trigger: 'blur'
     }
   ],
-  password: [
+  chargeCoin: [
     {
       required: true,
-      message: '请输入密码',
+      message: '请输入单集定价',
       trigger: 'blur'
-    },
-    {
-      pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/,
-      message:
-        '必须包含大小写字母和数字的组合,可以使用特殊字符,长度在6-20之间'
     }
-  ],
-  repassword: [
-    {
-      required: true,
-      message: '请再次输入密码',
-      trigger: 'blur'
-    },
-    { validator: validatePasswordConfirmation, trigger: 'blur' }
   ]
+
 });
 const loading = ref(false)
 
-const remoteMethod = (query: string) => {
-  if (query) {
-    initCpOtion({ cp_name: query })
-  } else {
-    initCpOtion({})
-  }
-}
-
-const initCpOtion = (params: object) => {
-  cpOptions(params).then(res => {
-    cpList.value = res.data;
-  })
-}
-
-const passwordRules = [
-  {
-    required: true,
-    message: '密码必须填写'
-  },
-  {
-    pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/,
-    message: '必须包含大小写字母和数字的组合,可以使用特殊字符,长度在6-20之间'
-  }
-];
 const submitForm = (formEl: FormInstance | undefined) => {
   console.log(formData.value, 'formData.valueformData.value');
   if (!formEl) return;
   formEl
     .validate(valid => {
       if (valid) {
-        let api;
-        if (props.primary) {
-          api = advertiserUpdateAdvertiser
-        } else {
-          api = advertiserAdd
-        }
-        api({ ...formData.value }).then(res => {
+        videoStockVideoSetChargeConfig({ ...formData.value }).then(res => {
           console.log(res);
           ElMessage.success(res.message)
           emit('close')
@@ -161,15 +78,13 @@ const submitForm = (formEl: FormInstance | undefined) => {
 }
 
 if (props.primary) {
-  advertiserGetAdvertiser({ id: props.primary }).then(res => {
-    console.log(res, 'props.primary');
-    formData.value = res.data
-  })
+  formData.value.id = props.primary.id
+  formData.value.chargeCoin = props.primary.charge_coin
+  formData.value.chargeType = props.primary.d_charge_type
+  formData.value.chargeSequence = props.primary.charge_sequence
 }
-
 const emit = defineEmits(['close']);
-const cpList = ref();
 onMounted(() => {
-  initCpOtion({})
+
 });
 </script>

+ 37 - 76
src/views/videoManage/form/uploadVideo.vue

@@ -1,29 +1,17 @@
 <template>
   <el-form :model="formDataForm" label-width="120px" ref="form" v-loading="loading" class="pr-4">
-    <el-form-item label="短剧名称" prop="book_name" :rules="[{ required: true, message: '书名必须填写' }]">
-      <el-input v-model="formDataForm.book_name" name="name" clearable />
+    <el-form-item label="短剧名称" prop="name" :rules="[{ required: true, message: '短剧名称必须填写' }]">
+      <el-input disabled v-model="formDataForm.name" name="name" clearable />
     </el-form-item>
-    <el-form-item label="集数" prop="author" :rules="[{ required: true, message: '作者必须填写' }]">
-      <el-input v-model="formDataForm.author" name="author" clearable />
+    <el-form-item label="集数" prop="total_episode_num" :rules="[{ required: true, message: '集数必须填写' }]">
+      <el-input disabled v-model="formDataForm.total_episode_num" name="author" clearable />
     </el-form-item>
-    <el-form-item label="上传" prop="path" :rules="[{ required: true, message: '请上传文件', trigger: 'change' }]">
-      <div>支持MP4格式</div>
-      <el-tabs type="border-card" class="w-full">
-        <el-tab-pane label="默认剧集上传">
-          <div>默认剧集名称</div>
-          <div>
-            <my-upload :isShowTips="false" listType="text" acceptType=".mp4" acceptTypeDesc="mp4"
-              :isLimitSize="false"></my-upload>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="自定义剧集上传">
-          <div class="flex items-start w-full">
-            <el-input v-model="formDataForm.video" class="w-full" placeholder="请输入剧集" size="normal" clearable></el-input>
-            <my-upload class="w-auto" :isShowTips="false" listType="text" acceptType=".mp4" acceptTypeDesc="mp4"
-              :isLimitSize="false"></my-upload>
-          </div>
-        </el-tab-pane>
-      </el-tabs>
+    <el-form-item label="上传" prop="videos" :rules="[{ required: true, message: '请上传文件', trigger: 'change' }]">
+      <div class="w-full">支持MP4格式</div>
+      <div class="w-full">
+        <video-uploader :fileList="videofileList" :public_video_url='formDataForm.cover_image' @fileRemove="fileRemove"
+          @success="fileSuccess"></video-uploader>
+      </div>
     </el-form-item>
     <div class="flex justify-end">
       <el-button type="primary" @click="submitForm(form)">{{
@@ -34,49 +22,24 @@
 </template>
 
 <script lang="ts" setup>
-import {
-  bookCreateBook,
-  bookCategorylist
-} from '@/api/bookManage/index';
-import { cpOptions } from '@/api/cp/index';
 import { FormInstance } from 'element-plus';
 import { InfoFilled } from '@element-plus/icons-vue';
-
+import { videoStockEpisodeAdd } from '@/api/video/index'
+const props = defineProps({
+  primary: String | Number,
+});
 const form = ref<FormInstance>();
-const cpoptions = ref([]);
 const loading = ref(false);
-const cooperations = ref([]);
-const formDataForm = ref({})
+const formDataForm = ref({ videos: [] })
+const videofileList = ref([])
 const emit = defineEmits(['close']);
-const categoryList = ref([])
-
-const uploadSuccess = (data) => {
-  console.log(data, 'uploaduploadupload');
-  formDataForm.value.path = data.path
-}
-const uploadDestory = () => {
-  formDataForm.value.path = ''
-}
-watch(() => formDataForm.value.channel, (newValue, oldValue) => {
-  formDataForm.value.category = ''
-  if (newValue == 1) {
-    categoryList.value = cooperations.value.length > 0 ? cooperations.value[0]?.list : []
-  } else if (newValue == 2) {
-    categoryList.value = cooperations.value.length > 0 ? cooperations.value[1]?.list : []
-  }
-});
-
-const remoteMethod = (query: string) => {
-  if (query) {
-    initCpOtion({ cp_name: query })
-  } else {
-    initCpOtion({})
-  }
+const fileRemove = (e: object) => {
+  formDataForm.value.videos = formDataForm.value.videos.filter(item => item.name != e.name)
+  console.log(formDataForm.value, e, 'fileRemove');
 }
-const initCpOtion = (params: object) => {
-  cpOptions(params).then(res => {
-    cpoptions.value = res.data
-  })
+const fileSuccess = (e: object) => {
+  formDataForm.value.videos.push({ name: e.fname, duration: parseInt(e.duration), url: e.url })
+  console.log(formDataForm.value, 'fileSuccess');
 }
 
 const submitForm = (formEl: FormInstance | undefined) => {
@@ -86,33 +49,31 @@ const submitForm = (formEl: FormInstance | undefined) => {
   formEl
     .validate(valid => {
       if (valid) {
-        console.log({ ...formDataForm.value });
-        formDataForm.value.cp_id = formDataForm.value.cp.cp_id
-        formDataForm.value.cp_name = formDataForm.value.cp.cp_name
-        formDataForm.value.category_id = formDataForm.value.category.category_id
-        formDataForm.value.category_name = formDataForm.value.category.category_name
-
-        bookCreateBook({ ...formDataForm.value }).then(res => {
+        let params = {
+          video_id: formDataForm.value.video_id,
+          videos: formDataForm.value.videos
+        }
+        videoStockEpisodeAdd(params).then(res => {
           console.log(res);
-          ElMessage.success(res.message)
           loading.value = false;
+          videofileList.value = []
           emit('close')
-        }).catch(e => {
-          console.log(e);
-          loading.value = false;
         })
+        console.log(formDataForm.value, 'params', params);
       } else {
         loading.value = false;
       }
     })
     .then(() => { });
 }
-
+if (props.primary) {
+  console.log(props.primary, 'props.primary');
+  formDataForm.value.video_id = props.primary.id
+  formDataForm.value.name = props.primary.name
+  formDataForm.value.total_episode_num = props.primary.total_episode_num
+  formDataForm.value.cover_image = props.primary.cover_image
+}
 onMounted(async () => {
-  initCpOtion({})
-  bookCategorylist().then(res => {
-    console.log(res);
-    cooperations.value = res.data
-  })
+
 });
 </script>

+ 49 - 70
src/views/videoManage/form/videoDL.vue

@@ -1,94 +1,71 @@
 <template>
   <div class="wrapper">
-    <video id="my-player" class="video-js" controls autoplay preload="auto" ref="myVideo" :poster="videoImg">
+    <!-- :poster="videoImg"视频封面 -->
+    <video id="my-player" class="video-js" controls autoplay preload="auto" ref="myVideo">
       <source :src="videoUrl" type="video/mp4">
     </video>
     <!-- 视频名称列表布局 -->
     <div class="video-wrapper">
       <div class="flex items-center justify-between h-10">
-        <div>剧名</div>
-        <div v-if="videoChoose.length > 0"><el-button type="primary" size="default">下载</el-button></div>
+        <div class="text-base font-medium">剧名: <span class="ml-2 text-lg font-bold text-dark-600">{{
+          props.primary.name }}</span></div>
+        <download v-if="videoChoose.length > 0" :urls="videoChooseUrls" :fileName="props.primary.name"></download>
+        <!-- <div v-if="videoChoose.length > 0"><el-button type="primary" size="default">下载</el-button></div> -->
       </div>
-      <el-tabs>
-        <el-tab-pane :label="item.title" v-for="(item, i) in videoUrlList" :key="i">
+      <mytabs :meta="meta" v-if="meta.total" @change="handChange">
+        <template v-slot:content>
           <el-checkbox-group v-model="videoChoose" size="large" class="flex items-center">
-            <el-checkbox border class="video-item" @click="videoShow(source)" v-for="source in item.source"
-              :key="source.id" :label="source.ji">
-              {{ source.ji }}
+            <el-checkbox border class="video-item" @change="videoShow(source)" v-for="(source, index) in videoUrlList"
+              :key="index" :label="source.video_url">
+              {{ source.series_sequence }}
             </el-checkbox>
           </el-checkbox-group>
-        </el-tab-pane>
-      </el-tabs>
+        </template>
+      </mytabs>
     </div>
   </div>
 </template>
 <script lang="ts" setup>
 import { ref } from 'vue'
+import { videoStockEpisodeList } from '@/api/video/index'
 const myVideo = ref()
 const videoChoose = ref([])
+const changeForm = ref({ page: 1, limit: 5 })
+const props = defineProps({
+  primary: String | Number,
+});
+const videoUrlList = ref([])
+if (props.primary) {
 
-const videoImg = ref('https://img1.baidu.com/it/u=3406241258,3374807816&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1683651600&t=56bcf4002d6c1ebe1c05fcb9a82b793c')
-const videoUrlList = ref([
-  {
-    title: '1-100',
-    source: [{
-      id: 1,
-      ji: 1,
-      urlName: '视频1',
-      urlImg: 'https://img1.baidu.com/it/u=3406241258,3374807816&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1683651600&t=56bcf4002d6c1ebe1c05fcb9a82b793c',
-      urlMp4: 'https://vjs.zencdn.net/v/oceans.mp4'
-    }, {
-      id: 1,
-      ji: 2,
-      urlName: '视频1',
-      urlImg: 'https://cn.vuejs.org/images/logo.svg',
-      urlMp4: 'https://www.bilibili.com/video/BV1hV4y1G7i9?t=6.4'
-    },
-    {
-      id: 1,
-      ji: 3,
-      urlName: '视频1',
-      urlImg: 'https://cn.vuejs.org/images/logo.svg',
-      urlMp4: 'https://vjs.zencdn.net/v/oceans.mp4'
-    },
-    {
-      id: 1,
-      ji: 4,
-      urlName: '视频1',
-      urlImg: 'https://cn.vuejs.org/images/logo.svg',
-      urlMp4: 'https://vjs.zencdn.net/v/oceans.mp4'
-    },
-    {
-      id: 1,
-      ji: 5,
-      urlName: '视频1',
-      urlImg: 'https://cn.vuejs.org/images/logo.svg',
-      urlMp4: 'https://vjs.zencdn.net/v/oceans.mp4'
-    },
-    {
-      id: 1,
-      ji: 6,
-      urlName: '视频1',
-      urlImg: 'https://cn.vuejs.org/images/logo.svg',
-      urlMp4: 'https://vjs.zencdn.net/v/oceans.mp4'
-    }],
-  },
-  {
-    title: '100-101',
-    source: [{
-      id: 1,
-      ji: 101,
-      urlName: '视频1',
-      urlImg: 'https://cn.vuejs.org/images/logo.svg',
-      urlMp4: 'https://vjs.zencdn.net/v/oceans.mp4'
-    }],
-  }
-])
-const videoUrl = ref(videoUrlList.value[1].urlMp4)
+}
+const meta = ref({})
+
+const videoUrl = ref('')
+const videoChooseUrls = ref([])
+const initVideoList = (params: object) => {
+  videoStockEpisodeList({ video_id: props.primary.id, ...params }).then(res => {
+    videoUrlList.value = res.data;
+    meta.value = { limit: res.limit, total: res.total }
+  })
+}
+
+
+const handChange = (e) => {
+  console.log(e, 'handChange');
+  changeForm.value.page = Number(e.page);
+  initVideoList(changeForm.value);
+}
 const videoShow = (source: object) => {
-  myVideo.value.src = source.urlMp4
-  videoImg.value = source.urlImg
+  console.log(source);
+  videoUrl.value = source.video_url
+  videoChooseUrls.value.push(source)
+  videoChooseUrls.value = videoChooseUrls.value.filter(el => videoChoose.value.includes(el.video_url))
+  console.log(videoChooseUrls.value, 'videoChooseUrls.value');
 }
+
+onMounted(() => {
+  initVideoList(changeForm.value)
+})
 </script>
 
 
@@ -97,6 +74,7 @@ const videoShow = (source: object) => {
   padding-left: 40px;
   margin-top: 30px;
   display: flex;
+  width: 100%;
 }
 
 //播放器样式
@@ -107,6 +85,7 @@ video#my-player.video-js {
 }
 
 .video-wrapper {
+  width: 100%;
   margin-left: 40px;
 
   .video-item {

+ 102 - 85
src/views/videoManage/index.vue

@@ -27,49 +27,34 @@
         <el-table-column type="index" width="100" label="剧号" />
         <el-table-column label="封面" width="200" show-overflow-tooltip>
           <template #default="scope">
-            <div class="book-wrapper">
-              <span class="book-label">书名:</span>
-              <span class="book-content">
-                {{ scope.row.name }}
-              </span>
-            </div>
-            <div class="book-wrapper">
-              <span class="book-label">bid:</span>
-              <span class="book-content">{{ scope.row.id }}</span>
+            <div class="flex flex-col items-center justify-center wrapper">
+              <el-image :src="scope.row.cover_image" style="width:148px;height:148px;" fit="fill" :lazy="true"></el-image>
+              <el-button type="primary" link size="default" @click="dowload(scope.row)">下载</el-button>
             </div>
           </template>
         </el-table-column>
-        <el-table-column label="短剧名称" width="200" show-overflow-tooltip>
+        <el-table-column label="短剧名称" width="250" show-overflow-tooltip>
           <template #default="scope">
-            <div class="book-wrapper">
-              <span class="book-label">书名:</span>
-              <span class="book-content">
+            <div class="wrapper">
+              <span class="text-lg font-bold text-blue-600 cursor-pointer content"
+                @click="openType('videoDetailVisible', scope.row)">
                 {{ scope.row.name }}
               </span>
+              <span>
+                【 {{ scope.row.update_type_str }} 】
+              </span>
             </div>
-            <div class="book-wrapper">
-              <span class="book-label">bid:</span>
-              <span class="book-content">{{ scope.row.id }}</span>
-            </div>
-            <div class="book-wrapper">
-              <span class="book-label">章节数:</span>
-              <span class="book-content">{{ scope.row.chapter_count }}</span>
-            </div>
-            <div class="book-wrapper">
-              <span class="book-label">字数:</span>
-              <span class="book-content">{{ scope.row.size }}</span>
-            </div>
-            <div class="book-wrapper">
-              <span class="book-label">状态:</span>
-              <span class="book-content">{{ scope.row.status_text }}</span>
+            <div class="wrapper">
+              <span class="label">上架时间:</span>
+              <span class="content">{{ scope.row.shelf_at }}</span>
             </div>
           </template>
         </el-table-column>
-        <el-table-column prop="author" label="集数" />
-        <el-table-column prop="category_name" label="频道" />
-        <el-table-column prop="channel_text" label="上架状态" />
-        <el-table-column prop="cp_name" label="起始集" />
-        <el-table-column prop="settlement_type_text" label="定价">
+        <el-table-column prop="total_episode_num" label="集数" />
+        <el-table-column prop="category_str" label="频道" />
+        <el-table-column prop="shelf_type_str" label="上架状态" />
+        <el-table-column prop="charge_sequence" label="起始集" />
+        <el-table-column prop="charge_coin" label="定价">
           <template #header>
             <div class="flex items-center">
               <span>定价</span>
@@ -82,17 +67,16 @@
             </div>
           </template>
           <template #default="scope">
-            <span>{{ scope.row.price }}</span>
+            <span>{{ scope.row.charge_coin }}</span>
           </template>
         </el-table-column>
         <el-table-column label="操作">
           <template #default="scope">
-            <el-button link type="primary" size="small" v-action="'contentManage.book.export'"
-              @click="exportBook(scope.row)">导出</el-button><br />
-            <el-button @click="open(scope.row.id)" link type="primary" size="small"
-              v-action="'contentManage.book.editauthorbybid'">编辑</el-button><br />
-            <el-button link type="primary" size="small" @click="opendepots(scope.row)"
-              v-action="'contentManage.book.distributeSubmit'">分库权限</el-button>
+            <el-button link type="primary" size="small"
+              @click="openType('uploadVisible', scope.row)">上传视频</el-button><br />
+            <el-button @click="open(scope.row)" link type="primary" size="small">编辑</el-button><br />
+            <el-button @click="openType('takeVisible', scope.row)" link type="primary" size="small">订阅设置</el-button><br />
+            <el-button link type="primary" size="small" @click="openType('videoDLVisible', scope.row)">视频下载</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -102,39 +86,83 @@
     <Dialog v-model="visible" :title="title" destroy-on-close>
       <Create @close="close(search)" :primary="id" :eidapi="addApi" :addapi="addApi" :ismulSet="ismulSet" />
     </Dialog>
-    <Dialog v-model="depotsVisible" width="50%" title="视频下载" destroy-on-close>
-      <videoDL @close="closeDeptos()" :primary="depotsData"></videoDL>
+    <Dialog v-model="videoDLVisible" width="50%" title="视频下载" destroy-on-close>
+      <videoDL @close="closeType('videoDLVisible')" :primary="depotsData"></videoDL>
+    </Dialog>
+    <Dialog v-model="takeVisible" width="50%" title="订阅设置" destroy-on-close>
+      <subscribeSet @close="closeType('takeVisible')" :primary="takeData"></subscribeSet>
+    </Dialog>
+    <Dialog v-model="uploadVisible" width="50%" title="上传视频" destroy-on-close>
+      <uploadVideo @close="closeType('uploadVisible')" :primary="uploadData"></uploadVideo>
+    </Dialog>
+    <Dialog v-model="videoDetailVisible" width="50%" title="短剧详情" destroy-on-close>
+      <videoDetail @close="closeType('videoDetailVisible')" :primary="videoDetailData"></videoDetail>
     </Dialog>
   </div>
 </template>
 
 <script lang="ts" setup>
+import { downloadImage } from '@/utils/index'
 import { InfoFilled } from '@element-plus/icons-vue';
 import Create from './form/create.vue';
 import videoDL from './form/videoDL.vue';
+import videoDetail from './detail.vue';
+import subscribeSet from './form/subscribeSet.vue';
+import uploadVideo from './form/uploadVideo.vue';
 import { useGetList } from '@/hook/curd/useGetList';
 import { useDestroy } from '@/hook/curd/useDestroy';
 import { useOpen } from '@/hook/curd/useOpen';
 import { shortcuts } from '@/utils/shortcuts';
-import { bookSettlementypes, bookList } from '@/api/bookManage/index';
 import moment from 'moment';
 import { cpOptions } from '@/api/cp/index';
+import { ca } from 'element-plus/es/locale';
 let ismulSet = ref(false)
-const api = 'contentManage/book/list';
+const api = 'videoStock/video/list';
 const addApi = 'contentManage/book/edit_author';
 let multipleSelection = reactive([]);
 const { data, query, search, reset, loading } = useGetList(api);
 const { deleted } = useDestroy();
-const depotsVisible = ref(false)
 const uploadBooksVisible = ref(false)
+const videoDLVisible = ref(false)
 const depotsData = ref({})
-const opendepots = (data) => {
-  depotsVisible.value = true
-  depotsData.value = data
+const uploadVisible = ref(false)
+const uploadData = ref({})
+const takeVisible = ref(false)
+const takeData = ref({})
+const videoDetailVisible = ref(false)
+const videoDetailData = ref({})
+const openType = (type, data) => {
+  switch (type) {
+    case 'videoDLVisible':
+      videoDLVisible.value = true
+      depotsData.value = data
+      break;
+    case 'takeVisible':
+      takeVisible.value = true
+      takeData.value = data
+      break;
+    case 'uploadVisible':
+      uploadVisible.value = true
+      uploadData.value = data
+      break;
+    case 'videoDetailVisible':
+      videoDetailVisible.value = true
+      videoDetailData.value = data
+      break;
+  }
 }
-const openuploadBooksVisible = (data) => {
-  uploadBooksVisible.value = true
-  depotsData.value = data
+
+const dowload = (e: object) => {
+  console.log(e);
+  // const link = document.createElement('a')
+  // link.href = e.cover_image
+  // link.setAttribute('download', e.name)
+  // document.body.appendChild(link)
+  // link.click()
+  // document.body.removeChild(link)
+  // (e.cover_image, e.name)
+  downloadImage(e.cover_image, e.name)
+  console.log(downloadImage());
 }
 
 const tableData = computed(() => data.value?.data);
@@ -142,45 +170,37 @@ const { open, close, title, visible, id } = useOpen();
 let cooperations = ref([]);
 let cpoptions = ref([]);
 
-const exportBook = (item) => {
-  window.open(`http://121.37.183.29:8093/api/contentManage/book/export/${item.id}`)
-}
-
-const timeChange = (e) => {
-  if (query.value.time) {
-    const timeArr = toRaw(e);
-    query.value.create_start = timeArr[0]
-    query.value.create_end = timeArr[1]
-  } else {
-    delete query.value.create_start
-    delete query.value.create_end
-  }
-}
-
-const cooperateChange = (e) => {
-  if (query.value.cooperate_date) {
-    const timeArr = toRaw(e);
-    query.value.start_date = timeArr[0]
-    query.value.end_date = timeArr[1]
-  } else {
-    delete query.value.start_date
-    delete query.value.end_date
-  }
-}
-
 const handleSelectionChange = (val: []) => {
   console.log(toRaw(val));
   multipleSelection = val;
 };
 
-const closeDeptos = () => {
-  depotsVisible.value = false
+const closeType = (type) => {
+  console.log(type, 'typetype');
+  switch (type) {
+    case 'videoDLVisible':
+      videoDLVisible.value = false
+      break;
+    case 'takeVisible':
+      takeVisible.value = false
+      break;
+    case 'uploadVisible':
+      uploadVisible.value = false
+      break;
+    case 'videoDetailVisible':
+      videoDetailVisible.value = false
+      break;
+  }
   search()
 }
 const closeUpload = () => {
   uploadBooksVisible.value = false
   search()
 }
+
+// watch(() => uploadVisible.value, (newVal, oldVal) => {
+//   search()
+// })
 const mulSet = () => {
   if (multipleSelection.length <= 0) {
     return ElMessage.warning({
@@ -231,9 +251,6 @@ const initCpOtion = (params: object) => {
 
 onMounted(() => {
   initCpOtion({})
-  bookSettlementypes().then(res => {
-    cooperations.value = res.data
-  })
   search();
   deleted(reset);
 });
@@ -248,14 +265,14 @@ onMounted(() => {
     justify-content: flex-end;
   }
 
-  .book-wrapper {
+  .wrapper {
     margin: 8px;
 
-    .book-label {
+    .label {
       margin-right: 6px;
     }
 
-    .book-content {
+    .content {
       font-size: 15px;
     }
   }