react antd-mobile ActionSheet+tag實(shí)現(xiàn)多選方式
實(shí)現(xiàn)效果如下
在手機(jī)下方彈出彈窗
選擇月份點(diǎn)確定后
再次點(diǎn)擊申請(qǐng)?jiān)路荩耙堰x的月份會(huì)自動(dòng)選上
全部功能描述
1、點(diǎn)擊輸入框,自動(dòng)彈出選擇彈窗(使用actionsheet,這部分不過(guò)多闡釋)
2、彈窗可多選的內(nèi)容由后臺(tái)接口傳入(或自己寫(xiě)數(shù)據(jù),看需求)
3、灰色部分為后臺(tái)返回的不可選擇的標(biāo)簽,不允許用戶點(diǎn)擊選擇(disabled)
4、選擇一個(gè)或多個(gè)標(biāo)簽后,點(diǎn)擊確定,保存選擇的標(biāo)簽,并在輸入框中顯示選擇的內(nèi)容。倘若沒(méi)有點(diǎn)擊確定,直接點(diǎn)取消或彈框外的區(qū)域關(guān)閉彈窗,則不會(huì)保存所選標(biāo)簽
4、再次點(diǎn)擊輸入框,彈窗會(huì)自動(dòng)勾選上之前選過(guò)的彈窗。之后操作可如上循環(huán)。
功能實(shí)現(xiàn)
一、從后臺(tái)獲取所有月份信息
這里就是普通的通過(guò)redux的網(wǎng)絡(luò)請(qǐng)求獲取數(shù)據(jù),按實(shí)際情況獲取數(shù)據(jù)
getMouth = () => { const {dispatch} = this.props; dispatch({ type: `${NAMESPACE}/getMouth`, }); };
不允許點(diǎn)擊的月份
const disabledMouth = [2,5,8];
按理說(shuō),不允許點(diǎn)擊的月份也應(yīng)該通過(guò)后臺(tái)獲取,此處因?yàn)檫€沒(méi)拿到后臺(tái)接口,暫時(shí)用全局變量充當(dāng)不允許點(diǎn)擊的月份數(shù)組
二、添加組件的state數(shù)據(jù)
this.state = { checkMouth: [],//保存已選擇的月份 allMouth: [],//保存月份標(biāo)簽的屬性數(shù)據(jù) temCheckMouth: [],//臨時(shí)保存已選擇的魚(yú)粉 temAllMouth: [],//臨時(shí)保存月份標(biāo)簽的屬性數(shù)據(jù) sign: false//判斷用戶是否點(diǎn)擊確定 }
三、定義含有標(biāo)簽屬性的數(shù)組allMouth
getAllMouth = () => { //mouth即為第一步通過(guò)后臺(tái)拿到的model里state的月份值 const mouth = this.props.mouth.data || ''; const newMonth = []; if (mouth && mouth.length > 0) { mouth.map(item => { newMonth.push( { value: item.code,//可理解為每個(gè)月份的id label: item.detail,//月份標(biāo)簽需展示的內(nèi)容,如一月、二月等的字樣 selected: false,//月份標(biāo)簽是否被選擇 disabled: false//該月份是否不可點(diǎn)擊 } ) }); } // 修改無(wú)法選擇的月份信息,把無(wú)法選擇的月份disabled置灰掉,不允許用戶點(diǎn)擊選擇 disabledMouth.map(item => { newMonth[item].disabled = true; }); // console.log(newMonth); // 把每個(gè)月份的屬性數(shù)據(jù)數(shù)組存入state this.setState({ allMouth: newMonth, temAllMouth: newMonth }) };
注:
一般后臺(tái)傳的數(shù)據(jù)只會(huì)有code和detail或其他內(nèi)容,不會(huì)有selected和disabled,所以此處我們要在數(shù)組手動(dòng)添加,給每個(gè)月份自己獨(dú)有的selected值和disabled值。
在最初默認(rèn)都是未選中和允許點(diǎn)擊狀態(tài)。
四、保存月份屬性數(shù)組allMouth
componentDidMount() { let timer; new Promise(resolve => { this.getMouth(); timer = setInterval(() => { if (this.props.mouth.data) { resolve(); } }, 100); }).then(() => { this.getAllMouth(); clearInterval(timer); }); }
這部分代碼其實(shí)我考慮了很久,因?yàn)檫@里有一個(gè)前提條件
必須通過(guò)getMouth()后臺(tái)請(qǐng)求到數(shù)據(jù)之后,getAllMouth()才能訪問(wèn)數(shù)據(jù),初始化月份數(shù)組數(shù)據(jù)并放到state
但是在這一步我報(bào)錯(cuò)了很久,因?yàn)樘热羝胀▽?xiě)法(如下)
this.getMouth();× this.getAllMouth();×
這一步雖然顯示網(wǎng)絡(luò)請(qǐng)求成功了,但是props.mouth并沒(méi)有拿到數(shù)據(jù)!
這一步該如何寫(xiě)思考了很久,直接簡(jiǎn)單實(shí)用promise也不行
componentDidMount() { × new Promise(resolve => { × this.getMouth(); × resolve(); × }).then(() => { × this.getAllMouth(); × }); × } ×
此處getMouth()方法的確是執(zhí)行完了,只是還沒(méi)拿到數(shù)據(jù),所以這樣子寫(xiě)是不行的
而且componentDidMount正常來(lái)說(shuō)只會(huì)執(zhí)行一次,所以在componentDidMount執(zhí)行完,而model的state還沒(méi)拿到數(shù)據(jù)的時(shí)候,盡管過(guò)了一段時(shí)間拿到數(shù)據(jù)了this.getAllMouth(); 也不會(huì)再次執(zhí)行了,所以初始化無(wú)法進(jìn)行。
思前想后,能解決這一步的只能多次執(zhí)行,確保拿到數(shù)據(jù)了,再進(jìn)行this.getAllMouth();初始化數(shù)據(jù)。便有了上方代碼的操作。
當(dāng)然,開(kāi)了定時(shí)器記得用完就關(guān)!這一步操作可能性能上不太友好,如果大家有什么更好的方法,請(qǐng)多多指教呀~
五、遍歷顯示月份
上面步驟成功后,可以看到我們初始化的月份數(shù)組allMouth是這樣的,接下來(lái)就可以讓他們展示出來(lái)了
(注:0號(hào)全選是后臺(tái)一同傳過(guò)來(lái)的,大家可忽略)
input框:
<div onClick={this.handleMouth}> <InputItem placeholder="請(qǐng)選擇申請(qǐng)?jiān)路? readonly="readonly" clear > 申請(qǐng)?jiān)路? </InputItem> </div>
點(diǎn)擊后彈出彈窗
主要是使用ActionSheet彈出彈窗顯示內(nèi)容,不多闡述。詳情可見(jiàn)antd-mobile文檔
handleMouth = () => { const {allMouth} = this.state; //這一步是為了刪去數(shù)組第一條全選數(shù)據(jù),全選不需要顯示。可忽略 const newMouthData = allMouth && allMouth.slice(1); const modalData = ( <div className={styles.modalStyle}> <div className={styles['tag-body']}> <div style={{textAlign: 'left'}}>可選擇月份(可多選)</div> <div className={styles['tag-box']}> { newMouthData && newMouthData.map((item) => { return ( <Tag data-seed="logId" onChange={(selected) => this.handleOnTagChange(selected, item.value)} // 用戶點(diǎn)擊不同月份時(shí)觸發(fā)的方法(詳情見(jiàn)下方標(biāo)題六) selected={item.selected}// 控制不同月份的選中狀態(tài) disabled={item.disabled}// 控制不同月份是否可點(diǎn)擊 > {item.label} </Tag> ) }) } </div> </div> </div> ) const button1 = ( <div className={styles.buttonStyle}> <span>取消</span> <span onClick={this.handleConfirm}>確定</span>//詳情請(qǐng)見(jiàn)標(biāo)題七的內(nèi)容 </div> ) const BUTTONS = [modalData]; ActionSheet.showActionSheetWithOptions({ options: [button1], message: BUTTONS, }, // 關(guān)閉頁(yè)面執(zhí)行的方法 (buttonIndex) => { this.closePage();//詳情請(qǐng)見(jiàn)標(biāo)題八的內(nèi)容 }); };
六、選擇不同月份觸發(fā)事件
handleOnTagChange = (selected, value) => { /** * 用戶在彈窗選擇月份時(shí),雖然當(dāng)前處于選中狀態(tài)。 * 但是倘若用戶選擇完畢時(shí)不點(diǎn)擊確定,而且直接關(guān)閉頁(yè)面 * 則之前選中的月份都不應(yīng)該保存!所以此處用臨時(shí)數(shù)組進(jìn)行保存 * 只有用戶點(diǎn)擊確定,才把選擇的所有內(nèi)容保存到正式的數(shù)組中 */ const {temAllMouth, temCheckMouth} = this.state; // 修改月份對(duì)應(yīng)的selected屬性 const newAllMouth = JSON.parse(JSON.stringify(temAllMouth)); newAllMouth[value].selected = selected; // console.log(newAllMouth); // 記錄選中的月份 const newCheckMouth = JSON.parse(JSON.stringify(temCheckMouth)); if (selected) {//如果選中,把選中的月份添加進(jìn)數(shù)組 newCheckMouth.push(parseInt(value)); } else {//如果是取消選中,需要把之前的選中從數(shù)組中刪除 const deleteMouth = newCheckMouth.findIndex(item => item == value); newCheckMouth.splice(deleteMouth, 1); } // console.log(newCheckMouth); this.setState({ temCheckMouth: newCheckMouth, temAllMouth: newAllMouth }); };
關(guān)于const newAllMouth = JSON.parse(JSON.stringify(temAllMouth))的解釋:
這一步其實(shí)我繞了很多彎路,最后一步步排除錯(cuò)誤才發(fā)現(xiàn)我犯了個(gè)超級(jí)低級(jí)的錯(cuò)誤!
因?yàn)檫@個(gè)方法中,我們很明顯需要修改數(shù)組,但是我們不能直接修改state的數(shù)據(jù)
所以必須在方法中深拷貝state的數(shù)組,對(duì)新數(shù)組進(jìn)行修改,最后再通過(guò)setstate把新數(shù)組賦值給state的數(shù)組。
而在寫(xiě)代碼最初,我是直接
const {temAllMouth, temCheckMouth} = this.state; const newAllMouth = temAllMouth;×××××錯(cuò)誤范例?。?!
很明顯這樣寫(xiě)是錯(cuò)誤的!直接賦值是把temAllMouth的地址賦值給了newAllMouth ,兩者其實(shí)沒(méi)有區(qū)別,修改newAllMouth 也會(huì)修改到state的數(shù)據(jù)temAllMouth,故出現(xiàn)問(wèn)題。
所以最后直接把temAllMouth通過(guò)JSON深拷貝一份,保證newAllMouth與temAllMouth不相同,互不干擾。
七、點(diǎn)擊確定觸發(fā)實(shí)現(xiàn)
handleConfirm = () => { const {temCheckMouth, temAllMouth} = this.state; /** * 對(duì)選中月份進(jìn)行排序 * 因?yàn)殛P(guān)閉彈窗后需要在input框中顯示用戶選擇的月份 * 而用戶選擇的順序是不定,所以temCheckMouth保存的選中月份也是亂的 * 故此處為了美觀,對(duì)選中月份進(jìn)行了排序 * 使用的是最簡(jiǎn)單的冒泡排序,可以忽略 */ const newCheckMouth = JSON.parse(JSON.stringify(temCheckMouth)); if (newCheckMouth && newCheckMouth.length > 1){ newCheckMouth.map((item,index) => { for (let j = 0; j<newCheckMouth.length-index;j+=1){ if (newCheckMouth[j] > newCheckMouth[j + 1]){ const temp = newCheckMouth[j]; newCheckMouth[j] = newCheckMouth[j + 1]; newCheckMouth[j + 1] = temp; } } }); } console.log('選中的月份--' + newCheckMouth); // 此時(shí)用戶點(diǎn)擊確定按鈕,需要把之前保存的臨時(shí)數(shù)據(jù)賦值給正式的數(shù)組 this.setState({ allMouth: temAllMouth, checkMouth: newCheckMouth, temCheckMouth: newCheckMouth, sign: true //確定頁(yè)面 }); // 把選中的月份展示在input框,只有用戶有選擇月份的情況才需要展示 if (temCheckMouth){ this.props.form.setFieldsValue({ sqzzjtyf: newCheckMouth.toString() }); } }
八、關(guān)閉彈窗觸發(fā)事件
注:這里關(guān)閉彈窗并不是點(diǎn)擊取消或者點(diǎn)擊彈窗外的手機(jī)部分關(guān)閉彈窗時(shí)才會(huì)觸發(fā)!!
點(diǎn)擊確定之后關(guān)閉彈窗同樣會(huì)觸發(fā)!?。。](méi)注意這部分就會(huì)出現(xiàn)錯(cuò)誤!
closePage = () => { setTimeout(()=>{ const {checkMouth, allMouth, sure} = this.state; // sure=false代表用戶沒(méi)有點(diǎn)擊確定,則需要把之前保存的臨時(shí)數(shù)據(jù)重置,保證用戶下次打開(kāi)彈窗時(shí)數(shù)據(jù)的正確性 if(!sure){ this.setState({ temCheckMouth: checkMouth, temAllMouth: allMouth }); }else{ // 把點(diǎn)擊確定時(shí)修改的sure=true修改回false,保證下次再次打開(kāi)彈窗的正確性 this.setState({ sure: false }); } },50) }
此處使用settimeout的原因:
上面也說(shuō)了,這里調(diào)用的方法是只要關(guān)閉了彈窗就會(huì)執(zhí)行,而該方法的執(zhí)行順序與用戶點(diǎn)擊確定的執(zhí)行順序應(yīng)該是不相上下的(此處未做深入研究,不確定兩者的執(zhí)行順序)
而這里我們有一個(gè)必須前提,關(guān)閉彈窗的這個(gè)方法必須在點(diǎn)擊確定的方法后面執(zhí)行,原因如下:
在點(diǎn)擊確定方法后,我們會(huì)設(shè)置sure=true,并且把checkMouth和allMouth設(shè)置為新的值
而倘若關(guān)閉彈窗方法沒(méi)有在確定方法后執(zhí)行,則關(guān)閉彈窗方法執(zhí)行的時(shí)候,sure值仍為false!并且checkMouth和allMouth也扔為舊值
這樣,該方法就會(huì)執(zhí)行
temCheckMouth: checkMouth(舊值), temAllMouth: allMouth(舊值)
如此,用戶之前點(diǎn)擊確定應(yīng)該要保存的數(shù)據(jù)就沒(méi)有保存成功了!錯(cuò)誤就出現(xiàn)了!
故此處使用settimeout保證該方法執(zhí)行的延后!
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
react native基于FlatList下拉刷新上拉加載實(shí)現(xiàn)代碼示例
這篇文章主要介紹了react native基于FlatList下拉刷新上拉加載實(shí)現(xiàn)代碼示例2018-09-09React Native第三方平臺(tái)分享的實(shí)例(Android,IOS雙平臺(tái))
本篇文章主要介紹了React Native第三方平臺(tái)分享的實(shí)例(Android,IOS雙平臺(tái)),具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08React高級(jí)指引之Refs and the DOM使用時(shí)機(jī)詳解
在典型的React數(shù)據(jù)流中,props是父組件與子組件交互的唯一方式。要修改一個(gè)子組件,你需要使用新的props來(lái)重新渲染它。但是,在某些情況下,你需要在典型數(shù)據(jù)流之外強(qiáng)制修改子組件2023-02-02React利用路由實(shí)現(xiàn)登錄界面的跳轉(zhuǎn)
這篇文章主要介紹了React利用路由實(shí)現(xiàn)登錄界面的跳轉(zhuǎn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04react實(shí)現(xiàn)同頁(yè)面三級(jí)跳轉(zhuǎn)路由布局
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)同頁(yè)面三級(jí)跳轉(zhuǎn)路由布局,一個(gè)路由小案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09React中事件綁定this指向三種方法的實(shí)現(xiàn)
這篇文章主要介紹了React中事件綁定this指向三種方法的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05