Based on React and node Design and implementation of form entry system based on JS

1, Write in front

This is a real project. It has been a long time. Although it is very simple, there are still many thinking points. Follow the steps of the author and have a look. This article is purely fictional, and the relevant information involved has been fictitious,

2, Background

People must have faith when they live. The soul without faith is empty. You can believe in Jesus, Buddha, Islam, science and so on. In order to control the gathering of people in major religious places and add a modest force to the society, the leaders of Jingzhou decided to make a form system to count the number of visits of people at a certain time or period and control the scope of religious activities. Secretary Sha Ruijin of Handong provincial Party committee was particularly concerned about this matter and decided to check it in person. After several turns, the task fell to programmer Jiang Tao, The story unfolds.

3, Demand analysis

The following functions need to be realized:

  • Entry of form data
  • Query of the latest record of the entered data
  • Use of SMS verification code
  • Scan code and fill in form information

There are two schemes. One is to go in and choose the corresponding religious place (asymmetric distribution and three-level linkage). The other is to click the corresponding religious place to fill in the form. The place in the form cannot be changed. Different designs have different ideas. Although I have written both, here I will write this article according to the second one. If you are interested in understanding the first one, you are welcome to communicate with me.

4, System design

This time I decided not to use vue, but to use react's Taro framework to write this small project (try the multi terminal framework taro ha ha). The backend side plans to use nodejs's eggjs framework, mysql for the database, and redis for the database. Due to the limitation of server port, docker can't be moved, and there's no nginx. It doesn't matter. Egg's own web server will be used, and the project will be completed. In this way, the test tube baby of taro and egg was born.

5, Code implementation

Well, there are many and miscellaneous things. Let's talk about them. It's suggested to read these two articles together, based on Vue JS and node Design and implementation of anti fraud system based on JS https://www.cnblogs.com/cnroadbridge/p/15182552.html , design and implementation of demo based on React and GraphQL https://www.cnblogs.com/cnroadbridge/p/15318408.html

5.1 front end implementation

For the installation and use of taroJS, see https://taro-docs.jd.com/taro/docs/GETTING-STARTED

5.1.1 overall layout design

It is mainly the head and other layouts. It is relatively simple. Then pull out a public component header and throw it a method that can jump to the link. The logic is very simple, that is, a title, followed by an icon to return to the home page

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 (
    <View className='header'>
      <Text className='header-text'>{ props.title }</Text>
      <Text onClick={() => goToPage('index')}>
        <AtIcon prefixClass='icon' className='iconfont header-reback' value='home' color='#6190e8'></AtIcon>
      </Text>
    </View>
  )
}

For this part, you can also see the packaging of card components under components

5.1.2 form design

There's nothing to say about form design. It's mainly that you need to write some css to adapt to the page. The specific logic implementation code is as follows:

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: 'Reservation registration', // title
      username: '', // full name
      gender: '', // Gender
      mobile: '', // mobile phone
      idcard: '', // ID
      orgin: '', //Visitor origin
      province: '', //province
      city: '', // city
      place: '', //Religious address
      religiousCountry: '', // Religious County
      religiousType: '', // Religious type
      matter: '', // Reason for visit
      visiteDate: '', // Date of visit
      visiteTime: '', // Visit time
      leaveTime: '', // Departure time
      genderOptions: [
        { label: 'male', value: 'male' },
        { label: 'female', value: 'female' },
      ], // Gender options
      genderMap: { male: 'male', female: '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',
      ], // Time options
      orginRangeOptions: [[],[]], // Provincial and municipal options
      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 === 'municipality directly under the Central Government') {
          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: 'Please fill in the user name',
        icon: 'none',
        duration: 2000
      })
      return;
    } else if (!gender) {
      Taro.showToast({
        title: 'Please select gender',
        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: 'Please fill in the correct mobile phone number',
        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: 'Please fill in the correct ID number.',
        icon: 'none',
        duration: 2000
      })
      return;
    } else if (!orgin) {
      Taro.showToast({
        title: 'Please select the source',
        icon: 'none',
        duration: 2000
      })
      return;
    } else if (!place) {
      Taro.showToast({
        title: 'Please choose a religious place',
        icon: 'none',
        duration: 2000
      })
      return;
    } else if (!visiteDate) {
      Taro.showToast({
        title: 'Please select an appointment date',
        icon: 'none',
        duration: 2000
      })
      return;
    } else if (!visiteTime) {
      Taro.showToast({
        title: 'Please select an appointment time',
        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: 'Appointment succeeded',
        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: 'Appointment failed',
        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 (
      <View className='visitor-registration'>
        <Header title={title}/>
        <AtForm
          onSubmit={this.onSubmit.bind(this)}
        >
        <View className='row'>
          <AtInput
              required
              type='text'
              name='username'
              className='col'
              title='Visitor name'
              placeholder='Please enter visitor name'
              value={username}
              onChange={(value) => {this.handleChange('username', value)}}
            />
          </View>
          <View className='row'>
            <View className='col at-input'>
              <Text className='at-input__title at-input__title--required'>
                Gender
              </Text>
              <View className='at-input__input'>
                <RadioGroup>
                  {genderOptions.map((genderOption, i) => {
                    return (
                      <Label for={i} key={i}>
                        <Radio
                          value={genderOption.value}
                          onClick={(event) => {this.handleRadioClick('gender', genderOption.value)}}>
                          {genderOption.label}
                        </Radio>
                      </Label>
                    )
                  })}
                </RadioGroup>
              </View>
            </View>
          </View>
          <View className='row'>
            <AtInput
              required
              type='phone'
              name='mobile'
              title='phone number'
              className='col'
              placeholder='Please enter your mobile phone number'
              value={mobile}
              onChange={(value) => {this.handleChange('mobile', value)}}
            />
          </View>
          <View className='row'>
            <AtInput
              required
              name='idcard'
              type='idcard'
              className='col'
              title='ID number'
              placeholder='Please enter your ID number.'
              value={idcard}
              onChange={(value) => {this.handleChange('idcard', value)}}
            />
          </View>
          <View className='row'>
             <View className='at-input col col-fix'>
                <Text className='at-input__title at-input__title--required'>
                  Source
                </Text>
                <Picker mode='multiSelector'
                        onChange={(event) => this.handleOriginRangeChange(event)}
                        onColumnChange={(event) => this.handleOriginRangleColumnChange(event)}
                        range={orginRangeOptions}
                        value={orginRangeKey}>
                  <AtList>
                   {orgin ? (
                     <AtListItem
                     className='at-list__item-fix'
                     extraText={orgin}
                   />) : (<Text className='input-placeholder-fix'>Please select the source of visitors</Text>)}
                  </AtList>
                </Picker>
            </View>
          </View>
          <View className='row'>
            <AtInput
                required
                type='text'
                name='place'
                className='col'
                title='Religious sites'
                disabled
                placeholder='Please choose a religious place'
                value={place}
                onChange={(value) => {this.handleChange('place', value)}}
              />
          </View>
          <View className='row'>
            <View className='at-input col col-fix'>
                <Text className='at-input__title at-input__title--required'>
                  Appointment date
                </Text>
                <Picker mode='date'
                        onChange={(event) => this.handleDateChange('visiteDate', event)}>
                  <AtList>
                   {visiteDate ? (
                     <AtListItem
                      className='at-list__item-fix'
                      extraText={visiteDate}
                    />) : (<Text className='input-placeholder-fix'>Please select an appointment date</Text>)}
                  </AtList>
                </Picker>
              </View>
          </View>
          <View className='row'>
            <View className='at-input col col-fix'>
                <Text className='at-input__title at-input__title--required'>
                  time of appointment
                </Text>
                <Picker mode='selector'
                        range={timeRangeOptions}
                        onChange={(event) => this.handlePickerChange('visiteTime', 'timeRangeOptions', event)}>
                  <AtList>
                   {visiteTime ? (
                     <AtListItem
                     className='at-list__item-fix'
                     extraText={visiteTime}
                   />) : (<Text className='input-placeholder-fix'>Please select an appointment time</Text>)}
                  </AtList>
                </Picker>
              </View>
          </View>
          <View className='row'>
            <View className='at-input col col-fix'>
              <Text className='at-input__title'>
                Departure time
              </Text>
              <Picker mode='selector'
                      range={timeRangeOptions}
                      onChange={(event) => this.handlePickerChange('leaveTime', 'timeRangeOptions', event)}>
                  <AtList>
                   {leaveTime ? (
                      <AtListItem
                      className='at-list__item-fix'
                      extraText={leaveTime}
                    />) : (<Text className='input-placeholder-fix'>Please select the departure time</Text>)}
                  </AtList>
              </Picker>
            </View>
          </View>
          <View className='row'>
            <View className='col at-input'>
              <Text className='at-input__title'>
                   Reason for visit
              </Text>
              <AtTextarea
                maxLength={200}
                className='textarea-fix'
                value={matter}
                onChange={(value) => {this.handleChange('matter', value)}}
                placeholder='Please enter the reason for visiting...'
              />
            </View>
          </View>
          <View className='row'>
            <AtButton
              circle
              loading={isLoading}
              disabled={isLoading}
              type='primary'
              size='normal'
              formType='submit'
              className='col btn-submit'>
                Submit
            </AtButton>
          </View>
        </AtForm>
      </View>
    );
  }
}

5.1.3 design and implementation of SMS verification code

You can also separate a component here. The main point is that the countdown and resending after clicking can be focused on. The specific implementation logic is as follows:

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: '', // cell-phone number
      confirmCode: '', // Verification Code
      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: 'Please fill in the correct mobile phone number',
        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() {
    // Calibration parameters
    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: 'Please fill in the correct mobile phone number',
        icon: 'none',
        duration: 2000
      })
      return;
    } else if (confirmCode.length !== 6) {
      Taro.showToast({
        title: 'Incorrect verification code input',
        icon: 'none',
        duration: 2000
      })
      return;
    }
    this.props.submit({ mobile, code: confirmCode });
  }

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

    return (
      <View className='sms-box'>
         <View className='row-inline'>
            <AtInput
              required
              type='phone'
              name='mobile'
              title='phone number'
              className='row-inline-col-7'
              placeholder='Please enter your mobile phone number'
              value={mobile}
              onChange={(value) => {this.handleChange('mobile', value)}}
            />
            {!isClick ? ( <Text
              onClick={() => this.processSMSRequest()}
              className='row-inline-col-3 at-input__input code-fix'>
               Send verification code
            </Text>) : ( <Text
              onClick={() => this.processSMSRequest()}
              className='row-inline-col-3 at-input__input code-fix red'>
               {( smsCountDown === DEFAULT_SECOND ) ? 'Resend' : `${smsCountDown}Try again in seconds`}
            </Text>)}
          </View>
          <View>
            <AtInput
              required
              type='text'
              name='confirmCode'
              title='Verification Code'
              placeholder='Please enter the verification code'
              value={confirmCode}
              onChange={(value) => {this.handleChange('confirmCode', value)}}
            />
          </View>
          <View>
            <AtButton
              circle
              type='primary'
              size='normal'
              onClick={() => this.submit()}
              className='col btn-submit'>
                query
            </AtButton>
          </View>
      </View>
    )
  }
}

5.1.4 some configurations of the front end

Encapsulation of routing page hopping module

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
  });
};

Encapsulation of request method module

import Taro from '@tarojs/taro';
const baseUrl = 'http://127.0.0.1:9000'; //  Requested address

export function request(options, isLoading = true) {
  const { url, data, method, header } = options;
  isLoading &&
    Taro.showLoading({
      title: 'Loading'
    });
  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();
      }
    });
  });
}

Encapsulation of date format

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');
};

Ali mother icon library is imported and opened https://www.iconfont.cn/ , find the chart you like, download it, then import it, and add the values of iconfont and its corresponding style class in the corresponding place

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 (
    <View className='header'>
      <Text className='header-text'>{ props.title }</Text>
      <Text onClick={() => goToPage('index')}>
        <AtIcon prefixClass='icon' className='iconfont header-reback' value='home' color='#6190e8'></AtIcon>
      </Text>
    </View>
  )
}

The use of redux is mainly used when sharing data on multiple pages. That's the core code

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;
  }
}

The usage is as follows

@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: ''
    })
  }

The packaging configuration of the development environment and the generation environment should be integrated into the egg service, so the publicPath and baseName of the production environment should be / public

module.exports = {
  env: {
    NODE_ENV: '"production"'
  },
  defineConstants: {},
  mini: {},
  h5: {
    /**
     * If the compiled volume of the h5 side is too large, you can use the webpack bundle analyzer plug-in to analyze the packed volume.
     * Reference codes are as follows:
     * webpackChain (chain) {
     *   chain.plugin('analyzer')
     *     .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
     * }
     */
    publicPath: '/public',
    router: {
      basename: '/public'
    }
  }
};

The development environment name can be customized, such as:

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

5.2 backend implementation

There is nothing else to say about the back-end. For details, please refer to the two articles I wrote before, or read the source code. Here we focus on preventing malicious registration of SMS verification code.

5.2.1 how to prevent malicious use of SMS verification code

This is mainly because it uses the internally implemented short message verification code interface (for home use), not some mature short message verification code interfaces on the market. Therefore, in the pre release stage, there has been an attack on Security (the server of the Contractor's head is attacked every day, but unfortunately I just hit it), and about 1W short messages have been maliciously used, Lost 8 pieces of Grandpa Mao. The following lessons are summarized, mainly from IP, sending frequency, and adding csrf Token to prevent malicious use.

That's about it.

Install class libraries relative to

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

In config / plugin JS configuration

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

In config / config default. JS configuration

 config.ratelimiter = {
    // db: {},
    router: [
      {
        path: '/sms/send',
        max: 5,
        time: '60s',
        message: 'Lying trough, you don't talk about martial virtue. You always ask for something!',
      },
    ],
  };

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

The effect is this

6, References

  • TaroJS official website: https://taro-docs.jd.com/taro/docs/README
  • ReactJS official website: https://reactjs.org/
  • eggJS official website: https://eggjs.org/

7, Write at the end

That's all about UI. I should know more about UI sister to help take a look. I'll say goodbye to you here. Have you learned how to make forms by reading this article? Welcome to express your views below, and also welcome to communicate with the author!

GitHub project address: https://github.com/cnroadbridge/jingzhou-religion

Gitee project address: https://gitee.com/taoge2021/jingzhou-religion

Added by banjax on Tue, 25 Jan 2022 15:17:16 +0200