<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          基于React和Node.JS的表單錄入系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)

          共 20370字,需瀏覽 41分鐘

           ·

          2021-12-27 23:38

          一、寫在前面

          這是一個(gè)真實(shí)的項(xiàng)目,項(xiàng)目已經(jīng)過去好久了,雖然很簡(jiǎn)單,但還是有很多思考點(diǎn),跟隨著筆者的腳步,一起來(lái)看看吧。本文純屬虛構(gòu),涉及到的相關(guān)信息均已做虛構(gòu)處理,

          二、背景

          人活著一定要有信仰,沒有信仰的靈魂是空洞的。你可以信耶穌,信佛,信伊斯蘭,信科學(xué)等等。為了管控各大宗教場(chǎng)所的人員聚集,為社會(huì)增添一份綿薄之力,京州領(lǐng)導(dǎo)決定做一個(gè)表單系統(tǒng)來(lái)統(tǒng)計(jì)某個(gè)時(shí)間或者時(shí)間段的人員訪問量,控制宗教人員活動(dòng)的范圍,漢東省委沙瑞金書記特別關(guān)心這件事決定親自檢查,幾經(jīng)周轉(zhuǎn),這個(gè)任務(wù)落到了程序員江濤的頭上,故事由此展開。

          三、需求分析

          大致需要實(shí)現(xiàn)如下功能

          • 表單數(shù)據(jù)的錄入
          • 錄入數(shù)據(jù)的最近記錄查詢
          • 短信驗(yàn)證碼的使用
          • 掃碼填寫表單信息

          有兩種方案, 一種是進(jìn)去自己選擇對(duì)應(yīng)的宗教場(chǎng)所(不對(duì)稱分布三級(jí)聯(lián)動(dòng)),第二種是點(diǎn)擊對(duì)應(yīng)的宗教場(chǎng)所進(jìn)行填寫表單,表單處的場(chǎng)所不可更改,不同的設(shè)計(jì)不同的思路。雖然兩種都寫了, 但這里我就按第二種寫這篇文章,如果有興趣了解第一種歡迎與我交流。


          四、系統(tǒng)設(shè)計(jì)

          這次我決定不用vue,改用react的taro框架寫這個(gè)小項(xiàng)目(試一下多端框架taro哈哈), 后端這邊打算用nodejs的eggjs框架, 數(shù)據(jù)庫(kù)還是用mysql, 還會(huì)用到redis。由于服務(wù)器端口限制,搞不動(dòng)docker啊, 也沒有nginx,莫得關(guān)系,egg自帶web服務(wù)器將就用一下項(xiàng)目也就做完了,就這樣taro和egg的試管嬰兒誕生了。

          五、代碼實(shí)現(xiàn)

          額,東西又多又雜,挑著講吧, 建議結(jié)合這兩篇篇文章一起看, 基于Vue.js和Node.js的反欺詐系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn) https://www.cnblogs.com/cnroadbridge/p/15182552.html, 基于React和GraphQL的demo設(shè)計(jì)與實(shí)現(xiàn) https://www.cnblogs.com/cnroadbridge/p/15318408.html

          5.1 前端實(shí)現(xiàn)

          taroJS的安裝使用參見https://taro-docs.jd.com/taro/docs/GETTING-STARTED

          5.1.1 整體的布局設(shè)計(jì)

          主要還是頭部和其他這種布局,比較簡(jiǎn)單,然后抽離出一個(gè)公共組件header,給它拋出一個(gè)可以跳轉(zhuǎn)鏈接的方法, 邏輯很簡(jiǎn)單就是一個(gè)標(biāo)題,然后后面有一個(gè)返回首頁(yè)的圖標(biāo)

          import { View, Text } from '@tarojs/components';
          import { AtIcon } from 'taro-ui'
          import "taro-ui/dist/style/components/icon.scss";

          import 'assets/iconfont/iconfont.css'
          import './index.scss'

          import { goToPage } from 'utils/router.js'

          export default function Header(props) {
          return (

          { props.title }
          goToPage('index')}>



          )
          }

          關(guān)于這一塊,還可以看下components下的card組件的封裝

          5.1.2 表單的設(shè)計(jì)

          表單設(shè)計(jì)這塊,感覺也沒啥好講的,主要是你要寫一些css去適配頁(yè)面,具體的邏輯實(shí)現(xiàn)代碼如下:

          import Taro, { getCurrentInstance } from '@tarojs/taro';
          import React, { Component } from 'react';
          import { connect } from 'react-redux';
          import { update } from 'actions/form';
          import { View, Text, RadioGroup, Radio, Label, Picker } from '@tarojs/components';
          import { AtForm, AtInput, AtButton, AtTextarea, AtList, AtListItem } from 'taro-ui';
          import Header from 'components/header'

          import 'taro-ui/dist/style/components/input.scss';
          import 'taro-ui/dist/style/components/icon.scss';
          import 'taro-ui/dist/style/components/button.scss';
          import 'taro-ui/dist/style/components/radio.scss';
          import 'taro-ui/dist/style/components/textarea.scss';
          import 'taro-ui/dist/style/components/list.scss';
          import "taro-ui/dist/style/components/loading.scss";
          import './index.scss';

          import cityData from 'data/city.json';
          import provinceData from 'data/province.json';

          import { goToPage } from 'utils/router';
          import { request } from 'utils/request';

          @connect(({ form }) => ({
          form
          }), (dispatch) => ({
          updateForm (data) {
          dispatch(update(data))
          }
          }))

          export default class VisitorRegistration extends Component {

          constructor (props) {
          super(props);
          this.state = {
          title: '預(yù)約登記', // 標(biāo)題
          username: '', // 姓名
          gender: '', // 性別
          mobile: '', // 手機(jī)
          idcard: '', // 身份證
          orgin: '', //訪客來(lái)源地
          province: '', //省
          city: '', // 市
          place: '', //宗教地址
          religiousCountry: '', // 宗教縣區(qū)
          religiousType: '', // 宗教類型
          matter: '', // 來(lái)訪事由
          visiteDate: '', // 拜訪日期
          visiteTime: '', // 拜訪時(shí)間
          leaveTime: '', // 離開時(shí)間
          genderOptions: [
          { label: '男', value: 'male' },
          { label: '女', value: 'female' },
          ], // 性別選項(xiàng)
          genderMap: { male: '男', female: '女' },
          timeRangeOptions: [
          '00:00-02:00',
          '02:00-04:00',
          '04:00-06:00',
          '06:00-08:00',
          '08:00-10:00',
          '10:00-12:00',
          '12:00-14:00',
          '14:00-16:00',
          '16:00-18:00',
          '18:00-20:00',
          '20:00-22:00',
          '22:00-24:00',
          ], // 時(shí)間選項(xiàng)
          orginRangeOptions: [[],[]], // 省市選項(xiàng)
          orginRangeKey: [0, 0],
          provinces: [],
          citys: {},
          isLoading: false,
          }
          this.$instance = getCurrentInstance()
          Taro.setNavigationBarTitle({
          title: this.state.title
          })
          }

          async componentDidMount () {
          console.log(this.$instance.router.params)
          const { place } = this.$instance.router.params;
          const cityOptions = {};
          const provinceOptions = {};
          const provinces = [];
          const citys = {};
          provinceData.forEach(item => {
          const { code, name } = item;
          provinceOptions[code] = name;
          provinces.push(name);
          })
          for(const key in cityData) {
          cityOptions[provinceOptions[key]] = cityData[key];
          citys[provinceOptions[key]] = [];
          for (const item of cityData[key]) {
          if (item.name === '直轄市') {
          citys[provinceOptions[key]].push('');
          } else {
          citys[provinceOptions[key]].push(item.name);
          }
          }
          }
          const orginRangeOptions = [provinces, []]

          await this.setState({
          provinces,
          citys,
          orginRangeOptions,
          place
          });
          }


          handleOriginRangeChange = event => {
          let { value: [ k1, k2 ] } = event.detail;
          const { provinces, citys } = this.state;
          const province = provinces[k1];
          const city = citys[province][k2];
          const orgin = `${province}${city}`;
          this.setState({
          province,
          city,
          orgin
          })
          }

          handleOriginRangleColumnChange = event => {
          let { orginRangeKey } = this.state;
          let changeColumn = event.detail;
          let { column, value } = changeColumn;
          switch (column) {
          case 0:
          this.handleRangeData([value, 0]);
          break;
          case 1:
          this.handleRangeData([orginRangeKey[0], value]);
          }
          }

          handleRangeData = orginRangeKey => {
          const [k0] = orginRangeKey;
          const { provinces, citys } = this.state;
          const cityOptions = citys[provinces[k0]]
          const orginRangeOptions = [provinces, cityOptions];
          this.setState({
          orginRangeKey,
          orginRangeOptions
          })
          }

          handleChange (key, value) {
          this.setState({
          [key]: value
          })
          return value;
          }

          handleDateChange(key, event) {
          const value = event.detail.value;
          this.setState({
          [key]: value
          })
          return value;
          }

          handleClick (key, event) {
          const value = event.target.value;
          this.setState({
          [key]: value
          })
          return value;
          }

          handleRadioClick (key, value) {
          this.setState({
          [key]: value
          })
          return value;
          }

          async onSubmit (event) {
          const {
          username,
          gender,
          mobile,
          idcard,
          orgin,
          province,
          city,
          place,
          religiousCountry,
          religiousType,
          visiteDate,
          visiteTime,
          leaveTime,
          matter,
          genderMap,
          } = this.state;

          if (!username) {
          Taro.showToast({
          title: '請(qǐng)?zhí)顚懹脩裘?,
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!gender) {
          Taro.showToast({
          title: '請(qǐng)選擇性別',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!mobile || !/^1(3[0-9]|4[579]|5[012356789]|66|7[03678]|8[0-9]|9[89])\d{8}$/.test(mobile)) {
          Taro.showToast({
          title: '請(qǐng)?zhí)顚懻_的手機(jī)號(hào)',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!idcard || !/^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/.test(idcard)) {
          Taro.showToast({
          title: '請(qǐng)?zhí)顚懻_的身份證號(hào)',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!orgin) {
          Taro.showToast({
          title: '請(qǐng)選擇來(lái)源地',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!place) {
          Taro.showToast({
          title: '請(qǐng)選擇宗教場(chǎng)所',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!visiteDate) {
          Taro.showToast({
          title: '請(qǐng)選擇預(yù)約日期',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (!visiteTime) {
          Taro.showToast({
          title: '請(qǐng)選擇預(yù)約時(shí)間',
          icon: 'none',
          duration: 2000
          })
          return;
          }

          await this.setState({
          isLoading: true
          })

          const data = {
          username,
          gender: genderMap[gender],
          mobile,
          idcard,
          orgin,
          province,
          city,
          place,
          religiousCountry,
          religiousType,
          visiteDate,
          visiteTime,
          leaveTime,
          matter,
          };

          const { data: { code, status, data: formData }} = await request({
          url: '/record',
          method: 'post',
          data
          });

          await this.setState({
          isLoading: false
          });

          if (code === 0 && status === 200 && data) {
          Taro.showToast({
          title: '預(yù)約成功',
          icon: 'success',
          duration: 2000,
          success: () => {
          // goToPage('result-query', {}, (res) => {
          // res.eventChannel.emit('formData', { data: formData })
          // })
          this.props.updateForm(formData)
          goToPage('result-query')
          }
          });
          } else {
          Taro.showToast({
          title: '預(yù)約失敗',
          icon: 'none',
          duration: 2000
          })
          return;
          }
          }

          handlePickerChange = (key, optionName, event) => {
          const options = this.state[optionName];
          this.setState({
          [key]: options[event.detail.value]
          })
          }

          render() {
          const { title,
          username,
          genderOptions,
          mobile,
          idcard,
          visiteTime,
          timeRangeOptions,
          leaveTime,
          matter,
          visiteDate,
          orgin,
          orginRangeOptions,
          orginRangeKey,
          place,
          isLoading
          } = this.state;
          return (


          onSubmit={this.onSubmit.bind(this)}
          >

          required
          type='text'
          name='username'
          className='col'
          title='訪客姓名'
          placeholder='請(qǐng)輸入訪客姓名'
          value={username}
          onChange={(value) => {this.handleChange('username', value)}}
          />




          性別



          {genderOptions.map((genderOption, i) => {
          return (

          )
          })}





          required
          type='phone'
          name='mobile'
          title='手機(jī)號(hào)碼'
          className='col'
          placeholder='請(qǐng)輸入手機(jī)號(hào)碼'
          value={mobile}
          onChange={(value) => {this.handleChange('mobile', value)}}
          />


          required
          name='idcard'
          type='idcard'
          className='col'
          title='身份證號(hào)'
          placeholder='請(qǐng)輸入身份證號(hào)碼'
          value={idcard}
          onChange={(value) => {this.handleChange('idcard', value)}}
          />




          來(lái)源地

          onChange={(event) => this.handleOriginRangeChange(event)}
          onColumnChange={(event) => this.handleOriginRangleColumnChange(event)}
          range={orginRangeOptions}
          value={orginRangeKey}>

          {orgin ? (
          className='at-list__item-fix'
          extraText={orgin}
          />) : (請(qǐng)選擇訪客來(lái)源地)}





          required
          type='text'
          name='place'
          className='col'
          title='宗教場(chǎng)所'
          disabled
          placeholder='請(qǐng)選擇宗教場(chǎng)所'
          value={place}
          onChange={(value) => {this.handleChange('place', value)}}
          />




          預(yù)約日期

          onChange={(event) => this.handleDateChange('visiteDate', event)}>

          {visiteDate ? (
          className='at-list__item-fix'
          extraText={visiteDate}
          />) : (請(qǐng)選擇預(yù)約日期)}







          預(yù)約時(shí)間

          range={timeRangeOptions}
          onChange={(event) => this.handlePickerChange('visiteTime', 'timeRangeOptions', event)}>

          {visiteTime ? (
          className='at-list__item-fix'
          extraText={visiteTime}
          />) : (請(qǐng)選擇預(yù)約時(shí)間)}







          離開時(shí)間

          range={timeRangeOptions}
          onChange={(event) => this.handlePickerChange('leaveTime', 'timeRangeOptions', event)}>

          {leaveTime ? (
          className='at-list__item-fix'
          extraText={leaveTime}
          />) : (請(qǐng)選擇離開時(shí)間)}







          來(lái)訪事由

          maxLength={200}
          className='textarea-fix'
          value={matter}
          onChange={(value) => {this.handleChange('matter', value)}}
          placeholder='請(qǐng)輸入來(lái)訪事由...'
          />



          circle
          loading={isLoading}
          disabled={isLoading}
          type='primary'
          size='normal'
          formType='submit'
          className='col btn-submit'>
          提交




          );
          }
          }

          5.1.3 短信驗(yàn)證碼的設(shè)計(jì)實(shí)現(xiàn)

          這里也可以單獨(dú)抽離出一個(gè)組件,主要的點(diǎn)在于,點(diǎn)擊后的倒計(jì)時(shí)和重新發(fā)送,可以重點(diǎn)看下,具體的實(shí)現(xiàn)邏輯如下:

          import Taro from '@tarojs/taro';
          import { Component } from 'react';
          import { View, Text } from '@tarojs/components';
          import { AtInput, AtButton } from 'taro-ui';

          import 'taro-ui/dist/style/components/input.scss';
          import 'taro-ui/dist/style/components/button.scss';
          import './index.scss';

          const DEFAULT_SECOND = 120;
          import { request } from 'utils/request';

          export default class SendSMS extends Component {

          constructor(props) {
          super(props);
          this.state = {
          mobile: '', // 手機(jī)號(hào)
          confirmCode: '', // 驗(yàn)證碼
          smsCountDown: DEFAULT_SECOND,
          smsCount: 0,
          smsIntervalId: 0,
          isClick: false,
          };
          }

          componentDidMount () { }

          componentWillUnmount () {
          if (this.state.smsIntervalId) {
          clearInterval(this.state.smsIntervalId);
          this.setState(prevState => {
          return {
          ...prevState,
          smsIntervalId: 0,
          isClick: false
          }
          })
          }
          }

          componentDidUpdate (prevProps, prveState) {
          }

          componentDidShow () { }

          componentDidHide () { }

          handleChange (key, value) {
          this.setState({
          [key]: value
          })
          return value;
          }

          processSMSRequest () {
          const { mobile } = this.state;
          if (!mobile || !/^1(3[0-9]|4[579]|5[012356789]|66|7[03678]|8[0-9]|9[89])\d{8}$/.test(mobile)) {
          Taro.showToast({
          title: '請(qǐng)?zhí)顚懻_的手機(jī)號(hào)',
          icon: 'none',
          duration: 2000
          })
          return;
          }
          this.countDown()
          }

          sendSMS () {
          const { mobile } = this.state;
          request({
          url: '/sms/send',
          method: 'post',
          data: { mobile }
          }, false).then(res => {
          console.log(res);
          const { data: { data: { description } } } = res;
          Taro.showToast({
          title: description,
          icon: 'none',
          duration: 2000
          })
          }).catch(err => {
          console.log(err);
          });
          }

          countDown () {
          if (this.state.smsIntervalId) {
          return;
          }
          const smsIntervalId = setInterval(() => {
          const { smsCountDown } = this.state;
          if (smsCountDown === DEFAULT_SECOND) {
          this.sendSMS();
          }
          this.setState({
          smsCountDown: smsCountDown - 1,
          isClick: true
          }, () => {
          const { smsCount, smsIntervalId, smsCountDown } = this.state;
          if (smsCountDown <= 0) {
          this.setState({
          smsCountDown: DEFAULT_SECOND,
          })
          smsIntervalId && clearInterval(smsIntervalId);
          this.setState(prevState => {
          return {
          ...prevState,
          smsIntervalId: 0,
          smsCount: smsCount + 1,
          }
          })
          }
          })
          }, 1000);
          this.setState({
          smsIntervalId
          })
          }

          submit() {
          // 校驗(yàn)參數(shù)
          const { mobile, confirmCode } = this.state;
          if (!mobile || !/^1(3[0-9]|4[579]|5[012356789]|66|7[03678]|8[0-9]|9[89])\d{8}$/.test(mobile)) {
          Taro.showToast({
          title: '請(qǐng)?zhí)顚懻_的手機(jī)號(hào)',
          icon: 'none',
          duration: 2000
          })
          return;
          } else if (confirmCode.length !== 6) {
          Taro.showToast({
          title: '驗(yàn)證碼輸入有誤',
          icon: 'none',
          duration: 2000
          })
          return;
          }
          this.props.submit({ mobile, code: confirmCode });
          }

          render () {
          const { mobile, confirmCode, smsCountDown, isClick } = this.state;

          return (


          required
          type='phone'
          name='mobile'
          title='手機(jī)號(hào)碼'
          className='row-inline-col-7'
          placeholder='請(qǐng)輸入手機(jī)號(hào)碼'
          value={mobile}
          onChange={(value) => {this.handleChange('mobile', value)}}
          />
          {!isClick ? ( onClick={() => this.processSMSRequest()}
          className='row-inline-col-3 at-input__input code-fix'>
          發(fā)送驗(yàn)證碼
          ) : ( onClick={() => this.processSMSRequest()}
          className='row-inline-col-3 at-input__input code-fix red'>
          {( smsCountDown === DEFAULT_SECOND ) ? '重新發(fā)送' : `${smsCountDown}秒后重試`}
          )}


          required
          type='text'
          name='confirmCode'
          title='驗(yàn)證碼'
          placeholder='請(qǐng)輸入驗(yàn)證碼'
          value={confirmCode}
          onChange={(value) => {this.handleChange('confirmCode', value)}}
          />


          circle
          type='primary'
          size='normal'
          onClick={() => this.submit()}
          className='col btn-submit'>
          查詢



          )
          }
          }

          5.1.4 前端的一些配置

          路由跳頁(yè)模塊的封裝

          import?Taro?from?'@tarojs/taro';

          //?https://taro-docs.jd.com/taro/docs/apis/route/navigateTo
          export?const?goToPage?=?(page,?params?=?{},?success,?events)?=>?{
          ??let?url?=?`/pages/${page}/index`;
          ??if?(Object.keys(params).length?>?0)?{
          ????let?paramsStr?=?'';
          ????for?(const?key?in?params)?{
          ??????const?tmpStr?=?`${key}=${params[key]}`;
          ??????paramsStr?=?tmpStr?+?'&';
          ????}
          ????if?(paramsStr.endsWith('&'))?{
          ??????paramsStr?=?paramsStr.substr(0,?paramsStr.length?-?1);
          ????}
          ????if?(paramsStr)?{
          ??????url?=?`${url}?${paramsStr}`;
          ????}
          ??}
          ??Taro.navigateTo({
          ????url,
          ????success,
          ????events
          ??});
          };

          請(qǐng)求方法模塊的封裝

          import?Taro?from?'@tarojs/taro';
          const?baseUrl?=?'http://127.0.0.1:9000';?//?請(qǐng)求的地址

          export?function?request(options,?isLoading?=?true)?{
          ??const?{?url,?data,?method,?header?}?=?options;
          ??isLoading?&&
          ????Taro.showLoading({
          ??????title:?'加載中'
          ????});
          ??return?new?Promise((resolve,?reject)?=>?{
          ????Taro.request({
          ??????url:?baseUrl?+?url,
          ??????data:?data?||?{},
          ??????method:?method?||?'GET',
          ??????header:?header?||?{},
          ??????success:?res?=>?{
          ????????resolve(res);
          ??????},
          ??????fail:?err?=>?{
          ????????reject(err);
          ??????},
          ??????complete:?()?=>?{
          ????????isLoading?&&?Taro.hideLoading();
          ??????}
          ????});
          ??});
          }

          日期格式的封裝

          import?moment?from?'moment';

          export?const?enumerateDaysBetweenDates?=?function(startDate,?endDate)?{
          ??let?daysList?=?[];
          ??let?SDate?=?moment(startDate);
          ??let?EDate?=?moment(endDate);
          ??let?xt;
          ??daysList.push(SDate.format('YYYY-MM-DD'));
          ??while?(SDate.add(1,?'days').isBefore(EDate))?{
          ????daysList.push(SDate.format('YYYY-MM-DD'));
          ??}
          ??daysList.push(EDate.format('YYYY-MM-DD'));
          ??return?daysList;
          };

          export?const?getSubTractDate?=?function(n?=?-2)?{
          ??return?moment()
          ????.subtract(n,?'months')
          ????.format('YYYY-MM-DD');
          };

          阿里媽媽圖標(biāo)庫(kù)引入, 打開https://www.iconfont.cn/ ,找到喜歡的圖表下載下來(lái), 然后引入,在對(duì)應(yīng)的地方加上iconfont和它對(duì)應(yīng)的樣式類的值

          import { View, Text } from '@tarojs/components';
          import { AtIcon } from 'taro-ui'
          import "taro-ui/dist/style/components/icon.scss";

          import 'assets/iconfont/iconfont.css'
          import './index.scss'

          import { goToPage } from 'utils/router.js'

          export default function Header(props) {
          return (

          { props.title }
          goToPage('index')}>



          )
          }

          redux的使用,這里主要是多頁(yè)面共享數(shù)據(jù)的時(shí)候用了下,核心代碼就這點(diǎn)

          import?{?UPDATE?}?from?'constants/form';

          const?INITIAL_STATE?=?{
          ??city:?'',
          ??createTime:?'',
          ??gender:?'',
          ??id:?'',
          ??idcard:?'',
          ??leaveTime:?'',
          ??matter:?'',
          ??mobile:?'',
          ??orgin:?'',
          ??place:?'',
          ??province:?'',
          ??religiousCountry:?'',
          ??religiousType:?'',
          ??updateTime:?'',
          ??username:?'',
          ??visiteDate:?'',
          ??visiteTime:?''
          };

          export?default?function?form(state?=?INITIAL_STATE,?action)?{
          ??switch?(action.type)?{
          ????case?UPDATE:
          ??????return?{
          ????????...state,
          ????????...action.data
          ??????};
          ????default:
          ??????return?state;
          ??}
          }

          使用方法如下

          @connect(({?form?})?=>?({
          ??form
          }),?(dispatch)?=>?({
          ??updateForm?(data)?{
          ????dispatch(update(data))
          ??}
          }))
          ??componentWillUnmount?()?{
          ????const?{?updateForm?}?=?this.props;
          ????updateForm({
          ??????city:?'',
          ??????createTime:?'',
          ??????gender:?'',
          ??????id:?'',
          ??????idcard:?'',
          ??????leaveTime:?'',
          ??????matter:?'',
          ??????mobile:?'',
          ??????orgin:?'',
          ??????place:?'',
          ??????province:?'',
          ??????religiousCountry:?'',
          ??????religiousType:?'',
          ??????updateTime:?'',
          ??????username:?'',
          ??????visiteDate:?'',
          ??????visiteTime:?''
          ????})
          ??}

          開發(fā)環(huán)境和生成環(huán)境的打包配置, ?因?yàn)樽詈笠系絜gg服務(wù)里面,所以這里生產(chǎn)環(huán)境的publicPath和baseName都應(yīng)該是 /public

          module.exports?=?{
          ??env:?{
          ????NODE_ENV:?'"production"'
          ??},
          ??defineConstants:?{},
          ??mini:?{},
          ??h5:?{
          ????/**
          ?????*?如果h5端編譯后體積過大,可以使用webpack-bundle-analyzer插件對(duì)打包體積進(jìn)行分析。
          ?????*?參考代碼如下:
          ?????*?webpackChain?(chain)?{
          ?????*???chain.plugin('analyzer')
          ?????*?????.use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin,?[])
          ?????*?}
          ?????*/

          ????publicPath:?'/public',
          ????router:?{
          ??????basename:?'/public'
          ????}
          ??}
          };

          開發(fā)環(huán)境名字可自定義如:

          module.exports?=?{
          ??env:?{
          ????NODE_ENV:?'"development"'
          ??},
          ??defineConstants:?{},
          ??mini:?{},
          ??h5:?{
          ????publicPath:?'/',
          ????esnextModules:?['taro-ui'],
          ????router:?{
          ??????basename:?'/religion'
          ????}
          ??}
          };

          5.2 后端實(shí)現(xiàn)

          后端這塊,其他的都沒啥好講的,具體可以參看我之前寫的兩篇文章,或者閱讀源碼,這里著重講下防止短信驗(yàn)證碼惡意注冊(cè)吧。

          5.2.1 如何防止短信驗(yàn)證碼對(duì)惡意使用

          這個(gè)主要是在于用的是內(nèi)部實(shí)現(xiàn)的短信驗(yàn)證碼接口(自家用的),不是市面上一些成熟的短信驗(yàn)證碼接口,所以在預(yù)發(fā)布階段安全方面曾經(jīng)收到過一次攻擊(包工頭家的服務(wù)器每天都有人去攻擊,好巧不巧剛被我撞上了),被惡意使用了1W條左右短信,痛失8張毛爺爺啊??偨Y(jié)了下這次教訓(xùn),主要是從IP、發(fā)送的頻率、以及加上csrf Token去預(yù)防被惡意使用。

          大致是這樣搞得。

          安裝相對(duì)于的類庫(kù)

          "egg-ratelimiter":?"^0.1.0",
          "egg-redis":?"^2.4.0",

          config/plugin.js下配置

          ?ratelimiter:?{
          ????enable:?true,
          ????package:?'egg-ratelimiter',
          ??},
          ??redis:?{
          ????enable:?true,
          ????package:?'egg-redis',
          ??},

          config/config.default.js下配置

          ?config.ratelimiter?=?{
          ????//?db:?{},
          ????router:?[
          ??????{
          ????????path:?'/sms/send',
          ????????max:?5,
          ????????time:?'60s',
          ????????message:?'臥槽,你不講武德,老是請(qǐng)求干嘛干嘛干嘛!',
          ??????},
          ????],
          ??};

          ??config.redis?=?{
          ????client:?{
          ??????port:?6379,?//?Redis?port
          ??????host:?'127.0.0.1',?//?Redis?host
          ??????password:?null,
          ??????db:?0,
          ????},
          ??};

          效果是這樣的


          六、參考文獻(xiàn)

          • TaroJS官網(wǎng):https://taro-docs.jd.com/taro/docs/README
          • ReactJS官網(wǎng):https://reactjs.org/
          • eggJS官網(wǎng):https://eggjs.org/

          七、寫在最后

          關(guān)于UI這塊也就這樣了, 我應(yīng)該多認(rèn)識(shí)些UI小姐姐幫忙看一看的,看到這里就要和大家說(shuō)再見了, 通過閱讀本文,對(duì)于表單的制作你學(xué)會(huì)了嗎?歡迎在下方發(fā)表你的看法,也歡迎和筆者交流!

          github項(xiàng)目地址:https://github.com/cnroadbridge/jingzhou-religion

          gitee項(xiàng)目地址:https://gitee.com/taoge2021/jingzhou-religion


          瀏覽 93
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  99精品欧美一区二区蜜桃免费 | 中文字幕第一页精品视频 | 久久国产免费娱乐视频 | 成人18禁免费网站 | 一区国产好的作爱视 |