Recently, I have been working on the requirements of H5 official account. The technology stack used is like the title. From the initial project to the stable growth stage, React is used at the front end. If you need to use React, please refer to wechat H5.
Features of this paper:
- React+H5+ wechat
- Switch from Vue to React.
Project Introduction:
The car logistics platform can complete the order of car transportation online through the public account.
- Line price enquiry
- Order information filling
- Address book management
- Real-name authentication
- Bank card binding
- coupons
- WeChat pay
- Activity posters
The body of the
- Distinguish Umi from Dva
- Umi configuration
- React is usually written like this
- Introduction to Dva use
- WeChat configuration
- TypeScript components
- H5 tip
- logging
- other
1. Differentiate BETWEEN Umi and Dva
When I first changed from Vue to React, it was easy to confuse Umi and Dva. They both called themselves application frameworks.
Umi
Loosely speaking: this can be interpreted as a vue-CLI scaffolding that helps you generate project templates with the Router right out of the box. Integration of Router + ANTD, as well as some internationalization, configuration routing and other functions of deep integration, Umi documents.
Dva
Loosely speaking: can be understood as a Vuex – like state management library. Integrated redux + ReduxSaga, Dva documentation.
Why Umi + Dva
When developing with Vue, we prefer vuE-CLI template creation and Vuex state management.
Umi is also more powerful and closer to business scenarios than create-React-app, the official React scaffolding, right out of the box.
React had Redux for state management, but redux-Saga was redux-Saga, which was packaged by Dva as a more useful data flow solution. See “An article Summarizing Redux, React-Redux, redux-Saga”.
2. Umi configuration
Umi has a powerful configuration. The following lists the important configurations used for Umi. For details, see configuration on the official website.
The environment variable
The front end packages different environment code based on environment variables, such as Api addresses, static resource addresses, and so on.
branch | Packaging scripts | The environment | Address of the Api | Static resource address |
---|---|---|---|---|
Maser | build:test |
test | fat.*.com |
cdn.fat.*.com |
Develop | build:prod |
A formal | *.com |
cdn.*.com |
// package.json
{
"scripts": {
"start": "cross-env APP_TYPE=site BUILD_ENV=dev PORT=80 umi dev"."build": "umi build"."build:test": "BUILD_ENV=test umi build"."build:prod": "BUILD_ENV=prod umi build",}}Copy the code
Umi – plugin – react configuration
React plugin is an official plugin for React. It loads pages on demand and rem ADAPTS.
dynamicImport
Specify the loading component to enter the page.hd
Enable rem scheme
Wechat development must turn off the PWA option, otherwise it will lead to serious cache after the launch.
Other configuration
PostCSS/ load on demand/Theme/Proxy Proxy.
code
import pageRoutes from './router.config';
import theme from '.. /src/theme';
import webpackPlugin from './plugin.config';
const plugins = [
[
'umi-plugin-react',
{
antd: true.dva: {
hmr: true,},dynamicImport: { //
loadingComponent: './components/PageLoading/index'.webpackChunkName: true,},pwa: false.title: {
defaultTitle: 'Default title',},dll: false.hd: true.fastClick: false.routes: {
exclude: []},hardSource: false,}]];const env = process.env.BUILD_ENV,
publicPath = {
"dev": ""."test": "//*.fat.*.com/"."prod": "//*.*.com/"
}[env];
const apiPath = {
"dev": 'http://*.feature.*.com/api'."test": 'http://*.fat.*.com/api'."prod": 'https://*.*.com/api'
}[env];
export default {
base: '/'.publicPath: publicPath,
define: {
APP_TYPE: process.env.APP_TYPE || ' '.apiPath: apiPath || ' ',},// history: 'hash', // default is browser
plugins,
routes: pageRoutes,
theme: { / / theme
'brand-primary': theme.primaryColor,
'brand-primary-tap': theme.brandPrimaryTap,
},
externals: {},
lessLoaderOptions: {
javascriptEnabled: true,},targets: {
android: 5.chrome: 58.edge: 13.firefox: 45.ie: 9.ios: 7.safari: 10,},outputPath: './dist'.hash: true.alias: {},
proxy: { / / agent
'/api/': {
changeOrigin: true.target: 'http://doclever.xin.com/mock/5d0b67ac3eb3ea0008d58a31',}},ignoreMomentLocale: true.manifest: {
basePath: '/',},chainWebpack: webpackPlugin,
extraPostCSSPlugins: [ / / postcss plug-in
require('postcss-flexbugs-fixes')],es5ImcompatibleVersions: true.extraBabelPlugins: [['import', { libraryName: 'antd-mobile'.style: true }] // Load the ANTD-mobile style file on demand]};Copy the code
3. How do you React
React/Vue React/Vue React/Vue React/Vue React/Vue React/Vue React
Map replaces the V-for instruction
const arr = ['aaa'.'bbb'.'ccc'];
arr.map(str= > <div>{str}</div>);
Copy the code
Logical operation symbol replaced with V-if/V-else
const show = false;
render (){
return <>
{show && <div>I've shown</div>}
{show ? <div>To true to show</div> : <div>To false to show</div>}
</>
};
Copy the code
Route parameters are transmitted across pages
In React, only components wrapped with withRouter can obtain routing parameters. In BasicLayout, page-level components must be wrapped manually if they need to obtain routing parameters.
import { withRouter } from 'react-router-dom'
class FixBar extends React.Component{
public render() {
const { location: { query = {} }, } = this.props;
return (<div>{query. Id | | 'default text'}</div>); }}export default withRouter(FixBarCom);
Copy the code
Listen for props/state changes
In Vue, watchapi can be used to monitor parameter changes conveniently. React requires other apis to implement similar functions. GetDerivedStateFromProps is not used often, that is, when the value of state is dependent on props at all times.
componentDidUpdate(prevProps, prevState) {
/ / to monitor props
if (this.props.userID ! == prevProps.userID) {// doSomething
}
/ / to monitor the state
if (this.state.name ! == prevState.name) {// doSomething}}Copy the code
SetState and fiber
For better performance, React uses a Fiber architecture, which means that setState operations can be asynchronous.
/ / do not recommend
this.setState({ a:1})
this.setState({ a:this.state.a + 1})
/ / recommend
this.setState({a:1},() = > {
this.setState({ a:this.state.a + 1})})/ / do not recommend
this.setState({
counter: this.state.counter + this.props.increment,
});
/ / recommend
this.setState((state, props) = > ({
counter: state.counter + props.increment
}));
Copy the code
ES6 with setState:
const data = { a: '11' }
this.setState({ ... data })this.setState({ a:'222'})
this.setState({ ['a'] :'333' })
this.setState((prevState, props) = > ({a: '444' }));
Copy the code
The React debounce stabilization
Many scenarios require additional shock protection for the onChage event Input, using the lodash. Debounce method.
import _ from 'lodash';
class DebounceExample extends React.Component {
constructor(props) {
this.handleInputThrottled = _.debounce(this.getSomeFn, 100)}getSomeFn(res){
// doSomeThing
}
render() {
return <input onChange={this.handleInputThrottled}/>}}export defaultDebounceExample;
Copy the code
React createPortal
Some scenarios require that the content node of the child element be placed in another component, such as the box component, which wants to be under the root of the body element each time, using createPortal.
Official explanation:
Portal provides an excellent solution for rendering child nodes to DOM nodes that exist outside the parent component.
For example, we need A/B2 input box components whose pull-down results will be notified under the parent component.
As shown in figure:
/ / the parent component
<inputCom />
<inputCom />
<div id="listBox"></div>
// inputCom
class InputCom extends React.Component{
ExternalCityComp(){
return return ReactDOM.createPortal(<div>The results of</div>.document.querySelector('#listBox'))
}
public render() {
return (<div><input onChage={} />{this.ExternalCityComp()}</div>); }}Copy the code
Static static method
The React component can set up static methods, such as implementing methods similar to the Toast component.
class Toast extends React.Component {...static success(Param) { // do something }
static fail(Param) { // do something }. } Toast.success() Toast.fail()Copy the code
The className style
const s = require('./index.less');
render (){
const active = true
return <>
<li className={` ${s.inputItem} ${s.borderBottom} `} ></li>
<li className={` ${active ? s.inputItem : s.other } ${s.borderBottom} `} ></li>
</>
}
Copy the code
Reference picture
import btnImg from './images/floatBtn.png';
render (){
return <img src={btnImg} />
}
Copy the code
4. Introduction to Dva use
In the useDva
Before, there was always a connectionVuex
The usage is not much different, but the first time to use, chew for a long timeDva
Documents, or around the dizzy; In fact, it is not complicated, today write how do not understandredux
,redux-saga
In case of pleasant useDva
“, the god skipped by himself.
File directory
We only need to focus on the files in three directories.
- Page:
src/pages
- Model:
src/models
- Services:
src/services
The page is the page component that we add to the route, the model is the focus, and the service is basically the encapsulated request request.
Asynchronous request, synchronous request
- We treat models as a global variable that holds data and can be retrieved by any page, with apis for storing and retrieving data.
- There are two ways to store data, one is to directly store the data in models (synchronous request), and the other is to send a request and store the data in Models (asynchronous request).
- Synchronous request
reducers
The methods in themodels.state
Data in. - Asynchronous requests are called
models
theeffects
Method, which is calledservices
Method to get the request data. - The asynchronous request is getting
services
Return the data if you want to save tomodels.state
, then call the synchronization methodreducers
Can.
To refresh your memory: synchronous means save directly, asynchronous means request and save.
Page to obtainmodels
data
The page gets the data through the dvA.connect method + models.namespace (each model has its own namespace). The main function of the CONNECT method is to merge the data in the Models into the props of the page component. The code lists three different call syntax, if you feel that ES6 decorator + destruct + arrow function is not intuitive, you can look at the code of ES5 version.
Code:
import React from 'react';
import { connect } from 'dva';
// Version 1 decorator syntax
@connect(({ list:{ payInfo, detail } }) = > ({ payInfo, detail }))
class PayInfo extends React.Component<ITextPaperProps.IEntranceState.any> {
public render() {
// Get this from props
const { payInfo, detail } = this.props;
return (
<div >
{payInfo}
</div>); }}export default PayInfo;
// Version 2 functions
export default connect(
({ list:{ payInfo, detail } }) = > ({ payInfo, detail }))(PayInfo);
Version 3 ES5 functions
export default connect(function(modules){
return {
payInfo: modules.list.payInfo,
detail: modules.list.detail
}
})(PayInfo);
Copy the code
Page callsmodels
request
In the page, call requests through the Dispatch method. Synchronous and asynchronous call forms are the same, but the processing in module is different. The complete code is shown below. Page code:
import React from 'react';
import { connect } from 'dva';
// Version 1 decorator syntax
@connect(({ list:{ payInfo, detail } }) = > ({ payInfo, detail }))
class PayInfo extends React.Component<ITextPaperProps.IEntranceState.any> {
// Asynchronous invocation
setPayInfo(){
const { dispatch } = props;
dispatch({
type: 'list/setPayInfo'.payload: 'aaaaa'}); }// Synchronous call
setDetail(){
const { dispatch } = props;
dispatch({
type: 'list/setDetail'.payload: 'bbb'}); } publicrender() {
// Get this from props
const { payInfo, detail } = this.props;
return (
<div >
{payInfo}
</div>); }}export default PayInfo;
Copy the code
Models of code:
import { setPayInfoRequest } from '@/services/list';
export default {
namespace: 'list'.state: {
payInfo: {},detail:' ',},effects: {*setPayInfo({ payload }, { call, put }) {
const response = yield call(setPayInfoRequest, payload);
yield put({
type: 'setPayInfoReducers'.payload: {
res: response,
},
});
returnresponse; }},reducers: {
setDetail(state, { payload }) {
return {
...state,
detail:payload
};
},
setPayInfoReducers(state, { payload }) {
return {
...state,
payInfo: payload, }; ,}}};Copy the code
The services code:
export async function setPayInfoRequest(params) {
return request('/api/setPayInfo', {
method: 'POST'.body: {
...params
}
});
}
Copy the code
Can compare the picture to sort out the idea again, ha ha ha 😄.
5. Wechat configuration
Wechat signature involves more details, more complicated, we step by step.
Wechat domain name authentication
Whether it is wechat, enterprise wechat development, need to first expose the domain name to the external network, to ensure that the external network can access the domain name, and then download the wechat. TXT authentication file in the background, put it in the root directory of the domain name, ensure that the GET request TXT file can be captured by the wechat server.
Example: www.nihaojobo.com/WW_verify_cU0ZETJpcItcKYc8.txt.
For security reasons, o&M personnel may require a list of wechat server IP addresses, as shown in the document.
Wechat obtains Token
This part of the content of the back end, in theory, the front end does not need to care about, or explain, god please skip. In theory, all calls to the wechat server Api require an Access_token, and you need to obtain the access_token according to appID +secret. The access_token will be invalid every 2 hours. Repeated access will cause the last access_token to be invalid.
WeChat login
The wechat login process is as follows. Recently, I have been using Node to develop small programs, which are similar with minor differences. Please refer to the document for detailed information.
- Front end access wechat authorized address and belt
appid
Type of authorizationscope
, callback addressredirect_uri
. - After user authorization or silent authorization, jump to the callback address with
code
. - The front end
code
Sent to the back end, the back end passesaccess_token
+code
+appid
+secret
Get user information, includingopenid
And user levelaccess_token
Information such as, - The back end passes again
openid
And user levelaccess_token
Obtain and save the user’s nickname and other information. - The backend is uniquely identified
openid
And user data generation their own system leveltoken
And back to the front end. - Front-end on each request
token
, according to the backendtoken
Associate users.
Front-end fetch code:
public weChatAuthorize = () = > {
const param = {
appid: '* * * * * * * * *'.redirect_uri: encodeURI(window.location.href), // Callback address
response_type: 'code'.scope: 'snsapi_base'.// snsapi_base Silent snsapi_userinfo authorization
};
// Add the wechat authorized address
const weChatUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize';
const link = `${weChatUrl}?${qs.stringify(param)}#wechat_redirect`;
window.location.href = link;
};
Copy the code
WeChat signature
If the front end needs to invoke SDK interfaces such as uploading pictures and analyzing moments of friends, it needs to execute signature first and then invoke them after passing. The front end gives the Url address of the current page to the back end, and the back end returns the appId, TIMESTAMP, nonceStr, and signature parameters to the front end for permission verification of the wx.config method.
import { Toast } from 'antd-mobile';
// It is the enterprise wechat
export function isWeChatWork() {
const userAgent = String(navigator.userAgent.toLowerCase());
return userAgent.includes('wxwork');
};
import { getSignature } from '@/services/home';
import { GETOPENSIGNATURE, } from '@/services/list';
Close the menu in the upper right corner
export function getSignatureWX(cb? :any) {
cb = cb || function(){}
// Judge wechat public platform or enterprise wechat signature
const requestFn = isWeChatWork() ? getSignature : GETOPENSIGNATURE;
const url = isWeChatWork() ? location.href.split(The '#') [0] : encodeURIComponent(location.href.split(The '#') [0]);
Toast.hide();
Toast.loading('Loading'.10000)
return requestFn({ url }).then(res= > {
if(res.status === 0){
Toast.hide();
const { AppId, Timestamp, NonceStr, Signature, } = res.data;
wx.config({
beta: true.// This must be done otherwise the JsAPI in the form of wx.invoke calls will have problems
// debug: true, // Enable the debug mode, the return value of all API calls will be displayed in the client alert, to view the passed parameters, you can open the PC, parameter information will be printed in the log, only on the PC.
appId: AppId, // Required, enterprise wechat corpID
timestamp: Timestamp, // Mandatory to generate the timestamp of the signature
nonceStr: NonceStr, // Mandatory to generate a random string of signatures
signature: Signature, // Mandatory, signature, see appendix - js-SDK Using permission signature algorithm
jsApiList: ['chooseImage'.'previewImage'.'uploadImage'.'hideOptionMenu'.'onMenuShareAppMessage'.'onMenuShareTimeline'].// Mandatory, a list of JS interfaces that need to be used. Any interface to be called needs to be passed in
});
wx.ready( () = > { // It should be called before the user can click the share button
if(wx.onMenuShareTimeline){
wx.onMenuShareTimeline({
title: 'Default title'.// Share the title
link: window.location.origin + '/groupSendCar'.// Share link, the link domain name or path must be the same as the current page corresponding public number JS security domain name
imgUrl: `The ${window.location.origin}/favicon.png`.// Share ICONS}}})); cb(true)
return true
}else{
cb(false)
Toast.fail('Failed to load wechat signature, please refresh and try again'.3)
return false}})};Copy the code
Our early stage is the simultaneous development of wechat and enterprise wechat, you can see that there is judgment in the code, in addition to a few small holes, roughly speaking:
- IOS 12.x wechat public platform must be used
encodeURIComponent
, can not contain Chinese. - Enterprise wechat and wechat
SDK
The version is inconsistent. The enterprise wechat is 1.2.0, and the wechat version is higher. - Only one trusted domain name can be set for the test public account.
Image upload
Ensure that the signature is approved before executing the upload event.
chooseImage
Event select image, getlocalIds
.uploadImage
Events to uploadlocalIds
To obtainserverId
namelyMediaId
.- The front end sends to the back end
MediaId
, the back end according to the entry to the wechat material address and climb to its own server, back to the front end of the picture address. - The front end saves the image address and displays it.
public upPic = key= > {
const { dispatch } = this.props;
// Select the image
wx.chooseImage({
count: 1./ / the default 9
sizeType: ['original'.'compressed'].// You can specify whether the image is original or compressed. By default, both are available
sourceType: ['album'.'camera'].// You can specify whether the source is photo album or camera, and default is both
// defaultCameraMode: 'normal', // Indicates the default mode for entering the photo taking interface. Two options are available: Normal and Batch. Normal indicates the single shot mode, and Batch indicates the continuous shot mode. (Note: Users can freely switch between two modes when entering the photo taking interface)
// isSaveToAlbum: 1, // Integer value, 0 means not saved to the system album when taking a photo, 1 means automatically saved, the default value is 1
success: res= > {
const [localId] = res.localIds; // Returns a list of local ids for the selected photo,
// Upload the enterprise wechat
wx.uploadImage({
localId, // The local ID of the image to upload, obtained by the chooseImage interface
isShowProgressTips: 1.// The default value is 1, indicating progress
success: (res: { serverId: string }) = > {
const serverId = res.serverId; // Return the server ID of the image
// Judge wechat public platform or enterprise wechat signature
const path = this.isWeChatWork() ? 'home/getPicUrl' : 'list/getPicUrl';
Toast.loading('up in'.10000)
// Upload the image
dispatch({
type: path,
payload: {
MediaId: serverId,
},
}).then(res= > {
Toast.hide();
if (this.isWeChatWork()){
this.setState({ [key]: res });
} else {
this.setState({ [key]: res.AbsoluteAddress });
}
this.merage(); }); }}); },fail:res= >{
console.log("fail",res)
},
});
};
Copy the code
6. TypeScript components
If the component catalog is not planned in the early stage, and there is no lower-cost way to use components, it is almost impossible to achieve the ideal reuse of components. This is an entropy increasing process, all the preparations must be made in the early stage to ensure that the reuse of components is cheaper than the copy style.
SRC/Components /carUI places business components, including theme styles and utils public methods.
─ ─ carUI ├ ─ ─ Banner ├ ─ ─ Button ├ ─ ─ Coupon ├ ─ ─ the Empty ├ ─ ─ Fixbar ├ ─ ─ Form ├ ─ ─ Input ├ ─ ─ the List ├ ─ ─ Radio ├ ─ ─ the Select ├ ─ ─ Address - the list ├ ─ ─ bank - card ├ ─ ─ check box ├ ─ ─ fixbar - box ├ ─ ─ float - ball ├ ─ ─ index. The TSX ├ ─ ─ init ├ ─ ─ oder - car ├ ─ ─ themes └ ─ ─ utilsCopy the code
Theme style variable
According to the design draft, the theme color is configured into variables in advance to facilitate reuse.
/ / theme color
@color:#F67A23;
/ / the background color
@bg:#F9F9F9;
// Link color
@link:#fa6400;
// Button gradient color
@gradient:linear-gradient(90deg.#ff8c00 0%.#ff4800 100%);
/ / the shadow
@shadow:0px 0px 5px 0px rgba(0.0.0.0.13);
Copy the code
Creating a component Template
In order to create components at a lower cost, init template is placed in the component directory to save code segments that are likely to be used. Components can be directly copied to create, and plop.js can be introduced later without manual input.
Use classNames for handy styling, omit unnecessary arguments, and introduce as much type notation code as possible in propsTypes.
// index.less
@import '.. /themes/index.less';
/ / variable names
// @color
// @bg
// @link
// @gradient
// @shadow
.textCar{
color: @color;
box-shadow: @shadow;
}
Copy the code
// index.tsx
import React from 'react';
import classNames from 'classnames/bind';
import omit from 'omit.js';
const styles = require('./index.less');
constcx = classNames.bind(styles); interface ITextPaperProps { className? :string, style? : React.CSSProperties; onMouseEnter? : React.MouseEventHandler<HTMLDivElement>; onChange? :(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) = > void, children? : React.ReactNode; } interface IEntranceState {}class Coupon extends React.Component<ITextPaperProps.IEntranceState.any> {
constructor(props) {
super(props);
this.state = {};
}
public render() {
const {} = this.state
const { className, onChange } = this.props;
const nextProps = omit(this.props,['onChange'.'className']);
return (
<div className={cx('textCar', className)} onChange={(e)= > onChange && onChange(e) }>
<h2 {. nextProps} >default component</h2>
</div>); }}export default Coupon;
Copy the code
H5 Tips
Pop-up layer page
In the H5 scenario, there are many requirements for pop-up layer interaction, such as selecting address book and coupons, etc. Ant Mobile provides the component of pop-up box, but it is not flexible in actual use. The simplest thing is to realize it by yourself.
/ / CSS
.CouponsSelectBox{
position: absolute;
height: 100vh;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow-y: auto;
-webkit-overflow-scrolling: touch; // Resolve div scroll bar lag
}
/ / JSX parts
{showCoupon && <CouponsSelect value={selectCoupon} onChange={res= > { this.setState({ selectCoupon:res, showCoupon:false })}}/>}
Copy the code
Input onCompositionEnd event
Due to the input method, onChange event can be triggered when Struggling sound chooses Chinese characters, and onCompositionEnd event can be used to handle it.
Loading cover
In the future, most pages will avoid multiple clicks or poor network conditions. Loading effect is suggested to improve user experience, or it can be encapsulated in Request. Ant Mobile’s Toast component is very convenient.
Toast.loading('In order'.10000)
const { dispatch } = this.props;
return dispatch({
type: 'home/placeOrder'.payload: param,
}).then(res= > {
Toast.hide();
router.push('/order/detail? order_no=' + res.wl_order_no);
});
Copy the code
8. Log
C-facing users generally need to record exposure, click and other key node information; In addition, in order to distinguish whether the user has cache or not, the front-end code version needs to be added to the front-end code.
Exposure of the log
The exposure log event is triggered in SRC /pages/Authorized.js and each route switch is performed.
const Authority = getAuthority();
const Authorized = RenderAuthorized(Authority);
// Path and log configuration
const LogIdList = {
'/groupSendCar': 1-1 ' '.'/oderPerfect': '2-2'.'/payInfo': '3-3'.'/order/list': 4-4 ' '.'/order/detail': '5-5'.'/myBill': '6-6'.'/couponsList': 7 '7'};const AuthorizedCom = (props) = > {
const { children, dispatch } = props;
useEffect(() = > {
const { pathname } = children.props.location;
if (LogIdList[pathname]) {
const [funcModule, eventName] = LogIdList[pathname].split(The '-');
dispatch({
type: 'global/log'.payload: {
product: 1.platform: 1.funcModule: Number(funcModule),
eventName: Number(eventName)
}
});
}
}, [children.props.location]);
return (
<Authorized authority={children.props.route.authority} noMatch={<Redirect to="/exception/403" />}>
{children}
</Authorized>
);
};
export default connect(({ global, home, list }) = > ({ home, list, userType: global.userType }))(AuthorizedCom);
Copy the code
Click on the event log
Click event in SRC/layouts/BasicLayout trigger in js, can’t be handwritten function call each click of the place, to the body in the componentDidMount binding global click event, if you click on the element contains data – log properties commit log, Where logs are needed, the element is added to the data-log attribute, and the logID entry parameter is kept in the component directory.
componentDidMount() {
const _this = this;
document.body.addEventListener('click'.function(e){
const logString = e.target.getAttribute('data-log');
if(logString){
const [ funcModule, eventName ] = logString.split(The '-');
_this.sendLog(Number(funcModule),Number(eventName)); }}); }// Click log
sendLog = (funcModule,eventName='sd') = > {
const { dispatch, } = this.props;
dispatch({
type: 'global/log'.payload: {
product:1.platform:1,
funcModule,
eventName
}
});
}
Copy the code
Front-end code version
The reason is that there have been several bugs in the front end. I don’t know whether it is a cache or a logic problem. Originally, I wanted to add version variable to the environment variable, modify the variable every time we publish, and then add version attribute to each request header. Setting the versionTime property in the environment variable to the package time generated by moment makes it easier to determine which front-end version the request is coming from. Config /config.js to the define property.
{
versionTime:moment('YYYY-MM-DD hh:mm:ss')}Copy the code
9. Other
MD5 encryption
Theoretically, front-end MD5 is almost useless, because the encryption value is also in the front, we also do encryption, in the words of the leader, to increase the complexity.
Encryption process:
- The parameter objects are sorted by key and serialized as strings.
- use
CryptoJS.MD5
Concatenation follows the back-end convention: serialized string + secret key + date string. - Send as required by the backend
sn
+ backend conventionfrom
+ Original parameters. - The backend verifies the data in a specified format and returns service data.
// Parameter sort serialization
const paramStringify = params= > {
const newParams = {};
/ / sorting
Object.keys(params)
.sort()
.forEach(key= > {
newParams[key] = params[key];
});
return qs.stringify(newParams, { encode: false });
}
WeChatWorkSecret and WeChatWorkAppkey are environment variables
const OAAddSalt = params= > {
const str = params ? paramStringify(params) :' ';
// Add salt to generate MD5
const sn = CryptoJS.MD5(str + WeChatWorkSecret + moment().format('YYYY-MM-DD')).toString();
return { ...params, sn, from: WeChatWorkAppkey };
};
// Obtain wechat authorized signature
export async function getPicUrl(params) {
return request(WeChatWorkApi + '/WeChat/WechatPicUrl', {
method: 'POST'.headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: OAAddSalt(params),
});
}
Copy the code
Real machine commissioning
Such as verification of wechat signature, picture upload and other functions, or need to be debugged under the real machine. Sudo apachectl -k stop to stop Apache from using port 80 by default.
Process:
- Local startup front-end service guarantee
127.0.0.1
Can be accessed. - Set host to the authentication domain name locally, for example:
127.0.0.1 nihaojob.com
. - Start the
charles
Agent software. - Mobile phone set proxy to computer IP and
charles
Proxy port. - Mobile phone wechat access authentication address.
In addition, Charles needs to install a certificate to capture HTTPS packets. Both mobile phones and computers need to install certificates, otherwise the captured packets will be garbled characters.
conclusion
Generally speaking, the knowledge is very shallow, unlike notes like React source code parsing, but for a Vue to React start wechat H5 development practice for me, or harvest quite a lot.
- Use the React base Api.
- Familiar with wechat signature, login and other processes.
- TypeScript components gain experience.
- React development ecology Umi+Dva familiar.
The online preview
The recommended project template on the Dva website is UMI-DVA-ANTD-Mobile.