Commit f4b452b7 authored by baixian's avatar baixian

erp营销页

parent 38a99787
......@@ -11,8 +11,8 @@ if (ENVIRONMENT == 'pro') {
} else if (ENVIRONMENT == 'dev') {
// api = 'https://wx.m.shangjiadao.cn/v2/api/';
// dakaapi = 'https://qxapi.qingxiao.online/daka/v3/';
api = 'http://test.wp53.cn/v2/api/';
dakaapi = 'http://clock.wp53.cn/v3/';
api = 'https://test.wp53.cn/v2/api/';
dakaapi = 'https://clock.wp53.cn/v3/';
} else if (ENVIRONMENT == 'testenv') {
api = 'https://test.wp53.cn/v2/api/';
dakaapi = 'https:clock.wp53.cn/v3/';
......@@ -182,8 +182,8 @@ export default {
unlockSubjectDetail: `${dakaapi}member/unlock_subject/detail`,
unlockAddSubjectCount: `${dakaapi}member/unlock_subject/add_subject_count`,
unlock_student_export: `${dakaapi}member/erp/student/unlock_student_export`,
classList: `${dakaapi}member/erp/courses`,
landing_pages: `${dakaapi}member/erp/landing_pages`,
classCourseList: `${dakaapi}member/erp/courses`,
landing_pages: `${dakaapi}member/landing_pages`,
},
getschooluuid: `${dakaapi}member/school_uuid`,
analyzeCenter: {
......
import { routerRedux } from 'dva/router';
import { message } from 'antd';
import { delay } from 'redux-saga';
import moment from 'moment';
import {
LocalStorage,
SessionStorage,
isExpired,
getRandomFilename, getAudioDuration, getVideoDuration,
} from '../utils/index';
import errorcode from '../common/errorcode';
import * as uploader from '../services/uploader';
import * as themeAjax from '../services/createtheme';
export default {
namespace: 'createmarke',
state: {
markeParams: {
title: '',
name: '',
mobile: '',
img: '',
cover: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2c/sharemoretheme/newunlockbg.png',
content: [
{
title: '',
content: [{
type: 'text',
value: '1.全程陪伴练字教学,我们通过听课+作业+点评+群内解答等互动形式,提高学习效果' +
'\n' +
'2.国家级书法名师亲自授课,指导你快速学习正确的握笔姿势和练字方法' +
'\n' +
'3.从零基础讲起,适合小白快速入门,课程系统全面,有经验的学员也能得到进一步提高;' +
'\n' +
'4.我们的每一位同学都将得到一对一教师指导,每天督促学习,陪伴式成长' +
'适合人群\n' +
'(1)零基础练字学员\n' +
'(2)有一点基础,想提升的学员\n' +
'(3)节假日期间想快速练好字的学员',
},
{ type: 'img', value: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2b/themeeditorlock/title-1.png' },
{ type: 'text', value: '洛子帅, 书法练字训练营创始人,杭州阳关灿烂教育 \r\n 教学主管,专业书法教师,已帮助3000多人写好字。' },
{ type: 'img', value: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2b/themeeditorlock/shoubiao.png' },
],
},
],
},
progressRate: 0,
themeAdInfo: {
id: 0,
title: '',
},
has_subject: false, // 营销页是否在使用
},
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-line
},
},
effects: {
* deleteMarke({ payload }, { call, put, select }) {
const { item } = payload;
const { has_subject } = yield select(state => state.createmarke);
if (has_subject) {
message.error('这个营销页已经有主题使用,不能删除', 0.5);
return;
}
const data = yield call(themeAjax.deleteMarke, {
id: item.id,
});
if (data.code == 200) {
message.success('删除成功', 0.5);
yield put({
type: 'createtheme/queryLandingList',
payload: {
params: {
},
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* saveMarke({ payload }, { call, put, select }) {
const { sid } = yield select(state => state.webapp);
const {
markeParams,
markeLoading,
} = yield select(state => state.createmarke);
const {
title,
name,
mobile,
callBack,
} = payload;
if (name.trim() == '') {
message.error('请填写老师姓名', 0.5);
return;
}
if (mobile == '' && markeParams.img == '') {
message.error('请添加手机号码或者上传微信二维码', 0.5);
return;
}
if (markeLoading) {
return;
}
yield put({
type: 'updateState',
payload: {
markeLoading: true,
},
});
const newParams = { ...markeParams };
const loadmessage = message.loading('保存中...', 0);
const postFunction = (newParams.id != undefined) && newParams.id !== 0 ? themeAjax.editMarke : themeAjax.addMarke;
const data = yield call(postFunction, Object.assign(newParams,
{
school_id: sid,
title,
name,
mobile,
content: JSON.stringify(newParams.content),
},
));
yield put({
type: 'updateState',
payload: {
markeLoading: false,
},
});
setTimeout(loadmessage);
if (data.code === 200) {
message.success('保存成功', 0.5);
if (callBack && (typeof callBack == 'function')) {
callBack();
}
yield put({
type: 'createtheme/queryLandingList',
payload: {
markeLoading: false,
},
});
yield put({
type: 'updateState',
payload: {
addMarkeVisible: false,
markeParams: {
title: '',
name: '',
mobile: '',
img: '',
cover: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2c/sharemoretheme/newunlockbg.png',
content: [
{
title: '',
content: [{
type: 'text',
value: '1.全程陪伴练字教学,我们通过听课+作业+点评+群内解答等互动形式,提高学习效果' +
'\n' +
'2.国家级书法名师亲自授课,指导你快速学习正确的握笔姿势和练字方法' +
'\n' +
'3.从零基础讲起,适合小白快速入门,课程系统全面,有经验的学员也能得到进一步提高;' +
'\n' +
'4.我们的每一位同学都将得到一对一教师指导,每天督促学习,陪伴式成长' +
'适合人群\n' +
'(1)零基础练字学员\n' +
'(2)有一点基础,想提升的学员\n' +
'(3)节假日期间想快速练好字的学员',
},
{ type: 'img', value: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2b/themeeditorlock/title-1.png' },
{ type: 'text', value: '洛子帅, 书法练字训练营创始人,杭州阳关灿烂教育 \r\n 教学主管,专业书法教师,已帮助3000多人写好字。' },
{ type: 'img', value: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2b/themeeditorlock/shoubiao.png' },
],
},
],
},
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* editMarke({ payload }, { call, put, select }) {
const { item } = payload;
const data = yield call(themeAjax.markeDetail, {
id: item.id,
});
if (data.code == 200) {
yield put({
type: 'updateState',
payload: {
has_subject: data.data.has_subject,
markeParams: {
id: data.data.id,
title: data.data.title,
name: data.data.name,
mobile: data.data.mobile,
img: data.data.img,
cover: data.data.cover,
content: JSON.parse(data.data.content),
},
addMarkeVisible: true,
},
});
}
},
* markeAddPlate({ payload }, { call, put, select }) {
const { markeParams } = yield select(state => state.createmarke);
markeParams.content.push({
title: '',
content: [
],
});
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* markeAddText({ payload }, { call, put, select }) {
const { index } = payload;
const { markeParams } = yield select(state => state.createmarke);
markeParams.content[index].content.push({
type: 'text',
value: '',
});
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* markeChangeSize({ payload }, { call, put, select }) {
const { textValue, index, sort } = payload;
const { markeParams } = yield select(state => state.createmarke);
markeParams.content[sort].content[index] = {
type: 'text',
value: textValue,
};
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* markeChangeTitle({ payload }, { call, put, select }) {
const { textValue, sort } = payload;
const { markeParams } = yield select(state => state.createmarke);
markeParams.content[sort].title = textValue;
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* markeMoveContent({ payload }, { call, put, select }) {
const { index, direction, sort } = payload;
const { markeParams } = yield select(state => state.createmarke);
let newContent = [];
newContent = markeParams.content[sort].content;
const currentImg = newContent[index];
const preImg = newContent[index - 1];
const afterImg = newContent[index + 1];
if (direction == 'up') {
newContent.splice(index - 1, 2, currentImg, preImg);
} else if (direction == 'down') {
newContent.splice(index, 2, afterImg, currentImg);
}
markeParams.content[sort].content = newContent;
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* deleteMarkeImg({ payload }, { call, put, select }) {
const { index, sort } = payload;
const { markeParams } = yield select(state => state.createmarke);
const newContent = markeParams.content[sort].content;
newContent.splice(index, 1);
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* markeDeletePlate({ payload }, { call, put, select }) {
const { sort } = payload;
const { markeParams } = yield select(state => state.createmarke);
const newContent = markeParams.content;
newContent.splice(sort, 1);
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
},
* queryimagesignature({ payload }, { call, put, select }) {
const { userInfo, sid } = yield select(state => state.webapp);
const { files, uploadtype, contentSort } = payload;
const file = files.files ? files.files[0] : null;
const REGEXP_VIDEO = /^image\/\w+/;
const params = { type: 1, token: userInfo.token, schoolId: sid };
let signature = {};
if (file && (REGEXP_VIDEO.test(file.type) || file.type === '')) {
const uploaderLoading = message.loading('正在上传图片');
const uploadSignature = yield call(uploader.uploadImageSignature, params);
if (uploadSignature.code == 200) {
signature = uploadSignature.data;
yield put({
type: 'uploadimage',
payload: {
signature,
files,
uploaderLoading,
uploadtype,
contentSort,
},
});
} else {
setTimeout(uploaderLoading);
yield put({
type: 'webapp/errorrequestresolve',
payload: {
code: uploadSignature,
},
});
}
}
},
* uploadimage({ payload }, { call, put, select }) {
const {
signature, files, uploaderLoading, uploadtype,
contentSort,
} = payload;
const file = files.files ? files.files[0] : null;
const { markeParams } = yield select(state => state.createmarke);
const filename = `${signature.dir}${getRandomFilename(file.name)}`;
const params = {
key: filename,
policy: signature.policy,
OSSAccessKeyId: signature.accessid,
signature: signature.signature,
file,
url: signature.host,
};
const uploadImg = yield call(uploader.uploadImg, params);
const imageUrl = filename;
if (uploadtype == 'uploadImgMarke') {
markeParams.content[contentSort].content.push({
type: 'img',
value: imageUrl,
});
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
} else if (uploadtype == 'quickCover') {
markeParams.cover = imageUrl;
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
} else if (uploadtype == 'uploadWxImg') {
markeParams.img = imageUrl;
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
}
setTimeout(uploaderLoading);
},
* queryvoicesignature({ payload }, { call, put, select }) {
const { avatorUploader } = yield select(state => state.uploader);
const { userInfo, sid } = yield select(state => state.webapp);
const {
files, uploadtype, orgIndex, contentSort,
} = payload;
const file = files.files ? files.files[0] : null;
const REGEXP_VIDEO = /^audio\/\w+/;
const params = { type: 2, token: userInfo.token, schoolId: sid };
let signature = {};
if (file && (REGEXP_VIDEO.test(file.type) || file.type === '')) {
const uploadSignature = yield call(uploader.uploadVideoSignature, params);
signature = uploadSignature.data;
yield put({
type: 'uploadvoice',
payload: {
signature,
avatorUploader,
files,
uploadtype,
orgIndex,
contentSort,
},
});
}
},
* uploadvoice({ payload }, { call, put, select }) {
const {
signature, files, uploadtype, orgIndex,
contentSort,
} = payload;
const file = files.files ? files.files[0] : null;
const uploaderLoading = message.loading('正在上传录音', 0);
const { markeParams } = yield select(state => state.createmarke);
const filename = `${signature.dir}${getRandomFilename(file.name)}`;
const params = {
key: filename,
policy: signature.policy,
OSSAccessKeyId: signature.accessid,
signature: signature.signature,
file,
url: signature.host,
};
const uploadImg = yield call(uploader.uploadVideo, params);
const videoUrl = filename;
document.getElementById('uploadVoice').value = '';
if (uploadtype == 'uploadVoiceMarke') {
const duration = yield call(getAudioDuration, file);
markeParams.content[contentSort].content.push({
type: 'voice',
value: videoUrl,
duration,
});
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
}
setTimeout(uploaderLoading);
},
* queryvideosignature({ payload }, { call, put, select }) {
const { avatorUploader } = yield select(state => state.uploader);
const { userInfo, sid } = yield select(state => state.webapp);
const {
files, uploadtype, orgIndex, contentSort,
progressCallBack,
} = payload;
const file = files.files ? files.files[0] : null;
if (file && file.size > 2000 * 1024 * 1024) {
message.error('视频的大小不能超过2GB,请重新上传', 1);
return;
}
const REGEXP_VIDEO = /^video\/\w+/;
const params = { type: 1, token: userInfo.token, schoolId: sid };
let signature = {};
if (file && (REGEXP_VIDEO.test(file.type) || file.type === '')) {
const uploadSignature = yield call(uploader.uploadVideoSignature, params);
signature = uploadSignature.data;
if (uploadSignature.code == 200) {
yield put({
type: 'uploadvideo',
payload: {
signature,
avatorUploader,
files,
uploadtype,
orgIndex,
contentSort,
progressCallBack,
},
});
} else {
yield put({
type: 'updateState',
payload: {
progressRate: 0,
},
});
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data: uploadSignature,
},
});
}
}
},
* uploadvideo({ payload }, { call, put, select }) {
const {
signature, files, uploadtype, orgIndex,
contentSort,
progressCallBack,
} = payload;
const file = files.files ? files.files[0] : null;
const { markeParams } = yield select(state => state.createmarke);
const filename = `${signature.dir}${getRandomFilename(file.name)}`;
const params = {
key: filename,
policy: signature.policy,
OSSAccessKeyId: signature.accessid,
signature: signature.signature,
file,
url: signature.host,
callback: signature.callback,
};
document.getElementById('uploadVideo').value = '';
const uploadImg = yield call(uploader.uploadVideo, params, (res) => {
progressCallBack(res);
});
const videoUrl = filename;
if (JSON.stringify(uploadImg) == '{"status":"ok"}') {
yield put({
type: 'updateState',
payload: {
progressRate: 0,
},
});
if (uploadtype == 'uploadVideoMarke') {
const duration = yield call(getVideoDuration, file);
markeParams.content[contentSort].content.push({
type: 'video',
value: videoUrl,
duration,
});
yield put({
type: 'updateState',
payload: {
markeParams: { ...markeParams },
},
});
}
} else {
yield put({
type: 'updateState',
payload: {
progressRate: 0,
},
});
message.error('上传失败', 1);
}
},
* pageInit({ payload }, { call, put, select }) {
yield put({
type: 'updateState',
payload: {
markeParams: {
title: '',
name: '',
mobile: '',
img: '',
cover: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2c/sharemoretheme/newunlockbg.png',
content: [
{
title: '',
content: [{
type: 'text',
value: '1.全程陪伴练字教学,我们通过听课+作业+点评+群内解答等互动形式,提高学习效果' +
'\n' +
'2.国家级书法名师亲自授课,指导你快速学习正确的握笔姿势和练字方法' +
'\n' +
'3.从零基础讲起,适合小白快速入门,课程系统全面,有经验的学员也能得到进一步提高;' +
'\n' +
'4.我们的每一位同学都将得到一对一教师指导,每天督促学习,陪伴式成长' +
'适合人群\n' +
'(1)零基础练字学员\n' +
'(2)有一点基础,想提升的学员\n' +
'(3)节假日期间想快速练好字的学员',
},
{ type: 'img', value: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2b/themeeditorlock/title-1.png' },
{ type: 'text', value: '洛子帅, 书法练字训练营创始人,杭州阳关灿烂教育 \r\n 教学主管,专业书法教师,已帮助3000多人写好字。' },
{ type: 'img', value: 'https://cdn.img.shangjiadao.cn/qingxiao/daka/images/2b/themeeditorlock/shoubiao.png' },
],
},
],
},
progressRate: 0,
markeLoading: false,
has_subject: false,
},
});
},
},
reducers: {
save(state, action) {
return { ...state, ...action.payload };
},
updateState(state, action) {
return { ...state, ...action.payload };
},
},
};
......@@ -19,7 +19,6 @@ import * as themeAjax from '../services/createtheme';
import * as classMgtAjax from '../services/classmgt';
import * as uploader from '../services/uploader';
import { calendar } from '../utils/calendar';
import * as goodsAjax from '../services/integral';
import * as commonAjax from '../services/common';
import exportExcel from '../utils/exportexcel';
export default {
......@@ -175,7 +174,21 @@ export default {
themeAdInfo: {
id: 0,
title: '',
}, // 打卡营销页
},
landingParams: {
page: 1,
perPage: 5,
},
landingTotal: 0,
clockClassVisible: false,
clockClassList: [],
clockClassParams: {
page: 1,
perPage: 299,
extra: 'class',
},
classes: [],
chooseClasses: [],
},
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-line
......@@ -183,10 +196,38 @@ export default {
},
effects: {
* queryInfo({ payload }, { call, put, select }) {
yield put({
type: 'querymemberinfo',
});
yield delay(500);
yield put({
type: 'queryLandingList',
payload: {
params: {},
},
});
yield put({
type: 'queryCourseClassList',
payload: {
params: {},
},
});
},
* querymemberinfo({ payload }, { call, put, select }) { // 释放该页面存储的所有状态
const { sid } = yield select(state => state.webapp);
const nickname = yield call(commonAjax.myNickname, {
school_id: sid,
});
if (nickname.code == 200) {
console.log(nickname.data, 'nickname.data');
yield put({
type: 'updateState',
payload: {
schoolUserInfo: nickname.data,
},
});
}
},
* tabChange({ payload }, { call, put, select }) {
const { tabIndex } = payload;
yield put({
......@@ -2258,20 +2299,31 @@ export default {
}
},
// 查询班级列表
* queryMyClassList({ payload }, { call, put, select }) {
* queryCourseClassList({ payload }, { call, put, select }) {
const { params } = payload;
const { sid } = yield select(state => state.webapp);
const classListData = yield call(themeAjax.getClassList, Object.assign({
const { clockClassParams, schoolUserInfo } = yield select(state => state.createtheme);
const newParams = Object.assign({
school_id: sid,
// page: 1,
// perPage: 1000,
extra: 'class',
}, params));
}, clockClassParams, params);
const classListData = yield call(themeAjax.getCourseClassList, newParams);
if (classListData.code == 200) {
classListData.data.list.forEach((item) => {
const teachers = [];
item.classes.forEach((ele) => {
const name = ele.school_teachers.find(e => e.id == schoolUserInfo.schoolTeacher.id);
if (name) {
ele.school_teachers.unshift(name);
}
// eslint-disable-next-line no-param-reassign
ele.school_teachers = [...new Set(ele.school_teachers)];
});
});
yield put({
type: 'updateState',
payload: {
classList: (classListData.data && classListData.data.list) || [],
clockClassList: (classListData.data && classListData.data.list) || [],
clockClassParams: { ...newParams },
},
});
} else {
......@@ -2285,19 +2337,25 @@ export default {
},
// 获取营销页列表
* queryLandingList({ payload }, { call, put, select }) {
const { params } = payload;
const { landingParams } = yield select(state => state.createtheme);
const { sid } = yield select(state => state.webapp);
const data = yield call(themeAjax.getlandingList, {
const newParams = Object.assign({
school_id: sid,
});
}, landingParams, params);
const data = yield call(themeAjax.getlandingList, newParams);
if (data.code == 200) {
data.data.list.forEach((ele) => {
let content = [];
let _content = [];
try {
content = JSON.parse(ele.content);
_content = JSON.parse(ele.content);
} catch (e) {
content = [];
_content = [];
}
const textobj = content.find(ele => ele.type == 'text' && ele.value != '');
let textobj = '';
_content.forEach((item, index) => {
textobj = item.content && item.content.find(item => item.type == 'text' && item.value != '');
});
// eslint-disable-next-line no-param-reassign
ele.desc = textobj ? textobj.value : '';
});
......@@ -2305,6 +2363,8 @@ export default {
type: 'updateState',
payload: {
landingList: (data.data && data.data.list) || [],
landingParams: { ...newParams },
landingTotal: (data.data && data.data.total) || 0,
},
});
} else {
......@@ -2467,10 +2527,24 @@ export default {
subjectList: [],
storeImg: '',
progressRate: 0,
},
themeAdInfo: {
id: 0,
title: '',
themeAdInfo: {
id: 0,
title: '',
},
landingParams: {
page: 1,
perPage: 5,
},
landingTotal: 0,
clockClassVisible: false,
clockClassList: [],
clockClassParams: {
page: 1,
perPage: 299,
extra: 'class',
},
classes: [],
chooseClasses: [],
},
});
},
......
......@@ -57,6 +57,7 @@ import crmdetail from './crmdetail';
import holidays from './holidays';
import officialtheme from './officialtheme';
import liveclass from './liveclass';
import createmarke from './createmarke';
export default {
loginModel,
indexstaicModel,
......@@ -108,4 +109,5 @@ export default {
holidays,
officialtheme,
liveclass,
createmarke,
};
......@@ -4,10 +4,12 @@ import {
Form,
Input,
Modal,
Checkbox,
Checkbox, message,
Icon,
} from 'antd';
import pageStyle from './ChooseClass.less';
import pageStyle from './AddMarkeModal.less';
import { pageIn, hasBtnPower, imagify, ossVideofy } from '../../../utils/index';
import MarkeEditor from '../components/MarkeEditor';
const FormItem = Form.Item;
const { TextArea } = Input;
class AddMarkeModal extends React.Component {
......@@ -22,39 +24,190 @@ class AddMarkeModal extends React.Component {
}
componentWillUnmount() { // 卸载
}
close = () => {
const { close, form } = this.props;
close();
form.resetFields();
markeUploadImg = (e, sort) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/queryimagesignature',
payload: {
files: e.target,
uploadtype: 'uploadImgMarke',
contentSort: sort,
},
});
}
markeUploadVideo = (e, sort) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/queryvideosignature',
payload: {
files: e.target,
uploadtype: 'uploadVideoMarke',
contentSort: sort,
progressCallBack(res) {
dispatch({
type: 'createmarke/updateState',
payload: {
progressRate: ((res.loaded / res.total) * 100).toFixed(0),
},
});
},
},
});
}
markeUploadVoice = (e, sort) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/queryvoicesignature',
payload: {
files: e.target,
uploadtype: 'uploadVoiceMarke',
contentSort: sort,
},
});
}
markeChangeSize = (e, index, sort) => {
const { dispatch } = this.props;
const textValue = e.target.value;
if (textValue.length > 500) {
message.error('最多500字', 0.5);
return;
}
dispatch({
type: 'createmarke/markeChangeSize',
payload: {
textValue,
index,
sort,
},
});
}
markeChangeTitle = (e, sort) => {
const { dispatch } = this.props;
const textValue = e.target.value;
if (textValue.length > 20) {
message.warning('最多20字', 0.5);
return;
}
dispatch({
type: 'createmarke/markeChangeTitle',
payload: {
textValue,
sort,
},
});
}
deleteMarkeImg = (index, sort) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/deleteMarkeImg',
payload: {
index,
sort,
},
});
}
markeAddText = (index) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/markeAddText',
payload: {
index,
},
});
}
markeAddPlate = () => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/markeAddPlate',
payload: {
},
});
}
markeMoveContent = (index, sort, direction) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/markeMoveContent',
payload: {
index,
sort,
direction,
},
});
}
markeDeletePlate = (sort) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/markeDeletePlate',
payload: {
sort,
},
});
}
uploadCover = (e) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/queryimagesignature',
payload: {
files: e.target,
uploadtype: 'quickCover',
},
});
}
uploadWxImg = (e) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/queryimagesignature',
payload: {
files: e.target,
uploadtype: 'uploadWxImg',
},
});
}
save = (e) => {
const { save, form } = this.props;
const { dispatch, form } = this.props;
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
const {
is_encrypt,
password,
name,
mobile,
title,
} = values;
save({
is_encrypt,
password,
}, () => {
form.resetFields();
dispatch({
type: 'createmarke/saveMarke',
payload: {
title,
name,
mobile,
callBack: () => {
form.resetFields();
},
},
});
}
});
}
close = () => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/updateState',
payload: {
addMarkeVisible: false,
},
});
}
render() {
const {
visible,
form: { getFieldDecorator, getFieldValue },
replyLoading,
markeLoading,
markeParams,
progressRate,
} = this.props;
const formItemModalLineLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 3 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
......@@ -66,26 +219,26 @@ class AddMarkeModal extends React.Component {
visible={visible}
maskClosable={false}
zIndex={110}
width={600}
width={900}
bodyStyle={{
padding: '10px 20px',
}}
title="打卡营销页设置"
closable={false}
onCancel={this.close}
okText="保存"
cancelText="取消"
confirmLoading={replyLoading}
confirmLoading={markeLoading}
onOk={this.save}
>
<Form className={pageStyle.modalform} labelAlign="left" onSubmit={this.save}>
<FormItem {...formItemModalLineLayout} label="模板名称">
{getFieldDecorator('title', {
initialValue: '',
initialValue: markeParams.title,
rules: [
{
required: true,
message: '请输入模板名称',
whitespace: true,
},
],
})(
......@@ -93,6 +246,97 @@ class AddMarkeModal extends React.Component {
)}
<span className="ant-form-text">模板名称不会在打卡营销页展示</span>
</FormItem>
<FormItem {...formItemModalLineLayout} label="模板海报">
<div className={pageStyle.photoWrap}>
<div className={pageStyle.defaultImgBox}>
<img className={pageStyle.defaultImg} src={imagify(markeParams.cover)} alt="" />
<div className={pageStyle.chooseType}>
<div className={pageStyle.material}>
<img src={`${__IMGCDN__}course/upload_icon1.png`} alt="上传图片" />
<div className={pageStyle.tip}>上传图片</div>
<input
type="file"
className={pageStyle.uploadInput}
id="upload1"
accept="image/png, image/jpeg"
onChange={(e) => { this.uploadCover(e); }}
/>
</div>
</div>
</div>
</div>
<span className="ant-form-text">图片建议格式位JPG/PNG,尺寸750*422</span>
</FormItem>
<FormItem {...formItemModalLineLayout} label="打卡概述">
<div className={pageStyle.ediorWrap}>
<MarkeEditor
commentParams={markeParams}
editorUploadImg={this.markeUploadImg}
editorUploadAudio={this.markeUploadVideo}
editorUploadVoice={this.markeUploadVoice}
editorChange={this.markeChangeSize}
deleteThemeImg={this.deleteMarkeImg}
moveContent={this.markeMoveContent}
editorAddText={this.markeAddText}
editorAddPlate={this.markeAddPlate}
editorChangeTitle={this.markeChangeTitle}
editorDeletePlate={this.markeDeletePlate}
progressRate={progressRate}
/>
</div>
</FormItem>
<div className={pageStyle.teahcerTip}>老师联系方式 <span>让意向家长方便添加老师微信</span></div>
<FormItem {...formItemModalLineLayout} label="老师姓名">
{getFieldDecorator('name', {
initialValue: markeParams.name,
})(
<Input maxLength={50} placeholder="请输入老师姓名 例如: 方老师" style={{ width: 435 }} />,
)}
</FormItem>
<FormItem {...formItemModalLineLayout} label="手机号码">
{getFieldDecorator('mobile', {
rules: [
{ pattern: /^1[3456789]{1}[0-9]{9}$/, message: '请输入正确的手机号码!' },
{ max: 11, message: '手机号长度为11位!' },
],
initialValue: markeParams.mobile,
})(
<Input maxLength={50} placeholder="请输入手机号码(可选)" style={{ width: 435 }} />,
)}
</FormItem>
<FormItem {...formItemModalLineLayout} label="微信号">
<div className={pageStyle.uploadFlex}>
{
markeParams.img ?
<div className={pageStyle.wxImg}>
<img src={imagify(markeParams.img)} alt="" />
</div> :
<div className={pageStyle.wxWrap}>
<Icon type="plus" style={{ fontSize: 32, color: '#999' }} />
<input
type="file"
className={pageStyle.uploadInput}
id="upload1"
accept="image/png, image/jpeg"
onChange={(e) => { this.uploadWxImg(e); }}
/>
</div>
}
{
markeParams.img &&
<div className={pageStyle.uploadText}>
<input
type="file"
className={pageStyle.uploadInput}
id="upload1"
accept="image/png, image/jpeg"
onChange={(e) => { this.uploadWxImg(e); }}
/>更换二维
</div>
}
</div>
<span className="ant-form-text">上传老师微信二维码</span>
</FormItem>
</Form>
</Modal>
);
......@@ -103,6 +347,19 @@ AddMarkeModal.propTypes = {
};
const AddMarkeModalForm = Form.create()(AddMarkeModal);
export default connect()(AddMarkeModalForm);
function mapStateToProps(state) {
const {
markeParams,
themeAdInfo,
progressRate,
markeLoading,
} = state.createmarke;
return {
themeAdInfo,
markeParams,
progressRate,
markeLoading,
};
}
export default connect(mapStateToProps)(AddMarkeModalForm);
.photoWrap {
display: inline-flex;
width: 375px;
height: 211px;
position: relative;
margin-right: 30px;
.defaultImgBox {
width: 375px;
height: 211px;
cursor: pointer;
.defaultImg {
width: 100%;
height: 100%;
border-radius: 6px;
}
&:hover .chooseType {
opacity: 1;
z-index: 0;
}
}
.chooseType {
position: absolute;
width: 100%;
height: 100%;
bottom: 0;
left: 0;
opacity: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: -10;
background: rgba(0,0,0,0.7);
transition: opacity .3s linear;
border-radius: 6px;
}
.photoCover {
width: 375px;
height: 211px;
position: relative;
&:hover .chooseType {
opacity: 1;
z-index: 0;
}
&>img {
width: 424px;
height: 230px;
border-radius: 6px;
}
.coverDelete {
font-size: 22px;
color: #d5d5d5;
cursor: pointer;
position: absolute;
right: -25px;
top: 0;
}
.coverDelete:hover {
color: #fa4f53;
}
}
.material {
width: 50%;
height: 211px;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
margin-right: 11px;
cursor: pointer;
position: relative;
.uploadInput {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.tip {
font-size:14px;
font-weight:400;
color:#fff;
}
&>img {
width: 32px;
height: 32px;
margin-top: 10px;
}
}
}
.ediorWrap {
width: 650px;
min-height: 319px;
padding: 20px;
background-color: #fafafa;
-webkit-border-radius: 4px;
border-radius: 4px;
border: 1px solid #eee;
max-height: 350px;
overflow-y: scroll;
}
::-webkit-scrollbar {
width: 8px;
height: 10px;
background-color: rgba(0,0,0,0);
}
::-webkit-scrollbar-thumb {
border-radius: 6px;
background-color: rgba(0,0,0,.2);
transition: all .4s ease;
-moz-transition: all .4s ease;
-webkit-transition: all .4s ease;
-o-transition: all .4s ease;
}
.modalform {
:global {
.ant-form-item {
margin-bottom: 10px;
}
}
}
.teahcerTip {
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 600;
line-height: 24px;
color: rgba(0, 0, 0, 0.85);
margin-bottom: 20px;
}
.uploadFlex {
display: flex;
align-items: center;
.wxImg {
width: 100px;
height: 100px;
margin-right: 20px;
position: relative;
&>img {
width: 100%;
height: 100%;
}
.uploadInput {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
}
.wxWrap {
width: 100px;
height: 100px;
position: relative;
cursor: pointer;
background-color: #fafafa;
border: 1px dashed #d9d9d9;
border-radius: 4px;
cursor: pointer;
-webkit-transition: border-color 0.3s ease;
transition: border-color 0.3s ease;
line-height: 100px;
text-align: center;
&:hover {
border-color: #1890ff;
}
.uploadInput {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
}
.uploadText {
position: relative;
color: #16b0fd;
font-size: 16px;
.uploadInput {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
}
}
......@@ -5,6 +5,7 @@ import {
Input,
Modal,
Checkbox,
Button,
} from 'antd';
import pageStyle from './ChooseClass.less';
import { pageIn, hasBtnPower, imagify, ossVideofy } from '../../../utils/index';
......@@ -22,89 +23,112 @@ class ChooseClassModal extends React.Component {
}
componentWillUnmount() { // 卸载
}
indexOf = (arr, item) => {
return arr.indexOf(item);
}
close = () => {
const { close, form } = this.props;
close();
form.resetFields();
}
save = (e) => {
const { save, form } = this.props;
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
const {
is_encrypt,
password,
} = values;
save({
is_encrypt,
password,
}, () => {
form.resetFields();
});
}
});
changeCourseClass = (type) => {
const { changeCourseClass } = this.props;
changeCourseClass(type);
}
chooseClass = (item) => {
const { classes, chooseClasses, choose } = this.props;
const classesIndex = classes.indexOf(item.id);
const chooseClassesIndex = chooseClasses.findIndex(ele => ele.id == item.id);
if (classesIndex == -1) {
classes.push(item.id);
} else {
classes.splice(classesIndex, 1);
}
if (chooseClassesIndex == -1) {
chooseClasses.push({
title: item.title,
id: item.id,
});
} else {
chooseClasses.splice(classesIndex, 1);
}
choose({ classes, chooseClasses });
}
saveClasses = () => {
const { classes, saveClasses } = this.props;
saveClasses(classes);
}
render() {
const {
visible,
form: { getFieldDecorator, getFieldValue },
record,
replyLoading,
info,
list,
clockClassParams,
classes,
chooseClasses,
} = this.props;
const formItemModalLineLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 3 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 },
},
};
return (
<Modal
visible={visible}
maskClosable={false}
zIndex={110}
width={600}
bodyStyle={{
padding: '40px 20px',
padding: '10px 20px',
}}
title="修改密码"
closable={false}
title="选择参与打卡的班级"
onCancel={this.close}
okText="保存"
cancelText="取消"
confirmLoading={replyLoading}
onOk={this.save}
footer={null}
>
<Form className={pageStyle.modalform} labelAlign="left" onSubmit={this.save}>
<FormItem>
{getFieldDecorator('is_encrypt', {
valuePropName: 'checked',
initialValue: info.is_encrypt == 1,
})(<Checkbox><span className={pageStyle.tip}>设置密码</span></Checkbox>)}
</FormItem>
{
Number(getFieldValue('is_encrypt')) === 1 ?
<FormItem {...formItemModalLineLayout}>
{getFieldDecorator('password', {
initialValue: info.password,
rules: [
<div>
<div className={pageStyle.topHead}>
<Button type={clockClassParams.extra == 'class' ? 'primary' : ''} className={pageStyle.btn} onClick={() => this.changeCourseClass('class')}>全部班级</Button>
<Button type={clockClassParams.extra == 'my_class' ? 'primary' : ''} className={pageStyle.btn} onClick={() => this.changeCourseClass('my_class')}>只看我的</Button>
</div>
<div className={pageStyle.classList}>
{
list.length > 0 && list.map((item, index) => {
return (
<div className={pageStyle.classItem}>
<div className={pageStyle.classTitle}>{item.title}</div>
{
required: true,
message: '请输入4-8位数字密码',
pattern: new RegExp(/^\d{4,8}$/, 'g'),
},
],
})(
<Input style={{ width: 200 }} maxLength={8} placeholder="请输入4-8位数字密码" />,
)}
</FormItem> : ''
}
</Form>
item.classes.map((ele, i) => {
return (
<div className={pageStyle.classFlex} onClick={() => this.chooseClass(ele)}>
<div className={pageStyle.classLeft}>
{ele.title}
</div>
<div className={pageStyle.classRight}>
<div className={pageStyle.teacherList}>
{
ele.school_teachers.map((ll, i) => {
return (
<span className={pageStyle.teacherName}>{ll.nickname}</span>
);
})
}
</div>
{
this.indexOf(classes, ele.id) == -1 ?
<img className={pageStyle.check} src={`${__IMGCDN__}theme/check.png`} alt="" />
:
<img className={pageStyle.check} src={`${__IMGCDN__}theme/check_active.png`} alt="" />
}
</div>
</div>
);
})
}
</div>
);
})
}
</div>
<div className={pageStyle.save}>
<Button type="primary" onClick={this.saveClasses}>保存</Button>
</div>
</div>
</Modal>
);
}
......
.topHead {
text-align: center;
.btn {
border-radius: 0;
}
}
.classList {
margin-top: 20px;
max-height: 400px;
min-height: 350px;
overflow-y: scroll;
.classItem {
margin-bottom: 20px;
.classTitle {
font-size:13px;
font-family:PingFangSC-Medium,PingFang SC;
font-weight:600;
color:rgba(34,34,34,1);
line-height:35px;
margin-bottom: 5px;
}
.classFlex {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #F1F2F3;
padding: 5px 0;
.classLeft {
font-size:13px;
font-family:PingFangSC-Regular,PingFang SC;
font-weight:400;
color:rgba(34,34,34,1);
}
.classRight {
display: flex;
align-items: center;
.teacherList {
overflow: hidden;
white-space: nowrap;
width: 150px;
text-overflow: ellipsis;
margin-right: 15px;
text-align: right;
}
.teacherName {
font-size: 12px;
color: #666666;
}
.teacherName:not(:last-child) {
&:after{
content: '、';
}
}
.teacherName:nth-child(1){
color: #FEA915;
}
}
.check {
width: 19px;
height: 19px;
cursor: pointer;
}
}
}
}
.save {
text-align: center;
margin-top: 15px;
}
::-webkit-scrollbar {
width: 8px;
height: 10px;
background-color: rgba(0,0,0,0);
}
::-webkit-scrollbar-thumb {
border-radius: 6px;
background-color: rgba(0,0,0,.2);
transition: all .4s ease;
-moz-transition: all .4s ease;
-webkit-transition: all .4s ease;
-o-transition: all .4s ease;
}
import { connect } from 'dva';
import React from 'react';
import { Icon, Divider, Tabs, Select, Form, Upload, Row, Col, Input, Radio, Modal, message, Progress } from 'antd';
import pageStyle from './MarkeEditor.less';
import { pageIn, hasBtnPower, imagify, ossVideofy, audioorigin } from '../../../utils/index';
const upImg = `${__IMGCDN__}subjectUp.png`;
const { TabPane } = Tabs;
const { TextArea } = Input;
class ThemeEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
previewVisible: false,
previewImage: '',
};
}
componentDidMount() { // 挂载
pageIn('轻校-发布主题');
}
componentDidUpdate() {
}
componentWillUnmount() { // 卸载
}
priviewImg = (img) => {
this.setState({
previewVisible: true,
previewImage: img,
});
}
handleCancel = () => {
this.setState({
previewVisible: false,
});
}
render() {
const { previewVisible, previewImage } = this.state;
const {
editorUploadImg,
commentParams,
editorUploadAudio,
editorChange,
deleteThemeImg,
deleteThemeVideo,
editorText,
editorAddText,
moveContent,
editorUploadVoice,
editorAddPlate,
editorChangeTitle,
editorDeletePlate,
progressRate,
} = this.props;
return (
<div className={pageStyle.container}>
{
progressRate && progressRate > 0 ?
<div className={pageStyle.progressWrap}>
<Progress width={150} type="circle" percent={progressRate} />
<p> {progressRate == 100 ? '上传成功' : '上传中。。。'}</p>
</div> : ''
}
<div className={pageStyle.editorwrap}>
{
commentParams.content && commentParams.content.length > 0 && commentParams.content.map((ele, sort) => {
return (
<div className={pageStyle.plateWrap}>
{/* <div className={pageStyle.plateDelete} onClick={() => editorDeletePlate(sort)}><Icon type="close-circle" theme="filled" /></div> */}
{/* <Input style={{ width: 560 }} value={ele.title} maxLength={15} placeholder="请输入标题,最多15个字" onChange={e => editorChangeTitle(e, sort)} /> */}
{
ele.content && ele.content.length > 0 && ele.content.map((item, index) => {
switch (item.type) {
case 'text':
return (
<div key={index} className={pageStyle.textWrap}>
<TextArea autoSize={{ minRows: 4, maxRows: 10 }} value={item.value} onChange={e => editorChange(e, index, sort)} placeholder="请输入打卡主题的具体要求,如:演唱歌曲《小小少年》,具体要求1.跟随伴奏唱一段副歌 2.视频录下唱歌过程 3.上传歌唱视频打卡成功 如果有该歌曲示例视频,你可以点击上传视频,供学员参考。" style={{ width: 560 }} maxLength={500} />
<div className={pageStyle.sizeNumber}>{item.value.length}/500</div>
<div className={pageStyle.toolList}>
{ index === 0 && <div aria-disabled="true" className={pageStyle.noup}><img src={upImg} alt="" /> </div> }
{ index > 0 && <div className={pageStyle.up} onClick={() => moveContent(index, sort, 'up')}><img src={upImg} alt="" /> </div> }
{ index === (ele.content.length - 1) && <div className={pageStyle.nodown}><img src={upImg} alt="" /> </div> }
{ index < (ele.content.length - 1) && <div className={pageStyle.down} onClick={() => moveContent(index, sort, 'down')}><img src={upImg} alt="" /> </div> }
<div className={pageStyle.delete} onClick={() => deleteThemeImg(index, sort)}><Icon type="delete" theme="filled" /></div>
</div>
</div>
);
case 'img':
return (
<div key={index} className={pageStyle.uploadimgbox}>
<div className={pageStyle.imgwrap}>
<div className={pageStyle.uploadimgwrap}> <img alt={item.type} className={pageStyle.teacheruploadimg} src={imagify(item.value)} /></div>
<div className={pageStyle.imghide}>
<span title="预览文件" onClick={() => this.priviewImg(item.value)} ><Icon style={{ color: '#fff' }} type="eye" /> 预览</span>
</div>
<div className={pageStyle.toolList}>
{ index === 0 && <div aria-disabled="true" className={pageStyle.noup}><img src={upImg} alt="" /> </div> }
{ index > 0 && <div className={pageStyle.up} onClick={() => moveContent(index, sort, 'up')}><img src={upImg} alt="" /> </div> }
{ index === (ele.content.length - 1) && <div className={pageStyle.nodown}><img src={upImg} alt="" /> </div> }
{ index < (ele.content.length - 1) && <div className={pageStyle.down} onClick={() => moveContent(index, sort, 'down')}><img src={upImg} alt="" /> </div> }
<div className={pageStyle.delete} onClick={() => deleteThemeImg(index, sort)}><Icon type="delete" theme="filled" /></div>
</div>
</div>
</div>
);
case 'voice':
return (
<div key={index} className={pageStyle.uploadimgbox}>
<div className={pageStyle.imgwrap}>
<audio controls="controls" style={{ width: '100%' }} src={audioorigin(item.value)}></audio>
<div className={pageStyle.toolList}>
{ index === 0 && <div aria-disabled="true" className={pageStyle.noup}><img src={upImg} alt="" /> </div> }
{ index > 0 && <div className={pageStyle.up} onClick={() => moveContent(index, sort, 'up')}><img src={upImg} alt="" /> </div> }
{ index === (ele.content.length - 1) && <div className={pageStyle.nodown}><img src={upImg} alt="" /> </div> }
{ index < (ele.content.length - 1) && <div className={pageStyle.down} onClick={() => moveContent(index, sort, 'down')}><img src={upImg} alt="" /> </div> }
<div className={pageStyle.delete} onClick={() => deleteThemeImg(index, sort)}><Icon type="delete" theme="filled" /></div>
</div>
</div>
</div>
);
case 'video':
return (
<div key={index} className={pageStyle.videowrap}>
<video controls="controls" className={pageStyle.videoPoster} src={ossVideofy(item.value)} />
<div className={pageStyle.toolList}>
{ index === 0 && <div aria-disabled="true" className={pageStyle.noup}><img src={upImg} alt="" /> </div> }
{ index > 0 && <div className={pageStyle.up} onClick={() => moveContent(index, sort, 'up')}><img src={upImg} alt="" /> </div> }
{ index === (ele.content.length - 1) && <div className={pageStyle.nodown}><img src={upImg} alt="" /> </div> }
{ index < (ele.content.length - 1) && <div className={pageStyle.down} onClick={() => moveContent(index, sort, 'down')}><img src={upImg} alt="" /> </div> }
<div className={pageStyle.delete} onClick={() => deleteThemeImg(index, sort)}><Icon type="delete" theme="filled" /></div>
</div>
</div>
);
default:
return <div></div>;
}
})
}
<div className={pageStyle.uploadflex}>
<div className={pageStyle.uploadimg} onClick={() => editorAddText(sort)}><Icon style={{ marginRight: 10 }} type="font-size" />添加文字</div>
<div className={pageStyle.uploadimg}><input type="file" id="uploadImg" className={pageStyle.fileuploadinput} onChange={e => editorUploadImg(e, sort)} accept="image/*" /><Icon style={{ marginRight: 10 }} type="picture" />添加图片</div>
<div className={pageStyle.uploadimg}><input type="file" id="uploadVoice" className={pageStyle.fileuploadinput} onChange={e => editorUploadVoice(e, sort)} accept="audio/mp3" /><Icon style={{ marginRight: 10 }} type="audio" />添加录音</div>
<div className={pageStyle.uploadimg}><input type="file" id="uploadVideo" className={pageStyle.fileuploadinput} onChange={e => editorUploadAudio(e, sort)} accept="video/mp4,video/*" /><Icon style={{ marginRight: 10 }} type="video-camera" />添加视频</div>
</div>
<p>请上传视频小于2G,支持MP4格式</p>
</div>
);
})
}
</div>
{/* <div className={pageStyle.addPlate} onClick={editorAddPlate}>添加版块</div> */}
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="图片" style={{ width: '100%' }} src={imagify(previewImage)} />
</Modal>
</div>
);
}
}
ThemeEditor.propTypes = {
};
export default connect()(ThemeEditor);
.textWrap {
width: 560px;
height: auto;
position: relative;
margin-bottom:10px;
:global {
.ant-input {
padding: 6px 15px 20px;
}
}
.sizeNumber {
position: absolute;
bottom: 8px;
right: 10px;
}
}
.editorwrap {
//width: 563px;
margin-top: 15px;
.videowrap {
width: 100%;
height: 200px;
position: relative;
margin-bottom: 10px;
.videoPoster {
width: 100%;
height: 100%;
}
}
.videowrap:hover {
cursor: pointer;
.videohide {
opacity: 1;
background-color: rgba(0,0,0,0.5);
transition: all .3s;
z-index: 1;
color: #fff;
}
}
.videohide {
position: absolute;
width: 100%;
height: 100%;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
color: #fff;
z-index: -1;
a {
color: #fff;
}
}
.uploadimgbox {
display: flex;
align-items: flex-end;
margin-bottom: 18px;
flex-wrap: wrap;
.imgwrap {
width: 100%;
// height: 120px;
border-radius: 4px;
margin-right: 6px;
position: relative;
.uploadimgwrap {
width: 100%;
// height: 120px;
overflow: hidden;
}
}
.imgwrap:hover {
cursor: pointer;
.imghide {
opacity: 1;
background-color: rgba(0,0,0,0.5);
transition: all .3s;
z-index: 1;
color: #fff;
}
}
.imghide {
position: absolute;
width: 100%;
height: 100%;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
color: #fff;
z-index: -1;
a {
color: #fff;
}
}
.teacheruploadimg {
object-fit: contain;
border-radius: 4px;
width: 100%;
}
.teacheruploadimg:nth-child(4n){
margin-right: 0;
}
}
}
.uploadflex {
display: flex;
align-items: center;
margin-top: 20px;
.uploadimg {
cursor: pointer;
width: 106px;
height: 32px;
line-height: 32px;
text-align: center;
border: 1px solid #D9D9D9;
border-radius: 4px;
color: rgba(0,0,0,0.65);
position: relative;
margin-right: 30px;
.fileuploadinput {
cursor: pointer;
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
opacity: 0;
left: 0;
top: 0;
}
}
}
.toolList {
position: absolute;
right: -30px;
top: 0;
width: 30px;
height: 90px;
display: flex;
flex-direction: column;
background-color: #D3D3D3;
opacity: 0;
border-radius: 2px;
z-index: -1;
div {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 14px;
cursor: pointer;
}
div>img {
width: 14px;
height: 14px;
}
.up:hover {
background-color: #1890FF;
}
.down,.nodown {
transform: rotate(180deg);
}
.nodown,.noup {
cursor: not-allowed;
}
.down:hover {
background-color: #1890FF;
}
.delete:hover {
background-color: #FF6060;
}
}
.textWrap:hover,.imgwrap:hover,.videowrap:hover {
.toolList {
opacity: 1;
z-index: 1;
}
}
.addPlate {
width: 214px;
height: 34px;
line-height: 34px;
text-align: center;
color: #65B8F4;
border: 1px solid #65B8F4;
border-radius: 17px;
margin: 40px auto 0;
cursor: pointer;
}
.plateWrap {
border:2px dashed rgba(208,208,208,1);
padding: 20px;
margin-bottom: 30px;
position: relative;
}
.plateDelete {
position: absolute;
right: -20px;
top: -25px;
font-size: 30px;
color: #FF6060;
cursor: pointer;
}
.progressWrap {
width: 300px;
height: 300px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f1f1f1;
border-radius: 15px;
z-index: 9999;
&>p {
font-size: 18px;
margin-top: 30px;
color: #1e8bff;
}
}
\ No newline at end of file
......@@ -4,7 +4,8 @@ import {
Form,
Input,
Modal,
Checkbox,
Checkbox, Pagination,
Divider,
} from 'antd';
import pageStyle from './MarketingListModal.less';
import { pageIn, hasBtnPower, imagify, ossVideofy } from '../../../utils/index';
......@@ -34,14 +35,27 @@ class MarketingListModal extends React.Component {
const { createMarke } = this.props;
createMarke();
}
changePagination = (page, perPage) => {
const { changePagination } = this.props;
changePagination({ page, perPage });
}
editMarke = (item) => {
const { editMarke } = this.props;
editMarke(item);
}
deleteMarke = (item) => {
const { deleteMarke } = this.props;
deleteMarke(item);
}
render() {
const {
visible,
form: { getFieldDecorator, getFieldValue },
list,
id,
landingParams,
landingTotal,
} = this.props;
console.log(list);
return (
<Modal
visible={visible}
......@@ -58,7 +72,7 @@ class MarketingListModal extends React.Component {
<div className={pageStyle.container}>
<div className={pageStyle.list}>
{
list.length > 0 && list.map((item, index) => {
list && list.length > 0 && list.map((item, index) => {
return (
<div className={pageStyle.item}>
<div className={pageStyle.itemLeft}>
......@@ -72,8 +86,11 @@ class MarketingListModal extends React.Component {
<div className={pageStyle.itemFlex}>
<div className={pageStyle.now}>{item.id == id ? '当前使用' : ''}</div>
<div>
<span className={pageStyle.nowBtn} onClick={() => this.getID(item)}>立即使用</span>
<span className={pageStyle.lookBtn}>查看模板</span>
<span className={pageStyle.lookBtn} onClick={() => this.getID(item)}>立即使用</span>
<Divider type="vertical" />
<span className={pageStyle.lookBtn} onClick={() => this.editMarke(item)}>编辑</span>
<Divider type="vertical" />
<span className={pageStyle.lookBtn} onClick={() => this.deleteMarke(item)}>删除</span>
</div>
</div>
</div>
......@@ -82,6 +99,13 @@ class MarketingListModal extends React.Component {
})
}
</div>
<div className={pageStyle.page}>
<Pagination
total={Number(landingTotal)}
onChange={this.changePagination}
pageSize={landingParams.perPage}
/>
</div>
<div className={pageStyle.footer} onClick={this.createMarke}>
新建打卡营销页
</div>
......
......@@ -2,7 +2,8 @@
.item {
display: flex;
align-items: center;
margin-bottom: 15px;
border-bottom: 1px solid #e8e8e8;
padding: 15px 0;
.itemLeft {
width: 128px;
height: 100px;
......@@ -32,7 +33,8 @@
font-size: 12px;
margin-bottom: 10px;
word-break: break-all;
color: #666666
color: #666666;
min-width: 36px;
}
.itemFlex {
display: flex;
......@@ -42,26 +44,11 @@
color: #FEA917;
font-size: 12px;
}
.nowBtn {
font-size: 12px;
color: #1890ff;
border-radius: 2px;
display: inline-block;
width: 52px;
height: 20px;
text-align: center;
line-height: 20px;
margin-right: 20px;
cursor: pointer;
}
.lookBtn {
font-size: 12px;
color: #1890ff;
display: inline-block;
width: 52px;
height: 20px;
text-align: center;
line-height: 20px;
line-height: 35px;
cursor: pointer;
}
}
......@@ -73,4 +60,10 @@
line-height: 45px;
color: #16B0FD;
cursor: pointer;
width: 150px;
margin: 0 auto;
}
.page {
text-align: right;
margin-top: 10px;
}
......@@ -242,12 +242,114 @@ class JobClockForm extends React.Component {
addMarke = () => {
const { dispatch } = this.props;
dispatch({
type: 'createtheme/updateState',
type: 'createmarke/updateState',
payload: {
addMarkeVisible: true,
},
});
}
handleChangeMarke = ({ page, perPage }) => {
const { dispatch } = this.props;
dispatch({
type: 'createtheme/queryLandingList',
payload: {
params: {
page,
perPage,
},
},
});
}
editMarke = (item) => {
const { dispatch } = this.props;
dispatch({
type: 'createmarke/editMarke',
payload: {
item,
},
});
}
deleteMarke = (item) => {
const { dispatch } = this.props;
Modal.confirm({
title: '确认要删除这个营销页吗?',
okText: '确认',
cancelText: '取消',
okType: 'danger',
centered: true,
okButtonProps: {
type: 'danger',
style: {
color: '#fff',
backgroundColor: '#ff4d4f',
borderColor: '#ff4d4f',
},
},
icon: <Icon type="close-circle" style={{ color: 'red' }} />,
onOk: () => {
dispatch({
type: 'createmarke/deleteMarke',
payload: {
item,
},
});
},
onCancel: () => {
},
});
}
handleChangeRadio = (e) => {
const { dispatch } = this.props;
if (Number(e.target.value) == 2 || Number(e.target.value) == 4) {
dispatch({
type: 'createtheme/updateState',
payload: {
clockClassVisible: true,
},
});
}
}
closeClassList = () => {
const { dispatch } = this.props;
dispatch({
type: 'createtheme/updateState',
payload: {
clockClassVisible: false,
classes: [],
chooseClasses: [],
},
});
}
changeCourseClass = (type) => {
const { dispatch } = this.props;
dispatch({
type: 'createtheme/queryCourseClassList',
payload: {
params: {
extra: type,
},
},
});
}
handleChooseClass = ({ classes, chooseClasses }) => {
const { dispatch } = this.props;
dispatch({
type: 'createtheme/updateState',
payload: {
classes,
chooseClasses,
},
});
}
saveClasses = (classes) => {
const { dispatch } = this.props;
dispatch({
type: 'createtheme/updateState',
payload: {
classes,
},
});
}
render() {
const { isShow, textLength } = this.state;
const {
......@@ -262,6 +364,13 @@ class JobClockForm extends React.Component {
landingList,
themeAdInfo,
addMarkeVisible,
landingParams,
landingTotal,
clockClassVisible,
clockClassList,
clockClassParams,
classes,
chooseClasses,
} = this.props;
const formItemModalLineLayout = {
labelCol: {
......@@ -406,17 +515,48 @@ class JobClockForm extends React.Component {
{getFieldDecorator('join_rule_type', {
initialValue: '',
})(
<Radio.Group>
<Radio.Group onChange={this.handleChangeRadio}>
<Radio style={radioStyle} value="1"><div className={pageStyle.radioText}>任何人都可参与&nbsp;<span>(无通知)</span></div></Radio>
<Radio style={radioStyle} value="2"><div className={pageStyle.radioText}>任何人都可参与&nbsp;<span>(指定班级学生收到通知)</span></div></Radio>
<Radio style={radioStyle} value="3"><div className={pageStyle.radioText}>输入密码都可参与&nbsp;<span>(无通知)</span></div></Radio>
<Radio style={radioStyle} value="4"><div className={pageStyle.radioText}>指定班级学生参与</div></Radio>
<Radio style={radioStyle} value="3"><div className={pageStyle.radioText}>输入密码都可参与&nbsp;<span>(无通知)</span></div>
</Radio>
</Radio.Group>,
)}
{Number(getFieldValue('join_rule_type')) == 3 ?
<div>
<FormItem {...formItemModaltypeLayout} label="" style={{ marginBottom: 0 }}>
{getFieldDecorator('join_secret', {
initialValue: '',
rules: [
{
message: '请输入正整数',
pattern: new RegExp(/^[0-9]\d*$/, 'g'),
},
],
})(
<Input style={{ width: 200 }} maxLength={6} placeholder="请设置密码" />,
)}
</FormItem>
</div>
: null}
</FormItem>
</div>
</div>
{Number(getFieldValue('join_rule_type')) == 2 ? <ChooseClass /> : null}
{(Number(getFieldValue('join_rule_type')) == 2 || Number(getFieldValue('join_rule_type')) == 4) ?
<ChooseClass
visible={clockClassVisible}
close={this.closeClassList}
list={clockClassList}
clockClassParams={clockClassParams}
changeCourseClass={this.changeCourseClass}
classes={classes}
chooseClasses={chooseClasses}
choose={this.handleChooseClass}
saveClasses={this.saveClasses}
/>
: null
}
<div className={pageStyle.commonwrap}>
<div className={pageStyle.commonleft}>
学生作业要求:
......@@ -553,9 +693,14 @@ class JobClockForm extends React.Component {
visible={marketingVisible}
list={landingList}
id={themeAdInfo.id}
landingTotal={landingTotal}
landingParams={landingParams}
close={this.closeMarketing}
getID={this.getMarketingId}
createMarke={this.addMarke}
changePagination={this.handleChangeMarke}
editMarke={this.editMarke}
deleteMarke={this.deleteMarke}
/>
<AddMarkeModal
visible={addMarkeVisible}
......@@ -585,8 +730,17 @@ function mapStateToProps(state) {
marketingVisible,
landingList,
themeAdInfo,
addMarkeVisible,
landingParams,
landingTotal,
clockClassVisible,
clockClassList,
clockClassParams,
classes,
chooseClasses,
} = state.createtheme;
const {
addMarkeVisible,
} = state.createmarke;
return {
themeAddLoading,
classList,
......@@ -598,6 +752,13 @@ function mapStateToProps(state) {
landingList,
themeAdInfo,
addMarkeVisible,
landingParams,
landingTotal,
clockClassVisible,
clockClassList,
clockClassParams,
classes,
chooseClasses,
};
}
export default connect(mapStateToProps)(JobClock);
......
......@@ -117,10 +117,10 @@ export function UnlockStudentExport(params) {
data,
});
}
export function getClassList(params) {
export function getCourseClassList(params) {
const data = qs.stringify(params);
return request({
url: `${api.createtheme.classList}?${data}`,
url: `${api.createtheme.classCourseList}?${data}`,
method: 'GET',
data,
});
......@@ -133,4 +133,35 @@ export function getlandingList(params) {
data,
});
}
export function addMarke(params) {
const data = qs.stringify(params);
return request({
url: `${api.createtheme.landing_pages}`,
method: 'POST',
data,
});
}
export function editMarke(params) {
const data = qs.stringify(params);
return request({
url: `${api.createtheme.landing_pages}/${params.id}`,
method: 'PUT',
data,
});
}
export function deleteMarke(params) {
const data = qs.stringify(params);
return request({
url: `${api.createtheme.landing_pages}/${params.id}`,
method: 'DELETE',
data,
});
}
export function markeDetail(params) {
const data = qs.stringify(params);
return request({
url: `${api.createtheme.landing_pages}/${params.id}`,
method: 'GET',
data,
});
}
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