Commit 9be455f1 by chenshu

fix:merge

parents aaad704c 042c9ae5
@font-face {
font-family: 'iconfont'; /* project id 2223403 */
src: url('//at.alicdn.com/t/font_2223403_qb6r10go4s.eot');
src: url('//at.alicdn.com/t/font_2223403_qb6r10go4s.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_2223403_qb6r10go4s.woff2') format('woff2'),
url('//at.alicdn.com/t/font_2223403_qb6r10go4s.woff') format('woff'),
url('//at.alicdn.com/t/font_2223403_qb6r10go4s.ttf') format('truetype'),
url('//at.alicdn.com/t/font_2223403_qb6r10go4s.svg#iconfont') format('svg');
src: url('//at.alicdn.com/t/font_2223403_325yz7wxu2d.eot');
src: url('//at.alicdn.com/t/font_2223403_325yz7wxu2d.eot?#iefix') format('embedded-opentype'),
url('//at.alicdn.com/t/font_2223403_325yz7wxu2d.woff2') format('woff2'),
url('//at.alicdn.com/t/font_2223403_325yz7wxu2d.woff') format('woff'),
url('//at.alicdn.com/t/font_2223403_325yz7wxu2d.ttf') format('truetype'),
url('//at.alicdn.com/t/font_2223403_325yz7wxu2d.svg#iconfont') format('svg');
}
.iconfont{
font-family:"iconfont" !important;
......
/*
* @Author: wufan
* @Date: 2020-12-12 11:57:10
* @LastEditors: wufan
* @LastEditTime: 2021-01-06 20:18:16
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-05 17:20:29
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -52,6 +52,11 @@ export function turnOnOrOffLiveCloudCourse(params: object) {
export function delLiveCloudCourse(params: object) {
return Service.Hades("public/courseCloud/delLiveCloudCourse", params);
}
//该接口主要用于培训计划关联直播课的接口(会筛选掉已关联的直播课)
export function getLiveCloudCourseBasePage(params: object) {
return Service.Hades("public/courseCloud/getLiveCloudCourseBasePage", params);
}
//视频课相关接口
export function changeVideoShelfState(params: object) {
return Service.Hades("public/hades/changeVideoShelfState", params);
......@@ -77,3 +82,9 @@ export function videoSchedulePage(params: object) {
export function videoWatchInfo(params: object) {
return Service.Hades("public/hades/videoWatchInfo", params);
}
export function videoScheduleBasePage(params: object) {
return Service.Hades("public/hades/videoScheduleBasePage", params);
}
export function relatedCourseToPlan(params: object) {
return Service.Hades("public/hades/relatedCourseToPlan", params);
}
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-02-21 16:08:38
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-09 12:54:31
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import Service from "@/common/js/service";
export function getTrainingPlanPage(params: object) {
return Service.Hades("public/hades/getTrainingPlanPage", params);
}
export function createTrainingPlan(params: object) {
return Service.Hades("public/hades/createTrainingPlan", params);
}
export function updateStateTrainingPlan(params: object) {
return Service.Hades("public/hades/updateStateTrainingPlan", params);
}
export function getTrainingPlanDetail(params: object) {
return Service.Hades("public/hades/getTrainingPlanDetail", params);
}
export function updateTrainingPlan(params: object) {
return Service.Hades("public/hades/updateTrainingPlan", params);
}
export function deleteTrainingPlan(params: object) {
return Service.Hades("public/hades/deleteTrainingPlan", params);
}
export function getPlanUserRecordPage(params: object) {
return Service.Hades("public/hades/getPlanUserRecordPage", params);
}
export function getPlanCustomerRecordPage(params: object) {
return Service.Hades("public/hades/getPlanCustomerRecordPage", params);
}
export function getPlanCustomerDetail(params: object) {
return Service.Hades("public/hades/getPlanCustomerDetail", params);
}
export function getPlanCustomerAboutUser(params: object) {
return Service.Hades("public/hades/getPlanCustomerAboutUser", params);
}
export function removePlanCustomer(params: object) {
return Service.Hades("public/hades/removePlanCustomer", params);
}
export function getStorePlanAll(params: object) {
return Service.Hades("public/hades/getStorePlanAll", params);
}
export function getTrainingCourseAutoCancel(params: object) {
return Service.Hades("public/hades/getTrainingCourseAutoCancel", params);
}
\ No newline at end of file
/*
* @Author: wufan
* @Date: 2020-11-25 18:25:02
* @LastEditors: wufan
* @LastEditTime: 2021-01-11 19:38:50
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-09 10:28:03
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -68,3 +68,11 @@ export function delCourseCategory(params: object) {
export function getStoreUserBasicPage(params: object) {
return Service.Hades("public/hades/getStoreUserBasicPage", params);
}
export function updateStoreMessage(params: object) {
return Service.Hades("public/hades/updateStoreMessage", params);
}
export function getStoreDetail(params: object) {
return Service.Hades("public/hades/getStoreDetail", params);
}
/*
* @Author: 陈剑宇
* @Date: 2020-05-07 14:43:01
* @LastEditTime: 2021-01-28 16:28:08
* @LastEditTime: 2021-03-01 10:09:42
* @LastEditors: zhangleyuan
* @Description:
* @FilePath: /wheat-web-demo/src/domains/basic-domain/constants.ts
......
/*
* @Author: wufan
* @Date: 2020-11-25 18:25:02
* @LastEditors: wufan
* @LastEditTime: 2021-01-06 20:17:40
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-05 17:20:56
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import {
fetchLecturerData, fetchUserData, exportStudentCourseData, exportPlayBackCourseData, fetchPlaybackList, createLiveCloudCourse, getLiveCloudCoursePage,
getLiveCloudCourseDetail, updateLiveCloudCourse, turnOnOrOffLiveCloudCourse, delLiveCloudCourse, changeVideoShelfState, createVideoSchedule, delVideoSchedule, editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode
getLiveCloudCourseDetail, updateLiveCloudCourse, turnOnOrOffLiveCloudCourse, delLiveCloudCourse, changeVideoShelfState, createVideoSchedule, delVideoSchedule,
editVideoSchedule, userWatchInfo, videoSchedulePage, videoScheduleDetail, videoWatchInfo, getQrcode,getLiveCloudCourseBasePage,videoScheduleBasePage,relatedCourseToPlan
} from '@/data-source/course/request-api';
export default class courseService {
......@@ -84,4 +85,13 @@ export default class courseService {
static videoWatchInfo(params: any) {
return videoWatchInfo(params);
}
static getLiveCloudCourseBasePage(params: any){
return getLiveCloudCourseBasePage(params);
}
static videoScheduleBasePage(params: any){
return videoScheduleBasePage(params);
}
static relatedCourseToPlan(params: any){
return relatedCourseToPlan(params);
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-20 09:21:40
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-27 19:53:17
* @LastEditTime: 2021-02-20 17:08:58
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......
/*
* @Author: zhangleyuan
* @Date: 2021-02-21 16:15:38
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-09 12:54:46
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import {getTrainingPlanPage,createTrainingPlan,updateStateTrainingPlan,getTrainingPlanDetail,updateTrainingPlan,deleteTrainingPlan,getPlanUserRecordPage,getPlanCustomerRecordPage,getPlanCustomerDetail,getPlanCustomerAboutUser,removePlanCustomer,getStorePlanAll,getTrainingCourseAutoCancel} from '@/data-source/plan/request-apis';
export default class PlanService {
// 获取员工列表
static getTrainingPlanPage(params: any) {
return getTrainingPlanPage(params);
}
static createTrainingPlan(params: any) {
return createTrainingPlan(params);
}
static updateStateTrainingPlan(params: any) {
return updateStateTrainingPlan(params);
}
static getTrainingPlanDetail(params: any) {
return getTrainingPlanDetail(params);
}
static updateTrainingPlan(params: any) {
return updateTrainingPlan(params);
}
static deleteTrainingPlan(params: any) {
return deleteTrainingPlan(params);
}
static getPlanUserRecordPage(params: any) {
return getPlanUserRecordPage(params);
}
static getPlanCustomerRecordPage(params: any) {
return getPlanCustomerRecordPage(params);
}
static getPlanCustomerDetail(params: any) {
return getPlanCustomerDetail(params);
}
static getPlanCustomerAboutUser(params: any) {
return getPlanCustomerAboutUser(params);
}
static removePlanCustomer(params: any) {
return removePlanCustomer(params);
}
static getStorePlanAll(params: any) {
return getStorePlanAll(params);
}
static getTrainingCourseAutoCancel(params: any) {
return getTrainingCourseAutoCancel(params);
}
}
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-01-19 11:27:56
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-08 10:36:04
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
export const storeRoleEnum = {
"0": "店铺管理员",
"1": "管理员",
"2": "普通讲师"
};
export const industryList = ["IT服务","制造业","批发/零售","生活服务","文化/体育/娱乐业","建筑/房地产","教育","运输/物流/仓储","医疗","政府","金融","能源/采矿","农林渔牧","其他行业"];
export const childIndustryList = {
"IT服务":["计算机软件/硬件/信息服务","互联网和相关服务","其他"],
"制造业":["机械/电子","服装/纺织","汽车","金属制品","家具/家纺","重工制造","家电/数码","橡胶/塑料","日用品/化妆品","化学原料制品","文教/工美/体育/娱乐用品","烟酒/茶","非金属矿物","其他"],
"批发/零售":["批发","零售","超市/便利店/百货商场","进出口","其他"],
"生活服务":["餐饮","居民服务","租赁和商务服务","酒店/住宿","其他"],
"文化/体育/娱乐业":["文化/体育","娱乐/旅游","新闻传媒","其他"],
"建筑/房地产":["建筑业","建材装修","房地产","其他"],
"教育":["学前教育","初中等教育","高等教育","培训机构","其他"],
"运输/物流/仓储":["物流/仓储","道路/铁路运输","邮政/快递","航空运输","水上运输","其他"],
"医疗":["医院/医疗机构","医疗器械","医药制造","医药流通","其他"],
"政府":["党政机关","国家权利/行政机构","检察院/法院/公安","民政/人社/交通/卫生","发改委/经信委/商务局/统计局","国土/规划","税务/海关/工商/环保/物价/药品","政协/民主党派","地方政府","其他"],
"金融":["保险","银行","证券/投资/基金","其他"],
"能源/采矿":["电力/热力/燃气/水供应业","石油/天然气","煤炭","有色金属","钢铁","其他"],
"农林渔牧":["农林渔牧"],
"其他行业":["科学研究和技术服务业","社会组织","水利和环境管理","国际组织","其他"]
}
\ No newline at end of file
......@@ -2,11 +2,13 @@
* @Author: wufan
* @Date: 2020-11-25 18:25:02
* @LastEditors: zhangleyuan
* @LastEditTime: 2020-12-23 16:54:10
* @LastEditTime: 2021-03-08 11:48:43
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import { getEmployeeList, getUserList, getStoreDecorationList, getStoreRole, addEmployee, editEmployee, deleteEmployee, getCourseCatalogList, getAllSonCategory, addCourseCategory, delCourseCategory, editCourseCategory, deleteStoreDecorationList, addStoreBanner, editStoreBanner, moveBannerSequence,getStoreUserBasicPage} from '@/data-source/store/request-apis';
import { getEmployeeList, getUserList, getStoreDecorationList, getStoreRole, addEmployee, editEmployee, deleteEmployee, getCourseCatalogList,
getAllSonCategory, addCourseCategory, delCourseCategory, editCourseCategory, deleteStoreDecorationList, addStoreBanner, editStoreBanner,
moveBannerSequence,getStoreUserBasicPage,updateStoreMessage,getStoreDetail} from '@/data-source/store/request-apis';
export default class StoreService {
// 获取员工列表
......@@ -81,5 +83,10 @@ export default class StoreService {
static delCourseCategory(params: any) {
return delCourseCategory(params);
}
static updateStoreMessage(params: any) {
return updateStoreMessage(params);
}
static getStoreDetail(params: any) {
return getStoreDetail(params);
}
}
\ No newline at end of file
<!--
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: wufan
* @LastEditTime: 2021-01-12 13:52:14
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-02 15:16:04
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......
<!--
* @Author: 吴文洁
* @Date: 2020-08-24 12:20:57
* @LastEditors: wufan
* @LastEditTime: 2021-01-18 21:18:43
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-02 15:16:11
* @Description:
* @Copyright: 杭州杰竞科技有限公司 版权所有
-->
......
......@@ -15,7 +15,7 @@ import DownloadLiveModal from '@/components/DownloadLiveModal';
import ManageCoursewareModal from '../modal/ManageCoursewareModal';
import ShareLiveModal from '../modal/ShareLiveModal';
import RelatedPlanModal from '../modal/RelatedPlanModal';
import './LiveCourseList.less';
import { QuestionCircleOutlined } from '@ant-design/icons';
......@@ -56,7 +56,9 @@ class LiveCourseList extends React.Component {
this.state = {
columns: [],
openDownloadModal:false,
url:''
url:'',
RelatedPlanModalVisible:false,
selectPlanList:{}
}
}
componentWillMount(){
......@@ -271,6 +273,37 @@ class LiveCourseList extends React.Component {
},
},
{
title: '创建时间',
width: "9%",
key: "created",
dataIndex: "created",
sorter: true,
render: (val, item) => {
return (
<span>{formatDate('YYYY-MM-DD H:i', val)}</span>
);
},
},
{
title: '关联项',
width: "15%",
key: "planList",
dataIndex: "planList",
render: (val, record) => {
return (
<span>
{ record.relatedPlanList ?
record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
})
:
<span></span>
}
</span>
)
},
},
{
title: "操作",
width: "15%",
key: "operate",
......@@ -456,11 +489,39 @@ class LiveCourseList extends React.Component {
);
},
},
{
title: '创建时间',
width: "9%",
key: "created",
dataIndex: "created",
sorter: true,
render: (val, item) => {
return (
<span>{formatDate('YYYY-MM-DD H:i', val)}</span>
);
},
},
{
title: '关联项',
width: "15%",
key: "planList",
dataIndex: "planList",
render: (val, record) => {
return (
<span>
{ record.relatedPlanList ?
record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
})
:
<span></span>
}
</span>
)
},
},
];
}
this.setState({ columns })
}
handleAdminName = (adminArray)=>{
......@@ -477,6 +538,12 @@ class LiveCourseList extends React.Component {
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
{ (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<div
className="operate__item"
onClick={()=>this.handleRelatedModalShow(item)}
>关联培训计划</div>
}
<div
className="operate__item"
onClick={()=>this.toEditCoursePage(item)}
......@@ -597,14 +664,65 @@ class LiveCourseList extends React.Component {
}
window.open(htmlUrl);
}
handleRelatedModalShow = (item)=>{
const selectPlanList = {};
if(item.relatedPlanList){
item.relatedPlanList.map((item,index)=>{
selectPlanList[item.planId] = {}
selectPlanList[item.planId].planId = item.planId;
selectPlanList[item.planId].taskBaseVOList = [{taskId:item.taskId}];
return item
})
}
this.setState({
RelatedPlanModalVisible:true,
selectCourseId:item.liveCourseId,
selectPlanList:selectPlanList
})
}
closeRelatedPlanModalVisible = ()=>{
this.setState({
RelatedPlanModalVisible:false
})
}
onChangeSelectPlanList = (selectPlanList)=>{
this.setState({
selectPlanList:selectPlanList
})
}
onConfirmSelectPlanList = ()=>{
this.setState({
RelatedPlanModalVisible:false
},()=>{this.props.onChange();})
}
handleChangeTable = (pagination, filters, sorter) => {
const { columnKey, order } = sorter;
const { query } = this.props;
let _columnKey;
let _order;
// 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') {_columnKey="CREATED"; _order = 'SORT_ASC'; }
// 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { _columnKey="CREATED"; _order = 'SORT_DESC';}
const _query = {
...query,
sortMap:{}
};
_query.sortMap[_columnKey]=_order;
this.props.onChange(_query);
}
render() {
const { total, query, courseList, loading} = this.props;
const { current, size } = query;
const { openDownloadModal,
downloadUrl, url, columns,
openCoursewareModal,
editData
editData,
RelatedPlanModalVisible,
selectCourseId,
selectPlanList
} = this.state;
const { match } = this.props;
......@@ -617,6 +735,7 @@ class LiveCourseList extends React.Component {
columns={columns}
loading={loading}
dataSource={courseList}
onChange={this.handleChangeTable}
rowKey={(row) => row.liveCourseId}
/>
{ total>0 &&
......@@ -655,6 +774,16 @@ class LiveCourseList extends React.Component {
}}
/>
)}
{ RelatedPlanModalVisible &&
<RelatedPlanModal
onClose={this.closeRelatedPlanModalVisible}
visible={RelatedPlanModalVisible}
selectCourseId={selectCourseId}
selectPlanList={selectPlanList}
onChange={this.onChangeSelectPlanList}
onConfirm={this.onConfirmSelectPlanList}
/>
}
<iframe src={url} style={{ display: "none" }} />
<Route path={`${match.url}/live-course-data`} component={DataList} />
</div>
......
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import PlanService from "@/domains/plan-domain/planService";
import User from '@/common/js/user'
import './RelatedPlanModal.less';
import _ from "underscore";
const { Search } = Input;
class RelatedPlanModal extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource:[],
size:10,
query: {
current: 1,
},
totalCount:0,
selectPlanList:{},
};
}
componentDidMount() {
this.handleFetchDataList();
}
// 获取培训计划列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const params ={
...query,
size,
storeId:User.getStoreId()
}
PlanService.getStorePlanAll(params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
handleChangePlanName = (value)=>{
const {query} = this.state;
query.planName = value;
query.current = 1;
this.setState({
query
})
}
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
size
},()=>{this.handleFetchDataList()})
}
// 请求表头
parsePlanColumns = () => {
const columns = [
{
title: '培训计划',
key: 'planName',
dataIndex: 'planName',
render:(val,record)=>{
return (
<span>{val}</span>
)
}
}
];
return columns;
}
parseTaskColumns = (parentIndex) => {
const columns = [
{
title: '任务名称',
key: 'taskName',
dataIndex: 'taskName',
render:(val,record)=>{
return (
<span>{val}</span>
)
}
}
];
return columns;
}
selectPlanList = (record,selected,planId) =>{
const { selectPlanList } = this.props;
let _selectPlanList = {...selectPlanList};
if (selected) {
if(!_selectPlanList[planId]){
_selectPlanList[planId] = {}
}
_selectPlanList[planId].taskBaseVOList = [];
_selectPlanList[planId].planId = planId;
_selectPlanList[planId].taskBaseVOList.push(record);
} else {
if(!_selectPlanList[planId]){
_selectPlanList[planId] = {}
}
_selectPlanList[planId].taskBaseVOList = [];
_selectPlanList[planId].planId = planId;
}
this.props.onChange(_selectPlanList);
// this.setState({selectPlanList:_selectPlanList});
}
handleSelectPlanListData(selectPlanList){
let _selectPlanList = [];
for(let key in selectPlanList ){
let item = {};
if(selectPlanList[key].taskBaseVOList){
item.planId = selectPlanList[key].planId;
if(selectPlanList[key].taskBaseVOList[0]){
item.taskId = selectPlanList[key].taskBaseVOList[0].taskId;
}
}
_selectPlanList.push(item)
}
return _selectPlanList;
}
confirmRelatedPlan =()=>{
const {selectPlanList } = this.props;
const params = {
courseId:this.props.selectCourseId,
relatedPlanList:this.handleSelectPlanListData(selectPlanList),
storeId:User.getStoreId(),
}
CourseService.relatedCourseToPlan(params).then((res) => {
this.props.onConfirm();
});
}
getSelectLength = (selectList)=>{
let num = 0;
for(let key in selectList ){
if(selectList[key].taskBaseVOList){
num = num + 1
}
}
return num;
}
clearSelect = ()=>{
const _selectPlanList = {};
this.props.onChange(_selectPlanList);
}
render() {
const { size,dataSource,totalCount,query} = this.state;
const { visible,selectPlanList} = this.props;
return (
<Modal
title="关联培训计划"
onCancel={this.props.onClose}
maskClosable={false}
visible={visible}
className="related-plan-modal"
closable={true}
width={800}
onOk={() => this.confirmRelatedPlan() }
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div className="search-container">
<Search placeholder="搜索培训计划名称"
style={{ width: 207 }}
onChange={(e) => { this.handleChangePlanName(e.target.value)}}
onSearch={ () => { this.handleFetchDataList()}}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="select-container">
<span className="icon iconfont tip">&#xe6f2;</span>
<span className="text">已选择{this.getSelectLength(selectPlanList)}个任务</span>
<span className="clear" onClick={this.clearSelect}>清空</span>
</div>
<div>
<Table
rowKey={record => record.planId}
className="plan-table"
dataSource={dataSource}
columns={this.parsePlanColumns()}
pagination={false}
expandedRowRender={(_record,index) => {
if(!_record.taskBaseVOList){
return
}
if (_record.taskBaseVOList.length !== 0 ){
const selectPlan = selectPlanList[_record.planId]
let taskBaseVOList = [];
if(selectPlan){
taskBaseVOList = selectPlan.taskBaseVOList;
}
console.log('taskBaseVOList',taskBaseVOList);
return <div>
<Table
rowKey={record => record.taskId}
pagination={false}
dataSource={_record.taskBaseVOList}
columns={this.parseTaskColumns(index)}
className="child-table"
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(taskBaseVOList, 'taskId'),
onSelect: (record, selected) => {
this.selectPlanList(record,selected,_record.planId);
},
onSelectAll: (selected, _selectedRows, changeRows) => {
// let _list = [];
// if (selected) {
// _list = _.uniq(selectVideo.concat(changeRows), false, (item) => item.id);
// } else {
// _list = _.reject(selectVideo, (item) => _.find(changeRows, (data) => data.id === item.id));
// }
// this.setState({selectVideo:_list});
},
}}
/>
</div>
}
}}
rowClassName={(record,index)=>{if(index%2===0){return 'odd-row'}else{ return 'even-row'}}}
/>
{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()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
</Modal>
)
}
}
export default RelatedPlanModal;
\ No newline at end of file
.related-plan-modal{
.search-container{
margin-bottom:16px;
}
.select-container{
margin-bottom:12px;
background: #FFF4DD;
border-radius: 4px;
padding:6px 16px;
width: 207px;
height: 32px;
.tip{
font-size:14px;
color:#FF9D14;
margin-right:8px;
}
.text{
font-size:14px;
color:#666;
margin-right:30px;
}
.clear{
color:#5289FA;
font-size:14px;
}
}
.plan-table{
.taskName{
color:#666666;
font-size:14px;
}
.task-learn-percentage{
color:#666666;
font-size:14px;
}
.course-info{
margin-left:57px;
.course-type{
font-size:11px;
color:#666666;
padding:1px 8px;
border: 1px solid #999999;
margin-right:4px;
border-radius: 2px;
}
.course-name{
color:#666666;
font-size:14px;
margin-right:8px;
}
.tip{
font-size:14px;
color:#FF4F4F;
margin-right:2px;
}
.course-state{
color:#999;
font-size:14px;
}
}
.ant-table-content{
border:1px solid #e8e8e8;
tr{
td{
border:none;
}
.child-table{
.ant-table-content{
border:none;
thead{
display:none;
}
tbody tr td{
border-bottom:none;
}
}
}
}
.odd-row{
background:transparent;
td{
background: #FFF;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FFF;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
}
}
}
.even-row{
background:transparent;
td{
background: #FAFAFA;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FAFAFA;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
}
}
}
}
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-05 10:07:47
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-04 10:26:07
* @LastEditTime: 2021-03-08 15:24:25
* @Description: 视频课新增/编辑页
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -23,6 +23,8 @@ import StoreService from "@/domains/store-domain/storeService";
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user';
import _ from "underscore";
import Upload from '@/core/upload';
import PhotoClip from 'photoclip';
import './AddVideoCourse.less';
const EDIT_BOX_KEY = Math.random();
......@@ -38,7 +40,7 @@ const defaultScheduleMedia = [{
}]
const whetherVisitorsJoin = 'NO'
let cutFlag = false;
class AddVideoCourse extends React.Component {
constructor(props) {
......@@ -71,17 +73,17 @@ class AddVideoCourse extends React.Component {
categoryName:null, //分类名称
courseCatalogList:[], //分类列表
categoryId:null, //分类的Id值
whetherVisitorsJoin:'NO' // 是否允许游客加入
whetherVisitorsJoin:'NO', // 是否允许游客加入
showSelectCoverModal:false,
cutImageBlob: null,
}
}
componentWillMount() {
// this.handleFetchDiskList();
const { id, pageType } = this.state;
this.getCourseCatalogList();
if (pageType === 'edit') {
this.handleFetchScheudleDetail(id);
// this.handleFetchStudentList(id);
}
}
//获取分类列表
......@@ -114,15 +116,7 @@ class AddVideoCourse extends React.Component {
}).then((res) => {
const { result = {} } = res || {};
const {
// coverId,
// coverUrl,
// videoType,
// videoDuration,
// videoName,
// scheduleMedia,
courseName,
// scheduleVideoId,
// scheduleVideoUrl,
shelfState,
whetherVisitorsJoin,
courseMediaVOS,
......@@ -216,7 +210,6 @@ class AddVideoCourse extends React.Component {
// 修改表单
handleChangeForm = (field, value, coverUrl) => {
console.log('field',value);
this.setState({
[field]: value,
coverUrl: coverUrl ? coverUrl : this.state.coverUrl
......@@ -228,9 +221,6 @@ class AddVideoCourse extends React.Component {
this.setState({ studentModal : true });
const { studentList, selectedStuList } = this.state;
// const _studentList = _.map(studentList, (item) => {
// return item.studentId
// })
const studentModal = (
<SelectStudent
showTabs={true}
......@@ -316,15 +306,6 @@ class AddVideoCourse extends React.Component {
}
}
// 上传封面图
handleShowImgCutModal = (event) => {
const imageFile = event.target.files[0];
if (!imageFile) return;
this.setState({
imageFile,
showCutModal: true,
});
}
// 保存
handleSubmit = () => {
const { instId, adminId } = window.currentUserInstInfo;
......@@ -412,14 +393,122 @@ class AddVideoCourse extends React.Component {
resolve(true);
});
}
handleSelectCover = (file)=> {
this.uploadImage(file);
}
//上传图片
uploadImage = (imageFile) => {
const { folderName } = imageFile;
const fileName = window.random_string(16) + folderName.slice(folderName.lastIndexOf("."));
const self = this;
this.setState(
{
visible: true,
},
() => {
setTimeout(() => {
const okBtnDom = document.querySelector("#headPicModal");
const options = {
size: [500, 282],
ok: okBtnDom,
maxZoom: 3,
style: {
jpgFillColor: "transparent",
},
done: function (dataUrl) {
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if ((self.state.rotate != this.rotate()) || (self.state.scale != this.scale())) {
const _dataUrl = this.clip()
const cutImageBlob = self.convertBase64UrlToBlob(_dataUrl);
self.setState({
cutImageBlob,
dataUrl: _dataUrl,
rotate: this.rotate(),
scale: this.scale()
})
}
}, 500)
const cutImageBlob = self.convertBase64UrlToBlob(dataUrl);
self.setState({
cutImageBlob,
dataUrl
})
setTimeout(() => {
cutFlag = false;
}, 2000);
},
fail: (failInfo) => {
message.error("图片上传失败了,请重新上传");
},
loadComplete: function (img) {
setTimeout(() => {
const _dataUrl = this.clip()
self.setState({
dataUrl: _dataUrl,
hasImgReady: true
})
}, 100)
},
};
const imgUrl = `${imageFile.ossUrl}?${new Date().getTime()}`
if (!this.state.photoclip) {
const _photoclip = new PhotoClip("#headPicModal", options);
_photoclip.load(imgUrl);
this.setState({
photoclip: _photoclip,
});
} else {
this.state.photoclip.clear();
this.state.photoclip.load(imgUrl);
}
}, 200);
}
);
};
//获取resourceId
getSignature = (blob, fileName) => {
Upload.uploadBlobToOSS(blob, 'cover' + (new Date()).valueOf(),null,'signInfo').then((signInfo) => {
this.setState({
coverClicpPath:signInfo.fileUrl,
coverId:signInfo.resourceId,
visible: false
},()=>this.updateCover())
});
};
updateCover = () =>{
const {coverClicpPath,coverId} = this.state
this.setState({
showSelectCoverModal: false,
coverUrl:coverClicpPath,
coverId:coverId
})
}
// base64转换成blob
convertBase64UrlToBlob = (urlData) => {
const bytes = window.atob(urlData.split(",")[1]);
const ab = new ArrayBuffer(bytes.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: "image/png" });
};
render() {
const {
pageType,courseName, scheduleVideoId, coverId,
coverUrl, scheduleVideoUrl, studentList, scheduleMedia,
showCutModal, showSelectFileModal, diskList,
imageFile, joinType, videoName, videoType,shelfState,
categoryName,courseCatalogList,whetherVisitorsJoin
categoryName,courseCatalogList,whetherVisitorsJoin,
visible,showSelectCoverModal,hasImgReady,cutImageBlob
} = this.state;
// 已选择的上课学员数量
......@@ -491,20 +580,7 @@ class AddVideoCourse extends React.Component {
}
</div>
<div className="opt-btns">
<input
type="file"
accept="image/png, image/jpeg, image/jpg"
ref="picInputFile"
style={{display: 'none'}}
onChange={(event) => { this.handleShowImgCutModal(event) }}
/>
<Button onClick={() => {
this.setState({
currentInputFile: this.refs.picInputFile
});
this.refs.picInputFile.click()
}}>{`${(pageType === 'add' && (!scheduleVideoId && !coverUrl)) ? '上传' : '修改'}封面`}</Button>
<Button onClick={() => {this.setState({ showSelectCoverModal:true })}}>{`${(pageType === 'add' && (!scheduleVideoId && !coverUrl)) ? '上传' : '修改'}封面`}</Button>
<div className="tips">建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
</div>
......@@ -558,24 +634,75 @@ class AddVideoCourse extends React.Component {
onSelect={this.handleSelectVideo}
/>
}
<ImgCutModalNew
title="裁剪"
width={550}
cutWidth={500}
cutHeight={282}
cutContentWidth={500}
cutContentHeight={300}
visible={showCutModal}
imageFile={imageFile}
bizCode='LIVE_COURSE_MEDIA'
onOk={(urlStr, resourceId) => {
this.setState({ showCutModal: false });
this.handleChangeForm('coverId', resourceId, urlStr)
this.state.currentInputFile.value = '';
{showSelectCoverModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectCoverModal}
onClose={() => {
this.setState({ showSelectCoverModal: false })
}}
onClose={() => this.setState({ showCutModal: false })}
reUpload={() => { this.state.currentInputFile.click() }}
onSelect={this.handleSelectCover}
/>
}
<Modal
title="设置图片"
width={1080}
visible={visible}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
onCancel={() => {
this.setState({ visible: false });
}}
zIndex={10001}
footer={[
<Button
key="back"
onClick={() => {
this.setState({ visible: false });
}}
>
重新上传
</Button>,
<Button
key="submit"
type="primary"
disabled={!hasImgReady}
onClick={() => {
if (!cutFlag) {
cutFlag = true;
this.refs.hiddenBtn.click();
}
this.getSignature(cutImageBlob);
}}
>
确定
</Button>,
]}
>
<div className="clip-box">
<div
id="headPicModal"
ref="headPicModal"
style={{
width: "500px",
height: "430px",
marginBottom: 0,
}}
></div>
<div id="clipBtn" style={{ display: "none" }} ref="hiddenBtn"></div>
<div className="preview-img">
<div className="title">效果预览</div>
<div id="preview-url-box" style={{width:500,height:282}}>
<img src={this.state.dataUrl} style={{ width: '100%' }} alt="" />
</div>
</div>
</div>
</Modal>
{ this.state.previewCourseModal }
</div>
)
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2020-08-05 10:12:45
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-01 16:34:11
* @LastEditTime: 2021-03-10 15:53:11
* @Description: 视频课-列表模块
* @Copyright: 杭州杰竞科技有限公司 版权所有
*/
......@@ -17,21 +17,23 @@ import { appId, shareUrl, LIVE_SHARE } from '@/domains/course-domain/constants';
import ShareLiveModal from '@/modules/course-manage/modal/ShareLiveModal';
import WatchDataModal from '../modal/WatchDataModal'
import CourseService from "@/domains/course-domain/CourseService";
import RelatedPlanModal from '../../modal/RelatedPlanModal';
import User from '@/common/js/user'
import './VideoCourseList.less';
const ENV = process.env.DEPLOY_ENV || 'dev';
const userRole = User.getUserRole();
class VideoCourseList extends React.Component {
constructor(props) {
super(props);
this.state = {
id: '', // 视频课ID
studentIds:[]
studentIds:[],
RelatedPlanModalVisible:false,
selectPlanList:{}
}
}
......@@ -166,6 +168,25 @@ class VideoCourseList extends React.Component {
}
},
{
title: '关联项',
width: 200,
key: "planList",
dataIndex: "planList",
render: (val, record) => {
return (
<span>
{ record.relatedPlanList ?
record.relatedPlanList.map((item,index)=>{
return <span>{item.planName} { (index < record.relatedPlanList.length-1)&&(<span></span>)} </span>
})
:
<span></span>
}
</span>
)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
......@@ -200,6 +221,12 @@ class VideoCourseList extends React.Component {
renderMoreOperate = (item) => {
return (
<div className="live-course-more-menu">
{ (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<div
className="operate__item"
onClick={()=>this.handleRelatedModalShow(item)}
>关联培训计划</div>
}
<div
className="operate__item"
onClick={() => {
......@@ -237,6 +264,7 @@ class VideoCourseList extends React.Component {
}
})
}
// 删除视频课
handleDeleteVideoCourse = (scheduleId) => {
Modal.confirm({
......@@ -314,12 +342,42 @@ class VideoCourseList extends React.Component {
};
this.props.onChange(_query);
}
handleRelatedModalShow = (item)=>{
const selectPlanList = {};
if(item.relatedPlanList){
item.relatedPlanList.map((item,index)=>{
selectPlanList[item.planId] = {}
selectPlanList[item.planId].planId = item.planId;
selectPlanList[item.planId].taskBaseVOList = [{taskId:item.taskId}];
return item
})
}
this.setState({
RelatedPlanModalVisible:true,
selectCourseId:item.id,
selectPlanList:selectPlanList
})
}
closeRelatedPlanModalVisible = ()=>{
this.setState({
RelatedPlanModalVisible:false
})
}
onChangeSelectPlanList = (selectPlanList)=>{
this.setState({
selectPlanList:selectPlanList
})
}
onConfirmSelectPlanList = ()=>{
this.setState({
RelatedPlanModalVisible:false
},()=>{this.props.onChange();})
}
render() {
const { dataSource = [], totalCount, query } = this.props;
const { current, size } = query;
const {RelatedPlanModalVisible,selectPlanList,selectCourseId} = this.state;
return (
<div className="video-course-list">
<Table
......@@ -344,6 +402,16 @@ class VideoCourseList extends React.Component {
}}
/>
</div>
{ RelatedPlanModalVisible &&
<RelatedPlanModal
onClose={this.closeRelatedPlanModalVisible}
visible={RelatedPlanModalVisible}
selectCourseId={selectCourseId}
selectPlanList={selectPlanList}
onChange={this.onChangeSelectPlanList}
onConfirm={this.onConfirmSelectPlanList}
/>
}
{ this.state.shareLiveModal }
{ this.state.watchDataModal }
</div>
......
/*
* @Author: zhangleyuan
* @Date: 2020-11-27 15:06:31
* @LastEditors: wufan
* @LastEditTime: 2021-02-01 14:34:44
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-09 14:22:10
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -95,12 +95,14 @@ function PersonalInfoPage() {
phone: String(phone),
roleCodes: roleCodes,
avatar,
storeUserId: User.getStoreUserId()
storeUserId: User.getStoreUserId(),
storeId:User.getStoreId()
} : {
nickName,
roleCodes: roleCodes,
avatar,
storeUserId: User.getStoreUserId()
storeUserId: User.getStoreUserId(),
storeId:User.getStoreId()
};
StoreService.editEmployee(params).then((res) => {
......
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:13:39
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-10 16:51:57
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState } from "react";
import { Button, message, Modal} from 'antd';
import ShowTips from "@/components/ShowTips";
import Breadcrumbs from "@/components/Breadcrumbs";
import BasicInfo from './components/BasicInfo';
import TrainingTask from './components/TrainingTask';
import ExpiredCourseList from './components/ExpiredCourseList';
import PlanService from '@/domains/plan-domain/planService'
import User from '@/common/js/user';
import _ from "underscore";
import './AddPlan.less'
const defaultCover = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
const defaultBasicData = {
planName:"",
coverUrl: defaultCover,
coverId:null,
enableState:"YES",
selectOperatorList:[],
instro:'',
operateType:'All_Operate',
percentCompleteLive:80,
percentCompleteVideo:80,
}
const defaultTaskList = [];
function AddPlan() {
const id = getParameterByName("id");
const type = getParameterByName("type");
const [basicData,setBasicData] = useState(defaultBasicData);
const [taskList,setTaskList] = useState(defaultTaskList);
const [expiredCourseList,setExpiredCourseList] = useState([]);
useEffect(()=>{
if(type==='edit'){
getPlanDetail();
getPlanCustomerState();
}
},id)
function getPlanCustomerState(){
PlanService.getTrainingCourseAutoCancel({
planId: id
}).then((res) => {
const expiredCourseList = res.result;
setExpiredCourseList(expiredCourseList)
})
}
function getPlanDetail (){
PlanService.getTrainingPlanDetail({
planId: id
}).then((res) => {
const {
planName,
enableState,
operateType,
operateIds,
percentCompleteLive,
percentCompleteVideo,
courseMediaVOS,
trainingTaskList
} = res.result;
let coverId;
let coverUrl;
let instro;
courseMediaVOS.map((item) => {
switch (item.contentType){
case "COVER":
coverId = item.mediaContent;
coverUrl = item.mediaUrl;
break;
case "INTRO":
instro = item.mediaContent;
break;
default:
break;
}
return item;
})
let _selectOperatorList = [];
if(operateIds){
_selectOperatorList = operateIds.map((item,index)=>{
let _item = {};
_item.id = item;
return _item
})
}
setBasicData({
planName,
coverUrl:coverUrl || defaultCover,
coverId,
enableState,
selectOperatorList:_selectOperatorList,
instro,
operateType,
percentCompleteLive,
percentCompleteVideo
})
setTaskList(trainingTaskList)
})
}
function handleChangeBasicInfo(field, value){
setBasicData( {
...basicData,
[field]: value,
})
}
function handleChangeTaskInfo(value){
setTaskList(value)
}
function submitInfo(){
const {planName,enableState,selectOperatorList,instro,operateType,percentCompleteLive,percentCompleteVideo,coverId,coverUrl} = basicData;
if(!planName){
message.warning('请输入的培训计划名称');
return;
}
if(operateType==='Assign_Operate' && selectOperatorList.length===0){
message.warning('请选择指定运营师');
return;
}
if(!percentCompleteLive && percentCompleteLive !==0 ){
message.warning('请输入完成标准');
return;
}
if(!percentCompleteVideo && percentCompleteVideo !==0 ){
message.warning('请输入完成标准');
return;
}
if(taskList.length === 0){
message.warning('请输入培训计划内容');
return;
}
let scheduleMediaRequests = [];
let coverObj ={
contentType:'COVER',
mediaContent:coverId,
mediaType:'PICTURE',
mediaUrl: coverUrl,
}
if(coverId){
scheduleMediaRequests = [...scheduleMediaRequests,coverObj];
}
let instroObj = {
contentType:"INTRO",
mediaType: 'TEXT',
mediaContent:instro,
}
if(instro){
scheduleMediaRequests = [...scheduleMediaRequests,instroObj];
}
const params = {
createId:User.getStoreUserId(),
enableState,
operateIds:_.pluck(selectOperatorList,'id'),
operateType,
percentCompleteLive,
percentCompleteVideo,
planName,
scheduleMediaRequests,
storeId:User.getStoreId(),
trainingTaskList:handleSubmitTaskData(taskList)
}
if (type === 'add') {
PlanService.createTrainingPlan(params).then((res) => {
if (res.success){
message.success("新建成功");
window.RCHistory.goBack();
}
});
}else{
const _params = {
...params,
id
}
PlanService.updateTrainingPlan(_params).then((res) => {
if (res.success){
message.success("更新成功");
window.RCHistory.goBack();
}
});
}
}
function handleSubmitTaskData(taskData){
return taskData.map((item,index)=>{
let _item = {};
_item.taskId = item.taskId;
_item.taskName =item.taskName;
_item.courseList = item.courseList.map((childItem,index)=>{
let _childItem = {}
_childItem.courseId = childItem.courseId;
_childItem.courseName = childItem.courseName;
_childItem.courseType = childItem.courseType;
return _childItem;
});
return _item;
})
}
// 取消编辑并返回上一级路由
function handleGoBack (){
if (!_.isEqual(basicData, defaultBasicData) || !_.isEqual(taskList, defaultTaskList)
) {
Modal.confirm({
title: '确定要返回吗?',
content: '返回后,本次编辑的内容将不被保存',
okText: '确认返回',
cancelText: '留在本页',
icon: <span className="icon iconfont default-confirm-icon">&#xe6f4;</span>,
onOk: () => {
window.RCHistory.goBack();
}
})
} else {
window.RCHistory.goBack();
}
}
return (
<div className="page add-plan-page">
<Breadcrumbs
navList={type == "add" ? "新建培训计划" : "编辑培训计划"}
goBack={handleGoBack}
/>
<div className="box">
<div className="show-tips">
<ShowTips message="请遵守国家相关规定,切勿上传低俗色情、暴力恐怖、谣言诈骗、侵权盗版等相关内容,小麦企培保有依据国家规定及平台规则进行处理的权利" />
</div>
<div className="add-plan-page__form">
<div className="basic-info__wrap">
<div className="title">基本信息</div>
<BasicInfo
data={basicData}
onChange={handleChangeBasicInfo}
/>
</div>
<div className="basic-info__wrap">
<div className="title">培训任务</div>
{ (type==='edit' && taskList.length>0) &&
<TrainingTask data={taskList} onChange={handleChangeTaskInfo} />
}
{ type==='add' &&
<TrainingTask data={taskList} onChange={handleChangeTaskInfo} />
}
</div>
{ (type==='edit' && expiredCourseList.length > 0) &&
<div className="expired-info__wrap">
<div className="title">失效课程</div>
<ExpiredCourseList expiredCourseList={expiredCourseList}/>
</div>
}
</div>
</div>
<div className="footer">
<Button onClick={handleGoBack}>取消</Button>
<Button type="primary" onClick={submitInfo}>保存</Button>
</div>
</div>
)
}
export default AddPlan;
\ No newline at end of file
.add-plan-page {
position:relative !important;
.box {
margin-bottom: 66px !important;
.add-plan-page__form {
margin-top: 16px;
.title {
font-size: 16px;
color: #333;
font-weight: 500;
line-height: 22px;
margin-bottom:8px;
}
}
.expired-info__wrap{
margin-top:16px;
}
}
.footer {
position: fixed;
bottom: 0;
height: 58px;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 252px;
background: #fff;
border-top: 1px solid #E8E8E8;
z-index: 9999;
.ant-btn {
margin-left: 10px;
}
}
}
\ No newline at end of file
import React from 'react';
import { withRouter } from "react-router-dom";
import { Tabs } from 'antd';
import Breadcrumbs from "@/components/Breadcrumbs";
import EmployeeShareData from './components/EmployeeShareData';
import UserLearningData from './components/UserLearningData';
import PlanService from '@/domains/plan-domain/planService';
import User from '@/common/js/user'
import Bus from '@/core/bus';
import './LearningData.less';
const userRole = User.getUserRole();
const defaultCover = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class LearningData extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id");
this.state = {
id,
planName:"",
coverUrl:defaultCover,
courseNum:0,
created:"",
cultureCustomerNum:0,
activeKey:"employeeShareData",
createName:""
}
}
componentDidMount(){
this.getPlanDetail();
Bus.bind('watchDataView',() =>{this.setState({activeKey:"userLearningData"}) })
}
getPlanDetail = ()=>{
const { id } = this.state;
PlanService.getTrainingPlanDetail({
planId: id
}).then((res) => {
const {
planName,
courseMediaVOS,
courseNum,
created,
cultureCustomerNum,
createName
} = res.result;
let coverUrl;
courseMediaVOS.map((item) => {
if(item.contentType === "COVER"){
coverUrl = item.mediaUrl;
}
return item;
})
this.setState({
planName,
coverUrl: coverUrl || defaultCover,
courseNum,
created,
cultureCustomerNum,
createName
})
})
}
render() {
const {planName,coverUrl,courseNum,created,cultureCustomerNum,activeKey,createName} = this.state;
return (
<div className="page plan-learn-data-list">
<Breadcrumbs
navList="学习数据"
goBack={() => {
RCHistory.goBack();
}}
/>
<div className="plan-info">
<div className="plan-intro">
<div className="plan-img-con">
<img src="https://image.xiaomaiketang.com/xm/YNfi45JwFA.png"/>
</div>
<div>
<div className="plan-name">
{planName}
</div>
<div className="create-course">
<span className="createUser">创建人:{createName}</span>
<span className="split">|</span>
<span className="course-total">课程总数量:{courseNum}</span>
</div>
<div className="create-time">创建时间:{formatDate('YYYY-MM-DD H:i', created)} </div>
</div>
</div>
<div className="join">
<div className="number">{cultureCustomerNum}</div>
<div className="text">参培人数</div>
</div>
</div>
<div className="box">
{ (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")?
(<Tabs activeKey={activeKey} onChange={(activeKey)=>{this.setState({activeKey})}}>
<Tabs.TabPane tab="员工分享数据" key="employeeShareData">
<EmployeeShareData/>
</Tabs.TabPane>
<Tabs.TabPane tab="用户学习数据" key="userLearningData">
<UserLearningData/>
</Tabs.TabPane>
</Tabs>)
:
(<UserLearningData/>)
}
</div>
</div>
)
}
}
export default withRouter(LearningData);
\ No newline at end of file
.plan-learn-data-list{
.plan-info{
margin:16px;
padding:16px;
background: #FFF;
display:flex;
justify-content: space-between;
align-items: center;
.plan-intro{
display: flex;
align-items: center;
.plan-img-con{
margin-right:8px;
img{
width:136px;
height:77px;
}
}
.plan-name{
font-size: 16px;
font-weight: 500;
color: #333333;
line-height: 22px;
margin-bottom:8px;
}
.create-course{
font-size: 14px;
color: #666666;
.split{
margin:0 8px;
color:#666;
}
margin-bottom:4px;
}
.create-time{
font-size:14px;
color:#666;
}
}
.join{
margin-right:144px;
min-width:54px;
text-align:center;
.number{
font-size: 26px;
font-weight: 500;
color: #333333;
margin-botttom:4px;
}
.text{
font-size: 14px;
color:#999;
}
}
}
}
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:13:39
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-10 18:38:50
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useEffect, useState } from "react";
import PlanFilter from './components/PlanFilter';
import PlanOpt from './components/PlanOpt';
import PlanList from './components/PlanList';
import PlanService from "@/domains/plan-domain/planService";
import User from '@/common/js/user';
function PlanPage() {
const [planListData, setPlanListData] = useState([]);
const [query,setQuery] = useState({
current:1,
size:10,
});
useEffect(() => {
handleFetchPlanList();
}, [query]);
const [totalCount,setTotalCount] = useState(0);
function queryChange(_query){
const params = {
...query,
..._query,
};
setQuery(params);
}
function handleFetchPlanList(_query){
const params = {
...query,
..._query,
storeUserId:User.getStoreUserId()
};
//动态获取计划列表
PlanService.getTrainingPlanPage(params).then((res) => {
const { result: { records = [], total } } = res;
setPlanListData(records);
setTotalCount(total);
})
}
return (
<div className="page">
<div className="content-header">培训计划</div>
<div className="box">
<PlanFilter onChange={queryChange}/>
<PlanOpt/>
<PlanList
planListData={planListData}
query={query}
totalCount={totalCount}
onChange={queryChange}
/>
</div>
</div>
)
}
export default PlanPage;
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-10 19:03:09
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Button,Input,Switch,Radio,Row,Col,Modal,message,Tooltip} from 'antd';
import { withRouter } from 'react-router-dom';
import SelectOperatorModal from '../modal/SelectOperatorModal';
import { ImgCutModalNew } from '@/components';
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import Upload from '@/core/upload';
import PhotoClip from 'photoclip'
import './BasicInfo.less';
const { TextArea } = Input;
const defaultCover = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
let cutFlag = false;
let timer = null
class BasicInfo extends React.Component{
constructor(props) {
super(props);
this.state = {
operatorModalVisible: false,
showSelectFileModal:false,
cutImageBlob: null
};
}
handleShowSelectOperatorModal = () =>{
this.setState({
operatorModalVisible:true
})
}
handleCloseSelectOperatorMOdal = ()=>{
this.setState({
operatorModalVisible:false
})
}
handleConfirmSelectOperator = (selectOperatorList)=> {
if(selectOperatorList.length === 0){
message.warning('请选择运营师')
return;
}
this.props.onChange('selectOperatorList',selectOperatorList);
this.setState({
operatorModalVisible:false
})
}
enableStateChange = ()=> {
if(this.props.data.enableState==="NO"){
this.props.onChange('enableState','YES')
}else{
this.props.onChange('enableState','NO')
}
}
// 使用默认封面图
handleResetCoverUrl = ()=> {
const { data: { coverUrl } } = this.props;
const isDefaultCover = coverUrl === defaultCover;
// 如果已经是默认图的话,不做任何任何处理
if (isDefaultCover) return;
message.success('已替换为默认图');
this.props.onChange('coverUrl',defaultCover);
setTimeout(()=>{
this.props.onChange('coverId', null);
},1000)
}
handleSelectCover = (file)=> {
this.uploadImage(file);
}
//上传图片
uploadImage = (imageFile) => {
const { folderName } = imageFile;
const fileName = window.random_string(16) + folderName.slice(folderName.lastIndexOf("."));
const self = this;
this.setState(
{
visible: true,
},
() => {
setTimeout(() => {
const okBtnDom = document.querySelector("#headPicModal");
const options = {
size: [500, 282],
ok: okBtnDom,
maxZoom: 3,
style: {
jpgFillColor: "transparent",
},
done: function (dataUrl) {
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if ((self.state.rotate != this.rotate()) || (self.state.scale != this.scale())) {
console.log(this.scale(), 'scale')
const _dataUrl = this.clip()
const cutImageBlob = self.convertBase64UrlToBlob(_dataUrl);
self.setState({
cutImageBlob,
dataUrl: _dataUrl,
rotate: this.rotate(),
scale: this.scale()
})
}
}, 500)
const cutImageBlob = self.convertBase64UrlToBlob(dataUrl);
self.setState({
cutImageBlob,
dataUrl
})
setTimeout(() => {
cutFlag = false;
}, 2000);
},
fail: (failInfo) => {
message.error("图片上传失败了,请重新上传");
},
loadComplete: function (img) {
setTimeout(() => {
const _dataUrl = this.clip()
self.setState({
dataUrl: _dataUrl,
hasImgReady: true
})
}, 100)
},
};
const imgUrl = `${imageFile.ossUrl}?${new Date().getTime()}`
if (!this.state.photoclip) {
const _photoclip = new PhotoClip("#headPicModal", options);
_photoclip.load(imgUrl);
this.setState({
photoclip: _photoclip,
});
} else {
this.state.photoclip.clear();
this.state.photoclip.load(imgUrl);
}
}, 200);
}
);
};
//获取resourceId
getSignature = (blob, fileName) => {
Upload.uploadBlobToOSS(blob, 'cover' + (new Date()).valueOf(),null,'signInfo').then((signInfo) => {
this.setState({
coverClicpPath:signInfo.fileUrl,
coverId:signInfo.resourceId,
visible: false
},()=>this.updateCover())
});
};
updateCover = () =>{
const {coverClicpPath,coverId} = this.state
this.setState({
showSelectFileModal: false
})
this.props.onChange('coverUrl', coverClicpPath);
setTimeout(()=>{
this.props.onChange('coverId', coverId);
},1000)
}
// base64转换成blob
convertBase64UrlToBlob = (urlData) => {
const bytes = window.atob(urlData.split(",")[1]);
const ab = new ArrayBuffer(bytes.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: "image/png" });
};
limitNumber = value => {
if (typeof value === 'string') {
return !isNaN(Number(value)) ? value.replace(/^(0+)|[^\d]/g, '') : ''
} else if (typeof value === 'number') {
return !isNaN(value) ? String(value).replace(/^(0+)|[^\d]/g, '') : ''
} else {
return ''
}
}
percentCompleteBlur = (e,field) =>{
let _percentCompleteLive;
const { value } = e.target;
if(value > 100){
_percentCompleteLive = 100;
}else{
if(value < 0){
_percentCompleteLive = 0;
}else{
_percentCompleteLive = value;
}
}
this.props.onChange(field,_percentCompleteLive)
}
render(){
const { operatorModalVisible ,showSelectFileModal,visible,hasImgReady,cutImageBlob} = this.state;
const { data} = this.props;
const { planName,coverUrl,instro,enableState,operateType,selectOperatorList,percentCompleteLive,percentCompleteVideo} = data;
// 当前是否使用的是默认图片
const isDefaultCover = coverUrl === defaultCover;
return (
<div className="plan-basic-info">
<div className="plan-name">
<span className="label"><span className="require">*</span>培训计划名称:</span>
<Input
value={planName}
placeholder="请输入培训计划名称,最多20字"
maxLength={20}
style={{ width: 240 }}
onChange={(e)=>this.props.onChange('planName', e.target.value)}
/>
</div>
<div className="cover">
<span className="label">封面图:</span>
<div className="cover__wrap">
<div className="img-content">
{ isDefaultCover &&
<span className="tag">默认图</span>
}
<img src={coverUrl} width="690"/>
</div>
<div className="opt-btns">
<Button onClick={() => {
this.setState({
showSelectFileModal:true
})
}}>上传图片</Button>
<span
className={`default-btn ${isDefaultCover ? 'disabled' : ''}`}
onClick={this.handleResetCoverUrl}
>使用默认图</span>
<div className="tips">建议尺寸1280*720px或16:9。封面图最大5M,支持jpg、jpeg和png。</div>
</div>
</div>
</div>
<div className="introduction">
<span className="label">简介:</span>
<TextArea
placeholder="请输入培训计划简介"
maxLength={200}
style={{ width: 480 }}
className="instro-textarea"
value={instro}
onChange={(e)=>this.props.onChange('instro', e.target.value)}
/>
</div>
<div className="wether-use">
<span className="label">是否启用:</span>
<div className="content">
<div>
<Switch checked={enableState==="YES"? true:false} onChange={()=> {this.enableStateChange()}}/>
</div>
<div>
<div className="instro-text">
<div>开启:此培训计划可以分享给用户进行学习</div>
<div>关闭:此培训计划暂不可分享给用户进行学习,后续可开启</div>
</div>
</div>
</div>
</div>
<div className="view-range" >
<span className="label">
<span className="require">*</span>
可见范围
<Tooltip
title="店铺管理员、管理员默认都可见">
<span className="iconfont">&#xe61d;</span>
</Tooltip></span>
<div className="content">
<Radio.Group value={operateType} onChange={(e) => { this.props.onChange('operateType', e.target.value) }}>
<Row style={{ marginBottom: '5px' }}>
<Col span={24}>
<Radio value="All_Operate">
所有运营师
<span className="playback__text">后续新增的运营师都有权限可见</span>
</Radio>
</Col>
</Row>
<Row>
<Col span={8}>
<Radio value="Assign_Operate">
指定运营师
<span className="playback__text">仅被选择的运营师有权限可见</span>
</Radio>
</Col>
</Row>
</Radio.Group>
{operateType==="Assign_Operate" &&
<div className="choose-business">
<Button onClick={()=>{this.handleShowSelectOperatorModal()}}>选择运营师</Button>
<span>已选择<span>{selectOperatorList.length}</span>名运营师</span>
</div>
}
</div>
</div>
<div className="done-standard">
<span className="label"><span className="require">*</span>完成标准:</span>
<div>
<div className="live-standard-info">
<span className="icon iconfont">&#xe865;</span>
<span>直播课单个课程,用户学习进度达到
<Input
width="40"
value={percentCompleteLive}
onChange={(e) => { this.props.onChange('percentCompleteLive', e.target.value.replace(/\D/g,'')) }}
onBlur={(e)=>this.percentCompleteBlur(e,'percentCompleteLive')}
/>%
即视为“已完成”学习
</span>
</div>
<div className="video-standard-info">
<span className="icon iconfont">&#xe864;</span>
<span>视频课单个课程,用户学习进度达到
<Input width="40"
value={percentCompleteVideo}
onChange={(e) => { this.props.onChange('percentCompleteVideo', e.target.value.replace(/\D/g,'')) }}
onBlur={(e)=>this.percentCompleteBlur(e,'percentCompleteVideo')}/>
%即视为“已完成”学习
</span>
</div>
</div>
</div>
{ operatorModalVisible &&
<SelectOperatorModal
visible={operatorModalVisible}
onClose={this.handleCloseSelectOperatorMOdal}
selectOperatorList={selectOperatorList}
onSelect={this.handleConfirmSelectOperator}
/>
}
{showSelectFileModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectFileModal}
onClose={() => {
this.setState({
showSelectFileModal:false
})
}}
onSelect={this.handleSelectCover}
/>
}
<Modal
title="设置图片"
width={1080}
visible={visible}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
onCancel={() => {
this.setState({ visible: false });
}}
zIndex={10001}
footer={[
<Button
key="back"
onClick={() => {
this.setState({ visible: false });
}}
>
重新上传
</Button>,
<Button
key="submit"
type="primary"
disabled={!hasImgReady}
onClick={() => {
if (!cutFlag) {
cutFlag = true;
this.refs.hiddenBtn.click();
}
this.getSignature(cutImageBlob);
}}
>
确定
</Button>,
]}
>
<div className="clip-box">
<div
id="headPicModal"
ref="headPicModal"
style={{
width: "500px",
height: "430px",
marginBottom: 0,
}}
></div>
<div id="clipBtn" style={{ display: "none" }} ref="hiddenBtn"></div>
<div className="preview-img">
<div className="title">效果预览</div>
<div id="preview-url-box" style={{width:500,height:282}}>
<img src={this.state.dataUrl} style={{ width: '100%' }} alt="" />
</div>
</div>
</div>
</Modal>
</div>
);
}
}
export default withRouter(BasicInfo)
.plan-basic-info{
.label {
width: 110px;
text-align: right;
display:inline-block;
.require {
color: #EC4B35;
}
.iconfont{
font-size:14px;
color:#BFBFBF;
}
}
.cover {
display: flex;
margin-top: 16px;
&__wrap {
position: relative;
.tag {
border-radius: 2px;
background: #D6D6D6;
font-size: 12px;
height: 18px;
width: 52px;
text-align: center;
color: #FFF;
position: absolute;
top: 8px;
left: 8px;
}
}
.cover__wrap {
display: flex;
flex-direction: row;
}
.img-content {
margin-right: 20px;
width: 299px;
height: 169px;
img {
width: 100%;
height: 100%;
object-fit: contain;
border: 1px solid #E8e8e8;
}
}
}
.introduction{
margin-top:16px;
.instro-textarea{
vertical-align: top;
}
}
.wether-use{
display:flex;
margin-top:16px;
.instro-text{
color:#999;
margin-left:12px;
}
.content{
display:flex;
}
}
.view-range{
display:flex;
margin-top:16px;
.instro-text{
color:#999;
margin-left:12px;
}
.choose-business{
margin-top:16px;
}
}
.done-standard{
display: flex;
margin-top:22px;
.live-standard-info{
margin-bottom:10px;
}
input{
display:inline-block;
width:90px;
height:32px;
}
}
}
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 User from '@/common/js/user';
import Bus from '@/core/bus';
import './EmployeeShareData.less';
const { Search } = Input;
const UserRole = {
Store_Manager: {
text: "店铺管理员"
},
Cloud_Manager: {
text:"管理员"
},
Cloud_Operator: {
text:'运营师'
},
Cloud_Lecture: {
text:"讲师"
},
};
class EmployeeShareData extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id");
this.state = {
id,
dataSource:[],
size:10,
query: {
current: 1,
},
totalCount:0,
}
}
componentDidMount(){
this.handleFetchDataList();
}
handleFetchDataList = ()=>{
const { query ,size,id} = this.state;
const params ={
...query,
size,
planId:id,
storeId:User.getStoreId(),
}
PlanService.getPlanUserRecordPage(params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
size
},()=>{this.handleFetchDataList()})
}
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 === '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'; }
const _query = {
...query,
sortMap:{}
};
_query.sortMap[_columnKey]=_order;
this.setState({
query:_query
},()=>this.handleFetchDataList())
}
handleChangNickname = (value)=>{
const isPhone = (value || '').match(/^\d+$/);
const { query } = this.state;
if(isPhone){
query.userPhone = value;
query.userName = null;
}else{
query.userName = value;
query.userPhone = null;
}
query.current = 1;
this.setState({
query
})
}
watchDataView = (record)=>{
Bus.trigger('watchDataView',record.storeUserId);
}
// 请求表头
parselumns = () => {
const columns = [
{
title: '员工',
key: 'storeUserName',
dataIndex: 'storeUserName',
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '角色',
key: 'roleEnum',
dataIndex: 'roleEnum',
render: (val, record) => {
return (
<div>
{UserRole[record.roleEnum].text}
</div>
)
}
},
{
title: '手机号',
key: 'storeUserPhone',
dataIndex: 'storeUserPhone',
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '最近分享成功时间',
key: 'recentlyForwardTime',
dataIndex: 'recentlyForwardTime',
render: (val, record) => {
return (
<div>
{formatDate('YYYY-MM-DD H:i', val)}
</div>
)
}
},
{
title: '学习人数',
key: 'learnNum',
dataIndex: 'learnNum',
sorter:true,
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '已学完',
key: 'learnFinishNum',
dataIndex: 'learnFinishNum',
sorter:true,
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '未学完',
key: 'learnNoFinishNum',
dataIndex: 'learnNoFinishNum',
sorter:true,
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
render: (val, record) => {
return (
<span className="operate-item" onClick={()=>this.watchDataView(record)}>数据详情</span>
)
}
}
];
return columns;
}
render() {
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>
<div>
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parselumns()}
pagination={false}
onChange={this.handleChangeTable}
bordered
/>
{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()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
</div>
)
}
}
export default withRouter(EmployeeShareData);
\ No newline at end of file
.employee-share-data{
.search-container{
margin-bottom:12px;
}
.operate-item{
font-size:14px;
color:#5289FA;
cursor: pointer;
}
}
\ No newline at end of file
import React from 'react';
import { Button } from 'antd';
import { withRouter } from 'react-router-dom';
import User from '@/common/js/user';
import './ExpiredCourseList.less';
function ExpiredCourseList(props) {
return (
<div className="expired-course-list">
{ props.expiredCourseList.map((item,index)=>{
return <div className="course-item">
<div className="course-left">
<div className="course-status">
未成功开课
</div>
<div className="course-info">
<div className="course-type">
{ item.courseType === "LIVE" &&
<span>直播课</span>
}
{ item.courseType === "VOICE " &&
<span>视频课</span>
}
</div>
<div className="course-instro">
<div className="course-name">
{item.courseName}
</div>
<div className="task-name">
{item.taskName}
</div>
</div>
</div>
</div>
<div className="course-time">
上课时间:{formatDate('YYYY-MM-DD H:i', item.startTime)}
</div>
</div>
})
}
</div>
);
}
export default withRouter(ExpiredCourseList)
.expired-course-list{
margin:18px 10px 16px;
.course-item{
display:flex;
padding:16px 0;
border-bottom:1px solid #E8E8E8;
justify-content: space-between;
.course-left{
display:flex;
.course-status{
color:#999999;
font-size:14px;
margin-right:16px;
}
.course-info{
display:flex;
.course-type{
margin-right:16px;
span{
padding:2px 8px;
color:#666666;
font-size:11px;
border-radius: 2px;
border: 1px solid #999999;
}
}
.course-instro{
.course-name{
color:#333333;
font-size:14px;
margin-bottom:7px;
}
.task-name{
color:#999;
font-size:14px;
}
}
}
}
.course-time{
color:#999;
font-size:14px;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-14 15:41:30
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-23 13:45:16
* @Description: 大班直播、互动班课列表的筛选组件
*/
import React, { useState, useRef, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { Row, Input, Select ,Tooltip} from 'antd';
import RangePicker from "@/modules/common/DateRangePicker";
import moment from 'moment';
import StoreService from "@/domains/store-domain/storeService";
import './PlanFilter.less';
const { Search } = Input;
const { Option } = Select;
const DEFAULT_QUERY = {
planName: null,
startTime: null,
endTime:null,
enableState:null,
createId: null
}
const defaultCreatorQuery = {
size: 10,
current: 1,
nickName:null
}
function PlanFilter(props) {
const [expandFilter, setExpandFilter] = useState(false);
const [query,setQuery] = useState(DEFAULT_QUERY);
const [hasNext,setHasNext] = useState(false);
const [creatorQuery,setCreatorQuery] = useState(defaultCreatorQuery);
const [creatorList,setCreatorList] = useState([]);
useEffect(() => {
getCreatorList();
}, [creatorQuery]);
// 改变搜索条件
function handleChangeQuery(field, value){
const _query ={
...query,
[field]: value,
current: 1,
}
setQuery(_query);
if (field === 'planName') return;
props.onChange( _query);
}
function handleChangeDates (dates){
const _query = _.clone(query);
if (_.isEmpty(dates)) {
delete _query.startTime;
delete _query.endTime;
} else {
_query.startTime = dates[0].valueOf();
_query.endTime = dates[1].valueOf();
}
const param ={
..._query,
current: 1,
}
setQuery(param);
props.onChange(param);
}
// 重置搜索条件
function handleReset(){
setQuery(DEFAULT_QUERY);
props.onChange(DEFAULT_QUERY);
}
function getCreatorList(current = 1, selectList){
const _query = {
...creatorQuery,
current,
size:10
};
StoreService.getStoreUserBasicPage( _query).then((res) => {
const { result = {} } = res;
const { records = [], total = 0, hasNext } = result;
const list = current > 1 ? creatorList.concat(records) : records;
setHasNext(hasNext);
setCreatorList(list);
});
}
// 滑动加载更多讲师列表
function handleScrollCreatorList(e){
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
getCreatorList(creatorQuery.current + 1);
}
}
return (
<div className="plan-filter">
<Row>
<div className="search-condition">
<div className="search-condition__item">
<span className="search-name">培训计划:</span>
<Search
value={query.planName}
placeholder="搜索培训计划名称"
onChange={(e) => { handleChangeQuery('planName', e.target.value)}}
onSearch={ () => { props.onChange(query) } }
style={{ width: "calc(100% - 70px)" }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
<div className="search-condition__item">
<span>创建人:</span>
<Select
placeholder="请选择创建人"
style={{width:"calc(100% - 70px)"}}
showSearch
allowClear
filterOption={(input, option) => option}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
onPopupScroll={handleScrollCreatorList}
value={query.createId}
onChange={(value) => {
handleChangeQuery('createId', value)
}}
onSearch={(value) => {
creatorQuery.nickName = value
setCreatorQuery(creatorQuery)
getCreatorList()
}
}
onClear ={(value)=>{
setCreatorQuery({
size: 10,
current: 1,
nickName:null
})
getCreatorList()
}
}
>
{_.map(creatorList, (item, index) => {
return (
<Select.Option value={item.id} key={item.id}>{item.nickName}</Select.Option>
);
})}
</Select>
</div>
<div className="search-condition__item">
<span className="search-date">创建日期:</span>
<RangePicker
id="course_date_picker"
allowClear={false}
value={ query.startTime ? [moment(query.startTime), moment(query.endTime)] : null }
format={"YYYY-MM-DD"}
onChange={(dates) => { handleChangeDates(dates) }}
style={{ width: "calc(100% - 70px)" }}
/>
</div>
{ expandFilter &&
<div className="search-condition__item">
<span className="shelf-status">当前状态:</span>
<Select
style={{ width: "calc(100% - 70px)" }}
placeholder="请选择当前状态"
allowClear={true}
value={query.enableState}
onChange={(value) => { handleChangeQuery('enableState', value) }}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
>
<Option value="YES">开启</Option>
<Option value="NO">关闭</Option>
</Select>
</div>
}
</div>
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={handleReset}>&#xe61b; </span></Tooltip>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => {
setExpandFilter(!expandFilter)
}}>{expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span>
</div>
</Row>
</div>
)
}
export default withRouter(PlanFilter);
\ No newline at end of file
.plan-filter {
position: relative;
.ant-input-search-button{
border-left:none;
}
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
.search-name{
vertical-align: middle;
display:inline-block;
height:32px;
line-height:32px;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left:4px;
}
}
}
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:46:46
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-10 15:51:26
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React, { useState, useRef, useEffect } from 'react';
import { Table, Modal, message , Tooltip,Switch,Dropdown} from 'antd';
import { withRouter } from 'react-router-dom';
import { PageControl } from "@/components";
import PlanService from "@/domains/plan-domain/planService";
import SharePlanModal from '../modal/SharePlanModal';
import {LIVE_SHARE} from '@/domains/course-domain/constants';
import User from '@/common/js/user';
import './PlanList.less';
const { confirm } = Modal;
const userRole = User.getUserRole();
function PlanList(props) {
const [sharePlanModal, setSharePlanModal] = useState(null);
function parseColumns(){
const columns = [
{
title: '培训计划',
key: 'planName',
dataIndex: 'planName',
width:'15%',
render: (val, record) => {
return (
<div className="plan__name">
{val}
</div>
)
}
},
{
title: '课程总数量',
key: 'courseNum',
dataIndex: 'courseNum',
width: '10%',
render: (val, record) => {
return (
<div className="course-number">
{val}
</div>
)
}
},
{
title: '当前状态',
width: '10%',
dataIndex: "status",
render: (val, item, index) => {
return (
<Switch checked={item.enableState==="NO"?false:true} onChange={()=>changeEnableState(item)} disabled={(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")?false:true}/>
)
},
},
{
title: '创建人',
key: 'createName',
dataIndex: 'createName',
width: '10%',
render: (val) => {
return (
<div className="create-name">
{val}
</div>
)
}
},
{
title: '创建时间',
width: "10%",
key: 'created',
dataIndex: 'created',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '更新时间',
width: "10%",
key: 'updated',
dataIndex: 'updated',
sorter: true,
render: (val) => {
return formatDate('YYYY-MM-DD H:i', val)
}
},
{
title: '参培人数',
width: "10%",
key: 'cultureCustomerNum',
dataIndex: 'cultureCustomerNum',
sorter: true,
render: (val) => {
return (
<div className="join-number">
{val}
</div>
)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
width: '25%',
render: (val, record) => {
return (
<div className="operate">
<div className="operate__item" onClick={()=>toLearningDataPage(record)}>学习数据</div>
{record.enableState==="YES" &&
<>
<span className="operate__item split"> | </span>
<div className="operate__item" onClick={() => {handleShowShareModal(record); }}>分享</div>
</>
}
{(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<>
<span className="operate__item split"> | </span>
<Dropdown overlay={renderMoreOperate(record)}>
<span className="more-operate">
<span className="operate-text">更多</span>
<span
className="iconfont icon"
style={{ color: "#5289FA" }}
>
&#xe824;
</span>
</span>
</Dropdown>
</>
}
</div>
)
}
}
];
return columns;
}
function renderMoreOperate(item){
return (
<div className="live-course-more-menu">
<div className="operate__item"
onClick={()=>toEditPlanPage(item)}
>编辑</div>
<div
className="operate__item" onClick={()=>handleDelete(item)}
>删除</div>
</div>
)
}
function handleChangeTable(pagination, filters, sorter){
const { columnKey, order } = sorter;
const { query } = props;
let _columnKey;
let _order;
// 按创建时间升序排序
if (columnKey === 'created' && order === 'ascend') {_columnKey="CREATED"; _order = 'SORT_ASC'; }
// 按创建时间降序排序
if (columnKey === 'created' && order === 'descend') { _columnKey="CREATED"; _order = 'SORT_DESC';}
// 按更新时间升序排序
if (columnKey === 'updated' && order === 'ascend') { _columnKey="UPDATED"; _order = 'SORT_ASC'; }
// 按更新时间降序排序
if (columnKey === 'updated' && order === 'descend') { _columnKey="UPDATED"; _order = 'SORT_DESC'; }
// 按更新时间升序排序
if (columnKey === 'cultureCustomerNum' && order === 'ascend') { _columnKey="CUSTOMER_NUM"; _order = 'SORT_ASC'; }
// 按更新时间降序排序
if (columnKey === 'cultureCustomerNum' && order === 'descend') { _columnKey="CUSTOMER_NUM"; _order = 'SORT_DESC'; }
const _query = {
...query,
sortMap:{}
};
_query.sortMap[_columnKey]=_order;
props.onChange(_query);
}
// 显示分享弹窗
function handleShowShareModal(item) {
const htmlUrl = `${LIVE_SHARE}training_plan_detail/${item.planId}?id=${User.getStoreId()}&storeUserId=${User.getStoreUserId()}`;
const longUrl = htmlUrl
const shareData = { ...item, longUrl };
const sharePlanModal = (
<SharePlanModal
data={shareData}
type="liveClass"
close={() => {
setSharePlanModal(null)
}}
/>
)
setSharePlanModal(sharePlanModal)
}
//改变上架状态
function changeEnableState(item){
let _enableState = item.enableState
if(_enableState==='NO'){
_enableState = "YES";
item.enableState = "YES"
}else{
_enableState = "NO"
item.enableState = "NO"
}
const params={
"planId": item.planId,
"enableState":_enableState
}
PlanService.updateStateTrainingPlan(params).then((res)=>{
if(res.success){
if(_enableState === "YES"){
message.success("已启用此计划");
}else{
message.success("已禁用此计划");
}
props.onChange();
}
})
// let _enableState = record.enableState
// if(_enableState==='NO'){
// // _enableState = "YES";
// const params={
// "planId": record.planId,
// "enableState":"YES"
// }
// PlanService.updateStateTrainingPlan(params).then((res)=>{
// if(res.success){
// // if(_enableState === "YES"){
// record.enableState = "YES";
// message.success("已启用此计划");
// // }
// }
// })
// }else{
// // _enableState = "NO";
// // item.enableState = "YES";
// return confirm({
// title: "确定要禁用培训计划吗?",
// content: "禁用后,培训计划不再支持新用户加入,已参与培训的用户可继续培训",
// icon: (
// <span className="icon iconfont default-confirm-icon">&#xe839; </span>
// ),
// okText: "确定",
// okType: "danger",
// cancelText: "取消",
// onOk: () => {
// const params={
// "planId": record.planId,
// "enableState":"NO"
// }
// PlanService.updateStateTrainingPlan(params).then((res)=>{
// if(res.success){
// // if(_enableState === "NO"){
// record.enableState = "NO";
// message.success("已禁用此计划");
// // }
// }
// })
// },
// });
// }
}
function toEditPlanPage(item){
window.RCHistory.push({
pathname: `/create-plan?type=edit&id=${item.planId}`,
})
}
function toLearningDataPage(item){
window.RCHistory.push({
pathname: `/learning-data?id=${item.planId}`,
})
}
function handleDelete(record){
return confirm({
title: '你确定要删除吗?',
content: '删除后,此培训计划的用户将无法继续学习,所有学习数据将同步删除不可恢复',
icon: <span className="icon iconfont default-confirm-icon">&#xe839; </span>,
okText: '删除',
okType: 'danger',
cancelText: '取消',
width:440,
height:188,
onOk: () => {
if(record.enableState === "YES"){
Modal.warning({
title: '无法删除',
content: '培训计划启用中,无法直接删除',
});
}else{
deleteConfirm(record);
}
}
})
}
function deleteConfirm(item){
const params={
"planId": item.planId,
}
PlanService.deleteTrainingPlan(params).then((res)=>{
if(res.success){
message.success("删除成功");
props.onChange();
}
})
}
function onShowSizeChange(current, size){
if (current === size) {
return
}
let _query = props.query
_query.size = size;
props.onChange(_query)
}
return (
<div className="plan-list">
<Table
rowKey={record => record.id}
dataSource={props.planListData}
columns={ parseColumns() }
pagination={false}
onChange={handleChangeTable}
bordered
/>
<div className="box-footer">
<PageControl
current={props.query.current - 1}
pageSize={props.query.size}
total={props.totalCount}
toPage={(page) => {
const _query = {...props.query, current: page + 1};
props.onChange(_query)
}}
onShowSizeChange={onShowSizeChange}
/>
</div>
{sharePlanModal }
</div>
)
}
export default withRouter(PlanList);
\ No newline at end of file
.plan-list{
margin-top:12px;
.operate-text {
color: #5289FA;
cursor: pointer;
}
.operate {
display: flex;
&__item {
color: #5289FA;
cursor: pointer;
&.split {
margin: 0 8px;
color: #BFBFBF;
}
}
}
}
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-10 15:50:02
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Button } from 'antd';
import { withRouter } from 'react-router-dom';
import User from '@/common/js/user';
import './PlanOpt.less';
function PlanOpt() {
function handleCreatePlan(){
window.RCHistory.push({
pathname: '/create-plan?type=add',
})
}
return (
<div className="plan-opt">
{ (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager") &&
<Button
type="primary"
className="mr12"
onClick={handleCreatePlan}
>新建培训计划</Button>
}
</div>
);
}
export default withRouter(PlanOpt)
.plan-opt{
margin-top:4px;
}
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-02-20 16:45:51
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-11 14:02:25
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
import React from 'react';
import { Table ,Button,Input,Form,Collapse,Modal } from 'antd';
import { sortableContainer, sortableElement, sortableHandle} from 'react-sortable-hoc';
import { MenuOutlined } from '@ant-design/icons';
import arrayMove from 'array-move';
import RelatedCourseModal from '../modal/relatedCourseModal'
import { withRouter } from 'react-router-dom';
import './TrainingTask.less';
const { Panel } = Collapse;
const { confirm } = Modal;
const CourseType = {
LIVE: {
text: "直播课"
},
VOICE : {
text:"视频课"
},
RECORD : {
text:'录播课'
}
};
const courseStateShow = {
UN_START: {
title: "待开播",
},
STARTING: {
title: "直播中",
},
FINISH: {
title: "回放",
},
EXPIRED: {
title: "未成功开课",
},
};
const DragHandle = sortableHandle(() => (
<span className="operate__item" >
<span className="icon iconfont">&#xe7cd;</span>
<span className="text">移动</span>
</span>
));
const SortableTaskItem = sortableElement(props => <div {...props}>{props.taskItem}</div>)
const SortableTaskContainer = sortableContainer(props => <div {...props}></div>);
const SortableCourseItem = sortableElement(props => <div {...props}>{props.courseItem}</div>)
const SortableCourseContainer = sortableContainer(props => <div {...props}></div>);
class TrainingTask extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource:this.props.data,
selectedTaskIndex:0,
relatedCourseModalVisible:false
};
}
componentWillMount(){
}
componentWillReceiveProps(nextProps) {
}
onTaskSortEnd = ({ oldIndex, newIndex }) => {
const { dataSource } = this.state;
if (oldIndex !== newIndex) {
const newData = arrayMove([].concat(dataSource), oldIndex, newIndex).filter(el => !!el);
this.setState({
dataSource:newData,
},()=>{this.props.onChange(newData);})
}
};
onCourseSortEnd = ({ oldIndex, newIndex },parentIndex) => {
const { dataSource } = this.state;
const _dataSource = [...dataSource];
if (oldIndex !== newIndex) {
_dataSource[parentIndex].courseList = arrayMove([].concat(dataSource[parentIndex].courseList), oldIndex, newIndex).filter(el => !!el);
this.setState({
dataSource:_dataSource,
},()=>{this.props.onChange(_dataSource);})
}
};
addTask = () => {
const { dataSource } = this.state;
const taskObj={
taskName: '',
index:dataSource.length,
type:'input',
open:true,
courseList:[
]
}
const newData = [...dataSource,taskObj];
this.setState({
dataSource:newData
},()=>{this.props.onChange(newData);})
}
handleRenameTaskName = (e,record) => {
const { value } = e.target;
const { dataSource } = this.state;
record.taskName = value;
this.setState({
dataSource,
},()=>{this.props.onChange(dataSource);})
}
handleTaskNameBlur = (e,record)=>{
const { value } = e.target;
const { dataSource }= this.state;
let input = /^[\s]*$/;
if(value && !input.test(value)){
record.type="text";
this.setState({
dataSource,
},()=>{this.props.onChange(dataSource);})
}
}
handleRenameCourseName = (e,record) => {
const { value } = e.target;
const { dataSource } = this.state;
record.courseName = value;
this.setState({
dataSource,
},()=>{this.props.onChange(dataSource);})
}
handleCourseNameBlur = (e,record)=>{
const { value } = e.target;
const { dataSource }= this.state;
let input = /^[\s]*$/;
if(value && !input.test(value)){
record.type="text";
this.setState({
dataSource,
},()=>{this.props.onChange(dataSource);})
}
}
handleDeleteTask = (index)=>{
return confirm({
title: "删除任务",
content: "删除该任务会同步删除任务下的课程,是否仍要删除?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
this.handleConfirmDeleteTask(index);
},
});
}
handleConfirmDeleteTask = (index)=>{
const {dataSource}= this.state;
const newData=[...dataSource];
newData.splice(index,1);
this.setState({
dataSource:newData,
},()=>{this.props.onChange(newData);})
}
handleDeleteCourse = (parentIndex,index)=>{
return confirm({
title: "删除课程",
content: "确定删除该课程吗?",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
this.handleConfirmDeleteCourse(parentIndex,index);
},
});
}
handleConfirmDeleteCourse = (parentIndex,index)=>{
const {dataSource}= this.state;
const newData=[...dataSource];
const selectData = [...newData[parentIndex].courseList]
selectData.splice(index,1)
newData[parentIndex].courseList= selectData;
this.setState({
dataSource:newData,
},()=>{this.props.onChange(newData);})
}
showRelatedCourseModal = (index)=>{
this.setState({
selectedTaskIndex:index,
relatedCourseModalVisible:true
})
}
closeRelatedCourseModal = (index)=>{
this.setState({
relatedCourseModalVisible:false
})
}
confirmSelectCourse = (selectList) =>{
const {selectedTaskIndex}= this.state;
const { dataSource } = this.state
const newData=[...dataSource];
const selectData = [...newData[selectedTaskIndex].courseList]
const _selectData =[...selectData,...selectList];
newData[selectedTaskIndex].courseList= _selectData;
this.setState({
relatedCourseModalVisible:false,
dataSource:newData,
},()=>{
this.props.onChange(newData);
})
}
openOrCloseTask = (index)=>{
const {dataSource}= this.state;
const newData=[...dataSource];
newData[index].open = !newData[index].open;
this.setState({
dataSource:newData,
},()=>{this.props.onChange(newData);})
}
handleValidatorTaskName = (rule,value)=>{
let input = /^[\s]*$/;
if (input.test(value) || !value){
return Promise.reject(new Error('请输入任务名称'))
}
return Promise.resolve()
}
handleValidatorCourseName = (rule,value)=>{
let input = /^[\s]*$/;
if (input.test(value) || !value){
return Promise.reject(new Error('请输入课程名称'))
}
return Promise.resolve()
}
renderTaskItem = (record,index)=>{
return <div className="task-item">
<div className="task-con">
<div className="task-instro">
<span onClick={()=>this.openOrCloseTask(index)}>
{record.open?
<span className="icon iconfont open-icon">&#xe82d;</span>:
<span className="icon iconfont open-icon">&#xe835;</span>
}
</span>
{record.type==='input'?
<div className="task-name-con">
<span className="number">{index + 1}.</span>
<Form>
<Form.Item
validateTrigger={['onChange', 'onBlur']}
name={['taskName']}
rules={[
{
validator:(rule,value)=>this.handleValidatorTaskName(rule,value)
}
]}>
<Input className="task-name-input" defaultValue={record.taskName} style={{ width: 300 }} placeholder="请输入任务名称(20字以内)" maxLength={20} onChange={(e) => { this.handleRenameTaskName(e,record)}} onBlur={(e)=>{this.handleTaskNameBlur(e,record)}}/>
</Form.Item>
</Form>
</div>
:
<div className="task-name-con">
<span className="number">{index + 1}.</span>
<span className="task-name">{record.taskName}</span>
</div>
}
</div>
<div className="operate">
<DragHandle />
<span className="operate__item">
<span className="icon iconfont">&#xe6f5;</span>
<span className="text" onClick={()=>{const { dataSource }= this.state; record.type="input";this.setState({dataSource})}}>重命名</span>
</span>
<span className="operate__item" onClick={()=>{this.handleDeleteTask(index)}} >
<span className="icon iconfont">&#xe6f6;</span>
<span className="text">删除</span>
</span>
</div>
</div>
{record.open &&
<div className="course-box">
<SortableCourseContainer
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={(record)=>this.onCourseSortEnd(record,index)} >
{record.courseList.map((courseItem, courseIndex) =>
<SortableCourseItem courseItem={this.renderCourseItem(courseItem,courseIndex,index)} index={courseIndex}>
</SortableCourseItem>
)}
</SortableCourseContainer>
<div className="add-course-con">
{record.courseList.length>19?
<span className="add-course-btn-disabled" onClick={()=>{this.showRelatedCourseModal(index)}}><span>+</span><span>关联课程</span></span>
:
<span className="add-course-btn" onClick={()=>{this.showRelatedCourseModal(index)}}><span>+</span><span>关联课程</span></span>
}
</div>
</div>
}
</div>
}
renderCourseItem = (record,index,parentIndex)=>{
return <div className="course-item">
<div className="course-info">
<span className="course-type">{CourseType[record.courseType].text}</span>
{record.type==='input'?
<Form>
<Form.Item
validateTrigger={['onChange', 'onBlur']}
name={['courseName']}
rules={[
{
validator:(rule,value)=>this.handleValidatorCourseName(rule,value)
}
]}>
<Input className="course-name-input" defaultValue={record.courseName} style={{ width: 300 }} placeholder="请输入课程名称(40字以内)" maxLength={40} onChange={(e) => { this.handleRenameCourseName(e,record)}} onBlur={(e)=>{this.handleCourseNameBlur(e,record)}}/></Form.Item>
</Form>
:
<span className="course-name">{parentIndex + 1}.{index + 1}{record.courseName}</span>
}
{record.courseState === "EXPIRED" &&
<span className="icon iconfont tip">&#xe834;</span>
}
{ record.courseType==="LIVE" &&
<span className="course-state">{courseStateShow[record.courseState].title}</span>
}
</div>
<div className="operate">
<DragHandle />
<span className="operate__item">
<span className="icon iconfont">&#xe6f5;</span>
<span className="text" onClick={(e)=>{const { dataSource } = this.state; record.type="input";this.setState({dataSource})}}>重命名</span>
</span>
<span className="operate__item" onClick={()=>{this.handleDeleteCourse(parentIndex,index)}}>
<span className="icon iconfont">&#xe6f6;</span>
<span className="text">删除</span>
</span>
</div>
</div>
}
render() {
const { dataSource,selectedTaskIndex,relatedCourseModalVisible} = this.state;
const { data } = this.props;
return (
<div className="training-task">
<SortableTaskContainer
useDragHandle
disableAutoscroll
helperClass="row-dragging"
onSortEnd={this.onTaskSortEnd}
>
{dataSource.map((item, index) =>
<SortableTaskItem taskItem={this.renderTaskItem(item,index)} index={index}>
</SortableTaskItem>
)}
</SortableTaskContainer>
<div className="add-task-con">
{dataSource.length > 9 ?
<span className="add-task-btn-disabled"><span>+</span><span>添加任务</span></span>
:
<span className="add-task-btn" onClick={()=>this.addTask()}><span>+</span><span>添加任务</span></span>
}
</div>
{ relatedCourseModalVisible &&
<RelatedCourseModal
selectedTaskIndex={selectedTaskIndex}
data={dataSource}
visible={relatedCourseModalVisible}
onClose={this.closeRelatedCourseModal}
onSelect={this.confirmSelectCourse}
/>
}
</div>
);
}
}
export default withRouter(TrainingTask)
.training-task{
thead{
display:none;
}
.ant-form-item{
margin-bottom:0 !important;
}
.task-item{
.task-con{
display:flex;
padding:16px;
background: #F7F8F9;
border-radius: 2px;
justify-content: space-between;
align-items: center;
.task-instro{
display:flex;
align-items: center;
.open-icon{
color:#999999;
font-size:10px;
}
.task-name-con{
display:flex;
align-items: center;
color:#333333;
font-size:14px;
.number{
margin-right:10px;
margin-left:10px;
}
.task-name-input{
width: 300px;
height: 32px;
background: #FFFFFF;
border-radius: 4px;
}
}
}
}
.operate{
.operate__item{
cursor:pointer;
margin-left:16px;
color:#666666;
font-size:14px;
.icon{
color:#999;
}
.text{
margin-left:8px;
}
}
}
.course-box{
.course-item{
display:flex;
padding:16px 16px 16px 51px;
justify-content: space-between;
align-items: center;
.course-info{
.ant-form{
display:inline-block;
}
.course-name-input{
margin-right:8px;
}
.course-type{
font-size:11px;
color:#666666;
padding:1px 8px;
border: 1px solid #999999;
margin-right:4px;
border-radius: 2px;
}
.course-name{
color:#666666;
font-size:14px;
margin-right:8px;
}
.tip{
font-size:14px;
color:#FF4F4F;
margin-right:2px;
}
.course-state{
color:#999;
font-size:14px;
}
}
}
.add-course-con{
padding:16px 51px;
color: #5289FA;
font-size:14px;
.add-course-btn-disabled{
font-size:14px;
color:#ccc;
}
}
}
}
.add-task-con{
height: 52px;
background: #F7F8F9;
border-radius: 2px;
padding:16px;
margin-top:16px;
.add-task-btn-disabled{
color:#CCCCCC;
font-size:14px;
}
.add-task-btn{
color: #5289FA;
font-size:14px;
}
}
}
import React from 'react';
import { withRouter } from "react-router-dom";
import {Table, Modal,Input,message,Tooltip} from 'antd';
import { PageControl } from "@/components";
import UserLearningDataFilter from './UserLearningDataFilter';
import PlanService from '@/domains/plan-domain/planService';
import UserLearnDetailModal from "../modal/UserLearnDetailModal";
import UnbundEmployeeModal from '../modal/UnbundEmployeeModal'
import User from '@/common/js/user';
import './UserLearningData.less';
const userRole = User.getUserRole();
const { Search } = Input;
const { confirm } = Modal;
const LearnState = {
UN_PLAY: {
text: "未开始"
},
UNDER_WAY: {
text:"进行中"
},
FINISH : {
text:'已完成'
}
};
class UserLearningData extends React.Component {
constructor(props) {
super(props);
const id = getParameterByName("id");
this.state = {
id,
dataSource:[],
size:10,
query: {
current: 1,
},
totalCount:0,
userLearnDetailModalSHow:false,
unbundEmployeeModalVisible:false
}
}
componentDidMount(){
this.handleFetchDataList();
}
handleFetchDataList = (_query)=>{
const { query ,size,id} = this.state;
const params ={
...query,
..._query,
size,
planId:id,
storeId:User.getStoreId(),
storeUserId:User.getStoreUserId(),
}
this.setState({ query: params });
PlanService.getPlanCustomerRecordPage(params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
dataSource: records,
totalCount: Number(total)
});
});
}
onShowSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
size
},()=>{this.handleFetchDataList()})
}
handleChangeTable = (pagination, filters, sorter)=> {
const { columnKey, order } = sorter;
const { query } = this.state;
let _columnKey;
let _order;
if (columnKey === 'latelyLearnTime' && order === 'ascend') {_columnKey="LATE_LEARN_TIME"; _order = 'SORT_ASC'; }
if (columnKey === 'latelyLearnTime' && order === 'descend') { _columnKey="LATE_LEARN_TIME"; _order = 'SORT_DESC';}
if (columnKey === 'startLearnTime' && order === 'ascend') { _columnKey="START_LEARN_TIME"; _order = 'SORT_ASC'; }
if (columnKey === 'startLearnTime' && order === 'descend') { _columnKey="START_LEARN_TIME"; _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'; }
const _query = {
...query,
sortMap:{}
};
_query.sortMap[_columnKey]=_order;
this.setState({
query:_query
}, ()=>this.handleFetchDataList())
}
watchDetail = (record)=>{
this.setState({
userLearnDetailModalSHow:true,
storeCustomerId:record.storeCustomerId,
planId:record.planId
})
}
closeUserLearnDetailModal = ()=>{
this.setState({
userLearnDetailModalSHow:false,
})
}
UnbundEmployee = (record)=>{
if(User.getUserRole()==='CloudOperator'){
return confirm({
title: "你确定要解绑与用户的关系吗?",
content: "解绑后,用户该培训计划的学习数据将同步移出",
icon: (
<span className="icon iconfont default-confirm-icon">&#xe839; </span>
),
okText: "删除",
okType: "danger",
cancelText: "取消",
onOk: () => {
this.handleConfirmUnbundEmployee(record.storeCustomerId);
},
});
}else{
this.setState({
unbundEmployeeModalVisible:true,
storeCustomerId:record.storeCustomerId
})
}
}
handleConfirmUnbundEmployee=(storeCustomerId)=>{
let removeUserIds = [];
removeUserIds.push(storeCustomerId)
const params = {
planId:getParameterByName("id"),
removeUserIds,
storeCustomerId:storeCustomerId,
storeId:User.getStoreId(),
storeUserId:User.getStoreUserId()
}
PlanService.removePlanCustomer(params).then((res) => {
this.handleFetchDataList();
message.success('解绑成功');
});
}
handleCloseUnbundEmployeeModal = ()=>{
this.setState({
unbundEmployeeModalVisible:false,
})
}
// 请求表头
parselumns = () => {
let columns;
if(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager"){
columns = [
{
title: '用户',
key: 'storeCustomerName',
dataIndex: 'storeCustomerName',
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '学习状态',
key: 'learnState',
dataIndex: 'learnState',
render: (val, record) => {
return (
<div>
{LearnState[val].text}
</div>
)
}
},
{
title: <span>
<span>负责人</span>
<Tooltip title="用户加入学习时,培训计划的分享者"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
key: 'userNameList',
dataIndex: 'userNameList',
render: (val, record) => {
return (
<div>
{record.userNameList.map((item,index)=>{
return <span>{item} { (index < record.userNameList.length-1)&&(<span></span>)} </span>
})}
</div>
)
}
},
{
title: '最近学习时间',
key: 'latelyLearnTime',
dataIndex: 'latelyLearnTime',
sorter:true,
render: (val, record) => {
return (
<div>
{formatDate('YYYY-MM-DD H:i', val)}
</div>
)
}
},
{
title: '开始学习时间',
key: 'startLearnTime',
dataIndex: 'startLearnTime',
sorter:true,
render: (val, record) => {
return (
<div>
{formatDate('YYYY-MM-DD H:i', val)}
</div>
)
}
},
{
title: <span>
<span>学习进度</span>
<Tooltip title="用户培训计划中达到“已完成”状态的课程数/总课程数"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
key: 'learnNum',
dataIndex: 'learnNum',
sorter:true,
render: (val, record) => {
return (
<div>
<span>{record.courseFinishNum}</span>
<span>/</span>
<span>{record.courseNum}</span>
</div>
)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
render: (val, record) => {
return (
<div className="operate-area">
<span className="operate-item" onClick={()=>this.watchDetail(record)}>学习详情</span>
<span className="split">|</span>
<span className="operate-item" onClick={()=>{this.UnbundEmployee(record)}}>解绑</span>
</div>
)
}
}
];
}else{
columns = [
{
title: '用户',
key: 'storeCustomerName',
dataIndex: 'storeCustomerName',
render: (val, record) => {
return (
<div>
{val}
</div>
)
}
},
{
title: '学习状态',
key: 'learnState',
dataIndex: 'learnState',
render: (val, record) => {
return (
<div>
{LearnState[val].text}
</div>
)
}
},
{
title: '最近学习时间',
key: 'latelyLearnTime',
dataIndex: 'latelyLearnTime',
sorter:true,
render: (val, record) => {
return (
<div>
{formatDate('YYYY-MM-DD H:i', val)}
</div>
)
}
},
{
title: '开始学习时间',
key: 'startLearnTime',
dataIndex: 'startLearnTime',
sorter:true,
render: (val, record) => {
return (
<div>
{formatDate('YYYY-MM-DD H:i', val)}
</div>
)
}
},
{
title: <span>
<span>学习进度</span>
<Tooltip title="用户培训计划中达到“已完成”状态的课程数/总课程数"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip>
</span>,
key: 'learnNum',
dataIndex: 'learnNum',
sorter:true,
render: (val, record) => {
return (
<div>
<span>{record.courseFinishNum}</span>
<span>/</span>
<span>{record.courseNum}</span>
</div>
)
}
},
{
title: '操作',
key: 'operate',
dataIndex: 'operate',
render: (val, record) => {
return (
<div className="operate-area">
<span className="operate-item" onClick={()=>this.watchDetail(record)}>学习详情</span>
<span className="split">|</span>
<span className="operate-item" onClick={()=>{this.UnbundEmployee(record)}}>解绑</span>
</div>
)
}
}
];
}
return columns;
}
render() {
const { dataSource,query,size,totalCount,userLearnDetailModalSHow,storeCustomerId,planId,unbundEmployeeModalVisible} = this.state;
return (
<div className="user-learning-data">
<div className="search-container">
<UserLearningDataFilter onChange={this.handleFetchDataList}/>
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parselumns()}
pagination={false}
className="user-learning-table"
onChange={this.handleChangeTable}
bordered
/>
{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()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
{ userLearnDetailModalSHow &&
<UserLearnDetailModal storeCustomerId={storeCustomerId} planId={planId} visible={userLearnDetailModalSHow} onClose={this.closeUserLearnDetailModal}/>
}
{unbundEmployeeModalVisible &&
<UnbundEmployeeModal
visible={unbundEmployeeModalVisible}
onClose={this.handleCloseUnbundEmployeeModal}
storeCustomerId={storeCustomerId}
onConfirm={()=>{this.handleFetchDataList();this.handleCloseUnbundEmployeeModal();}}
/>
}
</div>
)
}
}
export default withRouter(UserLearningData);
\ No newline at end of file
.user-learning-data{
.user-learning-table{
margin-top:4px;
}
.operate-area{
.operate-item{
font-size:14px;
color:#5289FA;
cursor: pointer;
}
.split{
margin:0 4px;
color: #BFBFBF;
}
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-14 15:41:30
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-23 13:45:16
* @Description: 大班直播、互动班课列表的筛选组件
*/
import React, { useState, useRef, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { Row, Input, Select ,Tooltip} from 'antd';
import RangePicker from "@/modules/common/DateRangePicker";
import moment from 'moment';
import StoreService from "@/domains/store-domain/storeService";
import User from '@/common/js/user'
import Bus from '@/core/bus';
import './UserLearningDataFilter.less';
const { Search } = Input;
const { Option } = Select;
const userRole = User.getUserRole();
const DEFAULT_QUERY = {
customerName: null,
startTime: null,
endTime:null,
learnState:null,
operateId: null
}
const defaultCreatorQuery = {
size: 10,
current: 1,
nickName:null
}
function UserLearningDataFilter(props) {
const [expandFilter, setExpandFilter] = useState(false);
const [query,setQuery] = useState(DEFAULT_QUERY);
const [hasNext,setHasNext] = useState(false);
const [creatorQuery,setCreatorQuery] = useState(defaultCreatorQuery);
const [creatorList,setCreatorList] = useState([]);
useEffect(() => {
Bus.bind('watchDataView',(value) => handleChangeCreatorQuery(value))
}, []);
useEffect(() => {
getCreatorList();
}, [creatorQuery]);
function handleChangeCreatorQuery (value){
const _creatorQuery = {...creatorQuery};
_creatorQuery.operateId = value;
setCreatorQuery(_creatorQuery);
}
// 改变搜索条件
function handleChangeQuery(field, value){
const _query ={
...query,
[field]: value,
current: 1,
}
setQuery(_query);
if (field === 'customerName') return;
props.onChange( _query);
}
function handleChangeDates (dates){
const _query = _.clone(query);
if (_.isEmpty(dates)) {
delete _query.startTime;
delete _query.endTime;
} else {
_query.startTime = dates[0].valueOf();
_query.endTime = dates[1].valueOf();
}
const param ={
..._query,
current: 1,
}
setQuery(param);
props.onChange(param);
}
// 重置搜索条件
function handleReset(){
setQuery(DEFAULT_QUERY);
props.onChange(DEFAULT_QUERY);
}
function getCreatorList(current = 1, selectList){
const _query = {
...creatorQuery,
current,
size:10
};
StoreService.getStoreUserBasicPage( _query).then((res) => {
const { result = {} } = res;
const { records = [], total = 0, hasNext } = result;
const list = current > 1 ? creatorList.concat(records) : records;
setHasNext(hasNext);
setCreatorList(list);
});
}
// 滑动加载更多讲师列表
function handleScrollCreatorList(e){
const container = e.target;
const scrollToBottom = container && container.scrollHeight <= container.clientHeight + container.scrollTop;
if (scrollToBottom && hasNext) {
getCreatorList(creatorQuery.current + 1);
}
}
return (
<div className="user-learn-data-filter">
<Row>
<div className="search-condition">
<div className="search-condition__item">
<span className="label customer-label">用户:</span>
<Search
value={query.customerName}
placeholder="搜索用户名称"
onChange={(e) => { handleChangeQuery('customerName', e.target.value)}}
onSearch={ () => { props.onChange(query) } }
style={{ width: "calc(100% - 70px)" }}
enterButton={<span className="icon iconfont">&#xe832;</span>}
/>
</div>
{(User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")&&
<div className="search-condition__item">
<span className="label lead-label">负责人:</span>
<Select
id="leadSelect"
placeholder="请选择创建人"
style={{width:"calc(100% - 70px)"}}
showSearch
allowClear
filterOption={(input, option) => option}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
onPopupScroll={handleScrollCreatorList}
value={query.operateId}
onChange={(value) => {
handleChangeQuery('operateId', value)
}}
onSearch={(value) => {
creatorQuery.nickName = value
setCreatorQuery(creatorQuery)
getCreatorList();
}
}
onClear ={(value)=>{
setCreatorQuery({
size: 10,
current: 1,
nickName:null
})
getCreatorList()
}
}
>
{_.map(creatorList, (item, index) => {
return (
<Select.Option value={item.id} key={item.id}>{item.nickName}</Select.Option>
);
})}
</Select>
</div>
}
<div className="search-condition__item">
<span className="label learn-date-label">最近学习日期:</span>
<RangePicker
id="course_date_picker"
allowClear={false}
value={ query.startTime ? [moment(query.startTime), moment(query.endTime)] : null }
format={"YYYY-MM-DD"}
onChange={(dates) => { handleChangeDates(dates) }}
style={{ width: "calc(100% - 98px)" }}
/>
</div>
{ ((expandFilter && (User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")) || User.getUserRole === "CloudOperator")&&
<div className="search-condition__item">
<span className="label learn-status-label">学习状态:</span>
<Select
style={{ width: "calc(100% - 70px)" }}
placeholder="请选择当前状态"
allowClear={true}
value={query.learnState}
onChange={(value) => { handleChangeQuery('learnState', value) }}
suffixIcon={<span className="icon iconfont" style={{fontSize:'12px',color:'#BFBFBF'}}>&#xe835;</span>}
>
<Option value="UN_PLAY">未开始</Option>
<Option value="UNDER_WAY">进行中</Option>
<Option value="FINISH">已完成</Option>
</Select>
</div>
}
</div>
{( User.getUserRole() === "CloudManager" || User.getUserRole() === "StoreManager")&&
<div className="reset-fold-area">
<Tooltip title="清空筛选"><span className="resetBtn iconfont icon" onClick={handleReset}>&#xe61b; </span></Tooltip>
<span style={{ cursor: 'pointer' }} className="fold-btn" onClick={() => {
setExpandFilter(!expandFilter)
}}>{expandFilter ? <span><span>收起</span><span className="iconfont icon fold-icon" >&#xe82d; </span> </span> : <span>展开<span className="iconfont icon fold-icon" >&#xe835; </span></span>}</span>
</div>
}
</Row>
</div>
)
}
export default withRouter(UserLearningDataFilter);
\ No newline at end of file
.user-learn-data-filter {
position: relative;
.ant-input-search-button{
border-left:none;
}
.search-condition {
width: calc(100% - 80px);
display: flex;
align-items: center;
flex-wrap: wrap;
&__item {
width: 30%;
margin-right: 3%;
margin-bottom: 12px;
.label{
font-size:14px;
color:#666;
vertical-align: middle;
width:70px;
text-align:right;
display:inline-block;
}
.learn-date-label{
width:98px;
text-align:right;
display:inline-block;
}
.ant-input-group-wrapper{
vertical-align: middle;
}
}
}
.reset-fold-area {
position: absolute;
right: 12px;
}
.resetBtn {
color: #999999;
font-size: 18px;
margin-right: 8px;
}
.fold-btn {
font-size: 14px;
color: #666666;
line-height: 20px;
.fold-icon {
font-size: 12px;
margin-left:4px;
}
}
}
\ No newline at end of file
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import StoreService from "@/domains/store-domain/storeService";
import User from '@/common/js/user'
import './SelectOperatorModal.less';
import _ from "underscore";
const { Search } = Input;
class SelectOperatorModal extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource:[],
size:10,
query: {
current: 1,
},
totalCount:0,
selectOperatorList:this.props.selectOperatorList
};
}
componentDidMount() {
this.handleFetchDataList();
}
// 获取运营师列表
handleFetchDataList = () => {
const {query,size,totalCount} = this.state
const params ={
...query,
size,
roleCodes:['CloudOperator']
}
StoreService.getStoreUserBasicPage(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: 'nickName',
dataIndex: 'nickName'
},
{
title: '手机号',
key: 'phone',
dataIndex: 'phone'
}
];
return columns;
}
selectOperator = (record,selected) =>{
const {selectOperatorList} = this.state;
let _list = [];
if (selected || !_.find(selectOperatorList, (item) => item.id == record.id)) {
_list = _.uniq(selectOperatorList.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectOperatorList, (item) => item.id === record.id);
}
this.setState({selectOperatorList:_list});
}
render() {
const { size,dataSource,totalCount,query,selectOperatorList} = this.state;
const { visible } = this.props;
return (
<Modal
title="选择运营师"
onCancel={this.props.onClose}
maskClosable={false}
visible={visible}
className="select-operator-modal"
closable={true}
width={800}
onOk={() => this.props.onSelect(selectOperatorList) }
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()}} />
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectOperatorList, 'id'),
onSelect: (record, selected) => {
this.selectOperator(record, selected)
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectOperatorList.concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectOperatorList, (item) => _.find(changeRows, (data) => data.id === item.id));
}
this.setState({selectOperatorList:_list});
},
}}
/>
{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()})
}}
onShowSizeChange={this.onShowSizeChange}
/>
</div>
}
</div>
</Modal>
)
}
}
export default SelectOperatorModal;
\ No newline at end of file
.select-operator-modal{
.search-container{
margin-bottom:16px;
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-07-20 19:12:49
* @Last Modified by: 吴文洁
* @Last Modified time: 2020-07-20 20:25:13
* @Description: 大班直播分享弹窗
*/
import React from 'react';
import { Modal, Input, 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 CourseService from "@/domains/course-domain/CourseService";
import './SharePlanModal.less';
const storeName = User.getStoreName();
const DEFAULT_COVER = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
class ShareLiveModal extends React.Component {
constructor(props) {
super(props);
this.state = {
shareUrl: ''
}
}
componentDidMount() {
// 获取短链接
this.handleConvertShortUrl();
}
handleConvertShortUrl = () => {
const { longUrl } = this.props.data;
// 发请求
CourseService.getQrcode({
urls: [longUrl]
}).then((res) => {
const { result = [] } = res;
this.setState({
shareUrl: result[0].shortUrl
}, () => {
const qrcodeWrapDom = document.querySelector('#qrcodeWrap');
const qrcodeNode = new qrcode({
text: this.state.shareUrl,
size: 98,
})
qrcodeWrapDom.appendChild(qrcodeNode);
});
})
}
componentWillUnmount() {
// 页面销毁之前清空定时器
clearTimeout(this.timer);
}
// 下载海报
handleDownloadPoster = () => {
this.setState({
showImg:true,
time:new Date().valueOf()
},()=>{
this.setState({time:new Date().valueOf()},()=>{
let node = document.getElementById('poster');
domtoimage.toPng(node)
.then((imgData) => {
console.log(imgData)
const download = document.createElement('a');
const { planName } = this.props.data;
$(download).attr('href', imgData).attr('download', `${planName}.png`).get(0).click();
})
})
})
}
// 复制分享链接
handleCopy = () => {
const textContent = document.getElementById('shareUrl').innerText;
window.copyText(textContent);
message.success('复制成功!');
}
render() {
const {data} = this.props;
const { planName, coverUrl = DEFAULT_COVER} = data;
const { shareUrl,showImg,time} = this.state;
return (
<Modal
title={'分享培训计划'}
width={680}
visible={true}
footer={null}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
className="share-live-modal"
onCancel={this.props.close}
>
<div className="left">
<div id="poster">
<div className="store-name">
<span className="text">{storeName}</span>
</div>
<div className="course-name-title">邀请你参与培训:</div>
<div class="live-couse-name">{planName}</div>
{
showImg ? <img
crossOrigin='*'
src={coverUrl+`?=${time}`}
className="course-cover"
/>: <img
src={coverUrl+`?=${time}`}
className="course-cover"
/>
}
<div className="qrcode-wrap">
<div className="qrcode-wrap__left">
<div className="text">长按识别二维码进入观看</div>
<img className="finger" src="https://image.xiaomaiketang.com/xm/thpkWDwJsC.png"/>
</div>
<div className="qrcode-wrap__right" id="qrcodeWrap">
</div>
</div>
</div>
</div>
<div className="right">
<div className="share-poster right__item">
<div className="title">① 海报分享</div>
<div className="sub-title">用户可通过微信扫描海报二维码,查看培训计划</div>
<div className="content" onClick={this.handleDownloadPoster}>下载海报</div>
</div>
<div className="share-url right__item">
<div className="title">② 链接分享</div>
<div className="sub-title">用户可通过微信打开以下链接,查看培训计划</div>
<div className="content url-content">
<div className="share-url" id="shareUrl">{shareUrl}</div>
<Button type="primary" onClick={this.handleCopy}>复制</Button>
</div>
</div>
</div>
</Modal>
)
}
}
export default ShareLiveModal;
.share-live-modal {
.ant-modal-body {
display: flex;
#poster{
background: #FFF;
margin:0;
padding: 20px;
}
.left {
width: 303px;
margin: 0 32px 0 16px;
box-shadow:0px 2px 10px 0px rgba(0,0,0,0.05);
border-radius: 12px;
.course-name-title {
font-size: 14px;
color: #333;
line-height: 20px;
margin-bottom: 4px;
}
.live-couse-name{
font-size:16px;
color:#333333;
font-weight: 600;
}
.course-name {
color: #333;
font-size: 16px;
font-weight: 600;
line-height: 20px;
}
.course-cover {
width: 263px;
height: 143px;
border-radius: 6px;
margin-top: 8px;
}
.qrcode-wrap {
padding: 0 16px;
display: flex;
align-items: center;
margin: 24px 0 16px 0;
&__left {
width: 98px;
text-align: center;
margin-right: 22px;
.text {
line-height: 20px;
}
.finger {
width: 40px;
height: 40px;
margin-top: 8px;
}
}
&__right {
width: 110px;
height: 110px;
padding: 6px
}
}
.store-name {
// padding: 8px 16px;
display: flex;
align-items: center;
margin-bottom: 8px;
.text {
font-size: 12px;
color: #999;
font-size: 14px;
line-height: 20px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
}
}
.right {
.title {
color: #333;
font-weight: 500;
}
.sub-title {
color: #999;
margin-top: 16px;
}
.content {
display: flex;
align-items: center;
margin-top: 8px;
.share-url {
width: 212px;
overflow: hidden;
height: 28px;
line-height: 28px;
border-radius: 4px 0 0 4px;
padding-left: 12px;
white-space: nowrap;
color: #999999;
background: #EFEFEF;
}
.ant-btn {
margin-left: -2px;
}
}
.url-content{
position:relative;
&:after{
content:'';
width: 12px;
height: 22px;
background: #EFEFEF;
position:absolute;
right:71px;
}
}
.share-poster {
margin-bottom: 40px;
.content {
color:rgba(82, 137, 250, 1);
cursor: pointer;
}
}
}
}
}
\ No newline at end of file
import React from 'react';
import {Table, Modal,Input,message} from 'antd';
import { PageControl } from "@/components";
import PlanService from '@/domains/plan-domain/planService'
import User from '@/common/js/user'
import './UnbundEmployeeModal.less';
import _ from "underscore";
const { Search } = Input;
const UserRole = {
StoreManager: {
text: "店铺管理员"
},
CloudManager: {
text:"管理员"
},
CloudOperator: {
text:'运营师'
},
Cloud_Lecture: {
text:"讲师"
},
};
class UnbundEmployeeModal extends React.Component {
constructor(props) {
super(props);
this.state = {
dataSource:[],
selectOperatorList:[]
};
}
componentDidMount() {
this.handleFetchDataList();
}
// 获取绑定员工列表
handleFetchDataList = () => {
const params ={
planId: getParameterByName("id"),
storeCustomerId: this.props.storeCustomerId,
storeId:this.props.storeId
}
PlanService.getPlanCustomerAboutUser(params).then((res) => {
const { result = {} } = res ;
this.setState({
dataSource: result,
});
});
}
// 请求表头
parseColumns = () => {
const columns = [
{
title: '员工',
key: 'storeUserName',
dataIndex: 'storeUserName',
},
{
title: '角色',
key: 'role',
dataIndex: 'role',
render:(val,record)=>{
return <span>{UserRole[val].text}</span>
}
},
{
title: '手机号',
key: 'storeUserPhone',
dataIndex: 'storeUserPhone'
}
];
return columns;
}
selectOperator = (record,selected) =>{
const {selectOperatorList} = this.state;
let _list = [];
if (selected || !_.find(selectOperatorList, (item) => item.storeUserId == record.storeUserId)) {
_list = _.uniq(selectOperatorList.concat([record]), false, (item) => item.storeUserId);
} else {
_list = _.reject(selectOperatorList, (item) => item.storeUserId === record.storeUserId);
}
this.setState({selectOperatorList:_list});
}
confirmUnbund = ()=>{
const { selectOperatorList } = this.state;
const params = {
planId:getParameterByName("id"),
removeUserIds:_.pluck(selectOperatorList, 'storeUserId'),
storeCustomerId:this.props.storeCustomerId,
storeId:User.getStoreId(),
storeUserId:User.getStoreUserId()
}
PlanService.removePlanCustomer(params).then((res) => {
message.success('解绑成功');
this.props.onConfirm();
});
}
render() {
const { size,dataSource,totalCount,query,selectOperatorList} = this.state;
const { visible } = this.props;
return (
<Modal
title="选择需解绑的员工"
onCancel={this.props.onClose}
maskClosable={false}
visible={visible}
className="select-operator-modal"
closable={true}
width={800}
onOk={() => {this.confirmUnbund()}}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div>
<Table
rowKey={record => record.storeUserId}
dataSource={dataSource}
columns={this.parseColumns()}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectOperatorList, 'storeUserId'),
onSelect: (record, selected) => {
this.selectOperator(record, selected)
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectOperatorList.concat(changeRows), false, (item) => item.storeUserId);
} else {
_list = _.reject(selectOperatorList, (item) => _.find(changeRows, (data) => data.id === item.storeUserId));
}
this.setState({selectOperatorList:_list});
}
}}
/>
</div>
</Modal>
)
}
}
export default UnbundEmployeeModal;
\ No newline at end of file
import React from 'react';
import {Table, Modal,Input} from 'antd';
import { PageControl } from "@/components";
import PlanService from '@/domains/plan-domain/planService'
import User from '@/common/js/user'
import './UserLearnDetailModal.less';
import _ from "underscore";
const { Search } = Input;
const defaultCover = 'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png';
const CourseType = {
LIVE: {
text: "直播课"
},
VOICE : {
text:"视频课"
},
RECORD : {
text:'录播课'
}
};
const courseStateShow = {
UN_START: {
title: "待开播",
},
STARTING: {
title: "直播中",
},
FINISH: {
title: "回放",
},
EXPIRED: {
title: "未成功开课",
},
};
class UserLearnDetailModal extends React.Component {
constructor(props) {
super(props);
this.state = {
planDataSource:[],
taskDataSource:[],
taskSize:10,
taskQuery: {
current: 1,
},
taskTotalCount:0,
courseDataSource:[],
storeCustomerName:'',
storeCustomerPhone:''
};
}
componentDidMount() {
this.getPlanCustomerDetail();
}
getPlanCustomerDetail = ()=>{
PlanService.getPlanCustomerDetail({
planId:getParameterByName("id"),
storeCustomerId:this.props.storeCustomerId,
storeId:User.getStoreId()
}).then((res) => {
const {
storeCustomerName,
storeCustomerPhone,
planName,
learnFinishPercentage,
taskCustomerVOList,
courseMediaVOS,
}=res.result;
let coverUrl;
courseMediaVOS.map((item) => {
if(item.contentType === "COVER"){
coverUrl = item.mediaUrl;
}
return item;
})
const planDataSource = [{
planName,
learnFinishPercentage,
coverUrl:coverUrl || defaultCover
}]
this.setState({
storeCustomerName,
storeCustomerPhone,
planDataSource,
taskDataSource:taskCustomerVOList
})
})
}
parsePlanColumns = () => {
const columns = [
{
title: '培训计划名称',
key: 'planInfo',
dataIndex: 'planInfo',
render: (val, record) => {
return (
<div className="plan-instro">
<div className="img-con">
<img src={record.coverUrl}/>
</div>
<div className="plan-name">{record.planName}</div>
</div>
)
}
},
{
title: '学习进度',
key: 'learnFinishPercentage',
dataIndex: 'learnFinishPercentage',
width:167,
render: (val, record) => {
return (
<div className="plan-learn-percentage">
{val}%
</div>
)
}
}
];
return columns;
}
parseTaskColumns = () => {
const columns = [
{
title: '培训任务',
key: 'taskName',
dataIndex: 'taskName',
render: (val, record,index) => {
return (
<div className="taskName">
{index + 1}.{record.taskName}
</div>
)
}
},
{
title: '学习进度',
key: 'learnFinishPercentage',
dataIndex: 'learnFinishPercentage',
width:167,
render: (val, record) => {
return (
<div className="task-learn-percentage">
{ val === 100 ?<span>已完成</span>:<span>{val}%</span>}
</div>
)
}
}
];
return columns;
}
parseCoursecolumns = (parentIndex) => {
const columns = [
{
title: '课程',
key: 'courseName',
dataIndex: 'courseName',
render: (val, record,index) => {
return (
<div className="course-info">
<span className="course-type">{CourseType[record.courseType].text}</span>
<span className="course-name">{parentIndex + 1}.{index + 1}{record.courseName}</span>
{record.courseState === "EXPIRED" &&
<span className="icon iconfont tip">&#xe834;</span>
}
{ record.courseType==="LIVE" &&
<span className="course-state">{courseStateShow[record.courseState].title}</span>
}
</div>
)
}
},
{
title: '学习进度',
key: 'learnFinishPercentage',
dataIndex: 'learnFinishPercentage',
width:152,
render: (val, record) => {
return (
<div className="course-learn-percentage">
{ record.learnState === "FINISH" ?<span>已完成</span>:<span>{val}%</span>}
</div>
)
}
}
];
return columns;
}
render() {
const {storeCustomerName,storeCustomerPhone,planDataSource,taskDataSource,taskQuery,taskTotalCount} = this.state;
const { visible } = this.props;
return (
<Modal
title="用户学习详情"
onCancel={this.props.onClose}
maskClosable={false}
visible={visible}
className="user-Learn-modal"
closable={true}
width={800}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
>
<div className="customer-info">
<span className="customer-name">
<span>用户:</span>
<span>{storeCustomerName}</span>
</span>
<span className="customer-phone">
<span>手机号:</span>
<span>{storeCustomerPhone}</span>
</span>
</div>
<div>
<Table
dataSource={planDataSource}
columns={this.parsePlanColumns()}
pagination={false}
bordered
className="plan-table"
/>
</div>
<div>
<Table
rowKey={(record) => record.taskId}
className="task-table"
dataSource={taskDataSource}
columns={this.parseTaskColumns()}
pagination={false}
expandedRowRender={(record,index) => {
if(!record.courseVOList){
return
}
if (record.courseVOList.length !== 0 ){
return <div>
<Table
pagination={false}
dataSource={record.courseVOList}
columns={this.parseCoursecolumns(index)}
className="child-table"
/>
</div>
}
}}
rowClassName={(record,index)=>{if(index%2===0){return 'odd-row'}else{ return 'even-row'}}}
/>
</div>
</Modal>
)
}
}
export default UserLearnDetailModal;
\ No newline at end of file
.user-Learn-modal{
.customer-info{
margin-bottom:16px;
.customer-name{
font-size:14px;
color:#333;
margin-right:32px;
}
.customer-phone{
font-size:14px;
color:#333;
}
}
.plan-table{
margin-bottom:8px;
.plan-instro{
display: flex;
align-items: center;
.img-con{
margin-right:8px;
img{
width: 97px;
height: 55px;
display: inline-block;
border-radius:4px;
}
}
.plan-name{
color:#666666;
font-size:14px;
}
.plan-learn-percentage{
color:#666666;
font-size:14px;
}
}
}
.task-table{
.taskName{
color:#666666;
font-size:14px;
}
.task-learn-percentage{
color:#666666;
font-size:14px;
}
.course-info{
margin-left:57px;
.course-type{
font-size:11px;
color:#666666;
padding:1px 8px;
border: 1px solid #999999;
margin-right:4px;
border-radius: 2px;
}
.course-name{
color:#666666;
font-size:14px;
margin-right:8px;
}
.tip{
font-size:14px;
color:#FF4F4F;
margin-right:2px;
}
.course-state{
color:#999;
font-size:14px;
}
}
.ant-table-content{
border:1px solid #e8e8e8;
tr{
td{
border:none;
}
.child-table{
.ant-table-content{
border:none;
thead{
display:none;
}
tbody tr td{
border-bottom:none;
}
}
}
}
.odd-row{
background:transparent;
td{
background: #FFF;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FFF;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
}
}
}
.even-row{
background:transparent;
td{
background: #FAFAFA;
}
& + .ant-table-expanded-row{
background:transparent;
td{
background: #FAFAFA;
}
}
&:hover{
& + .ant-table-expanded-row{
background:transparent;
td{
background: #F3f6fa !important;
}
}
}
}
}
}
}
\ No newline at end of file
import React from 'react';
import {Table, Modal,Input,message,Button,Tooltip} from 'antd';
import { PageControl } from "@/components";
import CourseService from "@/domains/course-domain/CourseService";
import User from '@/common/js/user';
import { Tabs } from 'antd';
import './relatedCourseModal.less';
import _ from "underscore";
import dealTimeDuration from "../../course-manage/utils/dealTimeDuration";
const { Search } = Input;
const { TabPane } = Tabs;
const courseStateShow = {
UN_START: {
code: 1,
title: "待开课",
color: "#FFB714",
},
STARTING: {
code: 2,
title: "上课中",
color: "#238FFF",
},
FINISH: {
code: 3,
title: "已完成",
color: "#3BBDAA",
},
EXPIRED: {
code: 4,
title: "未成功开课",
color: "#999",
},
};
class SelectOperatorModal extends React.Component {
constructor(props) {
super(props);
this.state = {
liveDataSource:[],
liveSize:10,
liveQuery: {
current: 1,
},
liveTotalCount:0,
selectLive:[],//弹窗内已选择的直播课程
currentCourseListData:[],
currentLiveCourseListData:[], //页面中已关联的直播课程
videoDataSource:[],
videoSize:10,
videoQuery: {
current: 1,
},
videoTotalCount:0,
selectVideo:[], //弹窗内已选择的视频课程
currentVideoCourseListData:[], //页面中已关联的视频课程
activeKey:'video',
currentTaskCourseData:this.props.data[this.props.selectedTaskIndex].courseList || []
}
}
componentDidMount() {
this.handleFetchLiveDataList();
this.handleFetchVideoDataList();
}
// 获取直播课列表
handleFetchLiveDataList = () => {
const {liveQuery,liveSize} = this.state;
const { selectedTaskIndex } =this.props;
const _data = [...this.props.data];
let currentLiveCourseListData = [];
_data.map((item,index) => {
item.courseList.map((childItem,childIndex)=>{
if(childItem.courseType ==="LIVE"){
currentLiveCourseListData.push(childItem.courseId)
}
return childItem
})
return item
});
const params ={
...liveQuery,
size:liveSize,
excludeCourseIdList:currentLiveCourseListData
}
CourseService.getLiveCloudCourseBasePage(params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
liveDataSource: records,
liveTotalCount: Number(total),
currentLiveCourseListData
});
});
}
// 获取视频课列表
handleFetchVideoDataList = () => {
const {videoQuery,videoSize,videoTotalCount} = this.state;
const { selectedTaskIndex } =this.props;
const _data = [...this.props.data];
let currentVideoCourseListData = [];
_data.map((item,index) => {
item.courseList.map((childItem,childIndex)=>{
if(childItem.courseType ==="VOICE"){
currentVideoCourseListData.push(childItem.courseId)
}
return childItem
})
return item
});
const params ={
...videoQuery,
size:videoSize,
excludeCourseIdList:currentVideoCourseListData
}
CourseService.videoScheduleBasePage(params).then((res) => {
const { result = {} } = res ;
const { records = [], total = 0 } = result;
this.setState({
videoDataSource: records,
videoTotalCount: Number(total),
currentVideoCourseListData
});
});
}
handleChangVideoCourseName = (value)=>{
const { videoQuery } = this.state;
videoQuery.courseName = value;
videoQuery.current = 1;
this.setState({
videoQuery
})
}
handleChangLiveCourseName = (value)=>{
const { liveQuery } = this.state;
liveQuery.courseName = value;
liveQuery.current = 1;
this.setState({
liveQuery
})
}
onShowLiveSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
liveSize:size
},()=>{this.handleFetchLiveDataList()})
}
onShowVideoSizeChange = (current, size) => {
if (current == size) {
return
}
this.setState({
videoSize:size
},()=>{this.handleFetchLiveDataList()})
}
// 请求表头
parseLiveColumns = () => {
const columns = [
{
title: <span><span>课程信息</span><Tooltip title="以下为该培训计划暂未关联的课程。 已关联的课程不支持重复选择,因此不显示。"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip></span>,
key: 'course',
dataIndex: 'course',
width:'45%',
render: (val, record) => {
let hasCover = false;
return (
<div className="course-info">
{
record.courseMediaVOS.map((item,index)=>{
if( item.contentType === "COVER"){
hasCover = true;
return <img className="course-cover" src={item.mediaUrl}/>
}
})
}
{ !hasCover &&
<img className="course-cover" src={'https://image.xiaomaiketang.com/xm/YNfi45JwFA.png'} />
}
<div>
<div className="course-name">{record.courseName}</div>
<span className="course-status" style={{color:courseStateShow[record.courseState].color,border:`1px solid ${courseStateShow[record.courseState].color}`}}>{courseStateShow[record.courseState].title}</span>
</div>
</div>
)
}
},
{
title: '上课时间',
key: 'courseTime',
dataIndex: 'courseTime',
width:'40%',
render: (val, record) => {
return (
<span>{formatDate('YYYY-MM-DD H:i', record.startTime)}</span>
)
}
},
{
title: '店铺展示',
key: 'shelfState',
dataIndex: 'shelfState',
width:'15%',
render: (val, record) => {
return (
<span>
{record.shelfState==="YES"?
<span>开启</span>
:
<span>关闭</span>
}
</span>
)
}
},
];
return columns;
}
// 请求表头
parseVideoColumns = () => {
const columns = [
{
title: <span><span>课程信息</span><Tooltip title="以下为该培训计划暂未关联的课程。 已关联的课程不支持重复选择,因此不显示。"><i className="icon iconfont" style={{ marginLeft: '5px',cursor:'pointer',color:'#bfbfbf',fontSize:'14px'}}>&#xe61d;</i></Tooltip></span>,
key: 'course',
dataIndex: 'course',
width:'60%',
render: (val, record) => {
const { coverUrl, scheduleVideoUrl } = record;
return (
<div className="course-info">
{/* 上传了封面的话就用上传的封面, 没有的话就取视频的第一帧 */}
<img className="course-cover" src={coverUrl || `${scheduleVideoUrl}?x-oss-process=video/snapshot,t_0,m_fast`}/>
<div className="course-name">{record.courseName}</div>
</div>
)
}
},
{
title: '课程时长',
key: 'courseTime',
dataIndex: 'courseTime',
width:'20%',
render: (val, record) => {
return (
<span className="course-status">{dealTimeDuration(record.videoDuration)}</span>
)
}
},
{
title: '店铺展示',
key: 'shelfState',
dataIndex: 'shelfState',
width:'20%',
render: (val, record) => {
return (
<span>
{record.shelfState==="YES"?
<span>开启</span>
:
<span>关闭</span>
}
</span>
)
}
}
];
return columns;
}
selectLiveList = (record,selected) =>{
const {selectVideo,currentTaskCourseData,selectLive} = this.state;
let _list = [];
if (selected || !_.find(selectLive, (item) => item.liveCourseId == record.liveCourseId)) {
_list = _.uniq(selectLive.concat([record]), false, (item) => item.liveCourseId);
} else {
_list = _.reject(selectLive, (item) => item.liveCourseId === record.liveCourseId);
}
if(_list.length + currentTaskCourseData.length + selectVideo.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({selectLive:_list});
}
selectVideoList = (record,selected) =>{
const {selectVideo,currentTaskCourseData,selectLive} = this.state;
let _list = [];
if (selected || !_.find(selectVideo, (item) => item.id == record.id)) {
_list = _.uniq(selectVideo.concat([record]), false, (item) => item.id);
} else {
_list = _.reject(selectVideo, (item) => item.id === record.id);
}
if(_list.length + currentTaskCourseData.length + selectLive.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
return;
}
this.setState({selectVideo:_list});
}
clearSelectCourse = ()=>{
this.setState({
selectLive:[],
selectVideo:[]
})
}
handleSelectVideo = (selectVideo)=>{
return selectVideo.map((item,index)=>{
let _item = {};
_item.courseId = item.id;
_item.courseType = "VOICE";
_item.courseName = item.courseName;
return _item;
})
}
handleSelectLive = (selectLive)=>{
return selectLive.map((item,index)=>{
let _item = {};
_item.courseId = item.liveCourseId;
_item.courseType = "LIVE";
_item.courseName = item.courseName;
_item.courseState = item.courseState;
return _item;
})
}
render() {
const { visible } = this.props;
const { liveDataSource,liveSize,liveQuery,liveTotalCount,selectLive,videoDataSource,videoSize,videoQuery,videoTotalCount,selectVideo,currentTaskCourseData,activeKey} = this.state;
return (
<Modal
title="关联课程"
onCancel={this.props.onClose}
maskClosable={false}
visible={visible}
className="related-course-modal"
closable={true}
width={800}
onOk={() => this.props.onSelect([...this.handleSelectVideo(selectVideo),...this.handleSelectLive(selectLive)]) }
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
footer={[
activeKey ==='live'?
<a target='_blank' className="link-create-course" href="https://dev.xiaomai5.com/xiaomai-cloud-class-web/index.html#/create-live-course?type=add">没有找到需要的直播课?<span>去创建</span></a>
:
<a target='_blank' className="link-create-course" href="https://dev.xiaomai5.com/xiaomai-cloud-class-web/index.html#/create-video-course?type=add">没有找到需要的视频课?<span>去创建</span></a>
,
<Button
onClick={() => {
this.props.onClose()
}}
>
取消
</Button>,
<Button type="primary" onClick={() => this.props.onSelect([...this.handleSelectVideo(selectVideo),...this.handleSelectLive(selectLive)]) }>
确定
</Button>
]}
>
<div>
<Tabs defaultActiveKey="video" onChange={(activeKey)=>{this.setState({activeKey:activeKey})}}>
<TabPane tab="视频课" key="video">
<div className="search-container">
<Search enterButton={<span className="icon iconfont">&#xe832;</span>} placeholder="搜索课程名称" style={{ width: 200 }} onChange={(e) => { this.handleChangVideoCourseName(e.target.value)}} onSearch={ () => { this.handleFetchVideoDataList()}}/>
</div>
<div className="select-area">
<div className="select-box">
<div>
<span className="icon iconfont tip-icon">&#xe61d;</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length}</span>
</div>
<div>
<span className="clear-btn" onClick={this.clearSelectCourse}>清空</span>
</div>
</div>
<div className="related-box">该任务已关联{currentTaskCourseData.length}个课程,可继续选择{20- currentTaskCourseData.length }</div>
</div>
<div>
<Table
rowKey={record => record.id}
dataSource={videoDataSource}
columns={this.parseVideoColumns()}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectVideo, 'id'),
onSelect: (record, selected) => {
this.selectVideoList(record, selected);
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectVideo.concat(changeRows), false, (item) => item.id);
} else {
_list = _.reject(selectVideo, (item) => _.find(changeRows, (data) => data.id === item.id));
}
if(_list.length + currentTaskCourseData.length + selectLive.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
console.log('1111');
const extraLength = (_list.length + currentTaskCourseData.length + selectLive.length) -20;
_list.splice(_list.length - extraLength,extraLength);
}
this.setState({selectVideo:_list});
},
}}
/>
{videoDataSource.length >0 &&
<div className="box-footer">
<PageControl
current={videoQuery.current - 1}
pageSize={videoSize}
size="small"
total={videoTotalCount}
toPage={(page) => {
const _query = {...videoQuery, current: page + 1};
this.setState({
videoQuery:_query
},()=>{ this.handleFetchVideoDataList()})
}}
onShowSizeChange={this.onShowVideoSizeChange}
/>
</div>
}
</div>
</TabPane>
<TabPane tab="直播课" key="live">
<div className="search-container">
<Search enterButton={<span className="icon iconfont">&#xe832;</span>} placeholder="搜索课程名称" style={{ width: 200 }} onChange={(e) => { this.handleChangLiveCourseName(e.target.value)}} onSearch={ () => { this.handleFetchLiveDataList()}} />
</div>
<div className="select-area">
<div className="select-box">
<div>
<span className="icon iconfont tip-icon">&#xe61d;</span>
<span className="select-num">已选择{selectVideo.length + selectLive.length}</span>
</div>
<div>
<span className="clear-btn" onClick={this.clearSelectCourse}>清空</span>
</div>
</div>
<div className="related-box">该任务已关联{currentTaskCourseData.length}个课程,可继续选择{20- currentTaskCourseData.length }</div>
</div>
<div>
<Table
rowKey={record => record.liveCourseId}
dataSource={liveDataSource}
columns={this.parseLiveColumns()}
pagination={false}
bordered
rowSelection={{
type: 'checkbox',
selectedRowKeys: _.pluck(selectLive, 'liveCourseId'),
onSelect: (record, selected) => {
this.selectLiveList(record, selected)
},
onSelectAll: (selected, _selectedRows, changeRows) => {
let _list = [];
if (selected) {
_list = _.uniq(selectLive.concat(changeRows), false, (item) => item.liveCourseId);
} else {
_list = _.reject(selectLive, (item) => _.find(changeRows, (data) => data.liveCourseId === item.liveCourseId));
}
if(_list.length + currentTaskCourseData.length + selectVideo.length> 20){
message.warning('无法继续选择,一个任务最多关联20个课程');
const extraLength = (_list.length + currentTaskCourseData.length + selectVideo.length) -20;
_list.splice(_list.length - extraLength,extraLength);
console.log('_list',_list);
}
this.setState({selectLive:_list});
},
}}
/>
{liveDataSource.length >0 &&
<div className="box-footer">
<PageControl
current={liveQuery.current - 1}
pageSize={liveSize}
size="small"
total={liveTotalCount}
toPage={(page) => {
const _query = {...liveQuery, current: page + 1};
this.setState({
liveQuery:_query
},()=>{ this.handleFetchLiveDataList()})
}}
onShowSizeChange={this.onShowLiveSizeChange}
/>
</div>
}
</div>
</TabPane>
</Tabs>
</div>
</Modal>
)
}
}
export default SelectOperatorModal;
\ No newline at end of file
.related-course-modal{
.ant-tabs-top > .ant-tabs-nav::before{
border-bottom: 0px;
}
.ant-tabs-nav-list{
margin:0 auto;
}
.ant-tabs-nav .ant-tabs-tab{
padding:6px 12px !important;
margin:0;
border: 1px solid #E8E8E8;
font-size:14px !important;
color:#999;
&:nth-child(1){
border-radius: 4px 0px 0px 4px;
}
&:nth-child(2){
border-radius: 0px 4px 4px 0px;
}
}
.ant-tabs-nav .ant-tabs-tab-active{
border: 1px solid #FFB714;
color:#FFB714;
}
.ant-tabs-top .ant-tabs-ink-bar-animated:after{
height:0;
}
.link-create-course{
color:#666666;
font-size:14px;
width:638px;
text-align:left;
display:inline-block;
span{
color:#5289FA;
}
}
.search-container{
margin-bottom:16px;
}
.select-area{
margin-bottom:12px;
display:flex;
justify-content:space-between;
.select-box{
display:inline-box;
width: 186px;
background: #FFF4DD;
border-radius: 4px;
padding:6px 16px;
margin-right:8px;
display: flex;
justify-content: space-between;
.tip-icon{
color:#FF9D14;
font-size:14px;
margin-right:4px;
}
.select-num{
color:#666666;
font-size:14px;
}
.clear-btn{
text-align:right;
color:#5289FA;
font-size:14px;
}
}
.related-box{
padding:6px 16px;
background: #FFF4DD;
border-radius: 4px;
flex:1;
color:#666666;
font-size:14px;
}
}
.course-info{
display: flex;
align-items: center;
.course-cover{
width: 97px;
height: 55px;
display: inline-block;
border-radius:4px;
margin-right:8px;
}
.course-name{
font-size:14px;
color:#666;
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
width:238px;
}
.course-status {
font-size:12px;
line-height:18px;
display:inline-block;
border-radius:2px;
padding:0 8px;
margin-top:8px;
}
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2019-07-10 10:30:49
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-02-02 16:42:04
* @LastEditTime: 2021-03-09 18:23:29
* @Description:
*/
import React, { useContext, useEffect, useState } from 'react';
......@@ -16,6 +16,7 @@ import User from '@/common/js/user';
import BaseService from "@/domains/basic-domain/baseService";
import { XMContext } from '@/store/context';
import { setStoreGroupPermission, setStorePermission, setStoreGroupList, setStoreList } from '@/store/actions/index';
import Bus from '@/core/tbus';
declare var window: any;
const App: React.FC = (props: any) => {
......@@ -47,9 +48,9 @@ const App: React.FC = (props: any) => {
User.setStoreId(id);
User.setStoreUserId(storeUserId);
User.setStoreName(storeName);
Bus.trigger('storeNameChange',storeName);
User.setUserRole(userRole);
User.setStoreType(storeType);
ctx.dispatch(setStoreGroupList(storeGroupVOS))
ctx.dispatch(setStoreList(storeVOS));
serStoreUserId(storeUserId)
......
......@@ -2,7 +2,7 @@
* @Author: 吴文洁
* @Date: 2019-09-10 18:26:03
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-01-28 15:24:43
* @LastEditTime: 2021-03-09 19:05:49
* @Description:
*/
import React, { useContext, useEffect, useState } from "react";
......@@ -15,17 +15,22 @@ import { XMContext } from "@/store/context";
import logoImg from "@/common/images/logo.png";
import CourseService from "@/domains/course-domain/CourseService";
import qrcode from "@/libs/qrcode/qrcode.js";
import Bus from '@/core/tbus';
const baseImg = "https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png";
const { confirm } = Modal;
function Header(props) {
const { menuType, handleMenuType } = props;
const [storeName,setStoreName] = useState(User.getStoreName())
const ctx = useContext(XMContext);
const htmlUrl = `${LIVE_SHARE}store/index?id=${User.getStoreId()}&userId=${User.getUserId()}&from=work_weixin`;
useEffect(() => {
htmlUrl && handleConvertShortUrl();
Bus.bind('storeNameChange', (value) => {
setStoreName(value);
});
}, []);
function userMenu() {
......@@ -135,7 +140,7 @@ function Header(props) {
)}
<div className="message-help">
<div className="store-related">
<div className="store-name">{User.getStoreName()}</div>
<div className="store-name">{storeName}</div>
<div className="line"></div>
<div className="link-to-store">
<div className="link">
......
......@@ -2,7 +2,7 @@
* @Author: wufan
* @Date: 2020-12-26 11:51:14
* @LastEditors: wufan
* @LastEditTime: 2020-12-26 15:04:26
* @LastEditTime: 2021-03-09 15:18:09
* @Description: 登录后跳转承载页面
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -39,6 +39,11 @@ function SwitchRoute(props: SwitchProps) {
pathname: `/home`,
});
break;
case "CloudOperator":
window.RCHistory.replace({
pathname: `/home`,
});
break;
}
}, [User.getUserRole()]);
return <div></div>
......
/*
* @Author: wufan
* @Date: 2020-11-27 16:21:49
* @LastEditors: wufan
* @LastEditTime: 2021-01-25 21:14:48
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-09 14:21:35
* @Description: Description
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
......@@ -40,11 +40,10 @@ interface AddEmployeeModalProps {
onClose: () => void;
isWorkWechat: boolean;
}
function AddEmployeeModal(props: AddEmployeeModalProps) {
const [nickName, setName] = useState("");
const [phone, setPhone] = useState("");
const [role, setRole] = useState("CloudLecturer");
const [role, setRole] = useState("CloudOperator");
const [avatar, setAvatar] = useState(
"https://image.xiaomaiketang.com/xm/rJeQaZxtc7.png"
);
......@@ -64,16 +63,15 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
const [form] = Form.useForm();
useEffect(() => {
if (props.choosedItem.nickName) {
console.log("props.choosedItem", props.choosedItem);
setName(props.choosedItem.nickName);
console.log('choosedItem',props.choosedItem);
props.choosedItem.phone && setPhone(props.choosedItem.phone);
props.choosedItem.role && setRole(props.choosedItem.role[0]);
props.choosedItem.avatar && setAvatar(props.choosedItem.avatar);
const _role =
props.choosedItem.role[0] === "CloudLecturer"
? "CloudLecturer"
: "CloudManager";
const _role = props.choosedItem.role[0];
form.setFieldsValue({
nickName: props.choosedItem.nickName,
role: _role,
......@@ -190,6 +188,7 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
roleCodes: [role],
avatar,
storeUserId: storeUserId,
storeId:User.getStoreId()
};
console.log("params", params);
......@@ -284,8 +283,13 @@ function AddEmployeeModal(props: AddEmployeeModalProps) {
}}
className="mt5"
>
<Radio value={"CloudLecturer"} className="mt-4"
>
<Radio value={"CloudOperator"} className="mt-4">
<span style={{ color: "#333" }}>运营师</span>
<p className="radio-tip">
仅可查看/转发培训计划内容,并查看其负责的用户学习进度
</p>
</Radio>
<Radio value={"CloudLecturer"} className="mt-4">
<span style={{ color: "#333" }}>普通讲师</span>
<p className="radio-tip">
仅可查看/使用与自己相关的文件和课表,并进行上课
......
import React from 'react';
import { withRouter } from "react-router-dom";
import { Form, Input, Button, Checkbox ,Select,Modal,message} from 'antd';
import {industryList,childIndustryList} from '@/domains/store-domain/constants'
import SelectPrepareFileModal from '@/modules/prepare-lesson/modal/SelectPrepareFileModal';
import Upload from '@/core/upload';
import PhotoClip from 'photoclip';
import StoreService from "@/domains/store-domain/storeService";
import User from "@/common/js/user";
import Bus from '@/core/tbus';
import "./StoreInfo.less";
let cutFlag = false;
class StoreInfo extends React.Component {
formRef = React.createRef();
constructor(props) {
super(props);
this.state = {
storeName:'',
storeFullName:'',
subjectType:'',
corpIndustry:'',
corpSubIndustry:'',
logo:'',
showSelectFileModal:false,
cutImageBlob: null,
}
}
componentWillMount(){
this.getStoreDetail()
}
getStoreDetail = ()=>{
const params= {
storeId:User.getStoreId()
}
StoreService.getStoreDetail(params).then((res) => {
const { result = { } } = res;
const {
storeName,
storeFullName,
subjectType,
corpIndustry,
corpSubIndustry,
logo
} = result;
this.formRef.current.setFieldsValue({
storeName:storeName
});
this.formRef.current.setFieldsValue({
storeFullName:storeFullName
});
this.formRef.current.setFieldsValue({
subjectType:subjectType
});
this.formRef.current.setFieldsValue({
corpIndustry:corpIndustry
});
this.formRef.current.setFieldsValue({
corpSubIndustry:corpSubIndustry
});
this.setState({
storeName,
storeFullName,
subjectType,
corpIndustry,
corpSubIndustry,
logo
})
});
}
onChangeFiledValue=(filed,value)=>{
this.setState({
[filed]:value
})
}
handleSelectCover = (file)=> {
this.uploadImage(file);
}
//上传图片
uploadImage = (imageFile) => {
const { folderName } = imageFile;
const fileName = window.random_string(16) + folderName.slice(folderName.lastIndexOf("."));
const self = this;
this.setState(
{
visible: true,
},
() => {
setTimeout(() => {
const okBtnDom = document.querySelector("#headPicModal");
const options = {
size: [500, 282],
ok: okBtnDom,
maxZoom: 3,
style: {
jpgFillColor: "transparent",
},
done: function (dataUrl) {
clearTimeout(self.timer);
self.timer = setTimeout(() => {
if ((self.state.rotate != this.rotate()) || (self.state.scale != this.scale())) {
console.log(this.scale(), 'scale')
const _dataUrl = this.clip()
const cutImageBlob = self.convertBase64UrlToBlob(_dataUrl);
self.setState({
cutImageBlob,
dataUrl: _dataUrl,
rotate: this.rotate(),
scale: this.scale()
})
}
}, 500)
const cutImageBlob = self.convertBase64UrlToBlob(dataUrl);
self.setState({
cutImageBlob,
dataUrl
})
setTimeout(() => {
cutFlag = false;
}, 2000);
},
fail: (failInfo) => {
message.error("图片上传失败了,请重新上传");
},
loadComplete: function (img) {
setTimeout(() => {
const _dataUrl = this.clip()
self.setState({
dataUrl: _dataUrl,
hasImgReady: true
})
}, 100)
},
};
const imgUrl = `${imageFile.ossUrl}?${new Date().getTime()}`
if (!this.state.photoclip) {
const _photoclip = new PhotoClip("#headPicModal", options);
_photoclip.load(imgUrl);
this.setState({
photoclip: _photoclip,
});
} else {
this.state.photoclip.clear();
this.state.photoclip.load(imgUrl);
}
}, 200);
}
);
};
//获取resourceId
getSignature = (blob, fileName) => {
Upload.uploadBlobToOSS(blob, 'cover' + (new Date()).valueOf(),null,'signInfo').then((signInfo) => {
this.setState({
coverClicpPath:signInfo.fileUrl,
coverId:signInfo.resourceId,
visible: false
},()=>this.updateCover())
});
};
updateCover = () =>{
const {coverClicpPath,coverId} = this.state
this.setState({
showSelectFileModal: false
})
this.setState({
logo:coverClicpPath
})
}
// base64转换成blob
convertBase64UrlToBlob = (urlData) => {
const bytes = window.atob(urlData.split(",")[1]);
const ab = new ArrayBuffer(bytes.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < bytes.length; i++) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ab], { type: "image/png" });
};
updateInfo=()=>{
const {storeName,storeFullName,logo,subjectType,corpIndustry,corpSubIndustry} = this.state;
if(!storeName){
message.warning('请输入店铺简称');
return;
}
if(!storeFullName){
message.warning('请输入店铺名称');
return;
}
if(!subjectType){
message.warning('请选择店铺类型');
return;
}
if(!subjectType){
message.warning('店铺所属行业');
return;
}
const params= {
storeName,
storeFullName,
logo,
subjectType,
corpIndustry,
corpSubIndustry,
storeId:User.getStoreId()
}
StoreService.updateStoreMessage(params).then((res) => {
User.setStoreName(storeName);
Bus.trigger('storeNameChange',storeName);
message.success('保存成功');
});
}
render() {
const {storeName,storeFullName,subjectType,corpIndustry,corpSubIndustry,showSelectFileModal,visible,hasImgReady,logo,cutImageBlob } = this.state;
return (
<div className="page store-info-page">
<div className="content-header">店铺基本信息</div>
<div className="box">
<div className="store-info-header">店铺基本信息</div>
<div className="store-info-page-form">
<Form ref={this.formRef}>
<Form.Item
label="店铺简称"
name="storeName"
rules={[{ required: true}]}
>
<Input value={storeName} style={{ width: '300px' }} placeholder="请输入店铺简称" onChange={(e)=>this.onChangeFiledValue("storeName",e.target.value)} maxLength={20}/>
</Form.Item>
<Form.Item
label="店铺名称"
name="storeFullName"
rules={[{ required: true}]}
>
<Input value={storeFullName} style={{ width: '300px' }} placeholder="请输入店铺名称" onChange={(e)=>this.onChangeFiledValue("storeFullName",e.target.value)} maxLength={30}/>
</Form.Item>
<Form.Item
label="店铺logo"
>
<div className="logo-con">
<div className="logo-img-con">
{ logo ? <img src={logo} className="logo-img"/> : <div className="logo-box"><span className="text">Logo</span></div>}
</div>
<div className="operate-con">
<div><span onClick={() => {this.setState({ showSelectFileModal:true })}} className="upload-btn">上传</span></div>
<div className="tip">建议尺寸702*180px。最大2M,支持jpg、jpeg和png。</div>
</div>
</div>
</Form.Item>
<Form.Item
label="店铺类型"
name="subjectType"
rules={[{ required: true }]}
>
<Select
placeholder="Select a option and change input text above"
allowClear
style={{ width: '300px' }}
placeholder="请选择店铺类型"
value={subjectType}
onChange={(value)=>this.onChangeFiledValue("subjectType",value)}
>
<Option value="COMPANY">企业</Option>
<Option value="INSTITUTION">政府以及事业单位</Option>
<Option value="ELSE">其他组织</Option>
<Option value="TEAM ">团队号</Option>
</Select>
</Form.Item>
<Form.Item
label="店铺所属行业"
rules={[{ required: true }]}
name="corpIndustry"
>
<Select
placeholder="请选择店铺所属行业"
style={{ width: '300px' }}
allowClear
value={corpIndustry}
onChange={(value)=>this.onChangeFiledValue("corpIndustry",value)}
>
{industryList.map((item,index)=>{
return <Option value={item}>{item}</Option>
})
}
</Select>
</Form.Item>
<Form.Item
label="店铺所属子行业"
name="corpSubIndustry"
>
<Select
placeholder="请选择店铺所属子行业"
allowClear
style={{ width: '300px' }}
value={corpSubIndustry}
onChange={(value)=>this.onChangeFiledValue("corpSubIndustry",value)}
>
{childIndustryList[corpIndustry] &&
childIndustryList[corpIndustry].map((item,index)=>{
return <Option value={item}>{item}</Option>
})
}
</Select>
</Form.Item>
</Form>
</div>
{showSelectFileModal &&
<SelectPrepareFileModal
key="basic"
operateType="select"
multiple={false}
accept="image/jpeg,image/png,image/jpg"
selectTypeList={['JPG', 'JPEG', 'PNG']}
tooltip='支持文件类型:jpg、jpeg、png'
isOpen={showSelectFileModal}
onClose={() => {
this.setState({
showSelectFileModal:false
})
}}
onSelect={this.handleSelectCover}
/>
}
<Modal
title="设置图片"
width={1080}
visible={visible}
maskClosable={false}
closeIcon={<span className="icon iconfont modal-close-icon">&#xe6ef;</span>}
onCancel={() => {
this.setState({ visible: false });
}}
zIndex={10001}
footer={[
<Button
key="back"
onClick={() => {
this.setState({ visible: false });
}}
>
重新上传
</Button>,
<Button
key="submit"
type="primary"
disabled={!hasImgReady}
onClick={() => {
if (!cutFlag) {
cutFlag = true;
this.refs.hiddenBtn.click();
}
this.getSignature(cutImageBlob);
}}
>
确定
</Button>,
]}
>
<div className="clip-box">
<div
id="headPicModal"
ref="headPicModal"
style={{
width: "500px",
height: "430px",
marginBottom: 0,
}}
></div>
<div id="clipBtn" style={{ display: "none" }} ref="hiddenBtn"></div>
<div className="preview-img">
<div className="title">效果预览</div>
<div id="preview-url-box" style={{width:500,height:282}}>
<img src={this.state.dataUrl} style={{ width: '100%' }} alt="" />
</div>
</div>
</div>
</Modal>
<div><Button type="primary" onClick={this.updateInfo} htmlType="submit" className="submit-btn">更新信息</Button></div>
</div>
</div>
)
}
}
export default withRouter(StoreInfo);
\ No newline at end of file
.store-info-page{
.store-info-header{
font-weight:bold;
color:#333;
font-size:16px;
margin-bottom: 16px;
}
.ant-form-item-label > label{
width:112px;
display:inline-block;
text-align:right;
color:#666666;
font-size:14px;
}
.store-info-page-form{
margin-left:29px;
.logo-con{
display: flex;
.logo-img-con{
width: 258px;
height: 60px;
margin-right:8px;
.logo-box{
width: 258px;
height: 60px;
background: #F7F8F9;
border-radius: 1px;
text-align:center;
.text{
text-align:center;
line-height:60px;
color:#ccc;
font-size:14px;
}
}
.logo-img{
width: 258px;
height: 60px;
}
}
.operate-con{
.upload-btn{
color:#5289FA;
font-size:14px;
margin-bottom:4px;
}
.tip{
color:#999999;
font-size:14px;
}
}
}
}
.submit-btn{
margin-left:142px;
}
}
\ No newline at end of file
/*
* @Author: 吴文洁
* @Date: 2020-04-29 10:26:32
* @LastEditors: wufan
* @LastEditTime: 2021-01-18 21:23:08
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-02 15:56:22
* @Description: 内容线路由配置
*/
import Home from '@/modules/home/Home';
......@@ -17,10 +17,14 @@ import VideoCoursePage from '@/modules/course-manage/video-course'
import GraphicsCoursePage from '@/modules/course-manage/graphics-course'
import AddVideoCoursePage from '@/modules/course-manage/video-course/AddVideoCourse'
import AddGraphicsCoursePage from '@/modules/course-manage/graphics-course/AddGraphicsCourse'
import DataList from '@/modules/course-manage/DataList/DataList';
import ClassBook from '@/modules/resource-disk';
// import DataList from '@/modules/course-manage/DataList/DataList';
// import ClassBook from '@/modules/resource-disk';
import ResourceDisk from '@/modules/resource-disk';
import SwitchRoute from '@/modules/root/SwitchRoute';
import PlanPage from '@/modules/plan-manage/PlanPage';
import AddPlanPage from '@/modules/plan-manage/AddPlan';
import LearningDataPage from '@/modules/plan-manage/LearningData';
import StoreInfoPage from '@/modules/store-manage/StoreInfo';
const mainRoutes = [
{
......@@ -92,7 +96,28 @@ const mainRoutes = [
path: '/switch-route',
component: SwitchRoute,
name: '登录后跳转承载页'
},
{
path:'/plan',
component: PlanPage,
name: '培训计划'
},
{
path: '/create-plan',
component:AddPlanPage,
name: '创建视频课'
},
{
path: '/store-info',
component:StoreInfoPage,
name: '店铺信息'
},
{
path: '/learning-data',
component:LearningDataPage,
name: '学习数据'
}
]
export default mainRoutes;
\ No newline at end of file
/*
* @Author: zhangleyuan
* @Date: 2021-01-19 11:27:56
* @LastEditors: zhangleyuan
* @LastEditTime: 2021-03-02 15:18:12
* @Description: 描述一下
* @@Copyrigh: © 2020 杭州杰竞科技有限公司 版权所有
*/
export const menuList: any = [
{
groupName: "中心首页",
......@@ -34,11 +42,28 @@ export const menuList: any = [
link: '/resource-disk'
},
{
groupName: "培训管理",
groupCode: "TrainManage",
icon: '&#xe863;',
children: [
{
groupName: "培训计划",
groupCode: "TrainPlan",
link: '/plan'
}
]
},
{
groupName: "店铺管理",
groupCode: "CloudShop",
icon: '&#xe82e;',
children: [
{
groupName: "店铺信息",
groupCode: "ShopInfo",
link: '/store-info'
},
{
groupName: "员工管理",
groupCode: "ShopStaff",
link: '/employees-manage'
......
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