视频点播客户端上传

dev-v2
Joe 4 months ago
parent b026ee3375
commit 93d68df04b

@ -0,0 +1,190 @@
# 视频客户端上传修改说明
## 修改概述
本次修改将视频上传从服务端上传改为客户端上传使用阿里云视频点播上传SDKaliyun-upload-vod实现直接上传到阿里云视频点播服务。
## 主要修改内容
### 1. 新增依赖包
- `aliyun-upload-vod`: 阿里云视频点播上传SDK专门用于视频上传
- 移除了 `ali-oss``@types/ali-oss`
### 2. 新增组件
#### `src/components/AliVideoUpload/index.tsx`
创建了专门的客户端视频上传组件,主要功能:
- 使用阿里云视频点播上传SDK进行客户端上传
- 支持上传进度显示
- 文件类型和大小验证
- 错误处理和用户提示
- 上传成功后返回videoId和playAuth
- **新增**: 可配置的文件大小限制maxSize参数
- **新增**: 可配置的文件类型限制accept参数
- **新增**: 支持多文件上传multiple参数
- **新增**: 最大上传个数限制maxCount参数
#### 组件参数
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| value | UploadFile[] | [] | 文件列表 |
| onChange | (fileList: UploadFile[]) => void | - | 文件列表变化回调 |
| onSuccess | (data: any) => void | - | 上传成功回调 |
| disabled | boolean | false | 是否禁用 |
| maxSize | number | 500 | 最大文件大小单位MB |
| maxCount | number | 9 | 最大上传个数 |
| accept | string | 'video/*' | 接受的文件类型 |
| multiple | boolean | false | 是否支持多文件上传 |
### 3. 修改页面
#### `src/pages/TrainingClasses/detail.tsx`
- 导入新的`AliVideoUpload`组件
- 修改ModalForm中的视频上传部分
- 根据文件类型选择不同的上传方式:
- 视频文件使用客户端上传AliVideoUpload
- 其他文件:继续使用服务端上传
- 修改数据处理逻辑,适配客户端上传的数据格式
- **新增**: 设置maxSize={3000}限制视频文件大小为3000MB
- **新增**: 设置maxCount={1}限制单次只能上传1个文件
### 4. 服务接口
#### `src/services/pop-b2b2c/pbcVodController.ts`
- 使用现有的`createUploadVideoUsingPost`接口获取上传凭证
- 该接口返回阿里云VOD的上传地址和凭证信息
### 5. 类型定义
#### `src/types/aliyun-upload-vod.d.ts`
- 为aliyun-upload-vod包创建了TypeScript类型定义
- 确保类型安全和开发体验
## 技术实现
### 客户端上传流程
1. **创建上传器**: 使用`AliyunUpload.Vod`创建上传客户端
2. **添加文件**: 调用`addFile`方法添加文件到上传队列
3. **获取上传凭证**: 在`onUploadstarted`回调中调用`createUploadVideoUsingPost`接口获取上传凭证
4. **设置凭证**: 调用`setUploadAuthAndAddress`方法设置上传凭证和地址
5. **开始上传**: 自动开始上传过程
6. **处理结果**: 在`onUploadSucceed`回调中处理上传成功结果
### 关键代码
```typescript
// 创建VOD上传客户端
const uploader = new AliyunUpload.Vod({
userId: '1303984639806000', // 需要根据实际情况配置
region: 'cn-shanghai',
partSize: 1048576,
parallel: 5,
retryCount: 3,
retryDuration: 2,
onUploadstarted: async (uploadInfo) => {
// 获取上传凭证
const uploadAuthData = await getUploadAuth(file.name);
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId);
},
onUploadSucceed: (uploadInfo) => {
// 处理上传成功
},
onUploadProgress: (uploadInfo, totalSize, loadedPercent) => {
// 更新上传进度
}
});
// 添加文件到上传队列
uploader.addFile(file, undefined, undefined, undefined, userData);
```
## 使用示例
### 基本使用
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
onSuccess={(data) => {
console.log('videoId:', data.videoId);
}}
/>
```
### 自定义文件大小限制
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
maxSize={200} // 限制200MB
onSuccess={handleSuccess}
/>
```
### 多文件上传
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
multiple={true}
maxCount={5}
maxSize={100}
onSuccess={handleSuccess}
/>
```
## 优势
1. **减少服务器压力**:文件直接从客户端上传到阿里云,不经过业务服务器
2. **提高上传速度**:避免了服务器中转,减少网络延迟
3. **支持断点续传**aliyun-upload-vod SDK内置断点续传功能
4. **更好的用户体验**:实时显示上传进度
5. **节省服务器带宽**:减少服务器带宽消耗
6. **灵活配置**:支持自定义文件大小、类型和多文件上传
7. **专门优化**aliyun-upload-vod是专门为视频上传优化的SDK
## 注意事项
1. 确保后端`createUploadVideoUsingPost`接口返回正确的上传凭证格式
2. 上传凭证包含敏感信息,注意安全性
3. 客户端上传需要浏览器支持aliyun-upload-vod SDK
4. 文件大小限制可通过maxSize参数配置默认500MB
5. 需要确保阿里云VOD服务配置正确
6. **重要**: 需要配置正确的userId当前使用的是示例值
## 测试建议
1. 测试小文件上传功能
2. 测试大文件上传和断点续传
3. 测试网络异常情况下的错误处理
4. 验证上传后的视频播放功能
5. 测试不同浏览器兼容性
6. 测试不同的maxSize配置
7. 测试多文件上传功能
8. 测试maxCount限制功能
## 修改的文件列表
1. `src/components/AliVideoUpload/index.tsx` - 新增客户端上传组件
2. `src/components/AliVideoUpload/README.md` - 组件使用说明文档
3. `src/pages/TrainingClasses/detail.tsx` - 修改视频上传逻辑
4. `src/types/aliyun-upload-vod.d.ts` - 新增类型定义文件
5. `package.json` - 更新依赖包
6. `CLIENT_UPLOAD_README.md` - 本说明文档
## 部署注意事项
1. 确保生产环境已安装aliyun-upload-vod依赖
2. 检查阿里云VOD服务配置
3. 验证上传凭证接口的可用性
4. 测试生产环境的网络连接
5. 根据业务需求调整maxSize等参数配置
6. **重要**: 配置正确的userId替换示例值

@ -55,6 +55,7 @@
"@dnd-kit/utilities": "^3.2.2",
"@umijs/route-utils": "^2.2.2",
"aliyun-aliplayer": "^2.29.1",
"aliyun-upload-vod": "^1.0.6",
"antd": "^5.2.2",
"antd-img-crop": "^4.23.0",
"braft-editor": "^2.3.9",

@ -0,0 +1,226 @@
# AliVideoUpload 组件使用说明
## 组件介绍
AliVideoUpload 是一个基于阿里云视频点播上传SDK的客户端视频上传组件支持直接上传到阿里云视频点播服务。
## 功能特性
- ✅ 客户端直接上传到阿里云VOD
- ✅ 支持上传进度显示
- ✅ 文件类型和大小验证
- ✅ 错误处理和用户提示
- ✅ 支持断点续传
- ✅ 可配置的文件大小限制
- ✅ 可配置的文件类型限制
- ✅ 支持单文件/多文件上传
- ✅ **新增**: 可配置的最大上传个数限制
## 基本用法
```tsx
import AliVideoUpload from '@/components/AliVideoUpload';
// 基本使用
<AliVideoUpload
value={fileList}
onChange={setFileList}
onSuccess={(data) => {
console.log('上传成功:', data);
}}
/>
```
## 参数说明
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| value | UploadFile[] | [] | 文件列表 |
| onChange | (fileList: UploadFile[]) => void | - | 文件列表变化回调 |
| onSuccess | (data: any) => void | - | 上传成功回调 |
| disabled | boolean | false | 是否禁用 |
| maxSize | number | 500 | 最大文件大小单位MB |
| maxCount | number | 9 | 最大上传个数 |
| accept | string | 'video/*' | 接受的文件类型 |
| multiple | boolean | false | 是否支持多文件上传 |
## 使用示例
### 1. 基本使用(默认配置)
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
onSuccess={(data) => {
console.log('videoId:', data.videoId);
console.log('playAuth:', data.playAuth);
}}
/>
```
### 2. 自定义文件大小限制
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
maxSize={200} // 限制200MB
onSuccess={handleSuccess}
/>
```
### 3. 限制上传个数(单文件模式)
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
maxCount={1} // 只能上传1个文件
multiple={false} // 单文件模式
onSuccess={handleSuccess}
/>
```
### 4. 多文件上传
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
multiple={true}
maxCount={5} // 最多上传5个文件
maxSize={100}
onSuccess={handleSuccess}
/>
```
### 5. 完整配置示例
```tsx
<AliVideoUpload
value={fileList}
onChange={setFileList}
onSuccess={(data) => {
console.log('videoId:', data.videoId);
console.log('playAuth:', data.playAuth);
console.log('duration:', data.duration);
}}
maxCount={3}
maxSize={500}
multiple={true}
accept="video/*"
disabled={false}
/>
```
## maxCount 功能说明
### 功能描述
maxCount 参数用于限制用户可以上传的最大文件个数。
### 工作原理
1. 在 `beforeUpload` 回调中检查当前文件列表长度
2. 如果已达到最大数量限制,显示错误提示并阻止上传
3. 支持单文件和多文件模式下的数量限制
### 使用场景
- **单文件上传**: 设置 `maxCount={1}` 确保只能上传一个文件
- **多文件上传**: 设置 `maxCount={n}` 限制最多上传n个文件
- **批量上传**: 结合 `multiple={true}` 实现批量文件上传
### 测试方法
1. 设置 `maxCount={2}`
2. 尝试上传第3个文件
3. 应该看到错误提示:"最多只能上传2个文件!"
## 上传流程
1. **文件选择**: 用户选择文件
2. **前置检查**:
- 检查文件数量限制 (maxCount)
- 检查文件大小限制 (maxSize)
- 检查文件类型 (accept)
3. **获取凭证**: 调用后端接口获取上传凭证
4. **开始上传**: 使用阿里云VOD SDK上传文件
5. **进度显示**: 实时显示上传进度
6. **获取播放凭证**: 上传成功后获取播放凭证
7. **完成回调**: 调用 onSuccess 回调
## 返回数据格式
上传成功后,`onSuccess` 回调会返回以下数据:
```typescript
{
videoId: string; // 视频ID
playAuth: string; // 播放凭证
duration: string; // 视频时长
}
```
## 注意事项
1. **userId配置**: 需要配置正确的阿里云账号ID
2. **网络环境**: 确保网络环境能够访问阿里云服务
3. **文件格式**: 支持常见的视频格式MP4、AVI、MOV等
4. **文件大小**: 建议根据实际需求设置合理的文件大小限制
5. **并发上传**: 多文件上传时注意并发数量控制
## 常见问题
### Q: maxCount设置无效怎么办
A: 确保在 `beforeUpload` 中正确检查文件数量,并且没有其他逻辑覆盖了这个检查。
### Q: 如何实现单文件上传?
A: 设置 `maxCount={1}``multiple={false}`
### Q: 上传失败如何处理?
A: 检查网络连接、文件格式、文件大小等,查看控制台错误信息。
### Q: 如何获取上传进度?
A: 组件会自动显示上传进度条,也可以通过 `onUploadProgress` 回调获取。
## 更新日志
### v1.1.0
- ✅ 新增 maxCount 参数支持
- ✅ 优化文件数量限制逻辑
- ✅ 改进错误提示信息
- ✅ 更新文档和示例
### v1.0.0
- ✅ 基础视频上传功能
- ✅ 支持文件大小和类型验证
- ✅ 支持上传进度显示
- ✅ 支持错误处理
## 错误处理
组件会自动处理以下错误情况:
- 文件大小超限
- 文件类型不支持
- 网络连接失败
- 上传凭证获取失败
- OSS上传失败
所有错误都会通过 `message.error` 显示给用户。
## 样式定制
组件使用Ant Design的Upload和Progress组件可以通过CSS自定义样式
```css
.ali-video-upload {
/* 自定义样式 */
}
.ali-video-upload .ant-upload {
/* 上传按钮样式 */
}
.ali-video-upload .ant-progress {
/* 进度条样式 */
}
```

@ -0,0 +1,306 @@
import React, { useRef, useState } from 'react';
import { Button, message, Modal, Progress, Upload } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { createUploadVideoUsingPost, getVideoAuthByVideoIdUsingGet } from '@/services/pop-b2b2c/pbcVodController';
import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
import AliyunUpload from 'aliyun-upload-vod';
import AliPlayer from '../AliPlayer';
interface AliVideoUploadProps {
value?: UploadFile[];
onChange?: (fileList: UploadFile[]) => void;
onSuccess?: (data: any) => void;
disabled?: boolean;
maxSize?: number; // 最大文件大小单位MB默认500MB
maxCount?: number; // 最大上传个数
accept?: string; // 接受的文件类型默认video/*
multiple?: boolean; // 是否支持多文件上传默认false
}
const AliVideoUpload: React.FC<AliVideoUploadProps> = ({
value = [],
onChange,
onSuccess,
disabled = false,
maxSize = 500,
maxCount = 9,
accept = 'video/*',
multiple = false,
}) => {
const [uploading, setUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState(0);
const uploadRef = useRef<any>(null);
const vodUploadRef = useRef<any>(null);
// 获取上传凭证
const getUploadAuth = async (fileName: string) => {
try {
const response = await createUploadVideoUsingPost({
title: fileName,
fileName: fileName,
});
if (response.retcode && response.data) {
return response.data;
} else {
throw new Error(response.retmsg || '获取上传凭证失败');
}
} catch (error) {
console.error('获取上传凭证失败:', error);
throw error;
}
};
// 客户端上传到阿里云VOD
const uploadToAliyunVOD = async (file: File) => {
return new Promise((resolve, reject) => {
try {
// 创建VOD上传客户端
const uploader = new AliyunUpload.Vod({
// 阿里账号ID必须有值
userId: '1303984639806000', // 这里需要根据实际情况配置
// 上传到视频点播的地域,默认值为'cn-shanghai'
region: 'cn-shanghai',
// 分片大小默认1 MB不能小于100 KB
partSize: 1048576,
// 并行上传分片个数默认5
parallel: 5,
// 网络原因失败时重新上传次数默认为3
retryCount: 3,
// 网络原因失败时重新上传间隔时间默认为2秒
retryDuration: 2,
// 添加文件成功
addFileSuccess: () => {
if (uploader !== null) {
uploader.startUpload();
}
},
// 开始上传
onUploadstarted: async (uploadInfo: any) => {
try {
// 获取上传凭证
const uploadAuthData = await getUploadAuth(file.name);
console.log('上传凭证:', uploadAuthData);
// 解析上传凭证数据 - 根据实际返回格式处理
let uploadAuth: string;
let uploadAddress: string;
let videoId: string;
if (typeof uploadAuthData === 'string') {
// 如果返回的是字符串尝试解析JSON
try {
const parsed = JSON.parse(uploadAuthData);
uploadAuth = parsed.uploadAuth || uploadAuthData;
uploadAddress = parsed.uploadAddress || '';
videoId = parsed.videoId || '';
} catch {
// 如果解析失败,直接使用字符串
uploadAuth = uploadAuthData;
uploadAddress = '';
videoId = '';
}
} else {
// 如果返回的是对象
uploadAuth = (uploadAuthData as any).uploadAuth || uploadAuthData;
uploadAddress = (uploadAuthData as any).UploadAddress || '';
videoId = (uploadAuthData as any).videoId || '';
}
// 设置上传凭证和地址
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId);
} catch (error) {
console.error('获取上传凭证失败:', error);
reject(error);
}
},
// 文件上传成功
onUploadSucceed: (uploadInfo: any) => {
console.log('上传成功:', uploadInfo);
getVideoAuthByVideoIdUsingGet({ id: uploadInfo.videoId }).then(res => {
if (res.retcode) {
resolve({
videoId: uploadInfo.videoId,
playAuth: res.data?.playAuth, // 需要单独获取播放凭证
duration: res.data?.videoMeta?.duration,
});
} else {
message.error(res.retmsg);
reject(new Error(res.retmsg));
}
}).catch(error => {
message.error(error);
reject(new Error(error));
})
},
// 文件上传失败
onUploadFailed: (uploadInfo: any, code: string, message: string) => {
console.error('上传失败:', uploadInfo, code, message);
reject(new Error(message || '上传失败'));
},
// 文件上传进度,单位:字节
onUploadProgress: (uploadInfo: any, totalSize: number, loadedPercent: number) => {
const progress = Math.ceil(loadedPercent * 100);
setUploadProgress(progress);
},
// 上传凭证或STS token超时
onUploadTokenExpired: () => {
message.error('文件上传token超时');
reject(new Error('上传凭证超时'));
},
// 全部文件上传结束
onUploadEnd: () => {
console.log("onUploadEnd: uploaded all the files");
}
});
// 保存上传器引用
vodUploadRef.current = uploader;
// 添加文件到上传队列
const userData = '{"Vod":{}}';
uploader.addFile(file, undefined, undefined, undefined, userData);
} catch (error) {
reject(error);
}
});
};
const handleUpload = async (file: File) => {
if (!file) return;
setUploading(true);
setUploadProgress(0);
try {
// 上传到阿里云VOD
const result = await uploadToAliyunVOD(file);
// 更新文件列表
const newFile: UploadFile = {
uid: (file as any).uid || Date.now().toString(),
name: file.name,
status: 'done',
url: '',
response: {
retcode: 1,
data: result,
},
};
const newFileList = [...value, newFile];
onChange?.(newFileList);
onSuccess?.(result);
message.success('视频上传成功');
} catch (error) {
console.error('上传失败:', error);
message.error(error instanceof Error ? error.message : '上传失败');
// 更新文件状态为错误
const errorFile: UploadFile = {
uid: (file as any).uid || Date.now().toString(),
name: file.name,
status: 'error',
error: error instanceof Error ? error : new Error('上传失败'),
};
const newFileList = [...value, errorFile];
onChange?.(newFileList);
} finally {
setUploading(false);
setUploadProgress(0);
}
};
const uploadProps: UploadProps = {
name: 'file',
accept: accept,
maxCount: maxCount,
multiple: multiple,
disabled: disabled || uploading,
beforeUpload: (file) => {
// 检查文件数量限制
if (value.length >= maxCount) {
message.error(`最多只能上传${maxCount}个文件!`);
return Upload.LIST_IGNORE;
}
// 检查文件大小 (限制为maxSize MB)
const isLtMaxSize = file.size / 1024 / 1024 < maxSize;
if (!isLtMaxSize) {
message.error(`视频文件大小不能超过${maxSize}MB!`);
return Upload.LIST_IGNORE;
}
// 检查文件类型
const isVideo = file.type.startsWith('video/');
if (!isVideo) {
message.error('只能上传视频文件!');
return Upload.LIST_IGNORE;
}
// 手动处理上传
handleUpload(file);
return Upload.LIST_IGNORE;
},
fileList: value,
onRemove: (file) => {
const newFileList = value.filter(item => item.uid !== file.uid);
onChange?.(newFileList);
},
onPreview: (file) => {
let videoId = ""
if (file.uid === '-1' && file.url) {
videoId = file.url
} else if (file.response?.data?.videoId) {
videoId = file.response?.data?.videoId
}
getVideoAuthByVideoIdUsingGet({ id: videoId }).then(res => {
if (res.retcode && res.data?.playAuth) {
setVideoId(videoId)
setPlayAuth(res.data?.playAuth)
handleShowVideo(true)
}
})
},
};
const [showVideo, handleShowVideo] = useState<boolean>(false);
const [playAuth, setPlayAuth] = useState<string>('')
const [videoId, setVideoId] = useState<string>('')
return (
<div>
<Upload {...uploadProps} ref={uploadRef}>
<Button
icon={<UploadOutlined />}
disabled={disabled || uploading}
>
{uploading ? '上传中...' : '选择视频文件'}
</Button>
</Upload>
{uploading && (
<div style={{ marginTop: 8 }}>
<Progress percent={uploadProgress} status="active" />
</div>
)}
<div style={{ marginTop: 8, fontSize: '12px', color: '#666' }}>
MP4AVIMOV{maxSize}MB
</div>
<Modal
title={'预览视频'}
open={showVideo}
onCancel={() => handleShowVideo(false)}
width={800}
>
<AliPlayer playAuth={playAuth} vid={videoId} />
</Modal>
</div>
);
};
export default AliVideoUpload;

@ -126,7 +126,7 @@ const Detail: React.FC<any> = () => {
title="填写驳回理由"
open={isModalOpen}
modalProps={{
destroyOnClose: true,
destroyOnHidden: true,
onCancel: () => setIsModalOpen(false),
}}
requiredMark={false}

@ -31,7 +31,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...props.values, ...value })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcContentTypeName: props.values.pbcContentTypeName,

@ -26,7 +26,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
onOpenChange={(visible) => {
formRef.current?.resetFields();

@ -25,7 +25,7 @@ const UpdateItemForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
onOpenChange={(visible) => {
formRef.current?.resetFields();

@ -110,7 +110,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value, pbcPicAddress, pbcThumbNail, pbcContent: value.pbcType === 3 ? '预览文件' : value.pbcContent, pbcId: props.values.pbcId })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcTitle: props.values.pbcTitle,

@ -110,7 +110,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value, pbcPicAddress, pbcThumbNail, pbcContent: value.pbcType === 3 ? '预览文件' : value.pbcContent, pbcId: props.values.pbcId })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcTitle: props.values.pbcTitle,

@ -30,7 +30,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value,pbcVipGradeDiscount: value.pbcVipGradeDiscount ? value.pbcVipGradeDiscount / 10 : 1, pbcId: props.values.pbcId })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcVipGradeName: props.values.pbcVipGradeName,

@ -173,7 +173,7 @@ const TableList: React.FC<{}> = () => {
width={600}
closeIcon={null}
footer={null}
destroyOnClose={true}
destroyOnHidden={true}
open={openDrawer}
>
<div>

@ -90,7 +90,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...props.values, ...value, pbcSpecificationList: arr, pbcCategoryImage, pbcId: props.values.pbcId })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcCategoryName: props.values.pbcCategoryName

@ -28,7 +28,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...props.values, ...value })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcLabelName: props.values.pbcLabelName

@ -28,7 +28,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...props.values, ...value })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcLabelTypeName: props.values.pbcLabelTypeName

@ -56,7 +56,7 @@ const UpdateBannerForm: React.FC<UpdateBannerFormProps> = (props) => {
return props.onSubmit({ ...value, pbcBannerImage, pbcId: props.values.pbcId, pbcLink: value.pbcLink ? linkType + value.pbcLink : '' })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
afterOpenChange: (visible) => {
if (!visible) props.afterClose();
}

@ -42,7 +42,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value, pbcAdvertisement, pbcId: props.values.pbcId })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
afterOpenChange: (visible) => {
if (!visible) props.afterClose();
}

@ -28,7 +28,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...props.values, ...value })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcType: props.values.pbcType

@ -22,6 +22,7 @@ import { addOrUpdateClassUsingPost, classDetailForAdminUsingGet } from '@/servic
import { addOrUpdateChapterUsingPost, removeChapterUsingGet } from '@/services/pop-b2b2c/pbcTrainingClassesChapterController';
import { addOrUpdateVideoUsingPost, getVideoAuthByIdUsingGet, removeVideoUsingGet } from '@/services/pop-b2b2c/pbcTrainingClassesVideoController';
import AliPlayer from '@/components/AliPlayer';
import AliVideoUpload from '@/components/AliVideoUpload';
/**
*
@ -451,7 +452,7 @@ const Detail: React.FC<any> = () => {
title={stepFormValues.pbcId ? '编辑章节' : '新增章节'}
open={updateModalVisible}
modalProps={{
destroyOnClose: true,
destroyOnHidden: true,
onCancel: () => handleUpdateModalVisible(false),
}}
requiredMark={false}
@ -493,7 +494,7 @@ const Detail: React.FC<any> = () => {
title={stepFormValues1.pbcId ? '编辑文件' : '新增文件'}
open={updateModalVisible1}
modalProps={{
destroyOnClose: true,
destroyOnHidden: true,
onCancel: () => handleUpdateModalVisible1(false),
}}
requiredMark={false}
@ -522,8 +523,14 @@ const Detail: React.FC<any> = () => {
value.pbcVideoAddress[0].response &&
value.pbcVideoAddress[0].response.retcode
) {
pbcVideoAddress = fileType === '1' ? value.pbcVideoAddress[0].response.data.videoId : value.pbcVideoAddress[0].response.data;
pbcVideoDuration = fileType === '1' ? value.pbcVideoAddress[0].response.data.duration : '';
if (fileType === '1') {
// 客户端上传的视频数据
pbcVideoAddress = value.pbcVideoAddress[0].response.data.videoId;
pbcVideoDuration = value.pbcVideoAddress[0].response.data.duration || '';
} else {
// 服务端上传的文件数据
pbcVideoAddress = value.pbcVideoAddress[0].response.data;
}
}
}
await addOrUpdateVideoUsingPost({
@ -575,78 +582,71 @@ const Detail: React.FC<any> = () => {
},
]}
/>
<ProFormUploadButton
label={fileType === '1' ? '上传视频' : '上传文件'}
name="pbcVideoAddress"
max={1}
fieldProps={{
name: 'file',
accept: fileType === '1' ? 'video/mp4' : 'image/*,.pdf',
multiple: true,
headers: {
authorization: localStorage.getItem('token') ?? '',
},
onChange: (info: any) => {
switch (info.file.status) {
case 'done':
if (info.file.response.retcode === 0) {
message.error(info.file.response.retmsg);
formRef1.current?.setFieldValue('pbcVideoAddress', [])
} else {
const { data } = info.file.response
if (fileType === '1') {
setPlayAuth(data.playAuth)
setVideoId(data.videoId)
}
}
break;
default:
break;
}
},
action: process.env.BASE_URL + (fileType === '1' ? '/b2b2c/pbcTrainingClassesVideo/uploadVideoAndGetInfo' : '/oss/imgUpload'),
onPreview: async (file) => {
if (fileType === '1') {
if (file.uid === '-1' && stepFormValues1.pbcId) {
getVideoAuthByIdUsingGet({pbcId: stepFormValues1.pbcId}).then(res => {
if (res.retcode && res.data && file.url) {
setPlayAuth(res.data)
setVideoId(file.url)
handleShowVideo(true)
} else {
message.error(res.retmsg)
}
})
{fileType === '1' ? (
<ProForm.Item label="上传视频" name="pbcVideoAddress">
<AliVideoUpload
// value={formRef1.current?.getFieldValue('pbcVideoAddress') || []}
// onChange={(fileList) => {
// formRef1.current?.setFieldValue('pbcVideoAddress', fileList);
// }}
onSuccess={(data) => {
console.log(data)
if (data) {
setPlayAuth(data.playAuth);
setVideoId(data.videoId);
}
if (file.response && file.response.retcode) {
const { data } = file.response
setPlayAuth(data.playAuth)
setVideoId(data.videoId)
handleShowVideo(true)
}}
maxCount={1}
maxSize={3000}
/>
</ProForm.Item>
) : (
<ProFormUploadButton
label="上传文件"
name="pbcVideoAddress"
max={1}
fieldProps={{
name: 'file',
accept: 'image/*,.pdf',
multiple: true,
headers: {
authorization: localStorage.getItem('token') ?? '',
},
onChange: (info: any) => {
switch (info.file.status) {
case 'done':
if (info.file.response.retcode === 0) {
message.error(info.file.response.retmsg);
formRef1.current?.setFieldValue('pbcVideoAddress', [])
}
break;
default:
break;
}
} else {
},
action: process.env.BASE_URL + '/oss/imgUpload',
onPreview: async (file) => {
if (file.uid === '-1') {
window.open(file.url);
}
if (file.response && file.response.retcode) {
window.open(file.response.data);
}
}
},
progress: {
strokeColor: {
'0%': '#108ee9',
'100%': '#87d068',
},
strokeWidth: 3,
format: (percent) => percent && `${parseFloat(percent.toFixed(2))}%`,
},
}}
rules={[
{ required: true, message: fileType === '1' ? '请上传视频' : '请上传文件' },
]}
/>
progress: {
strokeColor: {
'0%': '#108ee9',
'100%': '#87d068',
},
strokeWidth: 3,
format: (percent) => percent && `${parseFloat(percent.toFixed(2))}%`,
},
}}
rules={[
{ required: true, message: '请上传文件' },
]}
/>
)}
</ModalForm>
<Modal
title={'预览视频'}

@ -39,7 +39,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return props.onSubmit({ ...value, pbcUserRoleName: roleName, pbcUserType: 2, pbcId: props.values.pbcId })
}}
drawerProps={{
destroyOnClose: true,
destroyOnHidden: true,
}}
initialValues={{
pbcUserName: props.values.pbcUserName,

@ -2,41 +2,41 @@
/* eslint-disable */
import request from '@/utils/request';
/** errorHtml GET /error */
export async function errorHtmlUsingGet(options?: { [key: string]: any }) {
return request<API.ModelAndView>('/error', {
/** error GET /error */
export async function errorUsingGet(options?: { [key: string]: any }) {
return request<Record<string, any>>('/error', {
method: 'GET',
...(options || {}),
});
}
/** errorHtml PUT /error */
export async function errorHtmlUsingPut(options?: { [key: string]: any }) {
return request<API.ModelAndView>('/error', {
/** error PUT /error */
export async function errorUsingPut(options?: { [key: string]: any }) {
return request<Record<string, any>>('/error', {
method: 'PUT',
...(options || {}),
});
}
/** errorHtml POST /error */
export async function errorHtmlUsingPost(options?: { [key: string]: any }) {
return request<API.ModelAndView>('/error', {
/** error POST /error */
export async function errorUsingPost(options?: { [key: string]: any }) {
return request<Record<string, any>>('/error', {
method: 'POST',
...(options || {}),
});
}
/** errorHtml DELETE /error */
export async function errorHtmlUsingDelete(options?: { [key: string]: any }) {
return request<API.ModelAndView>('/error', {
/** error DELETE /error */
export async function errorUsingDelete(options?: { [key: string]: any }) {
return request<Record<string, any>>('/error', {
method: 'DELETE',
...(options || {}),
});
}
/** errorHtml PATCH /error */
export async function errorHtmlUsingPatch(options?: { [key: string]: any }) {
return request<API.ModelAndView>('/error', {
/** error PATCH /error */
export async function errorUsingPatch(options?: { [key: string]: any }) {
return request<Record<string, any>>('/error', {
method: 'PATCH',
...(options || {}),
});

@ -107,8 +107,8 @@ export default {
pbcUserMessageController,
pbcUsersController,
pbcUserRecordLogController,
pbcVodController,
wxController,
errorController,
pbcOssImgController,
pbcVodController,
};

@ -51,6 +51,21 @@ export async function frontChangeBusinessInfoUsingPost(
});
}
/** 通过店铺id得到联系人的账号 GET /b2b2c/pbcbusiness/getBusinessContactInfo */
export async function getBusinessContactInfoUsingGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getBusinessContactInfoUsingGETParams,
options?: { [key: string]: any },
) {
return request<API.AjaxResultPbcUsers_>('/b2b2c/pbcbusiness/getBusinessContactInfo', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 取得商户的图片,用以合成海报 GET /b2b2c/pbcbusiness/getBusinessImage */
export async function getBusinessImageUsingGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)

@ -2,38 +2,68 @@
/* eslint-disable */
import request from '@/utils/request';
/** 根据video id删除视频,后端自用 DELETE /vodFile/deleteAliyunVideo/${param0} */
/** 获得音视频上传地址和凭证 POST /b2b2c/vodFile/createUploadVideo */
export async function createUploadVideoUsingPost(
body: API.GetVideoPlayAuthDTO,
options?: { [key: string]: any },
) {
return request<API.AjaxResultString_>('/b2b2c/vodFile/createUploadVideo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
});
}
/** 根据video id删除视频,后端自用 DELETE /b2b2c/vodFile/deleteAliyunVideo/${param0} */
export async function deleteAliyunVideoUsingDelete(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteAliyunVideoUsingDELETEParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.AjaxResult>(`/vodFile/deleteAliyunVideo/${param0}`, {
return request<API.AjaxResult>(`/b2b2c/vodFile/deleteAliyunVideo/${param0}`, {
method: 'DELETE',
params: { ...queryParams },
...(options || {}),
});
}
/** 根据video id获取播放凭证,后端自用 DELETE /vodFile/getVideoAuthByVideoId/${param0} */
export async function getVideoAuthByVideoIdUsingDelete(
/** 根据video id获取播放凭证,后端自用 GET /b2b2c/vodFile/getVideoAuthByVideoId/${param0} */
export async function getVideoAuthByVideoIdUsingGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getVideoAuthByVideoIdUsingDELETEParams,
params: API.getVideoAuthByVideoIdUsingGETParams,
options?: { [key: string]: any },
) {
const { id: param0, ...queryParams } = params;
return request<API.AjaxResultGetVideoPlayAuthResponse_>(
`/vodFile/getVideoAuthByVideoId/${param0}`,
`/b2b2c/vodFile/getVideoAuthByVideoId/${param0}`,
{
method: 'DELETE',
method: 'GET',
params: { ...queryParams },
...(options || {}),
},
);
}
/** 上传视频到vod,测试用 POST /vodFile/upload */
/** 刷新视频上传凭证 GET /b2b2c/vodFile/refreshUploadVideo */
export async function refreshUploadVideoUsingGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.refreshUploadVideoUsingGETParams,
options?: { [key: string]: any },
) {
return request<API.AjaxResultString_>('/b2b2c/vodFile/refreshUploadVideo', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}
/** 上传视频到vod,测试用 POST /b2b2c/vodFile/upload */
export async function uploadVideoUsingPost(
body: {},
file?: File,
@ -61,7 +91,7 @@ export async function uploadVideoUsingPost(
}
});
return request<API.AjaxResultString_>('/vodFile/upload', {
return request<API.AjaxResultString_>('/b2b2c/vodFile/upload', {
method: 'POST',
data: formData,
requestType: 'form',

@ -825,6 +825,8 @@ declare namespace API {
type createQrCodeUsingGETParams = {
/** 类型 */
codeType: string;
/** parameterVal ue */
'parameterVal ue': string;
/** 参数值 */
parameterValue: string;
};
@ -928,6 +930,11 @@ declare namespace API {
pbcBannerType: number;
};
type getBusinessContactInfoUsingGETParams = {
/** businessId */
businessId: number;
};
type getBusinessImageUsingGETParams = {
/** businessId */
businessId: number;
@ -1040,11 +1047,36 @@ declare namespace API {
pbcId: number;
};
type getVideoAuthByVideoIdUsingDELETEParams = {
type getVideoAuthByVideoIdUsingGETParams = {
/** id */
id: string;
};
type GetVideoPlayAuthDTO = {
/** 分类ID可从点播控制台或API获取 */
cateId?: number;
/** 自定义视频封面URL地址 */
coverURL?: string;
/** 视频描述上传后展示在点播控制台UTF-8编码最长1024字符 */
description?: string;
/** 待上传的视频文件地址(必须带扩展名,如.mp4 */
fileName: string;
/** 视频文件大小(单位:字节) */
fileSize?: number;
/** 存储地址(不指定则使用默认地址) */
storageLocation?: string;
/** 视频标签多个标签用英文逗号分隔最多16个每个最长32字符 */
tags?: string;
/** 转码模板组ID通过控制台或API获取。若同时指定WorkflowId则以WorkflowId为准 */
templateGroupId?: string;
/** 视频标题上传后展示在点播控制台UTF-8编码最长128字符 */
title: string;
/** 自定义用户数据JSON格式支持回调通知和上传加速。注意回调需先在控制台配置地址 */
userData?: string;
/** 工作流ID通过点播控制台获取优先级高于TemplateGroupId */
workflowId?: string;
};
type GetVideoPlayAuthResponse = {
playAuth?: string;
requestId?: string;
@ -3149,6 +3181,8 @@ declare namespace API {
current?: number;
/** 条数 */
pageSize?: number;
/** 商户id */
pbcBusinessId?: number;
/** 产地城市 */
pbcProductOriginalCity?: string;
/** 产地城市编码 */
@ -4543,6 +4577,16 @@ declare namespace API {
expressNo: string;
};
type receiveUsingGETParams = {
/** echostr */
echostr: string;
};
type refreshUploadVideoUsingGETParams = {
/** videoId */
videoId: string;
};
type removeAddressUsingGETParams = {
/** pbcId */
pbcId: number;

@ -39,3 +39,18 @@ export async function getWxSignUsingGet1(
...(options || {}),
});
}
/** 获取微信签名sign的参数 获取微信签名sign的参数 GET /b2b2c/wx/receive */
export async function receiveUsingGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.receiveUsingGETParams,
options?: { [key: string]: any },
) {
return request<string>('/b2b2c/wx/receive', {
method: 'GET',
params: {
...params,
},
...(options || {}),
});
}

@ -0,0 +1,40 @@
declare module 'aliyun-upload-vod' {
interface VODUploadOptions {
userId: string;
region?: string;
partSize?: number;
parallel?: number;
retryCount?: number;
retryDuration?: number;
addFileSuccess?: () => void;
onUploadstarted?: (uploadInfo: any) => void;
onUploadSucceed?: (uploadInfo: any) => void;
onUploadFailed?: (uploadInfo: any, code: string, message: string) => void;
onUploadProgress?: (uploadInfo: any, totalSize: number, loadedPercent: number) => void;
onUploadTokenExpired?: () => void;
onUploadEnd?: () => void;
}
interface UploadInfo {
videoId?: string;
duration?: string;
[key: string]: any;
}
class Vod {
constructor(options: VODUploadOptions);
addFile(file: File, endpoint?: string, bucket?: string, object?: string, userData?: string): void;
startUpload(): void;
stopUpload(): void;
pauseUpload(): void;
resumeUpload(): void;
setUploadAuthAndAddress(uploadInfo: any, uploadAuth: string, uploadAddress: string, videoId: string): void;
}
interface AliyunUpload {
Vod: typeof Vod;
}
const AliyunUpload: AliyunUpload;
export = AliyunUpload;
}
Loading…
Cancel
Save