Commit d6d9fddf authored by baixian's avatar baixian

优化

parent 654825d3
......@@ -223,5 +223,7 @@ export default {
},
crm: {
crmList: `${dakaapi}member/crm/clues`,
clue_records: `${dakaapi}member/crm/clue_records`,
clue_logs: `${dakaapi}member/crm/clue_logs`,
},
};
This diff is collapsed.
......@@ -152,7 +152,7 @@ export default {
activeurl: `${__IMGCDN__}menu/crmactive.png`,
notactiveurl: `${__IMGCDN__}menu/crm.png`,
path: '/sjd/crm',
relativePath: ['/sjd/crm', '/sjd/addcrm', '/sjd/crmdetail/:id'],
relativePath: ['/sjd/crm', '/sjd/addcrm', '/sjd/crmdetail/:id', '/sjd/editcrm/:id'],
},
{
id: '30',
......
......@@ -31,6 +31,7 @@ export default {
remark: '',
birthday: '',
gender: 1,
customers: [],
},
},
subscriptions: {
......@@ -80,6 +81,45 @@ export default {
});
}
},
* queryCrmDetail({ payload }, { call, put, select }) {
const { id } = payload;
const loading = message.loading('数据加载中...', 0.5);
const data = yield call(crmAjax.crmDetail, {
id,
});
setTimeout(loading);
if (data.code == 200) {
if (data.data.reserve_mobile.length > 0) {
if (data.data.reserve_mobile && (typeof data.data.reserve_mobile == 'string')) {
data.data.reserve_mobile = JSON.parse(data.data.reserve_mobile);
}
}
yield put({
type: 'updateState',
payload: {
editCrmInfo: {
id: data.data.id,
reserve_mobile: [...data.data.reserve_mobile],
name: data.data.name,
mobile: data.data.mobile,
source_type: data.data.source_type,
intent_course_id: data.data.intent_course ? data.data.intent_course.id : '',
remark: data.data.remark,
birthday: data.data.birthday,
gender: data.data.gender,
customers: data.data.customers ? data.data.customers : [],
},
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* saveCrm({ payload }, { call, put, select }) {
const { sid } = yield select(state => state.webapp);
const {
......@@ -189,6 +229,7 @@ export default {
remark: '',
birthday: '',
gender: 1,
customers: [],
},
courseList: [],
operator: {
......
......@@ -11,11 +11,12 @@ import {
} from '../utils/index';
import * as crmAjax from '../services/crm';
import errorcode from '../common/errorcode';
import * as classMgtAjax from '../services/classmgt';
import * as courseAjax from '../services/course';
import * as studentsAjax from '../services/students';
export default {
namespace: 'crm',
state: {
courseList: [],
crmParams: {
page: 1,
perPage: 10,
......@@ -29,6 +30,18 @@ export default {
},
crmTotal: 0,
crmList: [],
checkedStatus: 1,
statusVisible: false,
statusLoading: false,
cluesId: 0,
operator: {
id: '',
nickname: '',
},
courseRelateClassList: [],
courseList: [],
goRenewCourseShow: false,
studentInfo: {},
},
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-line
......@@ -44,6 +57,11 @@ export default {
},
},
});
yield put({
type: 'queryCoureList',
payload: {
},
});
},
* queryCrmList({ payload }, { call, put, select }) {
const { params } = payload;
......@@ -80,7 +98,6 @@ export default {
}
},
* reset({ payload }, { call, put, select }) {
const { copyClassListQueryParams } = yield select(state => state.classmgt);
yield put({
type: 'queryCrmList',
payload: {
......@@ -109,6 +126,143 @@ export default {
pathname: `/sjd/crmdetail/${id}`,
}));
},
* saveStatus({ payload }, { call, put, select }) {
const { statusLoading, cluesId } = yield select(state => state.crm);
if (statusLoading) {
return;
}
yield put({
type: 'updateState',
payload: {
statusLoading: true,
},
});
const {
status,
callBack,
} = payload;
const loadmessage = message.loading('保存中...', 0);
const data = yield call(crmAjax.crmEdit, {
status,
id: cluesId,
});
yield put({
type: 'updateState',
payload: {
statusLoading: false,
},
});
setTimeout(loadmessage);
if (data.code == 200) {
message.success('修改成功', 1);
yield put({
type: 'updateState',
payload: {
statusVisible: false,
},
});
if (callBack && (typeof callBack == 'function')) {
callBack();
}
yield put({
type: 'queryCrmList',
payload: {
params: {
page: 1,
},
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* searchCourseRelateClass({ payload }, { call, put, select }) {
const { sid } = yield select(state => state.webapp);
const { courseId, callBack } = payload;
const classListData = yield call(classMgtAjax.getClassList, {
page: 1,
perPage: 1000,
school_id: sid,
course_id: courseId,
graduation_status: 1,
});
yield put({
type: 'updateState',
payload: {
courseRelateClassList: (classListData.data && classListData.data.list) || [],
},
});
if (callBack && (typeof callBack) == 'function') {
callBack();
}
},
* queryCoureList({ payload }, { call, put, select }) {
const { sid } = yield select(state => state.webapp);
const courselistinfo = yield call(courseAjax.courseList, {
school_id: sid,
page: 1,
perPage: 100,
});
if (courselistinfo.code == 200 && courselistinfo.data) {
yield put({
type: 'updateState',
payload: {
courseList: (courselistinfo.data && courselistinfo.data.list) || [],
},
});
}
},
// eslint-disable-next-line require-yield
* studentModelCoursesBuyOrRenew({ payload }, { call, put, select }) {
const { studentInfo } = yield select(state => state.crm);
const { sid } = yield select(state => state.webapp);
const { values, callBack } = payload;
const loadmessage = message.loading('购买中...', 0);
const data = yield call(studentsAjax.studentsAdd, {
school_id: sid,
name: studentInfo.name,
birthday: studentInfo.birthday,
mobile: studentInfo.mobile,
avatar: '',
remark: studentInfo.remark,
gender: studentInfo.gender,
force: 1,
operator_id: studentInfo.final_follow_id,
});
// eslint-disable-next-line no-empty
if (data.code === 200) {
}
// const buyOrRenewData = yield call(courseAjax.coursesBuyOrRenew, Object.assign(values, {
// class_ids: (values.class_ids && values.class_ids.join(',')) || '',
// student_id: selectdeStudent.id,
// }));
// setTimeout(loadmessage);
// if (buyOrRenewData.code == 200) {
// yield put({
// type: 'updateState',
// payload: {
// goRenewCourseShow: false,
// },
// });
// message.success('保存成功', 1);
// if (callBack && (typeof callBack) == 'function') {
// callBack();
// }
// } else {
// yield put({
// type: 'webapp/errorrequestresolve',
// payload: {
// data: buyOrRenewData,
// },
// });
// }
},
* pageInit({ payload }, { call, put, select }) {
yield put({
type: 'updateState',
......@@ -126,6 +280,18 @@ export default {
},
crmTotal: 0,
crmList: [],
checkedStatus: 1,
statusVisible: false,
statusLoading: false,
cluesId: 0,
operator: {
id: '',
nickname: '',
},
courseRelateClassList: [],
courseList: [],
goRenewCourseShow: false,
studentInfo: {},
},
});
},
......
import { routerRedux } from 'dva/router';
import { message } from 'antd';
import { delay } from 'redux-saga';
import moment from 'moment';
import {
LocalStorage,
SessionStorage,
isExpired,
getRandomFilename,
} from '../utils/index';
import * as crmAjax from '../services/crm';
import errorcode from '../common/errorcode';
import * as courseAjax from '../services/course';
export default {
namespace: 'crmdetail',
state: {
crmDetail: {},
addLogVisible: false,
addLogLoading: false,
clueParams: {
page: 1,
perPage: 10,
},
clueTotal: 0,
clueList: [],
activeIndex: 1,
logParams: {
page: 1,
perPage: 999,
},
logList: [],
},
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-line
},
},
effects: {
* queryinfo({ payload }, { call, put, select }) {
const { id } = payload;
yield put({
type: 'queryCrmDetail',
payload: {
id,
},
});
yield put({
type: 'queryClueList',
payload: {
params: {
clue_id: id,
},
},
});
yield put({
type: 'queryLogsList',
payload: {
params: {
clue_id: id,
},
},
});
},
* queryClueList({ payload }, { call, put, select }) {
const { params } = payload;
const { clueParams, clueTotal } = yield select(state => state.crmdetail);
const loading = message.loading('数据加载中...', 0.5);
const newParams = Object.assign(clueParams, params);
let newTotal = clueTotal;
const data = yield call(crmAjax.clueList, {
...newParams,
});
setTimeout(loading);
if (data.code == 200) {
if (data.data.total != undefined) {
newTotal = data.data.total;
}
yield put({
type: 'updateState',
payload: {
clueList: data.data && data.data.list,
clueTotal: newTotal,
clueParams: { ...newParams },
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* queryLogsList({ payload }, { call, put, select }) {
const { params } = payload;
const { logParams } = yield select(state => state.crmdetail);
const newParams = Object.assign(logParams, params);
const data = yield call(crmAjax.logsList, {
...newParams,
});
if (data.code == 200) {
yield put({
type: 'updateState',
payload: {
logList: data.data && data.data.list,
logParams: { ...newParams },
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* queryCrmDetail({ payload }, { call, put, select }) {
const { id } = payload;
const data = yield call(crmAjax.crmDetail, {
id,
});
if (data.code == 200) {
if (data.data.reserve_mobile.length > 0) {
if (data.data.reserve_mobile && (typeof data.data.reserve_mobile == 'string')) {
data.data.reserve_mobile = JSON.parse(data.data.reserve_mobile);
}
}
yield put({
type: 'updateState',
payload: {
crmDetail: { ...data.data },
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* saveLog({ payload }, { call, put, select }) {
const { addLogLoading, crmDetail } = yield select(state => state.crmdetail);
if (addLogLoading) {
return;
}
yield put({
type: 'updateState',
payload: {
addLogLoading: true,
},
});
const {
content,
type,
callBack,
} = payload;
const loadmessage = message.loading('保存中...', 0);
const data = yield call(crmAjax.addClueRecords, {
content,
type,
clue_id: crmDetail.id,
});
yield put({
type: 'updateState',
payload: {
addLogLoading: false,
},
});
setTimeout(loadmessage);
if (data.code == 200) {
message.success('添加成功', 1);
yield put({
type: 'updateState',
payload: {
addLogVisible: false,
},
});
if (callBack && (typeof callBack == 'function')) {
callBack();
}
yield put({
type: 'queryClueList',
payload: {
params: {
clue_id: crmDetail.id,
page: 1,
},
},
});
} else {
yield put({
type: 'webapp/errorrequestresolve',
payload: {
data,
},
});
}
},
* goEditCrm({ payload }, { put, select }) {
const { id } = payload;
yield put(routerRedux.push({
pathname: `/sjd/editcrm/${id}`,
}));
},
* pageInit({ payload }, { call, put, select }) {
yield put({
type: 'updateState',
payload: {
crmDetail: {},
addLogVisible: false,
addLogLoading: false,
activeIndex: 1,
logParams: {
page: 1,
perPage: 10,
},
logList: [],
},
});
},
},
reducers: {
save(state, action) {
return { ...state, ...action.payload };
},
updateState(state, action) {
return { ...state, ...action.payload };
},
},
};
......@@ -53,6 +53,7 @@ import personmanage from './personmanage';
import rolemanage from './rolemanage';
import crm from './crm';
import addcrm from './addcrm';
import crmdetail from './crmdetail';
export default {
loginModel,
indexstaicModel,
......@@ -100,4 +101,5 @@ export default {
rolemanage,
crm,
addcrm,
crmdetail,
};
......@@ -881,12 +881,12 @@ export default {
}
const potentialactive = pathToRegexp('/sjd/crmdetail/:id').exec(pathname);
if (potentialactive) {
// dispatch({
// type: 'onlineclasses/findCourse',
// payload: {
// id: potentialactive[1],
// },
// });
dispatch({
type: 'crmdetail/queryinfo',
payload: {
id: potentialactive[1],
},
});
dispatch({
type: 'updateState',
payload: {
......@@ -925,6 +925,35 @@ export default {
},
});
}
const editcrmactive = pathToRegexp('/sjd/editcrm/:id').exec(pathname);
if (editcrmactive) {
dispatch({
type: 'addcrm/queryinfo',
payload: {
},
});
dispatch({
type: 'addcrm/queryCrmDetail',
payload: {
id: editcrmactive[1],
},
});
dispatch({
type: 'updateState',
payload: {
breadcrumbList: [
{
path: 'sjd/crm',
name: '潜客管理',
},
{
path: pathname,
name: '编辑潜在学员',
},
],
},
});
}
if (pathname === '/sjd/singleclass') {
dispatch({
type: 'onlineclasses/pageInit',
......@@ -1265,10 +1294,18 @@ export default {
yield put({
type: 'permissionsetting/pageInit',
});
} else if (pathToRegexp('/sjd/addpotential').exec(locationPathname)) {
} else if (pathToRegexp('/sjd/crm').exec(locationPathname)) {
yield put({
type: 'crm/pageInit',
});
} else if (pathToRegexp('/sjd/addcrm').exec(locationPathname)) {
yield put({
type: 'addcrm/pageInit',
});
} else if (pathToRegexp('/sjd/editcrm/:id').exec(locationPathname)) {
yield put({
type: 'addcrm/pageInit',
});
}
// yield put({
// type: 'joinschooladd/pageInit',
......
......@@ -31,7 +31,6 @@ class AddCrmForm extends React.Component {
constructor(props) {
super(props);
this.state = {
age: 0,
};
}
componentDidMount() { // 挂载
......@@ -50,14 +49,12 @@ class AddCrmForm extends React.Component {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log(values, 'values');
const {
name,
mobile,
source_type,
intent_course_id,
remark,
reserve_mobile,
birthday,
gender,
} = values;
......@@ -67,7 +64,7 @@ class AddCrmForm extends React.Component {
name,
mobile,
source_type,
intent_course_id,
intent_course_id: intent_course_id === undefined ? 0 : intent_course_id,
remark,
birthday: birthday ? birthday.format('YYYY-MM-DD') : '',
gender,
......@@ -113,14 +110,6 @@ class AddCrmForm extends React.Component {
},
});
}
handleChangeAge = (dateString) => {
if (!dateString) {
this.state.age = 0;
return;
}
const toyear = new Date().getFullYear() + 1;
this.state.age = toyear - dateString.format('YYYY') || 0;
}
render() {
const {
form: { getFieldDecorator, getFieldValue },
......@@ -128,7 +117,6 @@ class AddCrmForm extends React.Component {
courseList,
operator,
} = this.props;
const { age } = this.state;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
......@@ -147,7 +135,7 @@ class AddCrmForm extends React.Component {
xxl: { span: 21 },
},
};
const formItems = editCrmInfo.reserve_mobile.map((k, index) => {
const formItems = editCrmInfo && editCrmInfo.reserve_mobile.map((k, index) => {
const newIndex = index;
return (
<Form.Item
......@@ -158,16 +146,17 @@ class AddCrmForm extends React.Component {
colon={index === 0}
>
{getFieldDecorator(`names[${index}]`, {
initialValue: k,
rules: [
{ required: false, message: '请输入您的手机号!' },
{ pattern: /^1[3456789]{1}[0-9]{9}$/, message: '请输入正确的手机号码!' },
{ max: 11, message: '手机号长度为11位!' },
],
})(
<Input value={k} style={{ width: 260 }} placeholder="请输入手机号码" onChange={e => this.mobileChange(e, newIndex)} maxLength={11} />,
)}
})(
<Input style={{ width: 260 }} placeholder="请输入手机号码" onChange={e => this.mobileChange(e, newIndex)} maxLength={11} />,
)}
{/* <Input value={k} style={{ width: 260 }} placeholder="请输入手机号码" onChange={e => this.mobileChange(e, newIndex)} maxLength={11} /> */}
{editCrmInfo.reserve_mobile.length > 1 ? (
{editCrmInfo.reserve_mobile.length > 1 && index != 0 ? (
<Button size="small" className={pageStyle.delmobile} type="danger" onClick={() => this.delMobile(newIndex)}>删除</Button>
) : null}
{index === 0 ? (
......@@ -184,7 +173,10 @@ class AddCrmForm extends React.Component {
<FormItem {...formItemLayout} label="学员姓名">
{getFieldDecorator('name', {
initialValue: editCrmInfo.name,
rules: [{ required: true, message: '学员姓名不能为空' }],
rules: [
{ required: true, message: '学员姓名不能为空' },
{ max: 20, message: '学员姓名不能超过20位!' },
],
})(
<Input style={{ width: 260 }} placeholder="请填写学员姓名" />,
)}
......@@ -198,7 +190,7 @@ class AddCrmForm extends React.Component {
{ max: 11, message: '手机号长度为11位!' },
],
})(
<Input style={{ width: 260 }} placeholder="请填写联系方式" />,
<Input disabled={editCrmInfo.id} style={{ width: 260 }} placeholder="请填写联系方式" />,
)}
<p className={pageStyle.mobileTip}>此号码用于接收上课与预约通知相关短信</p>
</FormItem>
......@@ -214,18 +206,20 @@ class AddCrmForm extends React.Component {
</FormItem>
<FormItem {...formItemLayout} label="生日日期">
{getFieldDecorator('birthday', {
initialValue: editCrmInfo.birthday,
initialValue: editCrmInfo.birthday ? moment(editCrmInfo.birthday) : '',
})(
<DatePicker disabledDate={this.disabledDate} allowClear onChange={this.handleChangeAge} style={{ width: 134 }} />,
)}
<span style={{ marginLeft: 12 }}>年龄:{age}</span>
</FormItem>
{formItems}
<FormItem {...formItemLayout} label="备注">
{getFieldDecorator('remark', {
initialValue: editCrmInfo.remark,
rules: [
{ max: 200, message: '备注不能超过200字!' },
],
})(
<TextArea rows={4} style={{ width: 260 }} placeholder="请输入备注" />,
<TextArea rows={4} maxLength={210} style={{ width: 260 }} placeholder="请输入备注" />,
)}
</FormItem>
<div className={pageStyle.commonTitle}>售卖信息</div>
......@@ -234,7 +228,7 @@ class AddCrmForm extends React.Component {
initialValue: editCrmInfo.source_type ? editCrmInfo.source_type : undefined,
rules: [{ required: true, message: '来源渠道不能为空' }],
})(
<Select style={{ width: 260 }} placeholder="请选择">
<Select disabled={editCrmInfo.id} style={{ width: 260 }} placeholder="请选择">
<Option value={1}>在线购买</Option>
<Option value={2}>在线招生</Option>
<Option value={3}>来电咨询</Option>
......@@ -249,7 +243,6 @@ class AddCrmForm extends React.Component {
<FormItem {...formItemLayout} label="意向课程">
{getFieldDecorator('intent_course_id', {
initialValue: editCrmInfo.intent_course_id ? editCrmInfo.intent_course_id : undefined,
rules: [{ required: true, message: '意向课程不能为空' }],
})(
<Select style={{ width: 260 }} placeholder="请选择">
{courseList.map(ele => <Option value={ele.id}>{ele.title}</Option>)}
......
import { connect } from 'dva';
import React from 'react';
import PropTypes from 'prop-types';
import { message, Row, Col, Input, Select, Modal, Form, InputNumber, Checkbox, Radio, Tabs } from 'antd';
import { imagify, pageIn } from '../../utils/index';
const FormItem = Form.Item;
const { TabPane } = Tabs;
const { TextArea } = Input;
const { Option } = Select;
class ChangeStatusModal extends React.Component {
componentDidMount() { // 挂载
}
componentWillUnmount() { // 卸载
}
save = () => {
const { form, save } = this.props;
form.validateFields((err, values) => {
if (!err) {
const {
status,
} = values;
save({
status,
callBack: () => {
form.resetFields();
},
});
}
});
}
close = () => {
const { form, close } = this.props;
form.resetFields();
close();
}
render() {
const {
visible,
loading,
status,
form: { getFieldDecorator, getFieldValue },
} = this.props;
const formItemModalLineLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 19 },
},
};
return (
<Modal
visible={visible}
title="跟进状态"
okText="保存"
confirmLoading={loading}
onCancel={this.close}
onOk={this.save}
maskClosable={false}
zIndex={110}
width={500}
bodyStyle={{ padding: 20 }}
>
<Form hideRequiredMark labelAlign="left">
<FormItem label="当前跟进状态" {...formItemModalLineLayout}>
{getFieldDecorator('status', {
initialValue: Number(status),
rules: [{ required: true, message: '当前跟进状态不能为空' }],
})(
<Select style={{ width: 260 }} placeholder="请选择">
<Option value={1}>待跟进</Option>
<Option value={2}>跟进中</Option>
<Option value={3}>已完成</Option>
<Option value={4}>无效</Option>
</Select>,
)}
</FormItem>
</Form>
</Modal>
);
}
}
const ChangeStatus = Form.create()(ChangeStatusModal);
export default ChangeStatus;
......@@ -22,6 +22,8 @@ import moment from 'moment';
import { routerRedux } from 'dva/router';
import pageStyle from './index.less';
import { pageIn, hasBtnPower, btnPermission } from '../../utils/index';
import ChangeStatusModal from './ChangeStatusModal';
import RenewEdit from '../student/RenewEdit';
import BtnPermission from '../../components/BtnPermission';
const { Option } = Select;
const { TextArea } = Input;
......@@ -68,7 +70,6 @@ class Crm extends React.Component {
}
searchParamsChange = (e, type, key) => {
const { dispatch } = this.props;
console.log(e, '11');
let getvalue = '';
if (type == 'Input') {
getvalue = e;
......@@ -130,12 +131,99 @@ class Crm extends React.Component {
},
});
}
handleChangeStatus = (record) => {
const { dispatch } = this.props;
dispatch({
type: 'crm/updateState',
payload: {
statusVisible: true,
checkedStatus: record.status,
cluesId: record.id,
},
});
}
closeStatusModal = () => {
const { dispatch } = this.props;
dispatch({
type: 'crm/updateState',
payload: {
statusVisible: false,
checkedStatus: 1,
cluesId: 0,
},
});
}
saveStatus = (values) => {
const { dispatch } = this.props;
dispatch({
type: 'crm/saveStatus',
payload: values,
});
}
goBuyCourse = (record) => {
const { dispatch, schoolUserInfo } = this.props;
console.log(record, 'record');
dispatch({
type: 'crm/updateState',
payload: {
goRenewCourseShow: true,
operator: { ...schoolUserInfo },
studentInfo: { ...record },
},
});
}
searchCourseRelateClass = (courseId, callBack) => {
const { dispatch } = this.props;
dispatch({
type: 'crm/searchCourseRelateClass',
payload: {
courseId,
callBack: callBack || '',
},
});
}
closeEditClassModal = () => {
const { dispatch } = this.props;
dispatch({
type: 'crm/updateState',
payload: {
goRenewCourseShow: false,
studentInfo: {},
operator: {
id: '',
nickname: '',
},
},
});
}
sureRenewCourse = (values, callBack) => {
const { dispatch } = this.props;
dispatch({
type: 'crm/studentModelCoursesBuyOrRenew',
payload: {
values: {
...values,
buy: values.buy || 0,
give: values.give || 0,
},
callBack,
},
});
}
render() {
const {
userPermission,
crmList,
crmTotal,
crmParams,
statusVisible,
checkedStatus,
statusLoading,
goRenewCourseShow,
courseRelateClassList,
courseList,
operator,
schoolUserInfo,
} = this.props;
const { isExpendMore } = this.state;
const columns = [
......@@ -222,8 +310,6 @@ class Crm extends React.Component {
return '已完成';
case 4:
return '无效';
case 5:
return '已成功';
default:
return '-';
}
......@@ -246,9 +332,9 @@ class Crm extends React.Component {
render: (text, record) => {
return (
<div className={pageStyle.tableoperatebox}>
<span className="hreflink">购课</span>
<span className="hreflink" onClick={() => this.goBuyCourse(record)}>购课</span>
<Divider type="vertical" />
<span className="hreflink">跟进</span>
<span className="hreflink" onClick={() => this.handleChangeStatus(record)}>跟进</span>
</div>
);
},
......@@ -290,10 +376,8 @@ class Crm extends React.Component {
<Option value="">全部</Option>
<Option value={1}>待跟进</Option>
<Option value={2}>跟进中</Option>
<Option value={3}>已邀约</Option>
<Option value={4}>已试听</Option>
<Option value={5}>已完成</Option>
<Option value={6}>无效</Option>
<Option value={3}>已完成</Option>
<Option value={4}>无效</Option>
</Select>
</Col>
{
......@@ -361,6 +445,23 @@ class Crm extends React.Component {
/>
</div>
</div>
<ChangeStatusModal
visible={statusVisible}
close={this.closeStatusModal}
status={checkedStatus}
save={this.saveStatus}
loading={statusLoading}
/>
<RenewEdit
visible={goRenewCourseShow}
searchCourseRelateClass={this.searchCourseRelateClass}
classList={courseRelateClassList}
renewCourses={courseList}
operator={operator}
close={this.closeEditClassModal}
save={this.sureRenewCourse}
isOperator
/>
</div>
);
}
......@@ -375,6 +476,13 @@ function mapStateToProps(state) {
crmList,
crmTotal,
crmParams,
statusVisible,
checkedStatus,
statusLoading,
goRenewCourseShow,
courseRelateClassList,
courseList,
operator,
} = state.crm;
const {
guideStep,
......@@ -382,6 +490,7 @@ function mapStateToProps(state) {
} = state.userguide;
const {
userPermission,
schoolUserInfo,
} = state.webapp;
return {
userPermission,
......@@ -390,6 +499,14 @@ function mapStateToProps(state) {
crmList,
crmTotal,
crmParams,
statusVisible,
checkedStatus,
statusLoading,
goRenewCourseShow,
courseRelateClassList,
courseList,
operator,
schoolUserInfo,
};
}
export default connect(mapStateToProps)(CrmForm);
......
import { connect } from 'dva';
import React from 'react';
import PropTypes from 'prop-types';
import { message, Row, Col, Input, Select, Modal, Form, InputNumber, Checkbox, Radio, Tabs } from 'antd';
import { imagify, pageIn } from '../../utils/index';
const FormItem = Form.Item;
const { TabPane } = Tabs;
const { TextArea } = Input;
class AddLogModal extends React.Component {
componentDidMount() { // 挂载
}
componentWillUnmount() { // 卸载
}
save = () => {
const { form, save } = this.props;
form.validateFields((err, values) => {
if (!err) {
const {
content,
type,
} = values;
save({
content,
type,
callBack: () => {
form.resetFields();
},
});
}
});
}
close = () => {
const { form, close } = this.props;
form.resetFields();
close();
}
render() {
const {
visible,
loading,
form: { getFieldDecorator, getFieldValue },
} = this.props;
const formItemModalLineLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 },
},
};
return (
<Modal
visible={visible}
title="添加跟进记录"
okText="发布"
confirmLoading={loading}
onCancel={this.close}
onOk={this.save}
maskClosable={false}
zIndex={110}
width={500}
bodyStyle={{ padding: 20 }}
>
<Form hideRequiredMark labelAlign="left">
<FormItem label="结果情况" {...formItemModalLineLayout}>
{getFieldDecorator('content', {
initialValue: '',
rules: [{ required: true, message: '结果情况不能为空' }],
})(
<TextArea rows={4} placeholder="请输入跟进的结果情况" />,
)}
</FormItem>
<FormItem label="沟通方式" {...formItemModalLineLayout}>
{getFieldDecorator('type', {
initialValue: '1',
rules: [{ required: true, message: '沟通方式不能为空' }],
})(
<Radio.Group>
<Radio value="1">微信沟通</Radio>
<Radio value="2">电话沟通</Radio>
</Radio.Group>,
)}
</FormItem>
</Form>
</Modal>
);
}
}
const AddLog = Form.create()(AddLogModal);
export default AddLog;
This diff is collapsed.
@import '../../less/variables.less';
.container {
background-color: #fff;
border-radius: 2px;
padding: 20px;
}
.headerbox{
background-color: #fff;
.headerbtn {
margin: 0 16px 0 0;
.headBox {
display: flex;
align-items: flex-start;
.leftBox {
width: 56px;
height: 56px;
margin-right: 30px;
&>img {
width: 56px;
height: 56px;
}
}
padding-bottom: 18px;
}
.searchrow {
.formitem {
display: flex;
align-items: center;
margin-bottom: 16px;
.formitemlabel {
color: #000000;
font-size: 14px;
line-height: 1;
min-width: 70px;
white-space: nowrap;
.rightBox {
width: calc(100% - 90px);
:global {
.ant-descriptions-row > th, .ant-descriptions-row > td {
padding-bottom: 10px;
}
}
.rightTop {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.rightName {
font-size: 15px;
&>span {
font-weight:500;
color:rgba(55,55,55,1);
font-family:PingFangSC-Medium,PingFang SC;
display: inline-block;
margin-right: 10px;
}
}
.btn {
padding: 0 15px;
&:first-child {
margin-right: 10px;
}
}
}
}
}
.selectitem {
display: block;
}
.searchbtnbox {
height: 54px;
display: flex;
align-items: flex-end;
}
.resetbtn {
margin-right: 16px;
}
.tablebox {
background: #fff;
.tableWrap {
margin-top: 2px;
:global {
.ant-table-fixed {
border-bottom: 1px solid #E8E8E8 !important;
}
.ant-table tbody tr:nth-child(2n) {
background-color: #FBFBFB;
}
.ant-table-thead > tr > th, .ant-table-tbody > tr > td {
padding: 11px 10px;
padding: 11px 16px;
}
.ant-table-bordered .ant-table-tbody tr td {
border-bottom: none;
......@@ -56,6 +62,9 @@
border-bottom: none;
border-right: none!important;
}
.ant-table-bordered table {
border-bottom: 1px solid #e8e8e8!important;;
}
.ant-table-bordered .ant-table-thead tr th:last-child {
border-right: 1px solid #e8e8e8!important;
}
......@@ -65,122 +74,43 @@
.ant-table-bordered .ant-table-tbody tr:last-child {
border-bottom: 1px solid #e8e8e8!important;
}
.tablefooterbox {
padding: 12px 0 12px 0;
margin-top: 10px;
.tablefooterstatic {
font-size: 16px;
color: #555555;
}
display: flex;
align-items: center;
justify-content: space-between;
}
}
}
.divideline {
color: #E9E9E9;
padding: 0 8px;
}
.alink {
color: #1890FF;
}
.classNamebox {
max-width: 250px;
word-break: break-all;
color: #1890FF;
cursor: pointer;
// background-color: #fff;
}
.tableoperatebox {
min-width: 160px;
line-height: 30px;
}
.endsearchcol {
margin-bottom: 18px;
}
.endclassfooter {
.dynamicHead {
display: flex;
align-items: center;
justify-content: space-between;
}
.classroomitem {
margin-bottom: 5px;
.classroom {
color:rgba(0,0,0,0.85);
}
.aLink {
color: #1890FF;
padding-left: 21px;
margin-bottom: 16px;
.dynamicSelect {
margin-left: 50px;
&>span {
font-size:13px;
font-family:PingFangSC-Regular,PingFang SC;
font-weight:400;
color:rgba(102,102,102,1);
display: inline-block;
margin-right: 5px;
}
}
}
.activeamount{
background-color: #cccccc;
color: #666666;
text-align: center;
line-height: 50px;
font-size: 20px;
.logitem{
margin: 20px 0;
word-break: break-all;
}
.rightList {
display: flex;
align-items: center;
height: 32px;
.resetIcon {
width: 14px;
height: 14px;
cursor: pointer;
margin-right: 15px;
}
.expend {
font-size:13px;
font-family:PingFangSC-Regular,PingFang SC;
font-weight:400;
color:rgba(102,102,102,1);
display: inline-block;
cursor: pointer;
}
.logTeacherName {
font-weight: 700;
}
.tablefooterbox {
color: rgba(0,0,0,0.6);
font-size: 16px;
line-height: 50px;
display: flex;
background-color: #fff;
align-items: center;
justify-content: space-between;
padding-left: 14px;
padding-right: 14px;
margin-top: 10px;
.tablefooterstatic {
color:rgba(0,0,0,0.65);
font-size: 14px;
}
}
.tabList {
display: flex;
border-bottom: 1px solid #E8E8E8;
margin-bottom: 18px;
.tabItem {
font-size:13px;
font-weight:400;
color:rgba(102,102,102,1);
line-height:18px;
padding: 0 30px 10px;
cursor: pointer;
position: relative;
&:hover {
font-weight:600;
color:#1890FF;
}
}
.tabItemActive {
font-size:13px;
font-weight:600;
color:#1890FF;
line-height:18px;
padding: 0 30px 10px;
cursor: pointer;
position: relative;
font-family:PingFangSC-Semibold,PingFang SC;
&:after {
content: '';
display: block;
width: 30%;
height: 2px;
background-color: #1890FF;
position: absolute;
bottom: 0;
left: 35%;
}
}
.logAction {
color: #108EE9;
font-weight: 700;
}
......@@ -129,6 +129,7 @@ class RenewEdit extends React.Component {
renewCourses,
operator,
toChangeOperator,
isOperator,
} = this.props;
const { selectedCourse, modeType, mybuyCourse } = this.state;
const { getFieldDecorator } = this.props.form;
......@@ -141,6 +142,7 @@ class RenewEdit extends React.Component {
onOk={this.renewCourse}
className="modifyclassModal"
zIndex={110}
maskClosable={false}
>
<Form className="modalform" hideRequiredMark>
<div className={pageStyle.courseinfo}>
......@@ -381,7 +383,7 @@ class RenewEdit extends React.Component {
})(
<span>{operator.nickname}</span>,
)}
<span className={`hreflink ${pageStyle.changeOperate}`} onClick={() => toChangeOperator(true)}>修改</span>
{!isOperator && <span className={`hreflink ${pageStyle.changeOperate}`} onClick={() => toChangeOperator(true)}>修改</span>}
</Form.Item>
</Col>
</Row>
......
......@@ -296,6 +296,7 @@ function RouterConfig({ history }) {
<Route path="/sjd/crm" exact component={Crm} />
<Route path="/sjd/addcrm" exact component={AddCrm} />
<Route path="/sjd/crmdetail/:id" exact component={CrmDetail} />
<Route path="/sjd/editcrm/:id" exact component={AddCrm} />
<Route component={Errorpage} />
</Switch>
</SjdIndex>
......
......@@ -24,3 +24,31 @@ export function crmEdit(params) {
data,
});
}
export function crmDetail(params) {
return request({
url: `${api.crm.crmList}/${params.id}`,
method: 'GET',
});
}
export function addClueRecords(params) {
const data = qs.stringify(params);
return request({
url: `${api.crm.clue_records}`,
method: 'POST',
data,
});
}
export function clueList(params) {
const data = qs.stringify(params);
return request({
url: `${api.crm.clue_records}?${data}`,
method: 'GET',
});
}
export function logsList(params) {
const data = qs.stringify(params);
return request({
url: `${api.crm.clue_logs}?${data}`,
method: 'GET',
});
}
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