Commit 4136b795 by wufan

feat:添加资料云盘

parent dffb2d6d
......@@ -18,6 +18,8 @@
"@types/underscore": "^1.10.22",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"@types/ali-oss": "^6.0.5",
"ali-oss": "^6.12.0",
"antd": "^4.8.5",
"axios": "^0.20.0",
"babel-eslint": "10.1.0",
......@@ -28,6 +30,7 @@
"babel-preset-react-app": "^9.1.2",
"camelcase": "^5.3.1",
"case-sensitive-paths-webpack-plugin": "2.3.0",
"classnames": "^2.2.6",
"cropper": "^3.1.4",
"cross-env": "^7.0.2",
"css-loader": "3.4.2",
......@@ -46,6 +49,7 @@
"file-loader": "4.3.0",
"fs-extra": "^8.1.0",
"html-webpack-plugin": "4.0.0-beta.11",
"html2canvas": "^1.0.0-rc.7",
"husky": "^4.2.5",
"identity-obj-proxy": "3.0.0",
"jest": "24.9.0",
......@@ -53,9 +57,11 @@
"jest-resolve": "24.9.0",
"jest-watch-typeahead": "0.4.2",
"jquery": "^3.4.1",
"less": "^3.12.2",
"less-loader": "^6.2.0",
"microevent": "^1.0.0",
"mini-css-extract-plugin": "0.9.0",
"moment": "^2.27.0",
"optimize-css-assets-webpack-plugin": "5.0.3",
"pnp-webpack-plugin": "1.6.4",
"postcss-flexbugs-fixes": "4.1.0",
......@@ -80,10 +86,13 @@
"typescript": "^4.0.2",
"underscore": "^1.10.2",
"url-loader": "2.3.0",
"video-react": "^0.14.1",
"wangeditor": "^3.1.1",
"webpack": "4.42.0",
"webpack-dev-server": "3.11.0",
"webpack-manifest-plugin": "2.2.0",
"workbox-webpack-plugin": "4.3.1"
"workbox-webpack-plugin": "4.3.1",
"xiaomai-b-components": "1.4.4"
},
"scripts": {
"start": "node scripts/start.js",
......@@ -116,8 +125,9 @@
"jsx-control-statements"
]
},
"devDependencies": {
"@types/jquery": "^3.5.4"
"@types/ali-oss": "^6.0.5",
"@types/jquery": "^3.5.4",
"ali-oss": "^6.12.0"
}
}
/*
* @Author: wanghaofeng
* @date: 2020/11/14 17:42
* @Description:权限管理-选择成员弹窗
*/
import React from 'react';
import { Modal, Input, Table } from 'antd';
import Service from '@/common/js/service';
// import _ from 'underscore';
import './ChooseMembersModal.less';
const { Search } = Input;
class ChooseMembersModal extends React.Component {
constructor(props) {
super(props);
this.state = {
allUserList : [], // 所有成员列表
selectUserList: [], // 已选则成员
temporaryList: [], // 临时用户成员列表(搜索时使用)
instId : window.currentUserInstInfo.instId, // 机构Id
searchKey : null, // 搜索内容
selectedRowKeys : [], // 勾选的成员
userAuthority : ['可编辑', '可查看/下载', '仅可查看'] ,
}
}
componentDidMount() {
this.getFileUserAuthority();
}
// 获取对应文件相关成员
getFileUserAuthority = (searchKey='') => {
const { dataInfo = {} } = this.props;
const { folderType, id } = dataInfo;
const _params = {
instId : this.state.instId,
folderTypeEnum : folderType,
id
}
Service.Apollo('public/apollo/getFileUser', _params).then((res) => {
if (res.result) {
let _allUserList = [];
_allUserList = res.result;
// 判断是否进行了搜索
if (searchKey === '') {
this.setState({
allUserList : _allUserList
})
} else {
let searchPersonList = [];
_allUserList.map(item => {
if (item.adminName.indexOf(searchKey) > -1) {
searchPersonList.push(item);
}
return searchPersonList
})
this.setState({
allUserList: searchPersonList
})
}
}
})
}
// 勾选成员
onChangeRow = (_selectedRowKeys, selectedRows) => {
let { temporaryList, allUserList } = this.state;
let _temporaryIdList = [], _temporaryList = [];
// 解决搜索款2次搜索的问题
if (temporaryList.length === 0) {
_temporaryList = selectedRows;
} else {
temporaryList = temporaryList.concat(selectedRows);
// 之前搜索并勾选的选项以及当前页被勾选的选项
temporaryList = temporaryList.filter(item => (!allUserList.includes(item) || selectedRows.includes(item)) && _selectedRowKeys.includes(item.userId));
temporaryList.map(item => {
if (!_temporaryIdList.includes(item.userId)) {
_temporaryIdList.push(item.userId);
_temporaryList.push(item);
}
return (_temporaryIdList, _temporaryList)
});
}
this.setState({
temporaryList : _temporaryList,
selectUserList : _temporaryList,
selectedRowKeys : _selectedRowKeys
})
}
// 勾选禁用
getCheckboxProps = (record) => {
if (record.whetherExist) {
return ({
disabled : true,
checked: true
})
}
}
// 清空所有成员
clearAllUser = () => {
this.setState({
selectedRowKeys : [],
selectUserList : [],
temporaryList : []
})
}
// 清除单个成员(点击“x”)
clearOneUser = (user) => {
let { selectUserList, selectedRowKeys } = this.state
// 清除成员不是之前选的
selectedRowKeys = selectedRowKeys.filter(item => item !== user.userId);
selectUserList = selectUserList.filter(item => item.userId !== user.userId);
this.setState({
selectedRowKeys,
selectUserList
})
}
// 点击全选
onSelectAll = (selected, selectedRows, changeRows) => {
let { temporaryList, allUserList } = this.state;
let newSelectUserList = [], allUserId = [];
allUserList.map(item => allUserId.push(item.userId));
temporaryList = temporaryList.filter(item => !allUserId.includes(item.userId));
if (selectedRows.length === 0) {
// 取消勾选,保留之前已经选择的成员
newSelectUserList = temporaryList;
} else {
newSelectUserList = temporaryList.concat(allUserList);
newSelectUserList = newSelectUserList.filter(item => !item.whetherExist);
}
this.setState({
selectUserList : newSelectUserList,
temporaryList: newSelectUserList
})
}
// 批量添加成员(点击确定)
addMember = () => {
const { selectUserList, instId } = this.state;
const { dataInfo, disk } = this.props;
const { id, createId } = dataInfo;
let rightsList = []; // 保存新加进去的成员
selectUserList.map((item) => {
rightsList.push({
rights: "LOOK_DOWNLOAD",
userId: item.userId
})
return rightsList
})
const _params = {
instId,
id,
disk,
createId,
rightsList,
}
Service.Apollo('public/apollo/addFileUserAuthorityList', _params).then((res) => {
if (res.code === '200') {
this.handleClose();
this.setState({
temporaryList: []
})
}
})
}
// 搜索成员
handleSearch = () => {
const { searchKey } = this.state;
this.getFileUserAuthority(searchKey);
}
// 搜索框值更改
handleChangeSearchKey = (e) => {
const { value } = e.target;
this.setState({
searchKey : value
});
}
// 关闭弹窗
handleClose = () => {
this.props.handleChooseModal();
}
// 信息列表-—左边
selectedColumnsLeft = () => {
const selectColumns = [
{
title: '全选', // 实际为头像,但在表格上这行要求显示为全选
key: 'avatar',
dataIndex: 'avatar',
width: '18%',
render: (value, record) => {
return (
<div className='avatar'>
{
value ?
[<img className='avatar-img' src={ value } key='avatar' alt=""/>]
: [<img className='avatar-img' src='https://image.xiaomaiketang.com/xm/dEyxDRKwFw.png' key='avatar' alt=""/>]
}
</div>
)
}
},{
title: '',
key: 'adminNameLeft',
dataIndex: 'adminName',
width: '40%',
render: (value, record) => {
const { adminName } = record;
return (
<span className='userImg'>
{ adminName }
</span>
)
}
}
]
return selectColumns
}
// 信息列表——右边
selectedColumnsRight = () => {
const selectColumns = [
{
title: '用户名',
key: 'adminNameRight',
dataIndex: 'adminName',
width: '40%',
render: (value, record) => {
const { adminName = '' } = record;
return (
<span className='userImg'>
{ adminName }
</span>
)
}
},{
title : '操作',
key : 'edit',
dataIndex : 'edit',
width : '20%',
align : 'right',
render: (value, record) => {
return (
<div className='edit' onClick={() => this.clearOneUser(record)}>
{/* <Icon type="close-circle-o" /> */}
<img src="https://image.xiaomaiketang.com/xm/sPxHWhs83R.png" className='edit-img' alt=""/>
</div>
)
}
},
]
return selectColumns;
}
render() {
const { isOpen } = this.props;
const { selectUserList, allUserList, searchKey, selectedRowKeys } = this.state;
const title = '添加成员';
return (
<div>
{/* 添加学员页面 */}
<Modal
visible={isOpen}
onCancel={this.handleClose}
onOk={this.addMember}
title={title}
width={560}
>
<div className='choose-container'>
{/* 拥有文件夹权限的成员列表 */}
<div className='container-left'>
<span className='container-left-header'>选择</span>
<div className='container-left-body'>
<Search
placeholder='请输入姓名'
value={searchKey}
onSearch={this.handleSearch}
onChange={this.handleChangeSearchKey}
className='search'
/>
<div className='container-left-body-table'>
<Table
rowKey={(record) => record.userId}
dataSource={allUserList}
columns={this.selectedColumnsLeft()}
pagination={false}
scroll={{ y: 216}}
// bordered={true}
size={'small'}
rowSelection={{
selectedRowKeys,
onChange : this.onChangeRow,
getCheckboxProps : this.getCheckboxProps,
onSelectAll : (selected, selectedRows, changeRows) => {
this.onSelectAll(selected, selectedRows, changeRows)
}
}}
/>
</div>
</div>
</div>
{/* 已选择的成员列表 */}
<div className='container-right'>
<span className='span-left'>
已选:
<span className={ (selectUserList.length > 0) ? 'span-left-l' : null }>
{selectUserList.length}
</span>
</span>
<div className='span-right' onClick={() => this.clearAllUser()}>
<span className={ (selectUserList.length > 0) ? 'span-right-l' : null }>清空</span>
</div>
<div className='container-right-body'>
<Table
rowKey={(record) => record.userId}
dataSource={selectUserList}
columns={this.selectedColumnsRight()}
showHeader={false}
scroll={{ y: 300 }}
pagination={false}
size={'small'}
/>
</div>
</div>
</div>
</Modal>
</div>
)
}
}
export default ChooseMembersModal;
\ No newline at end of file
.choose-container{
display: flex;
background-color: #fff !important;
.container-left{
width: 50%;
padding-right: 10px;
.container-left-header{
margin-bottom: 10px;
}
.container-left-body{
border: 1px solid #E9E9E9;
.search{
width: 250px;
padding: 7px 7px;
}
.container-left-body-table{
width: 252px;
height: 256px;
>table >thead >tr{
border-bottom:none;
}
.ant-table {
border: none;
min-height: 250px !important;
}
.ant-empty-normal {
margin: 100px 0 !important;
}
.avatar{
.avatar-img{
height: 23px;
width: 23px;
border-radius: 50%;
}
}
.ant-table-tbody{
>tr >td {
border-bottom:none;
}
}
.ant-table-row{
background-color: #fff !important;
}
.ant-table-row:hover{
background-color: #E9E9E9;
}
}
}
}
.container-right{
width: 50%;
height: 340px;
.span-left{
margin-left: 10px;
.span-left-l{
color: #FD9A6A;
cursor: pointer;
}
}
.span-right{
float: right;
margin-right: 10px;
.span-right-l{
color: #FD9A6A;
cursor: pointer;
}
}
.container-right-body{
margin: 10px;
.edit-img{
width: 16px;
height: 16px;
}
.ant-table {
min-height: 303px !important;
}
.ant-empty-normal {
margin: 100px 0 !important;
}
.ant-table-tbody{
>tr >td {
border-bottom:none;
}
>tr .ant-table-selection-column{
width: 30px !important;
}
.ant-table-row{
border: none !important;
background-color: #fff !important;
}
.ant-table-row:hover{
background-color: #e6e1e1;
}
}
}
}
}
\ No newline at end of file
/**
* @Author: wanghaofeng
* @date: 2020/11/19 10:25
* @Description:公共文件夹复制弹窗
*/
import React from 'react';
import { Modal, Button, Breadcrumb, Radio, message } from 'antd';
import Service from '@/common/js/service';
import { FILE_TYPE_ICON_MAP, DEFAULT_SIZE_UNIT } from "@/domains/class-book/constants";
import { LottieIcon } from 'xiaomai-b-components';
import _ from 'underscore';
import { getEllipsText } from "@/domains/basic-domain/utils";
import * as lodash from 'lodash';
import './CopyFileModal.less';
import User from '@/common/js/user';
window.currentUserInstInfo = {adminId: "1224977437688578050", adminName: "吴帆", gender: "UNKNOWN", parentId: "1224977437688578050", nickName: "吴帆", name: "5.0项目测试第七校区", instId: "1199660913171755009" }
const { instId } = window.currentUserInstInfo; // 机构Id
const folder_list_url = { // 文件夹url
'MYSELF': 'public/apollo/folderList',
'COMMON': 'public/apollo/commonFolderList'
}
const defaultQuery = { // 默认请求参数
size: 10,
current: 1,
folderIdType: 'FOLDER'
}
const defaultFolderPathList = [
{
disk : "COMMON",
folderName : "公共文件"
},
{
disk : "MYSELF",
folderName : "我的文件"
}
]
class CopyFileModal extends React.Component {
constructor(props) {
super(props);
this.state = {
folderPathList : [], // 文件储存路径
currentFolder : [], // 当前文件
folderList : [], // 信息列表
aid : localStorage.getItem('aid'), // aid
query : defaultQuery, // 初始化请求参数
totalCount : 0, // 但前页面加载数据总数
pages: 1, // 获取信息总页数,默认为1页
showLottieIcon: true,
disk: this.props.currentRootDisk.disk,
}
this.fileListRef = React.createRef();
}
componentWillMount() {
this.setState({
// 默认打开为公共文件夹
folderPathList : [defaultFolderPathList[0]],
})
}
componentDidMount() {
this.getFileList('COMMON');
}
// 关闭弹窗/取消复制
handleClose = () => {
this.setState({
folderPathList : [defaultFolderPathList[0]]
},() => {
this.props.onClose();
})
}
// 点击确认复制
confirmCopy = () => {
// const { folderPathList, folderList } = this.state;
const { folderPathList, disk } = this.state;
const { dataInfo } = this.props;
const { id, createId, folderName } = dataInfo;
const newParentId = folderPathList[folderPathList.length-1].id;
const _params = {
copyIds : [id], // 复制文件的ID
newParentId, // 新的父节点ID
instId, // 机构ID
disk, // 盘符
createId, // 创建者ID
}
// 判断目标文件夹是否是其自身或者是他的子文件夹
let allowCopy = 0;
folderPathList.map((item) => {
if (item.folderName === folderName) {
allowCopy = 1;
message.warning('不能将文件复制到自身或其子目录下')
}
return allowCopy;
})
if (allowCopy === 0) {
Service.Apollo('public/apollo/copyFolder', _params).then((res) => {
if (res.success) {
this.getFileList(disk, newParentId)
}
}).finally(() => {
message.success('复制成功');
})
}
}
// 点击面包屑
changeFolderPath = (folderPath) => {
const { folderPathList, folderList } = this.state;
const { disk } = folderPathList[0];
const { id } = folderPath;
// 当前页page设置为1
const query = _.clone(this.state.query);
query.current = 1;
this.setState({ query });
// 判断被点击文件夹是否存在父级文件夹
const _parentId = (folderList.length && folderList[0].parentId) ? id : null;
// 获取新的文件路径列表
let _folderPathList = JSON.parse(JSON.stringify(folderPathList));
folderPathList.map((item, index) => {
if (folderPath.folderName === item.folderName) {
_folderPathList.splice(index + 1);
}
return _folderPathList;
})
// 刷新数据
this.setState({
folderPathList : _folderPathList,
query
}, () => {
this.getFileList(disk, _parentId);
})
}
// 点击文件夹
handleSelect = (folder) => {
const { folderType, id, folderName } = folder;
const { folderPathList } = this.state;
const disk = folderPathList[0].disk;
let _folderPathList = JSON.parse(JSON.stringify(folderPathList));
const query = _.clone(this.state.query);
query.current = 1;
// 选择文件,直接返回
if (folderType !== 'FOLDER') return;
// 选择的是文件夹,更新文件路径
_folderPathList.push({
id,
folderName
});
this.setState({
folderPathList : _folderPathList,
query
},() => {
this.getFileList(disk, id, folderType);
})
}
// 获取信息列表
getFileList = (disk, parentId=null, folderType='FOLDER', hasMore = false) => {
const { current, size } = this.state.query;
const { folderPathList, folderList } = this.state;
// 如果不是文件夹,直接return
if (folderType !== 'FOLDER') return;
const _params = {
instId,
disk,
parentId,
current,
size,
folderIdType : folderType
}
// 是否切换盘符
if (folderPathList[0].disk !== disk){
if (disk === 'COMMON') {
this.setState({folderPathList : [defaultFolderPathList[0]]});
} else {
this.setState({folderPathList : [defaultFolderPathList[1]]});
}
}
this.setState({ disk });
Service.Apollo(folder_list_url[disk], _params).then((res) => {
if (res.result) {
if (hasMore) { // 是否是由滑动获取更多触发
const _folderList = JSON.parse(JSON.stringify(folderList)).concat(res.result.records);
this.setState({
pages : res.result.pages,
folderList : _folderList,
totalCount : res.result.total
})
} else {
this.setState({
pages : res.result.pages,
folderList : res.result.records,
totalCount : res.result.total
})
}
}
})
}
// 滑动加载更多
handleScrollEvent = () => {
const { folderList, totalCount, folderPathList, pages } = this.state;
// 判断是否还有更多信息
const hasMore = folderList.length < totalCount;
// 判断滑动到最下面
const hasReachBottom = (this.fileListRef.current.scrollTop + this.fileListRef.current.clientHeight) > this.fileListRef.current.scrollHeight - 1;
if (!hasReachBottom || !hasMore) return;
const query = _.clone(this.state.query);
if (query.current < pages) {
query.current = query.current + 1; // 修改当前页为下一页
const disk = folderPathList[0].disk;
const parentId = folderList[0].parentId;
this.setState({ query }, () => {
this.getFileList(disk, parentId, 'FOLDER', true);
})
}
}
render() {
const { isOpen } = this.props;
const { folderList, folderPathList, showLottieIcon } = this.state;
const title = '复制到';
return (
<Modal
visible={isOpen}
title={title}
width={560}
onCancel={this.handleClose}
footer={[
<Button key="back" onClick={this.handleClose}>
取消
</Button>,
<Button key="submit" type="primary" onClick={this.confirmCopy}>
复制到此目录
</Button>
]}
>
<div className='copy-header'>
<Radio.Group>
<Radio.Button value={'MYSELF'} onClick={() => this.getFileList('MYSELF')}>我的文件</Radio.Button>
<Radio.Button onClick={() => this.getFileList('COMMON')}>公共文件</Radio.Button>
</Radio.Group>
</div>
<div className='copy-body'>
{/* 面包屑 */}
<div className="bread-crumbs">
<Breadcrumb>
{
folderPathList.map((item, index) => {
return (
<Breadcrumb.Item key={index}>
{
// 判断是不是最后一项
(index !== folderPathList.length - 1) &&
<div
className='bread'
onClick={() => this.changeFolderPath(item)}
>
{item.folderName}
</div>
}
{
// 最后一项不给绑定点击事件
!(index !== folderPathList.length - 1) &&
<div>{item.folderName}</div>
}
</Breadcrumb.Item>
)
})
}
</Breadcrumb>
</div>
{/* 信息列表 */}
{
(folderList.length>0) &&
<div
onScrollCapture={() => this.handleScrollEvent()}
style={{ height: '320px', overflowY: 'scroll' }}
ref={this.fileListRef}
>
<div className='folder-list'>
{
folderList.map(item => {
const { folderType, folderFormat, folderSize } = item;
const isFolder = folderType === 'FOLDER'; // 是否是文件夹
let _size = `${(folderSize / DEFAULT_SIZE_UNIT).toFixed(1)}M`; // 文件大小
if (folderSize < 0.1 * DEFAULT_SIZE_UNIT) {
_size = `${(folderSize / 1000).toFixed(1)}kb`;
}
let imgSrc = !isFolder ? // 文件/文件夹图标
FILE_TYPE_ICON_MAP[folderFormat] :
'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594871430788.png';
return (
<div
className={`information ${isFolder ? 'enable' : 'disable'}`}
key={item.id}
onClick={lodash.debounce(() => this.handleSelect(item), 500)}
>
<img src={imgSrc} alt="file-item__img" height="24"/>
<span className='folderName'>{getEllipsText(item.folderName, 20)}</span>
{
isFolder &&
<div className="file-item__expend-icon">
<span className="icon iconfont">&#xe71b;</span>
</div>
}
{ !isFolder && <div className="file-item__size">{_size}</div> }
</div>
)
})
}
</div>
</div>
}
{
!(folderList.length>0) && showLottieIcon &&
<LottieIcon
title={<span className="desc">此文件夹下无可用文件</span>}
/>
}
</div>
</Modal>
)
}
}
export default CopyFileModal;
.copy-header{
margin-left: 34%;
}
.copy-body{
.bread:hover{
color: #FD9A6A;
}
}
.folder-list{
margin-top: 10px;
height: 260px;
// overflow-y: scroll;
border-top: 1px solid #E8E8E8;
border-top-left-radius:4px;
border-top-right-radius:4px;
.enable{
cursor: pointer;
}
.disable{
cursor: not-allowed;
opacity: 0.5;
}
.information{
display: flex;
height: 50px;
border: 1px solid #E8E8E8;
border-top: none;
padding-top: 12px;
padding-left: 25px;
position: relative;
.folderName{
padding-left: 15px;
}
.file-item__expend-icon{
position: absolute;
right: 20px;
.iconfont {
color: #BFBFBF;
}
}
.file-item__size{
position: absolute;
right: 20px;
}
}
}
.lottie-icon{
margin: 100px 0;
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-17 16:17:10
* @Description: 添加直播-简介
*/
import React from 'react';
import { Input, message, Upload } from 'antd';
import _ from 'underscore';
import { randomString } from '@/domains/basic-domain/utils';
import Service from '@/common/js/service';
import EditorBox from '../components/EditorBox';
import './Introduction.less';
const { TextArea } = Input;
class Introduction extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
// 删除简介
handleDeleteIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.splice(index, 1);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 上移简介
handleMoveUpIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index];
const nextItem = liveCourseMediaRequests[index + 1];
liveCourseMediaRequests.splice(index, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
// 下移简介
handleMoveDownIntro = (index) => {
const { liveCourseMediaRequests } = this.props.data;
const prevItem = liveCourseMediaRequests[index - 1];
const nextItem = liveCourseMediaRequests[index];
liveCourseMediaRequests.splice(index - 1, 2, nextItem, prevItem);
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
renderLittleIcon = (index) => {
const { liveCourseMediaRequests } = this.props.data;
return (
<div className="little-icon">
<span
className="icon iconfont close"
onClick={() => { this.handleDeleteIntro(index); }}
></span>
{
index > 0 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveDownIntro(index); }}
>&#xe6d1;</span>
}
{
index !== liveCourseMediaRequests.length - 1 &&
<span
className="icon iconfont"
onClick={() => { this.handleMoveUpIntro(index); }}
>&#xe6cf;</span>
}
</div>
)
}
handleChangeIntro = (index, value) => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests[index].mediaContent = value;
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
handleAddIntroText = () => {
const { liveCourseMediaRequests } = this.props.data;
liveCourseMediaRequests.push({
mediaType: 'TEXT',
mediaContent: '',
key: randomString(16)
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
handleUpload = (Blob) => {
// 最多添加九图片
const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType === "PICTURE";
});
if (list.length > 8) {
message.warning("最多添加9张图片");
return;
}
const { instId } = window.currentUserInstInfo;
const { name, size } = Blob;
const resourceName = randomString(16) + name.slice(name.lastIndexOf('.'));
const params = {
resourceName,
accessTypeEnum: 'PUBLIC',
bizCode: 'LIVE_COURSE_MEDIA',
instId: instId,
}
Service.Apollo("public/apollo/commonOssAuthority", params).then((res) => {
const { resourceId } = res.result;
const signInfo = res.result;
// 构建上传的表单
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("OSSAccessKeyId", signInfo.accessId);
formData.append("policy", signInfo.policy);
formData.append("callback", signInfo.callback);
formData.append("Signature", signInfo.signature);
formData.append("key", signInfo.key);
formData.append("file", Blob);
formData.append("success_action_status", 200);
xhr.open("POST", signInfo.host);
xhr.onload = () => {
liveCourseMediaRequests.push({
size,
mediaName: name,
mediaContent: resourceId,
mediaType: 'PICTURE',
mediaUrl: window.URL.createObjectURL(Blob),
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
};
xhr.onerror = () => {
xhr.abort();
};
xhr.send(formData);
this.setState({ xhr })
});
}
componentWillUnmount() {
const { xhr } = this.state;
xhr && xhr.abort();
}
render() {
const {
iveScene,
data: {
introduction,
label = '直播课简介',
liveCourseMediaRequests = []
},
} = this.props;
// 是否是互动班课
const isInteractive = iveScene === 'interactive';
return (
<div className="add-live__intro-info">
<span className="lable">{label}</span>
<div className="content">
{
isInteractive &&
<TextArea
value={introduction}
placeholder="简单介绍下这次课吧"
maxLength={200}
style={{ width: 480 }}
onChange={(e) => { this.props.onChange('introduction', e.target.value) }}
/>
}
<If condition={!isInteractive}>
<div className="intro-list">
{
liveCourseMediaRequests.map((item, index) => {
if (item.mediaType === 'TEXT') {
return (
<div className="intro-list__item" key={item.key}>
<EditorBox
detail={{
content: item.mediaContent
}}
onChange={(val) => { this.handleChangeIntro(index, val)}}
/>
{ this.renderLittleIcon(index) }
</div>
)
}
if (item.mediaType === 'PICTURE') {
return (
<div className="intro-list__item picture" key={index}>
<div className="img__wrap">
<img src={item.mediaUrl} alt="introImg" />
</div>
{ this.renderLittleIcon(index) }
</div>
)
}
return item;
})
}
</div>
<div className="operate">
<div className="operate__item" onClick={this.handleAddIntroText}>
<span className="icon iconfont">&#xe760;</span>
<span className="text">文字</span>
</div>
<Upload
fileList={[]}
accept="image/jpeg, image/png, image/jpg, image/gif"
beforeUpload={(Blob) => {
this.handleUpload(Blob);
return false;
}}
>
<div className="operate__item">
<span className="icon iconfont">&#xe74a;</span>
<span className="text">图片</span>
</div>
</Upload>
</div>,
<div className="tips">
• 图片支持jpeg、jpg、png、gif格式
</div>
</If>
</div>
</div>
)
}
}
export default Introduction;
.add-live__intro-info {
display: flex ;
.label {
padding-top: 12px;
}
textarea.ant-input {
min-height: 80px;
}
.intro-list__item {
display: flex;
margin-bottom: 16px;
position: relative;
&.picture {
width: 550px;
padding: 16px;
border: 1px solid #EEE;
border-radius: 4px;
.img__wrap {
width: 299px;
height: 168px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
.little-icon {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
right: -20px;
.iconfont {
width: 20px;
height: 20px;
line-height: 20px;
font-size: 20px;
color: #999;
margin-bottom: 4px;
cursor: pointer;
&.close {
margin-top: 8px;
background-image: url('https://image.xiaomaiketang.com/xm/eesMPhNP3e.png');
background-size: 100% 100%;
}
}
}
}
.operate {
display: flex;
align-items: center;
justify-content: center;
width: 550px;
height: 80px;
line-height: 80px;
padding: 16px;
margin-top: 16px;
border: 1px dashed #EBEBEB;
border-radius: 4px;
.ant-upload {
vertical-align: middle;
}
&__item {
display: flex;
flex-direction: column;
cursor: pointer;
&:not(:last-child) {
margin-right: 82px;
}
.iconfont {
font-size: 22px;
line-height: 22px;
color: #BFBFBF;
text-align: center;
}
.text {
color: #999;
line-height: 20px;
margin-top: 4px;
}
}
}
.tips {
color: #999;
margin-top: 16px;
}
}
\ No newline at end of file
/**
* @Author: wanghaofeng
* @date: 2020/11/14 17:42
* @Description:权限管理弹窗
*/
import React from 'react';
import { Modal, Table, Dropdown, Menu, Tooltip, message } from 'antd';
import Service from '@/common/js/service';
import ChooseMembersModal from './ChooseMembersModal';
import _ from 'underscore';
import './ManagingMembersModal.less';
class ManagingMembersModal extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource : [], // 数据源
addManagingMember: false, // 是否点击了添加成员
instId : window.currentUserInstInfo.instId, // 机构Id
iconRotateList: [],
userAuthority : ['可编辑', '可查看/下载', '仅可查看', '', '创建者', '主账号'] , // 空 代表删除
}
}
componentDidMount() {
this.setState({
showLoading: true
})
this.getFileUserAuthority();
}
// 获取成员列表
getFileUserAuthority = () => {
const { instId } = this.state;
const { dataInfo } = this.props;
const { id } = dataInfo;
const _params = {
instId,
id
}
Service.Apollo('public/apollo/getFileUserAuthority', _params).then((res) => {
if(res.result) {
// 数据排序,文件创建者在最前,并修改文案为所有者
let _selectUserList = [], _dataSource = [], flag = [], _iconRotateList = [];
// 列表排序,文件夹创建者和机构主账号默认排在最前
res.result.map((item, index) => {
if (item.fileUserEnum === "CREATE" || item.fileUserEnum === 'PRIMARY') {
_dataSource.push(item);
flag.push(index);
} else {
_iconRotateList.push({ userId: item.userId, iconRotate: false});
}
return _dataSource;
})
_dataSource = _.uniq(_dataSource.concat(res.result));
this.setState({
dataSource : _dataSource, // 所有成员
selectUserList : _selectUserList, // 已拥有该文件权限的成员
iconRotateList: _iconRotateList
})
}
})
}
// 修改成员权限
updateFileUserAuthority = (params, newRights = 1) => {
const rightList = ['EDIT', 'LOOK_DOWNLOAD', ''];
let { iconRotateList } = this.state;
let _params = params;
_params.rights = rightList[newRights];
// 展开图标
if (_params.currentRight === rightList[newRights]) {
iconRotateList.map(item => {
if (item.userId === _params.userId) {
item.iconRotate = false;
}
return iconRotateList;
})
this.setState({ iconRotateList });
return;
}
delete _params.currentRight;
// 此接口只返回成功与失败,没有其他数据
Service.Apollo('public/apollo/updateFileUserAuthority', _params).then((res) => {
this.getFileUserAuthority()
message.success(res.message);
})
}
// 移除成员二次确认弹窗
removeUser = (params, _rights = 1) => {
const rightList = ['EDIT', 'LOOK_DOWNLOAD', ''];
let _params = params;
_params.rights = rightList[_rights];
Modal.confirm({
title: '移除',
content: '确定要移除该成员',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
Service.Apollo('public/apollo/updateFileUserAuthority', _params).then((res) => {
if (res.code === '200') {
message.success('删除成功');
this.getFileUserAuthority();
}
})
}
});
}
// 关闭权限管理弹窗
handleClose = () => {
this.props.onClose();
}
// 旋转下拉图标
changeIconRotate = (record) => {
let { iconRotateList } = this.state;
iconRotateList.map(item => {
if (item.userId === record.userId) {
item.iconRotate = !item.iconRotate;
}
return iconRotateList
})
this.setState({
iconRotateList
})
}
// 点击添加成员按钮
handleChooseModal = () => {
this.setState({
isOpen : !this.state.isOpen,
addManagingMember : !this.state.addManagingMember
},() => {
this.getFileUserAuthority();
})
}
// 管理成员下拉框
renderMenu = (record) => {
let { dataSource } = this.state;
const { dataInfo } = this.props;
const { folderType, id } = dataInfo;
const updateId = dataSource.filter(item => item.fileUserEnum === "CREATE")[0].userId;
const params = {
instId : this.state.instId, // 机构Id
id : id, // 文件ID
userId : record.userId, // 被修改权限用户ID
updateId, // 文件夹创建者ID
currentRight: record.rights // 当前权限
};
const { userAuthority } = this.state;
const menu = (
<Menu>
<Menu.Item key="EDIT">
<div onClick={() => this.updateFileUserAuthority(params, 0)} >
{userAuthority[0]}
<br />
{
(folderType === 'FILE') ?
[<span className='menu-bottom' key='EDIT'>上传/下载/删除/复制/<br />移动/重命名</span>] :
[<span className='menu-bottom' key='EDIT'>创建/上传/下载/删除/<br />复制/移动/重命名</span> ]
}
</div>
</Menu.Item>
<Menu.Item key="DOWNLOADING">
<div onClick={() => this.updateFileUserAuthority(params, 1)}>
{userAuthority[1]}
<br />
<span className='menu-bottom'>下载、复制</span>
</div>
</Menu.Item>
{/* <Menu.Item key="LOOK" >
<div onClick={() => this.updateFileUserAuthority(params, 2)}>仅可查看</div>
<span className='menu-bottom'>{userAuthority[2]}</span>
</Menu.Item> */}
<Menu.Divider key='LINE'/>
<Menu.Item key="REMOVE" >
<div className='remove' onClick={() => this.removeUser(params, userAuthority[3])}>移除</div>
</Menu.Item>
</Menu>
)
return menu;
}
// 表格信息列表格式
selectedColumns = () => {
const selectColumns = [
{
title : '头像',
key : 'avatar',
dataIndex : 'avatar',
width : '13%',
align : 'right',
render : (value, record) => {
return (
<div className='avatar'>
{
value ?
[<img className='avatar-img' src={ value } key='avatar' alt=""/>]
: [<img className='avatar-img' src='https://image.xiaomaiketang.com/xm/dEyxDRKwFw.png' key='avatar' alt=""/>]
}
</div>
)
}
},{
title : '用户名',
key : 'adminName',
dataIndex : 'adminName',
width : '40%',
align : 'left',
render : (value, record) => {
return (
<span className='adminName'>
{ value }
</span>
)
}
},{
title : '权限',
key : 'rights',
dataIndex : 'rights',
width : '40%',
align :'right',
render : (value, record) => {
const imgSrcList = ['https://image.xiaomaiketang.com/xm/Th5cKisf6f.png', 'https://image.xiaomaiketang.com/xm/nzAcc6ryTp.png'];
let num = 0, imgSrc = imgSrcList[0];
const { iconRotateList } = this.state;
iconRotateList.map(item => {
if (item.userId === record.userId) {
imgSrc = !item.iconRotate ? imgSrcList[0] : imgSrcList[1];
}
return imgSrc;
})
// 判断不同权限的人所能看到的
if (record.fileUserEnum === "CREATE") { // 创建者
num = 4;
} else if (record.fileUserEnum === 'PRIMARY') { // 主账号
num = 5;
} else {
if (record.rights === "LOOK_DOWNLOAD") {
num = 1;
} else if (record.rights === "EDIT") {
num = 0;
}
}
return (
<div className='rights'>
{
(num === 4 || num === 5) && <span>{this.state.userAuthority[num]}</span>
}
<Dropdown
overlay={this.renderMenu(record)}
trigger={['click']}
onVisibleChange={() => this.changeIconRotate(record)}
>
<span>
{
num < 4 &&
[
<span key={num}>{this.state.userAuthority[num]}</span>,
<img className='img-type' src={imgSrc} key={imgSrc} alt=""/>
]
}
</span>
</Dropdown>
</div>
)
}
}
]
return selectColumns;
}
render() {
const { isOpen, disk } = this.props;
const { addManagingMember, dataSource } = this.state;
const title = '权限管理';
return (
<div>
{
// 管理学员页面
!addManagingMember &&
<Modal
footer={null}
visible={isOpen}
onCancel={this.handleClose}
title={title}
width={560}
>
<div className='managing-header'>
<div className='managing-left'>
已有成员
<Tooltip title="不在名单内的员工不可查看此文件/文件夹" arrowPointAtCenter>
{/* <Icon type="info-circle-o" /> */}
<img src="https://image.xiaomaiketang.com/xm/XWy5riGJTi.png" className='tool-tip-left' alt=""/>
</Tooltip>
</div>
<div className='managing-right' onClick={ () => this.handleChooseModal() }>
{/* <Icon type="plus-circle" /> */}
<img src="https://image.xiaomaiketang.com/xm/xkZs3C6G5R.png" className='tool-tip-right' alt=""/>
添加成员
</div>
</div>
<div className='managing-body'>
<Table
// bordered={true}
columns={this.selectedColumns()}
dataSource={dataSource}
rowKey={(record, index) => record.userId}
showHeader={false}
size={'middle'}
scroll={{ y : 300 }}
pagination={false}
/>
</div>
</Modal>
}
{
// 选择成员页面
addManagingMember &&
<ChooseMembersModal
isOpen={addManagingMember}
handleChooseModal={this.handleChooseModal}
dataInfo={this.props.dataInfo}
disk={disk}
/>
}
</div>
)
}
}
export default ManagingMembersModal;
\ No newline at end of file
.managing-header{
// width: 100%;
height: 30px;
.managing-left{
float: left;
margin-left: 10px;
.tool-tip-left{
cursor: pointer;
margin-left: 4px;
width: 14px;
height: 14px;
}
}
.managing-right{
float: right;
color: #FD9A6A;
margin-right: 10px;
cursor: pointer;
.tool-tip-right{
margin-right: 4px;
width: 14px;
height: 14px;
}
}
}
.managing-body{
height: 300px;
overflow: hidden;
border: 1px solid #E9E9E9;
border-radius: 10px;
.ant-table-tbody{
.ant-table-row{
background-color: #fff !important;
}
.ant-table-row:hover{
background-color: #E9E9E9;
}
}
.avatar{
.avatar-img{
height: 30px;
width: 30px;
border-radius: 50%;
}
}
.rights{
.img-type{
height: 18px;
width: 18px;
}
}
}
.menu-bottom{
font-size: 6px !important;
color: #AAAAAA !important;
}
.remove-modal{
font-size: 14px !important;
color: #AAAAAA !important;
height: 30px;
}
/*
* @Author: 吴文洁
* @Date: 2020-06-16 11:41:43
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-06 17:33:47
* @Description: 文件超出大小的限制
*/
import React from 'react';
import { Modal } from 'antd';
import { DEFAULT_SIZE_UNIT } from "@/domains/class-book/constants";
import './NonCompliantFileModal.less';
class NonCompliantFileModal extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
const { isOpen, fileList } = this.props;
return (
<Modal
title="文件超出可上传的大小限制"
visible={isOpen}
okText="继续上传"
cancelText="放弃上传"
onCancel={this.props.onClose}
onOk={this.props.onOk}
className="prepare-lesson__non-compliant-file-modal"
>
<div className="file-list">
<div className="file-list__title">以下文件已超出上传限制,无法上传。</div>
<div className="file-list__content">
{
fileList.map((file, index) => {
let _size = `${(file.size / DEFAULT_SIZE_UNIT).toFixed(1)}M`;
if (file.size < 0.1 * DEFAULT_SIZE_UNIT) {
_size = `${(file.size / 1000).toFixed(1)}kb`;
}
return (
<div className="file-list__item" key={`file-list__item${index}`}>
<span className="file-name">{file.name}</span>
<span className="file-size">{_size}</span>
</div>
)
})
}
</div>
</div>
<div className="tips">
<span className="icon iconfont">&#xe6f2;</span>
<span className="tips__text">
支持上传 Word(100M以内)、Excel(10M以内)、PPT(100M以内)、PDF(100M以内)、图片(50M以内)、音频(50M以内)、视频(500M以内)。
</span>
</div>
</Modal>
)
}
}
export default NonCompliantFileModal;
.prepare-lesson__non-compliant-file-modal {
.tips {
margin-top: 16px;
display: flex;
.iconfont {
margin-right: 4px;
font-size: 13px;
color: #FFB74C;
}
&__text {
color: #999;
}
}
.file-list {
&__title {
color: #666;
}
&__content {
max-height: 120px;
margin-top: 12px;
padding: 17px;
border: 1px solid #E8E8E8;
border-radius: 4px;
overflow: scroll;
.file-list__item span {
color: #333;
}
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-23 14:54:16
* @LastEditors: 吴文洁
* @LastEditTime: 2020-08-24 13:05:09
* @Description: 大班直播课预览弹窗
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Modal } from 'antd';
import moment from 'moment';
import './PreviewCourseModal.less';
class PreviewCourseModal extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
dealWithTime = (startTime, endTime) => {
const startDate = new Date(Number(startTime));
const endDate = new Date(Number(endTime));
const year = startDate.getFullYear();
const month = (startDate.getMonth() + 1) < 10 ? `0${startDate.getMonth() + 1}` : startDate.getMonth() + 1;
const day = startDate.getDate() < 10 ? `0${startDate.getDate()}` : startDate.getDate();
const startHour = startDate.getHours() < 10 ? `0${startDate.getHours()}` : startDate.getHours();
const startMinute = startDate.getMinutes() < 10 ? `0${startDate.getMinutes()}` : startDate.getMinutes();
const endHour = endDate.getHours() < 10 ? `0${endDate.getHours()}` : endDate.getHours();
const endMinute = endDate.getMinutes() < 10 ? `0${endDate.getMinutes()}` : endDate.getMinutes();
const liveDateStr = `${year}-${month}-${day}`;
const startTimeStr = `${startHour}:${startMinute}`;
const endTimeStr = `${endHour}:${endMinute}`;
return {
liveDateStr,
startTimeStr,
endTimeStr,
};
}
render() {
const { courseBasinInfo, courseClassInfo = {}, courseIntroInfo, type } = this.props;
const { coverUrl, courseName, scheduleVideoUrl } = courseBasinInfo;
const { liveDate, timeHorizonStart, timeHorizonEnd, nickname } = courseClassInfo;
const { liveCourseMediaRequests } = courseIntroInfo;
let liveDateStr, startTimeStr, endTimeStr;
if (liveDate) {
const _liveDate = moment(liveDate).format("YYYY-MM-DD");
const _timeHorizonStart = moment(timeHorizonStart).format('HH:mm');
const _timeHorizonEnd = moment(timeHorizonEnd).format('HH:mm');
const startTime = moment(_liveDate + ' ' + _timeHorizonStart).format('x');
const endTime = moment(_liveDate + ' ' + _timeHorizonEnd).format('x');
const {
liveDateStr: _liveDateStr,
startTimeStr: _startTimeStr,
endTimeStr: _endTimeStr
} = this.dealWithTime(startTime, endTime);
liveDateStr = _liveDateStr;
startTimeStr = _startTimeStr;
endTimeStr = _endTimeStr;
}
return (
<Modal
title="预览"
visible={true}
width={680}
onCancel={this.props.close}
footer={null}
className="preview-live-course-modal"
>
<div className="container__wrap">
<div className="container">
<div className="container__header">
<Choose>
<When condition={type === 'videoCourse'}>
<video
controls
src={scheduleVideoUrl}
poster={coverUrl ? coverUrl : `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`}
className="course-url"
/>
</When>
<Otherwise>
<img src={coverUrl} className="course-cover" alt="course-cover" />
</Otherwise>
</Choose>
</div>
<Choose>
<When condition={type === 'videoCourse'}>
<div className="container__body">
<div className="title__name">{courseName}</div>
<div className="title__inst-name">{window.currentUserInstInfo.name}</div>
</div>
</When>
<Otherwise>
<div className="container__body">
<div className="container__body__title">
<div className="title__name">{courseName}</div>
<div className="title__state">待开课</div>
</div>
<div className="container__body__time">
<span className="time__label">上课时间:</span>
<span className="time__value">
{
liveDate && timeHorizonStart && timeHorizonEnd &&
[
<span>{liveDateStr}&nbsp;</span>,
<span>{startTimeStr}~{endTimeStr }</span>
]
}
</span>
</div>
<div className="container__body__teacher">
<span className="teacher__label">上课老师:</span>
<span className="teacher__value">{nickname}</span>
</div>
</div>
</Otherwise>
</Choose>
<div className="container__introduction">
<div className="container__introduction__title">直播简介</div>
<div className="container__introduction__list editor-box">
{
liveCourseMediaRequests.map((item, index) => {
if (item.mediaType === 'TEXT') {
return (
<div
className="intro-item text"
dangerouslySetInnerHTML={{
__html: item.mediaContent
}}
/>
)
}
if (item.mediaType === 'PICTURE') {
return (
<div className="intro-item picture">
<img src={item.mediaUrl} alt="intro-item__picture" />
</div>
)
}
return item;
})
}
</div>
</div>
</div>
</div>
</Modal>
)
}
}
export default PreviewCourseModal;
.preview-live-course-modal {
.ant-modal-body {
background-image: url('https://image.xiaomaiketang.com/xm/xZWdziTCAf.png');
background-size: 100% 100%;
}
.container__wrap {
width: 340px;
height: 618px;
padding: 67px 46px 48px 47px;
margin: auto;
background-image: url('https://image.xiaomaiketang.com/xm/DHMzHiGc2E.png');
background-size: 100% 100%;
}
.container {
overflow: scroll;
height: 100%;;
.course-cover, .course-url {
width: 100%;
height: 141px;
background: #000;
}
&__body {
background-color: #FFF;
padding: 7px 0 11px 0;;
.title__name {
color: #000;
}
.title__inst-name {
color: #666;
font-size: 12px;
margin-top: 4px;
}
&__title {
display: flex;
justify-content: space-between;
.title__name {
line-height: 20px;
color: #000;
font-weight: 500;
}
.title__state {
min-width: 40px;
height: 17px;
padding: 0 2px;
margin-left: 6px;
font-size: 12px;
color: #FFF;
background-color: #34B88B;
}
}
&__time, &__teacher {
display: flex;
align-items: center;
margin-top: 8px;
color: #999;
line-height: 17px;
span {
font-size: 12px;
}
}
&__teacher {
margin-top: 4px;
}
}
&__introduction {
margin-top: 10px;
padding: 12px 0;
position: relative;
&::before {
content: '';
position: absolute;
top: -12px;
left: 0;
width: 100%;
height: 10px;
background-color: #F4F6FA;
}
&__title {
font-size: 12px;
color: #333;
text-align: center;
line-height: 17px;
position: relative;
&::before,
&::after {
position: absolute;
content: "";
top: 8px;
width: 13px;
height: 1px;
background-color: #ccc;
}
&::before {
left: 32%;
}
&::after {
right: 32%;
}
}
&__list {
margin-top: 12px;
.intro-item:not(:first-child) {
margin-top: 13px;
}
.text {
color: #666;
line-height: 17px;
p {
font-size: 12px;
}
}
.picture {
img {
width: 100%;
}
}
}
}
}
}
\ No newline at end of file
<!--
* @Author: 吴文洁
* @Date: 2020-04-29 16:54:52
* @LastEditors: 吴文洁
* @LastEditTime: 2020-04-29 17:15:16
* @Description:
-->
### 业务组件,比如某几个页面公用的组件,和实际业务相关
**本目录的引用别名是 @/bu-components**
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-06-10 13:54:01
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-23 09:33:21
* @Description:选择备课文件弹窗
*/
import React from 'react';
import { Modal, Button, Radio, Checkbox, message, Tooltip } from 'antd';
import _ from 'underscore';
import * as lodash from 'lodash';
import { LottieIcon } from 'xiaomai-b-components';
import Service from '@/common/js/service';
import { getEllipsText } from "@/domains/basic-domain/utils";
import { getFileTypeByName } from '@/domains/class-book/utils';
import {
DEFAULT_SIZE_UNIT,
FILE_TYPE_ICON_MAP,
NON_COMPLIANT_FILE_MAP,
LOCAL_FILE_TYPE_MAP
} from "@/domains/class-book/constants";
import UploadProgressModal from '@/bu-components/UploadProgressModal';
import NonCompliantFileModal from './NonCompliantFileModal';
import './SelectPrepareFileModal.less';
const defaultQuery = {
size: 10,
current: 1,
folderIdType: 'FOLDER'
}
const defaultRootDisk = {
folderName: '我的文件',
disk: 'MYSELF',
uploadPower: false
}
const FOLDERLIST_URL_MAP = {
'MYSELF': 'public/apollo/folderList',
'COMMON': 'public/apollo/commonFolderList',
'EMPLOYEE': 'public/apollo/employeeFolderList'
};
// 支持本地上传的文件类型
const localFileTypeMap = LOCAL_FILE_TYPE_MAP.join(',');
// 合法的文件名后缀
const FILE_SUFFIX_LIST = ['docx', 'doc', 'ppt', 'pptx', 'pdf', 'xlsx', 'xls', 'jpg', 'jpeg', 'png', 'mp3', 'mp4'];
// 禁止选择的文件类型
const DISABLE_FILE_FORMAT = ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
// 最多可以选择20个文件
const MAX_SELECT_LENGTH = 20;
let count = 0;
class SelectPrepareFileModal extends React.Component {
constructor(props) {
super(props);
this.state = {
targetFolder: {}, // 当前选中的目录
currentRootDisk: {}, // 当前所在根目录
uploadFolderPath: {}, // 上传文件的目录,防止中途切换文件夹
folderList: [], // 文件列表
folderPathList: [], // 文件路径列表
selectedFileList: [], // 已勾选的文件
nonCompliantFileList: [], // 超出限制的文件列表
query: defaultQuery, // 初始请求参数
showUploadModal: false, // 上传进度弹窗
showNonCompliantFileModal: false,
}
}
componentWillReceiveProps(nextProps) {
const { diskList = [], currentRootDisk} = nextProps;
if (nextProps.isOpen) {
const _currentRootDisk = diskList[0] || currentRootDisk || defaultRootDisk;
this.setState({
query: defaultQuery,
currentRootDisk: _currentRootDisk,
folderPathList: [_currentRootDisk],
}, () => {
this.handleFetchFolderList();
});
}
}
// 获取文件列表
handleFetchFolderList = (params = {}, loadMore = false) => {
// 根据当前根节点获取文件列表
const { selectType } = this.props;
const { query, folderList, currentRootDisk } = this.state;
const { instId } = window.currentUserInstInfo;
const _params = {
...query,
...params,
instId: instId,
disk: params.disk || currentRootDisk.disk,
}
if (selectType === 'video') {
_params.fileType = 'VEDIOFILE';
}
Service.Apollo(FOLDERLIST_URL_MAP[currentRootDisk.disk], _params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
folderList: loadMore ? [...folderList, ...records] : records,
totalCount: Number(total)
});
});
}
// 修改目录
handleChangeFolder = (folder) => {
this.setState({
query: defaultQuery,
folderPathList: [folder],
currentRootDisk: folder,
selectedFileList: [], // 切换目录不保存之前已勾选的记录
}, () => {
this.handleFetchFolderList({ disk: folder.disk });
});
}
// 选择文件/文件夹
handleSelect = (folder) => {
// 如果是文件,直接return, 否则进入下一级
const { operateType = 'select', scene, selectedFileList = [] } = this.props;
const { folderType, folderName, folderFormat } = folder;
if (folderType === 'FOLDER') {
this.handleSelectFolder(folder);
}
const suffix = _.last(folderName.split('.')).toLowerCase();
// 文件是否已经被关联了
const hasRelation = _.find(selectedFileList, (item) => item.resourceId === folder.resourceId);
// 文件禁止点击的情况(移动、直播场景下文件为Excel、文件已经被关联了、文件后缀不合法)
const disabled = operateType === 'move' ||
(scene === 'liveCourse' && folderFormat === 'EXCEL') ||
!!hasRelation || !FILE_SUFFIX_LIST.includes(suffix);
if (folderType === 'FILE' && !disabled) {
this.handleSelectFile(folder);
}
}
// 选择文件夹
handleSelectFolder = (folder) => {
const { folderPathList, currentRootDisk } = this.state;
// 更新文件路径
folderPathList.push({
id: folder.id,
folderName: folder.folderName
});
this.setState({
query: {
...this.state.query,
current: 1
},
folderPathList,
targetFolder: folder
}, () => {
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 2;
// 根据父级目录id获取文件夹列表
this.handleFetchFolderList({
parentId: folder.id,
folderIdType: employeeDisk ? 'USER' : 'FOLDER'
});
});
}
// 选择文件
handleSelectFile = (file) => {
const { selectedFileList } = this.state;
const index = _.findIndex(selectedFileList, (item) => item.resourceId === file.resourceId);
// 如果文件没有被勾选,则勾选
if (index < 0) {
selectedFileList.push(file);
} else { // 如果文件已经被勾选了,则取消勾选
selectedFileList.splice(index, 1);
}
this.setState({ selectedFileList });
}
handleSelectFileDone = () => {
const { selectedFileList } = this.state;
const {
selectedFileList: prevSelectedFileList = [],
maxSelectLength = MAX_SELECT_LENGTH
} = this.props;
const currSelectedFileList = [...(prevSelectedFileList || []), ...selectedFileList];
// 最多选择20个文件,判断是否超出了20个
if (currSelectedFileList.length > maxSelectLength) {
// 最多还可选择的个数 = 总量(20) - 已经选择的个数(prevSelectedFileList)
message.warning(`最多可选择${maxSelectLength - prevSelectedFileList.length}个文件`);
return;
}
const addFolderIds = _.pluck(selectedFileList, 'id');
this.props.onSelect(addFolderIds, selectedFileList);
// 上传完成之后将selectedFileList置为空,上传进度弹窗也一起消失
this.setState({
showUploadModal: false,
selectedFileList: []
})
}
// 前往特定的文件夹
handleChangeCurrentFolder = (folder, index) => {
// 请求接口,获取当前folder下所有的文件
const { folderPathList, currentRootDisk } = this.state;
if (folderPathList.length === 1) return;
folderPathList.splice(index+1, folderPathList.length);
const targetFolder = folderPathList[folderPathList.length - 1];
const { id = null } = targetFolder;
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 2;
this.setState({
targetFolder,
folderPathList
}, () => {
this.handleFetchFolderList({
parentId: id,
disk: currentRootDisk.disk,
folderIdType: employeeDisk ? 'USER' : 'FOLDER'
})
});
}
// 移动到此目录
handleMoveToTargetFolder = () => {
const { targetFolder, folderPathList, folderList} = this.state;
const query = {
targetFolder,
selectFolderPathList: folderPathList,
selectFolderList: folderList
}
// 请求接口,完成之后刷新文件列表
this.props.onMove(query);
}
// 滑动加载更多
handleScrollEvent = () => {
const { folderList, totalCount, currentRootDisk, folderPathList } = this.state;
const hasMore = folderList.length < totalCount;
const { fileListRef } = this.refs;
const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight === fileListRef.scrollHeight;
if (!hasReachBottom || !hasMore) return;
const currentFolder = folderPathList[folderPathList.length - 1];
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 2;
const query = _.clone(this.state.query);
query.current = query.current + 1;
this.setState({ query }, () => {
this.handleFetchFolderList({
disk: currentRootDisk.disk,
folderIdType: employeeDisk ? 'USER' : 'FOLDER',
parentId: currentFolder.id
}, true);
})
}
// 取消选择
handleClose = () => {
this.setState({
query: defaultQuery,
selectedFileList: []
}, () => {
this.props.onClose();
})
}
handleChooseFile = async () => {
// 校验是否已经欠费
const { instId } = window.currentUserInstInfo;
const res = await Service.Business("public/liveAssets/query", {
instId,
});
// balance小于等于0表示已经欠费,旗舰版用户不需要校验余额
const ultimateRes = await Service.Business('public/inst/checkInstProduct', {
instId,
productCodeList: ['ULTIMATESELL', 'PIP_TO_ULTIMATE', 'HIGH_TO_ULTIMATE']
});
const { result: { balance } } = res;
if (balance <= 0 && !ultimateRes.result) {
this.handleShowNoticeModal(balance);
return;
}
const dom = document.querySelector('#detailFileInput');
dom.click();
}
// 上传文件
handleUpload = (event) => {
const { selectType } = this.props;
const fileList = event.target.files;
// 判断文件的大小是否超出了限制
const nonCompliantFileList = [];
const _fileList = [...fileList];
_fileList.map((file, index) => {
let { size, type, name } = file;
if (!type) {
type = getFileTypeByName(name);
}
if (type.indexOf('image') > -1 && size > 50 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (type.indexOf('audio') > -1 && size > 50 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (type.indexOf('video') > -1 && size > 500 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (localFileTypeMap.indexOf(type) > -1 && size > 100 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
file.key = count++;
return file;
});
// 不符合规则的文件列表
if (nonCompliantFileList.length > 0) {
const { confirm } = NON_COMPLIANT_FILE_MAP[selectType];
if (confirm) {
Modal.info({
...confirm,
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
});
return;
}
this.setState({
fileList: _fileList,
nonCompliantFileList,
showNonCompliantFileModal: true,
})
} else {
this.handleShowUploadModal(_fileList);
}
// 清空文件,防止第二次无法上传同一个文件
const dom = document.querySelector('#detailFileInput');
dom.value = '';
}
// 显示上传进度弹窗
handleShowUploadModal = async (fileList) => {
if (fileList.length) {
const { folderPathList } = this.state;
this.setState({
showUploadModal: true,
localFileList: fileList,
uploadFolderPath: folderPathList[folderPathList.length - 1]
});
}
}
handleUploadDone = (file, resourceId) => {
const { folderPathList, currentRootDisk, uploadFolderPath } = this.state;
const { scene } = this.props;
const { teacherId, instId } = window.currentUserInstInfo;
const currentFolder = folderPathList[folderPathList.length - 1];
const { id = null } = uploadFolderPath || currentFolder;
let { size, type, name } = file;
if (!type) {
type = getFileTypeByName(name)
}
const params = {
name,
resourceId,
folderSize: size,
folderFormat: type,
folderTypeEnum: resourceId ? 'FILE' : 'FOLDER',
disk: currentRootDisk.disk,
instId: instId,
createUser: teacherId ? "TEACHER" : "ADMIN",
parentId: id
}
Service.Apollo('public/apollo/saveFolder', params).then((res) => {
const { query, selectedFileList, currentRootDisk } = this.state;
const _query = _.clone(query);
const _selectedFileList = [...selectedFileList, res.result];
_query.current = 1;
this.setState({
query: _query,
selectedFileList: scene === 'liveCourse' ?
_selectedFileList.filter(item => { return !DISABLE_FILE_FORMAT.includes(item.folderFormat)}) :
_selectedFileList,
}, () => {
if (resourceId && !_.isEqual(uploadFolderPath, currentFolder)) return;
// 上传之后根目录不变
this.handleFetchFolderList({ parentId: id, disk: currentRootDisk.disk });
});
});
}
// 取消上传
handleHiddenUploadModal = () => {
this.setState({
showUploadModal: false,
localFileList: []
});
}
// 余额欠费提示弹窗
handleShowNoticeModal = (balance) => {
Modal.info({
title: '无法继续操作',
content: '直播系统已升级,请联系运营老师。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
})
}
renderFooter = () => {
const { selectedFileList, currentRootDisk } = this.state;
const { operateType, selectType } = this.props;
const {
accept = '.ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.mp3,.mp4,.xlsx,.xls',
tooltip = '支持文件类型:ppt、word、excel、pdf、jpg、mp3、mp4'
} = NON_COMPLIANT_FILE_MAP[selectType] || {};
const selectedFileLength = selectedFileList.length;
const hasSelect = !!selectedFileLength;
// 是否有上传权限
const hasManagementAuthority = currentRootDisk.uploadPower;
return [
<input
multiple
type="file"
style={{ display: 'none' }}
id="detailFileInput"
accept={accept}
onChange={(e) => this.handleUpload(e)}
key="inputFile"
/>,
<div
key="footerLeft"
className={`footer__left prepare-lesson-upload ${operateType === 'select' && hasManagementAuthority ? 'visible' : 'hidden'}`}
>
<Tooltip title={tooltip}>
<span
className="footer__left"
onClick={this.handleChooseFile}
>上传文件</span>
</Tooltip>
</div>,
<div className="footer__right" key="footerRight">
<Button
onClick={() => {
this.setState({
showUploadModal: false,
selectedFileList: []
});
this.props.onClose();
}}
>取消</Button>
<Choose>
<When condition={operateType === 'select'}>
<Button
key="cancel"
type="primary"
disabled={!hasSelect}
onClick={this.handleSelectFileDone}
>{`确定${hasSelect ? `(${selectedFileLength})` : ''}`}</Button>
</When>
<Otherwise>
<Button
type="primary"
onClick={this.handleMoveToTargetFolder}
>移动到此目录</Button>
</Otherwise>
</Choose>
</div>
]
}
render() {
const {
folderPathList, folderList, selectedFileList,
currentRootDisk, showUploadModal, localFileList,
showNonCompliantFileModal, nonCompliantFileList
} = this.state;
const {
scene,
isOpen,
diskList = [],
operateType = 'move',
footer = this.renderFooter(),
selectedFileList: prevSelectedFileList = [],
} = this.props;
const currentFolder = folderPathList[folderPathList.length - 1];
const title = operateType === 'move' ? '移动到' : '选择文件';
const currSelectedFileList = [...(prevSelectedFileList || []), ...selectedFileList];
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 1;
return (
<Modal
visible={isOpen}
title={title}
footer={footer}
width={560}
onCancel={this.handleClose}
className="select-prepare-file-modal"
>
{
// 机构可见磁盘大于1且在选择文件的情况下才显示tabs
operateType === 'select' && diskList.length > 1 &&
<div className="radio-buttons">
<Radio.Group
defaultValue={currentRootDisk.disk}
value={currentRootDisk.disk}
>
{
diskList.map((item, index) => {
return (
<Radio.Button
value={item.disk}
key={`folder-item${index}`}
onClick={() => this.handleChangeFolder(item)}
>
{item.folderName}
</Radio.Button>
)
})
}
</Radio.Group>
</div>
}
<div className="bread-crumbs">
{
folderPathList.map((folderPath, index) => {
return (
<div
className="file-path"
key={`file-path${index}`}
onClick={() => this.handleChangeCurrentFolder(folderPath, index)}
>
{folderPath.folderName}
</div>
)
})
}
</div>
<Choose>
<When condition={!_.isEmpty(folderList)}>
<div
onScrollCapture={() => this.handleScrollEvent()}
style={{ height: '320px', overflowY: 'scroll' }}
ref="fileListRef"
>
<div className="file-list">
{
folderList.map((folder, index) => {
const { folderType, folderSize, folderFormat, folderName } = folder;
const isFolder = folderType === 'FOLDER';
let _size = `${(folderSize / DEFAULT_SIZE_UNIT).toFixed(1)}M`;
if (folderSize < 0.1 * DEFAULT_SIZE_UNIT) {
_size = `${(folderSize / 1000).toFixed(1)}kb`;
}
let imgSrc = !isFolder ?
FILE_TYPE_ICON_MAP[folderFormat] :
'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594871430788.png';
if (employeeDisk) {
imgSrc = 'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594871440736.png'
}
const suffix = _.last(folderName.split('.')).toLowerCase();
// 判断文件是否被选中了
const hasSelect = _.find(currSelectedFileList, (item) => item.resourceId === folder.resourceId);
// 判断文件是否已经被关联了
const hasRelation = _.find((prevSelectedFileList || []), (item) => item.resourceId === folder.resourceId);
// 文件禁止点击的情况(移动、直播场景下文件为Excel、文件已经被关联了、文件不合法)
const disabled = (!isFolder && operateType === 'move') || (scene === 'liveCourse' && folder.folderFormat === 'EXCEL') || !!hasRelation || (!isFolder && !FILE_SUFFIX_LIST.includes(suffix));
return (
<div
className={`file-item ${!disabled ? 'enable' : 'disable'}`}
key={`file-item${index}`}
onClick={
lodash.debounce(() => this.handleSelect(folder), 500)
}
>
<div className="file-item__cover">
<img src={imgSrc} alt="file-item__img" width="24" />
<span>{getEllipsText(folderName, 20)}</span>
</div>
{/* 文件夹不显示大小 */}
{ !isFolder && <div className="file-item__size">{_size}</div> }
{/* 当选择文件的时候,显示复选框 */}
{
!isFolder && operateType === 'select' &&
<Checkbox
checked={!!hasSelect}
disabled={!!hasRelation}
/>
}
{/* 文件不显示展开的图标 */}
{
isFolder &&
<div className="file-item__expend-icon">
<span className="icon iconfont">&#xe71b;</span>
</div>
}
</div>
)
})
}
</div>
</div>
</When>
<Otherwise>
<LottieIcon
title={<span className="desc">此文件夹下无可用文件</span>}
/>
</Otherwise>
</Choose>
<UploadProgressModal
isOpen={showUploadModal}
fileList={localFileList}
currentFolder={currentFolder}
onUpload={this.handleUploadDone}
onCancel={this.handleHiddenUploadModal}
/>
<NonCompliantFileModal
isOpen={showNonCompliantFileModal}
fileList={nonCompliantFileList}
onClose={() => {
this.setState({ showNonCompliantFileModal: false });
}}
onOk={() => {
this.setState({ showNonCompliantFileModal: false });
this.handleShowUploadModal(this.state.fileList)
}}
/>
{ this.state.chargeModal }
</Modal>
)
}
}
export default SelectPrepareFileModal;
\ No newline at end of file
.select-prepare-file-modal {
.ant-upload-list {
display: none;
}
.lottie-icon {
margin: 12px 0 0 0;
border: 1px solid #E8E8E8;
border-radius: 4px;
padding: 100px 0;
.desc {
color: #999;
.upload-btn {
color: #FF7519;
margin: 0 4px;
cursor: pointer;
}
}
}
.radio-buttons {
text-align: center;
margin-bottom: 16px;
}
.file-path {
display: inline-block;
color: #333;
position: relative;
margin-bottom: 12px;
margin-right: 12px;
&:last-child {
color: #999;
}
&:not(:last-child) {
cursor: pointer;
&:hover {
color: #FF8534;
}
&::before {
content: '/';
position: absolute;
color: #CCC;
right: -8px;
}
}
}
.file-list {
border: 1px solid #E8E8E8;
border-radius: 4px;
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
padding: 12px 25px;
&:not(:last-child) {
border-bottom: 1px solid #E8E8E8;
}
&.enable {
cursor: pointer;
}
&.disable {
cursor: not-allowed;
opacity: 0.5;
}
&__cover {
min-width: 320px;
img {
margin-right: 8px;
}
}
&__size {
margin-left: 40px;
}
.iconfont {
color: #BFBFBF;
}
}
}
.ant-modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
.prepare-lesson-upload.hidden {
visibility: hidden;
}
.prepare-lesson-upload.visible {
visibility: visible;
}
.footer__left {
color: #FF7519;
cursor: pointer;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-20 19:12:49
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-20 20:25:13
* @Description: 大班直播分享弹窗
*/
import React from 'react';
import { Modal, Button, message } from 'antd';
import html2canvas from 'html2canvas';
import QRCode from '@/lib/qrcode/qrcode';
import Service from '@/common/js/service';
import { copyText } from '@/domains/basic-domain/utils';
import './ShareLiveModal.less';
const { name } = window.currentUserInstInfo || {};
const DEFAULT_COVER = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class ShareLiveModal extends React.Component {
constructor(props) {
super(props);
this.state = {
shareUrl: 'https://xiaomai5.com/liveShare?courseId=12'
}
}
componentDidMount() {
// 获取短链接
this.handleConvertShortUrl();
}
handleConvertShortUrl = () => {
const { longUrl } = this.props.data;
// 发请求
Service.Sales('public/businessShow/convertShortUrls', {
urls: [longUrl]
}).then((res) => {
const { result = [] } = res;
this.setState({
shareUrl: result[0].shortUrl
}, () => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new QRCode({
text: this.state.shareUrl,
size: 98,
})
qrcodeWrapDom.appendChild(qrcodeNode);
});
})
}
componentWillUnmount() {
// 页面销毁之前清空定时器
clearTimeout(this.timer);
}
// 下载海报
handleDownloadPoster = () => {
const dom = document.querySelector('#poster');
html2canvas(dom, {
useCORS: true,
}).then(canvas => {
const downloadDOM = document.createElement('a');
const { courseName } = this.props.data;
const dataUrl = canvas.toDataURL('image/png');
downloadDOM.href = dataUrl;
downloadDOM.download = `${courseName}.png`;
downloadDOM.click();
});
}
// 复制分享链接
handleCopy = () => {
const textContent = document.getElementById('shareUrl').innerText;
copyText(textContent);
message.success('复制成功!');
}
render() {
const { needStr, data, type } = this.props;
const { courseName, coverUrl = DEFAULT_COVER, scheduleVideoUrl } = data;
const { shareUrl } = this.state;
// 判断是否是默认图, 默认图不需要在URL后面增加字符串
const isDefaultCover = coverUrl === DEFAULT_COVER;
const coverImgSrc = type === 'videoClass'
? (
// 如果是默认图, 显示视频的第一帧, 否则显示上传的视频封面
isDefaultCover
? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring`
: `${coverUrl}${!needStr ? '&anystring': ''}`
)
: `${coverUrl}${(!needStr && !isDefaultCover) ? '&anystring' : ''}`
return (
<Modal
title="分享直播课"
width={680}
visible={true}
footer={null}
className="share-live-modal"
onCancel={this.props.close}
>
<div className="left" id="poster">
<div
className="course-name"
>{`【${courseName}】开课啦,快来学习!`}</div>
<img
src={coverImgSrc}
crossOrigin="*"
className="course-cover"
alt="course-cover"
/>
<div className="qrcode-wrap">
<div className="qrcode-wrap__left">
<div className="text">长按识别二维码进入观看</div>
<img
className="finger"
alt="finger"
src="https://image.xiaomaiketang.com/xm/thpkWDwJsC.png"
/>
</div>
<div className="qrcode-wrap__right" id="qrcodeWrap">
</div>
</div>
<div className="inst-name">
<span className="icon iconfont">&#xe7b1;</span>
<span className="text">{name}</span>
</div>
</div>
<div className="right">
<div className="share-url right__item">
<div className="title">① 链接分享</div>
<div className="sub-title">家长可通过微信打开链接,报名观看直播</div>
<div className="content">
<div className="share-url" id="shareUrl">{shareUrl}</div>
<Button
type="primary"
onClick={this.handleCopy}
>复制</Button>
</div>
</div>
<div className="share-poster right__item">
<div className="title">② 海报分享</div>
<div className="sub-title">家长可通过微信识别二维码,报名观看直播</div>
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
</div>
</div>
</Modal>
)
}
}
export default ShareLiveModal;
.share-live-modal {
.ant-modal-body {
display: flex;
.left {
width: 303px;
padding: 20px;
margin: 0 32px 0 16px;
box-shadow:0px 2px 10px 0px rgba(0,0,0,0.05);
border-radius: 12px;
.course-name {
color: #333;
font-size: 16px;
font-weight: 500;
line-height: 20px;
}
.course-cover {
width: 263px;
height: 143px;
border-radius: 6px;
margin-top: 8px;
}
.qrcode-wrap {
padding: 0 16px;
display: flex;
align-items: center;
margin: 24px 0 16px 0;
&__left {
width: 98px;
text-align: center;
margin-right: 22px;
.text {
line-height: 20px;
}
.finger {
width: 40px;
height: 40px;
margin-top: 8px;
}
}
&__right {
width: 110px;
height: 110px;
padding: 6px
}
}
.inst-name {
background-color: #FAFAFA;
min-height: 36px;
border-radius: 9px;
padding: 8px 16px;
display: flex;
align-items: center;
.iconfont {
color: #999;
margin-right: 4px;
}
.text {
font-size: 12px;
color: #999;
}
}
}
.right {
.title {
color: #333;
font-weight: 500;
}
.sub-title {
color: #999;
margin-top: 16px;
}
.content {
display: flex;
align-items: center;
margin-top: 8px;
.share-url {
width: 212px;
overflow: hidden;
height: 32px;
line-height: 32px;
border-radius: 4px 0 0 4px;
padding-left: 12px;
white-space: nowrap;
color: #999999;
background: #EFEFEF;
}
.ant-btn {
margin-left: -2px;
}
}
.share-poster {
margin-top: 40px;
.content {
color: #FF7519;
cursor: pointer;
}
}
}
}
}
\ No newline at end of file
import React from 'react'
import './ShowTip.less'
function ShowTips(props) {
return (
<div className={`xm-show-tip xm-type-${props.type || 'defulat'}`} >
<span className="icon iconfont">&#xe6f2;</span>
<p>{props.message}</p>
</div>
)
}
export default ShowTips
\ No newline at end of file
.xm-show-tip {
position: relative;
min-height:32px;
background:#FFF0E7;
border-radius:4px;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 12px;
span.icon {
color:#FF8534;
line-height: 20px;
}
p {
color:#666666;
font-size: 14px;
line-height:20px;
margin-left: 12px;
}
}
.xm-type-default {
width: 100%;
}
.xm-type-info {
width: calc(~'100% - 190px');
}
\ No newline at end of file
import React from 'react';
import { Modal } from 'antd';
import _ from 'underscore';
import { getEllipsText, randomString } from "@/domains/basic-domain/utils";
import {
DEFAULT_SIZE_UNIT,
FILE_TYPE_ICON_MAP,
FILR_VERIFY_MAP,
UPLOAD_PART_SIZE,
UPLOAD_PARALLEL,
} from "@/domains/class-book/constants";
import { getFileTypeByName } from '@/domains/class-book/utils';
import UploadFileService from '@/domains/class-book/UploadFileService';
import './UploadProgressModal.less';
class UploadProgressModal extends React.Component {
constructor(props) {
super(props);
this.state = {
fileList: [],
showFileList: true, // 默认展开上传进度
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.isOpen && !_.isEqual(nextProps.fileList, this.props.fileList)) {
const { fileList } = nextProps;
const _fileList = fileList.map((file) => {
return {
fileContent: file,
status: 'waiting',
checkpoints: {},
}
});
const allFileList = [...this.state.fileList, ..._fileList];
this.setState({
fileList: allFileList
}, () => {
// 构建上传表单
this.handleConstructForm(_fileList);
})
}
}
// 遍历上传文件,给每个文件构建上传表单
handleConstructForm = (newFileList) => {
const { fileList } = this.state;
const length = fileList.length;
const newLength = newFileList.length;
const startI = length === newLength ? 0 : length - newLength;
// 只给新添加的文件构建上传表单
for (let i = startI; i < length; i++) {
this.handleStartUpload(i);
}
}
// 初始化ossClient
initOssClient = (id, resourceName) => {
const { instId } = window.currentUserInstInfo;
return new Promise(resolve => {
// 获取STS Token
UploadFileService.getOssClient({
instId,
resourceName,
data: { folderId: id },
accessTypeEnum: 'PUBLIC',
bizCode: 'UPLOAD_FOLDER',
}).then((result) => {
const { ossClient, resourceId, callBack, callbackBody, ossUri } = result;
this.setState({
ossClient,
resourceId,
callBack,
callbackBody,
ossUri
});
resolve({ ossClient, resourceId, callBack, callbackBody, ossUri });
})
})
}
// 开始分片上传
handleStartUpload = (index) => {
const { currentFolder } = this.props;
const { fileList } = this.state;
const file = fileList[index];
const { fileContent } = file;
const { name, type } = fileContent;
const resourceName = randomString(16) + name.slice(name.lastIndexOf('.'));
const { id = 0 } = currentFolder;
// 开始上传之前初始化OssClient
this.initOssClient(id, resourceName).then((result) => {
const { ossClient, resourceId, callBack, callbackBody, ossUri } = result;
ossClient.multipartUpload(ossUri, fileContent, {
callback: {
url: callBack,
body: callbackBody,
contentType: 'application/json',
},
parallel: UPLOAD_PARALLEL,
partSize: UPLOAD_PART_SIZE,
progress: (progress, checkpoint) => {
this.onMultipartUploadProgress(progress, checkpoint, file)
},
mime: type
}).then(url => {
file.status = 'success';
this.setState({ fileList });
this.props.onUpload(fileContent, resourceId);
}).catch(err => {
file.status = 'fail';
this.setState({ fileList });
});
});
}
// 给每个文件设置进度显示
onMultipartUploadProgress = (progress = 0, checkpoint, file) => {
const { fileList } = this.state;
file.status = 'uploading';
this.setState({ fileList })
file.progress = (progress * 100).toFixed(2);
// 如果文件小于100KB,不用记录断点,直接上传
const { fileContent } = file;
const { size } = fileContent;
if (size > 100 * 1024) {
file.checkpoints[checkpoint.uploadId] = checkpoint;
}
}
// 断点续传
handleReUpload = (index) => {
const { fileList, ossClient, resourceId, callBack, callbackBody } = this.state;
const currentFile = fileList[index];
const { checkpoints, fileContent } = currentFile
Object.values(checkpoints).forEach(checkpoint => {
const { uploadId, file } = checkpoint;
const { type } = file;
ossClient.multipartUpload(uploadId, file, {
callback: {
url: callBack,
body: callbackBody,
contentType: 'application/json',
},
checkpoint,
parallel: UPLOAD_PARALLEL,
partSize: UPLOAD_PART_SIZE,
progress: (progress, checkpoint) => {
this.onMultipartUploadProgress(progress, checkpoint, currentFile)
},
mime: type,
}).then(url => {
currentFile.status = 'success';
this.setState({ fileList });
this.props.onUpload(fileContent, resourceId);
}).catch(err => {
currentFile.status = 'fail';
this.setState({ fileList });
});
})
}
// 显示/隐藏上传进度
handleToggleFileList = () => {
this.setState({
showFileList: !this.state.showFileList
});
}
// 取消上传
handleCancelAllUpload = () => {
// 判断是否有正在上传或者待上传的文件,有的话弹出二次提示框
const { fileList, ossClient } = this.state;
const uploadingFileList = fileList.filter(file => file.status === 'uploading' || file.status === 'waiting');
if (uploadingFileList.length) {
Modal.confirm({
title: '放弃上传',
content: '上传列表中有未上传完成的文件,确定要放弃上传吗?',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
uploadingFileList.forEach((uploadingFile, index) => {
const { checkpoints } = uploadingFile;
Object.values(checkpoints).forEach(checkpoint => {
const { uploadId, name } = checkpoint;
ossClient.abortMultipartUpload(name, uploadId);
});
fileList.splice(index, 1);
});
this.setState({ fileList: [] });
this.props.onCancel();
}
})
return;
}
this.setState({ fileList: [] });
this.props.onCancel();
}
// 取消单个文件上传
handleCancelUpload = (currentFile, index) => {
const { fileList, ossClient } = this.state;
const { checkpoints } = currentFile;
Object.values(checkpoints).forEach(checkpoint => {
const { uploadId, name } = checkpoint;
ossClient.abortMultipartUpload(name, uploadId);
});
fileList.splice(index, 1);
this.setState({ fileList });
if (!fileList.length) {
this.props.onCancel();
}
}
renderOverallProgress = () => {
// 至少有一个文件正在上传,显示正在上传 N 项
// 文件全都上传成功,显示为“N 项上传成功”
// 文件全都上传失败,显示为“N 项上传失败
// 文件部分上传成功、部分上传失败,显示为“M 项上传成功,N 项上传失败”
const { fileList } = this.state;
const allFileLength = fileList.length;
const successFileLength = fileList.filter(file => file.status === 'success').length;
const failFileLength = fileList.filter(file => file.status === 'fail').length;
const uploadingFileLength = fileList.filter(file => file.status === 'uploading' || file.status === 'waiting').length;
if (!!uploadingFileLength) {
return (
<span>正在上传{uploadingFileLength}</span>
)
}
if (successFileLength === allFileLength ) {
return (
<span>{successFileLength}项上传完成</span>
)
}
if (failFileLength === allFileLength) {
return (
<span>{successFileLength}项上传失败</span>
)
}
return (
<span>{successFileLength}项上传完成,{failFileLength}项上传失败</span>
)
}
render() {
const { fileList, showFileList } = this.state;
const { isOpen } = this.props;
return (
<div className={`prepare-lesson-upload-progress-modal ${isOpen ? 'visibile' : 'hidden'}`}>
<div className={`modal-header ${showFileList ? 'no-radius' : ''}`}>
{/* 总体进度 */}
<div className="over-all-progress">
{ this.renderOverallProgress() }
</div>
<div className="operate">
<Choose>
<When condition={showFileList}>
<span
className="icon iconfont"
onClick={this.handleToggleFileList}
>&#xe660;</span>
</When>
<Otherwise>
<span
className="icon iconfont"
style={{ transform: 'rotate(180deg)'}}
onClick={this.handleToggleFileList}
>&#xe660;</span>
</Otherwise>
</Choose>
<span
className="icon iconfont"
onClick={this.handleCancelAllUpload}
>&#xe6ef;</span>
</div>
</div>
{
showFileList &&
<div className="modal-body">
{
fileList.map((file, index) => {
let { size, type, name } = file.fileContent;
let _size = `${(size / DEFAULT_SIZE_UNIT).toFixed(1)}M`;
if (size < 0.1 * DEFAULT_SIZE_UNIT) {
_size = `${(size / 1000).toFixed(1)}kb`;
}
if (!type) {
type = getFileTypeByName(name)
}
let imgSrc = URL.createObjectURL(file.fileContent);
if (type.indexOf('image') < 0 && type !== 'folder') {
imgSrc = FILE_TYPE_ICON_MAP[FILR_VERIFY_MAP[type].type]
}
return (
<div
className="file-item"
key={`file-item${index}`}
>
<div className="file-item__cover">
<img src={imgSrc} alt="file-item-cover" />
<span className="file-name">{getEllipsText(name, 10)}</span>
</div>
<span className="file-item__size">{_size}</span>
<div className="file-item__status">
{
file.status === 'uploading' &&
<span>{file.progress}%</span>
}
{
file.status === 'success' &&
<span className="icon iconfont succ">&#xe710;</span>
}
{
file.status === 'fail' &&
[
<span
key="icon-fail"
className="icon iconfont fail"
>上传失败,请重试</span>,
<span
key="icon reupload"
className="icon iconfont reupload"
onClick={() => this.handleReUpload(index)}
>&#xe75a;</span>
]
}
{
file.status === 'waiting' &&
<span className="icon iconfont waiting">排队中</span>
}
{
(file.status === 'uploading' || file.status === 'waiting') &&
<span
className="icon iconfont close"
onClick={() => this.handleCancelUpload(file, index)}
>&#xe6ef;</span>
}
</div>
{
file.status === 'uploading' &&
<div
className="file-item__progress"
style={{
width: `${file.progress}%`
}}
></div>
}
</div>
)
})
}
</div>
}
</div>
)
}
}
export default UploadProgressModal;
\ No newline at end of file
.prepare-lesson-upload-progress-modal {
width: 448px;
position: fixed;
bottom: 32px;
right: 0;
box-shadow: 0px 4px 12px 0px rgba(0,0,0,0.1);
border-radius: 6px;
right: 48px;
background-color: #FFF;
z-index: 10;
&:visible {
display: block;
}
&.hidden {
display: none;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
line-height: 50px;
background-color: #9A9DA7;
border-radius: 6px;
padding: 0 24px;
color: #FFF;
&.no-radius {
border-radius: 6px 6px 0 0;
}
.over-all-progress span {
font-size: 16px;
}
.iconfont {
cursor: pointer;
font-size: 12px;
margin-left: 24px;
}
}
.modal-body {
max-height: 290px;
overflow: scroll;
.file-item {
display: flex;
align-items: center;
height: 50px;
line-height: 50px;
padding: 0 24px;
position: relative;
&:not(:last-child) {
margin-bottom: 4px;
}
&__cover {
display: flex;
align-items: center;
img {
width: 24px;
margin-right: 8px;
}
.file-name {
min-width: 154px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: inline-block;
}
}
&__size {
margin: 0 24px;
min-width: 54px !important;
color:#999999;
}
&__status {
.iconfont {
color: #BFBFBF;
font-size: 12px;
&.succ {
color: #5DD333;
}
&.close, &.reupload {
position: absolute;
top: 1px;
right: 20px;
cursor: pointer;
}
&.fail {
color: #EC4B35;
}
}
}
&__progress {
height: 100%;
position: absolute;
height: 50px;
margin-left: -24px;
background-color: rgba(32,161,255,0.06);
}
}
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-31 09:34:31
* @LastEditors: wufan
* @LastEditTime: 2020-12-09 10:53:57
* @LastEditTime: 2020-12-09 19:07:53
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -33,6 +33,7 @@ class Axios {
options: FetchOptions = { requestType: 'json' }
): Promise<any> {
const _url = `${url}?storeId=${User.getStoreId()}&token=${User.getToken()}&storeUserId=${User.getStoreUserId()}&userId=${User.getUserId()}`;
// const _url = `${url}?storeId=${User.getStoreId()}&token=${User.getToken()}&storeUserId=${User.getStoreUserId()}&userId=${User.getUserId()}p=w&v=v5.4.8&userType=B&token=9f1609b2e7234afd8922a8676dbbf721&uid=920261769807528000&tid=1305385165390426114&aid=1305385165390426114`;
return new Promise((resolve, reject) => {
const instance: AxiosInstance = axios.create({
......@@ -46,6 +47,44 @@ class Axios {
product: "xmCloudClass",
'Content-Type': options.requestType === 'form' ? 'application/x-www-form-urlencoded' : 'application/json; charset=UTF-8',
}
// headers: {
// "content-length": "2",
// "referer": "https://dev.xiaomai5.com/",
// "x-b3-parentspanid": "e5e8fdfc471ed007",
// "sec-fetch-site": "same-site",
// "origin": "https://dev.xiaomai5.com",
// "x-b3-sampled": "1",
// "x-forwarded-port": "443",
// "project": "xmzj-web-b",
// "tid": "1305385165390426114",
// "uid": "920261769807528000",
// "instid": "1213001850820476929",
// "x-forwarded-host": "dev-heimdall.xiaomai5.com",
// "host": "dev-heimdall.xiaomai5.com",
// "content-type": "application/json; charset=UTF-8",
// "x-b3-flags": "0",
// "x-request-id": "0d7ad401ed447ff75c0de254563e76fd",
// "sec-fetch-mode": "cors",
// "x-forwarded-proto": "https",
// "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
// "usertype": "B",
// "xmversion": "5.0",
// "x-forwarded-for": "60.191.55.162",
// "accept": "application/json, text/plain, */*",
// "token": "9f1609b2e7234afd8922a8676dbbf721",
// "x-real-ip": "60.191.55.162",
// "p": "w",
// "x-b3-traceid": "12a41d862eb3e551",
// "x-b3-spanid": "08dc4d59580c4bee",
// "v": "VERSION",
// "vn": "v5.4.8",
// "x-scheme": "https",
// "bizaccountid": "1305385165390426114",
// "accept-encoding": "gzip, deflate, br",
// "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
// "cid": "940838898156699712",
// "sec-fetch-dest": "empty"
// },
});
if (method !== 'GET' && options.requestType === 'form') {
......
/*
* @Author: sunbingqing
* @Date: 2019-08-05 11:05:00
* @Last Modified by: zhujian
* @Last Modified time: 2020-05-26 19:50:20
*/
import React from 'react';
import { Spin, Select } from 'antd';
import _ from 'underscore';
import Service from '@/common/js/service';
class ClassSearchSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
query: {
size: 10,
current: 1,
classNameLike: null,
status: ['INIT', 'STUDYING', 'END'],
},
dataSet: [],
isAll: false
}
}
componentWillMount() {
this.handleFetchClassList();
}
handleFetchClassList = (current = 1) => {
const { courseId } = this.props;
const _query = _.clone(this.state.query);
_query.courseId = courseId;
_query.current = current;
Service.Business('public/class/digestList', _query)
.then((res) => {
const { result = {} } = res || {};
const { records = [], hasNext = false } = result;
_.map(records, (item) => {
item.formatName = item.name;
return item;
});
this.setState({
isAll: !hasNext,
dataSet: current === 1 ? records : [...this.state.data, ...records]
});
})
}
handleClassSelect = (classId) => {
const { query } = this.state;
if (query.classNameLike) {
query.classNameLike = null;
this.setState({ query }, this.handleFetchClassList);
}
this.props.onSelect(classId);
}
handleFetchMore = (e) => {
const { isAll } = this.state;
const ulDom = e.currentTarget.getElementsByTagName("ul")[0];
if (ulDom.length <= 0) return;
const { scrollTop, clientHeight, scrollHeight } = ulDom;
if (scrollTop + clientHeight > scrollHeight - 30 && !isAll) {
this.handleFetchClassList();
}
}
render() {
const { label, defaultValue, placeholder } = this.props;
const { query, isAll, dataSet } = this.state;
return (
<div className={"common-select staticSelect"}>
{
!!label && <div className='label'> {label}:</div>
}
<Select
showSearch
allowClear
onSearch={(value) => {
query.classNameLike = value;
this.setState({ query }, this.handleFetchClassList)
}}
onPopupScroll={this.handleFetchMore}
placeholder={placeholder}
value={defaultValue}
onChange={this.handleClassSelect}
filterOption={(input, option) => option}
>
{
_.map(dataSet, (item, index) => {
return (
<Select.Option
id={`class_select_item_${index}` }
key={item.classId}
value={item.classId}
title={item.name}
>
{item.name}
</Select.Option>
);
})
}
{!isAll &&
<Select.Option
disabled
tyle={{ textAlign: 'center' }}
value="spin"
>
<Spin size="small" />
</Select.Option>
}
</Select>
</div>
)
}
}
export default ClassSearchSelect;
/*
* @Author: leehu
* @Date: 2017-09-04 11:52:25
* @Last Modified by: zhujian
* @Last Modified time: 2020-03-09 20:19:04
*/
import React from 'react';
import { Spin, Select } from 'antd';
import _ from 'underscore';
import classNames from 'classnames';
import Service from '@/common/js/service';
class CourseSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
close: true,
dataSource: [],
query: {
current: 1,
size: 10000,
showNumber: 100,
courseNameLike: null,
},
dataSet: [],
visible: false,
loading: false,
selectedIds: [],
selected: props.selected || [],
}
}
componentWillMount() {
this.handleFetchCourseList();
}
handleQueryReset = () => {
this.setState({
query: {
current: 1,
size: 10000,
showNumber: 100,
courseNameLike: null,
}
}, () => {
this.handleFetchCourseList();
});
}
handleFetchCourseList(current = 1) {
this.setState({ loading: true });
let query = _.clone(this.state.query)
if (this.props.queryAll) {
delete query.status;
}
query.current = current;
Service.Business('public/course/digestList', query)
.then((res) => {
const { result = {} } = res || {};
let { records = [], total = 0, hasNext } = result;
records.map(function (item, index) {
item.title = item.name;
item.desc = item.name;
return item;
});
this.setState({
dataSet: current > 1 ? [...this.state.dataSet, ...records] : records,
isAll: !hasNext,
loading: false,
totalCount: Number(total),
});
}).finally((res) => {
this.setState({ loading: false });
});
}
handleCourseSelect = (course) => {
const { query } = this.state;
if (query.courseNameLike) {
query.courseNameLike = null;
this.setState({ query }, this.handleFetchCourseList);
}
this.props.onSelect(course);
}
render() {
const {
id,
style,
label,
defaultValue,
placeholder = '请选择'
} = this.props;
const { visible, query, dataSet, isAll } = this.state;
return (
<div
className={classNames("common-select staticSelect", {
'common-select-active': visible
})}
style={style}>
{
!!label && <div className='label'> {label}:</div>
}
<Select
id={id}
ref='course'
showSearch
allowClear
onSearch={(value) => {
query.courseNameLike = value
this.setState({ query }, this.handleFetchCourseList)
}}
onBlur={() => {
query.courseNameLike = null;
this.setState({ query }, this.handleFetchCourseList)
}}
placeholder={placeholder}
value={defaultValue}
onChange={this.handleCourseSelect}
filterOption={(input, option) => option}
>
{
_.map(dataSet, (item, index) => {
return <Select.Option id={'course_select_item_' + index} key={item.id} value={item.id} title={item.name}>
{item.name}
</Select.Option>
})
}
{!isAll &&
<Select.Option disabled style={{ textAlign: 'center' }} value="spin">
<Spin size="small" />
</Select.Option>
}
</Select>
</div>
)
}
}
export default CourseSelect;
\ No newline at end of file
import React from 'react';
import E from 'wangeditor';
import { randomString } from '@/domains/basic-domain/utils';
import './EditorBox.less';
class EditorBox extends React.Component {
constructor(props) {
super(props)
this.state = {
editorId: randomString(16),
textLength: 0,
}
}
componentDidMount() {
this.renderEditor()
}
renderEditor() {
const { editorId } = this.state;
const { detail, onChange } = this.props;
const editorInt = new E(`#editor${editorId}`);
editorInt.customConfig.menus = [
'bold', // 粗体
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'list', // 列表
'justify', // 对齐方式
'emoticon', // 表情
]
editorInt.customConfig.emotions = [
{
title: 'emoji',
type: 'emoji',
content: ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '😊', '🙂', '🙃', '😉', '😓', '😅', '😪', '🤔', '😬', '🤐']
}
]
editorInt.customConfig.zIndex = 999;
editorInt.customConfig.pasteFilterStyle = false;
editorInt.customConfig.pasteIgnoreImg = true;
// 自定义处理粘贴的文本内容
editorInt.customConfig.pasteTextHandle = function (content) {
if (content === '' && !content) return ''
var str = content
str = str.replace(/<xml>[\s\S]*?<\/xml>/ig, '')
str = str.replace(/<style>[\s\S]*?<\/style>/ig, '')
str = str.replace(/<\/?[^>]*>/g, '')
str = str.replace(/[ | ]*\n/g, '\n')
str = str.replace(/&nbsp;/ig, ' ')
return str
}
editorInt.customConfig.onchange = (html) => {
const textLength = editorInt.txt.text().replace(/&nbsp;/ig, ' ').length;
this.setState({ textLength }, () => {
onChange(html, this.state.textLength);
})
}
editorInt.create();
editorInt.txt.html(detail.content);
editorInt.change && editorInt.change();
}
render() {
const { editorId, textLength } = this.state;
const { limitLength = 1000 } = this.props;
return <div className="wang-editor-container ">
<div className="editor-box" id={`editor${editorId}`}></div>
{textLength > limitLength && <div className="editor-tips">超了{textLength - limitLength}个字</div>}
</div>
}
}
export default EditorBox;
.wang-editor-container {
border: 1px solid #E8E8E8;
border-radius: 4px;
width: 552px;
position: relative;
.w-e-text p,
.w-e-text h1,
.w-e-text h2,
.w-e-text h3,
.w-e-text h4,
.w-e-text h5,
.w-e-text table,
.w-e-text pre {
margin: 0;
}
.w-e-toolbar {
background-color: #fff !important;
border: none !important;
border-bottom: 1px solid #E8E8E8 !important;
}
.w-e-text-container {
border: none !important;
height: 88px !important;
}
.editor-tips {
position: absolute;
top: 5px;
right: 8px;
color: #f5222d;
}
}
\ No newline at end of file
import React from 'react';
import { Modal, Button } from 'antd';
import Service from '@/common/js/service';
import { randomString } from '@/domains/basic-domain/utils';
let cutFlag = false;
class ImgCutModal extends React.Component {
state = {
photoclip: null,
imgAddress: null
}
componentDidUpdate(prevProps) {
const {
visible,
cutWidth = 550,
cutHeight = 283,
imageFile,
bizCode = 'HOMEWORK_FILE'
} = this.props;
if (visible && imageFile) {
setTimeout(() => {
const { photoclip } = this.state;
const okBtnDom = document.querySelector('#clipBtn');
const options = {
size: [cutWidth, cutHeight],
rotateFree: false,
ok: okBtnDom,
maxZoom: 1,
style: {
jpgFillColor: 'transparent'
},
done: (dataUrl) => {
const { name } = imageFile;
const fileName = randomString(16) + name.slice(name.lastIndexOf('.'));
const { instId } = window.currentUserInstInfo;
const params = {
instId,
bizCode: bizCode,
accessTypeEnum: 'PUBLIC',
resourceName: fileName
}
const cutImage = this.convertBase64UrlToBlob(dataUrl);
Service.Apollo("public/apollo/commonOssAuthority", params).then((res) => {
const { resourceId, accessId, policy, callback, signature,key, host } = res.result;
const localUrl = URL.createObjectURL(cutImage);
// 构建上传的表单
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append("OSSAccessKeyId", accessId);
formData.append("policy", policy);
formData.append("callback", callback);
formData.append("Signature", signature);
formData.append("key", key);
formData.append("file", cutImage);
formData.append("success_action_status", 200);
xhr.open("POST", host);
xhr.onload = () => {
this.props.onOk(localUrl, resourceId);
setTimeout(() => {
cutFlag = false;
}, 2000)
};
xhr.send(formData);
this.setState({ xhr })
})
}
};
if (!photoclip) {
const _photoclip = new window.PhotoClip('#imgCutModalNew', options);
_photoclip.load(imageFile);
this.setState({ photoclip: _photoclip });
} else {
photoclip.clear();
photoclip.load(imageFile);
}
}, 0);
}
}
// base64转换成blob
convertBase64UrlToBlob(urlData) {
const bytes = window.atob(urlData.split(",")[1]);
const ab = new ArrayBuffer(bytes.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: "image/png" });
}
handleImgCutDone = () => {
if (!cutFlag) {
cutFlag = true;
this.refs.hiddenBtn.click();
}
}
render() {
const {
title,
visible,
onClose,
reUpload,
cutContentWidth,
cutContentHeight,
width = 550,
needReUpload = false,
} = this.props;
return (
<Modal
title={title}
width={width}
visible={visible}
onCancel={onClose}
footer={[
<Choose>
<When condition={!needReUpload}>
<Button
key="back"
onClick={onClose}
>取消</Button>
</When>
<Otherwise>
<Button
key="back"
onClick={reUpload}
>重新上传</Button>
</Otherwise>
</Choose>,
<Button
key="submit"
type="primary"
onClick={this.handleImgCutDone}
>确定</Button>
]}>
<div
id="imgCutModalNew"
style={{
width: `${cutContentWidth || 620}px`,
height: `${cutContentHeight || 420}px`
}}
></div>
<div
id="clipBtn"
style={{ display: 'none' }}
ref="hiddenBtn"
/>
</Modal>
)
}
}
export default ImgCutModal;
\ No newline at end of file
/*
* @Author: wufan
* @Date: 2020-12-01 17:21:21
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-08 10:38:04
* @LastEditors: wufan
* @LastEditTime: 2020-12-09 14:49:24
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -34,4 +34,19 @@ export function sendNewPhoneAuthCode(params: object) {
export function editUserPhone(params: object) {
return Service.Hades("public/hades/editUserPhone", params);
}
export const getOssClient = (
data: object,
instId: string,
bizCode: string,
resourceName: string,
accessTypeEnum: string
) => {
return Service.postJSON('mfs/anon/mfs/multiPartUpload', {
data,
instId,
bizCode,
resourceName,
accessTypeEnum
});
}
/*
* @Author: 吴文洁
* @Date: 2020-10-10 18:21:47
* @LastEditors: wufan
* @LastEditTime: 2020-12-09 16:05:24
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import Service from '@/common/js/service';
export const getOssClient = (
data: object,
instId: string,
bizCode: string,
resourceName: string,
accessTypeEnum: string
) => {
return Service.postJSON('mfs/anon/mfs/multiPartUpload', {
data,
instId,
bizCode,
resourceName,
accessTypeEnum
});
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-20 09:20:43
* @LastEditors: 吴文洁
* @LastEditTime: 2020-08-21 09:02:36
* @LastEditors: wufan
* @LastEditTime: 2020-12-09 14:51:33
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -16,6 +16,59 @@ const getEllipsText = (text: string, limitNum: number) => {
return limitText;
}
// 随机生成32位字符串
const randomString = (strLen: number = 32) => {
const chars: string = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
const maxPos = chars.length;
let pwd: string = '';
for (let i: number = 0; i < strLen; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd;
}
const copyText = (textContent: string) => {
const inputDom = document.createElement('textarea')
inputDom.value = textContent
document.body.appendChild(inputDom)
inputDom.select()
document.execCommand('copy')
document.body.removeChild(inputDom)
}
const formatDate = (format: string, timestamp: string) => {
if (!timestamp) return;
const date = new Date(parseInt(timestamp))
let y: any = date.getFullYear(),
m: any = date.getMonth() + 1,
d: any = date.getDate(),
h: any = date.getHours(),
i: any = date.getMinutes(),
s: any = date.getSeconds(),
w: number = date.getDay(),
week: string[] = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
m = m < 10 ? '0' + m : m;
d = d < 10 ? '0' + d : d;
h = h < 10 ? '0' + h : h;
i = i < 10 ? '0' + i : i;
s = s < 10 ? '0' + s : s;
return format
.replace('YYYY', y)
.replace('MM', m)
.replace('DD', d)
.replace('H', h)
.replace('i', i)
.replace('s', s)
.replace('WW', week[w])
}
export {
getEllipsText
getEllipsText,
randomString,
copyText,
formatDate
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-10-10 18:19:34
* @LastEditors: 吴文洁
* @LastEditTime: 2020-10-13 18:09:39
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import OSS from 'ali-oss';
import { UPLOAD_REGION } from '@/domains/class-book/constants';
import { getOssClient } from '@/data-source/basic/basic-apis.ts';
interface IMultiPartUpload {
data: object;
instId: string;
bizCode: string;
resourceName: string;
accessTypeEnum: string;
}
export default class UploadFileService {
static getOssClient(multiPartUploadParams: IMultiPartUpload) {
const { data, instId, bizCode, resourceName, accessTypeEnum } = multiPartUploadParams;
return new Promise(resolve => {
getOssClient(
data,
instId,
bizCode,
resourceName,
accessTypeEnum
).then((res) => {
const { result = {} } = res;
const {
bucket,
callBack,
resourceId,
accessKeyId,
securityToken,
accessKeySecret,
callbackBody,
ossUri
} = result;
const ossClient = new OSS({
bucket,
accessKeyId,
accessKeySecret,
region: UPLOAD_REGION,
stsToken: securityToken,
});
resolve({
ossClient,
resourceId,
callBack,
callbackBody,
ossUri
});
})
})
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-20 09:21:40
* @LastEditors: 吴文洁
* @LastEditTime: 2020-10-13 14:55:24
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
const DEFAULT_SIZE_UNIT: number = 1000 * 1000; // 将B转换成M
const SUFFIX_MAP: {
[key: string]: string
} = {
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'pdf': 'application/pdf',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xls': 'application/vnd.ms-excel'
}
const FILE_TYPE_ICON_MAP: {
[key: string]: string
} = {
word: "https://image.xiaomaiketang.com/xm/Cw44ekhNfR.png",
DOCX: "https://image.xiaomaiketang.com/xm/Cw44ekhNfR.png",
DOC: "https://image.xiaomaiketang.com/xm/Cw44ekhNfR.png",
// 第一期备后端返回的是长的文件类型,备课版改版之后后端返回的是短的文件类型,且大写,所以做个兼容,下面的EXCEl也是一样的
WORD: "https://image.xiaomaiketang.com/xm/Cw44ekhNfR.png",
Excel: "https://image.xiaomaiketang.com/xm/NTFMxcdQnd.png",
EXCEL: "https://image.xiaomaiketang.com/xm/NTFMxcdQnd.png",
PPT: "https://image.xiaomaiketang.com/xm/cDrBPi5TrH.png",
PPTX: "https://image.xiaomaiketang.com/xm/cDrBPi5TrH.png",
PDF: "https://image.xiaomaiketang.com/xm/7TAy7kbTZB.png",
MP3: "https://image.xiaomaiketang.com/xm/ykjnSWDyQ6.png",
MP4: "https://image.xiaomaiketang.com/xm/whSYMTdR57.png",
JPG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
PNG: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
GIF: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
BMP: "https://image.xiaomaiketang.com/xm/XRkX8JBTPs.png",
};
const SUPPORT_FILE_TYPE_MAP: string[] = [
"application/msword",
"text/csv",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint",
"application/pdf",
"image/jpeg",
"image/png",
"audio/mpeg",
"audio/mp4",
"video/mp4",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"audio/mp3",
"application/wps-office.pdf",
"application/wps-office.xls"
];
const LOCAL_FILE_TYPE_MAP: string[] = [
"application/msword",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-powerpoint",
"application/pdf",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/wps-office.pdf",
"application/wps-office.xls"
];
const FILR_VERIFY_MAP = {
"application/msword": {
type: "word",
maxSize: 10
},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
type: "word",
maxSize: 10
},
"text/csv": {
type: "Excel",
maxSize: 10
},
"application/vnd.ms-excel": {
type: "Excel",
maxSize: 10
},
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
type: "Excel",
maxSize: 10
},
"application/wps-office.xls": {
type: "Excel",
maxSize: 10
},
"application/vnd.ms-powerpoint": {
type: "PPT",
maxSize: 20
},
"application/vnd.openxmlformats-officedocument.presentationml.presentation": {
type: "PPT",
maxSize: 20
},
"application/pdf": {
type: "PDF",
maxSize: 50
},
"application/wps-office.pdf": {
type: "PDF",
maxSize: 50
},
"image/jpeg": {
type: "JPG",
maxSize: 10
},
"image/png": {
type: "JPG",
maxSize: 10
},
"image/jpg": {
type: "JPG",
maxSize: 10
},
"image/gif": {
type: "GIF",
maxSize: 10
},
"audio/mpeg": {
type: "MP3",
maxSize: 50
},
"audio/mp3": {
type: "MP3",
maxSize: 50
},
"audio/mp4": {
type: "MP4",
maxSize: 50
},
"video/mp4": {
type: "MP4",
maxSize: 50
}
};
const DISK_MAP: {
[key: string]: string
} = {
MYSELF: '我的文件',
COMMON: '公共文件',
EMPLOYEE: '员工文件'
};
const NON_COMPLIANT_FILE_MAP = {
'video': {
accept: 'video/mp4',
confirm: {
title: '文件过大,无法上传',
content: '为保障学员的观看体验,上传的视频大小不能超过100M',
},
tooltip: '格式支持mp4,大小不超过100M'
}
}
// 文件上传
const UPLOAD_REGION = 'oss-cn-hangzhou';
const UPLOAD_PART_SIZE = 1024 * 1024; // 每个分片大小(byte)
const UPLOAD_PARALLEL = 5; // 同时上传的分片数
export {
DEFAULT_SIZE_UNIT,
FILE_TYPE_ICON_MAP,
SUPPORT_FILE_TYPE_MAP,
LOCAL_FILE_TYPE_MAP,
FILR_VERIFY_MAP,
NON_COMPLIANT_FILE_MAP,
DISK_MAP,
SUFFIX_MAP,
UPLOAD_REGION,
UPLOAD_PART_SIZE,
UPLOAD_PARALLEL,
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-09-29 10:01:08
* @LastEditors: 吴文洁
* @LastEditTime: 2020-09-29 10:01:38
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import { SUFFIX_MAP } from '@/domains/class-book/constants';
const getFileTypeByName = (name: string) => {
const nameArray: string[] = name.split(".");
const suffix: string = nameArray[nameArray.length - 1];
return SUFFIX_MAP[suffix];
}
export {
getFileTypeByName
};
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-06-09 09:47:21
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-06-23 14:54:14
* @Description: 网络磁盘(我的文件、公共文件、员工文件)
*/
import React from 'react';
function DiskList(props) {
const { diskList, currentRootDisk } = props;
return (
<div className="disk-list__wrap">
<div className="disk-list">
{
diskList.map((item) => {
const isActive = item.disk === currentRootDisk.disk;
return (
<div
key={item.disk}
className={`item ${isActive ? 'active' : ''}`}
onClick={() => { props.onChange(item)}}
>
{
item.disk === 'MYSELF' &&
<span className="icon iconfont">&#xe7a5;</span>
}
{
item.disk === 'COMMON' &&
<span className="icon iconfont">&#xe79d;</span>
}
{
item.disk === 'EMPLOYEE' &&
<span className="icon iconfont">&#xe7a3;</span>
}
<span className="disk-name">{ item.folderName }</span>
</div>
)
})
}
</div>
<a
className="guide-href"
href={window.NewVersion ? 'https://mp.weixin.qq.com/s/s0XN0Gk4Xul192SmTd6znw' : 'https://mp.weixin.qq.com/s/2EMWaaa3LQwkJd59bmy8pA'}
target="_blank"
rel="noopener noreferrer"
>
进一步了解资料云盘
</a>
</div>
)
};
export default DiskList;
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-06-09 10:47:51
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-23 09:33:09
* @Description: 文件夹列表
*/
import React from 'react';
import { Table, Menu, Dropdown, Modal, message, Icon, Tooltip } from 'antd';
import _ from 'underscore';
// import * as lodash from 'lodash';
import { PageControl, LottieIcon } from 'xiaomai-b-components';
import Service from '@/common/js/service';
import { formatDate } from '@/domains/basic-domain/utils';
import { FILE_TYPE_ICON_MAP, SUPPORT_FILE_TYPE_MAP, DEFAULT_SIZE_UNIT } from '@/domains/class-book/constants';
import { getFileTypeByName } from '@/domains/class-book/utils';
import UploadProgressModal from '@/bu-components/UploadProgressModal';
import SelectPrepareFileModal from '@/bu-components/SelectPrepareFileModal';
import CopyFileModal from '@/bu-components/CopyFileModal';
import ManagingMembersModal from '@/bu-components/ManagingMembersModal';
import ScanFileModal from '../modal/ScanFileModal';
import CreateFolderModal from '../modal/CreateFolderModal';
const DEL_FOLDER_URL_MAP = {
'MYSELF': 'public/apollo/delFolder',
'COMMON': 'public/apollo/delCommonFolder'
}
// 支持本地上传的文件类型
const loaclFileType = SUPPORT_FILE_TYPE_MAP.join(',');
let count = 0;
class FolderList extends React.Component {
constructor(props) {
super(props);
this.state = {
localFileList: [], // 本地文件列表(待上传)
currentFolder: {}, // 当前文件/文件夹(操作列表中的文件/文件夹的时候需要用到)
uploadFolderPath: {}, // 上传文件的目录,防止中途切换文件夹
renameModalData: {}, // 重命名弹窗
scanFileModal: null, // 预览文件弹窗
showUploadModal: false, // 上传进度弹窗
showCopyFileModal: false, // 复制文件弹窗
showManagingModal: false, // 管理文件查看编辑权限
nonCompliantFileList: [], // 不符合上限的文件列表
parentRights: '' // 继承父级文件夹权限
}
}
componentWillReceiveProps(nextProps) {
const { folderPathList } = nextProps
const currentFolder = folderPathList[folderPathList.length - 1];
this.setState({
currentFolder
})
}
//预览文件
handleSelect = (folder) => {
// 只有文件才有预览功能
if (folder.folderType === 'FOLDER') {
this.handleSelectFolder(folder);
} else {
this.handleScanFile(folder);
}
}
// 预览文件
handleScanFile = (folder) => {
const { folderFormat, folderSize, ossUrl } = folder;
switch (folderFormat) {
case 'PDF':
window.open(ossUrl, "_blank");
break;
case "WORD":
case "DOCX":
case "DOC":
case "EXCEL":
case "PPT":
case "PPTX":
case "PDF":
if ((folderFormat === 'PPT' || folderFormat === 'PPTX' ||
folderFormat === 'DOCX' || folderFormat === 'WORD' ||
folderFormat === 'DOC') && folderSize > 10 * DEFAULT_SIZE_UNIT) {
Modal.confirm({
title: '抱歉,不能在线预览',
content: '由于文件较大,不支持在线预览,请下载后再查看',
// icon: <Icon type="question-circle" theme="filled" style={{ color: '#FF8534' }}></Icon>,
okText: "下载",
onOk: () => {
const a = document.createElement('a');
a.href = ossUrl;
a.click();
}
});
break;
}
if (folderFormat === 'EXCEL') {
Modal.confirm({
title: '抱歉,不能在线预览',
content: ' 该文件类型不支持在线预览,请下载后再查看',
// icon: <Icon type="question-circle" theme="filled" style={{ color: '#FF8534' }}></Icon>,
okText: "下载",
onOk: () => {
const a = document.createElement('a');
a.href = ossUrl;
a.click();
}
});
break;
}
const prefixUrl = "https://view.officeapps.live.com/op/view.aspx?src=";
const scanUrl = `${prefixUrl}${encodeURIComponent(ossUrl)}`
window.open(scanUrl, "_blank");
break;
default:
const scanFileModal = <ScanFileModal
fileType={folderFormat}
item={folder}
close={() => {
this.setState({ scanFileModal: null })
}}
/>
this.setState({ scanFileModal });
break;
}
}
// 选择文件夹
handleSelectFolder = (folder) => {
const { folderPathList, showResultPage, currentRootDisk } = this.props;
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 1;
if (showResultPage) {
folderPathList.pop();
}
folderPathList.push({
id: folder.id,
folderName: folder.folderName
});
this.props.onChangeFolderPath(folderPathList);
this.props.onRefresh({
parentId: folder.id,
parentRights: folder.rights,
folderIdType: employeeDisk ? 'USER' : 'FOLDER'
});
}
// 修改文件路径
handleChangeFolderPath = (folder) => {
const { instId } = window.currentUserInstInfo;
const { id } = folder;
const { currentRootDisk } = this.props;
const params = {
id,
instId,
disk: currentRootDisk.disk,
}
Service.Apollo('public/apollo/folderPath', params).then((res) => {
const { result = [] } = res;
this.props.onChangeFolderPath(result, false);
})
}
parseColumns = () => {
const { currentRootDisk, showResultPage, folderPathList } = this.props;
const hasManagementAuthority = currentRootDisk.uploadPower;
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 1;
const columns = [
{
title: '名称',
key: 'folderName',
dataIndex: 'folderName',
width: '28%',
sorter: (employeeDisk || !hasManagementAuthority) ? false : true,
render: (value, record) => {
const { folderType, folderFormat } = record;
const isFolder = folderType === 'FOLDER';
let imgSrc = !isFolder ?
FILE_TYPE_ICON_MAP[folderFormat] :
'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594871430788.png';
if (employeeDisk) {
imgSrc = 'https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594871440736.png'
}
return (
<div
className="file-name"
onClick={() => this.handleSelect(record)}
>
{
<img
alt="img-src"
className="file-name__icon"
src={imgSrc}
/>
}
<span
className={`file-name__text ${!isFolder ? 'highlight' : ''}`}
>
{value}
</span>
</div>
)
}
},
{
title: '更新时间',
key: 'updated',
dataIndex: 'updated',
sorter: (employeeDisk || !hasManagementAuthority) ? false : true,
render: (value) => {
return <span>{formatDate('YYYY-MM-DD H:i', value)}</span>
}
},
{
title: '大小',
key: 'folderSize',
dataIndex: 'folderSize',
width: '10%',
sorter: (employeeDisk || !hasManagementAuthority) ? false : true,
render: (value, record) => {
const { folderType } = record;
const _fileSize = Number(value);
let _size = `${(_fileSize / DEFAULT_SIZE_UNIT).toFixed(1)}M`;
if (_fileSize < 0.1 * DEFAULT_SIZE_UNIT) {
_size = `${(_fileSize / 1000).toFixed(1)}kb`;
}
return (
<span>{folderType === 'FILE' ? _size : '-'}</span>
)
}
},
{
title: '操作',
key: 'operate',
render: (value, record) => {
if (!(currentRootDisk.disk === 'EMPLOYEE' && (folderPathList.length === 1 || record.folderType === 'FOLDER')) ||
hasManagementAuthority) {
return (
<Dropdown overlay={this.renderMenu(record)} trigger={['hover']}>
<span className="icon iconfont">&#xe756;</span>
</Dropdown>
)
}
return <span>-</span>
}
}
]
// 公共文件需要显示创建者
if (currentRootDisk.disk === 'COMMON') {
columns.splice(1, 0, {
title: '创建者',
key: 'createName',
dataIndex: 'createName'
});
}
// 搜索结果需要显示所在目录
if (showResultPage) {
columns.push({
title: '所在目录',
key: 'parentName',
dataIndex: 'parentName',
render: (value, record) => {
return <span
className="file-path"
onClick={() => this.handleChangeFolderPath(record)}
>
{value || currentRootDisk.folderName}
</span>
}
})
}
return columns;
}
// 删除文件
handleDeleteFolder = (folder) => {
const { currentRootDisk: { disk } } = this.props;
const { instId } = window.currentUserInstInfo;
// 判断此文件是否有关联的课次
Service.Apollo('public/apollo/judgeRelation', {
instId,
folderIds: [folder.id],
}).then((res) => {
// 如果有关联的文件,二次弹窗确认
const hasRelative = !!res.result;
Modal.confirm({
title: '确认删除所选的文件吗?',
content: hasRelative ? '此文件已关联了课次,删除后,学员将不能查看到此文件。' : '删除后,数据将无法恢复。',
// icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
const { currentFolder } = this.state;
Service.Apollo(DEL_FOLDER_URL_MAP[disk], {
instId,
ids: [folder.id],
}).then(() => {
message.success('删除成功');
this.props.onRefresh({ parentId: currentFolder.id || null });
})
}
});
})
}
// 重命名
handleRename = (folder) => {
this.setState({
renameModalData: {
visible: true,
id: folder.id,
folderName: folder.folderName,
}
});
}
// 重命名完成或者取消重命名之后隐藏重命名弹窗
handleRenameDone = (folderName) => {
const { renameModalData, currentFolder } = this.state;
const { folderPathList } = this.props;
// 名称未修改不发送请求
if (folderName === renameModalData.folderName) {
this.setState({ renameModalData: {} });
return;
}
// 判断是否有同名文件
this.handleGetSameNameFiles(folderName).then((res) => {
if ((!!res) || (res && Object.keys(res).length)) {
if (!res.isLook && folderPathList.length === 1) {
message.warning('此目录下已存在同名文件,有疑问请联系机构校长');
} else {
message.warning('此目录下已存在同名文件');
}
return;
}
Service.Apollo('public/apollo/renameFolder', {
id: renameModalData.id,
name: folderName
}).then(() => {
message.success('重命名成功');
this.setState({ renameModalData: {} });
this.props.onRefresh({ parentId: currentFolder.id || null });
})
});
}
// 获取同名文件
handleGetSameNameFiles = async (folderName) => {
const { currentRootDisk, folderPathList } = this.props;
const currentFolder = folderPathList[folderPathList.length - 1];
const { instId } = window.currentUserInstInfo;
const params = {
instId,
name: folderName,
disk: currentRootDisk.disk,
parentId: currentFolder.id,
folderType: 'FOLDER',
}
const res = await Service.Apollo('public/apollo/sameNameFile', params);
const { result } = res;
return result;
}
// 显示移动文件弹窗
handleShowSelectFileModal = (file) => {
this.setState({
currentFile: file,
showSelectFileModal: true
});
}
// 显示复制文件弹窗
handleShowCopyFileModal = (file) => {
this.setState({
currentFile: file,
showCopyFileModal: true
});
}
// 显示管理成员弹窗
handleShowManagingModal = (file) => {
this.setState({
currentFile: file,
showManagingModal: true
});
}
getBlob = (url) => {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'blob'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
}
}
xhr.send()
})
}
saveAs = (blob, filename) => {
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, filename)
} else {
const link = document.createElement('a')
const body = document.querySelector('body')
// 创建对象url
link.href = window.URL.createObjectURL(blob)
link.download = filename
body.appendChild(link)
link.click()
body.removeChild(link)
// 通过调用 URL.createObjectURL() 创建的 URL 对象
window.URL.revokeObjectURL(link.href)
}
}
// 下载文件
handleDownload = (folder) => {
this.getBlob(folder.ossUrl).then((blob) => {
this.saveAs(blob, folder.folderName)
})
}
handleChooseFile = async () => {
// 判断是否欠费,旗舰版用户不需要校验余额
const { instId } = window.currentUserInstInfo;
const ultimateRes = await Service.Business('public/inst/checkInstProduct', {
instId,
productCodeList: ['ULTIMATESELL', 'PIP_TO_ULTIMATE', 'HIGH_TO_ULTIMATE']
});
const { balance } = this.props;
if (balance <= 0 && !ultimateRes.result) {
this.handleShowNoticeModal();
return;
}
const dom = document.querySelector('#detailFileInput');
dom.click();
}
// 准备上传
handleUpload = (event) => {
const fileList = event.target.files;
// 判断文件的大小是否超出了限制
const nonCompliantFileList = [];
const _fileList = [...fileList];
_fileList.map((file, index) => {
let { size, type, name } = file;
if (!type) {
type = getFileTypeByName(name);
}
if (type.indexOf('image') > -1 && size > 50 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (type.indexOf('audio') > -1 && size > 50 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (type.indexOf('video') > -1 && size > 500 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (loaclFileType.indexOf(type) > -1 && size > 100 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
file.key = count++;
return file;
});
// 不符合规则的文件列表
if (nonCompliantFileList.length > 0) {
this.setState({
nonCompliantFileList,
showNonCompliantFileModal: true,
})
} else {
this.handleShowUploadModal(_fileList);
}
// 清空文件,防止第二次无法上传同一个文件
const dom = document.querySelector('#detailFileInput');
dom.value = '';
}
// 显示上传进度弹窗
handleShowUploadModal = (fileList) => {
// 保存当前路径
const { folderPathList } = this.props;
if (fileList.length) {
this.setState({
showUploadModal: true,
localFileList: fileList,
uploadFolderPath: folderPathList[folderPathList.length - 1]
});
}
}
// 上传成功
handleUploadDone = (file, resourceId) => {
this.props.onCreate(file, resourceId);
}
// 取消上传
handleHiddenUploadModal = () => {
this.setState({
showUploadModal: false,
localFileList: []
});
}
// 余额欠费提示弹窗
handleShowNoticeModal = () => {
Modal.info({
title: '无法继续操作',
content: '直播系统已升级,请联系运营老师。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
})
}
// 排序
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
let sort = null;
if (columnKey === 'folderName' && order === 'ascend') { sort = 'NAME_ASC'; }
if (columnKey === 'folderName' && order === 'descend') { sort = 'NAME_DESC'; }
if (columnKey === 'folderSize' && order === 'ascend') { sort = 'SIZE_ASC'; }
if (columnKey === 'folderSize' && order === 'descend') { sort = 'SIZE_DESC'; }
if (columnKey === 'updated' && order === 'ascend') { sort = 'UPDATE_ASC'; }
if (columnKey === 'updated' && order === 'descend') { sort = 'UPDATE_DESC'; }
const { currentFolder } = this.state;
this.props.onRefresh({
sort,
parentId: currentFolder.id || null
});
}
// 操作选项
renderMenu = (record) => {
const { currentRootDisk } = this.props;
// 是否有编辑权限
const hasManagementAuthority = currentRootDisk.uploadPower;
// 公共文件权限和复制权限
const { folderType, rights } = record;
// 判断当前版本
const NewVersion = window.NewVersion;
if (rights) {
const menu = (
<Menu>
{/* ----------------- 5.0公共文件 --------------------*/}
{
rights === "EDIT" && !record.parentId && NewVersion &&
[
<Menu.Item key="administration">
<span onClick={() => this.handleShowManagingModal(record)}>权限管理</span>
</Menu.Item>,
<Menu.Divider key="administration-bottom"/>
]
}
{
folderType === 'FILE' && NewVersion &&
<Menu.Item key="download">
<span onClick={() => { this.handleDownload(record) }}>下载</span>
</Menu.Item>
}
{
currentRootDisk.disk === 'COMMON' && NewVersion &&
<Menu.Item key="copy">
<span onClick={() => this.handleShowCopyFileModal(record)}>复制到</span>
</Menu.Item>
}
{
hasManagementAuthority && rights === "EDIT" && NewVersion &&
[
<Menu.Item key="move">
<span onClick={() => this.handleShowSelectFileModal(record)}>移动到</span>
</Menu.Item>,
<Menu.Item key="rename">
<span onClick={() => this.handleRename(record)}>重命名</span>
</Menu.Item>,
<Menu.Divider key="administration-top"/>,
<Menu.Item key="delete">
<span onClick={() => this.handleDeleteFolder(record)}>删除</span>
</Menu.Item>
]
}
</Menu>
);
return menu;
} else {
const menu = (
<Menu>
{/* ----------------- 4.0公共文件或5.0我的文件 --------------------*/}
{
folderType === 'FILE' &&
<Menu.Item key="download">
<span onClick={() => { this.handleDownload(record) }}>下载</span>
</Menu.Item>
}
{
hasManagementAuthority &&
[
<Menu.Item key="move">
<span onClick={() => this.handleShowSelectFileModal(record)}>移动到</span>
</Menu.Item>,
<Menu.Item key="rename">
<span onClick={() => this.handleRename(record)}>重命名</span>
</Menu.Item>,
<Menu.Item key="delete">
<span onClick={() => this.handleDeleteFolder(record)}>删除</span>
</Menu.Item>
]
}
</Menu>
)
return menu;
}
}
render() {
const {
currentFolder, currentFile, renameModalData, showSelectFileModal,
showUploadModal, localFileList, showCopyFileModal, showManagingModal
} = this.state;
const {
selectedFileIds, folderList, showResultPage,
currentRootDisk, query, totalCount, folderPathList
} = this.props;
const _disk = folderPathList[0].disk;
// 是否有编辑权限
const hasManagementAuthority = currentRootDisk.uploadPower;
return (
<div className="file-list">
<Choose>
<When condition={!_.isEmpty(folderList)}>
<Table
key="table"
rowKey={(record) => record.id}
columns={this.parseColumns()}
dataSource={folderList}
rowSelection={
hasManagementAuthority ? {
selectedRowKeys: selectedFileIds,
onChange: this.props.onChangeRow
} : null
}
pagination={false}
onChange={this.handleChangeTable}
/>
<PageControl
current={query.current - 1}
pageSize={query.size}
total={totalCount}
showSizeChanger={true}
toPage={(page) => {
this.props.onChangePage('current', page + 1);
}}
onShowSizeChange={(current, size) => {
this.props.onChangePage('size', size);
}}
/>
</When>
<Otherwise>
<LottieIcon
title={
<Choose>
<When condition={!showResultPage}>
<input
multiple
type="file"
style={{ display: 'none' }}
id="detailFileInput"
accept=".ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.mp3,.mp4,.xlsx,.xls"
onChange={(e) => this.handleUpload(e)}
/>
{
<Choose>
<When condition={hasManagementAuthority}>
<div>你还没有上传文件,点击
<Tooltip title="支持文件类型:ppt、word、excel、pdf、jpg、mp3、mp4">
<span
className="upload-btn"
onClick={this.handleChooseFile}
>上传文件</span>
</Tooltip>
按钮
</div>
</When>
<Otherwise>
<span>这个文件夹是空的</span>
</Otherwise>
</Choose>
}
</When>
<Otherwise>
<div className="desc">搜索无结果</div>
</Otherwise>
</Choose>
}
/>
</Otherwise>
</Choose>
{
folderPathList &&
<CreateFolderModal
title="重命名"
folderName={renameModalData.folderName}
folderPathList={folderPathList}
isOpen={renameModalData.visible}
onClose={() => { this.setState({ renameModalData: {} }) }}
onOk={this.handleRenameDone}
/>
}
{
!folderPathList &&
<CreateFolderModal
title="重命名"
folderName={renameModalData.folderName}
isOpen={renameModalData.visible}
onClose={() => { this.setState({ renameModalData: {} }) }}
onOk={this.handleRenameDone}
/>
}
<UploadProgressModal
isOpen={showUploadModal}
currentFolder={currentFolder}
fileList={localFileList}
onUpload={this.handleUploadDone}
onCancel={this.handleHiddenUploadModal}
/>
<SelectPrepareFileModal
isOpen={showSelectFileModal}
currentRootDisk={currentRootDisk}
onClose={() => {
this.setState({ showSelectFileModal: false });
}}
onMove={(query) => {
this.setState({ showSelectFileModal: false });
this.props.onMove(query, [currentFile.id]);
}}
/>
{
showCopyFileModal &&
<CopyFileModal
isOpen={showCopyFileModal}
dataInfo={currentFile}
currentRootDisk={currentRootDisk}
onClose={() => {
this.setState({ showCopyFileModal: false });
}}
/>
}
{
showManagingModal &&
<ManagingMembersModal
isOpen={showManagingModal}
dataInfo={currentFile}
disk={_disk}
onClose={() => {
this.setState({ showManagingModal: false });
}}
/>
}
{ this.state.scanFileModal }
{ this.state.chargeModal }
</div>
)
}
}
export default FolderList;
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-06-09 09:47:44
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-16 17:53:33
* @Description: 文件夹管理
*/
import React from 'react';
import { withRouter } from 'react-router-dom';
import { Spin, message, Modal } from 'antd';
import _ from 'underscore';
import Service from '@/common/js/service';
import { SUFFIX_MAP } from '@/domains/class-book/constants';
import OperateArea from './OperateArea';
import FolderList from './FolderList';
const FOLDERLIST_URL_MAP = {
'MYSELF': 'public/apollo/folderList',
'COMMON': 'public/apollo/commonFolderList',
'EMPLOYEE': 'public/apollo/employeeFolderList'
};
export const getFileTypeByName = (name) => {
const nameArray = name.split(".");
const suffix = nameArray[nameArray.length - 1];
return SUFFIX_MAP[suffix];
}
const defaultQuery = {
size: 10,
current: 1,
folderIdType: 'FOLDER'
}
class FolderManage extends React.Component {
constructor(props) {
super(props);
const showTips = localStorage.getItem('showTips');
this.state = {
showTips,
folderPathList: [props.currentRootDisk],
selectedFileIds: [], // 已被选择的文件
selectedFileRights: [], // 已被选择的文件对应权限
folderList: [], // 当前目录下的文件
searchKey: null, // 搜索关键字
totalCount: 0, // 该磁盘总共有的文件个数
showResultPage: false, // 是否显示结果页
loading: false, // 是否正在请求数据
hasManagementAuthority: true, // 是否有管理者权限
query: defaultQuery, // 文件列表的初始请求条件
_parentRights: '', // 父级文件夹权限
}
}
componentWillReceiveProps(nextProps) {
if (!_.isEqual(nextProps.currentRootDisk, this.props.currentRootDisk) ||
nextProps.match.path !== this.props.match.path) {
this.setState({
showResultPage: false,
query: defaultQuery,
folderPathList: [nextProps.currentRootDisk],
selectedFileIds: [], // 切换目录后,取消勾选已经选择的文件
selectedFileRights: []
}, () => {
this.handleFetchFolderList();
});
}
}
componentDidMount() {
// 校验余额(欠费的情况下限制上传文件)
this.handleCheckBalance();
}
// 请求当前目录下的文件列表
handleFetchFolderList = (params = {}) => {
this.setState({
loading: true
}, () => {
const { parentRights } = params;
const { showResultPage, searchName } = this.state;
const { currentRootDisk: { disk } } = this.props;
const { instId } = window.currentUserInstInfo;
const _params = {
...this.state.query,
...params,
disk,
instId,
searchName: showResultPage ? searchName : null,
}
if (parentRights) {
this.setState({ _parentRights: parentRights });
}
Service.Apollo(FOLDERLIST_URL_MAP[disk], _params).then((res) => {
const { result = {} } = res;
const { records = [], total = 0 } = result;
let _records = [];
// 判断是否继承了父级文件权限
if (records.length > 0 && !records[0].rights) {
records.map((item,index) => {
item.rights = parentRights;
_records.push(item);
return _records;
})
this.setState({
loading: false,
folderList: _records,
totalCount: Number(total),
selectedFileIds: [], // 删除之后需要将已经选择的文件清空
selectedFileRights: []
});
} else {
this.setState({
loading: false,
folderList: records,
totalCount: Number(total),
selectedFileIds: [], // 删除之后需要将已经选择的文件清空
selectedFileRights: []
});
}
});
})
}
// 搜索
handleSearch = (value) => {
if (!value) return;
const { currentRootDisk } = this.props;
const folderPathList = [currentRootDisk, {
folderName: `搜索"${value}"`
}]
this.setState({
searchName: value,
folderPathList,
showResultPage: true
}, () => {
// 根据关键字搜索结果页文件列表
this.handleFetchFolderList();
});
}
handleChangeRow = (selectedRowKeys) => {
const { folderList } = this.state;
let _selectedFileRights = [];
selectedRowKeys.map(folderId => {
folderList.map(item => {
if (item.id === folderId) {
_selectedFileRights.push(item.rights);
}
return _selectedFileRights;
})
return _selectedFileRights;
})
this.setState({
selectedFileIds: selectedRowKeys,
selectedFileRights: _selectedFileRights
})
}
// 上传完成之后,添加到文件夹
handleUploadDone = (file, resourceId, uploadFolderPath) => {
const { currentRootDisk } = this.props;
const { folderPathList } = this.state;
const { teacherId, instId } = window.currentUserInstInfo;
const currentFolder = folderPathList[folderPathList.length - 1];
const { id = null } = uploadFolderPath || currentFolder;
let { size, type, name } = file;
if (!type) {
type = getFileTypeByName(name)
}
const params = {
name,
instId,
resourceId,
folderSize: size,
folderFormat: type,
folderTypeEnum: resourceId ? 'FILE' : 'FOLDER',
disk: currentRootDisk.disk,
createUser: teacherId ? "TEACHER" : "ADMIN",
parentId: id
}
Service.Apollo('public/apollo/saveFolder', params).then((res) => {
const query = _.clone(this.state.query);
query.current = 1;
this.setState({ query }, () => {
if (resourceId && !_.isEqual(uploadFolderPath, currentFolder)) return;
this.handleFetchFolderList({ parentId: id });
});
});
}
// 移动文件
handleMove = (query, sourceFileIds = this.state.selectedFileIds) => {
const { targetFolder, selectFolderPathList } = query;
const { currentRootDisk: { disk} } = this.props;
const { folderPathList } = this.state;
const currentFolder = folderPathList[folderPathList.length - 1];
const { id } = targetFolder;
// 将selectedFileIds移动到targetFolder
let params = {
disk,
moveIds: sourceFileIds,
}
if (selectFolderPathList.length > 1) {
params.newParentId = id
}
Service.Apollo('public/apollo/moveFolder', params).then((res) => {
// 判断是否将文件移动到了自身或者子目录
const { result = {} } = res;
const { code, message: _message } = result;
if (code === 'PARAM_ERROR') {
message.warning(_message);
return;
}
if (id == null) {
Modal.success({
title: '文件移动成功',
content: '移动到一级目录的文件仅自己可以查看,如需其他成员查看请设置权限'
})
} else {
message.success('文件移动成功');
}
// 移动成功之后需要将selectedFileIds置为空
this.setState({ selectedFileIds: [] });
this.handleFetchFolderList({ parentId: currentFolder.id, current: 1});
})
}
// 修改文件路径
handleChangeFolderPath = (folderPathList, hasRootDisk = true) => {
// 点击列表页的‘所在目录’,获取的目录是不带根目录的,所以要区分处理
const { currentRootDisk } = this.props;
const { disk } = currentRootDisk;
const _folderPathList = hasRootDisk ? folderPathList : [currentRootDisk, ...folderPathList];
this.setState({
showResultPage: false,
folderPathList: _folderPathList,
});
// 重新获取对应目录下的文件
const currentFolder = _folderPathList[_folderPathList.length - 1];
const { id = null } = currentFolder;
// 判断是否是员工文件的根目录
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 2;
this.setState({
query: {
...this.state.query,
current: 1
}
}, () => {
if (disk !== "COMMON") {
this.handleFetchFolderList({
parentId: id,
folderIdType: employeeDisk ? 'USER' : 'FOLDER'
});
} else {
this.handleFetchFolderList({
parentRights: this.state._parentRights,
parentId: id,
folderIdType: employeeDisk ? 'USER' : 'FOLDER'
});
}
})
}
// 校验余额
handleCheckBalance = async () => {
const { instId } = window.currentUserInstInfo;
const res = await Service.Business("public/liveAssets/query", {
instId,
});
const { result: { balance } } = res;
this.setState({ balance })
};
// 翻页
handleChangePage = (field, value) => {
const { query, folderPathList } = this.state;
const { currentRootDisk } = this.props;
const employeeDisk = currentRootDisk.disk === 'EMPLOYEE' && folderPathList.length === 2;
// 重新获取对应目录下的文件
const currentFolder = folderPathList[folderPathList.length - 1];
const _query = _.clone(query);
_query[field] = value;
if (field === 'size') {
_query.current = 1;
}
this.setState({ query: _query }, () => {
this.handleFetchFolderList({
folderIdType: employeeDisk ? 'USER' : 'FOLDER',
parentId: currentFolder.id
});
})
}
render() {
const {
selectedFileIds, searchKey, query, selectedFileRights,
folderPathList, folderList, totalCount,
showResultPage, showTips, loading,
hasManagementAuthority, balance
} = this.state;
const { currentRootDisk } = this.props;
return (
<Spin spinning={loading} style={{width: '100%'}}>
<div className="folder-manage">
{/* 只有‘我的文件’才显示提示文案 */}
{
currentRootDisk.disk === 'MYSELF' && !showTips &&
<div
className="tips"
>
<span className="tips__text">管理者有权限查看员工个人文件</span>
<span
className="icon iconfont"
onClick={() => {
this.setState({ showTips: true });
localStorage.setItem('showTips', true);
}}
>&#xe6ef;</span>
</div>
}
{/* 操作区:新建文件夹、上传文件、批量移动、批量删除和搜索 */}
<OperateArea
balance={balance}
searchKey={searchKey}
currentRootDisk={currentRootDisk}
showResultPage={showResultPage}
hasManagementAuthority={hasManagementAuthority}
folderPathList={folderPathList}
selectedFileIds={selectedFileIds}
selectedFileRights={selectedFileRights}
onMove={this.handleMove}
onSearch={this.handleSearch}
onCreate={this.handleUploadDone}
onChangeFolderPath={this.handleChangeFolderPath}
onRefresh={this.handleFetchFolderList}
/>
{/* 文件夹列表 */}
<FolderList
query={query}
totalCount={totalCount}
balance={balance}
showResultPage={showResultPage}
currentRootDisk={currentRootDisk}
hasManagementAuthority={hasManagementAuthority}
folderList={folderList}
folderPathList={folderPathList}
selectedFileIds={selectedFileIds}
onChangeRow={this.handleChangeRow}
onChangeFolderPath={this.handleChangeFolderPath}
onMove={this.handleMove}
onUpload={this.handleUploadDone}
onChangePage={this.handleChangePage}
onRefresh={this.handleFetchFolderList}
/>
</div>
</Spin>
)
}
}
export default withRouter(FolderManage);
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-06-09 09:59:27
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-23 09:33:15
* @Description: 文件夹操作区
*/
import React from 'react';
import { Input, Button, Modal, message, Tooltip } from 'antd';
import _ from 'underscore';
import Service from '@/common/js/service';
import { getEllipsText } from '@/domains/basic-domain/utils';
import { DEFAULT_SIZE_UNIT, LOCAL_FILE_TYPE_MAP } from '@/domains/class-book/constants';
import { getFileTypeByName } from '@/domains/class-book/utils';
import UploadProgressModal from '@/bu-components/UploadProgressModal';
import SelectPrepareFileModal from '@/bu-components/SelectPrepareFileModal';
import CopyFileModal from '@/bu-components/CopyFileModal';
import NonCompliantFileModal from '@/bu-components/NonCompliantFileModal';
import CreateFolderModal from '../modal/CreateFolderModal';
const { Search } = Input;
const DEL_FOLDER_URL_MAP = {
'MYSELF': 'public/apollo/delFolder',
'COMMON': 'public/apollo/delCommonFolder'
}
// 支持本地上传的文件类型
const localFileTypeMap = LOCAL_FILE_TYPE_MAP.join(',');
let count = 0;
class OperateArea extends React.Component {
constructor(props) {
super(props);
this.state = {
fileList: [],
localFileList: [], // 本地文件列表(待上传)
folderPathList: [],
nonCompliantFileList: [], // 超出大小的文件列表
uploadFolderPath: {}, // 上传文件的目录,防止中途切换文件夹
showUploadModal: false, // 上传进度弹窗
showSelectFileModal: false, // 移动文件弹窗
showCopyFileModal: false, // 复制文件弹窗
showCreateFolderModal: false, // 创建文件夹弹窗
showNonCompliantFileModal: false, // 文件超出大小限制弹窗
searchKey: props.searchKey
}
}
// 显示创建文件夹弹窗
handleToggleCreateFolderModal = async () => {
const { instId } = window.currentUserInstInfo;
const ultimateRes = await Service.Business('public/inst/checkInstProduct', {
instId,
productCodeList: ['ULTIMATESELL', 'PIP_TO_ULTIMATE', 'HIGH_TO_ULTIMATE']
});
// 校验余额,旗舰版用户不需要校验余额
if (this.props.balance <= 0 && !ultimateRes.result) {
this.handleShowNoticeModal();
return;
}
// 判断当前目录是否在第10层,如果是的话提示最多只能创建10层文件夹
const { folderPathList } = this.props;
if (folderPathList.length > 10) {
message.warning('最多只能创建10层文件夹');
return;
}
this.setState({
showCreateFolderModal: !this.state.showCreateFolderModal
});
}
// 创建成功
handleCreateFolderDone = (folderName) => {
return new Promise((resolve) => {
// 判断是否有同名文件
this.handleGetSameNameFiles(folderName).then((res) => {
if (res) {
message.warning('此目录下已存在同名文件');
return;
}
this.props.onCreate({ name: folderName });
this.handleToggleCreateFolderModal();
resolve(true);
});
})
}
// 获取同名文件
handleGetSameNameFiles = async (folderName) => {
const { currentRootDisk, folderPathList } = this.props;
const currentFolder = folderPathList[folderPathList.length - 1];
const { instId } = window.currentUserInstInfo;
const params = {
instId,
name: folderName,
disk: currentRootDisk.disk,
parentId: currentFolder.id,
folderType: 'FOLDER',
}
const res = await Service.Apollo('public/apollo/sameNameFile', params);
const { result } = res;
return (!!result) || (result && Object.keys(result).length);
}
// 准备上传
handleUpload = (event) => {
const fileList = event.target.files;
// 判断文件的大小是否超出了限制
const nonCompliantFileList = [];
const _fileList = [...fileList];
_fileList.map((file, index) => {
let { size, type, name } = file;
if (!type) {
type = getFileTypeByName(name);
}
if (type.indexOf('image') > -1 && size > 50 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (type.indexOf('audio') > -1 && size > 50 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (type.indexOf('video') > -1 && size > 500 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
if (localFileTypeMap.indexOf(type) > -1 && size > 100 * DEFAULT_SIZE_UNIT) {
nonCompliantFileList.push(file);
_fileList.splice(index, 1);
}
file.key = count++;
return file;
});
// 不符合规则的文件列表
if (nonCompliantFileList.length > 0) {
this.setState({
fileList: _fileList,
nonCompliantFileList,
showNonCompliantFileModal: true,
})
} else {
this.handleShowUploadModal(_fileList);
}
// 清空文件,防止第二次无法上传同一个文件
const dom = document.querySelector('#detailFileInput');
dom.value = '';
}
handleChooseFile = async () => {
// 判断是否欠费,旗舰版用户不需要校验余额
const { balance } = this.props;
const { instId } = window.currentUserInstInfo;
const ultimateRes = await Service.Business('public/inst/checkInstProduct', {
instId,
productCodeList: ['ULTIMATESELL', 'PIP_TO_ULTIMATE', 'HIGH_TO_ULTIMATE']
});
if (balance <= 0 && !ultimateRes.result) {
this.handleShowNoticeModal();
return;
}
const dom = document.querySelector('#detailFileInput');
dom.click();
}
// 显示上传进度弹窗
handleShowUploadModal = (fileList) => {
// 保存当前路径
const { folderPathList } = this.props;
if (fileList.length) {
this.setState({
showUploadModal: true,
localFileList: fileList,
uploadFolderPath: folderPathList[folderPathList.length - 1]
});
}
}
// 上传成功
handleUploadDone = (file, resourceId) => {
const { uploadFolderPath } = this.state;
this.props.onCreate(file, resourceId, uploadFolderPath);
}
// 取消上传
handleHiddenUploadModal = () => {
this.setState({
showUploadModal: false,
localFileList: []
});
}
// 批量删除文件
handleDeleteFile = () => {
const { instId } = window.currentUserInstInfo;
// 判断此文件是否有关联的课次
const { selectedFileIds, currentRootDisk: { disk }, folderPathList } = this.props;
Service.Apollo('public/apollo/judgeRelation', {
instId,
folderIds: selectedFileIds,
}).then((res) => {
// 如果有关联的文件,二次弹窗确认
const hasRelative = !!res.result;
Modal.confirm({
title: '确认删除所选的文件吗?',
content: hasRelative ? '此文件已关联了课次,删除后,学员将不能查看到此文件。' : '删除后,数据将无法恢复。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
const currentFolder = folderPathList[folderPathList.length - 1];
Service.Apollo(DEL_FOLDER_URL_MAP[disk], {
instId,
ids: selectedFileIds,
}).then(() => {
message.success('删除成功');
this.props.onRefresh({ parentId: currentFolder.id || null });
})
}
})
})
}
// 显示移动文件弹窗
handleShowSelectFileModal = () => {
this.setState({
showSelectFileModal: true
})
}
// 显示复制文件弹窗
handleShowCopyFileModal = () => {
this.setState({
showCopyFileModal: true
})
}
// 格式化文件路径
handleFormatFilePath = (folderPathList) => {
let currentIndex = folderPathList.length - 1;
if (currentIndex >= 4) {
const _currentIndex = currentIndex - 3;
const _folderPathList = [...folderPathList];
_folderPathList.splice(1, _currentIndex, {
folderName: '...',
key: 'ellipses'
});
return _folderPathList;
}
return folderPathList;
}
// 切换当前显示的文件夹
handleChangeCurrentFolder = (folderPath) => {
// 点击中间的省略号或者搜索的关键字,不请求数据
const { folderPathList } = this.props;
const index = _.findIndex(folderPathList, (item) => item.id === folderPath.id);
if (folderPath.key === 'ellipses' ||
index === folderPathList.length - 1 ||
(!folderPath.key && !folderPath.disk && !folderPath.id)) return;
folderPathList.splice(index+1, folderPathList.length);
this.props.onChangeFolderPath(folderPathList);
}
// 余额欠费提示弹窗
handleShowNoticeModal = () => {
Modal.info({
title: '无法继续操作',
content: '直播系统已升级,请联系运营老师。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>
})
}
// 修改文件/文件夹名称
handleChangeSearchKey = (e) => {
const { value } = e.target;
this.setState({ searchKey: value });
}
handleSearch = () => {
const { searchKey } = this.state;
this.props.onSearch(searchKey)
}
render() {
const {
searchKey, showCreateFolderModal, showUploadModal, showSelectFileModal, showCopyFileModal,
localFileList, nonCompliantFileList, showNonCompliantFileModal
} = this.state;
const {
selectedFileIds, showResultPage, selectedFileRights,
currentRootDisk, folderPathList,
} = this.props;
// 是否有编辑权限
const hasManagementAuthority = currentRootDisk.uploadPower;
// 是否有文件被选择了
const hasSelectedFile = !!selectedFileIds.length;
// 勾选文件夹里面是否存在权限不足的
const hasEnoughRights = selectedFileRights.includes('LOOK_DOWNLOAD');
const _folderPathList = this.handleFormatFilePath(folderPathList);
const currentFolder = _folderPathList[_folderPathList.length - 1];
return (
<div className="operate-area">
<div className={`operate-area__opt ${hasManagementAuthority ? 'visible' : 'hidden'}`}>
<input
multiple
type="file"
style={{ display: 'none' }}
id="detailFileInput"
accept=".ppt,.pptx,.doc,.docx,.pdf,.jpg,.jpeg,.png,.mp3,.mp4,.xlsx,.xls"
onChange={(e) => this.handleUpload(e)}
/>
<Tooltip title="支持文件类型:ppt、word、excel、pdf、jpg、mp3、mp4,上传后默认私密,可邀请其他成员协作">
<Button
type={!showResultPage?"primary":""}
disabled={showResultPage}
onClick={this.handleChooseFile}
>
<span className="icon iconfont">&#xe7a0;</span>
上传文件
</Button>
</Tooltip>
<Button
onClick={this.handleToggleCreateFolderModal}
disabled={showResultPage}
>
<span className="icon iconfont">&#xe7a2;</span>
新建文件夹
</Button>
{
hasSelectedFile &&
<Button
onClick={this.handleShowSelectFileModal}
disabled={hasEnoughRights}
>
移动
</Button>
}
{
hasSelectedFile &&
<Button
onClick={this.handleDeleteFile}
disabled={hasEnoughRights}
>
删除
</Button> }
</div>
<div className="operate-area__search">
<Search
placeholder='搜索文件/文件夹'
value={searchKey}
onSearch={this.handleSearch}
onChange={this.handleChangeSearchKey}
style={{width: '245px'}}
/>
</div>
<div className="operate-area__bottom">
<div className="bread-crumbs">
{
_folderPathList.map((folderPath, index) => {
return (
<div
title={folderPath.folderName}
className="file-path"
key={`file-path${index}`}
onClick={() => this.handleChangeCurrentFolder(folderPath, index)}
>{getEllipsText(folderPath.folderName, 10)}</div>
)
})
}
</div>
</div>
<CreateFolderModal
isOpen={showCreateFolderModal}
folderPathList={folderPathList}
onClose={this.handleToggleCreateFolderModal}
onOk={this.handleCreateFolderDone}
disk={this.props.currentRootDisk.disk}
/>
<UploadProgressModal
isOpen={showUploadModal}
fileList={localFileList}
currentFolder={currentFolder}
onUpload={this.handleUploadDone}
onCancel={this.handleHiddenUploadModal}
/>
<SelectPrepareFileModal
isOpen={showSelectFileModal}
currentRootDisk={currentRootDisk}
onClose={() => {
this.setState({ showSelectFileModal: false });
}}
onMove={(query) => {
this.setState({ showSelectFileModal: false });
this.props.onMove(query);
}}
/>
<CopyFileModal
isOpen={showCopyFileModal}
currentRootDisk={currentRootDisk}
onClose={() => {
this.setState({ showCopyFileModal: false });
}}
onMove={(targetFolder, folderPathList) => {
this.setState({ showCopyFileModal: false });
this.props.onMove(targetFolder, [], folderPathList);
}}
/>
<NonCompliantFileModal
isOpen={showNonCompliantFileModal}
fileList={nonCompliantFileList}
onClose={() => {
this.setState({ showNonCompliantFileModal: false });
}}
onOk={() => {
this.setState({ showNonCompliantFileModal: false });
this.handleShowUploadModal(this.state.fileList)
}}
/>
{ this.state.chargeModal }
</div>
)
}
}
export default OperateArea;
\ No newline at end of file
import React from 'react';
import { Modal } from 'antd';
import Service from '@/common/js/service';
import { DISK_MAP } from '@/domains/class-book/constants';
import FolderManage from './components/FolderManage';
import DiskList from './components/DiskList';
import './index.less';
const { gmtCreate } = window.currentUserInstInfo || {};
// 判断是新用户还是老用户(gmtCreate小于上线日期的话就是老用户)
const onlineDate = +new Date('2020-07-17 00:00:00');
const isOldUser = gmtCreate <= onlineDate;
const defaultRootDisk = {
folderName: '我的文件',
disk: 'MYSELF',
uploadPower: false
}
class PrepareLessonPage extends React.Component {
constructor(props) {
super(props);
const prepareLessonTips = localStorage.getItem('prepare_lesson_tips');
this.state = {
prepareLessonTips,
diskList: [], // 可见磁盘目录
currentRootDisk: defaultRootDisk
}
}
componentWillMount() {
this.handleFetchDiskList();
}
handleFetchDiskList = async () => {
const res = await Service.Apollo('public/apollo/getUserDisk', {});
const { result = [] } = res;
const diskList = result.map((item) => {
return {
...item,
folderName: DISK_MAP[item.disk]
}
});
this.setState({
diskList,
currentRootDisk: diskList[0] || defaultRootDisk
});
}
handleChangeDisk = (disk) => {
this.setState({
currentRootDisk: disk
});
}
render() {
const { currentRootDisk, prepareLessonTips, diskList } = this.state;
return (
<div className="prepare-lesson-page page">
<div className="content-header">资料云盘</div>
<div className="box content-body">
<DiskList
diskList={diskList}
currentRootDisk={currentRootDisk}
onChange={this.handleChangeDisk}
/>
<FolderManage
currentRootDisk={currentRootDisk}
/>
</div>
{/* 老用户显示弹窗提示 */}
<Modal
title="备课本改版"
visible={!prepareLessonTips && isOldUser}
footer={null}
width={680}
maskClosable={false}
className="prepare-lesson-upgrade-modal"
onCancel={() => {
this.setState({
prepareLessonTips: true
})
}}
>
<div className="title">“备课本” 升级为 “资料云盘” 了!</div>
<div className="upgrade-list">
<div className="upgrade-list__item">
<img src="https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594780611301.png" alt=""/>
<div className="item-title">存储更便捷</div>
<div className="item-sub-title">讲次关联模式升级文件夹模式,存储不再受讲次限制</div>
</div>
<div className="upgrade-list__item">
<img src="https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594780629259.png" alt=""/>
<div className="item-title">结构更清晰</div>
<div className="item-sub-title">新增“我的文件”“公共文件”“员工文件”,满足机构存储需求</div>
</div>
<div className="upgrade-list__item">
<img src="https://xiaomai-image.oss-cn-hangzhou.aliyuncs.com/1594780641665.png" alt=""/>
<div className="item-title">同步更方便</div>
<div className="item-sub-title">支持主管直接查看员工文件,优质资料一目了然</div>
</div>
</div>
<div
className="footer"
onClick={() => {
this.setState({ prepareLessonTips: true });
localStorage.setItem('prepare_lesson_tips', true);
}}
>我知道了</div>
</Modal>
</div>
)
}
}
export default PrepareLessonPage;
\ No newline at end of file
.prepare-lesson-page {
.ant-upload-list {
display: none;
}
.ant-spin-nested-loading {
width: 100%;
}
table tr, table tr th {
background-color: #FFF !important;
}
table tr th.ant-table-column-has-actions.ant-table-column-has-sorters:hover {
background-color: #FFF !important;
}
.content-body {
display: flex;
}
.disk-list__wrap {
min-width: 200px;
border-right: 1px solid #EEE;
position: relative;
.item {
display: flex;
align-items: center;
height: 50px;
line-height: 50px;
margin-bottom: 8px;
padding-left: 32px;
text-align: center;
position: relative;
cursor: pointer;
&.hidden {
display: none;
}
&:hover {
background: rgba(255,133,52,0.06);
}
&.active {
background: rgba(255,133,52,0.06);
.iconfont, .disk-name {
font-weight: 500;
}
&::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100%;
background-color: #FF8534;
}
}
.iconfont, .disk-name {
color: #333;
}
.iconfont {
font-size: 20px;
margin-right: 8px;
}
.disk-name {
font-size: 16px;
}
}
.guide-href {
position: absolute;
left: 33px;
bottom: 24px;
color: #FF7519;
}
}
.folder-manage {
padding: 0 16px;
width: 100%;
.ant-upload-list {
display: none;
}
.tips {
border-radius: 4px;
margin-bottom: 16px;
height: 32px;
line-height: 32px;
padding: 0 16px;
background-color: #FFF0E7;
display: flex;
align-items: center;
justify-content: space-between;
.iconfont {
cursor: pointer;
font-size: 12px;
opacity: 0.45;
}
}
.operate-area {
position: relative;
&__opt {
width: fit-content;
&.visible {
display: block;
}
&.hidden {
display: none;
}
.ant-btn {
margin-right: 8px;
.iconfont {
margin-right: 4px;
}
}
}
&__search {
position: absolute;
top: 0;
right: 0;
}
&__bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin: 16px 0;
.file-path {
display: inline-block;
color: #333;
position: relative;
margin-right: 12px;
&:last-child {
color: #999;
}
&:not(:last-child) {
cursor: pointer;
&:hover {
color: #FF8534;
}
&::before {
content: '/';
position: absolute;
color: #CCC;
right: -8px;
}
}
}
.load-count__inner {
color: #999;
}
}
}
.file-list {
height: ~'calc(100vh - 240px)';
overflow: scroll;
.file-name {
display: flex;
align-items: center;
cursor: pointer;
&__icon {
width: 24px;
margin-right: 4px;
color: #FBD140;
}
&__text:hover {
color: #FF7519;
}
}
.file-path {
cursor: pointer;
text-decoration: underline;
&:hover {
color: #FF7519;
}
}
.ant-dropdown-trigger {
cursor: pointer;
}
.lottie-icon {
&__title {
color: #999;
.upload-btn {
color: #FF7519;
margin: 0 4px;
cursor: pointer;
}
}
}
}
}
}
.prepare-lesson-upgrade-modal {
.ant-modal-header, .ant-modal-close-x {
display: none;
}
.title {
color: #333;
font-size: 24px;
font-weight: 500;
text-align: center;
margin-top: 16px;
}
.upgrade-list {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 40px;
padding: 0 26px;
&__item {
text-align: center;
max-width: 170px;
img {
width: 157px;
}
.item-title {
color: #333;
margin-top: 5px;
}
.item-sub-title {
color: #999;
font-size: 12px;
margin-top: 8px;
}
}
}
.footer {
width: 136px;
height: 40px;
line-height: 40px;
border-radius: 20px;
margin: auto;
margin-top: 46px;
margin-bottom: 8px;
text-align: center;
font-size: 16px;
color: #FFF;
background-color: #FC9C6B;
cursor: pointer;
}
}
.create-folder-modal .ant-modal-body {
min-height: 101px !important;
}
import React from 'react';
import { Modal, Input, Form, message } from 'antd';
import ShowTips from '@/bu-components/ShowTip';
import './CreateFolderModal.less';
class CreateFolderModal extends React.Component {
constructor(props) {
super(props);
this.state = {
validate: true,
warnText: null,
folderName: props.folderName
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.isOpen) {
this.setState({
folderName: nextProps.folderName
});
}
}
componentDidUpdate(prevProps) {
// 输入框自动聚焦
this.refs.folderNameInput && this.refs.folderNameInput.focus();
}
handleChangeFolderName = (event) => {
const { value } = event.target;
// 是否超出了限制
const hasExceededLimit = value.length >= 50;
this.setState({
folderName: value,
validate: !hasExceededLimit,
warnText: hasExceededLimit && '名称不能超过50个字'
});
}
handleOk = () => {
this.props.form.validateFields((err) => {
const { folderName, validate } = this.state;
if (!folderName) {
this.setState({
validate: false,
warnText: '名称不能为空'
});
return;
};
if (!validate) return;
this.props.onOk(folderName).then((res) => {
// 防止新建和重命名的时候重复提示
if (!this.props.title) {
message.success('创建文件夹成功');
}
});
});
}
handleCancel = () => {
this.setState({
validate: true
});
this.props.onClose();
}
render() {
const { folderName, warnText, validate } = this.state;
const { isOpen, title = '新建文件夹', folderPathList } = this.props;
let showTip = null;
if (folderPathList) {
showTip = folderPathList.length < 2 ? true : false;
} else {
showTip = true;
}
return (
<Modal
title={title}
visible={isOpen}
onCancel={this.handleCancel}
onOk={this.handleOk}
width={448}
className="create-folder-modal"
>
<Form>
<Form.Item
required
help={ !validate && warnText}
validateStatus={!validate ? 'error' : null}
>
{
this.props.disk === 'COMMON' && window.NewVersion && showTip &&
<div className="show-tip">
<ShowTips message="创建后仅自己可以查看,如需其他成员查看请设置权限" />
</div>
}
<Input
value={folderName}
placeholder="请输入文件夹名称"
style={{width: '400px'}}
onChange={this.handleChangeFolderName}
maxlength="50"
ref="folderNameInput"
/>
</Form.Item>
</Form>
</Modal>
)
}
}
// export default Form.create()(CreateFolderModal);
export default CreateFolderModal;
\ No newline at end of file
import React from 'react';
import { Modal } from "antd";
import { Player, BigPlayButton } from "video-react";
import "./ScanFileModal.less";
class ScanFileModal extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleCancel = () => {
this.props.close();
};
render() {
const { fileType, item } = this.props;
return (
<Modal
visible={true}
onCancel={this.handleCancel}
title="查看文件"
footer={null}
width={680}
className="scan-file-modal"
>
<div className="scan-file-modal" style={{ width: 632 }}>
{(fileType === "JPG" || fileType === 'PNG') && (
<img
src={item.ossAddress || item.ossUrl}
alt="oss-address"
style={{ width: 632, objectFit: "cover" }}
/>
)}
{fileType === "MP4" && (
<div>
<Player
src={item.ossAddress || item.ossUrl}
fluid={false}
height={306}
width={"100%"}
>
<BigPlayButton position="center" />
</Player>
<video
style={{ display: "none" }}
ref="video"
src={item.ossAddress || item.ossUrl}
constrols="constrols"
id="video"
></video>
</div>
)}
{fileType === "MP3" && (
<div className="scan-file-modal__mp3">
<Player
src={item.ossAddress || item.ossUrl}
fluid={false}
height={306}
width={"100%"}
autoPlay={true}
>
<BigPlayButton position="center" />
</Player>
</div>
)}
</div>
</Modal>
);
}
}
export default ScanFileModal;
.scan-file-modal {
z-index: 10;
&__mp3 {
.video-react-video {
background: #F1F3F6 url("https://image.xiaomaiketang.com/xm/3y8cRkJA5K.png") no-repeat center;
background-size: 60px 60px;
}
.mp3-img {
width: 60px;
height: 60px;
}
}
}
/*
* @Author: 吴文洁
* @Date: 2019-07-10 10:30:49
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-09 11:27:58
* @LastEditors: wufan
* @LastEditTime: 2020-12-09 16:50:18
* @Description:
*/
import React, { useContext, useEffect, useState } from 'react';
......@@ -21,6 +21,7 @@ declare var window: any;
const App: React.FC = (props: any) => {
const ctx: any = useContext(XMContext);
const userId = User.getUserId();
window.currentUserInstInfo = {adminId: "1305385165390426114", adminName: "吴帆", gender: "UNKNOWN", parentId: "1305385165390426114", roleRemark: "", instId: "1213001850820476929" }
window.ctx = ctx;
useEffect(() => {
......
/*
* @Author: 吴文洁
* @Date: 2020-04-29 10:26:32
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-09 15:00:48
* @LastEditors: wufan
* @LastEditTime: 2020-12-09 19:06:51
* @Description: 内容线路由配置
*/
import EmployeesManagePage from '@/modules/store-manege/EmployeesManagePage';
......@@ -11,6 +11,8 @@ import UserManagePage from '@/modules/store-manege/UserManagePage';
import StoreDecorationPage from '@/modules/store-manege/StoreDecorationPage';
import CourseCatalogPage from '@/modules/store-manege/CourseCatalogPage'
import LiveCoursePage from '@/modules/course-manage/LiveCoursePage'
import ClassBook from '@/modules/class-book';
const mainRoutes = [
{
path: '/employees-manage',
......@@ -42,6 +44,11 @@ const mainRoutes = [
component:LiveCoursePage,
name: '课程分类'
},
{
path: '/resource-disk',
component:ClassBook,
name: '资料云盘'
},
]
export default mainRoutes;
\ No newline at end of file
......@@ -17,12 +17,12 @@ export const menuList: any = [
// ]
// },
// {
// groupName: "资料云盘",
// groupCode: "CloudDisk",
// icon: '&#xe83b;',
// link: '/CloudDisk'
// },
{
groupName: "资料云盘",
groupCode: "CloudDisk",
icon: '&#xe83b;',
link: '/resource-disk'
},
{
groupName: "店铺管理",
groupCode: "CloudShop",
......@@ -43,11 +43,11 @@ export const menuList: any = [
groupCode: "CourseCategory",
link: '/course-catalog'
},
// {
// groupName: "店铺装修",
// groupCode: "ShopDecoration",
// link: '/store-decoration'
// }
{
groupName: "店铺装修",
groupCode: "ShopDecoration",
link: '/store-decoration'
}
]
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment