Browse Source

已完成VOD上传页面的菜单和路由配置

yourname 6 months ago
parent
commit
537d9c88ea
6 changed files with 684 additions and 3 deletions
  1. 7 0
      client/admin/menu.tsx
  2. 270 0
      client/admin/pages_vod_upload.tsx
  3. 6 0
      client/admin/routes.tsx
  4. 2 1
      deno.json
  5. 45 2
      deno.lock
  6. 354 0
      docs/vod_upload_demo.md

+ 7 - 0
client/admin/menu.tsx

@@ -155,6 +155,13 @@ export const useMenu = () => {
           path: '/admin/know-info',
           permission: 'content:manage'
         },
+        {
+          key: 'vod-upload',
+          label: 'VOD上传',
+          path: '/admin/vod-upload',
+          permission: 'content:manage',
+          icon: <FileTextOutlined />
+        },
         {
           key: 'file-library',
           label: '文件库',

+ 270 - 0
client/admin/pages_vod_upload.tsx

@@ -0,0 +1,270 @@
+import React, { useState, useRef } from 'react';
+import { Button, Card, Progress, Space, Tag, message } from 'antd';
+import TcVod from 'vod-js-sdk-v6';
+import axios from "axios";
+
+interface UploadTask {
+  file: File;
+  progress: number;
+  status: 'pending' | 'uploading' | 'success' | 'error' | 'canceled';
+  fileId?: string;
+  videoUrl?: string;
+  coverUrl?: string;
+  cancel?: () => void;
+}
+
+export const VodUploadPage = () => {
+  const videoInputRef = useRef<HTMLInputElement>(null);
+  const coverInputRef = useRef<HTMLInputElement>(null);
+  const [uploadTasks, setUploadTasks] = useState<UploadTask[]>([]);
+  const [selectedVideo, setSelectedVideo] = useState<File | null>(null);
+  const [selectedCover, setSelectedCover] = useState<File | null>(null);
+
+  // 获取上传签名
+  const getSignature = async () => {
+    try {
+      // TODO: 替换为实际获取签名的API调用
+      const response = await fetch('/api/vod/signature');
+      const data = await response.json();
+      return data.signature;
+    } catch (error) {
+      message.error('获取上传签名失败');
+      throw error;
+    }
+  };
+
+  
+      /**
+       * 计算签名。
+      **/
+      function getDemoSignature() {
+        return axios.post('https://demo.vod2.myqcloud.com/ugc-upload/', JSON.stringify({
+          "Action": "GetUgcUploadSign"
+        })).then(function (response) {
+          return response.data.data.sign
+        })
+      };
+
+  // 初始化VOD SDK
+  const tcVod = new TcVod.default({
+    // getSignature: getSignature
+    getSignature: getDemoSignature
+  });
+
+  // 处理视频文件选择
+  const handleVideoSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (e.target.files && e.target.files[0]) {
+      setSelectedVideo(e.target.files[0]);
+    }
+  };
+
+  // 处理封面文件选择
+  const handleCoverSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (e.target.files && e.target.files[0]) {
+      setSelectedCover(e.target.files[0]);
+    }
+  };
+
+  // 开始上传
+  const startUpload = async () => {
+    if (!selectedVideo) {
+      message.warning('请先选择视频文件');
+      return;
+    }
+
+    const newTask: UploadTask = {
+      file: selectedVideo,
+      progress: 0,
+      status: 'pending',
+    };
+
+    setUploadTasks(prev => [...prev, newTask]);
+    
+    try {
+      const uploader = tcVod.upload({
+        mediaFile: selectedVideo,
+        coverFile: selectedCover || undefined,
+      });
+
+      const updatedTask: UploadTask = {
+        ...newTask,
+        status: 'uploading',
+        cancel: () => {
+          uploader.cancel();
+          setUploadTasks(prev => 
+            prev.map(task => 
+              task.file === newTask.file 
+                ? { ...task, status: 'canceled' } 
+                : task
+            )
+          );
+        }
+      };
+
+      setUploadTasks(prev => 
+        prev.map(task => 
+          task.file === newTask.file ? updatedTask : task
+        )
+      );
+
+      // 监听上传进度
+      uploader.on('media_progress', (info) => {
+        setUploadTasks(prev => 
+          prev.map(task => 
+            task.file === newTask.file 
+              ? { ...task, progress: info.percent * 100 } 
+              : task
+          )
+        );
+      });
+
+      // 监听上传完成
+      uploader.on('media_upload', (info) => {
+        setUploadTasks(prev => 
+          prev.map(task => 
+            task.file === newTask.file 
+              ? { ...task, status: 'success' } 
+              : task
+          )
+        );
+      });
+
+      // 执行上传
+      const result = await uploader.done();
+      
+      setUploadTasks(prev => 
+        prev.map(task => 
+          task.file === newTask.file 
+            ? { 
+                ...task, 
+                fileId: result.fileId,
+                videoUrl: result.video.url,
+                coverUrl: result.cover?.url,
+                status: 'success'
+              } 
+            : task
+        )
+      );
+
+      message.success('视频上传成功');
+    } catch (error) {
+      setUploadTasks(prev => 
+        prev.map(task => 
+          task.file === newTask.file 
+            ? { ...task, status: 'error' } 
+            : task
+        )
+      );
+      message.error('视频上传失败');
+    } finally {
+      setSelectedVideo(null);
+      setSelectedCover(null);
+      if (videoInputRef.current) videoInputRef.current.value = '';
+      if (coverInputRef.current) coverInputRef.current.value = '';
+    }
+  };
+
+  // 渲染上传状态标签
+  const renderStatusTag = (status: UploadTask['status']) => {
+    switch (status) {
+      case 'pending':
+        return <Tag color="default">等待上传</Tag>;
+      case 'uploading':
+        return <Tag color="processing">上传中</Tag>;
+      case 'success':
+        return <Tag color="success">上传成功</Tag>;
+      case 'error':
+        return <Tag color="error">上传失败</Tag>;
+      case 'canceled':
+        return <Tag color="warning">已取消</Tag>;
+      default:
+        return <Tag color="default">未知状态</Tag>;
+    }
+  };
+
+  return (
+    <div>
+      <Card title="视频上传" className="mb-4">
+        <Space direction="vertical" style={{ width: '100%' }}>
+          <div>
+            <input
+              type="file"
+              ref={videoInputRef}
+              onChange={handleVideoSelect}
+              accept="video/*"
+              style={{ display: 'none' }}
+            />
+            <Button 
+              onClick={() => videoInputRef.current?.click()}
+              type="primary"
+            >
+              {selectedVideo ? selectedVideo.name : '选择视频文件'}
+            </Button>
+
+            <input
+              type="file"
+              ref={coverInputRef}
+              onChange={handleCoverSelect}
+              accept="image/*"
+              style={{ display: 'none', marginLeft: 16 }}
+            />
+            <Button 
+              onClick={() => coverInputRef.current?.click()}
+              style={{ marginLeft: 16 }}
+            >
+              {selectedCover ? selectedCover.name : '选择封面(可选)'}
+            </Button>
+
+            <Button 
+              type="primary" 
+              onClick={startUpload}
+              disabled={!selectedVideo}
+              style={{ marginLeft: 16 }}
+            >
+              开始上传
+            </Button>
+          </div>
+
+          <div style={{ marginTop: 24 }}>
+            {uploadTasks.map((task, index) => (
+              <div key={index} style={{ marginBottom: 16 }}>
+                <div style={{ marginBottom: 8 }}>
+                  <span style={{ marginRight: 8 }}>{task.file.name}</span>
+                  {renderStatusTag(task.status)}
+                  {task.status === 'uploading' && task.cancel && (
+                    <Button 
+                      type="link" 
+                      danger 
+                      onClick={task.cancel}
+                      style={{ marginLeft: 8 }}
+                    >
+                      取消上传
+                    </Button>
+                  )}
+                </div>
+                {task.status === 'uploading' && (
+                  <Progress percent={task.progress} status="active" />
+                )}
+                {task.fileId && (
+                  <div style={{ marginTop: 8 }}>
+                    <div>File ID: {task.fileId}</div>
+                    {task.videoUrl && (
+                      <div>
+                        视频地址: <a href={task.videoUrl} target="_blank" rel="noreferrer">{task.videoUrl}</a>
+                      </div>
+                    )}
+                    {task.coverUrl && (
+                      <div>
+                        封面地址: <a href={task.coverUrl} target="_blank" rel="noreferrer">{task.coverUrl}</a>
+                      </div>
+                    )}
+                  </div>
+                )}
+              </div>
+            ))}
+          </div>
+        </Space>
+      </Card>
+    </div>
+  );
+};

+ 6 - 0
client/admin/routes.tsx

@@ -18,6 +18,7 @@ import { ClassroomDataPage } from './pages_classroom_data.tsx';
 import { DateNotesPage } from './pages_date_notes.tsx';
 import { SubmissionRecordsPage } from './pages_submission_records.tsx';
 import { XunlianCodePage } from './pages_xunlian_codes.tsx';
+import { VodUploadPage } from './pages_vod_upload.tsx';
 
 export const router = createBrowserRouter([
   {
@@ -105,6 +106,11 @@ export const router = createBrowserRouter([
         element: <XunlianCodePage />,
         errorElement: <ErrorPage />
       },
+      {
+        path: 'vod-upload',
+        element: <VodUploadPage />,
+        errorElement: <ErrorPage />
+      },
       {
         path: '*',
         element: <NotFoundPage />,

+ 2 - 1
deno.json

@@ -39,7 +39,8 @@
     "aliyun-rtc-sdk":"https://esm.d8d.fun/aliyun-rtc-sdk@6.14.6?standalone",
     "decimal.js":"https://esm.d8d.fun/decimal.js@10.4.3",
     "echarts":"https://esm.d8d.fun/echarts@5.6.0",
-    "echarts/types/src/util/types":"https://esm.d8d.fun/echarts@5.6.0/types/src/util/types"
+    "echarts/types/src/util/types":"https://esm.d8d.fun/echarts@5.6.0/types/src/util/types",
+    "vod-js-sdk-v6": "npm:vod-js-sdk-v6"
   },
   "compilerOptions": {
     "lib": ["dom", "dom.iterable", "esnext", "deno.ns"]

+ 45 - 2
deno.lock

@@ -9,7 +9,8 @@
     "npm:@alicloud/live20161101@^1.1.1": "1.1.1",
     "npm:@alicloud/openapi-client@~0.4.14": "0.4.14",
     "npm:@alicloud/pop-core@1.8.0": "1.8.0",
-    "npm:@types/node@*": "22.12.0"
+    "npm:@types/node@*": "22.12.0",
+    "npm:vod-js-sdk-v6@*": "1.7.1-beta.1"
   },
   "jsr": {
     "@std/async@1.0.13": {
@@ -185,6 +186,13 @@
       ],
       "tarball": "https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.14.tgz"
     },
+    "axios@0.21.4": {
+      "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+      "dependencies": [
+        "follow-redirects"
+      ],
+      "tarball": "https://registry.npmmirror.com/axios/-/axios-0.21.4.tgz"
+    },
     "bignumber.js@9.3.0": {
       "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==",
       "tarball": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.0.tgz"
@@ -203,6 +211,14 @@
       ],
       "tarball": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz"
     },
+    "eventemitter3@4.0.7": {
+      "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+      "tarball": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz"
+    },
+    "follow-redirects@1.15.9": {
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "tarball": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz"
+    },
     "fsevents@2.3.2": {
       "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
       "os": ["darwin"],
@@ -221,6 +237,10 @@
       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
       "tarball": "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz"
     },
+    "js-sha1@0.6.0": {
+      "integrity": "sha512-01gwBFreYydzmU9BmZxpVk6svJJHrVxEN3IOiGl6VO93bVKYETJ0sIth6DASI6mIFdt7NmfX9UiByRzsYHGU9w==",
+      "tarball": "https://registry.npmmirror.com/js-sha1/-/js-sha1-0.6.0.tgz"
+    },
     "json-bigint@1.0.0": {
       "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
       "dependencies": [
@@ -266,6 +286,11 @@
       "integrity": "sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==",
       "tarball": "https://registry.npmmirror.com/sm3/-/sm3-1.0.3.tgz"
     },
+    "typescript@5.8.3": {
+      "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+      "bin": true,
+      "tarball": "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz"
+    },
     "undici-types@6.19.8": {
       "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
       "tarball": "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz"
@@ -278,6 +303,23 @@
       "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
       "tarball": "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz"
     },
+    "uuid@3.4.0": {
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "deprecated": true,
+      "bin": true,
+      "tarball": "https://registry.npmmirror.com/uuid/-/uuid-3.4.0.tgz"
+    },
+    "vod-js-sdk-v6@1.7.1-beta.1": {
+      "integrity": "sha512-YZljTVwogoCcx7VtlAhem9w3AP7RCnDlqKsxaU3bv7sylwTsC5/jE9QlvpCKEorkOHRBsbu37m3mYXUkelwBaw==",
+      "dependencies": [
+        "axios",
+        "eventemitter3",
+        "js-sha1",
+        "typescript",
+        "uuid"
+      ],
+      "tarball": "https://registry.npmmirror.com/vod-js-sdk-v6/-/vod-js-sdk-v6-1.7.1-beta.1.tgz"
+    },
     "xml2js@0.5.0": {
       "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
       "dependencies": [
@@ -1475,7 +1517,8 @@
       "npm:@alicloud/live-interaction20201214@2.1.6",
       "npm:@alicloud/live20161101@^1.1.1",
       "npm:@alicloud/openapi-client@~0.4.14",
-      "npm:@alicloud/pop-core@1.8.0"
+      "npm:@alicloud/pop-core@1.8.0",
+      "npm:vod-js-sdk-v6@*"
     ]
   }
 }

+ 354 - 0
docs/vod_upload_demo.md

@@ -0,0 +1,354 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>QCloud VIDEO UGC UPLOAD SDK</title>
+  <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+  <style type="text/css">
+    .text-danger {
+      color: red;
+    }
+
+    .control-label {
+      text-align: left !important;
+    }
+
+    #resultBox {
+      width: 100%;
+      height: 300px;
+      border: 1px solid #888;
+      padding: 5px;
+      overflow: auto;
+      margin-bottom: 20px;
+    }
+
+    .uploaderMsgBox {
+      width: 100%;
+      border-bottom: 1px solid #888;
+    }
+
+    .cancel-upload {
+      text-decoration: none;
+      cursor: pointer;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="content">
+    <div class="container">
+      <h1>UGC-Uploader</h1>
+    </div>
+  </div>
+  <div class="container" id="main-area">
+    <div class="row" style="padding:10px;">
+      <p>
+        1.示例中的签名是直接从demo后台获取签名。该 demo 对应的点播账号已开启防盗链功能,此视频分发 URL 仅限 2 个独立 IP 进行访问,有效期为 10 分钟。<br>
+        2.示例1点击“直接上传视频”按钮即可上传视频。<br>
+        3.示例2点击“添加视频”添加视频文件,点击“添加封面”添加封面文件,然后点击“上传视频和封面”按钮即可上传视频和封面。<br>
+        4.取消上传为取消上传中的视频,上传成功的视频不能取消上传。
+      </p>
+    </div>
+
+    <form ref="vExample">
+      <input type="file" style="display:none;" ref="vExampleFile" @change="vExampleUpload" />
+    </form>
+    <div class="row" style="padding:10px;">
+      <h4>示例1:直接上传视频</h4>
+      <a href="javascript:void(0);" class="btn btn-default" @click="vExampleAdd">直接上传视频</a>
+    </div>
+
+    <form ref="vcExample">
+      <input type="file" style="display:none;" ref="vcExampleVideo" @change="setVcExampleVideoName()" />
+      <input type="file" style="display:none;" ref="vcExampleCover" @change="setVcExampleCoverName()" />
+    </form>
+    <div class="row" style="padding:10px;">
+      <h4>示例2:上传视频和封面</h4>
+      <a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddVideo">{{vcExampleVideoName || '添加视频'}}</a>
+      <a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddCover">{{vcExampleCoverName || '添加封面'}}</a>
+      <a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddUpload">上传视频和封面</a>
+    </div>
+
+    <form ref="cExample">
+      <input type="file" style="display:none;" ref="cExampleCover" @change="cExampleUpload" />
+    </form>
+    <!-- <div class="row form-group form-group-sm" style="padding:10px;">
+      <h4>示例3:修改封面</h4>
+      <label class="col-sm-1" style="padding: 0;">fileId:</label>
+      <div class="col-sm-4">
+        <input name="fileId" class="form-control" v-model="cExampleFileId"></input>
+      </div>
+      <div class="col-sm-4">
+        <a href="javascript:void(0);" class="btn btn-default" @click="cExampleAddCover">修改封面</a>
+      </div>
+    </div> -->
+    <div class="row" id="resultBox">
+      <!-- 上传信息组件	 -->
+      <div class="uploaderMsgBox" v-for="uploaderInfo in uploaderInfos">
+        <div v-if="uploaderInfo.videoInfo">
+          视频名称:{{uploaderInfo.videoInfo.name + '.' + uploaderInfo.videoInfo.type}};
+          上传进度:{{Math.floor(uploaderInfo.progress * 100) + '%'}};
+          fileId:{{uploaderInfo.fileId}};
+          上传结果:{{uploaderInfo.isVideoUploadCancel ? '已取消' : uploaderInfo.isVideoUploadSuccess ? '上传成功' : '上传中'}};
+          <br>
+          地址:{{uploaderInfo.videoUrl}};
+          <a href="javascript:void(0);" class="cancel-upload" v-if="!uploaderInfo.isVideoUploadSuccess && !uploaderInfo.isVideoUploadCancel" @click="uploaderInfo.cancel()">取消上传</a><br>
+        </div>
+
+        <div v-if="uploaderInfo.coverInfo">
+          封面名称:{{uploaderInfo.coverInfo.name}};
+          上传进度:{{Math.floor(uploaderInfo.coverProgress * 100) + '%'}};
+          上传结果:{{uploaderInfo.isCoverUploadSuccess ? '上传成功' : '上传中'}};
+          <br>
+          地址:{{uploaderInfo.coverUrl}};
+          <br>
+        </div>
+      </div>
+    </div>
+
+  </div>
+  <script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
+  <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.js"></script>
+  <script src="//cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
+  <script src="https://cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>
+
+  <script type="text/javascript">
+
+    ;(function () {
+
+      /**
+       * 计算签名。
+      **/
+      function getSignature() {
+        return axios.post('https://demo.vod2.myqcloud.com/ugc-upload/', JSON.stringify({
+          "Action": "GetUgcUploadSign"
+        })).then(function (response) {
+          return response.data.data.sign
+        })
+      };
+
+      /*
+        防盗链地址获取。这是腾讯云官网demo的特殊逻辑,用户可忽略此处。
+      */
+      function getAntiLeechUrl(videoUrl, callback) {
+        return axios.post('https://demo.vod2.myqcloud.com/ugc-upload/', JSON.stringify({
+          Action: "GetAntiLeechUrl",
+          Url: videoUrl,
+        })).then(function (response) {
+          return response.data.data.url
+        })
+      }
+
+      var app = new Vue({
+        el: '#main-area',
+        data: {
+          uploaderInfos: [],
+
+          vcExampleVideoName: '',
+          vcExampleCoverName: '',
+
+          cExampleFileId: '',
+        },
+        created: function () {
+          this.tcVod = new TcVod.default({
+            getSignature: getSignature
+          })
+        },
+        methods: {
+          /**
+           * vExample示例。添加视频
+          **/
+          vExampleAdd: function () {
+            this.$refs.vExampleFile.click()
+          },
+          /**
+           * vExample示例。上传视频过程。
+          **/
+          vExampleUpload: function () {
+            var self = this;
+            var mediaFile = this.$refs.vExampleFile.files[0]
+
+            var uploader = this.tcVod.upload({
+              mediaFile: mediaFile,
+            })
+            uploader.on('media_progress', function (info) {
+              uploaderInfo.progress = info.percent;
+            })
+            uploader.on('media_upload', function (info) {
+              uploaderInfo.isVideoUploadSuccess = true;
+            })
+
+            console.log(uploader, 'uploader')
+
+            var uploaderInfo = {
+              videoInfo: uploader.videoInfo,
+              isVideoUploadSuccess: false,
+              isVideoUploadCancel: false,
+              progress: 0,
+              fileId: '',
+              videoUrl: '',
+              cancel: function() {
+                uploaderInfo.isVideoUploadCancel = true;
+                uploader.cancel()
+              },
+            }
+
+            this.uploaderInfos.push(uploaderInfo)
+
+            uploader.done().then(function(doneResult) {
+              console.log('doneResult', doneResult)
+
+              uploaderInfo.fileId = doneResult.fileId;
+
+              return getAntiLeechUrl(doneResult.video.url);
+            }).then(function (videoUrl) {
+              uploaderInfo.videoUrl = videoUrl
+              self.$refs.vExample.reset();
+            })
+          },
+
+          setVcExampleVideoName: function () {
+            this.vcExampleVideoName = this.$refs.vcExampleVideo.files[0].name;
+          },
+          setVcExampleCoverName: function () {
+            this.vcExampleCoverName = this.$refs.vcExampleCover.files[0].name;
+          },
+          /*
+            vcExample添加视频
+          */
+          vcExampleAddVideo: function () {
+            this.$refs.vcExampleVideo.click()
+          },
+          /*
+            vcExample添加封面
+          */
+          vcExampleAddCover: function () {
+            this.$refs.vcExampleCover.click()
+          },
+          /*
+            vcExample上传过程
+          */
+          vcExampleAddUpload: function () {
+            var self = this;
+
+            var mediaFile = this.$refs.vcExampleVideo.files[0];
+            var coverFile = this.$refs.vcExampleCover.files[0];
+
+            var uploader = this.tcVod.upload({
+              mediaFile: mediaFile,
+              coverFile: coverFile,
+            })
+            uploader.on('media_progress', function(info) {
+              uploaderInfo.progress = info.percent;
+            })
+            uploader.on('media_upload', function(info) {
+              uploaderInfo.isVideoUploadSuccess = true;
+            })
+            uploader.on('cover_progress', function(info) {
+              uploaderInfo.coverProgress = info.percent;
+            })
+            uploader.on('cover_upload', function(info) {
+              uploaderInfo.isCoverUploadSuccess = true;
+            })
+            console.log(uploader, 'uploader')
+
+            var uploaderInfo = {
+              videoInfo: uploader.videoInfo,
+              coverInfo: uploader.coverInfo,
+              isVideoUploadSuccess: false,
+              isVideoUploadCancel: false,
+              isCoverUploadSuccess: false,
+              progress: 0,
+              coverProgress: 0,
+              fileId: '',
+              videoUrl: '',
+              coverUrl: '',
+              cancel: function () {
+                uploaderInfo.isVideoUploadCancel = true;
+                uploader.cancel()
+              },
+            }
+
+            this.uploaderInfos.push(uploaderInfo)
+
+            uploader.done().then(function (doneResult) {
+              console.log('doneResult', doneResult)
+
+              uploaderInfo.fileId = doneResult.fileId;
+
+              uploaderInfo.coverUrl = doneResult.cover.url;
+              return getAntiLeechUrl(doneResult.video.url);
+            }).then(function (videoUrl) {
+              uploaderInfo.videoUrl = videoUrl
+              self.$refs.vcExample.reset();
+              self.vcExampleVideoName = ''
+              self.vcExampleCoverName = ''
+            })
+          },
+
+
+          // cExample 添加封面
+          cExampleAddCover: function() {
+            this.$refs.cExampleCover.click()
+          },
+          // cExample 上传过程
+          cExampleUpload: function() {
+            var self = this;
+
+            var coverFile = this.$refs.cExampleCover.files[0];
+
+            var uploader = this.tcVod.upload({
+              fileId: this.cExampleFileId,
+              coverFile: coverFile,
+            })
+            uploader.on('cover_progress', function(info) {
+              uploaderInfo.coverProgress = info.percent;
+            })
+            uploader.on('cover_upload', function(info) {
+              uploaderInfo.isCoverUploadSuccess = true;
+            })
+            console.log(uploader, 'uploader')
+
+            var uploaderInfo = {
+              coverInfo: uploader.coverInfo,
+              isCoverUploadSuccess: false,
+              coverProgress: 0,
+              coverUrl: '',
+              cancel: function () {
+                uploader.cancel()
+              },
+            }
+
+            this.uploaderInfos.push(uploaderInfo)
+
+            uploader.done().then(function (doneResult) {
+              console.log('doneResult', doneResult)
+
+              uploaderInfo.coverUrl = doneResult.cover.url;
+
+              self.$refs.cExample.reset();
+            })
+          },
+        },
+      })
+    })();
+
+  </script>
+  <!-- Global site tag (gtag.js) - Google Analytics -->
+  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-26476625-7"></script>
+  <script>
+    // add by alsotang@gmail.com
+    window.dataLayer = window.dataLayer || [];
+    function gtag(){dataLayer.push(arguments);}
+    gtag('js', new Date());
+
+    gtag('config', 'UA-26476625-7');
+  </script>
+
+</body>
+
+</html>