Commit 9b0932b3 by wufan

fix:合并master

parents 12d08908 9aff52ac
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
# 小麦企学院B端
## Available Scripts
小麦企学院是一个全场景企业培训数字化平台。提供了多场景解决方案,同时满足对客对内培训需求;提供了完善的服务保障体系,助力各行业持续经营进阶以及客户成功。
In the project directory, you can run:
## 目录
### `yarn start`
- [安装](#安装)
- [用法](#用法)
- [开发](#开发)
- [发布地址](#发布地址)
- [关联项目](#关联项目)
- [项目网址](#项目网址)
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
## 安装
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
```bash
git clone ssh://git@xmgit.ixm5.cn:10022/xiaomai-cloud-class/xiaomai-cloud-class-web.git
cd xiaomai-cloud-class-web
cnpm install
```
### `yarn test`
## 用法
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
```bash
npm start
```
### `yarn build`
## 开发
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
```bash
git add .
git commit -m 'commit comments'
git pull
git push
```
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
## 发布地址
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
开发环境发布地址: [http://jenkins-web.ixm5.cn/view/%E5%89%8D%E7%AB%AF/job/web-dev-%E5%B0%8F%E9%BA%A6%E4%BC%81%E5%9F%B9-cloud-class-web/](http://jenkins-web.ixm5.cn/view/%E5%89%8D%E7%AB%AF/job/web-dev-%E5%B0%8F%E9%BA%A6%E4%BC%81%E5%9F%B9-cloud-class-web/)
### `yarn eject`
正式环境发布地址:[http://jenkins-web.ixm5.cn/view/%E5%89%8D%E7%AB%AF-%E7%BA%BF%E4%B8%8A/job/web-prod-cloud-class/](http://jenkins-web.ixm5.cn/view/%E5%89%8D%E7%AB%AF-%E7%BA%BF%E4%B8%8A/job/web-prod-cloud-class/)
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
## 关联项目
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
小麦企学院c端: [https://xmgit.ixm5.cn/xiaomai-cloud-class/xiaomai-live-room](https://xmgit.ixm5.cn/xiaomai-cloud-class/xiaomai-live-room)
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
## 项目网址
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
dev环境: [https://dev.xiaomai5.com/xiaomai-cloud-class-web/index.html#/login](https://dev.xiaomai5.com/xiaomai-cloud-class-web/index.html#/login)
## Learn More
rc环境: [https://rc.xiaomai5.com/xiaomai-cloud-class-web/index.html#/login](https://rc.xiaomai5.com/xiaomai-cloud-class-web/index.html#/login)
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
灰度环境: [https://res.xiaomai5.com/xiaomai-cloud-class-web/gray/index.html#/login](https://res.xiaomai5.com/xiaomai-cloud-class-web/gray/index.html#/login)
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `yarn build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
线上环境: [https://res.xiaomai5.com/xiaomai-cloud-class-web/index.html#/login](https://res.xiaomai5.com/xiaomai-cloud-class-web/index.html#/login)
\ No newline at end of file
......@@ -41,6 +41,7 @@
"dom-to-image": "^2.6.0",
"dotenv": "8.2.0",
"dotenv-expand": "5.1.0",
"download": "^8.0.0",
"eslint": "^6.6.0",
"eslint-config-react-app": "^5.2.1",
"eslint-loader": "3.0.3",
......
......@@ -19,7 +19,7 @@ class ChooseMembersModal extends React.Component {
this.state = {
allUserList : [], // 所有成员列表
selectUserList: [], // 已选则成员
temporaryList: [], // 临时用户成员列表(搜索时使用)
temporaryList: [], // 临时学员成员列表(搜索时使用)
instId : window.currentUserInstInfo.instId, // 机构Id
searchKey : null, // 搜索内容
selectedRowKeys : [], // 勾选的成员
......@@ -229,7 +229,7 @@ class ChooseMembersModal extends React.Component {
selectedColumnsRight = () => {
const selectColumns = [
{
title: '用户名',
title: '学员名',
key: 'adminNameRight',
dataIndex: 'adminName',
width: '70%',
......
/*
* @Author: 吴文洁
* @Date: 2020-08-31 09:34:25
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-04-13 17:56:34
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-05-12 17:27:08
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -101,7 +101,12 @@ class User {
setCustomerStoreId(value:any) {
return Storage.set(`${PREFIX}_customerStoreId`,value);
}
setIdentifier(value:any){
return Storage.set(`${PREFIX}_identifier`,value);
}
getIdentifier(){
return Storage.get(`${PREFIX}_identifier`);
}
clearUserInfo(){
Storage.remove(`${USER_PREFIX}_token`);
Storage.remove(`${USER_PREFIX}_userId`);
......
......@@ -115,7 +115,7 @@ class MultipleDatePicker extends React.Component {
}
handleShowDetail(time, className) {
if(!this.props.canSelectTodayBefore && className === 'before-disabled') {
if((!this.props.canSelectTodayBefore && className === 'before-disabled') || this.props.disabled) {
return
}
let date = time.valueOf();
......
/*
* @Author: sunbingqing
* @Date: 2019-07-24 17:21:00
* @Last Modified by: mikey.zhaopeng
* @Last Modified time: 2020-11-25 14:36:16
* @Last Modified by: chenshu
* @Last Modified time: 2021-05-25 15:14:55
*/
import React from 'react';
......@@ -43,7 +43,7 @@ const PageControl = (props: PageControlProps) => {
total={total || 0}
pageSizeOptions={['10', '20', '50', '100']}
onShowSizeChange={onShowSizeChange}
showSizeChanger={showSizeChanger}
showSizeChanger={!!showSizeChanger}
pageSize={pageSize || 10}
onChange={(page: any) => _onChange(page)}
showQuickJumper
......
......@@ -6,6 +6,7 @@
import domtoimage from 'dom-to-image'
import { Popover } from 'antd'
import React from 'react'
import $ from 'jquery';
// 时间控件优化方法
window.setCorrectDate = date => {
......@@ -958,17 +959,8 @@ window.copyText = textContent => {
}
window.downloadFile = (dataURL, fileName) => {
const eleDom = document.createElement('a')
const blob = window.convertBase64ToBlob(dataURL)
const href = window.getObjectURL(blob)
$(eleDom)
.attr({
href,
download: fileName
})
.get(0)
.click()
window.download(blob, fileName, 'image/png');
}
window.getCouponRule = record => {
......
/*
* @Author: wufan
* @Date: 2020-11-25 18:25:02
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-22 13:58:04
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 16:25:01
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -23,7 +23,7 @@ export default class courseService {
return getQrcode(params);
}
// 获取用户上课数据
// 获取学员上课数据
static fetchUserData(params: any) {
return fetchUserData(params);
}
......
<!--
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-05-26 16:25:17
* @LastEditors: wufan
* @LastEditTime: 2021-05-27 10:24:06
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......@@ -13,10 +13,8 @@
<link rel="icon" href="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<meta name="description" content="小麦企学院,一站式企业培训数字化服务商,通过“工具+内容”,帮助企业快速从0到1搭建数字化培训体系,并让整个培训过程可视化,降低培训成本,提升培训效率。">
<meta name="keywords" content="小麦企学院,企业培训,员工培训,企业大学,企业内训,企业外训,培训计划,培训素材,企培,企训,资料云盘,培训课程,培训任务,直播课,视频课,图文课,线下课,知识库,作业,考试,排行榜,培训类别管理,定制培训计划,管理数据,学习数据,企学院,资料共享,培训数字化,数字化培训,培训工具,在线培训,线上培训,培训saas,培训管理,企业微信培训,对客培训,客户培训,直播培训,互联网培训,新员工培训,管理培训,管理者培训,工人培训,制造业培训,餐饮培训,服务业培训,零售培训,门店培训,工厂培训,车间培训,培训补贴,人事培训,财务培训,职场培训,企业学院平台,教育企业学院,教育企业平台,教育平台学院,企业学习,酷学院,小鹅通,企业学院,云学堂,时代光华,云课堂,魔学院,云大学,米知云,授课学堂">
<!-- <link rel="apple-touch-icon" href="../src/common/images/logo.png" /> -->
<link rel="shortcut icon" href="https://image.xiaomaiketang.com/xm/c4KiP2epBP.png">
......@@ -57,5 +55,160 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script>
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.download = factory();
}
}(this, function () {
return function download(data, strFileName, strMimeType) {
var self = window, // this script is only for browsers anyway...
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
mimeType = strMimeType || defaultMime,
payload = data,
url = !strFileName && !strMimeType && payload,
anchor = document.createElement("a"),
toString = function(a){return String(a);},
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
fileName = strFileName || "download",
blob,
reader;
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
payload=[payload, mimeType];
mimeType=payload[0];
payload=payload[1];
}
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
fileName = url.split("/").pop().split("?")[0];
anchor.href = url; // assign href prop to temp anchor
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
var ajax=new XMLHttpRequest();
ajax.open( "GET", url, true);
ajax.responseType = 'blob';
ajax.onload= function(e){
download(e.target.response, fileName, defaultMime);
};
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
return ajax;
} // end if valid url?
} // end if url?
//go ahead and download dataURLs right away
if(/^data\:[\w+\-]+\/[\w+\-]+[,;]/.test(payload)){
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
payload=dataUrlToBlob(payload);
mimeType=payload.type || defaultMime;
}else{
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
saver(payload) ; // everyone else can save dataURLs un-processed
}
}//end if dataURL passed?
blob = payload instanceof myBlob ?
payload :
new myBlob([payload], {type: mimeType}) ;
function dataUrlToBlob(strUrl) {
var parts= strUrl.split(/[:;,]/),
type= parts[1],
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
binData= decoder( parts.pop() ),
mx= binData.length,
i= 0,
uiArr= new Uint8Array(mx);
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
return new myBlob([uiArr], {type: type});
}
function saver(url, winMode){
if ('download' in anchor) { //html5 A[download]
anchor.href = url;
anchor.setAttribute("download", fileName);
anchor.className = "download-js-link";
anchor.innerHTML = "downloading...";
anchor.style.display = "none";
document.body.appendChild(anchor);
setTimeout(function() {
anchor.click();
document.body.removeChild(anchor);
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
}, 66);
return true;
}
// handle non-a[download] safari as best we can:
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
url=url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
if(!window.open(url)){ // popup blocked, offer direct download:
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
}
return true;
}
//do iframe dataURL download (old ch+FF):
var f = document.createElement("iframe");
document.body.appendChild(f);
if(!winMode){ // force a mime that will download:
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
}
f.src=url;
setTimeout(function(){ document.body.removeChild(f); }, 333);
}//end saver
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
return navigator.msSaveBlob(blob, fileName);
}
if(self.URL){ // simple fast and modern way using Blob and URL:
saver(self.URL.createObjectURL(blob), true);
}else{
// handle non-Blob()+non-URL browsers:
if(typeof blob === "string" || blob.constructor===toString ){
try{
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
}catch(y){
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
}
}
// Blob but not URL support:
reader=new FileReader();
reader.onload=function(e){
saver(this.result);
};
reader.readAsDataURL(blob);
}
return true;
}; /* end download() */
}))
</script>
</body>
</html>
/*
* @Author: wufan
* @Date: 2020-11-30 10:47:38
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-02 10:22:34
* @Description: 用户管理页面
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 17:45:53
* @Description: 学员管理页面
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -60,7 +60,7 @@ function UserManagePage() {
function parseColumn() {
const list = [
{
title: "用户姓名",
title: "学员姓名",
dataIndex: "nickName",
render: (val: string, item: any) => {
return (
......@@ -117,16 +117,16 @@ function UserManagePage() {
return (
<div className=" page user-manage-page">
<div className="content-header">用户管理</div>
<div className="content-header">学员管理</div>
<div className="box">
<div className="box-header">
<div className="header-item">
<span className="item-name">搜索用户</span>
<span className="item-name">搜索学员</span>
<Search
style={{
width: 300,
}}
placeholder="搜索用户姓名/手机号"
placeholder="搜索学员姓名/手机号"
onSearch={(value) => {
const _query = { ...query };
if (value) {
......@@ -149,7 +149,7 @@ function UserManagePage() {
</div>
<div className="header-item">
<span className="item-name">用户来源:</span>
<span className="item-name">学员来源:</span>
<Select
style={{ width: '100%' }}
placeholder="请选择"
......@@ -195,7 +195,7 @@ function UserManagePage() {
onClick={() => {
handleToAddEmployee();
}}
>添加用户</Button>
>添加学员</Button>
}
<div className="box-body">
<Table
......
......@@ -21,7 +21,7 @@ class ChooseMembersModal extends React.Component {
isOpen: props.isOpen,
allUserList : [], // 所有成员列表
selectUserList: [], // 已选则成员
temporaryList: [], // 临时用户成员列表(搜索时使用)
temporaryList: [], // 临时学员成员列表(搜索时使用)
instId : window.currentUserInstInfo.instId, // 机构Id
searchKey : null, // 搜索内容
selectedRowKeys : [], // 勾选的成员
......@@ -230,7 +230,7 @@ class ChooseMembersModal extends React.Component {
selectedColumnsRight = () => {
const selectColumns = [
{
title: '用户名',
title: '学员名',
key: 'nameRight',
dataIndex: 'name',
width: '70%',
......@@ -274,7 +274,7 @@ class ChooseMembersModal extends React.Component {
isOpen,
selectObject,
} = this.state;
const title = type === 'USER' ? '添加员工' : '添加用户';
const title = type === 'USER' ? '添加员工' : '添加学员';
return (
<div>
{/* 添加学员页面 */}
......@@ -284,7 +284,7 @@ class ChooseMembersModal extends React.Component {
onCancel={() => this.handleClose()}
onOk={() => {
if (_.isEmpty(selectUserList)) {
message.warning(type === 'USER' ? '请选择员工' : '请选择用户')
message.warning(type === 'USER' ? '请选择员工' : '请选择学员')
return null;
}
type === 'USER' ? this.setState({ openSetModal: true, isOpen: false }) : this.addCustomer();
......@@ -341,7 +341,7 @@ class ChooseMembersModal extends React.Component {
</div>
{/* 已选择的成员列表 */}
<div className='container-right'>
<span className='span-left'>已选择{type === 'USER' ? '员工' : '用户'}</span>
<span className='span-left'>已选择{type === 'USER' ? '员工' : '学员'}</span>
<div className='span-right' onClick={() => this.clearAllUser()}>
<span className={ (selectUserList.length > 0) ? 'span-right-l' : null }>清空</span>
</div>
......
......@@ -88,7 +88,7 @@ export default class SetEmployeeModal extends React.Component {
<Radio value={"Cloud_Operator"} className="mt-4">
<span style={{ color: "#333" }}>运营师</span>
<p className="radio-tip">
仅可查看/转发培训计划内容,并查看负责的用户学习进度
仅可查看/转发培训计划内容,并查看负责的学员学习进度
</p>
</Radio>
<Radio value={"Cloud_Lecturer"} className="mt-4">
......
......@@ -16,7 +16,6 @@ class DateRangePicker extends React.Component {
const showTime = { showTime: false }
return (
<RangePicker
{...this.props}
format={this.props.format || 'YYYY-MM-DD'}
allowClear={this.props.allowClear}
onChange={(date) => {
......@@ -28,6 +27,7 @@ class DateRangePicker extends React.Component {
}}
{...showTime}
suffixIcon={<span className="icon iconfont">&#xe838;</span>}
{...this.props}
/>
)
}
......
......@@ -106,7 +106,7 @@ class DataList extends React.Component {
getStudentColumns() {
const columns = [
{
title: "用户姓名",
title: "学员姓名",
dataIndex: "userName",
},
{
......@@ -304,9 +304,9 @@ class DataList extends React.Component {
</div>
</div>
{/* 用户上课数据 */}
{/* 学员上课数据 */}
<div className="courseData-student">
<p className="title">用户上课数据</p>
<p className="title">学员上课数据</p>
<div className="filter-wrap">
<div className="filter">
<Button
......
......@@ -56,7 +56,7 @@ class PlaybackData extends React.Component {
getPlaybackColumns() {
const columns = [
{
title: "观看用户",
title: "观看学员",
dataIndex: "userName",
},
{
......
......@@ -131,9 +131,9 @@ class AddLiveIntro extends React.Component {
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/>
</div>
<div>
<div className="instro-text">
<div>开启:允许未绑定手机号的用户进入直播间观看直播</div>
<div>关闭:仅限绑定了手机号的用户可以进入直播间观看直播</div>
<div class="instro-text">
<div>开启:允许未绑定手机号的学员进入直播间观看直播</div>
<div>关闭:仅限绑定了手机号的学员可以进入直播间观看直播</div>
</div>
</div>
</div>
......
......@@ -15,16 +15,15 @@ class GraphicsEditor extends React.Component {
textLength: 0,
showSelectImageModal: false,
showSelectVideoModal: false,
diskList: [],
}
this.editorInt = null;
this.isContent = true;
}
componentDidMount() {
this.renderEditor()
this.resetIndex(true);
this.initBus();
this.bindClick();
}
......@@ -32,23 +31,6 @@ class GraphicsEditor extends React.Component {
componentWillUnmount() {
this.resetIndex();
this.removeBus();
this.removeClick();
}
bindClick = () => {
window.addEventListener('click', this.clickEditor)
}
removeClick = () => {
window.removeEventListener('click', this.clickEditor)
}
clickEditor = (e) => {
if (e && e.target.closest('.content-editor')) {
this.isContent = true
} else if (e && e.target.closest('.introduce-editor')) {
this.isContent = false
}
}
resetIndex = (bool) => {
......@@ -102,9 +84,10 @@ class GraphicsEditor extends React.Component {
}
this.editorInt = new E(`#editor${editorId}`);
this.editorInt.config.focus = false;
this.editorInt.config.showFullScreen = !isIntro
this.editorInt.menus.extend('xmimage', ImageMenu);
this.editorInt.menus.extend('xmvideo', VideoMenu);
!isIntro && this.editorInt.menus.extend('xmvideo', VideoMenu);
this.editorInt.config.menus = isIntro ?
[
'head',
......@@ -184,19 +167,21 @@ class GraphicsEditor extends React.Component {
// 选择图文
handleSelectVideo = (file) => {
const { ossUrl } = file || {};
if (!ossUrl) return null;
this.setState({
showSelectVideoModal: false
})
const { ossUrl } = file;
const { detail } = this.props;
this.editorInt && this.editorInt.txt.html(`${detail.content}<p style="width: 100%;padding-top: 56.25%;position: relative;"><iframe style="position: absolute;width: 100%;height: 100%;top: 0;left: 0;" src="${ossUrl}"></iframe><br/></p><p><br/></p>`)
}
handleSelectImage = (file) => {
const { ossUrl } = file || {};
if (!ossUrl) return null;
this.setState({
showSelectImageModal: false
})
const { ossUrl } = file;
const { detail } = this.props;
this.editorInt && this.editorInt.txt.html(`${detail.content}<p><img style="max-width: 100%;" src="${ossUrl}" /><br/><p>`)
}
......@@ -204,13 +189,13 @@ class GraphicsEditor extends React.Component {
initBus = () => {
const { isIntro } = this.props;
Bus.bind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage)
Bus.bind('graphicsEditorVideo', this.uploadVideo)
!isIntro && Bus.bind('graphicsEditorVideo', this.uploadVideo)
}
removeBus = () => {
const { isIntro } = this.props;
Bus.unbind(`graphicsEditorImage${isIntro ? '' : 'Content'}`, this.uploadImage)
Bus.unbind('graphicsEditorVideo', this.uploadVideo)
!isIntro && Bus.unbind('graphicsEditorVideo', this.uploadVideo)
}
uploadImage = () => {
......@@ -222,7 +207,13 @@ class GraphicsEditor extends React.Component {
}
render() {
const { editorId, textLength, showSelectImageModal, showSelectVideoModal } = this.state;
const {
editorId,
textLength,
showSelectImageModal,
showSelectVideoModal,
diskList,
} = this.state;
const { limitLength = 1000, isIntro, maxLimit } = this.props;
return <div className={`graphics-editor-container${isIntro ? ' introduce' : ''} ${(textLength > maxLimit)&& 'warning'}`}>
<div className="editor-box" id={`editor${editorId}`} ></div>
......
......@@ -262,7 +262,7 @@ class LiveCourseList extends React.Component {
{
title: <span>
<span>学院展示</span>
<Tooltip title={<div>开启后,用户可在学院内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”学院展示。<br/>关闭后,学院内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
<Tooltip title={<div>开启后,学员可在学院内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”学院展示。<br/>关闭后,学院内不再展示此课程,但学员仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
width: "9%",
key: "shelfState",
......@@ -580,7 +580,7 @@ class LiveCourseList extends React.Component {
handleDelete = (record)=>{
return confirm({
title: '你确定要删除直播课?',
content: '删除后,用户将不能观看直播课/回放',
content: '删除后,学员将不能观看直播课/回放',
icon: <span className="icon iconfont default-confirm-icon">&#xe839; </span>,
okText: '删除',
okType: 'danger',
......@@ -658,7 +658,7 @@ class LiveCourseList extends React.Component {
}
getDownloadVersion() {
const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
// 判断用户系统
// 判断学员系统
let platform;
if(!isMac){
platform = 1
......
......@@ -28,7 +28,7 @@ class LiveCourseOpt extends React.Component {
// 下载直播客户端
handleDownloadClient = () => {
const { isMac } = this.state;
// 判断用户系统
// 判断学员系统
let platform;
if(!isMac){
platform = 1
......
......@@ -56,7 +56,6 @@ class AddGraphicsCourse extends React.Component {
coverUrl: defaultCoverUrl, // 图文课封面
studentList: [], // 上课学员列表
shelfState:'YES', //是否开启学院展示
diskList: [], // 机构可见磁盘目录
selectedFileList: [], // 已经从资料云盘中勾选的文件
showCutModal: false, // 是否显示截图弹窗
showSelectVideoModal: false,
......@@ -65,7 +64,6 @@ class AddGraphicsCourse extends React.Component {
courseCatalogList:[], //分类列表
categoryId:null, //分类的Id值
whetherVisitorsJoin: 'NO', // 是否允许游客加入
isContent: true,
}
}
......@@ -513,7 +511,6 @@ class AddGraphicsCourse extends React.Component {
courseMedia,
introduce,
showCutModal,
diskList,
imageFile,
videoType,
shelfState,
......
......@@ -118,8 +118,8 @@ class AddGraphicsIntro extends React.Component {
</div>
<div>
<div className="desc">
<div>开启:允许未绑定手机号的用户观看</div>
<div>关闭:仅限绑定了手机号的用户可以进入观看图文课</div>
<div>开启:允许未绑定手机号的学员观看</div>
<div>关闭:仅限绑定了手机号的学员可以进入观看图文课</div>
</div>
</div>
</div>
......@@ -133,8 +133,8 @@ class AddGraphicsIntro extends React.Component {
</Col>
<Col span={21}>
<div className="desc">
<div>开启:图文课将在用户学院图文课列表中展示</div>
<div>关闭:图文课将在用户学院图文课列表中隐藏</div>
<div>开启:图文课将在学员学院图文课列表中展示</div>
<div>关闭:图文课将在学员学院图文课列表中隐藏</div>
</div>
</Col>
</Row>
......
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:45
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-05-25 17:41:51
* @LastEditors: wufan
* @LastEditTime: 2021-05-27 10:23:55
* @Description: 视频课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -137,7 +137,7 @@ class GraphicsCourseList extends React.Component {
{
title: <span>
<span>学院展示</span>
<Tooltip title={<div>开启后,用户可在学院内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”学院展示。<br/>关闭后,学院内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
<Tooltip title={<div>开启后,学员可在学院内查看到此课程。<br/>关闭后,学院内不再展示此课程,但学员仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
width: 120,
dataIndex: "courseware",
......@@ -148,7 +148,7 @@ class GraphicsCourseList extends React.Component {
},
},
{
title: "观看用户数",
title: "观看学员数",
width: 110,
key: "watchUserCount",
dataIndex: "watchUserCount",
......
......@@ -84,7 +84,7 @@ class WatchDataModal extends React.Component {
parseColumns = () => {
const columns = [
{
title: '观看用户',
title: '观看学员',
key: 'name',
dataIndex: 'name'
},
......@@ -141,7 +141,7 @@ class WatchDataModal extends React.Component {
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div className="search-container">
<Search placeholder="搜索用户姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
<Search placeholder="搜索学员姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
</div>
<div>
<Table
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-20 19:12:49
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-16 17:41:40
* @Last Modified time: 2021-05-25 16:11:25
* @Description: 大班直播分享弹窗
*/
......@@ -105,16 +105,18 @@ class ShareLiveModal extends React.Component {
showImg:true,
time:new Date().valueOf()
},()=>{
this.setState({time:new Date().valueOf()},()=>{
let node = document.getElementById('poster');
domtoimage.toPng(node)
.then((imgData) => {
console.log(imgData)
const download = document.createElement('a');
const { courseName } = this.props.data;
$(download).attr('href', imgData).attr('download', `${courseName}.png`).get(0).click();
// this.props.close()
})
this.setState({ time: new Date().valueOf() }, () => {
setTimeout(() => {
let node = document.getElementById('poster');
domtoimage.toPng(node)
.then((imgData) => {
console.log(imgData)
const download = document.createElement('a');
const { courseName } = this.props.data;
$(download).attr('href', imgData).attr('download', `${courseName}.png`).get(0).click();
// this.props.close()
})
}, 1000)
})
})
......@@ -139,7 +141,7 @@ class ShareLiveModal extends React.Component {
let coverImgSrc = coverUrl;
if(type === 'videoClass'){
if((!coverUrl || isDefaultCover) && title !== '图文课'){
if((!coverUrl || isDefaultCover) && title !== '图文课' && title != '线下课'){
coverImgSrc = `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
}
}else{
......@@ -197,22 +199,22 @@ class ShareLiveModal extends React.Component {
<div className="share-poster right__item">
<div className="title">① 海报分享</div>
{ type === "liveClass" &&
<div className="sub-title">用户可通过微信扫描海报二维码,观看{title}</div>
<div className="sub-title">学员可通过微信扫描海报二维码,观看{title}</div>
}
{ type === "videoClass" &&
<div className="sub-title">用户可通过微信识别二维码,报名观看{title}</div>
<div className="sub-title">学员可通过微信识别二维码,报名观看{title}</div>
}
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
<div className="content" onClick={_.debounce(this.handleDownloadPoster, 1000, true)}>下载海报</div>
</div>
<div className="share-url right__item">
<div className="title">② 链接分享</div>
{ type === "liveClass" &&
<div className="sub-title">用户可通过微信打开以下链接,观看{title}</div>
<div className="sub-title">学员可通过微信打开以下链接,观看{title}</div>
}
{ type === "videoClass" &&
<div className="sub-title">用户可通过打开链接,报名观看{title}</div>
<div className="sub-title">学员可通过打开链接,报名观看{title}</div>
}
<div className="content url-content">
<div className="share-url" id="shareUrl">{shareUrl}</div>
......
.add-offline-course-page {
position:relative !important;
.box{
margin-bottom:66px !important;
}
.ant-radio-group {
display: flex;
flex-direction: column;
margin-top: 0 !important;
.radio-item {
margin-bottom: 12px;
.text {
color: #333;
}
.sub-text {
color: #999;
}
}
.ant-radio {
vertical-align: top;
padding-top: 2px;
}
}
.form {
margin-top: 16px;
padding: 0 16px;
.basic-info__wrap {
.title {
font-size: 16px;
color: #333;
font-weight: 500;
line-height: 22px;
margin-bottom: 16px;
margin-left: -16px;
}
}
.label{
display: inline-block;
text-align: right;
width: 120px;
flex-shrink: 0;
&.special {
line-height: 32px;
}
}
.required {
position: relative;
&::before {
position: absolute;
content: '*';
color: red;
left: 5px;
top: 6px;
}
&.label::before {
top: 0;
}
}
.course-catalog {
margin-bottom:16px;
margin-top:16px;
display: flex;
.switch-box {
.switch-item {
display: flex;
align-items: center;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
.switch-label {
color: #666;
}
.switch-tip {
color: #999;
margin-left: 4px;
}
}
}
}
.require {
color: red;
}
.introduce {
display: flex;
flex-direction: row;
}
.day {
display: flex;
flex-direction: row;
margin-bottom: 16px;
.select-day {
margin-bottom: 4px;
.mark-day {
color: #FFB714;
}
}
}
.allow-tourist-join{
display:flex;
margin-bottom:16px;
.desc {
color:#999;
margin-left:12px;
}
.content{
display:flex;
}
}
.course-ware {
display: flex;
align-items: center;
margin-bottom: 4px;
&__img {
width: 24px;
margin-right: 4px;
}
&__name {
color: #333;
}
}
.flex {
display: flex;
}
.course-cover {
display: flex;
margin-top: 16px;
&__wrap {
position: relative;
.tag {
border-radius: 2px;
background: #D6D6D6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
margin-right: 20px;
width: 299px;
height: 169px;
img {
width: 100%;
height: 100%;
object-fit: contain;
border: 1px solid #E8e8e8;
}
}
.opt-btns {
.default-btn {
margin-left: 16px;
color: #5289FA;
cursor: pointer;
&.disabled {
color: #CCC;
cursor: not-allowed;
}
}
.ant-upload-list {
display: none;
}
}
.tips {
margin-top: 8px;
color: #999;
}
}
.select-student {
align-items: center;
margin-left: 24px;
margin-top: 8px;
.has-selected {
margin-left: 12px;
color: #333;
}
}
.sub-content {
margin-left: 85px;
margin-top: 4px;
.tips {
margin-left: 4px;
color: #999;
}
}
}
.footer {
position: fixed;
bottom: 0;
height: 58px;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 252px;
background: #fff;
border-top: 1px solid #E8E8E8;
z-index: 999;
.ant-btn {
margin-left: 10px;
}
}
}
.offline-dropdown-box {
.rc-virtual-list-scrollbar {
display: none;
position: absolute;
left: 20000px;
}
}
\ No newline at end of file
.offline-course-data {
min-width: 1100px;
.box {
.offline-name {
font-size: 19px;
color: #333;
line-height: 26px;
font-weight: 500;
padding-left: 8px;
position: relative;
&::after {
position: absolute;
width: 4px;
height: 16px;
content: '';
background: #FFB714;
left: 0;
top: 5px;
}
}
.offline-application {
color: #333;
font-size: 14px;
line-height: 20px;
margin-top: 8px;
.iconfont {
color: #BFBFBF;
font-size: 14px;
margin: 0 4px;
}
}
}
.data-box {
padding: 16px 0 !important;
display: flex;
.left-box {
width: 260px;
border-right: 1px solid #EEEEEE;
.left-title {
color: #000;
font-size: 16px;
font-weight: 500;
line-height: 22px;
padding-left: 8px;
padding-bottom: 16px;
margin: 0 8px;
border-bottom: 1px solid #E8E8E8;
}
.left-calendar {
margin-top: 16px;
margin-left: 20px;
display: flex;
align-items: center;
.icon-box {
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid #E8E8E8;
display: flex;
align-items: center;
justify-content: center;
color: #BFBFBF;
cursor: pointer;
.iconfont {
font-size: 12px;
}
}
.calendar-text {
color: #FF9D14;
line-height: 20px;
margin: 0 44px;
}
}
.date-list {
width: 100%;
margin-top: 16px;
.date-item {
height: 44px;
width: 100%;
display: flex;
padding: 12px 32px;
color: #666;
&:hover {
background: #F3F6FA;
cursor: pointer;
}
&.selected {
background: rgba(255, 183, 20, 0.06);
}
}
}
}
.right-box {
padding: 0 16px;
width: ~'calc(100% - 261px)';
.selected-date {
font-size: 16px;
color: #333;
line-height: 22px;
font-weight: 500;
}
.detail-data {
display: flex;
margin-top: 10px;
align-items: center;
.iconfont {
font-size: 15px;
color: #999999;
margin-right: 4px;
}
.data-text {
color: #333;
font-size: 14px;
line-height: 20px;
margin-right: 24px;
.iconfont {
font-size: 14px;
color: #BFBFBF;
margin: 0 4px;
}
}
}
.detail-filter {
display: flex;
margin-top: 16px;
margin-bottom: 16px;
align-items: center;
.filter-box {
margin-right: 36px;
}
}
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-16 11:05:17
* @Last Modified by: chenshu
* @Last Modified time: 2021-04-06 16:17:57
* @Description: 添加直播-简介
*/
import React from 'react';
import { Input, message, Upload, Radio, Row, Col, Button, Popover, Switch } from 'antd';
import Service from '@/common/js/service';
import GraphicsEditor from '../../components/GraphicsEditor';
import User from '@/common/js/user';
import UploadOss from '@/core/upload';
import './AddGraphicsIntro.less';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import { DISK_MAP } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components';
const { TextArea } = Input;
class AddGraphicsIntro extends React.Component {
constructor(props) {
super(props);
this.state = {
showSelectFileModal: false,
diskList: [],
selectType: null,
}
}
// 上传封面图
handleShowImgCutModal = (event) => {
const imageFile = event.target.files[0];
if (!imageFile) return;
this.setState({
imageFile,
showCutModal: true,
});
}
// 选择暖场资源
handleSelectVideo = (file) => {
const { selectType } = this.state;
this.setState({
showSelectFileModal: false
})
const { ossUrl, resourceId, folderName, folderFormat, folderSize } = file;
if(selectType === 'WARMUP'){
const liveCourseWarmMedia = {
contentType: 'WARMUP',
mediaType: folderFormat === 'MP4' ? 'VIDEO' : 'PICTURE',
mediaContent: resourceId,
mediaUrl: ossUrl,
mediaName: folderName,
size: folderSize
}
this.props.onChange('liveCourseWarmMedia', liveCourseWarmMedia);
}else{
// 最多添加九图片
const { liveCourseMediaRequests } = this.props.data;
const list = _.filter(liveCourseMediaRequests, (item) => {
return item.mediaType == "PICTURE";
});
if (list.length > 8) {
message.warning("最多添加9张图片");
return;
}
liveCourseMediaRequests.push({
contentType: 'INTRO',
size: folderSize,
mediaName: folderName,
mediaContent: resourceId,
mediaType: 'PICTURE',
mediaUrl: ossUrl,
});
this.props.onChange('liveCourseMediaRequests', liveCourseMediaRequests);
}
}
changeDetail = (value) => {
this.props.onChange('courseMedia', value);
}
changeIntro = (value) => {
this.props.onChange('introduce', value);
}
whetherVisitorsJoinChange = ()=>{
if(this.props.data.whetherVisitorsJoin==="NO"){
this.props.onChange('whetherVisitorsJoin','YES')
}else{
this.props.onChange('whetherVisitorsJoin','NO')
}
}
shelfStateChange = ()=>{
if(this.props.data.shelfState==="NO"){
this.props.onChange('shelfState','YES')
}else{
this.props.onChange('shelfState','NO')
}
}
render() {
const {data: { id, whetherVisitorsJoin, courseMedia, introduce, shelfState, loadcourseMedia, loadintroduce } } = this.props;
const { showSelectFileModal, selectType } = this.state;
return (
<div className="add-video__intro-info">
<div className="allow-tourist-join">
<span className="label">观看设置:</span>
<div className="content">
<div>
<Switch checked={whetherVisitorsJoin==="YES"? true:false} onChange={this.whetherVisitorsJoinChange}/>
</div>
<div>
<div className="desc">
<div>开启:允许未绑定手机号的学员观看</div>
<div>关闭:仅限绑定了手机号的学员可以进入观看图文课</div>
</div>
</div>
</div>
</div>
<div className="store-show">
<span className="label">学院展示:</span>
<div className="content">
<Row>
<Col span={3}>
<Switch checked={shelfState==="YES"? true:false} onChange={this.shelfStateChange}/>
</Col>
<Col span={21}>
<div className="desc">
<div>开启:图文课将在学员学院图文课列表中展示</div>
<div>关闭:图文课将在学员学院图文课列表中隐藏</div>
</div>
</Col>
</Row>
</div>
</div>
<div className="introduce required">
<span className="label" style={{ marginTop: 5 }}>课程内容:</span>
<div className="content">
<div className="intro-list">
<div className="intro-list__item content-editor">
{(!id || loadcourseMedia) &&
<GraphicsEditor
id="content"
detail={{
content: courseMedia
}}
onChange={(val) => { this.changeDetail(val) }}
/>
}
</div>
</div>
</div>
</div>
<div className="introduce">
<span className="label">课程简介:</span>
<div className="content">
<div className="intro-list">
<div className="intro-list__item introduce-editor">
{(!id || loadintroduce) &&
<GraphicsEditor
id="intro"
isIntro={true}
detail={{
content: introduce
}}
onChange={(val) => { this.changeIntro(val) }}
/>
}
</div>
</div>
</div>
</div>
{/* 选择暖场图文件弹窗 */}
{ showSelectFileModal &&
<SelectPrepareFileModal
operateType="select"
accept={selectType==="INTRO"?"image/jpeg,image/png,image/jpg":"video/mp4,image/jpeg,image/png,image/jpg"}
selectTypeList={ selectType==="INTRO" ? ['JPG', 'JPEG', 'PNG']: ['MP4', 'JPG', 'JPEG', 'PNG'] }
tooltip={ selectType==="INTRO"?'支持文件类型:jpg、jpeg、png':'支持文件类型:jpg、jpeg、png、mp4'}
isOpen={showSelectFileModal}
onClose={() => {
this.setState({ showSelectFileModal: false })
}}
onSelect={this.handleSelectVideo}
/>
}
</div>
)
}
}
export default AddGraphicsIntro;
.add-video__intro-info {
.w-e-full-screen-editor {
background: #fff !important;
}
.playback {
margin-bottom: 10px;
.require {
color: #EC4B35;
}
&__text {
color: #999;
}
}
.label{
display:inline-block;
text-align:right;
width:85px;
}
.playback,
.introduce {
display: flex;
flex-direction: row;
}
.allow-tourist-join{
display:flex;
.content{
display:flex;
}
.desc{
margin-left:16px;
font-size:14px;
color:#999;
display:inline-block;
}
}
.store-show{
display:flex;
margin-top:16px;
margin-bottom:16px;
.desc{
margin-left:16px;
font-size:14px;
color:#999;
display:inline-block;
}
}
.radio {
display: block;
height: 30px;
line-height: 30px;
}
.interactive-playback {
display: flex;
margin-bottom: 20px;
}
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;
margin-bottom: 8px;
}
.checkExample {
width: 60px;
color: #FF7519;
cursor: pointer;
}
.warmup {
margin-bottom: 17px;
display: flex;
}
.course-cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
position: relative;
margin-right: 20px;
width: 300px;
height: 170px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
.img-delete-wrap {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
img {
position: absolute;
left: 50%;
top: 50%;
width: 40px;
height: 40px;
transform: translate(-50%, -50%);
}
&:hover {
opacity: 1;
cursor: pointer;
}
}
}
.opt-btns {
.default-btn {
margin-left: 16px;
color: #FF7519;
cursor: pointer;
&.disabled {
color: #CCC;
cursor: not-allowed;
}
}
}
}
.example-wrap {
font-family: PingFangSC-Regular, PingFang SC;
text-align: center;
.title {
margin-bottom: 6px;
font-size: 14px;
color: #333333;
}
.text {
margin-bottom: 16px;
font-size: 12px;
color: #999999;
}
img {
width: 180px;
}
}
.check-record-rule {
width: 120px;
color: #FF7519;
cursor: pointer;
z-index: 2;
}
.record-rule-wrap {
text-align: left;
ul {
margin-top: 10px;
padding-left: 34px;
list-style-type: disc;
li {
color: #999;
}
}
.text {
color: #999;
}
}
.auto-send-class-report {
.label {
width: 57px;
height: 12px;
font-size: 14px;
font-weight: 400;
color: #666666;
line-height: 12px;
}
.open-text, .close-text {
width: 572px;
font-size: 14px;
font-weight: 400;
color: #999999;
line-height: 20px;
margin-left: 100px;
margin-top: 5px;
}
.open-text {
margin-top: 8px;
}
.close-text {
margin-bottom: 16px;
}
}
\ No newline at end of file
import React from 'react';
import { Row, Input, Select,Tooltip } from 'antd';
import RangePicker from "@/modules/common/DateRangePicker";
import './OfflineCourseFilter.less';
import moment from 'moment';
import StoreService from "@/domains/store-domain/storeService";
const { Search } = Input;
const { Option } = Select;
const DEFAULT_QUERY = {
courseName: null, // 课程名称
operatorId: null, // 创建人
beginTime: null, // 开始日期
endTime: null, // 结束日期
shelfState:null,
}
const defaultTeacherQuery = {
size: 10,
current: 1,
nickName:null
}
class OfflineCourseFilter extends React.Component {
constructor(props) {
super(props);
this.state = {
query: { ...DEFAULT_QUERY }, // 使用扩展运算符,避免浅拷贝
teacherQuery: defaultTeacherQuery,
teacherList:[],
expandFilter:false
}
}
componentDidMount() {
this.getTeacherList();
}
getTeacherList(current = 1, selectList){
const { teacherQuery,teacherList} = this.state;
const _query = {
...teacherQuery,
current,
size:10
};
StoreService.getStoreUserBasicPage( _query).then((res) => {
const { result = {} } = res;
const { records = [], total = 0, hasNext } = result;
const list = current > 1 ? teacherList.concat(records) : records;
this.setState({
hasNext,
teacherList: list,
})
});
}
// 滑动加载更多讲师列表
handleScrollTeacherList = (e) => {
const { hasNext } = this.state;
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
const { teacherQuery } = this.state;
let _teacherQuery = teacherQuery;
_teacherQuery.current = _teacherQuery.current + 1
this.setState({
teacherQuery:{..._teacherQuery}
},()=>{this.getTeacherList(_teacherQuery.current)})
}
}
// 改变搜索条件
handleChangeQuery = (field, value) => {
this.setState({
query: {
...this.state.query,
[field]: value,
current: 1,
}
}, () => {
if (field === 'courseName') return;
this.props.onChange(this.state.query)
});
}
handleChangeDates = (dates) => {
const query = _.clone(this.state.query);
if (_.isEmpty(dates)) {
delete query.beginTime;
delete query.endTime;
} else {
query.beginTime = dates[0].valueOf();
query.endTime = dates[1].valueOf();
}
this.setState({
query:{
...query,
current: 1,
}
}, () => {
this.props.onChange(this.state.query);
})
}
// 重置搜索条件
handleReset = () => {
this.setState({
query: DEFAULT_QUERY,
}, () => {
this.props.onChange(this.state.query);
})
}
render() {
const {
query: {
courseName,
courseState,
shelfState,
},
expandFilter,
} = this.state;
return (
<div className="video-course-filter">
<Row type="flex" justify="space-between" align="top">
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">线下课名称:</span>
<Search
value={courseName}
placeholder="搜索线下课名称"
onChange={(e) => { this.handleChangeQuery('courseName', e.target.value)}}
onSearch={ () => { this.props.onChange(this.state.query) } }
style={{ width: "calc(100% - 84px)" }}
enterButton={<span className="icon iconfont" style={{fontSize: '16px'}}>&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span className="shelf-status">学院展示:</span>
<Select
style={{ width: "calc(100% - 84px)" }}
placeholder="请选择"
allowClear={true}
value={shelfState}
onChange={(value) => { this.handleChangeQuery('shelfState', value) }}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
>
<Option value="YES">开启</Option>
<Option value="NO">关闭</Option>
</Select>
</div>
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={this.handleReset}>&#xe61b; </span></Tooltip>
</div>
</Row>
</div>
)
}
}
export default OfflineCourseFilter;
.video-course-filter {
position: relative;
.video-list-table{
// tr:nth-child(even){
// background: transparent !important;
// }
// tr:nth-child(odd){
// td{
// background: #FAFAFA !important;
// }
// }
}
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
align-items: center;
display: flex;
.search-name{
vertical-align: middle;
}
.shelf-status{
width:84px;
display:inline-block;
text-align:right;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left:4px;
}
}
}
.data-icon {
cursor: pointer;
}
.offline-course-list {
margin-top: 12px;
.offline-list-table{
tbody {
tr{
&:nth-child(even){
background: transparent !important;
td{
background:#FFF !important;
}
}
&:nth-child(odd){
background: #FAFAFA !important;
td{
background: #FAFAFA !important;
}
}
&:hover{
td{
background:#F3f6fa !important;
}
}
}
}
}
.watchUserCount{
text-align:right;
padding:16px;
}
.operate-text {
color: #5289FA;
cursor: pointer;
}
.operate {
display: flex;
&__item {
color: #5289FA;
cursor: pointer;
&.split {
margin: 0 8px;
color: #BFBFBF;
}
}
}
.more-operate{
line-height:20px;
}
.course-state {
display: flex;
align-items: center;
}
.record__item {
display: flex;
.course-cover {
min-width: 116px;
max-width: 116px;
height: 60px;
border-radius: 2px;
margin-right: 8px;
background-color: #000;
}
.course-name {
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
line-height: 20px;
margin-bottom: 4px;
}
.course-text {
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 20px;
font-size: 12px;
}
}
}
.offline-course-more-menu {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
div {
line-height: 30px;
padding: 0 15px;
cursor: pointer;
&:hover {
background: #f3f6fa;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:15
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-26 16:07:27
* @Description: 视频课-操作模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Button } from 'antd';
import './OfflineCourseOpt.less';
export default function OfflineCourseOpt() {
return (
<div className="video-course-opt">
<Button
type="primary"
onClick={() => {
RCHistory.push('/create-offline-course?type=add');
}}
className="mr12"
>新建线下课</Button>
</div>
);
}
.video-course-opt {
margin-top:4px;
.link {
color: #FF8534;
}
}
\ No newline at end of file
import React from 'react';
import OfflineCourseFilter from './components/OfflineCourseFilter';
import OfflineCourseOpt from './components/OfflineCourseOpt';
import OfflineCourseList from './components/OfflineCourseList';
import Service from '@/common/js/service';
import User from '@/common/js/user'
class OfflineCoursePage extends React.Component {
constructor(props) {
super(props);
this.state = {
query: {
size: 10,
current: 1,
storeId:User.getStoreId()
},
dataSource: [], // 线下课列表
totalCount: 0, // 线下课数据总条数
}
}
componentWillMount() {
// 获取线下课列表
this.handleFetchScheduleList();
}
// 获取线下课列表
handleFetchScheduleList = (_query = {}) => {
const query = {
...this.state.query,
..._query
};
// 更新请求参数
this.setState({ query });
Service.Hades('public/hades/getOfflineCoursePage', query).then((res) => {
const { result = {} } = res || {};
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
})
}
render() {
const { dataSource, totalCount, query } = this.state;
return (
<div className="page video-course-page">
<div className="content-header">线下课</div>
<div className="box">
{/* 搜索模块 */}
<OfflineCourseFilter
onChange={this.handleFetchScheduleList}
/>
{/* 操作模块 */}
{window.ctx.xmState.storeUserPermissionList.includes('AddOfflineClass') && <OfflineCourseOpt />}
{/* 线下课列表模块 */}
<OfflineCourseList
query={query}
dataSource={dataSource}
totalCount={totalCount}
onChange={this.handleFetchScheduleList}
/>
</div>
</div>
)
}
}
export default OfflineCoursePage;
import React from 'react';
import { Modal } from 'antd';
import moment from 'moment';
import $ from 'jquery';
import Service from "@/common/js/service";
import './PreviewOfflineModal.less';
import ENUM from '@/modules/knowledge-base/ENUM';
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class PreviewOfflineModal extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
const { data } = this.props;
_.isEmpty(data) ? this.getCourseDetail() : this.setState({ ...data });
}
getCourseDetail = () => {
Service.Hades('public/hades/getOfflineCourseDetail', {
courseId: this.props.courseId,
}).then((res) => {
if (res.success) {
const { result } = res;
let coverUrl = '';
result.courseMediaVOS.map((item) => {
switch (item.contentType){
case "COVER":
coverUrl = item.mediaUrl;
break;
case "INTRO":
this.getTextDetail('introduce', item.mediaUrl);
break;
default:
break;
}
return item;
})
this.setState({
...result,
coverUrl,
})
}
})
}
getTextDetail = (key, url) => {
$.ajax({
data: {},
type: 'GET',
url,
contentType:'application/x-www-form-urlencoded; charset=UTF-8',
success: (res) => {
this.setState({ [key]: res, [`load${key}`]: true });
}
})
}
render() {
const {
coverUrl,
courseName,
teacherName,
courseState,
categoryName,
introduce,
startTime,
endTime,
offlinePlace,
whetherSetApply,
startTimeApply,
endTimeApply,
whetherSetSignIn,
whetherSetSignOut,
signInType,
signOutType,
signInTimeNum,
signInTimeUnit,
signOutStartTimeNum,
signOutStartTimeUnit,
signOutEndTimeNum,
signOutEndTimeUnit,
} = this.state;
const unit = (signInTimeUnit || '').toLocaleLowerCase() + 's';
const time = (signInType == 'START_AGO' ? startTime : endTime) && moment(signInType == 'START_AGO' ? startTime : endTime).subtract(signInTimeNum, unit);
const signInTime = (time && signInTimeNum) && `${moment(time).format('HH:mm')} ~ ${moment(signInType == 'START_AGO' ? startTime : endTime).format('HH:mm')}`;
const endUnit = (signOutEndTimeUnit || '').toLocaleLowerCase() + 's';
const end = (endTime && signOutEndTimeNum) && moment(endTime).add(signOutEndTimeNum, endUnit);
let startUnit = '';
let start = '';
if (signOutType === 'START_LATER') {
startUnit = (signOutStartTimeUnit || '').toLocaleLowerCase() + 's';
start = (startTime && signOutStartTimeNum) && moment(startTime).add(signOutStartTimeNum, startUnit);
}
console.log(start, end, startTime, endTime, 666666)
const signOutTime = (start || end) && (signOutType === 'START_LATER' ? `${moment(start).format('HH:mm')} ~ ${moment(end).format('HH:mm')}` : `${moment(endTime).format('HH:mm')} ~ ${moment(end).format('HH:mm')}`);
return (
<Modal
title="预览"
visible={true}
width={680}
onCancel={this.props.close}
footer={null}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
className="preview-live-graphics-modal"
>
<div className="container__wrap">
<div className="container">
<div className="container__header">
<div className="cover"></div>
<img src={coverUrl || defaultCoverUrl} className="course-cover" />
<span className="cover-teacher">主讲人:{teacherName}</span>
<span className="cover-state" style={{ background: (ENUM.offlineStateShow[courseState] || {}).color }}>{(ENUM.offlineStateShow[courseState] || {}).title}</span>
</div>
<div className="container__body">
<div className="title__name">{courseName}</div>
<div className="title__categery">
<img className="item-icon" src="https://image.xiaomaiketang.com/xm/cDMwz3jzaX.png" />
课程分类:{categoryName}
</div>
<div className="title__categery">
<img className="item-icon" src="https://image.xiaomaiketang.com/xm/BfTBK3dGda.png" />
上课时间:{startTime ? moment(startTime).format('HH:mm') : ''}~{endTime ? moment(endTime).format('HH:mm') : ''}
</div>
<div className="title__categery">
<img className="item-icon" src="https://image.xiaomaiketang.com/xm/KhFNBTtAKR.png" />
上课地点:{offlinePlace}
</div>
</div>
<div className="container__introduction">
<div className="title">线下课简介</div>
<div className="container__introduction__list editor-box">
{whetherSetApply === 'YES' && <div className="course-time">
<div className="time-title">报名时间</div>
<div className="time-text">{startTimeApply && moment(startTimeApply).format('MM-DD HH:mm')} ~ {endTimeApply && moment(endTimeApply).format('MM-DD HH:mm')}</div>
</div>}
{(whetherSetSignIn === 'YES' || whetherSetSignOut === 'YES') && <div className="course-time">
<div className="time-title">考勤时间</div>
{whetherSetSignIn === 'YES' &&
<div className="time-text">
签到:{signInTime}
</div>
}
{whetherSetSignOut === 'YES' &&
<div className="time-text">
签退:{signOutTime}
</div>
}
</div>}
<div
className="intro-item text"
dangerouslySetInnerHTML={{
__html: introduce
}}
/>
</div>
</div>
</div>
</div>
</Modal>
)
}
}
export default PreviewOfflineModal;
.preview-live-graphics-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%;;
&__header {
position: relative;
.cover {
position: absolute;
width: 100%;
height: 36px;
left: 0;
bottom: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.5) 100%); z-index: 10;
}
.course-cover, .course-url {
width: 100%;
height: 141px;
background: #000;
}
.cover-teacher {
position: absolute;
bottom: 8px;
left: 16px;
font-size: 10px;
color: #fff;
z-index: 10;
}
.cover-state {
position: absolute;
bottom: 8px;
right: 16px;
font-size: 12px;
height: 18px;
line-height: 18px;
padding: 0 8px;
color: #fff;
border-radius: 9px;
z-index: 10;
}
}
&__body {
background-color: #FFF;
padding: 7px 0 11px 0;;
.title__name {
color: #333333;
font-weight: 500;
font-size: 12px;
}
.title__categery {
font-size: 10px;
color: #999999;
display: flex;
align-items: center;
.item-icon {
width: 12px;
height: 12px;
margin-right: 4px;
font-size: 10px;
}
}
}
&__introduction {
margin-top: 10px;
padding: 12px 0;
position: relative;
&::after {
content: '';
position: absolute;
width: 241px;
top: -10px;
height: 10px;
background: #F4F6FA;
}
.title {
position: relative;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #333333;
padding: 0 10px;
border: none !important;
&::before {
content: '';
position: absolute;
width: 12px;
height: 1px;
top: 12px;
left: 50%;
transform: translateX(-50px);
background: #ccc;
}
&::after {
content: '';
position: absolute;
width: 12px;
height: 1px;
top: 12px;
left: 50%;
transform: translateX(36px);
background: #ccc;
}
}
&__list {
margin-top: 8px;
.course-time {
margin: 0 16px 8px 0;
.time-title {
position: relative;
padding-left: 6px;
font-size: 12px;
color: #333;
line-height: 18px;
&::before {
position: absolute;
content: '';
width: 2px;
height: 14px;
background: #FFB714;
left: 0;
top: 2px;
}
}
.time-text {
padding-left: 6px;
font-size: 12px;
color: #999999;
line-height: 18px;
.time-tag {
padding: 0 8px;
font-size: 12px;
height: 18px;
border-radius: 2px;
background: rgba(32, 206, 205, 0.1);
color: #20CECD;
margin-left: 8px;
&.apply {
color: #FF4F4F;
background: rgba(255, 79, 79, 0.1);
}
}
}
}
.intro-item:not(:first-child) {
margin-top: 13px;
}
color: #666;
p {
font-size: 12px;
}
img {
max-width: 100%;
}
}
}
}
}
\ No newline at end of file
import React from 'react';
import { Button, Modal, Select } from 'antd';
import moment from 'moment';
import html2canvas from 'html2canvas';
import { LIVE_SHARE } from "@/domains/course-domain/constants";
import QRCode from '../../../../libs/qrcode/qrcode';
import User from '@/common/js/user';
import Service from "@/common/js/service";
import ScanFileModal from '../../../resource-disk/modal/ScanFileModal';
import './QRCodeModal.less';
const { Option } = Select;
export default class QRCodeModal extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedDate: '',
data: {},
imgUrl1: '',
imgUrl2: '',
showPreviewModal: false,
}
}
componentDidUpdate(preProps) {
const { visible } = this.props;
if (!preProps.visible && visible) {
this.getCourseDetail();
}
}
getQrcode = () => {
const { selectedDate, data } = this.state;
let list = [];
const qrcodeWrapDom1 = document.querySelector('#qrcodeWrap1');
const qrcodeWrapDom2 = document.querySelector('#qrcodeWrap2');
if (data.whetherSetSignIn === 'YES') {
const htmlUrl = `${LIVE_SHARE}sign_in?id=${User.getStoreId()}&calendar=${selectedDate}&courseId=${data.courseId}&from=work_weixin`;
list.push(htmlUrl);
}
if (data.whetherSetSignOut === 'YES') {
const htmlUrl = `${LIVE_SHARE}sign_out?id=${User.getStoreId()}&calendar=${selectedDate}&courseId=${data.courseId}&from=work_weixin`;
list.push(htmlUrl);
}
Service.Sales('public/businessShow/convertShortUrls', {
urls: list
}).then((res) => {
const { result = [] } = res;
const qrcodeNode1 = qrcodeWrapDom1 && new QRCode({
text: result[0].shortUrl,
size: 170,
});
const qrcodeNode2 = qrcodeWrapDom2 && new QRCode({
text: result[data.whetherSetSignIn === 'YES' ? 1 : 0].shortUrl,
size: 170,
});
qrcodeWrapDom1 && qrcodeWrapDom1.childNodes[0] && qrcodeWrapDom1.removeChild(qrcodeWrapDom1.childNodes[0]);
qrcodeWrapDom1 && qrcodeWrapDom1.appendChild(qrcodeNode1);
qrcodeWrapDom2 && qrcodeWrapDom2.childNodes[0] && qrcodeWrapDom2.removeChild(qrcodeWrapDom2.childNodes[0]);
qrcodeWrapDom2 && qrcodeWrapDom2.appendChild(qrcodeNode2);
setTimeout(() => {
this.initQRCode();
}, 300)
})
}
getCourseDetail = () => {
const { courseId } = this.props.data;
Service.Hades('public/hades/getOfflineCourseDetail',{
courseId
}).then((res) => {
this.setState({ selectedDate: res.result.calendarTime[0], data: res.result }, () => {
this.getQrcode();
})
});
}
initQRCode = () => {
const domList = document.querySelectorAll('.image-box');
for (let index = 0; index < domList.length; index++) {
const dom = domList[index];
html2canvas(dom, {
useCORS: true,
}).then(canvas => {
const dataUrl = canvas.toDataURL('image/png');
this.setState({ [`imgUrl${index + 1}`]: dataUrl });
});
}
}
downloadImg = () => {
const { imgUrl1, imgUrl2, selectedDate, data } = this.state;
let fileName1 = `签到二维码_${data.courseName}_${moment(selectedDate).format('YYYY-MM-DD')}.png`;
let fileName2 = `签退二维码_${data.courseName}_${moment(selectedDate).format('YYYY-MM-DD')}.png`;
imgUrl1 && setTimeout(() => window.downloadFile(imgUrl1, fileName1), 0);
imgUrl2 && setTimeout(() => window.downloadFile(imgUrl2, fileName2), 0);
}
render() {
const { visible, onCancel } = this.props;
const { selectedDate, data = {}, imgUrl1, imgUrl2, imgUrl, showPreviewModal } = this.state;
const { courseName, whetherSetSignIn, whetherSetSignOut } = data;
const date = moment(selectedDate).format('YYYY-MM-DD');
const unit = (data.signInTimeUnit || '').toLocaleLowerCase() + 's';
const time = moment(`${date} ${moment(data.signInType == 'START_AGO' ? data.startTime : data.endTime).format('HH:mm')}`);
const signInTime = `${moment(time).subtract(data.signInTimeNum, unit).format('YYYY-MM-DD HH:mm')} ~ ${moment(time).format('YYYY-MM-DD HH:mm')}`;
const endUnit = (data.signOutEndTimeUnit || '').toLocaleLowerCase() + 's';
const end = moment(`${date} ${moment(data.endTime).format('HH:mm')}`).add(data.signOutEndTimeNum, endUnit);
let startUnit = '';
let start = '';
if (data.signOutType === 'START_LATER') {
startUnit = (data.signOutStartTimeUnit || '').toLocaleLowerCase() + 's';
start = moment(`${date} ${moment(data.startTime).format('HH:mm')}`).add(data.signOutStartTimeNum, startUnit);
}
const signOutTime = data.signOutType === 'START_LATER' ? `${moment(start).format('YYYY-MM-DD HH:mm')} ~ ${moment(end).format('YYYY-MM-DD HH:mm')}` : `${moment(`${date} ${moment(data.endTime).format('HH:mm')}`).format('YYYY-MM-DD HH:mm')} ~ ${moment(end).format('YYYY-MM-DD HH:mm')}`;
return (
<Modal
title="考勤二维码"
width={560}
visible={visible}
footer={<Button onClick={() => onCancel()}>关闭</Button>}
onCancel={() => onCancel()}
className="offline-qrcode-modal"
>
<div>
<Select
style={{ width: 200 }}
placeholder="请选择"
value={selectedDate}
onChange={(value) => {
this.setState({ selectedDate: value }, () => {
this.getQrcode(this.props);
})
}}
>
{(data.calendarTime || []).map(item => (
<Option value={item} key={item}>{moment(item).format('MM月DD日')}</Option>
))}
</Select>
<Button
type="primary"
style={{ marginLeft: 8 }}
onClick={() => {
this.downloadImg();
}}
>下载二维码</Button>
</div>
{whetherSetSignIn === 'YES' && <div
className="image-box"
key="1"
onClick={() => this.setState({ showPreviewModal: true, imgUrl: imgUrl1 })}
>
<img src="https://image.xiaomaiketang.com/xm/2YHQEYrWBD.png" className="image" />
<div className="title">签到二维码</div>
<div className="name" onClick={e => e.stopPropagation()}>{courseName}</div>
<div className="qrcodeWrap" id="qrcodeWrap1"></div>
<div className="time" onClick={e => e.stopPropagation()}>签到时间:{signInTime}</div>
</div>}
{whetherSetSignOut === 'YES' && <div
className="image-box"
key="2"
onClick={() => this.setState({ showPreviewModal: true, imgUrl: imgUrl2 })}
>
<img src="https://image.xiaomaiketang.com/xm/2YHQEYrWBD.png" className="image" />
<div className="title">签退二维码</div>
<div className="name" onClick={e => e.stopPropagation()}>{courseName}</div>
<div className="qrcodeWrap" id="qrcodeWrap2"></div>
<div className="time" onClick={e => e.stopPropagation()}>签退时间:{signOutTime}</div>
</div>}
{showPreviewModal &&
<ScanFileModal
fileType="JPG"
item={{ ossUrl: imgUrl }}
close={() => this.setState({ showPreviewModal: false })}
/>
}
</Modal>
)
}
}
\ No newline at end of file
.offline-qrcode-modal {
.ant-modal-body {
max-height: 483px!important;
}
.image-box {
margin: 16px auto 0;
width: 380px;
height: 350px;
position: relative;
.image {
width: 100%;
height: 100%;
object-fit: contain;
box-shadow: 0px 0px 30px 0px rgba(128, 128, 128, 0.1);
border-radius: 6px;
}
.title {
position: absolute;
font-size: 20px;
font-weight: 500;
top: 24px;
left: 50%;
transform: translateX(-50%);
color: #fff;
}
.name {
font-size: 14px;
color: #fff;
position: absolute;
top: 58px;
left: 50%;
transform: translateX(-50%);
width: 286px;
line-height: 20px;
text-align: center;
}
.qrcodeWrap {
width: 186px;
height: 186px;
left: 50%;
transform: translateX(-50%);
position: absolute;
top: 100px;
padding: 8px;
background: 8px;
border-radius: 4px;
background: #fff;
}
.time {
font-size: 12px;
color: #333;
padding: 0 8px;
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
line-height: 30px;
height: 30px;
border-radius: 15px;
background: #fff;
text-align: center;
}
}
}
\ No newline at end of file
......@@ -121,8 +121,8 @@ class AddVideoIntro extends React.Component {
</div>
<div>
<div className="desc">
<div>开启:允许未绑定手机号的用户观看</div>
<div>关闭:仅限绑定了手机号的用户可以进入观看视频</div>
<div>开启:允许未绑定手机号的学员观看</div>
<div>关闭:仅限绑定了手机号的学员可以进入观看视频</div>
</div>
</div>
</div>
......@@ -136,8 +136,8 @@ class AddVideoIntro extends React.Component {
</Col>
<Col span={21}>
<div className="desc">
<div>开启:此视频将在用户学院的视频列表中出现</div>
<div>关闭:此视频将在用户学院的视频列表中隐藏</div>
<div>开启:此视频将在学员学院的视频列表中出现</div>
<div>关闭:此视频将在学员学院的视频列表中隐藏</div>
</div>
</Col>
</Row>
......
......@@ -126,7 +126,7 @@ class VideoCourseList extends React.Component {
{
title: <span>
<span>学院展示</span>
<Tooltip title={<div>开启后,用户可在学院内查看到此课程。若课程“未成功开课”,则系统会自动“关闭”学院展示。<br/>关闭后,学院内不再展示此课程,但用户仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
<Tooltip title={<div>开启后,学员可在学院内查看到此课程。<br/>关闭后,学院内不再展示此课程,但学员仍可通过分享的海报/链接查看此课程。</div>}><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
width: 120,
dataIndex: "courseware",
......@@ -137,7 +137,7 @@ class VideoCourseList extends React.Component {
},
},
{
title: "观看用户数",
title: "观看学员数",
width: 110,
key: "watchUserCount",
dataIndex: "watchUserCount",
......
......@@ -84,7 +84,7 @@ class WatchDataModal extends React.Component {
parseColumns = () => {
const columns = [
{
title: '观看用户',
title: '观看学员',
key: 'name',
dataIndex: 'name'
},
......@@ -133,7 +133,7 @@ class WatchDataModal extends React.Component {
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div className="search-container">
<Search placeholder="搜索用户姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
<Search placeholder="搜索学员姓名/手机号" style={{ width: 200 }} onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
</div>
<div>
<Table
......
......@@ -102,11 +102,13 @@ class Home extends React.Component {
incLiveCourseNum: res.result.incLiveCourseNum,
incVideoCourseNum: res.result.incVideoCourseNum,
incPictureCourseNum: res.result.incPictureCourseNum,
incOfflineCourseNum: res.result.incOfflineCourseNum,
incTrainingPlanNum: res.result.incTrainingPlanNum,
liveCourseNum: res.result.liveCourseNum,
totalCustomerNum: res.result.totalCustomerNum,
videoCourseNum: res.result.videoCourseNum,
pictureCourseNum: res.result.pictureCourseNum,
offlineCourseNum: res.result.offlineCourseNum,
trainingPlanNum: res.result.trainingPlanNum,
})
}
......@@ -231,6 +233,8 @@ class Home extends React.Component {
unfinishedNum,
completeNum,
planCustomerNum,
incOfflineCourseNum,
offlineCourseNum,
} = this.state;
const data = [
{
......@@ -267,7 +271,7 @@ class Home extends React.Component {
<div className="data-item">
<div className="header">
<img className="header-icon" src="https://image.xiaomaiketang.com/xm/wAaFtjeRsM.png" />
<span className="header-word">用户总数 (人)</span>
<span className="header-word">学员总数 (人)</span>
</div>
<div className="data-number">{totalCustomerNum}</div>
<div className="data-footer">
......@@ -321,12 +325,12 @@ class Home extends React.Component {
<div className="course-item">
<div className="course-title">线下课</div>
<div className="data">
<span className="course-number">0</span>
<span className="course-number">{offlineCourseNum}</span>
<span className="course-word">本月新增</span>
{false &&
{incOfflineCourseNum > 0 &&
<span className="icon iconfont">&#xe635;</span>
}
<span className="add-number">0</span>
<span className="add-number">{incOfflineCourseNum}</span>
</div>
</div>
</div>
......
......@@ -30,6 +30,28 @@ const ENUM = {
color: "#999",
},
},
offlineStateShow: {
UN_START: {
code: 1,
title: "未开始",
color: "#FDBE31",
},
STARTING: {
code: 2,
title: "进行中",
color: "#238FFF",
},
FINISH: {
code: 3,
title: "已结束",
color: "#2FC83C",
},
EXPIRED: {
code: 4,
title: "已取消",
color: "#CCCCCC",
},
},
CourseTypeEnum: {
LIVE: "直播课",
......
......@@ -2,8 +2,8 @@
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-15 16:51:40
* @LastEditors: zangsuyun
* @LastEditTime: 2021-04-10 16:13:07
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 16:34:39
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -116,7 +116,7 @@ class CourseData extends React.Component {
getStudentColumns() {
const columns = [
{
title: "用户姓名",
title: "学员姓名",
dataIndex: "userName",
},
{
......@@ -314,9 +314,9 @@ class CourseData extends React.Component {
</div>
</div>
{/* 用户上课数据 */}
{/* 学员上课数据 */}
<div className="courseData-student">
<p className="title">用户上课数据</p>
<p className="title">学员上课数据</p>
<div className="filter-wrap">
<div className="filter">
<Button
......
......@@ -2,8 +2,8 @@
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-12 14:49:40
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-05-25 17:45:07
* @LastEditors: wufan
* @LastEditTime: 2021-05-27 10:23:39
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -251,7 +251,7 @@ class KnowledgeBaseList extends React.Component {
{record.hideToUser && (
<Tooltip
title={
<div>课程未成功开课,已在用户知识列表中隐藏</div>
<div>课程未成功开课,已在学员知识列表中隐藏</div>
}
>
<i
......@@ -423,7 +423,7 @@ class KnowledgeBaseList extends React.Component {
},
},
{
title: "观看用户数",
title: "观看学员数",
key: "watchUserCount",
dataIndex: "watchUserCount",
align:'right',
......
......@@ -56,7 +56,7 @@ class PlaybackData extends React.Component {
getPlaybackColumns() {
const columns = [
{
title: "观看用户",
title: "观看学员",
dataIndex: "userName",
},
{
......
......@@ -2,8 +2,8 @@
* @Description:
* @Author: zangsuyun
* @Date: 2021-03-16 10:18:31
* @LastEditors: zangsuyun
* @LastEditTime: 2021-03-30 10:17:59
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 16:36:00
* @Copyright: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -101,7 +101,7 @@ class WatchDataModal extends React.Component {
const { type } = this.props;
const columns = [
{
title: "观看用户",
title: "观看学员",
key: "name",
dataIndex: "name",
},
......@@ -195,7 +195,7 @@ class WatchDataModal extends React.Component {
<div className="watch-data">
<div className="search-container">
<Search
placeholder="搜索用户姓名/手机号"
placeholder="搜索学员姓名/手机号"
style={{ width: 200 }}
onChange={(e) => {
this.handleChangNickname(e.target.value);
......
......@@ -102,7 +102,7 @@ class LearningData extends React.Component {
<Tabs.TabPane tab="员工分享数据" key="employeeShareData" forceRender="true">
<EmployeeShareData/>
</Tabs.TabPane>
<Tabs.TabPane tab="用户学习数据" key="userLearningData" forceRender="true">
<Tabs.TabPane tab="学员学习数据" key="userLearningData" forceRender="true">
<UserLearningData/>
</Tabs.TabPane>
</Tabs>)
......
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-27 11:20:27
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 16:36:26
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -267,8 +267,8 @@ class BasicInfo extends React.Component{
</div>
<div>
<div className="instro-text">
<div>开启:此培训计划可以分享给用户进行学习</div>
<div>关闭:此培训计划暂不可分享给用户进行学习,后续可开启</div>
<div>开启:此培训计划可以分享给学员进行学习</div>
<div>关闭:此培训计划暂不可分享给学员进行学习,后续可开启</div>
</div>
</div>
</div>
......@@ -313,7 +313,7 @@ class BasicInfo extends React.Component{
<div>
<div className="live-standard-info">
<span className="icon iconfont">&#xe865;</span>
<span className="instro">直播课单个课程,用户学习进度达到
<span className="instro">直播课单个课程,学员学习进度达到
<Input
width="40"
value={percentCompleteLive}
......@@ -326,7 +326,7 @@ class BasicInfo extends React.Component{
</div>
<div className="live-standard-info">
<span className="icon iconfont">&#xe864;</span>
<span className="instro">视频课单个课程,用户学习进度达到
<span className="instro">视频课单个课程,学员学习进度达到
<Input
width="40"
value={percentCompleteVideo}
......@@ -339,7 +339,7 @@ class BasicInfo extends React.Component{
</div>
<div className="live-standard-info">
<span className="icon iconfont">&#xe601;</span>
<span className="instro">图文课单个课程,用户学习进度达到
<span className="instro">图文课单个课程,学员学习进度达到
<Input
width="40"
value={percentCompletePicture}
......@@ -350,19 +350,6 @@ class BasicInfo extends React.Component{
%,即视为"已完成"学习
</span>
</div>
{/* <div className="video-standard-info">
<span className="icon iconfont">&#xe864;</span>
<span>图文课单个课程,用户学习进度达到
<Input
width="40"
value={percentCompleteVideo}
onChange={(e) => { this.props.onChange('percentCompleteVideo', e.target.value.replace(/\D/g,'')) }}
onBlur={(e) => this.percentCompleteBlur(e, 'percentCompleteVideo')}
className="input-box"
/>
%,即视为“已完成”学习
</span>
</div> */}
</div>
</div>
{ operatorModalVisible &&
......
......@@ -242,7 +242,7 @@ function PlanList(props) {
function handleDelete(record){
return confirm({
title: '你确定要删除吗?',
content: '删除后,此培训计划的用户将无法继续学习,所有学习数据将同步删除不可恢复',
content: '删除后,此培训计划的学员将无法继续学习,所有学习数据将同步删除不可恢复',
icon: <span className="icon iconfont default-confirm-icon">&#xe839; </span>,
okText: '删除',
okType: 'danger',
......
......@@ -110,8 +110,8 @@ class UserLearningData extends React.Component {
UnbundEmployee = (record)=>{
if(User.getUserRole()==='CloudOperator'){
return confirm({
title: "你确定要解绑与用户的关系吗?",
content: "解绑后,用户该培训计划的学习数据将同步移出",
title: "你确定要解绑与学员的关系吗?",
content: "解绑后,学员该培训计划的学习数据将同步移出",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
......@@ -156,7 +156,7 @@ class UserLearningData extends React.Component {
if(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager"){
columns = [
{
title: '用户',
title: '学员',
key: 'storeCustomerName',
dataIndex: 'storeCustomerName',
render: (val, record) => {
......@@ -225,7 +225,7 @@ class UserLearningData extends React.Component {
{
title: <span>
<span>学习进度</span>
<Tooltip title="用户培训计划中达到“已完成”状态的课程数/总课程数"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
<Tooltip title="学员培训计划中达到“已完成”状态的课程数/总课程数"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
key: 'learnNum',
dataIndex: 'learnNum',
......@@ -259,7 +259,7 @@ class UserLearningData extends React.Component {
}else{
columns = [
{
title: '用户',
title: '学员',
key: 'storeCustomerName',
dataIndex: 'storeCustomerName',
render: (val, record) => {
......@@ -311,7 +311,7 @@ class UserLearningData extends React.Component {
{
title: <span>
<span>学习进度</span>
<Tooltip title="用户培训计划中达到“已完成”状态的课程数/总课程数"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
<Tooltip title="学员培训计划中达到“已完成”状态的课程数/总课程数"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
key: 'learnNum',
dataIndex: 'learnNum',
......
......@@ -126,10 +126,10 @@ function UserLearningDataFilter(props) {
<Row>
<div className="search-condition">
<div className="search-condition__item">
<span className="label customer-label">用户</span>
<span className="label customer-label">学员</span>
<Search
value={query.customerName}
placeholder="搜索用户名称"
placeholder="搜索学员名称"
onChange={(e) => { handleChangeQuery('customerName', e.target.value)}}
onSearch={ () => { props.onChange(query) } }
style={{ width: "calc(100% - 70px)" }}
......
......@@ -136,13 +136,13 @@ class ShareLiveModal extends React.Component {
<div className="right">
<div className="share-poster right__item">
<div className="title">① 海报分享</div>
<div className="sub-title">用户可通过微信扫描海报二维码,查看培训计划</div>
<div className="sub-title">学员可通过微信扫描海报二维码,查看培训计划</div>
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
</div>
<div className="share-url right__item">
<div className="title">② 链接分享</div>
<div className="sub-title">用户可通过微信或浏览器打开以下链接,查看培训计划</div>
<div className="sub-title">学员可通过微信或浏览器打开以下链接,查看培训计划</div>
<div className="content url-content">
<div className="share-url" id="shareUrl">{shareUrl}</div>
<Button type="primary" onClick={this.handleCopy}>复制</Button>
......
......@@ -163,7 +163,7 @@ class UserLearnDetailModal extends React.Component {
const { visible } = this.props;
return (
<Modal
title="用户学习详情"
title="学员学习详情"
onCancel={this.props.onClose}
onOk={this.props.onClose}
maskClosable={false}
......@@ -175,7 +175,7 @@ class UserLearnDetailModal extends React.Component {
>
<div className="customer-info">
<span className="customer-name">
<span>用户</span>
<span>学员</span>
<span>{storeCustomerName}</span>
</span>
<span className="customer-phone">
......
......@@ -288,7 +288,8 @@ class SelectPrepareFileModal extends React.Component {
const hasMore = folderList.length < totalCount;
const { fileListRef } = this.refs;
const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight === fileListRef.scrollHeight;
// const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight === fileListRef.scrollHeight;
const hasReachBottom = fileListRef.scrollTop + fileListRef.clientHeight > fileListRef.scrollHeight - 1;
if (!hasReachBottom || !hasMore) return;
const currentFolder = folderPathList[folderPathList.length - 1];
......
/*
* @Author: 吴文洁
* @Date: 2020-06-09 09:47:44
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-16 17:53:33
* @Last Modified by: chenshu
* @Last Modified time: 2021-05-08 10:46:44
* @Description: 文件夹管理
*/
......@@ -113,7 +113,6 @@ class FolderManage extends React.Component {
// 判断是否继承了父级文件权限
if (records.length > 0 && !records[0].rights) {
// debugger
records.map((item,index) => {
item.rights = parentRights;
_records.push(item);
......@@ -149,7 +148,6 @@ class FolderManage extends React.Component {
// 判断是否继承了父级文件权限
if (records.length > 0 && !records[0].rights) {
// debugger
records.map((item,index) => {
item.rights = _parentRights;
_records.push(item);
......
......@@ -48,6 +48,9 @@
width: 1280px;
margin: 0 auto;
padding: 60px 0 30px 60px;
.join-container{
margin-top:48px;
}
.user {
margin-bottom: 85px;
.image {
......
......@@ -183,7 +183,7 @@ function Header(props) {
}
function handleLogout() {
BaseService.logout({}).then((res) => {
BaseService.logout({identifier:User.getIdentifier()}).then((res) => {
User.removeUserId();
User.removeToken();
User.removeEnterpriseId();
......@@ -231,7 +231,7 @@ function Header(props) {
}}
className="college-container"
>
<div>
<div style={{ width: '100%', height: '100%' }}>
<div className="college" onClick={() => setOpenDropdown(false)}>
<span
className="college-name"
......
......@@ -198,6 +198,8 @@
align-items: center;
margin-right: 16px;
cursor: pointer;
width: 100%;
height: 100%;
.select {
cursor: pointer;
}
......
......@@ -17,11 +17,9 @@ function Aside(props: any) {
const rootSubmenuKeys = _.pluck(menuList, 'groupCode');
useEffect(() => {
const link = props.location.pathname;
console.log('link',link);
menuList.map((item: any, index: any) => {
if (link.indexOf(item.link) !== -1) {
setSelectKey(item.groupCode);
console.log('selectKey',selectKey);
setOpenKeys([])
} else if (item.children) {
item.children.map((_item: any, _index: any) => {
......@@ -46,6 +44,7 @@ function Aside(props: any) {
function handleMenu() {
handleMenuType();
}
return (
<div
id="left-container"
......@@ -75,10 +74,8 @@ function Aside(props: any) {
<Menu
style={{ minHeight: "100%", background: '#0E1935' }}
defaultSelectedKeys={selectKey}
selectedKeys={selectKey}
openKeys={openKeys}
// onOpenChange={onOpenChange}
inlineCollapsed={false}
mode={menuType ? "inline" : "vertical"}
>
......
......@@ -19,14 +19,14 @@
margin-top: 24px;
.error{
position: absolute;
width: 100%;
height: 100%;
width: 170px;
height: 170px;
background: rgba(255, 255, 255, 0.95);
display: flex;
align-items:center;
justify-content:center;
left:0px;
top: 0px;
left:-5px;
top:-5px;
div{
margin: 0 10px;
font-size: 14px;
......
......@@ -77,6 +77,7 @@ export default function WechatLogin(props: any) {
User.setUserId(_res.result.loginInfo.userId);
User.setToken(_res.result.loginInfo.xmToken);
User.setEnterpriseId(_res.result.enterpriseId);
User.setIdentifier(_res.result.identifier)
window.RCHistory.push({
pathname: `/switch-route`,
})
......
......@@ -298,7 +298,7 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
<Radio value={"CloudOperator"} className="mt-4">
<span style={{ color: "#333" }}>运营师</span>
<p className="radio-tip">
仅可查看/转发培训计划内容,并查看其负责的用户学习进度
仅可查看/转发培训计划内容,并查看其负责的学员学习进度
</p>
</Radio>
</RadioGroup>
......
/*
* @Author: wufan
* @Date: 2020-11-30 10:47:38
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-02 10:22:34
* @Description: 用户管理页面
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 16:38:40
* @Description: 学员管理页面
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -49,7 +49,7 @@ function UserManagePage() {
function parseColumn() {
return [
{
title: "用户姓名",
title: "学员姓名",
dataIndex: "nickName",
render: (val: string) => {
return (
......@@ -81,7 +81,7 @@ function UserManagePage() {
return (
<div className=" page user-manage-page">
<div className="content-header">用户管理</div>
<div className="content-header">学员管理</div>
<div className="box">
<div className="box-header">
<div
......@@ -93,13 +93,13 @@ function UserManagePage() {
}}
>
<div>
搜索用户
搜索学员
<Search
style={{
width: 300,
marginRight: 40,
}}
placeholder="搜索用户姓名/手机号"
placeholder="搜索学员姓名/手机号"
onSearch={(value) => {
const _query = { ...query };
if (value) {
......
......@@ -365,7 +365,7 @@ function AddExam(props: any) {
checked={needPhone == 'NEED_PHONE_VERIFY'}
onChange={(val) => { setNeedPhone(val ? 'NEED_PHONE_VERIFY' : 'DO_NOT_NEED_PHONE_VERIFY') }}
></Switch>
<div style={{ position: 'relative', top: 3, left: 8, color: "#999" }}><p>开启:需要绑定手机号的用户才能参加考试</p>
<div style={{ position: 'relative', top: 3, left: 8, color: "#999" }}><p>开启:需要绑定手机号的学员才能参加考试</p>
<p>关闭:微信/企业微信登陆直接参加考试</p></div>
</div>
......
......@@ -52,6 +52,10 @@ class ExamShareModal extends React.Component {
const dom = document.querySelector('#poster');
html2canvas(dom, {
useCORS: true,
scale: 2,
logging: false,
background: '#FFF',
allowTaint: true,
}).then(canvas => {
const downloadDOM = document.createElement('a');
const { courseName } = this.props.data;
......@@ -118,25 +122,11 @@ class ExamShareModal extends React.Component {
</div>
<div className="right">
<div className="share-poster right__item">
<div className="title">① 海报分享</div>
<div className="sub-title">用户可通过微信扫描海报二维码,查看考试</div>
<div className="sub-title">学员可通过微信扫描海报二维码,查看考试</div>
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
</div>
{/* <div className="share-url right__item" style={{ marginTop: 40 }}>
<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>
</Modal>
)
......
......@@ -115,7 +115,7 @@ function DataAnalysic(props: any) {
const columns = [
{
title: "用户",
title: "学员",
dataIndex: "userName",
render: (text: any, record: any) => <span>{text}<span style={{ color: record.userSource === 'WORK_WE_CHAT' ? 'rgba(255, 157, 20, 1)' : 'rgba(29, 204, 101, 1)' }} >@{(userTypeEnum as any)[record.userSource]}</span></span>,
},
......@@ -221,11 +221,11 @@ function DataAnalysic(props: any) {
<div style={{ display: 'flex' }}>
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">用户:</span>
<span className="search-name">学员:</span>
<Search
value={query.examName}
className='search-input'
placeholder="搜索用户名或手机号"
placeholder="搜索学员名或手机号"
onChange={(e) => {
const _query = { ...query }
_query.searchKey = e.target.value
......@@ -237,8 +237,8 @@ function DataAnalysic(props: any) {
</div>
<div className="search-condition__item">
<span className="search-name">用户类型:</span>
<Select value={query.userSource} placeholder="请选择用户类型" onChange={(val) => {
<span className="search-name">学员类型:</span>
<Select value={query.userSource} placeholder="请选择学员类型" onChange={(val) => {
const _query = { ...query }
_query.userSource = val
setQuery(_query);
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 11:04:03
* @LastEditTime: 2021-05-20 11:37:40
* @Description: 助学工具-题库-试卷列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -441,7 +441,6 @@ class PaperList extends Component {
pagination={false}
bordered
loading={loading}
onChange={this.handleChangeTable}
/>
) : (
<Table
......@@ -455,7 +454,6 @@ class PaperList extends Component {
columns={this.parseColumns()}
pagination={false}
bordered
onChange={this.handleChangeTable}
/>
)}
</ConfigProvider>
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-04-13 13:56:05
* @LastEditTime: 2021-05-20 11:43:21
* @Description: 助学工具-新建试卷-选择题目列表
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -108,15 +108,14 @@ class SelectQuestionList extends Component {
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
let sort = "UPDATED_DESC";
if (order === "ascend") {
if (columnKey === "accuracy" && order === "ascend") {
sort = "ACCURACY_ASC";
}
if (order === "descend") {
if (columnKey === "accuracy" && order === "descend") {
sort = "ACCURACY_DESC";
}
const _query = this.state.query;
_query.order = sort;
_query.current = 1;
this.setState({ query: _query }, () => this.queryQuestionPageListWithContent());
};
......
......@@ -53,8 +53,14 @@
}
.input-box {
line-height: 20px;
* {
display: inline;
display: inline-block;
vertical-align: top;
*:not(p) {
font-weight: normal !important;
font-size: 14px !important;
font-style: normal !important;
text-decoration: none !important;
margin: 0 !important;
}
.add-fill-line {
padding: 0 10px;
......
......@@ -120,7 +120,7 @@ class QuestionEditor extends Component {
};
editorRoot.config.zIndex = 999;
editorRoot.config.placeholder = "";
editorRoot.config.pasteFilterStyle = false;
editorRoot.config.pasteFilterStyle = true;
editorRoot.config.pasteIgnoreImg = true;
editorRoot.config.focus = false;
// 自定义处理粘贴的文本内容
......@@ -133,10 +133,11 @@ class QuestionEditor extends Component {
str1 = str1.replace(/[ | ]*\n/g, "\n");
str1 = str1.replace(/\&nbsp\;/gi, " ");
str1 = str1.replace(/[\r\n]/g, "");
str1 = str1.replace(/<\/?a.*?>/g, "");
var str2 = content; // 保留空格和换行的其他字符
str2 = str2.replace(/<xml>[\s\S]*?<\/xml>/gi, "");
str2 = str2.replace(/<style>[\s\S]*?<\/style>/gi, "");
str2 = str2.replace(/<(?!br).*?>/g, "");
str2 = str2.replace(/<\/?a.*?>/g, "");
if (editorRoot.txt.text().length + str1.length > 1000) {
content = str2.substring(0, 1000);
message.error("内容过长,不能超过1000字");
......
......@@ -38,10 +38,18 @@
border: 1px solid #e8e8e8;
display: flex;
justify-content: space-between;
font-size: 14px !important;
.editor-box_content {
width: calc(100% - 80px);
p {
display: inline;
font-size: 14px !important;
}
*:not(p){
font-weight: normal !important;
font-size: 14px !important;
font-style: normal !important;
text-decoration: none !important;
margin: 0 !important;
}
}
.editor-limit {
......@@ -56,13 +64,21 @@
border-radius: 4px;
padding: 4px 0;
border: 1px solid #e8e8e8;
font-size: 14px !important;
.editor-box_content {
max-height: 110px;
overflow: auto;
p {
display: inline;
font-size: 14px !important;
overflow-y: scroll;
}
*:not(p) {
font-weight: normal !important;
font-size: 14px !important;
font-style: normal !important;
text-decoration: none !important;
margin: 0 !important;
}
}
.editor-limit {
text-align: right;
......
......@@ -2,7 +2,7 @@
* @Author: yuananting
* @Date: 2021-02-25 11:23:47
* @LastEditors: yuananting
* @LastEditTime: 2021-04-12 14:14:19
* @LastEditTime: 2021-05-20 11:35:07
* @Description: 助学工具-题库-题目列表数据
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -161,7 +161,7 @@ class QuestionList extends Component {
height: 100,
}}
description={
<div>
<span>
<span>还没有题目</span>
{["CloudManager", "StoreManager"].includes(User.getUserRole()) &&
categoryId && (
......@@ -178,7 +178,7 @@ class QuestionList extends Component {
吧!
</span>
)}
</div>
</span>
}
></Empty>
);
......@@ -202,7 +202,6 @@ class QuestionList extends Component {
}
const _query = this.state.query;
_query.order = sort || "UPDATED_DESC";
_query.current = 1;
this.setState({ query: _query }, () => this.queryQuestionPageList());
};
......@@ -225,16 +224,27 @@ class QuestionList extends Component {
handleVal = handleVal.replace(/<(?!img|input).*?>/g, "");
handleVal = handleVal.replace(/<\s?input[^>]*>/gi, "_、");
handleVal = handleVal.replace(/\&nbsp\;/gi, " ");
handleVal = handleVal.replace(/style\s*?=\s*?([‘"])[\s\S]*?\1/gi, "");
return (
<Tooltip
overlayClassName="aid-tool-list"
title={
<div style={{ maxWidth: 700, width: "auto" }}>{handleVal}</div>
<div
style={{ maxWidth: 700, width: "auto" }}
dangerouslySetInnerHTML={{
__html: handleVal,
}}
/>
}
placement="topLeft"
overlayStyle={{ maxWidth: 700 }}
>
{handleVal}
<div
className="one-line-text"
dangerouslySetInnerHTML={{
__html: handleVal,
}}
/>
</Tooltip>
);
},
......
......@@ -32,8 +32,13 @@
color: #666666;
.input-box {
margin-bottom: 8px;
* {
display: inline-block;
display: inline-block;
*:not(p) {
font-weight: normal !important;
font-size: 14px !important;
font-style: normal !important;
text-decoration: none !important;
margin: 0 !important;
}
}
.picture-box {
......@@ -102,6 +107,13 @@
display: inline-block;
max-width: calc(100% - 20px);
vertical-align: top;
*:not(p) {
font-weight: normal !important;
font-size: 14px !important;
font-style: normal !important;
text-decoration: none !important;
margin: 0 !important;
}
}
}
......@@ -197,8 +209,13 @@
color: #666666;
.desc-input-box {
margin-bottom: 8px;
* {
display: inline-block;
display: inline-block;
*:not(p) {
font-weight: normal !important;
font-size: 14px !important;
font-style: normal !important;
text-decoration: none !important;
margin: 0 !important;
}
}
.desc-picture-box {
......
/*
* @Author: 吴文洁
* @Date: 2020-04-29 10:26:32
* @LastEditors: yuananting
* @LastEditTime: 2021-04-15 21:45:42
* @LastEditors: wufan
* @LastEditTime: 2021-05-13 16:39:51
* @Description: 内容线路由配置
*/
import Home from '@/modules/home/Home';
......@@ -17,8 +17,10 @@ import LiveCoursePage from '@/modules/course-manage/LiveCoursePage';
import AddLivePage from '@/modules/course-manage/AddLive'
import VideoCoursePage from '@/modules/course-manage/video-course'
import GraphicsCoursePage from '@/modules/course-manage/graphics-course'
import OfflineCoursePage from '@/modules/course-manage/offline-course'
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse'
import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGraphicsCourse'
import AddOfflineCoursePage from '@/modules/course-manage/offline-course/AddOfflineCourse'
// import DataList from '@/modules/course-manage/DataList/DataList';
// import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk';
......@@ -58,23 +60,18 @@ const mainRoutes = [
{
path: "/user-manage",
component: UserManagePage,
name: "用户管理",
name: "学员管理",
},
{
path: '/college-user',
component: UserManage,
name: '用户管理'
name: '学员管理'
},
{
path: '/store-decoration',
component: StoreDecorationPage,
name: '学院装修'
},
// {
// path: "/course-catalog",
// component: CourseCatalogPage,
// name: "课程分类",
// },
{
path: "/live-course",
component: LiveCoursePage,
......@@ -91,6 +88,11 @@ const mainRoutes = [
name: "图文课",
},
{
path: "/offline-course",
component: OfflineCoursePage,
name: "线下课",
},
{
path: "/create-live-course",
component: AddLivePage,
name: "创建直播课",
......@@ -112,6 +114,11 @@ const mainRoutes = [
name: "创建图文课",
},
{
path: "/create-offline-course",
component: AddOfflineCoursePage,
name: "创建线下课",
},
{
path: "/resource-disk",
component: ResourceDisk,
name: "资料云盘",
......
/*
* @Author: yuananting
* @Date: 2021-02-21 15:53:31
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-05-26 16:28:00
* @LastEditors: wufan
* @LastEditTime: 2021-05-27 10:23:30
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -33,6 +33,11 @@ export const menuList: any = [
groupCode: "GraphicLesson",
link: '/graphics-course'
},
{
groupName: "线下课",
groupCode: "OfflineClass",
link: '/offline-course'
},
]
},
{
......@@ -97,7 +102,7 @@ export const menuList: any = [
link: '/college-employee'
},
{
groupName: "用户管理",
groupName: "学员管理",
groupCode: "ShopUser",
link: '/college-user'
},
......
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