2019-10-09课件管理功能部分注意事项
-
删除操作的时候需要考虑到查询列表,要在删除后,根据查询列表的数据发起请求,才能正确显示
删除后错误显示方式 -
新建课件部分,存在文件重复问题,刷新后改善,需要解决;快速点击保存文件,可同时重复上传多份同一文件,需要节流,保存后先销毁再返回
-
文件格式
- 图片:"image/*"
- PDF:"application/pdf"
- 视频:"video/*"
-
在react中模板字符串需要用{}包起来:
action={`${config.domain}/file`}
-
点击课件名称显示课件信息包含预览功能,点击编辑的时候无预览功能,而只有文件展示与替换功能
-
预览PDF使用
<embed type="application/pdf" src={pdfUrl} width="100%" height="800px" />
-
预览视频使用
import Player from 'griffith';
-
培训管理新增培训按钮分两个业务逻辑处理,类似于:指挥中心---任务中心
-
课件管理的查看也类似于:公众举报与评价---公众举报
-
修改请求和接收的字段:
POST发送的newValue:
{
coursewareFileName: "PDF测试文件4.pdf"
coursewareFileUid: "rc-upload-1570667615043-8"
coursewareName: "哈法"
coursewareSourceUrl: "http://www.yuntsoft.com:7389/group1/M00/01/94/rBDZEl2efb6AAvQuAACwHeXRbq0429.pdf"
coursewareType: 0
}
GET得到的后端数据response.records[i]:
{
coursewareFileName: null
coursewareFileUid: null
coursewareId: 64
coursewareName: "噶"
coursewareSourceUrl: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
coursewareType: 0
coursewareTypeName: "文件"
createUserId: null
createUserName: "市场监管"
dele: 0
edit: null
gmtCreate: "2019-10-09 15:07:12"
}
- 新增课件、编辑课件、查看课件区别处理
1、编辑课件和查看课件都是modal,需要传入handleModalVisible方法,并且最外层标签改成modal
2、编辑课件的初始值来自record,接收之后需要进一步处理
3、经测试发现QueueAnim
动画效果在模态框中无效,故注释掉
4、去掉返回按钮,把返回按钮的功能给取消,去掉保存按钮,把保存按钮的功能给确定
5、父组件中对于编辑功能的支持{updateVisible && <UpdateCourseware modalVisible={updateVisible} {...updateHandle} />}
6、从record传过来的数据在componentDidMount中进行初始化
7、别忘了表单上的initialValue
值要重新设置;
8、modal标签上的属性设置注意:点击遮罩层不关闭maskClosable="false"
和显示隐藏由父组件的状态控制,子组件默认一直显示visible="true"
9、注意:fileIds上的值也要初始化否则会报错,此时的fileIds全局定义不合理
10、执行PUT方法的时候需要url传递coursewareId,否则无法成功请求
11、给模态框设置destroyOnClose
的话会影响文件上传
12、重要:一个完整的请求配置:
1、@connect与model建立连接
// 引入dva中的connect函数
import { connect } from 'dva';
@connect(({ courseware, loading }) => ({
courseware,
loading,
}))
2、在业务处理中发送dispatch请求,注意请求的是命名空间下的update方法
/**
* 更新
* @param params
*/
handleUpdate = params => {
const { dispatch } = this.props;
console.log('调用主页面更新方法')
dispatch({
type: 'courseware/update',
payload: params,
callback: this.handleUpdateCall,
});
};
3、model中的update是一个异步generator函数,注意请求了一个updateCourseware的函数
/**
* 更新模板
* @param payload
* @param callback
* @param call
* @param put
* @returns {IterableIterator<*>}
*/ *update({ payload, callback }, { call }) {
const response = yield call(updateCourseware, payload);
if (response.resultCode === 0) {
notification.success({ message: '更新成功!' });
} else {
notification.error({ message: '更新失败!', description: response.resultMsg });
}
if (callback && typeof callback === 'function') {
callback(response);
}
},
4、在model的文件开头从services中引入updateCourseware
import {
queryCoursewareList,
addCourseware,
deleteCourseware,
updateCourseware
} from '../services/courseware'
5、在services文件中暴露出异步请求方法
export async function updateCourseware(params) {
return request(`${config.domain}/courseware`, {
method: 'PUT',
body: params,
});
}
至此,一个完整的请求动作可以正常进行
注意主页面的dispatch动作命名空间下的异步方法名要和model一致,model执行请求的函数名要和service文件一致,model文件必须引入service文件暴露的函数。
只要任何一个步骤不对,轻则无法发送请求,重则dispatch(...).then is not a function
13、为防止Radio单选框值改变,文件类型不变导致错误提交的bug,需要在handleRadioClick函数中将state中的fileList和全局的fileIds置空
14、日期选择框
import moment from 'moment'; // 引入时间处理模块
import { DatePicker } from 'antd'
const { RangePicker } = DatePicker;
<RangePicker
style={{ width: '80%' }}
ranges={{
"今天":[moment(),moment()]
}}
disabledDate={(current) => {
console.log(current)
return current && current < moment().subtract(1, "days")
}}
showTime={{
hideDisabledOptions: true,
defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('11:59:59', 'HH:mm:ss')],
}}
/>
disableDate属性禁用日期:
handleData(time){
if(!time){
return false
}else{
// 大于当前日期不能选 time > moment()
// 小于当前日期不能选 time < moment().subtract(1, "days")
// 只能选前7后7 time < moment().subtract(7, "days") || time > moment().add(7, 'd')
return time < moment().subtract(7, "days") || time > moment().add(7, 'd')
}
}
<RangePicker disabledDate={this.handleData}/>
15、点击增加一行的改写
image.png
或者:
image.png
16、modal以及Table上的属性,如果是布尔值,需要用{}而不是用"",比如bordered={false}
17、当无法通过表单双向绑定验证数据的时候可以用if判断代替,用notifaction.warning提示
if(fieldsValue.templateName === undefined || fieldsValue.templateName === null || fieldsValue.templateName === ""){
notification.warning({
message: "模板名称不能为空",
});
return;
}
18、在<Row>标签的包裹下,用<Col>标签将<FormItem>包裹起来即可实现一行显示,响应式属性: md: 8, lg: 24, xl: 48 ,sm:24(响应式栅格有24份)
19、多重判断:initialValue:deptCanteenSelectList && deptCanteenSelectList.length > 0 ? deptCanteenSelectList[0].deptId : undefined
使用逻辑运算符+三元表达式
当前审核状态:
{auditStateNum === '0' || auditStateNum === null
? '待提交'
: auditStateNum === '1'
? '待审核'
: auditStateNum === '2'
? '已通过'
: auditStateNum === '3'
? '未通过'
: '-'}
如果前两项条件都为真才返回第三项:
{(auditStateNum === '0' || auditStateNum === null || auditStateNum === '3') && getUserInfo('perms').includes('cookbook:add') && (
<Button type="primary" onClick={this.handleOnClick}>
提交审核
</Button>
)}
多重逻辑运算符,flag最后取到一个布尔值
const flag = highRiskFood[index] !== undefined && highRiskFood[index].length > 0
20、整行删除技巧:
// 行删除
const {foodList} = this.state;
const index = foodList.findIndex(item => row.key === item.key);
foodList.splice(index,1);
21、新增培训--->培训内容部分思路:Table中的数据保存到state中,点击保存的时候进行校验,如果菜单太长可以notification.info整个界面提示
form.validateFields((err,fieldsValue) => {
if (err) return;
if(foodList.length ===0){
notification.warning({
message: "菜谱目录不能为空",
});
return;
}
handleAdd(this.handleAddData(foodList,fieldsValue,nowAddData));
},
);
提交的时候可以对不需要的数据直接使用delete方法:
foodList.forEach(item =>{
const food = {...item};
food.dishId = fieldsValue[`dishId${item.key}`].substring(fieldsValue[`dishId${item.key}`].indexOf('id:')+3); // 菜品id
food.employeeId = fieldsValue[`employeeId${item.key}`].substring(fieldsValue[`employeeId${item.key}`].indexOf('id:')+3); // 负责厨师id
delete food.dishName;
dishFoodDTOList.push(food);
});
点击添加课件按需弹出相应模态框,通过子组件传值保存到父组件的courseList中,父组件校验courseList不需要form方法,可以直接校验
子组件向父组件传值,需要父组件先传递一个方法过去,子组件触发方法将值作为参数传入,父组件调用该方法将值保存到自己state中,比如子组件handleSubmit直接调用this.handleAddCourseware,到父组件中接收传入的值this.setState
当新建课件完成之后,新建可见被课件名称取代,可以考虑使用components 覆盖默认的 table 元素
针对Radio.Group组件,form.getFileDecorator方法定义的名称不允许相同,否则radio的值会互相影响大
新增课程Table区域的DataSource生成主要依靠新增课程这个按钮的add事件,在事件每点击一次DataSource
- 双向绑定的方法名称
form.getFieldDecorator
而不是form.getFileDecorator
删除按钮针对的也是dataSource上的操作,每点击一下删除一行
- 点击新建课件之后,从子组件获取到课件的id保存到父组件state中的ids数组;此时新建课件变成了课件名称;当再次点击的时候,子组件再向父组件传值,父组件可以用新值替代旧值,杜绝实际课件Id不一致的bug
- 如果点击了之后不作修改,保存的时候就没有数据添加进去,所以需要在点击的时候把现有的ids传过去,哪怕为空值,但是为空值的话,校验就会发生问题。。。
- 只要关心子组件返回来的数据即可,可以加入逻辑判断,如果有返回数据,就覆盖,没有返回数据不覆盖,最后校验的时候仍然是有值可选的
22、总结:新增一个模态框组件的完整流程
- 新增模态框子组件的流程
1、当前pages文件夹下新建子组件文件:CreateCourseByWare.js
2、主页面中引入import CreateCourseByWare from '/CreateCourseByWare'
3、主页面render的return的最后一个标签前加入可视判断{createByWareVisible && ( <CreateCourseByWare modalVisible={createByWareVisible} {...createByWareHandle} /> )}
4、主页面的state中保存可视判断createByWareVisible :false
5、主页面的render方法中const { createByWareVisible } = this.state
6、主页面的render方法中定义传递给modal的对象(包含方法,父组件的数据等)
/**
* 定义传递给子组件的对象
* */
const createByWareHandle = {
handleAdd: this.handleCreateByWare,
handleModalVisible: () => {
this.setState({ createByWareVisible: false });
},
};
7、主页面组件创建之后(和render平级)定义处理子组件的方法:
/**
* 根据现有课件增加
* @param params
*/
handleCreateByWare = params => {
const { dispatch } = this.props;
dispatch({
type: 'courseware/add',
payload: params,
callback: this.handleCreateByWareCall,
});
};
/**
* 根据现有课件增加之后的回调函数
* @param res
*/
handleCreateByWareCall = res => {
if (res.resultCode === 0) {
const { dispatch } = this.props;
const { formValues,nowSelectCanteen } = this.state;
dispatch({
type: 'cookBookConfig/fetchCookBookList',
payload: { ...formValues,deptId:nowSelectCanteen },
callback: this.handleCookBookListCallBack,
});
this.setState({ createVisible: false });
}
};
8、子组件开始书写,首先头部引入modal组件import { Modal } from 'antd'
- 个别纯查看信息的modal,确定和取消按钮都绑定handleModalVisible事件
9、在主页面给控制子组件可视的元素绑定事件
/**
* 点击按需添加课件
* */
addCoureWare = () => {
const { trainType, createByWareVisible, createBySelfVisible } = this.state
if(trainType === 0){ // 从现有课件添加
this.setState({
createByWareVisible:true,
})
}else{
this.setState({
createBySelfVisible:true,
})
}
}
10、子组件制作页面
<Modal
width={800}
maskClosable={false}
// destroyOnClose
title="添加课件"
visible={modalVisible}
onCancel={() => handleModalVisible()}
// footer={footerButton}
>
写点内容
</Modal>
11、子组件render函数中从父组件接收所需属性和方法
const {
modalVisible,
handleModalVisible,
form,
} = this.props;
12、注意:modal上面的onOK和onCancel属性接收的是一个回调函数,必须加括号表示执行了,否则只传入一个方法是不会自动执行的
onCancel={() => handleModalVisible()}
onOk={() => this.okHandle()}
13、上传课件增加培训数据流程:
- 课程列表courseList,课程对象courseObj
1、每次点击增加课程的时候组织出一个课程对象,赋值key,其余置空,push到courseList中
const courseObj = {
key : indexNum, // 每一门课程的key
courseName : "", // 每一门课程的名称
coursewareName : "", // 课程对应的课件名称啊
coursewareId : "" // 课程对应的课件的Id
};
2、每次点击删除的时候传入row,根据row.key找到所在对象的index值,splice掉整个对象
const index = courseList.findIndex(item => row.key === item.key);
courseList.splice(index,1);
3、对特定项点击添加课件的时候,传入row,根据row得到当前项的key值,key需要将后台传回来的coursewareName和coursewareId以及当前页面的courseName组合在一起生成当前想的courseObj,并且替换掉courseList光有key的那一项,splice(index,Obj),只要用map方法遍历courseList数组,找到item.key === current.key的一项,进行相应项的改写即可
4、获取课程名称部分,通过form.getFileDecorator绑定courseName$(record.key)
,在handleSubmit的时候通过foreach遍历赋值
5、提交校验
自定义校验:
绑定的时候规则置空
{form.getFieldDecorator('content', {})(
<BraftEditor
controls={controls}
extendControls={extendControls}
onChange={this.handleChange}
style={{ border: '1px solid #bfbfbf' }}
/>
)}
校验的时候取值做判断
if (fieldsValue.content.isEmpty()) {
message.error('资讯内容不能为空');
return;
}
if (tags.length === 0) {
message.error('标签不能为空');
return;
}
if (fieldsValue.newsTitle.length > 30) {
message.error('资讯标题过长,30字符以内');
return;
}
6、添加培训学员部分可参考:检测管理——样品管理——采样食堂
使用treeSelect标签,值需要用递归获取,
7、 时间数据的传递:不能把moment对象直接POST,而是要先格式化
trainStartTime:values.trainTime[0].format('YYYY-MM-DD HH:mm:ss'), // .d trainEndTime:values.trainTime[1].format('YYYY-MM-DD HH:mm:ss'), // .d