Commit 62d535e8 by wufan

feat:合并master并解决冲突

parents a6ced707 4bb97c9a
......@@ -12,7 +12,6 @@ npm-shrinkwrap.json
.DS_Store
.AppleDouble
.LSOverride
yarn.lock
yarn-error.lock
# IntelliJ project files
......@@ -57,7 +56,6 @@ npm-debug.log
*.zip
build/vendor.js.map
package-lock.json
.vscode/*
demo.js
debug.log
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -22,7 +22,7 @@
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"ali-oss": "^6.12.0",
"antd": "^4.9.4",
"antd": "^4.16.3",
"array-move": "^3.0.1",
"axios": "^0.20.0",
"babel-eslint": "10.1.0",
......@@ -34,7 +34,7 @@
"bizcharts": "^3.3.0",
"camelcase": "^5.3.1",
"case-sensitive-paths-webpack-plugin": "2.3.0",
"classnames": "^2.2.6",
"classnames": "^2.3.1",
"cropper": "^3.1.4",
"cross-env": "^7.0.3",
"css-loader": "3.4.2",
......
......@@ -7,11 +7,12 @@
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosPromise, AxiosError } from 'axios';
import { message } from 'antd';
import { message, Modal } from 'antd';
import { BASIC_HOST, TIME_OUT, USER_TYPE, VERSION, PROJECT } from '@/domains/basic-domain/constants';
import User from './user';
import { content } from 'html2canvas/dist/types/css/property-descriptors/content';
interface FetchParams {
url: string,
......@@ -27,7 +28,8 @@ interface HeadersType{
storeId?:any,
storeUserId?:any,
userId?:any,
xmtoken?:any
xmtoken?:any,
enterpriseId?:string|null
}
class Axios {
static post(
......@@ -51,6 +53,9 @@ class Axios {
if(User.getToken()){
headerObject.xmtoken = User.getToken();
}
if (User.getEnterpriseId()) {
headerObject.enterpriseId = User.getEnterpriseId();
}
const instance: AxiosInstance = axios.create({
timeout: TIME_OUT,
responseType: 'json',
......@@ -84,8 +89,14 @@ class Axios {
})
instance.interceptors.response.use((response: AxiosResponse): AxiosResponse | AxiosPromise => {
const { message: ResMessage, success, resultMsg, resultCode,code} = response.data;
if (success || resultCode === 0) {
const { message: ResMessage, success, resultMsg, code: resultCode } = response.data;
if (resultCode === "CROP_DEPLOY_PAST_BETTER") {
Modal.warning({
title:"服务已到期",
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买",
okText: "我知道了"
})
} else if (success || resultCode === 0) {
return response;
} else if (!options.reject) {
message.error(ResMessage || resultMsg);
......
......@@ -12,6 +12,10 @@ import { PREFIX, USER_PREFIX } from '@/domains/basic-domain/constants';
declare var window:any;
class User {
getExpirationTime() {
return Storage.get(`${PREFIX}_expiration_time`)
}
getVersion() {
return Storage.getObj(`${PREFIX}_version`)
}
......@@ -56,8 +60,16 @@ class User {
return Storage.get(`${PREFIX}_isAdmin`);
}
setStoreId(value: any) {
return Storage.set(`${PREFIX}_storeId`, value);
setExpirationTime(value:number) {
return Storage.set(`${PREFIX}_expiration_time`,value)
}
setVersion(value:any) {
return Storage.setObj(`${PREFIX}_version`,value)
}
setStoreId(value:any){
return Storage.set(`${PREFIX}_storeId`,value)
}
setEnterpriseId(value: any) {
......
......@@ -11,3 +11,6 @@
padding: 16px;
background-color: #F0F2F5;
}
.ant-tooltip-inner {
max-width: 1000px;
}
\ No newline at end of file
.ant-popover .ant-popover-content .ant-popover-inner {
box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.06);
.ant-popover-inner-content {
padding: 0;
}
}
.contact-widget {
width: 276px;
height: 294px;
overflow: hidden;
background-color: white;
background-image: url(https://image.xiaomaiketang.com/xm/CZ4a752jzi.png);
background-repeat: no-repeat;
background-size: cover;
text-align: center;
font-size: 14px;
font-weight: 400;
color: #333333;
line-height: 22px;
.qrcode {
width: 182px;
height: 204px;
background-color: white;
margin: 28px auto 16px auto;
img {
width: 150px;
height: 150px;
margin: 16px 16px 8px 16px;
}
}
}
\ No newline at end of file
import React, { ReactElement } from "react";
import { Popover } from "antd";
import { TooltipPlacement } from "antd/lib/tooltip";
import { ActionType } from "rc-trigger/lib/interface";
import "./ContactWidget.less"
interface ContactWidgetProps {
placement: TooltipPlacement
children: React.ReactElement
visible?: boolean
trigger: ActionType | ActionType[]
}
function Content() {
return (
<div className="contact-widget">
<div className="qrcode">
<img src="https://cdn.xiaomai5.com/qixueyuankehu.png" alt=""></img>
<div className="des">微信/企业微信扫码咨询</div>
</div>
<div className="phone"><svg style={{position:"relative",top:"2px",marginRight:"4px"}} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512.651 3.78c-281.433 0-509.21 228.324-509.21 509.209 0 281.43 228.325 509.203 509.21 509.203 281.427 0 509.202-228.317 509.202-509.203 0.55-280.885-227.775-509.21-509.202-509.21z m198.205 743.553c-36.14 36.136-169.737 1.641-302.24-130.312-131.953-131.959-165.902-266.104-129.768-301.695 31.211-31.21 68.99-85.417 125.939-14.782 56.943 70.629 29.016 90.34-3.291 122.647-22.449 22.448 24.642 79.392 73.37 128.125 49.283 48.73 105.678 95.818 128.126 73.368 32.306-32.305 52.017-60.23 122.646-3.288 71.182 56.949 16.426 95.276-14.782 125.937z" p-id="4409" fill="#999999"></path></svg>
咨询电话:19157875632</div>
</div>
)
}
export default function ContactWidget(props:ContactWidgetProps) {
return <Popover
placement={props.placement}
arrowPointAtCenter
content={Content}
visible={props.visible}
trigger={props.trigger}
>
{props.children}
</Popover>
}
\ No newline at end of file
......@@ -12,6 +12,7 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100%;
overflow: hidden;
.page {
position: fixed;
top: 60px;
......@@ -21,6 +22,7 @@
z-index: 102;
overflow: auto;
margin: 0 16px;
.box {
&:first-child {
margin-bottom: 8px;
......
/*
* @Author: yuananting
* @Date: 2021-03-03 15:13:12
* @LastEditors: fusanqiasng
* @LastEditTime: 2021-06-16 09:57:18
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-22 14:31:46
* @Description: 助学工具接口
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......
/*
* @Author: wufan
* @Date: 2020-12-01 17:21:21
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-04-09 14:28:09
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-22 14:56:34
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from "@/common/js/service";
import User from "@/common/js/user";
import axios from 'axios';
export function sendLoginAuthCode(params: object) {
return Service.Hades("anon/hades/sendLoginAuthCode", params);
......@@ -47,6 +49,19 @@ export function getEnterpriseUser(params: object) {
export function getWXWorkLoginNoCheck(params: object) {
return Service.Hades('anon/hades/getWXWorkLoginNoCheck', params);
}
export function getLesseeVersionMsg() {
return Service.Hades("public/customerHades/getLesseeVersionMsg",{enterpriseId:User.getEnterpriseId()})
}
export function getYoZoSign(params: object) {
return Service.Apollo('public/apollo/getYoZoSign', params);
}
export function saveYoZoFileVersionId(params: object) {
return Service.Apollo('public/apollo/saveYoZoFileVersionId', params);
}
export function yoZoUpload(ossUrl:String,appId:String,uploadSign:String){
return axios.post(`https://dmc.yozocloud.cn/api/file/http?fileUrl=${ossUrl}&appId=${appId}&sign=${uploadSign}`)
}
export const getOssClient = (
data: object,
instId: string,
......@@ -63,3 +78,4 @@ export const getOssClient = (
});
}
......@@ -7,6 +7,7 @@
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from "@/common/js/service";
import User from "@/common/js/user"
export function getEmployeeList(params: object) {
return Service.Hades("public/hades/getStoreUserPage", params);
......@@ -76,3 +77,4 @@ export function getStoreDetail(params: object) {
}
/*
* @Author: wufan
* @Date: 2020-12-01 17:20:49
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-04-09 14:28:59
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-22 14:57:01
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { getUserStore, getUserPermission ,logout,getStoreUser,sendBizAuthCode,editUserPhone,checkBizAuthCode,sendNewPhoneAuthCode,sendLoginAuthCode,login,getLastedVersion, getEnterpriseUser,getWXWorkLoginNoCheck} from '@/data-source/base/request-apis';
import { getUserStore, getUserPermission ,logout,getStoreUser,sendBizAuthCode,editUserPhone,checkBizAuthCode,sendNewPhoneAuthCode,sendLoginAuthCode,login,getLastedVersion, getEnterpriseUser,getWXWorkLoginNoCheck,getLesseeVersionMsg,getYoZoSign,saveYoZoFileVersionId,yoZoUpload} from '@/data-source/base/request-apis';
export default class StoreService {
// 获取员工列表
......@@ -55,5 +55,22 @@ export default class StoreService {
static getWXWorkLoginNoCheck(params: any){
return getWXWorkLoginNoCheck(params);
}
//获取企业配置的版本信息
static getLesseeVersionMsg() {
return getLesseeVersionMsg();
}
static getYoZoSign(params:any){
return new Promise((resolve) => {
getYoZoSign(params).then(res => {
const { result = [] } = res;
resolve(result)
});
})
}
static saveYoZoFileVersionId(params: any){
return saveYoZoFileVersionId(params);
}
static yoZoUpload(ossUrl:String,appId:String,uploadSign:String){
return yoZoUpload(ossUrl,appId,uploadSign);
}
}
\ No newline at end of file
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-06-11 16:44:17
* @LastEditTime: 2021-06-22 16:49:06
* @LastEditors: Please set LastEditors
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
......@@ -26,6 +26,10 @@ const PATH_MAP: MapInterface = {
prod: 'https://res.xiaomai0.com/xiaomai-cloud-class-web/h5.html',
}
export const YZ_APPId = "yozoqvpO2Hvz8346";
export const YZ_PREVIEW_URL: string = 'http://eic.yozocloud.cn/api/view/file'
export const OFFICE_PREVIEW_URL: string = 'https://view.officeapps.live.com/op/view.aspx'
// axios headers config
export const TIME_OUT: number = 20000
export const USER_TYPE: string = 'B'
......@@ -37,3 +41,4 @@ export const USER_PREFIX = 'store-live'
// host
export const BASIC_HOST: string = BASIC_HOST_MAP[ENV]
export const PATH: string = PATH_MAP[ENV]
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-04-27 20:35:34
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-23 14:14:20
* @LastEditTime: 2021-06-28 16:15:59
* @Description:
*/
......@@ -21,6 +21,7 @@ import '@/core/function';
import '@/core/xmTD';
import User from '@/common/js/user';
import Service from "@/common/js/service";
import BaseService from '@/domains/basic-domain/baseService';
declare var getParameterByName: any;
declare var window: any;
......@@ -71,7 +72,27 @@ if (getParameterByName('code') && isWeiXin()) {
window.currentStoreUserInfo.enterpriseId = res.result.enterpriseId;
mount()
})
} else {
} else if(getParameterByName('from') === 'customer' && getParameterByName('enterpriseId') && getParameterByName('userId')){
User.setCustomerStoreId(getParameterByName('storeId'));
getWXWorkLoginNoCheck(getParameterByName('enterpriseId'),getParameterByName('userId')); //从C端跳转过来的学院自动执行免登录
}else{
mount()
}
function getWXWorkLoginNoCheck(enterpriseId:string,userId:string) {
const params = {
appTermEnum: 'XIAOMAI_CLOUD_CLASS_PC_WEB_ADMIN',
enterpriseId,
userId,
};
BaseService.getWXWorkLoginNoCheck(params).then((res:any) => {
User.setUserId(res.result.loginInfo.userId)
User.setToken(res.result.loginInfo.xmToken)
User.setEnterpriseId(res.result.enterpriseId)
window.currentStoreUserInfo = {}
window.currentStoreUserInfo.userId = res.result.loginInfo.userId;
window.currentStoreUserInfo.token = res.result.loginInfo.xmToken;
window.currentStoreUserInfo.enterpriseId = res.result.enterpriseId;
User.setIdentifier(res.result.identifier)
mount();
});
}
......@@ -15,6 +15,7 @@ import college from '@/common/lottie/college.json';
import StoreService from "@/domains/store-domain/storeService";
import EmployeeAddOrEditModal from "../store-manage/EmployeeAddOrEditModal";
import User from "@/common/js/user";
import LimitTip from "./LimitTip";
import "./EmployeeManage.less";
import ChooseMembersModal from "./modal/ChooseMembersModal";
......@@ -68,6 +69,7 @@ function EmployeeManage() {
});
const [total, setTotal] = useState(0);
const [realTotal, setRealTotal] = useState(0)
const [model, setModel] = useState<React.ReactNode>(null);
const [employeeModal, setEmployeeModal] = useState(false);
const [choosedItem, setChooseItem] = useState<ChoosedItemType>({
......@@ -85,6 +87,17 @@ function EmployeeManage() {
if (!User.getEnterpriseId()) {
window.RCHistory.replace('/employees-manage');
}
//获取员工数
const _query = {
current: 0,
size: 10,
nickName: "",
phone: "",
roleCodes: [],
}
StoreService.getEmployeeList(_query).then((res: any) => {
setRealTotal(res.result.total);
});
}, [])
useEffect(() => {
......@@ -362,6 +375,7 @@ function EmployeeManage() {
</Button>
}
</div>
<LimitTip type="员工" total={realTotal} tip={()=>{return (<div>数据为当前学院的员工数,若员工存在多个学院,企业人数只统计为1人</div>)}}/>
<div className="box-body">
<XMTable
renderEmpty={{
......
.limit-tip {
background: #E9EFFF;
border-radius: 2px;
margin-bottom: 13px;
.always {
display: inline-block;
font-size: 14px;
line-height: 32px;
font-weight: 400;
color: #666666;
margin-left: 16px;
.renew-text {
display: inline-block;
color: #2966FF;
cursor: pointer;
}
}
}
\ No newline at end of file
import React, { useContext, useEffect, useState } from "react";
import { Tooltip } from "antd"
import { VersionContext } from "@/store/context";
import ContactWidget from "@/components/ContactWidget";
import "./LimitTip.less"
export default function LimitTip(props:{total:number,type:string,tip:() => React.ReactNode}) {
const [isOver, setIsOver] = useState(false)
const [limitUser, setLimitUser] = useState("0")
const versionInfo = useContext(VersionContext)
useEffect(()=> {
if (versionInfo) {
setIsOver(versionInfo.userNum === -1 ? false : versionInfo.whetherReachUserNum)
setLimitUser(versionInfo.userNum === -1 ? "不限人数" : String(versionInfo.userNum))
}
},[versionInfo])
return (
<div className="limit-tip">
<div className="always">本学院{props.type}<span style={{color:"#333333",fontWeight:"bold"}}>{props.total}</span>
<Tooltip overlayStyle={{maxWidth:"587px",width:"fit-content"}} placement="topLeft" arrowPointAtCenter title={props.tip}>
<span className="icon iconfont" style={{cursor:"pointer",marginLeft:"4px",color:"#bfbfbf"}}>&#59449;</span>
</Tooltip>
{
isOver ? (
<>
<div style={{marginLeft:"14px",display:"inline-block"}}>当前企业使用人数已达到上限 (<span style={{color:"#333333",fontWeight:"bold"}}>{limitUser}</span>人),将无法添加新员工、新学员,如需增加人数限制,请联系小麦企学院服务平台。</div>
<ContactWidget trigger="hover" placement="bottom">
<div className="renew-text">立即续费<span className="icon iconfont" style={{fontSize:"10px"}}>&#59291;</span></div>
</ContactWidget>
</>
) : ("")
}
</div>
</div>
)
}
\ No newline at end of file
......@@ -17,4 +17,5 @@
margin-left: 4px;
}
}
}
\ No newline at end of file
......@@ -15,6 +15,8 @@ import { Input, DatePicker, Select, Button, message } from "antd";
import StoreService from "@/domains/store-domain/storeService";
import User from "@/common/js/user";
import ChooseMembersModal from "./modal/ChooseMembersModal";
import LimitTip from "./LimitTip"
import { XMTable } from '@/components';
import college from '@/common/lottie/college.json';
import "./UserManagePage.less";
......@@ -26,6 +28,8 @@ const { RangePicker } = DatePicker;
declare var window: any;
function UserManagePage() {
const [userList, setUserList] = useState([]);
const [model, setModel] = useState<React.ReactNode>(null);
......@@ -39,11 +43,25 @@ function UserManagePage() {
sourceEnum: undefined,
});
const [total, setTotal] = useState(0);
const [realTotal, setRealTotal] = useState(0)
useEffect(() => {
if (!User.getEnterpriseId()) {
window.RCHistory.replace('/user-manage');
}
//获取学员数
const _query = {
current: 0,
size: 10,
nickName: "",
phone: "",
registerBegin: null,
registerEnd: null,
sourceEnum: undefined,
}
StoreService.getUserList(_query).then((res: any) => {
setRealTotal(res.result.total);
});
}, [])
useEffect(() => {
......@@ -125,9 +143,6 @@ function UserManagePage() {
<div className="header-item">
<span className="item-name">搜索学员:</span>
<Search
style={{
width: 300,
}}
placeholder="搜索学员姓名/手机号"
onSearch={(value) => {
const _query = { ...query };
......@@ -199,6 +214,7 @@ function UserManagePage() {
}}
>添加学员</Button>
}
<LimitTip type="学员" total={realTotal} tip={()=>{ return (<div><div>1、数据为当前学院的员工数,若学员存在多个学院,企业人数只统计为1人;</div><div>2、若一个学员既用「企业微信」登录学习又用「微信」登录学习,企业人数将统计为2人。</div></div>)}}/>
<div className="box-body">
<XMTable
renderEmpty={{
......
......@@ -285,6 +285,10 @@ class ChooseMembersModal extends React.Component {
visible={isOpen}
onCancel={() => this.handleClose()}
onOk={() => {
if (User.getVersion() && User.getVersion().whetherReachUserNum) {
message.error("添加失败,企业使用人数超出限制")
return
}
if (_.isEmpty(selectUserList)) {
message.warning(type === 'USER' ? '请选择员工' : '请选择学员')
return null;
......
......@@ -269,6 +269,15 @@ handleChangeBasicInfo = (field, value) => {
// 完成创建/编辑
handleSubmit = () => {
//过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({
title:"服务已到期",
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买",
okText: "我知道了"
})
return
}
const { addLiveBasicInfo, addLiveClassInfo, addLiveIntroInfo, id, isEdit, type } = this.state;
const {liveDate, timeHorizonStart} = addLiveClassInfo;
const _liveDate = moment(liveDate).format("YYYY-MM-DD");
......
import User from '@/common/js/user';
import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components';
import CourseService from '@/domains/course-domain/CourseService';
import { Button, message } from 'antd';
import React from 'react';
import { withRouter } from "react-router-dom";
import { Table, Button, Modal, message } from 'antd';
import { withRouter } from 'react-router-dom';
import dealTimeDuration from '../utils/dealTimeDuration';
import { PageControl } from "@/components";
import './DataList.less';
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user';
const liveTypeMap = {
USER: "普通用户",
ANCHOR: "讲师",
ADMIN: "管理员(助教)",
GUEST: "游客"
USER: '普通用户',
ANCHOR: '讲师',
ADMIN: '管理员(助教)',
GUEST: '游客',
};
class PlaybackData extends React.Component {
constructor(props) {
super(props);
const courseId = getParameterByName("id"); // 课程ID
const courseId = getParameterByName('id'); // 课程ID
this.state = {
playbackData: [],
......@@ -24,8 +25,8 @@ class PlaybackData extends React.Component {
size: 10,
total: 0,
liveCourseId: courseId,
storeId: User.getStoreId()
}
storeId: User.getStoreId(),
};
}
componentDidMount() {
......@@ -33,12 +34,12 @@ class PlaybackData extends React.Component {
}
fetchPlaybackList = (page = 1) => {
const { size, liveCourseId } = this.state
const { size, liveCourseId } = this.state;
const params = {
liveCourseId,
current: page,
size
}
size,
};
CourseService.fetchPlaybackList(params).then((res) => {
if (res.result) {
......@@ -47,38 +48,35 @@ class PlaybackData extends React.Component {
playbackData: records,
current,
size,
total
total,
});
}
});
};
getPlaybackColumns() {
const columns = [
{
title: "观看学员",
dataIndex: "userName",
title: '观看学员',
dataIndex: 'userName',
},
{
title: "手机号",
dataIndex: "phone"
title: '手机号',
dataIndex: 'phone',
},
{
title: "观看者类型",
dataIndex: "userRole",
title: '观看者类型',
dataIndex: 'userRole',
render: (text) => <span>{liveTypeMap[text]}</span>,
},
{
title: "开始观看时间",
dataIndex: "entryTime",
render: (text) => (
<span>{text ? formatDate("YYYY-MM-DD H:i", parseInt(text)) : '-'}</span>
),
title: '开始观看时间',
dataIndex: 'entryTime',
render: (text) => <span>{text ? formatDate('YYYY-MM-DD H:i', parseInt(text)) : '-'}</span>,
},
{
title: "观看时长",
dataIndex: "lookingDuration",
title: '观看时长',
dataIndex: 'lookingDuration',
render: (text) => {
return <span>{text ? dealTimeDuration(text) : '-'}</span>;
},
......@@ -92,53 +90,55 @@ class PlaybackData extends React.Component {
CourseService.exportPlayBackCourseData({
liveCourseId,
exportLiveType: "PLAY_BACK",
storeId
exportLiveType: 'PLAY_BACK',
storeId,
}).then((res) => {
const link = res.result;
this.setState({
link
link,
});
document.getElementById("load-play-back-excel").click();
document.getElementById('load-play-back-excel').click();
if(res.success){
message.success("导出成功!")
if (res.success) {
message.success('导出成功!');
}
})
});
}
onShowSizeChange = (current, size) => {
if (current == size) {
return;
}
this.setState({ size }, this.fetchUserData)
}
this.setState({ size }, this.fetchUserData);
};
render() {
const { playbackData, total, current, size, link} = this.state
const { playbackData, total, current, size, link } = this.state;
return (
<div>
<a
href={link}
target="_blank"
download
id="load-play-back-excel"
style={{ position: "absolute", left: "-10000px" }}
>
<a href={link} target='_blank' download id='load-play-back-excel' style={{ position: 'absolute', left: '-10000px' }}>
111
</a>
<Button onClick={() => {this.handleplaybackExport()}}>导出</Button>
<Table
<Button
onClick={() => {
this.handleplaybackExport();
}}>
导出
</Button>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
bordered
size="small"
size='small'
columns={this.getPlaybackColumns()}
dataSource={playbackData}
pagination={false}
style={{ margin: '16px 0' }}>
</Table>
{ total > 0 &&
style={{ margin: '16px 0' }}></XMTable>
{total > 0 && (
<PageControl
size="small"
size='small'
current={current - 1}
pageSize={size}
total={total}
......@@ -147,9 +147,9 @@ class PlaybackData extends React.Component {
this.fetchPlaybackList(page + 1);
}}
/>
}
)}
</div>
)
);
}
}
......
......@@ -22,6 +22,13 @@ class LiveCoursePage extends React.Component {
componentWillMount() {
this.handleFetchLiveList(this.state.query);
}
changeShelfState = (index, shelfState) => {
const { courseList } = this.state;
courseList[index].shelfState = shelfState;
this.setState({
courseList,
});
};
// 获取直播课列表
handleFetchLiveList = (_query) => {
const { query } = this.state;
......@@ -54,6 +61,7 @@ class LiveCoursePage extends React.Component {
total={total}
courseList={courseList}
onChange={this.handleFetchLiveList}
changeShelfState={this.changeShelfState}
/>
</div>
</div>
......
......@@ -10,7 +10,7 @@
import React from 'react';
import { Button, Input, Radio, message, Modal, Cascader } from 'antd';
import $ from 'jquery';
import moment from 'moment';
import { DISK_MAP, FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum';
import { ImgCutModalNew } from '@/components';
import ShowTips from '@/components/ShowTips';
......@@ -390,6 +390,15 @@ class AddGraphicsCourse extends React.Component {
// 保存
handleSubmit = () => {
//过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({
title:"服务已到期",
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买",
okText: "我知道了"
})
return
}
const { id, coverId, pageType, courseName, courseMedia, introduce, categoryId, shelfState, whetherVisitorsJoin } = this.state;
const commonParams = {
......
......@@ -27,7 +27,14 @@ class GraphicsCourse extends React.Component {
this.handleFetchScheduleList();
}
// 获取线上课列表
changeShelfState = (index, shelfState) => {
const { dataSource } = this.state;
dataSource[index].shelfState = shelfState;
this.setState({
dataSource,
});
};
// 获取视频课列表
handleFetchScheduleList = (_query = {}) => {
const query = {
...this.state.query,
......@@ -75,6 +82,7 @@ class GraphicsCourse extends React.Component {
dataSource={dataSource}
totalCount={totalCount}
onChange={this.handleFetchScheduleList}
changeShelfState={this.changeShelfState}
/>
</div>
</div>
......
......@@ -6,79 +6,85 @@
* @Description 余额异常弹窗
*/
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import Service from "@/common/js/service";
import User from '@/common/js/user'
import { Modal, Input } from 'antd';
import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components';
import Service from '@/common/js/service';
import User from '@/common/js/user';
import './WatchDataModal.less';
import dealTimeDuration from "../../utils/dealTimeDuration";
import dealTimeDuration from '../../utils/dealTimeDuration';
const { Search } = Input;
class WatchDataModal extends React.Component {
constructor(props) {
super(props);
this.state = {
visible:true,
dataSource:[],
size:10,
visible: true,
dataSource: [],
size: 10,
query: {
current: 1,
},
totalCount:0
totalCount: 0,
};
}
componentDidMount() {
this.handleFetchDataList();
}
onClose = () =>{
onClose = () => {
this.props.close();
}
};
// 获取观看视频数据列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const { query, size, totalCount } = this.state;
const { id } = this.props.data;
const params ={
const params = {
...query,
size,
courseId:id,
storeId:User.getStoreId()
}
courseId: id,
storeId: User.getStoreId(),
};
Service.Hades('public/hades/mediaCourseWatchInfo', params).then((res) => {
const { result = {} } = res ;
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
totalCount: Number(total),
});
});
}
handleChangNickname = (value)=>{
};
handleChangNickname = (value) => {
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
if (isPhone) {
query.phone = value;
query.nickName = null;
}else{
} else {
query.nickName = value;
query.phone = null;
}
query.current = 1;
this.setState({
query
})
}
query,
});
};
onShowSizeChange = (current, size) => {
if (current == size) {
return
return;
}
this.setState({
size
},()=>{this.handleFetchDataList()})
this.setState(
{
size,
},
() => {
this.handleFetchDataList();
}
);
};
// 请求表头
parseColumns = () => {
......@@ -86,91 +92,109 @@ class WatchDataModal extends React.Component {
{
title: '观看学员',
key: 'name',
dataIndex: 'name'
dataIndex: 'name',
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone'
dataIndex: 'phone',
},
{
title: '观看者类型',
key: 'userRole',
dataIndex: 'userRole'
dataIndex: 'userRole',
},
{
title: '首次观看时间',
key: 'firstWatch',
dataIndex: 'firstWatch',
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
return formatDate('YYYY-MM-DD H:i', val);
},
},
{
title: '观看总时长',
key: 'watchDuration',
dataIndex: 'watchDuration',
render: (val) => {
return <span>{val ? dealTimeDuration(val) : "00:00:00" }</span>
}
return <span>{val ? dealTimeDuration(val) : '00:00:00'}</span>;
},
},
{
title: '学习进度',
key: 'progress',
dataIndex: 'progress',
render: (val) => {
return <span>{val === 100 ? '已完成' : `${val || 0}%`}</span>
}
}
return <span>{val === 100 ? '已完成' : `${val || 0}%`}</span>;
},
},
];
return columns;
}
};
render() {
const { visible,size,dataSource,totalCount,query} = this.state;
const { visible, size, dataSource, totalCount, query } = this.state;
return (
<Modal
title="图文课观看数据"
title='图文课观看数据'
visible={visible}
footer={null}
onCancel={this.onClose}
maskClosable={false}
className="watch-data-modal"
className='watch-data-modal'
closable={true}
width={800}
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>}/>
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>}
/>
</div>
<div>
<Table
rowKey={record => record.id}
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
/>
{dataSource.length >0 &&
<div className="box-footer">
{dataSource.length > 0 && (
<div className='box-footer'>
<PageControl
current={query.current - 1}
pageSize={size}
total={totalCount}
size="small"
size='small'
toPage={(page) => {
const _query = {...query, current: page + 1};
this.setState({
query:_query
},()=>{ this.handleFetchDataList()})
const _query = { ...query, current: page + 1 };
this.setState(
{
query: _query,
},
() => {
this.handleFetchDataList();
}
);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
)}
</div>
</Modal>
)
);
}
}
......
import college from '@/common/lottie/college';
import { XMTable } from '@/components';
import { Modal } from 'antd';
import React from 'react';
import { Modal, Table } from "antd";
import ChargeArgeement from "./ChargeArgeement";
import "./AccountChargeModal.less";
class AccountChargeRecords extends React.Component{
import './AccountChargeModal.less';
import ChargeArgeement from './ChargeArgeement';
class AccountChargeRecords extends React.Component {
constructor(props) {
super(props);
this.state = {
......@@ -15,9 +17,7 @@ class AccountChargeRecords extends React.Component{
getList = () => {
const { instId } = window.currentUserInstInfo;
axios
.Business("public/liveAssets/rechargeProtocol", { instId })
.then((res) => {
axios.Business('public/liveAssets/rechargeProtocol', { instId }).then((res) => {
const list = res.result;
this.setState({
list,
......@@ -42,29 +42,28 @@ class AccountChargeRecords extends React.Component{
render() {
const columns = [
{
title: "签订人",
dataIndex: "operatorName",
width: 140
title: '签订人',
dataIndex: 'operatorName',
width: 140,
},
{ title: "关联订单ID", dataIndex: "orderId" },
{ title: '关联订单ID', dataIndex: 'orderId' },
{
title: "签订时间",
dataIndex: "createTime",
title: '签订时间',
dataIndex: 'createTime',
render: (text, record) => {
return <span>{formatDate("YYYY-MM-DD H:i", parseInt(text))}</span>;
return <span>{formatDate('YYYY-MM-DD H:i', parseInt(text))}</span>;
},
},
{
title: "签订协议",
dataIndex: "operate",
title: '签订协议',
dataIndex: 'operate',
render: (text, record) => {
return (
<div
style={{ cursor: "pointer", color: "#FC9C6B" }}
style={{ cursor: 'pointer', color: '#FC9C6B' }}
onClick={() => {
this.handleProtcol(record.protocolId);
}}
>
}}>
《服务协议》
</div>
);
......@@ -74,29 +73,31 @@ class AccountChargeRecords extends React.Component{
const { list } = this.state;
return (
<Modal
title="服务协议签订记录"
title='服务协议签订记录'
visible={true}
width={680}
footer={null}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}
onCancel={() => {
this.props.close();
}}
>
}}>
<div>
<div
style={{
fontSize: "14px",
color: "#666666",
lineHeight: "20px",
fontSize: '14px',
color: '#666666',
lineHeight: '20px',
marginBottom: 16,
}}
>
}}>
以下是本校区自助充值时签订协议的记录
</div>
<Table
size="middle"
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
size='middle'
columns={columns}
dataSource={list}
pagination={false}
......
import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components';
import { Modal, Tooltip } from 'antd';
import React from 'react';
import { Modal, Table, Tooltip } from "antd";
import { ShowTips, PageControl } from "@/components";
import "./AccountChargeModal.less";
import './AccountChargeModal.less';
class ChargingDetailModal extends React.Component {
constructor(props) {
......@@ -25,9 +26,7 @@ class ChargingDetailModal extends React.Component {
handleToPage = (page = 1) => {
const params = _.clone(this.state.query);
params.current = page;
axios
.Apollo("public/businessLive/queryStudentVisitData", params)
.then((res) => {
axios.Apollo('public/businessLive/queryStudentVisitData', params).then((res) => {
if (res.result) {
const { records = [], total } = res.result;
this.setState({
......@@ -40,7 +39,7 @@ class ChargingDetailModal extends React.Component {
};
getTeacherData = () => {
window.axios
.Apollo("public/businessLive/queryTeacherVisitData", {
.Apollo('public/businessLive/queryTeacherVisitData', {
liveCourseId: this.props.liveCourseId,
})
.then((res) => {
......@@ -57,27 +56,27 @@ class ChargingDetailModal extends React.Component {
let hours = Math.floor(time / 3600);
let mins = Math.floor(diff / 60);
let seconds = Math.floor(time % 60);
hours = hours < 10 ? "0" + hours : hours;
mins = mins < 10 ? "0" + mins : mins;
seconds = seconds < 10 ? "0" + seconds : seconds;
return hours + ":" + mins + ":" + seconds;
hours = hours < 10 ? '0' + hours : hours;
mins = mins < 10 ? '0' + mins : mins;
seconds = seconds < 10 ? '0' + seconds : seconds;
return hours + ':' + mins + ':' + seconds;
};
getColumns = (type) => {
const columns = [
{
title: type == "student" ? "学生姓名" : "老师姓名",
dataIndex: "userName",
title: type == 'student' ? '学生姓名' : '老师姓名',
dataIndex: 'userName',
},
{
title: "手机号",
dataIndex: "phone",
title: '手机号',
dataIndex: 'phone',
render: (text, record) => {
return <p>{text}</p>;
},
},
{
title: "累计在线时长",
dataIndex: "totalDuration",
title: '累计在线时长',
dataIndex: 'totalDuration',
render: (text, record) => {
return <span>{text ? this.dealTimeDuration(text) : '-'}</span>;
},
......@@ -86,14 +85,14 @@ class ChargingDetailModal extends React.Component {
title: (
<span>
是否计费&nbsp;
<Tooltip title="仅对累计在线时长≥10分钟的老师或学员计费">
<span className="icon iconfont">&#xe6f2;</span>
<Tooltip title='仅对累计在线时长≥10分钟的老师或学员计费'>
<span className='icon iconfont'>&#xe6f2;</span>
</Tooltip>
</span>
),
dataIndex: "type",
dataIndex: 'type',
render: (text, record) => {
return <span>{record.totalDuration > 600 ? "计费" : "不计费"}</span>; //大于十分钟的计费
return <span>{record.totalDuration > 600 ? '计费' : '不计费'}</span>; //大于十分钟的计费
},
},
];
......@@ -103,38 +102,45 @@ class ChargingDetailModal extends React.Component {
const { list, query, totalCount, teacherList } = this.state;
return (
<Modal
title="计费人数详情"
title='计费人数详情'
visible={true}
width={680}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
className="charging-detail-modal"
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}
className='charging-detail-modal'
footer={null}
onCancel={() => {
this.props.close();
}}
>
}}>
<div>
<div style={{ marginBottom: 16 }}>
<div className="detail-title">老师详情</div>
<Table
size="middle"
columns={this.getColumns("teacher")}
<div className='detail-title'>老师详情</div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
size='middle'
columns={this.getColumns('teacher')}
dataSource={teacherList}
pagination={false}
bordered
/>
</div>
<div className="detail-title">学生详情</div>
<Table
size="middle"
columns={this.getColumns("student")}
<div className='detail-title'>学生详情</div>
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
size='middle'
columns={this.getColumns('student')}
dataSource={list}
pagination={false}
bordered
/>
<PageControl
size="small"
size='small'
current={query.current - 1}
pageSize={query.size}
total={totalCount}
......
......@@ -6,21 +6,19 @@
* @LastEditTime: 2021-02-01 14:00:36
*/
import React, { useState, useEffect } from "react";
import { Modal, Table, Button, message } from "antd";
import college from '@/common/lottie/college';
import { PageControl, XMTable } from '@/components';
import Bus from '@/core/bus';
import { PageControl } from "@/components";
import hasExportPermission from '../utils/hasExportPermission';
import { Button, message, Modal } from 'antd';
import React from 'react';
import dealTimeDuration from '../utils/dealTimeDuration';
import "./ClassRecordModal.less";
import hasExportPermission from '../utils/hasExportPermission';
import './ClassRecordModal.less';
const liveTypeMap = {
USER: "学生",
ANCHOR: "老师",
ADMIN: "助教",
USER: '学生',
ANCHOR: '老师',
ADMIN: '助教',
};
class PlayBackRecordModal extends React.Component {
......@@ -46,9 +44,7 @@ class PlayBackRecordModal extends React.Component {
fetchPlayBackList = (page = 1) => {
const params = _.clone(this.state.query);
params.current = page;
window.axios
.Apollo("public/businessLive/queryUserReplayRecordPage", params)
.then((res) => {
window.axios.Apollo('public/businessLive/queryUserReplayRecordPage', params).then((res) => {
const { records = [], total } = res.result;
this.setState({
query: params,
......@@ -61,7 +57,7 @@ class PlayBackRecordModal extends React.Component {
fetchAllStatistics = () => {
const { liveCourseId } = this.props.liveItem;
window.axios
.Apollo("public/businessLive/queryReplayStatistics", {
.Apollo('public/businessLive/queryReplayStatistics', {
liveCourseId,
})
.then((res) => {
......@@ -80,7 +76,7 @@ class PlayBackRecordModal extends React.Component {
const hours = Math.floor(time / 3600);
const mins = Math.floor(diff / 60);
const seconds = Math.floor(time % 60);
return hours + "小时" + mins + "分";
return hours + '小时' + mins + '分';
};
// 导出
......@@ -88,10 +84,12 @@ class PlayBackRecordModal extends React.Component {
const { liveItem, type } = this.props;
const { liveCourseId } = liveItem;
const url = !type ? 'api-b/b/lesson/exportLargeClassLiveAsync' : 'api-b/b/lesson/exportClassInteractionLiveSync';
window.axios.post(url, {
window.axios
.post(url, {
liveCourseId,
exportLiveType: 0
}).then((res) => {
exportLiveType: 0,
})
.then((res) => {
Bus.trigger('get_download_count');
Modal.success({
title: '导出任务提交成功',
......@@ -99,16 +97,18 @@ class PlayBackRecordModal extends React.Component {
okText: '我知道了',
});
});
}
};
handleExportV5 = () => {
const { liveItem, type } = this.props;
const { liveCourseId } = liveItem;
const url = !type ? 'public/businessLive/exportLargeClassLiveAsync' : 'public/businessLive/exportClassInteractionLiveSync';
window.axios.Apollo(url, {
window.axios
.Apollo(url, {
liveCourseId,
exportLiveType: 'PLAY_BACK'
}).then((res) => {
exportLiveType: 'PLAY_BACK',
})
.then((res) => {
Bus.trigger('get_download_count');
Modal.success({
title: '导出任务提交成功',
......@@ -116,78 +116,67 @@ class PlayBackRecordModal extends React.Component {
okText: '我知道了',
});
});
}
};
render() {
const columns = [
{
title: "观看者姓名",
dataIndex: "userName",
title: '观看者姓名',
dataIndex: 'userName',
},
{
title: "观看者手机号",
dataIndex: "phone",
title: '观看者手机号',
dataIndex: 'phone',
render: (text, record) => {
return (
<p>
{!(
(!window.NewVersion && !window.currentUserInstInfo.teacherId) ||
(window.NewVersion && Permission.hasEduStudentPhone())
)
? (text || "").replace(/(\d{3})(\d{4})(\d{4})/, "$1****$3")
{!((!window.NewVersion && !window.currentUserInstInfo.teacherId) || (window.NewVersion && Permission.hasEduStudentPhone()))
? (text || '').replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3')
: text}
</p>
);
},
},
{
title: "观看者类型",
dataIndex: "liveRole",
key: "liveRole",
title: '观看者类型',
dataIndex: 'liveRole',
key: 'liveRole',
render: (text) => <span>{liveTypeMap[text]}</span>,
},
{
title: "开始观看时间",
dataIndex: "entryTime",
key: "entryTime",
render: (text) => (
<span>{text ? formatDate("YYYY-MM-DD H:i", parseInt(text)) : '-'}</span>
),
title: '开始观看时间',
dataIndex: 'entryTime',
key: 'entryTime',
render: (text) => <span>{text ? formatDate('YYYY-MM-DD H:i', parseInt(text)) : '-'}</span>,
},
{
title: "观看时长",
dataIndex: "lookingDuration",
key: "lookingDuration",
title: '观看时长',
dataIndex: 'lookingDuration',
key: 'lookingDuration',
render: (text) => {
return <span>{text ? dealTimeDuration(text) : '-'}</span>;
},
},
];
const {
query,
total,
playBackList,
totalWatchNum,
recordDuration,
} = this.state;
const { query, total, playBackList, totalWatchNum, recordDuration } = this.state;
const { type } = this.props;
return (
<Modal
title="回放记录"
className="play-back-modal"
title='回放记录'
className='play-back-modal'
width={680}
visible={true}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
closeIcon={<span className='icon iconfont modal-close-icon'>&#xe6ef;</span>}
footer={null}
onCancel={() => {
this.props.close();
}}
>
{
hasExportPermission(type) &&
<Button onClick={_.debounce(() => {
}}>
{hasExportPermission(type) && (
<Button
onClick={_.debounce(
() => {
if (!playBackList.length) {
message.warning('暂无数据可导出');
return;
......@@ -197,17 +186,26 @@ class PlayBackRecordModal extends React.Component {
} else {
this.handleExport();
}
}, 500, true)}>导出</Button>
}
<Table
size="small"
},
500,
true
)}>
导出
</Button>
)}
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
size='small'
columns={columns}
dataSource={playBackList}
pagination={false}
className="table-no-scrollbar"
className='table-no-scrollbar'
/>
<PageControl
size="small"
size='small'
current={query.current - 1}
pageSize={query.size}
total={total}
......
......@@ -6,67 +6,67 @@
* @Description: 大班直播分享弹窗
*/
import React from 'react'
import { Modal, Button, message } from 'antd'
import domtoimage from 'dom-to-image'
import React from 'react';
import { Modal, Button, message } from 'antd';
import domtoimage from 'dom-to-image';
import qrcode from '@/libs/qrcode/qrcode.js'
import User from '@/common/js/user'
import $ from 'jquery'
import _ from 'underscore'
import CourseService from '@/domains/course-domain/CourseService'
import qrcode from '@/libs/qrcode/qrcode.js';
import User from '@/common/js/user';
import $ from 'jquery';
import _ from 'underscore';
import CourseService from '@/domains/course-domain/CourseService';
import './ShareLiveModal.less'
import './ShareLiveModal.less';
class ShareLiveModal extends React.Component {
constructor(props) {
super(props)
super(props);
this.state = {
shareUrl: 'https://xiaomai5.com/liveShare?courseId=12',
}
};
}
componentDidMount() {
// 获取短链接
this.handleConvertShortUrl()
this.handleConvertShortUrl();
}
handleConvertShortUrl = () => {
const { longUrl } = this.props.data
const { longUrl } = this.props.data;
// 发请求
CourseService.getQrcode({
urls: [longUrl],
}).then((res) => {
const { result = [] } = res
const { result = [] } = res;
this.setState(
{
shareUrl: result[0].shortUrl,
},
() => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap')
const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new qrcode({
text: this.state.shareUrl,
size: 98,
})
qrcodeWrapDom.appendChild(qrcodeNode)
});
qrcodeWrapDom.appendChild(qrcodeNode);
const qrcodeWrapDomDownload = document.querySelector('#qrcodeWrap-dowload')
const qrcodeWrapDomDownload = document.querySelector('#qrcodeWrap-dowload');
const qrcodeNodeDownLoad = new qrcode({
text: this.state.shareUrl,
size: 196,
})
qrcodeWrapDomDownload.appendChild(qrcodeNodeDownLoad)
}
)
})
});
qrcodeWrapDomDownload.appendChild(qrcodeNodeDownLoad);
}
);
});
};
componentWillUnmount() {
// 页面销毁之前清空定时器
clearTimeout(this.timer)
clearTimeout(this.timer);
}
// 下载海报
......@@ -79,47 +79,48 @@ class ShareLiveModal extends React.Component {
() => {
this.setState({ time: new Date().valueOf() }, () => {
setTimeout(() => {
let node = document.getElementById('poster-dowload')
let node = document.getElementById('poster-dowload');
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()
})
}, 1000)
})
}
)
console.log(imgData);
const download = document.createElement('a');
const { courseName } = this.props.data;
$(download).attr('href', imgData).attr('download', `${courseName}.png`).get(0).click();
});
}, 1000);
});
}
);
};
// 复制分享链接
handleCopy = () => {
const textContent = document.getElementById('shareUrl').innerText
const textContent = document.getElementById('shareUrl').innerText;
window.copyText(textContent)
message.success('复制成功!')
}
window.copyText(textContent);
message.success('复制成功!');
};
render() {
const { courseDivision, data, type, title } = this.props
const { courseName, scheduleVideoUrl, courseMediaVOS, coverUrl } = data
const { shareUrl, showImg, time } = this.state
const { courseDivision, data, type, title } = this.props;
const { courseName, scheduleVideoUrl, courseMediaVOS, coverUrl } = data;
const { shareUrl, showImg, time } = this.state;
// 判断是否是默认图, 默认图不需要在URL后面增加字符串
let coverImgSrc = '';
switch (type) {
case 'liveClass': // 直播课
if (courseMediaVOS && courseMediaVOS.length > 0) {
data.courseMediaVOS.map((item, index) => {
if (item.contentType === 'COVER') {
coverImgSrc = item.mediaUrl
}
})
const coverItem = courseMediaVOS.filter((item) => item.contentType === 'COVER');
coverImgSrc = coverItem.length === 0 ? 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png' : coverItem[0].mediaUrl;
} else {
coverImgSrc = 'https://image.xiaomaiketang.com/xm/Yip2YtFDwH.png';
}
break;
case 'videoClass': // 线上课
coverImgSrc = coverUrl || (courseDivision === 'internal' ? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring` : 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png')
case 'videoClass': // 视频课
coverImgSrc =
coverUrl ||
(courseDivision === 'internal'
? `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
: 'https://image.xiaomaiketang.com/xm/mt3ZQRxGKB.png');
break;
case 'graphicsClass': // 图文课
coverImgSrc = coverUrl || 'https://image.xiaomaiketang.com/xm/wFnpZtp2yB.png';
......@@ -214,8 +215,8 @@ class ShareLiveModal extends React.Component {
</div>
</div>
</Modal>
)
);
}
}
export default ShareLiveModal
export default ShareLiveModal;
......@@ -466,6 +466,15 @@ class AddOfflineCourse extends React.Component {
};
preSubmit = () => {
//过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({
title:"服务已到期",
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买",
okText: "我知道了"
})
return
}
const { courseId } = this.state;
if (courseId) {
this.checkDetail(courseId).then(bool => bool ? this.handleSubmit() : message.warning('课程已开始,无法继续编辑'))
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors: wufan
* @LastEditTime: 2021-05-30 20:35:49
* @LastEditTime: 2021-07-05 10:24:25
* @Description: 线上课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -12,6 +12,7 @@ import { Button, Input, message, Modal, Cascader, Tooltip, Form, Popconfirm } f
import { FileTypeIcon, FileVerifyMap } from '@/common/constants/academic/lessonEnum'
import ShowTips from '@/components/ShowTips'
import Breadcrumbs from '@/components/Breadcrumbs'
import moment from 'moment'
import AddVideoIntro from './components/AddVideoIntro'
import SelectStudent from '../modal/select-student'
import SelectPrepareFileModal from '../../prepare-lesson/modal/SelectPrepareFileModal'
......@@ -377,6 +378,15 @@ class AddVideoCourse extends React.Component {
// 保存
handleSubmit = () => {
//过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({
title:"服务已到期",
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买",
okText: "我知道了"
})
return
}
const { instId, adminId } = window.currentUserInstInfo
const {
......
......@@ -2,7 +2,7 @@
* @Author: wufan
* @Date: 2020-04-28 18:05:30
* @LastEditors: wufan
* @LastEditTime: 2021-06-25 11:49:55
* @LastEditTime: 2021-07-05 10:36:15
* @Description: 线上课课程课节详情
*/
......@@ -23,44 +23,7 @@ function VideoCourseDetail(){
const [coverUrl,setCoverUrl] = useState("");
const [videoName,setVideoName] = useState("");
const [scheduleVideoUrl,setScheduleVideoUrl] = useState("");
const [courseChapterList,setCourseChapterList] = useState<Array<Object>>([
{
mediaContent: 1408354568285888514,
contentType: 'SCHEDULE',
mediaType: "VIDEO",
name: '发生科技快快快规划的开发规划狂热结构化可如何改了如何更好润肤露萨科技股份考试',
videoDuration: 200,
id: 1408354568285888514,
mediaUrl: 'http://xmdev-resource-pub.oss-cn-hangzhou.aliyuncs.com/inst/22222222222222222/lesson/upload/0/20210630163635/AM4Mbf66P2hrEFPd.mp4'
},
{
mediaContent: 1408354568285888514,
contentType: 'SCHEDULE',
mediaType: "VIDEO",
name: '发生科技快快快规划的开发规试',
videoDuration: 200,
id: 1408354568285888515,
mediaUrl: 'http://xmdev-resource-pub.oss-cn-hangzhou.aliyuncs.com/inst/22222222222222222/lesson/upload/0/20210630163635/AM4Mbf66P2hrEFPd.mp4'
},
{
mediaContent: 1408354568285888514,
contentType: 'SCHEDULE',
mediaType: "VIDEO",
name: '发生科技快快快规划的开发规划狂热结构化可如何改了如何更好润肤露萨科技股份考试',
videoDuration: 200,
id: 1408354568285888516,
mediaUrl: 'http://xmdev-resource-pub.oss-cn-hangzhou.aliyuncs.com/inst/22222222222222222/lesson/upload/0/20210630163635/AM4Mbf66P2hrEFPd.mp4'
},
{
mediaContent: 1408354568285888514,
contentType: 'SCHEDULE',
mediaType: "VIDEO",
name: '发生科技快快快规划的开发规试',
videoDuration: 200,
id: 1408354568285888517,
mediaUrl: 'http://xmdev-resource-pub.oss-cn-hangzhou.aliyuncs.com/inst/22222222222222222/lesson/upload/0/20210630163635/AM4Mbf66P2hrEFPd.mp4'
}
]);
const [courseChapterList,setCourseChapterList] = useState<Array<Object>>([]);
const [courseName,setCourseName] = useState("");
const [categoryName,setCategoryName] = useState("");
const [scanFileModal,setScanFileModal] = useState<any>(null);
......@@ -107,7 +70,7 @@ function VideoCourseDetail(){
setScheduleVideoUrl(scheduleVideoUrl);
setCourseName(courseName);
setCategoryName(categoryName);
// setCourseChapterList(courseChapterVOList);
setCourseChapterList(courseChapterVOList);
})
}
......
......@@ -5,80 +5,86 @@
* @Last Modified time: 2020-05-25 16:50:47
* @Description 学员观看数据弹窗
*/
import User from '@/common/js/user';
import { PageControl, XMTable } from '@/components';
import CourseService from '@/domains/course-domain/CourseService';
import { Input, Modal } from 'antd';
import college from '@/common/lottie/college';
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user'
import dealTimeDuration from '../../utils/dealTimeDuration';
import './WatchDataModal.less';
import dealTimeDuration from "../../utils/dealTimeDuration";
const { Search } = Input;
class WatchDataModal extends React.Component {
constructor(props) {
super(props);
this.state = {
visible:true,
dataSource:[],
size:10,
visible: true,
dataSource: [],
size: 10,
query: {
current: 1,
},
totalCount:0
totalCount: 0,
};
}
componentDidMount() {
this.handleFetchDataList();
}
onClose = () =>{
onClose = () => {
this.props.close();
}
};
// 获取观看视频数据列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const { query, size, totalCount } = this.state;
const { id } = this.props.data;
const params ={
const params = {
...query,
size,
courseId:id,
storeId:User.getStoreId()
}
courseId: id,
storeId: User.getStoreId(),
};
CourseService.videoWatchInfo(params).then((res) => {
const { result = {} } = res ;
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
totalCount: Number(total),
});
});
}
handleChangNickname = (value)=>{
};
handleChangNickname = (value) => {
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
if (isPhone) {
query.phone = value;
query.nickName = null;
}else{
} else {
query.nickName = value;
query.phone = null;
}
query.current = 1;
this.setState({
query
})
}
query,
});
};
onShowSizeChange = (current, size) => {
if (current == size) {
return
return;
}
this.setState({
size
},()=>{this.handleFetchDataList()})
this.setState(
{
size,
},
() => {
this.handleFetchDataList();
}
);
};
// 请求表头
parseColumns = () => {
......@@ -86,40 +92,40 @@ class WatchDataModal extends React.Component {
{
title: '观看学员',
key: 'name',
dataIndex: 'name'
dataIndex: 'name',
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone'
dataIndex: 'phone',
},
{
title: '观看者类型',
key: 'userRole',
dataIndex: 'userRole'
dataIndex: 'userRole',
},
{
title: '首次观看时间',
key: 'firstWatch',
dataIndex: 'firstWatch',
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
return formatDate('YYYY-MM-DD H:i', val);
},
},
{
title: '观看时长',
key: 'watchDuration',
dataIndex: 'watchDuration',
render: (val) => {
return <span>{val ? dealTimeDuration(val) : "00:00:00" }</span>
}
}
return <span>{val ? dealTimeDuration(val) : '00:00:00'}</span>;
},
},
];
return columns;
}
};
render() {
const { visible,size,dataSource,totalCount,query} = this.state;
const { visible, size, dataSource, totalCount, query } = this.state;
return (
<Modal
title="线上课观看数据"
......@@ -136,7 +142,12 @@ class WatchDataModal extends React.Component {
<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
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
......
......@@ -2,34 +2,35 @@
// padding: 0 16px 16px;
min-width: 1100px;
position: relative;
z-index:3;
z-index: 3;
.g2-tooltip-marker {
border-radius: 50% !important;
}
.home-title {
color: #333;
padding-left:28px;
font-size:16px;
font-weight:bold;
padding-left: 28px;
font-size: 16px;
font-weight: bold;
position: relative;
padding-top:16px;
&::before{
width:4px;
height:12px;
content:'';
background-image: linear-gradient(#2966FF 83.5%, #0ACCA4 16.5%);
display:inline-block;
padding-top: 16px;
&::before {
width: 4px;
height: 12px;
content: '';
background-image: linear-gradient(#2966ff 83.5%, #0acca4 16.5%);
display: inline-block;
position: absolute;
left:16px;
top:22px;
left: 16px;
top: 22px;
}
}
@font-face {
font-family: 'number';
src: url('https://image.xiaomaiketang.com/xm/n2sADd2jY6.TTF');
}
.data-wrap{
background: #FFF;
.data-wrap {
background: #fff;
padding-bottom: 25px;
.data-box {
display: flex;
justify-content: space-between;
......@@ -41,7 +42,7 @@
width: ~'calc(16.67% - 8px)';
padding: 16px;
&.course-data {
width: ~'calc(50% - 24px)';
width: 33%;
}
.header {
display: flex;
......@@ -55,7 +56,7 @@
font-size: 14px;
line-height: 18px;
color: #333;
font-weight:500;
font-weight: 500;
}
}
.data-number {
......@@ -74,7 +75,7 @@
.iconfont {
font-size: 12px;
margin-right: 4px;
color: #EC4B35;
color: #ec4b35;
}
.footer-number {
font-size: 12px;
......@@ -83,17 +84,17 @@
}
.course-box {
border-radius: 4px;
background: #FAFAFA;
background: #fafafa;
height: 124px;
width: 66%;
width: 60%;
position: absolute;
right: 16px;
top: 16px;
padding: 8px 24px 0;
top: 28px;
padding: 8px 0 0;
.course-item {
display: inline-block;
width: 50%;
padding: 4px 0 12px;
padding: 4px 24px 12px;
.course-title {
font-size: 12px;
color: #999;
......@@ -105,6 +106,7 @@
font-size: 16px;
font-family: 'number';
margin-right: 16px;
white-space: nowrap;
}
.course-word {
font-size: 12px;
......@@ -114,11 +116,12 @@
.iconfont {
font-size: 12px;
margin-right: 4px;
color: #EC4B35;
color: #ec4b35;
}
.add-number {
font-size: 12px;
color: #999;
white-space: nowrap;
}
}
}
......@@ -126,9 +129,9 @@
}
}
}
.study-wrap{
background: #FFF;
margin-top:16px;
.study-wrap {
background: #fff;
margin-top: 16px;
.study-box {
display: flex;
justify-content: space-between;
......@@ -148,7 +151,7 @@
margin-bottom: 12px;
.iconfont {
font-size: 14px;
color: #BFBFBF;
color: #bfbfbf;
margin-left: 4px;
}
.tip {
......@@ -169,13 +172,13 @@
color: #666;
cursor: pointer;
&.selected {
color: #2966FF;
color: #2966ff;
&::after {
position: absolute;
width: 24px;
height: 2px;
content: '';
background: #2966FF;
background: #2966ff;
border-radius: 1px;
left: 9px;
bottom: -4px;
......@@ -196,7 +199,7 @@
display: flex;
align-items: center;
&.odd {
background: #FAFAFA;
background: #fafafa;
}
.table-image {
width: 24px;
......@@ -257,7 +260,7 @@
width: 204px;
height: 204px;
border-radius: 102px;
background: #F1F3F6;
background: #f1f3f6;
margin-top: -20px;
.small-circle {
display: flex;
......@@ -280,13 +283,13 @@
&.unfinished {
top: 152px;
.spot {
background: #2966FF;
background: #2966ff;
}
}
&.finished {
top: 232px;
.spot {
background: #FFBB54;
background: #ffbb54;
}
}
.spot {
......@@ -352,14 +355,14 @@
align-items: center;
color: #666;
.student-dot {
background: #2966FF;
background: #2966ff;
height: 8px;
width: 8px;
border-radius: 50%;
margin-right: 8px;
}
.time-dot {
background: #FEB613;
background: #feb613;
height: 8px;
width: 8px;
border-radius: 50%;
......@@ -382,7 +385,8 @@
font-size: 14px;
}
.g2-tooltip-list {
li,span {
li,
span {
display: flex;
align-items: center;
font-size: 14px;
......
.home-tip {
.tip {
height: 40px;
background: #FFE7E7;
margin-bottom: 16px;
.content {
font-size: 14px;
color: #666666;
font-weight: 400;
line-height: 40px;
padding-left: 16px;
.renew-btn {
display: inline-block;
width: 80px;
height: 28px;
background: #FF4F4F;
border-radius: 2px;
color: #ffffff;
font-size: 14px;
font-weight: 400;
line-height: 28px;
text-align: center;
cursor: pointer;
margin-left: 8px;
}
}
}
}
\ No newline at end of file
import React, { useContext, useEffect, useState, version } from "react";
import "./HomeTip.less"
import { VersionContext } from "@/store/context";
import ContactWidget from '@/components/ContactWidget';
import { Carousel } from "antd";
import moment from "moment";
export default function HomeTip() {
const [isOverNum, setIsOverNum] = useState<boolean>(false)
const [tipType, setTipType] = useState(0) //0不显示1即将过期2已过期
const [expirationTime, setExpirationTime] = useState("")
const [surplusDay, setSurplusDay] = useState(0)
const versionInfo = useContext(VersionContext)
useEffect(()=> {
if (versionInfo) {
setIsOverNum(versionInfo.userNum === -1 ? false : versionInfo.whetherReachUserNum)
setSurplusDay(versionInfo.surplusDayTime)
setExpirationTime(versionInfo.validEndTime)
if (versionInfo.stateEnum === "NO") {
setTipType(2)
} else if (versionInfo.surplusDayTime === 30 || versionInfo.surplusDayTime <= 7) {
setTipType(1)
}
}
},[versionInfo])
return (
<div className="home-tip">
{
(isOverNum || tipType !== 0) &&
<div className="tip">
<Carousel dotPosition="left" dots={false} autoplay={true} autoplaySpeed={5000}>
{
isOverNum && (
<div className="content">
<span className="icon iconfont" style={{color:"#FF4F4F",marginRight:"8px"}}>&#xe61d;</span>温馨提示:企业使用人数已达上限,将无法新增员工、学员,如需增加人数限制,请联系小麦企学院服务平台。
<ContactWidget placement="bottom" trigger="hover"><div className="renew-btn">立即续费</div></ContactWidget>
</div>
)
}
{
tipType === 2 && (
<div className="content">
<span className="icon iconfont" style={{color:"#FF4F4F",marginRight:"8px"}}>&#xe61d;</span>版本到期提醒:当前企业购买的小麦企学院服务已于{moment(versionInfo?.validEndTimeST).format("YYYY-MM-DD HH:mm:ss")}到期,到期后仍可访问,但功能不可使用,建议尽快续费购买哦~
<ContactWidget placement="bottom" trigger="hover"><div className="renew-btn">立即续费</div></ContactWidget>
</div>
)
}
{
tipType === 1 && (
<div className="content">
<span className="icon iconfont" style={{color:"#FF4F4F",marginRight:"8px"}}>&#xe61d;</span>当前企业购买的小麦企学院服务仅剩{surplusDay}天(于{expirationTime}到期),为了不影响使用,建议尽快续费购买哦~
<ContactWidget placement="bottom" trigger="hover"><div className="renew-btn">立即续费</div></ContactWidget>
</div>
)
}
</Carousel>
</div>
}
</div>
)
}
\ No newline at end of file
import React from 'react';
import { withRouter } from "react-router-dom";
import {Table, Modal,Input,message} from 'antd';
import { PageControl } from "@/components";
import PlanService from '@/domains/plan-domain/planService'
import { withRouter } from 'react-router-dom';
import { Input } from 'antd';
import { PageControl, XMTable } from '@/components';
import college from '@/common/lottie/college';
import PlanService from '@/domains/plan-domain/planService';
import User from '@/common/js/user';
import Bus from '@/core/bus';
import './EmployeeShareData.less';
......@@ -10,103 +11,129 @@ import './EmployeeShareData.less';
const { Search } = Input;
const UserRole = {
Store_Manager: {
text: "学院管理员"
text: '学院管理员',
},
Cloud_Manager: {
text:"管理员"
text: '管理员',
},
Cloud_Operator: {
text:'运营师'
text: '运营师',
},
Cloud_Lecture: {
text:"讲师"
text: '讲师',
},
};
class EmployeeShareData extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id");
const id = getParameterByName('id');
this.state = {
id,
dataSource:[],
size:10,
dataSource: [],
size: 10,
query: {
current: 1,
},
totalCount:0,
}
totalCount: 0,
};
}
componentDidMount(){
componentDidMount() {
this.handleFetchDataList();
}
handleFetchDataList = ()=>{
const { query ,size,id} = this.state;
const params ={
handleFetchDataList = () => {
const { query, size, id } = this.state;
const params = {
...query,
size,
planId:id,
storeId:User.getStoreId(),
}
planId: id,
storeId: User.getStoreId(),
};
PlanService.getPlanUserRecordPage(params).then((res) => {
const { result = {} } = res ;
const { result = {} } = res;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
totalCount: Number(total),
});
});
}
};
onShowSizeChange = (current, size) => {
if (current == size) {
return
return;
}
this.setState({
size
},()=>{this.handleFetchDataList()})
this.setState(
{
size,
},
() => {
this.handleFetchDataList();
}
handleChangeTable = (pagination, filters, sorter)=> {
);
};
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
const { query } = this.state;
let _columnKey;
let _order;
if (columnKey === 'learnNum' && order === 'ascend') { _columnKey="LEARN_NUM"; _order = 'SORT_ASC'; }
if (columnKey === 'learnNum' && order === 'descend') { _columnKey="LEARN_NUM"; _order = 'SORT_DESC'; }
if (columnKey === 'learnNum' && order === 'ascend') {
_columnKey = 'LEARN_NUM';
_order = 'SORT_ASC';
}
if (columnKey === 'learnNum' && order === 'descend') {
_columnKey = 'LEARN_NUM';
_order = 'SORT_DESC';
}
if (columnKey === 'learnFinishNum' && order === 'ascend') { _columnKey="FINISH_NUM"; _order = 'SORT_ASC'; }
if (columnKey === 'learnFinishNum' && order === 'descend') { _columnKey="FINISH_NUM"; _order = 'SORT_DESC'; }
if (columnKey === 'learnFinishNum' && order === 'ascend') {
_columnKey = 'FINISH_NUM';
_order = 'SORT_ASC';
}
if (columnKey === 'learnFinishNum' && order === 'descend') {
_columnKey = 'FINISH_NUM';
_order = 'SORT_DESC';
}
if (columnKey === 'learnNoFinishNum' && order === 'ascend') { _columnKey="NOT_NUM"; _order = 'SORT_ASC'; }
if (columnKey === 'learnNoFinishNum' && order === 'descend') { _columnKey="NOT_NUM"; _order = 'SORT_DESC'; }
if (columnKey === 'learnNoFinishNum' && order === 'ascend') {
_columnKey = 'NOT_NUM';
_order = 'SORT_ASC';
}
if (columnKey === 'learnNoFinishNum' && order === 'descend') {
_columnKey = 'NOT_NUM';
_order = 'SORT_DESC';
}
const _query = {
...query,
sortMap:{}
sortMap: {},
};
_query.sortMap[_columnKey]=_order;
this.setState({
query:_query
},()=>this.handleFetchDataList())
}
handleChangNickname = (value)=>{
_query.sortMap[_columnKey] = _order;
this.setState(
{
query: _query,
},
() => this.handleFetchDataList()
);
};
handleChangNickname = (value) => {
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
if (isPhone) {
query.userPhone = value;
query.userName = null;
}else{
} else {
query.userName = value;
query.userPhone = null;
}
query.current = 1;
this.setState({
query
})
}
watchDataView = (record)=>{
Bus.trigger('watchDataView',record);
}
query,
});
};
watchDataView = (record) => {
Bus.trigger('watchDataView', record);
};
// 请求表头
parselumns = () => {
const columns = [
......@@ -115,24 +142,16 @@ class EmployeeShareData extends React.Component {
key: 'storeUserName',
dataIndex: 'storeUserName',
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
return <div>{val}</div>;
},
},
{
title: '角色',
key: 'roleEnum',
dataIndex: 'roleEnum',
render: (val, record) => {
return (
<div>
{UserRole[record.roleEnum].text}
</div>
)
}
return <div>{UserRole[record.roleEnum].text}</div>;
},
},
//产品暂时性隐藏
// {
......@@ -151,28 +170,20 @@ class EmployeeShareData extends React.Component {
title: '最近分享成功时间',
key: 'recentlyForwardTime',
dataIndex: 'recentlyForwardTime',
width:240,
width: 240,
render: (val, record) => {
return (
<div>
{formatDate('YYYY-MM-DD H:i', val)}
</div>
)
}
return <div>{formatDate('YYYY-MM-DD H:i', val)}</div>;
},
},
{
title: '学习人数',
key: 'learnNum',
dataIndex: 'learnNum',
width:110,
sorter:true,
width: 110,
sorter: true,
render: (val, record) => {
return (
<div className="learn-num">
{val}
</div>
)
}
return <div className='learn-num'>{val}</div>;
},
},
// {
// title: '已学完',
......@@ -208,24 +219,39 @@ class EmployeeShareData extends React.Component {
dataIndex: 'operate',
render: (val, record) => {
return (
<span className="operate-item" onClick={()=>this.watchDataView(record)}>数据详情</span>
)
}
}
<span className='operate-item' onClick={() => this.watchDataView(record)}>
数据详情
</span>
);
},
},
];
return columns;
}
};
render() {
const { dataSource,query,size,totalCount} = this.state;
const { dataSource, query, size, totalCount } = this.state;
return (
<div className="employee-share-data">
<div className="search-container">
<Search placeholder="搜索员工姓名或手机号" onChange={(e) => { this.handleChangNickname(e.target.value)}} onSearch={ () => { this.handleFetchDataList()}} style={{ width: 200 }} enterButton={<span className="icon iconfont">&#xe832;</span>}/>
<div className='employee-share-data'>
<div className='search-container'>
<Search
placeholder='搜索员工姓名或手机号'
onChange={(e) => {
this.handleChangNickname(e.target.value);
}}
onSearch={() => {
this.handleFetchDataList();
}}
style={{ width: 200 }}
enterButton={<span className='icon iconfont'>&#xe832;</span>}
/>
</div>
<div>
<Table
rowKey={record => record.id}
<XMTable
renderEmpty={{
image: college,
description: '暂无数据',
}}
rowKey={(record) => record.id}
dataSource={dataSource}
columns={this.parselumns()}
pagination={false}
......@@ -233,25 +259,30 @@ class EmployeeShareData extends React.Component {
showSorterTooltip={false}
bordered
/>
{dataSource.length >0 &&
<div className="box-footer">
{dataSource.length > 0 && (
<div className='box-footer'>
<PageControl
current={query.current - 1}
pageSize={size}
total={totalCount}
toPage={(page) => {
const _query = {...query, current: page + 1};
this.setState({
query:_query
},()=>{ this.handleFetchDataList()})
const _query = { ...query, current: page + 1 };
this.setState(
{
query: _query,
},
() => {
this.handleFetchDataList();
}
);
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
)}
</div>
</div>
)
);
}
}
......
......@@ -13,7 +13,6 @@ import { Spin, message } from 'antd';
import User from '@/common/js/user';
import OperateArea from './OperateArea';
import FolderList from './FolderList';
import { DISK_MAP, suffixMap } from "@/common/constants/academic/lessonEnum";
......@@ -301,27 +300,6 @@ class FolderManage extends React.Component {
onChangeFolderPath={this.handleChangeFolderPath}
onRefresh={this.handleFetchFolderList}
/>
{/* 文件夹列表 */}
<FolderList
query={query}
totalCount={totalCount}
balance={balance}
showResultPage={showResultPage}
currentRootDisk={currentRootDisk}
hasManagementAuthority={hasManagementAuthority}
folderList={folderList}
folderPathList={folderPathList}
selectedFileIds={selectedFileIds}
onChangeRow={this.handleChangeRow}
onChangeFolderPath={this.handleChangeFolderPath}
onMove={this.handleMove}
onUpload={this.handleUploadDone}
onChangePage={this.handleChangePage}
onRefresh={this.handleFetchFolderList}
/>
</div>
</Spin>
......
......@@ -21,9 +21,9 @@ import UploadProgressModal from '@/bu-components/UploadProgressModal';
import SelectPrepareFileModal from '@/bu-components/SelectPrepareFileModal';
import CopyFileModal from '@/bu-components/CopyFileModal';
import ManagingMembersModal from '@/bu-components/ManagingMembersModal';
import PreviewFileModal from '../modal/PreviewFileModal'
import PreviewFileModal from '@/bu-components/PreviewFileModal';
import {YZ_APPId,YZ_PREVIEW_URL,OFFICE_PREVIEW_URL} from '@/domains/basic-domain/constants';
import BaseService from "@/domains/basic-domain/baseService";
import ScanFileModal from '../modal/ScanFileModal';
import CreateFolderModal from '../modal/CreateFolderModal';
import User from '@/common/js/user';
......@@ -121,40 +121,6 @@ class FolderList extends React.Component {
break;
}
}
getYoZoSign = (data,type,folderName)=>{
return new Promise((resolve) => {
let uploadParams;
if(type==="UPLOAD"){
uploadParams ={
fileUrl:data,
instId:window.currentUserInstInfo.instId,
yoZoTypeEnum:'UPLOAD'
}
}else{
uploadParams ={
fileVersionId:data,
instId:window.currentUserInstInfo.instId,
yoZoTypeEnum:'VIEW',
htmlTitle:folderName
}
}
Service.Apollo('public/apollo/getYoZoSign', uploadParams).then(res => {
const { result = [] } = res;
resolve(result)
});
})
}
saveYoZoFileVersionId = (fileVersionId,folderId)=>{
const params ={
fileVersionId,
folderId,
instId: window.currentUserInstInfo.instId,
}
Service.Apollo('public/apollo/saveYoZoFileVersionId', params).then(res => {
});
}
// 预览文件
handleScanFile = async (folder) => {
const { folderFormat, folderSize, ossUrl,rights,fileVersionId,id,folderName} = folder;
......@@ -177,24 +143,46 @@ class FolderList extends React.Component {
showPreviewModal:true,
previewStatus:'UPLOAD'
},async ()=>{
const uploadSign = await that.getYoZoSign(ossUrl,"UPLOAD");
axios.post(`https://dmc.yozocloud.cn/api/file/http?fileUrl=${ossUrl}&appId=${appId}&sign=${uploadSign}`)
.then(async function (response){
that.saveYoZoFileVersionId(response.data.data.fileVersionId,id);
const uploadParams ={
fileUrl:ossUrl,
instId:User.getStoreId(),
yoZoTypeEnum:'UPLOAD'
}
const uploadSign = await BaseService.getYoZoSign(uploadParams);
BaseService.yoZoUpload(ossUrl,YZ_APPId,uploadSign).then(async function (response){
const saveParams ={
fileVersionId:response.data.data.fileVersionId,
folderId:id,
instId:User.getStoreId(),
}
BaseService.saveYoZoFileVersionId(saveParams);
const { previewing } = that.state;
if(previewing){
const previewSign = await that.getYoZoSign(response.data.data.fileVersionId,"VIEW",folderName);
const url = `https://eic.yozocloud.cn/api/view/file?fileVersionId=${response.data.data.fileVersionId}&appId=${appId}&sign=${previewSign}&htmlTitle=${folderName}`
const previewParams ={
fileVersionId:response.data.data.fileVersionId,
instId:User.getStoreId(),
yoZoTypeEnum:'VIEW',
htmlTitle:folderName
}
const previewSign = await BaseService.getYoZoSign(previewParams);
const url = `${YZ_PREVIEW_URL}?fileVersionId=${response.data.data.fileVersionId}&appId=${YZ_APPId}&sign=${previewSign}&htmlTitle=${folderName}`
that.setState({
previewStatus:'UPLOAD_SUCCESS',
url
})
}
})
})
}else{
const previewSign = await that.getYoZoSign(fileVersionId,"VIEW",folderName);
const url = `http://eic.yozocloud.cn/api/view/file?fileVersionId=${fileVersionId}&appId=${appId}&sign=${previewSign}&htmlTitle=${folderName}`
const previewParams ={
fileVersionId,
instId:User.getStoreId(),
yoZoTypeEnum:'VIEW',
htmlTitle:folderName
}
const previewSign = await BaseService.getYoZoSign(previewParams);
const url = `${YZ_PREVIEW_URL}?fileVersionId=${fileVersionId}&appId=${YZ_APPId}&sign=${previewSign}&htmlTitle=${folderName}`
const a = document.createElement('a');
document.body.appendChild(a);
a.setAttribute('href', url);
......@@ -234,7 +222,6 @@ class FolderList extends React.Component {
Modal.confirm({
title: '抱歉,不能在线预览',
content: '由于文件较大,不支持在线预览,请下载后再查看',
// icon: <Icon type="question-circle" theme="filled" style={{ color: '#FF8534' }}></Icon>,
okText: "下载",
onOk: () => {
const a = document.createElement('a');
......@@ -244,21 +231,7 @@ class FolderList extends React.Component {
});
break;
}
// if (folderFormat === 'EXCEL') {
// Modal.confirm({
// title: '抱歉,不能在线预览',
// content: ' 该文件类型不支持在线预览,请下载后再查看',
// // icon: <Icon type="question-circle" theme="filled" style={{ color: '#FF8534' }}></Icon>,
// okText: "下载",
// onOk: () => {
// const a = document.createElement('a');
// a.href = ossUrl;
// a.click();
// }
// });
// break;
// }
const prefixUrl = "https://view.officeapps.live.com/op/view.aspx?src=";
const prefixUrl = `${OFFICE_PREVIEW_URL}?src=`;
const scanUrl = `${prefixUrl}${encodeURIComponent(ossUrl)}`
window.open(scanUrl, "_blank");
break;
......
......@@ -14,7 +14,8 @@ import Main from './Main'
import zhCN from 'antd/es/locale/zh_CN'
import User from '@/common/js/user';
import BaseService from "@/domains/basic-domain/baseService";
import { XMContext } from '@/store/context';
import moment from 'moment';
import { VersionContext, VersionInfo, XMContext } from '@/store/context';
import { setStoreGroupPermission, setStorePermission, setStoreGroupList, setStoreList } from '@/store/actions/index';
import Service from "@/common/js/service";
import Bus from '@/core/tbus';
......@@ -27,6 +28,7 @@ declare var window: any;
const App: React.FC = (props: any) => {
const [storeUserId, setStoreUserId] = useState('')
const ctx: any = useContext(XMContext);
const [versionInfo, setVersionInfo] = useState<VersionInfo|null>(null)
const userId = User.getUserId();
const [menuType, setMenuType] = useState(true);
const enterpriseId = User.getEnterpriseId();
......@@ -35,6 +37,7 @@ const App: React.FC = (props: any) => {
useEffect(() => {
getStoreAndUserInfo();
getVersion();
if (window.location.hash === "#/") {
window.RCHistory.replace({
pathname: '/home',
......@@ -58,6 +61,26 @@ const App: React.FC = (props: any) => {
}
});
}
function getVersion() {
BaseService.getLesseeVersionMsg().then((res) => {
let version = res.result;
User.setVersion(version);
User.setExpirationTime(res.result.validEndTime)
let versioninfo:VersionInfo = {
dayTime: version.dayTime,
stateEnum: version.stateEnum,
userNum: version.userNum === -1 ? '不限人数' : version.userNum,
surplusUserNum: version.userNum === -1 ? '不限人数' : version.surplusUserNum,
surplusDayTime: version.surplusDayTime,
validEndTime: moment(version.validEndTime).format('YYYY-MM-DD'),
validStartTime: moment(version.validStartTime).format('YYYY-MM-DD'),
validEndTimeST: version.validEndTime,
validStartTimeST: version.validStartTime,
whetherReachUserNum: version.whetherReachUserNum,
};
setVersionInfo(versioninfo)
});
}
async function getStoreAndUserInfo() {
await (enterpriseId ? getStoreInfo() : getStoreGroupAndStoreList());
......@@ -147,10 +170,13 @@ const App: React.FC = (props: any) => {
</Layout>
</Layout> */}
<Header id="app" handleMenuType={handleMenuType} menuType={menuType} />
<VersionContext.Provider value={versionInfo}>
<ConfigProvider locale={zhCN} autoInsertSpaceInButton={false}>
<Main menuType={menuType} />
</ConfigProvider>
<Menu menuType={menuType} handleMenuType={handleMenuType} />
</VersionContext.Provider>
</div>
)
}
......
import React from 'react';
import React, { useEffect, useState } from 'react';
import moment from "moment"
import Service from "@/common/js/service";
import BaseService from "@/domains/basic-domain/baseService";
import User from "@/common/js/user";
import { LIVE_SHARE } from "@/domains/course-domain/constants";
import moment from 'moment';
import { Modal, message } from 'antd';
import './CollegeManagePage.less';
import storage from '@/common/js/storage';
const roleMap = {
CloudManager: "管理员",
......@@ -14,6 +15,97 @@ const roleMap = {
CloudOperator: '运营师',
};
function ExpirationPopover(props) {
const [showType, setShowType] = useState(0); //0不显示,1剩余30天,2小于等于7天,3已过期
useEffect(()=> {
if (props.surplusDayTime === 0 ) {
//已过期
let loginflag = storage.get("expiration_tip_login")
if (loginflag === null || loginflag === "true") {
//只有登陆进来的时候提示一次
setShowType(3)
}
return
}
//即将过期
if (props.surplusDayTime === 30 || props.surplusDayTime <= 7) {
let daysflag = storage.get("expiration_tip"+User.getUserId()+"_days")
if (daysflag === null || daysflag !== moment().format("YYYYMMDD")) {
setShowType(2)
}
}
// if (props.surplusDayTime === 30) {
// if (storage.get("expiration_tip"+User.getUserId()+"_thirty") == null || storage.get("expiration_tip"+User.getUserId()+"_thirty") === "true") {
// setShowType(1)
// }
// return
// }
// if (props.surplusDayTime <= 7) {
// let daysflag = storage.getObj("expiration_tip"+User.getUserId()+"_7day");
// if (!daysflag) {
// setShowType(2)
// return
// }
// if (daysflag[props.surplusDayTime - 1] === 0) {
// setShowType(2)
// }
// }
},[props.endTime,props.surplusDayTime])
function iknow() {
storage.set("expiration_tip_login",false)
storage.set("expiration_tip"+User.getUserId()+"_days",moment().format("YYYYMMDD"))
/*
if (props.surplusDayTime === 0 ) {
//已过期
storage.set("expiration_tip_login",false)
} else if (props.surplusDayTime === 30) {
storage.set("expiration_tip"+User.getUserId()+"_thirty",false)
} else if (props.surplusDayTime <= 7) {
let daysflag = [0,0,0,0,0,0,0]
daysflag[props.surplusDayTime - 1] = 1
storage.setObj("expiration_tip"+User.getUserId()+"_7day",daysflag)
}
*/
setShowType(0)
}
if (props.surplusDayTime > 30) {
return ("")
}
return (
<>
{
showType === 0 ? ("") :(
<div className="expirationpopover">
<div className="dialog">
<div className="title">{props.surplusDayTime === 0 ? "服务已到期":"服务到期提醒"}</div>
{
showType === 3 ? (
<div className="tip-text">当前企业购买的小麦企学院服务已于<span style={{color:"#FF4F4F"}}>{moment(props.endTime).format("YYYY-MM-DD HH:mm:ss")}</span>到期,到期后仍可访问,但功能不可使用,建议尽快续费购买哦~</div>
) : (
<div className="tip-text">当前企业购买的小麦企学院服务 <span style={{color:"#FF4F4F"}}>仅剩{props.surplusDayTime}</span>(于<span>{moment(props.endTime).format("YYYY-MM-DD")}</span>到期),为了不影响使用,建议尽快续费购买哦~</div>
)
}
<div className="qrcode">
<img src="https://cdn.xiaomai5.com/qixueyuankehu.png" alt=""></img>
<div className="des">微信/企业微信扫码咨询</div>
</div>
<div className="phone"><svg style={{position:"relative",top:"2px",marginRight:"4px"}} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512.651 3.78c-281.433 0-509.21 228.324-509.21 509.209 0 281.43 228.325 509.203 509.21 509.203 281.427 0 509.202-228.317 509.202-509.203 0.55-280.885-227.775-509.21-509.202-509.21z m198.205 743.553c-36.14 36.136-169.737 1.641-302.24-130.312-131.953-131.959-165.902-266.104-129.768-301.695 31.211-31.21 68.99-85.417 125.939-14.782 56.943 70.629 29.016 90.34-3.291 122.647-22.449 22.448 24.642 79.392 73.37 128.125 49.283 48.73 105.678 95.818 128.126 73.368 32.306-32.305 52.017-60.23 122.646-3.288 71.182 56.949 16.426 95.276-14.782 125.937z" p-id="4409" fill="#999999"></path></svg>
咨询电话:19157875632</div>
<div className="button" onClick={iknow}>我知道了</div>
</div>
</div>
)
}
</>
)
}
export default class CollegeManagePage extends React.Component {
constructor(props) {
super(props);
......@@ -24,13 +116,16 @@ export default class CollegeManagePage extends React.Component {
enterpriseId: User.getEnterpriseId(),
isAdmin: false,
createStoreList:[],
joinStoreList:[]
joinStoreList:[],
surplusDayTime:365, //剩余天数
endTime: 0, //有效截至时间
};
}
componentDidMount() {
this.getStoreList();
this.getEnterpriseUser();
this.getVersion()
}
getEnterpriseUser() {
......@@ -45,6 +140,18 @@ export default class CollegeManagePage extends React.Component {
});
}
getVersion() {
BaseService.getLesseeVersionMsg()
.then(res=> {
User.setVersion(res.result)
User.setExpirationTime(res.result.validEndTime)
this.setState({
surplusDayTime: res.result.stateEnum === "NO" ? 0 : res.result.surplusDayTime,
endTime: res.result.validEndTime
})
})
}
getStoreList() {
const { enterpriseId } = this.state;
if (!enterpriseId) return null;
......@@ -132,6 +239,7 @@ export default class CollegeManagePage extends React.Component {
} = this.state;
return (
<div className="college-manage-page">
<ExpirationPopover surplusDayTime={this.state.surplusDayTime} endTime={this.state.endTime}/>
<div className="college-header">
<div className="box">
<img className="box-image" src="https://image.xiaomaiketang.com/xm/fe4NCjr7XF.png" />
......
......@@ -183,4 +183,78 @@
}
}
}
.expirationpopover {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0,0,0,0.7);
z-index: 1000;
.dialog {
width: 560px;
height: 486px;
background: #FFFFFF;
border-radius: 4px;
.title {
text-align: center;
font-size: 20px;
color: #333333;
font-weight: 500;
margin-top: 40px;
}
.tip-text {
font-size: 16px;
color: #666666;
font-weight: 400;
margin-top: 16px;
margin-right: 40px;
margin-left: 40px;
}
.qrcode {
width: 182px;
height: 204px;
background: #F1F3F6;
border-radius: 2px;
margin-top: 16px;
margin-left: auto;
margin-right: auto;
img {
width: 150px;
height: 150px;
margin: 16px 16px 8px 16px;
}
.des {
text-align: center;
font-size: 14px;
color: #333333;
font-weight: 400;
}
}
.phone {
text-align: center;
font-size: 14px;
color: #333333;
font-weight: 400;
margin-top: 16px;
}
.button {
width: 80px;
height: 32px;
background: #2966FF;
cursor: pointer;
margin-left: auto;
margin-right: auto;
margin-top: 24px;
font-size: 14px;
font-weight: 400;
color: #ffffff;
line-height: 32px;
text-align: center;
}
}
}
}
\ No newline at end of file
......@@ -346,7 +346,7 @@
width: 216px;
height: 260px;
top: 49px;
left: 0;
left: -50px;
background-color: #fff;
flex-wrap: wrap;
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
......
......@@ -7,6 +7,7 @@ import User from '@/common/js/user';
import WechatLogin from './WechatLogin';
import BaseService from '@/domains/basic-domain/baseService';
import axios from 'axios';
import storage from '@/common/js/storage';
import _ from 'underscore';
import user from '@/common/js/user';
const { TabPane } = Tabs;
......@@ -31,26 +32,10 @@ function Login(props) {
*/
useEffect(() => {
const enterpriseId = getParameterByName('enterpriseId');
const userId = getParameterByName('userId');
const from = getParameterByName('from');
const storeId = getParameterByName('storeId');
if (storeId) {
User.setCustomerStoreId(storeId);
}
if (from === 'customer' && enterpriseId && userId) {
if (!user.getToken() || enterpriseId !== user.getEnterpriseId() || userId !== User.getUserId()) {
getWXWorkLoginNoCheck(enterpriseId, userId);
} else {
window.RCHistory.push({
pathname: `/switch-route`,
});
}
} else {
User.removeUserId();
User.removeToken();
User.removeEnterpriseId();
}
storage.set("expiration_tip_login",true)
}, []);
function getWXWorkLoginNoCheck(enterpriseId, userId) {
const params = {
......
@import '../../core/variables.less';
@top-height: 0px;
@menu-bakg: #FFF;
@active-color: #2966FF;
@menu-bakg: #fff;
@active-color: #2966ff;
.left-container {
position: absolute;
z-index: 2;
z-index: 10;
top: @top-height;
left: 0;
bottom: 0;
......@@ -24,12 +24,12 @@
margin: 15px 0 15px 8px;
}
}
.menu-type-icon{
.menu-type-icon {
margin: 8px 14px 0px 4px;
cursor: pointer;
.icon{
font-size:14px;
color:#5E606A;
.icon {
font-size: 14px;
color: #5e606a;
}
}
}
......@@ -37,7 +37,10 @@
.ant-menu {
padding-left: 0 !important;
color: #333;
background: #FFF !important;
background: #fff !important;
.ant-menu-title-content {
margin-left: 0 !important;
}
}
.left {
-webkit-user-select: none;
......@@ -48,76 +51,202 @@
display: -webkit-flex;
flex-direction: column;
-webkit-flex-direction: column;
height: calc(~'100% - 60px');
.nav {
-webkit-flex: 1;
cursor: default;
font-size: 16px;
height: calc(~'100% - 72px');
height: calc(~'100% - 84px');
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.icon {
margin-right: 20px
margin-right: 20px;
}
.icon-img-box {
// display: flex;
display: inline-block;
width: 40px;
height: 40px;
.icon-img {
margin-left: 12px;
}
margin-right: 0 !important;
}
.icon-img-title {
margin-left: 0 !important;
}
.icon-img{
width:18px;
height:18px;
margin-right:6px;
.icon-img {
width: 18px;
height: 18px;
}
.listType {
width: 5px;
height: 5px;
background: #9A9DA7;
background: #9a9da7;
border-radius: 50%;
top: 18px;
left: 38px;
position: absolute;
}
.ant-menu-item{
padding-left: 13px !important;
.ant-menu-item {
padding-left: 0 !important;
padding-right: 0px;
margin: 6px 8px;
width: calc(100% - 15px);
&:hover{
background: #F3F6FA;
&:hover {
background: #f3f6fa;
border-radius: 2px;
color:#333;
color: #333;
}
}
.ant-menu-item-selected{
background-color:@active-color;
color:#FFF;
border-radius:2px;
&:hover{
color:#FFF;
.ant-menu-item-selected {
background-color: @active-color;
color: #fff;
border-radius: 2px;
&:hover {
color: #fff;
}
}
.ant-menu-submenu{
.ant-menu-submenu-title{
margin:6px 8px;
padding-left:13px !important;
.ant-menu-submenu {
.ant-menu-submenu-title {
margin: 6px 8px;
padding-left: 0 !important;
}
.ant-menu-item{
padding-left:46px !important;
.ant-menu-item {
padding-left: 46px !important;
}
}
.ant-menu-submenu-selected{
color:@active-color;
.ant-menu-submenu-selected {
color: @active-color;
.ant-menu-item-selected{
color:#FFF;
.ant-menu-item-selected {
color: #fff;
.listType {
background: @active-color;
}
}
}
.ant-menu-submenu-arrow {
right: 22px;
color: #5e606a;
}
}
.version-info {
// position: absolute;
height: 84px;
bottom: 0;
width: 180px;
cursor: pointer;
// z-index: 10;
padding-top: 10px;
background-color: white;
.row-1 {
width: fit-content;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 20px;
margin: 0 auto;
.version-name {
display: inline-block;
width: 58px;
text-align: center;
color: #333333;
margin: 0 auto;
border-radius: 2px;
border: 1px solid #E8E8E8;
}
.renew {
display: inline-block;
width: 58px;
color: #2966FF;
margin-left: 8px;
}
}
.expiration-time {
height: 24px;
text-align: center;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 22px;
margin: 6px auto 0 auto;
}
.popover {
display: none;
position: absolute;
z-index: 100;
padding: 16px 22px;
bottom: 22px;
width: 352px;
height: 198px;
right: -342px;
background: #FFFFFF;
box-shadow: 0px 2px 15px 0px rgba(0, 0, 0, 0.06);
.title {
display: inline-block;
width: 68px;
height: 22px;
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 22px;
margin-right: 8px;
}
.expiration-tag {
display: inline-block;
width: 52px;
height: 18px;
background: #EEEEEE;
border-radius: 2px;
text-align: center;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #999999;
line-height: 17px;
}
&::before {
position: absolute;
content: "";
width: 16px;
height: 16px;
left: -8px;
top: 80%;
border: 8px solid transparent;
box-shadow: 0px 2px 15px 0px rgba(0, 0, 0, 0.06);
}
.content {
margin-top: 24px;
.widget {
display: inline-block;
}
.lable {
font-size: 14px;
font-weight: 400;
color: #999999;
line-height: 22px;
}
.lable-text {
margin-top: 4px;
font-size: 16px;
font-weight: 500;
color: #333333;
line-height: 22px;
}
}
.ant-menu-submenu-arrow{
right:22px;
color:#5E606A;
}
.popover-show {
display: block;
}
}
......@@ -125,12 +254,12 @@
&.left-container-vertical {
width: 56px;
.menu-type-icon{
margin:4px 0 0px 22px;
.menu-type-icon {
margin: 4px 0 0px 22px;
}
.left {
.ant-menu-submenu-arrow{
display:none !important;
.ant-menu-submenu-arrow {
display: none !important;
}
}
}
......@@ -142,17 +271,21 @@
// display:inline-block;
// }
// }
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background:@active-color !important;
background: @active-color !important;
}
.ant-menu.ant-menu-dark, .ant-menu-dark .ant-menu-sub, .ant-menu.ant-menu-dark .ant-menu-sub{
.ant-menu.ant-menu-dark,
.ant-menu-dark .ant-menu-sub,
.ant-menu.ant-menu-dark .ant-menu-sub {
background: @menu-bakg !important;
}
.ant-menu-submenu-popup{
left:67px !important;
.ant-menu-submenu-popup {
left: 67px !important;
}
.ant-menu-submenu-popup>.ant-menu {
.ant-menu-submenu-popup > .ant-menu {
background: @menu-bakg !important;
color: #333;
width: 132px;
......@@ -160,18 +293,18 @@
li {
width: calc(100% - 16px);
padding-left: 20px;
margin:12px 8px !important;
&:hover{
background: #F3F6FA;
margin: 12px 8px !important;
&:hover {
background: #f3f6fa;
border-radius: 2px;
color:#333 !important;
color: #333 !important;
}
}
.ant-menu-item-selected {
background: @active-color;
color: #fff;
&:hover{
&:hover {
color: #fff !important;
}
}
......
......@@ -6,6 +6,9 @@
}
.table-style {
border: 1px solid #f0f0f0 !important;
margin-bottom: 70px;
max-height: calc(~'100vh - 465px');
overflow: scroll;
}
.ant-tabs {
color: #666666;
......
.batchscore {
.content {
.item {
display: flex;
width: 612px;
height: 48px;
background: #F7F8F9;
font-size: 14px;
line-height: 48px;
font-weight: 400;
color: #333333;
padding-left: 16px;
.type {
width: 112px;
}
.score {
margin-left: 8px;
margin-right: 29px;
width: 258px;
}
}
.item:not(:last-of-type) {
margin-bottom: 8px;
}
}
}
\ No newline at end of file
import React, { useEffect, useState} from "react";
import { Modal, Button, InputNumber, message } from 'antd';
import "./BatchScore.less"
import _ from "underscore";
interface Rule {
typeKey: "GAP_FILLING"|"INDEFINITE_CHOICE"| "JUDGE"|"MULTI_CHOICE"|"SINGLE_CHOICE",
score: number,
portionScore: number,
totalQuestion: number,
}
interface BatchScoreProps {
visible:boolean,
rules: Rule[],
onOK: (rules:Rule[]) => void,
onCancel: () => void,
}
export default function BatchScore(props:BatchScoreProps) {
const [rules, setRules] = useState<Rule[]>(_.sortBy(props.rules,"typeKey"))
const [singleCount, setSingleCount] = useState<number[]>([0])
const [multiCount, setMultiCount] = useState<number[]>([0])
const [judgeCount, setJudgeCount] = useState<number[]>([0])
const [gapCount, setgapCount] = useState<number[]>([0])
const [indefiniteCount, setIndefiniteCount] = useState<number[]>([0])
useEffect(()=> {
_.map(props.rules,(item)=> {
//更新分数统计
switch(item.typeKey) {
case "SINGLE_CHOICE":
setSingleCount([item.totalQuestion,item.totalQuestion*item.score])
break;
case "MULTI_CHOICE":
setMultiCount([item.totalQuestion,item.totalQuestion*item.score])
break;
case "JUDGE":
setJudgeCount([item.totalQuestion,item.totalQuestion*item.score])
break;
case "GAP_FILLING":
setgapCount([item.totalQuestion,item.totalQuestion*item.score])
break;
case "INDEFINITE_CHOICE":
setIndefiniteCount([item.totalQuestion,item.totalQuestion*item.score])
break;
default:
break;
}
})
},[props.rules,rules])
if (!props.visible) {
return ("")
}
function onOk() {
for (let i = 0;i < rules.length;++i) {
if (rules[i].score <= 0 || rules[i].score > 100) {
message.error("分值设置错误")
return;
}
}
props.onOK(rules)
}
function onCancel() {
props.onCancel()
}
const inputNumberStyle = {width:"57px",margin:"0 8px"}
return (
<Modal
className="batchscore"
title="批量设置分数"
onCancel={onCancel}
onOk={onOk}
visible={props.visible}
maskClosable={false}
width={660}
>
<div className="content">
<div className="item">
<span className="type">【单选题】</span>
<span className="score">每题
<InputNumber
min={1}
max={100}
value={rules[4].score}
defaultValue={rules[4].score}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
let _rules = [...rules]
rules[4].score = v
setRules(_rules)
}}
/>
</span>
<span className="total"><span style={{color:"#2966FF"}}>{singleCount[0]}</span>题,合计<span style={{color:"#2966FF"}}>{singleCount[1]}</span></span>
</div>
<div className="item">
<span className="type">【多选题】</span>
<span className="score">每题
<InputNumber
min={1}
max={100}
defaultValue={rules[3].score}
value={rules[3].score}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
if (v <= rules[3].portionScore) {
return
}
let _r = [...rules]
_r[3].score = v
setRules(_r)
}}
/>
分,漏选得
<InputNumber
min={0}
max={rules[3].score <= 0 ? 0 : rules[3].score-1}
defaultValue={rules[3].portionScore}
value={rules[3].portionScore}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
let _r = [...rules]
_r[3].portionScore = v
setRules(_r)
}}
/>
</span>
<span className="total"><span style={{color:"#2966FF"}}>{multiCount[0]}</span>题,合计<span style={{color:"#2966FF"}}>{multiCount[1]}</span></span>
</div>
<div className="item">
<span className="type">【不定项选择题】</span>
<span className="score">每题
<InputNumber
min={1}
max={100}
defaultValue={rules[1].score}
value={rules[1].score}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
if (v <= rules[1].portionScore) {
return
}
let _r = [...rules]
_r[1].score = v
setRules(_r)
}}
/>
分,漏选得
<InputNumber
min={0}
max={rules[1].score <= 0 ? 0 : rules[1].score-1}
defaultValue={rules[1].portionScore}
value={rules[1].portionScore}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
let _r = [...rules]
_r[1].portionScore = v
setRules(_r)
}}
/></span>
<span className="total"><span style={{color:"#2966FF"}}>{indefiniteCount[0]}</span>题,合计<span style={{color:"#2966FF"}}>{indefiniteCount[1]}</span></span>
</div>
<div className="item">
<span className="type">【判断题】</span>
<span className="score">每题
<InputNumber
min={1}
max={100}
defaultValue={rules[2].score}
value={rules[2].score}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
let _r = [...rules]
_r[2].score = v
setRules(_r)
}}
/>
</span>
<span className="total"><span style={{color:"#2966FF"}}>{judgeCount[0]}</span>题,合计<span style={{color:"#2966FF"}}>{judgeCount[1]}</span></span>
</div>
<div className="item">
<span className="type">【填空题】</span>
<span className="score">每题
<InputNumber
min={1}
max={100}
defaultValue={rules[0].score}
value={rules[0].score}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
if (v <= rules[0].portionScore) {
return
}
let _r = [...rules]
_r[0].score = v
setRules(_r)
}}
/>
分,半对得<InputNumber
min={0}
max={rules[0].score <= 0 ? 0 : rules[0].score-1}
defaultValue={rules[0].portionScore}
value={rules[0].portionScore}
style={inputNumberStyle}
formatter={(value:number|undefined) => String(value)}
parser={(value:string|undefined) => parseInt(String(value))}
onChange={(v)=> {
v = Math.round(v)
let _r = [...rules]
_r[0].portionScore = v
setRules(_r)
}}
/>
</span>
<span className="total"><span style={{color:"#2966FF"}}>{gapCount[0]}</span>题,合计<span style={{color:"#2966FF"}}>{gapCount[1]}</span></span>
</div>
</div>
</Modal>
)
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ import { Modal, Button, message, Spin } from "antd";
import "./BatchImportQuestionModal.less";
import SelectPrepareFileModal from "@/modules/prepare-lesson/modal/SelectPrepareFileModal";
import User from "@/common/js/user";
import moment from "moment";
import AidToolService from "@/domains/aid-tool-domain/AidToolService";
import { LoadingOutlined } from "@ant-design/icons";
class BatchImportQuestionModal extends Component {
......@@ -48,6 +49,15 @@ class BatchImportQuestionModal extends Component {
// 导入
handleImport = async () => {
//过期判断
if (User.getExpirationTime() && moment().valueOf() > Number(User.getExpirationTime())) {
Modal.warning({
title:"服务已到期",
content: "当前企业购买的小麦企学院服务已到期,如需继续使用学院功能,请尽快续费购买",
okText: "我知道了"
})
return
}
const { uploadFile } = this.state;
if (!uploadFile) {
message.warning("请选择要导入的文件");
......
/*
* @Author: yuananting
* @Date: 2021-02-21 15:53:31
* @LastEditors: Please set LastEditors
* @LastEditTime: 2021-06-08 16:46:15
* @LastEditors: wufan
* @LastEditTime: 2021-07-05 10:29:23
* @Description: 描述一下咯
* @Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
export const menuList: any = [
{
groupName: "中心首页",
groupCode: "CloudPage",
groupName: '中心首页',
groupCode: 'CloudPage',
icon: '&#xe8a7;',
link: '/home',
img:'https://image.xiaomaiketang.com/xm/ni3BFJDT3a.png',
selectImg:'https://image.xiaomaiketang.com/xm/GRDztTAWaM.png'
img: 'https://image.xiaomaiketang.com/xm/ni3BFJDT3a.png',
selectImg: 'https://image.xiaomaiketang.com/xm/GRDztTAWaM.png',
},
{
groupName: "课程管理",
groupCode: "CloudCourse",
groupName: '课程管理',
groupCode: 'CloudCourse',
icon: '&#xe8a5;',
img:'https://image.xiaomaiketang.com/xm/jBGrGjM7HQ.png',
img: 'https://image.xiaomaiketang.com/xm/jBGrGjM7HQ.png',
selectImg: 'https://image.xiaomaiketang.com/xm/TTBGBpf3BJ.png',
children: [
{
groupName: "直播课",
groupCode: "CourseLiveClass",
link: '/live-course'
groupName: '直播课',
groupCode: 'CourseLiveClass',
link: '/live-course',
},
{
groupName: "线上课",
groupCode: "CourseVideoClass",
link: '/video-course'
link: '/video-course',
},
{
groupName: "图文课",
groupCode: "GraphicLesson",
link: '/graphics-course'
groupName: '图文课',
groupCode: 'GraphicLesson',
link: '/graphics-course',
},
{
groupName: "线下课",
groupCode: "OfflineClass",
link: '/offline-course'
groupName: '线下课',
groupCode: 'OfflineClass',
link: '/offline-course',
},
]
],
},
{
groupName: "培训管理",
groupCode: "TrainManage",
groupName: '培训管理',
groupCode: 'TrainManage',
icon: '&#xe8a6;',
img:'https://image.xiaomaiketang.com/xm/Yy6pZ6G6kS.png',
img: 'https://image.xiaomaiketang.com/xm/Yy6pZ6G6kS.png',
selectImg: 'https://image.xiaomaiketang.com/xm/Z8G6NMQhaH.png',
children: [
{
groupName: "培训计划",
groupCode: "TrainPlan",
link: '/plan'
}
groupName: '培训计划',
groupCode: 'TrainPlan',
link: '/plan',
},
],
},
{
groupName: "助学工具",
groupCode: "AidTool",
groupName: '助学工具',
groupCode: 'AidTool',
icon: '&#xe8a9;',
img:'https://image.xiaomaiketang.com/xm/xsma4hx3b3.png',
img: 'https://image.xiaomaiketang.com/xm/xsma4hx3b3.png',
selectImg: 'https://image.xiaomaiketang.com/xm/3QZkdFMCS7.png',
children: [
{
groupName: "题库",
groupCode: "QuestionBank",
link: '/question-manage-index'
groupName: '题库',
groupCode: 'QuestionBank',
link: '/question-manage-index',
},
{
groupName: "试卷",
groupCode: "ExamPaper",
link: '/paper-manage-index'
groupName: '试卷',
groupCode: 'ExamPaper',
link: '/paper-manage-index',
},
{
groupName: "考试",
groupCode: "CloudExam",
link: '/examination-manage-index'
groupName: '考试',
groupCode: 'CloudExam',
link: '/examination-manage-index',
},
]
],
},
{
groupName: "知识库",
groupCode: "CloudKnowledge",
groupName: '知识库',
groupCode: 'CloudKnowledge',
icon: '&#xe8a8;',
link: '/knowledge-base',
img:'https://image.xiaomaiketang.com/xm/8sbP5rGQWh.png',
selectImg:'https://image.xiaomaiketang.com/xm/hJKCfibC22.png'
img: 'https://image.xiaomaiketang.com/xm/8sbP5rGQWh.png',
selectImg: 'https://image.xiaomaiketang.com/xm/hJKCfibC22.png',
},
{
groupName: "资料云盘",
groupCode: "CloudDisk",
groupName: '资料云盘',
groupCode: 'CloudDisk',
icon: '&#xe8aa;',
link: '/resource-disk',
img:'https://image.xiaomaiketang.com/xm/zGKbXJPzXx.png',
selectImg:'https://image.xiaomaiketang.com/xm/5sN4MzjxYc.png',
img: 'https://image.xiaomaiketang.com/xm/zGKbXJPzXx.png',
selectImg: 'https://image.xiaomaiketang.com/xm/5sN4MzjxYc.png',
},
{
groupName: "人员管理",
groupCode: "PersonManage",
groupName: '人员管理',
groupCode: 'PersonManage',
icon: '&#xe8a4;',
img:'https://image.xiaomaiketang.com/xm/PRCnrt35y8.png',
img: 'https://image.xiaomaiketang.com/xm/PRCnrt35y8.png',
selectImg: 'https://image.xiaomaiketang.com/xm/GhkwbdpwfK.png',
children: [
{
groupName: "员工管理",
groupCode: "ShopStaff",
link: '/college-employee'
groupName: '员工管理',
groupCode: 'ShopStaff',
link: '/college-employee',
},
{
groupName: "学员管理",
groupCode: "ShopUser",
link: '/college-user'
}
]
groupName: '学员管理',
groupCode: 'ShopUser',
link: '/college-user',
},
],
},
{
groupName: "学院管理",
groupCode: "CloudShop",
groupName: '学院管理',
groupCode: 'CloudShop',
icon: '&#xe8a4;',
img:'https://image.xiaomaiketang.com/xm/Q8i5RSMKNc.png',
img: 'https://image.xiaomaiketang.com/xm/Q8i5RSMKNc.png',
selectImg: 'https://image.xiaomaiketang.com/xm/pFFF3Wcy3t.png',
children: [
{
groupName: "学院信息",
groupCode: "ShopInfo",
link: '/college-info'
groupName: '学院信息',
groupCode: 'ShopInfo',
link: '/college-info',
},
{
groupName: "学院装修",
groupCode: "ShopDecoration",
link: '/store-decoration'
groupName: '学院装修',
groupCode: 'ShopDecoration',
link: '/store-decoration',
},
{
groupName: "分类管理",
groupCode: "CourseCategory",
link: '/course-category-manage'
groupName: '分类管理',
groupCode: 'CourseCategory',
link: '/course-category-manage',
},
{
groupName: "H5学院",
groupCode: "ShopDecorationH5",
link: '/store-decoration/h5'
groupName: 'H5学院',
groupCode: 'ShopDecorationH5',
link: '/store-decoration/h5',
},
{
groupName: "网页端学院",
groupCode: "ShopDecorationWeb",
link: '/store-decoration/web'
}
]
groupName: '网页端学院',
groupCode: 'ShopDecorationWeb',
link: '/store-decoration/web',
},
],
},
]
\ No newline at end of file
];
......@@ -7,4 +7,19 @@
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
export interface VersionInfo {
dayTime: number; //有效时长
stateEnum: string; //"NO"已过期,"YES"未过期
surplusDayTime: number; //剩余有效天数
surplusUserNum: number; //剩余限制人数
userNum: number; //限制人数
validEndTime: string;
validStartTime: string;
validEndTimeST: number;
validStartTimeST: number;
whetherReachUserNum: boolean;
}
export const XMContext: any = React.createContext(null);
export const VersionContext = React.createContext<VersionInfo|null>(null)
\ No newline at end of file
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