Commit e64090ea by chenshu

feat:初始化

parent 6a5d8f97
......@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_boiin24pch6.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_e0vkqcd8igi.css">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
......
......@@ -25,7 +25,7 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_boiin24pch6.css">
<link rel="stylesheet" href="//at.alicdn.com/t/font_2223403_e0vkqcd8igi.css">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-07-20 19:12:49
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-16 17:41:40
* @Last Modified time: 2021-05-11 12:00:05
* @Description: 大班直播分享弹窗
*/
......@@ -139,7 +139,7 @@ class ShareLiveModal extends React.Component {
let coverImgSrc = coverUrl;
if(type === 'videoClass'){
if((!coverUrl || isDefaultCover) && title !== '图文课'){
if((!coverUrl || isDefaultCover) && title !== '图文课' && title != '线下课'){
coverImgSrc = `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast&anystring=anystring`
}
}else{
......
......@@ -840,6 +840,7 @@ class AddOfflineCourse extends React.Component {
<GraphicsEditor
id="intro"
isIntro={true}
maxLimit={1000}
detail={{
content: introduce
}}
......
import React from 'react';
import { Tooltip, Input, Radio, Table } from 'antd';
import moment from 'moment';
import Breadcrumbs from "@/components/Breadcrumbs";
import PageControl from '@/components/PageControl';
import Service from "@/common/js/service";
import './OfflineCourseData.less';
const { Search } = Input;
export default class OfflineCourseData extends React.Component {
constructor(props) {
super(props);
this.state = {
courseId: window.getParameterByName('id'),
query: {
size: 10,
current: 1,
},
loading: false,
data: [],
total: 0,
courseName: '',
calendarTime: [],
fullJoin: 0,
}
}
componentDidMount() {
this.getOfflineBasic();
this.getOfflineCalendar();
}
getOfflineBasic = () => {
const { courseId } = this.state;
Service.Hades('public/hades/getOfflineCourseJoinBase', { courseId }).then((res) => {
if (res.success) {
const { result } = res;
this.setState({
courseName: result.courseName,
fullJoin: result.fullJoin,
});
}
})
}
getOfflineCalendar = () => {
const { courseId } = this.state;
Service.Hades('public/customerHades/offlineDateList', { courseId }).then((res) => {
if (res.success) {
console.log(result.calendarTime, _.groupBy(result.calendarTime, item => moment(item).format('YYYY-MM')), 1111111)
this.setState({
calendarTime: _.pluck(res.result, 'date'),
})
}
});
}
getColumns = () => {
const columns = [
{
title: '用户姓名',
key: 'name',
dataIndex: 'name',
render: (val, record) => {
return (
<div>{record.name}</div>
)
}
},
{
title: "手机号",
key: "teacher",
dataIndex: "teacher",
render: (val, item) => {
return (
<div>{item.teacherName}</div>
)
},
},
{
title: '报名时间',
key: 'updated',
dataIndex: 'updated',
render: (val, item) => {
return item.startTimeApply ? `${formatDate('MM-DD H:i', item.startTimeApply)} ~ ${formatDate('MM-DD H:i', item.endTimeApply)}` : '-'
}
},
{
title: '签到时间',
key: 'signIn',
dataIndex: 'signIn',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '签退时间',
key: 'signOut',
dataIndex: 'signOut',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
];
return columns;
}
handleChangeTable = () => {
}
render() {
const {
query,
loading,
data,
total,
courseName,
fullJoin,
} = this.state;
return (
<div className="page offline-course-data">
<Breadcrumbs
navList="参与数据"
goBack={() => {
window.RCHistory.goBack();
}}
/>
<div className="box">
<div className="offline-name">课程名称:{courseName}</div>
<div className="offline-application">报名总人数:100</div>
<div className="offline-application">
完成考勤总人数<Tooltip title="根据上课日期,在规定时间内完成签到和签退的用户数"><span className="icon iconfont">&#xe7c4;</span></Tooltip>{fullJoin}
</div>
</div>
<div className="box data-box">
<div className="left-box">
<div className="left-title">上课日期</div>
<div className="left-calendar">
<div className="icon-box">
<span className="icon iconfont">&#xe79c;</span>
</div>
<div className="calendar-text">2021年5月</div>
<div className="icon-box">
<span className="icon iconfont">&#xe79b;</span>
</div>
</div>
<div className="date-list">
<div className="date-item">5月1日(周五)</div>
</div>
</div>
<div className="right-box">
<div className="selected-date">5月1日</div>
<div className="detail-data">
<span className="icon iconfont">&#xe89f;</span>
<span className="data-text">报名人数:100</span>
<span className="icon iconfont">&#xe89e;</span>
<span className="data-text">完成考勤数<Tooltip title="当日在规定时间内完成签到和签退的用户数"><span className="icon iconfont">&#xe7c4;</span></Tooltip>:99</span>
<span className="icon iconfont">&#xe8a0;</span>
<span className="data-text">签到人数:99</span>
<span className="icon iconfont">&#xe89d;</span>
<span className="data-text">签退人数:99</span>
</div>
<div className="detail-filter">
<Search
className="search-input"
placeholder="搜索用户姓名/手机号"
style={{ width: 200, marginRight: 24 }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
<div className="filter-box">
<span className="label">签到情况:</span>
<Radio.Group
defaultValue="YES"
>
<Radio value="YES">已签到</Radio>
<Radio value="NO">未签到</Radio>
</Radio.Group>
</div>
<div className="filter-box">
<span className="label">签退情况:</span>
<Radio.Group
defaultValue="YES"
>
<Radio value="YES">已签退</Radio>
<Radio value="NO">未签退</Radio>
</Radio.Group>
</div>
</div>
<Table
bordered
size="middle"
pagination={false}
columns={this.getColumns()}
loading={loading}
dataSource={data}
onChange={this.handleChangeTable}
rowKey={(row) => row.liveCourseId}
/>
<PageControl
current={query.current}
pageSize={query.size}
total={total}
toPage={(page) => {
const queryStates = _.clone(query);
queryStates.current = page;
this.setState({ query: queryStates });
}}
/>
</div>
</div>
</div>
)
}
}
\ No newline at end of file
.offline-course-data {
min-width: 1100px;
.box {
.offline-name {
font-size: 19px;
color: #333;
line-height: 26px;
font-weight: 500;
padding-left: 8px;
position: relative;
&::after {
position: absolute;
width: 4px;
height: 16px;
content: '';
background: #FFB714;
left: 0;
top: 5px;
}
}
.offline-application {
color: #333;
font-size: 14px;
line-height: 20px;
margin-top: 8px;
.iconfont {
color: #BFBFBF;
font-size: 14px;
margin: 0 4px;
}
}
}
.data-box {
padding: 16px 0 !important;
display: flex;
.left-box {
width: 260px;
border-right: 1px solid #EEEEEE;
.left-title {
color: #000;
font-size: 16px;
font-weight: 500;
line-height: 22px;
padding-left: 8px;
padding-bottom: 16px;
margin: 0 8px;
border-bottom: 1px solid #E8E8E8;
}
.left-calendar {
margin-top: 16px;
margin-left: 20px;
display: flex;
align-items: center;
.icon-box {
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid #E8E8E8;
display: flex;
align-items: center;
justify-content: center;
color: #BFBFBF;
cursor: pointer;
.iconfont {
font-size: 12px;
}
}
.calendar-text {
color: #FF9D14;
line-height: 20px;
margin: 0 44px;
}
}
.date-list {
width: 100%;
margin-top: 16px;
.date-item {
height: 44px;
width: 100%;
display: flex;
padding: 12px 32px;
color: #666;
&:hover {
background: #F3F6FA;
cursor: pointer;
}
}
}
}
.right-box {
padding: 0 16px;
width: ~'calc(100% - 261px)';
.selected-date {
font-size: 16px;
color: #333;
line-height: 22px;
font-weight: 500;
}
.detail-data {
display: flex;
margin-top: 10px;
align-items: center;
.iconfont {
font-size: 15px;
color: #999999;
margin-right: 6px;
}
.data-text {
color: #333;
font-size: 14px;
line-height: 20px;
margin-right: 24px;
.iconfont {
font-size: 14px;
color: #BFBFBF;
margin: 0 4px;
}
}
}
.detail-filter {
display: flex;
margin-top: 16px;
margin-bottom: 16px;
align-items: center;
.filter-box {
margin-right: 36px;
}
}
}
}
}
\ No newline at end of file
......@@ -8,14 +8,12 @@
*/
import React from 'react';
import { Table, Modal, message , Tooltip,Switch,Dropdown} from 'antd';
import { Route, withRouter } from 'react-router-dom';
import { PageControl } from "@/components";
import { LIVE_SHARE_MAP } from '@/common/constants/academic/cloudClass';
import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import OfflineCourseData from '@/modules/course-manage/offline-course/OfflineCourseData';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import WatchDataModal from '../modal/WatchDataModal'
import Service from "@/common/js/service";
import CourseService from "@/domains/course-domain/CourseService";
import RelatedPlanModal from '../../modal/RelatedPlanModal';
......@@ -25,6 +23,7 @@ import User from '@/common/js/user'
import './OfflineCourseList.less';
import moment from 'moment';
import QRCodeModal from '../modal/QRCodeModal';
const ENV = process.env.DEPLOY_ENV || 'dev';
const defaultCoverUrl = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
......@@ -49,22 +48,6 @@ class OfflineCourseList extends React.Component {
}
}
// 观看数据弹窗
handleShowWatchDataModal = (record) => {
const watchDataModal = (
<WatchDataModal
type='videoCourseList'
data={record}
close={() => {
this.setState({
watchDataModal: null
});
}}
/>
);
this.setState({ watchDataModal });
}
handlePlanName = (planArray)=>{
let planStr = "";
planArray.map((item,index)=>{
......@@ -181,7 +164,7 @@ class OfflineCourseList extends React.Component {
render: (val, record) => {
return (
<div className="operate">
<div className="operate__item" onClick={()=>this.handleShowWatchDataModal(record)}>参与数据</div>
<div className="operate__item" onClick={() => window.RCHistory.push(`/offline-course/data?id=${record.courseId}`)}>参与数据</div>
<span className="operate__item split"> | </span>
<div className="operate__item" onClick={() => this.handleShowShareModal(record)}>分享</div>
<span className="operate__item split"> | </span>
......@@ -243,15 +226,27 @@ class OfflineCourseList extends React.Component {
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
{(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<div
className="operate__item"
key="plan"
onClick={() => {
this.handleRelatedModalShow(item);
}}
>关联培训计划</div>
}
<div
className="operate__item"
key="qrcode"
onClick={() => {
this.setState({ openQRCodeModal: true })
}}
>考勤二维码</div>
<div
className="operate__item"
key="preview"
onClick={() => {
this.setState({ openQRCodeModal: true })
}}
>预览</div>
<div
className="operate__item"
key="cancel"
onClick={() => {
this.handleDeleteOfflineCourse(item.courseId);
}}
>取消课程</div>
<div
className="operate__item"
key="edit"
......@@ -262,7 +257,7 @@ class OfflineCourseList extends React.Component {
<div
className="operate__item"
key="delete"
onClick={() => this.handleDeleteOfflineCourse(item.courseId)}
onClick={() => this.handleDeleteOfflineCourse(item.courseId, true)}
>删除</div>
</div>
)
......@@ -292,43 +287,58 @@ class OfflineCourseList extends React.Component {
})
}
// 删除视频课
handleDeleteOfflineCourse = (scheduleId) => {
Modal.confirm({
title: '你确定要删除此视频课吗?',
content: '删除后,学员将不能进行观看。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk: () => {
const param ={
courseId:scheduleId,
storeId:User.getStoreId()
handleDeleteOfflineCourse = (courseId, isDelete) => {
if (isDelete) {
Modal.confirm({
title: '删除课程',
content: '确定删除该学员吗?删除后用户数据和课程数据将无法恢复。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk: () => {
const param ={
courseId: courseId,
}
Service.Hades('public/hades/delOfflineCourse', param).then(() => {
message.success('删除成功');
this.props.onChange();
})
}
CourseService.delVideoSchedule(
param
).then(() => {
message.success('删除成功');
this.props.onChange();
})
}
});
});
} else {
Modal.confirm({
title: '取消课程',
content: '确定要取消该课程吗?取消后课程将失效,仅保留已有数据。',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
okText: '确定',
cancelText: '取消',
onOk: () => {
const param ={
courseId: courseId,
}
Service.Hades('public/hades/cancelOfflineCourse', param).then(() => {
message.success('取消成功');
this.props.onChange();
})
}
});
}
}
// 显示分享弹窗
handleShowShareModal = (record, needStr = false) => {
const { id, scheduleVideoUrl } = record;
const { courseId } = record;
const _appId = appId;
const htmlUrl = `${LIVE_SHARE}graphics_detail/${id}?id=${User.getStoreId()}`;
const htmlUrl = `${LIVE_SHARE}offline_detail/${courseId}?id=${User.getStoreId()}`;
const longUrl = htmlUrl;
const { coverUrl, courseName } = record;
const shareData = {
longUrl,
coverUrl,
scheduleVideoUrl,
courseName,
};
......@@ -401,8 +411,8 @@ class OfflineCourseList extends React.Component {
},()=>{this.props.onChange();})
}
render() {
const { RelatedPlanModalVisible, selectCourseId, selectPlanList } = this.state;
const { dataSource = [], totalCount, query } = this.props;
const { RelatedPlanModalVisible, selectCourseId, selectPlanList, openQRCodeModal } = this.state;
const { dataSource = [], totalCount, query, match } = this.props;
const { current, size } = query;
return (
<div className="offline-course-list">
......@@ -437,22 +447,18 @@ class OfflineCourseList extends React.Component {
onChange={this.onChangeSelectPlanList}
onConfirm={this.onConfirmSelectPlanList}
/>
}
{ RelatedPlanModalVisible &&
<RelatedPlanModal
onClose={this.closeRelatedPlanModalVisible}
visible={RelatedPlanModalVisible}
selectCourseId={selectCourseId}
selectPlanList={selectPlanList}
onChange={this.onChangeSelectPlanList}
onConfirm={this.onConfirmSelectPlanList}
/>
}
{ this.state.shareLiveModal }
{ this.state.watchDataModal }
{this.state.shareLiveModal}
<QRCodeModal
visible={openQRCodeModal}
onCancel={() => {
this.setState({ openQRCodeModal: false })
}}
/>
<Route path={`${match.url}/data`} component={OfflineCourseData} />
</div>
)
}
}
export default OfflineCourseList;
export default withRouter(OfflineCourseList);
import React from 'react';
import { Button, Modal, Select } from 'antd';
import './PreviewModal.less';
const { Option } = Select;
export default class PreviewModal extends React.Component {
constructor(props) {
super(props);
}
render() {
const { visible, onCancel } = this.props;
return (
<Modal
title="预览"
width={680}
visible={visible}
footer={null}
onCancel={() => onCancel()}
className="offline-preview-modal"
>
<div className="image-box">
<img src="https://image.xiaomaiketang.com/xm/xYSpX2y6ri.png" className="image" />
</div>
</Modal>
)
}
}
\ No newline at end of file
.offline-preview-modal {
}
\ No newline at end of file
import React from 'react';
import { Button, Modal, Select } from 'antd';
import './QRCodeModal.less';
const { Option } = Select;
export default class QRCodeModal extends React.Component {
constructor(props) {
super(props);
}
render() {
const { visible, onCancel } = this.props;
return (
<Modal
title="考勤二维码"
width={560}
visible={visible}
footer={<Button onClick={() => onCancel()}>关闭</Button>}
onCancel={() => onCancel()}
className="offline-qrcode-modal"
>
<div>
<Select
style={{ width: 200 }}
placeholder="请选择"
>
</Select>
<Button
type="primary"
style={{ marginLeft: 8 }}
>下载二维码</Button>
</div>
<div className="image-box">
<img src="https://image.xiaomaiketang.com/xm/xYSpX2y6ri.png" className="image" />
</div>
</Modal>
)
}
}
\ No newline at end of file
.offline-qrcode-modal {
.image-box {
margin: 16px auto 0;
width: 380px;
height: 350px;
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-05-19 11:01:31
* @Last Modified by: chenshu
* @Last Modified time: 2021-03-24 15:13:38
* @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 './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,
query: {
current: 1,
},
totalCount:0
};
}
componentDidMount() {
this.handleFetchDataList();
}
onClose = () =>{
this.props.close();
}
// 获取观看视频数据列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const { id } = this.props.data;
const params ={
...query,
size,
courseId:id,
storeId:User.getStoreId()
}
Service.Hades('public/hades/mediaCourseWatchInfo', params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
handleChangNickname = (value)=>{
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
query.phone = value;
query.nickName = null;
}else{
query.nickName = value;
query.phone = null;
}
query.current = 1;
this.setState({
query
})
}
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
size
},()=>{this.handleFetchDataList()})
}
// 请求表头
parseColumns = () => {
const columns = [
{
title: '观看用户',
key: 'name',
dataIndex: 'name'
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone'
},
{
title: '观看者类型',
key: 'userRole',
dataIndex: 'userRole'
},
{
title: '首次观看时间',
key: 'firstWatch',
dataIndex: 'firstWatch',
render: (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>
}
},
{
title: '学习进度',
key: 'progress',
dataIndex: 'progress',
render: (val) => {
return <span>{val === 100 ? '已完成' : `${val || 0}%`}</span>
}
}
];
return columns;
}
render() {
const { visible,size,dataSource,totalCount,query} = this.state;
return (
<Modal
title="图文课观看数据"
visible={visible}
footer={null}
onCancel={this.onClose}
maskClosable={false}
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>}/>
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
/>
{dataSource.length >0 &&
<div className="box-footer">
<PageControl
current={query.current - 1}
pageSize={size}
total={totalCount}
size="small"
toPage={(page) => {
const _query = {...query, current: page + 1};
this.setState({
query:_query
},()=>{ this.handleFetchDataList()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
</Modal>
)
}
}
export default WatchDataModal;
\ No newline at end of file
.watch-data-modal{
.search-container{
text-align:right;
margin-bottom:17px;
}
}
\ 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