pos请求t react-native_react-native 请求数据 - CSDN
  • 1.React-Native桥接原生UI 组件 2.Android 百度地图SDK配置 3.MapView 加载

    最近有个React-native项目需要添加定位功能,看了网上很多相关得帖子,基本都是桥接的3方的SDK ,而且有些功能不能定制,考虑到以后百度SDK版本更新的问题,以及方便后期维护,决定自己桥接一下百度原生定位功能


    1.React-Native桥接Android原生UI 组件和原生模块

    • 1.1桥接原生UI组件

    具体步骤

    • 创建一个继承React-Native ViewGroupManager 的 java 类
    //MapViewManager.java
    public class MapViewManager extends ViewGroupManager<MapView> {
    
    }
    
    • 重写 getName 方法,返回一个字符串,对应JavaScript 中requireNativeComponent 的对象
    //MapViewManager.java
    @Override
    public String getName() {
        return "MSMapView";
    }
    
    • 重写 createViewInstance 方法,返回自定义的原生组件
    //MapViewManager.java
    @Override
    protected MapView createViewInstance(ThemedReactContext themedReactContext) {
       //创建自定义的MapView
    }
    
    • 导出视图的属性设置器:通过@ReactProp 注解
    //MapViewManager.java
    @ReactProp(name = "posLatitude")
    public void setPosLatitude(MapView mapView, double posLatitude) {
        mStopLatitude = posLatitude;
    }
    
    • 对应JavaScript 模块调用
    //MapView.js
    MapView.propTypes = {
      posLatitude: PropTypes.number.isRequired
      }
    
    • 把视图管理类注册到应用程序包的creatViewManagers里
    //MapViewPackage.java
    public class MapViewPackage implements ReactPackage {
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(new GeolocationModule(reactContext));
            return modules;
        }
    
    • 在JavaScript 中使用
    //MapView.js
    import React from 'react'
    
    import { requireNativeComponent } from 'react-native'
    import PropTypes from 'prop-types'
    
    const MSMapView = requireNativeComponent('MSMapView', null)
    export default function MapView(props) {
      return <MSMapView {...props} />
    }
    
    MapView.propTypes = {
      posLatitude: PropTypes.number.isRequired,
      posLongitude: PropTypes.number.isRequired,
      posName: PropTypes.string.isRequired,
      posAddress: PropTypes.string.isRequired
    }
    

    具体参考官方文档原生UI组件

    • 1.2 桥接原生模块

    • 首先来创建一个原生模块(GeolocationModule) 。一个原生模块是一个继承了ReactContextBaseJavaModule的Java类,它可以实现一些JavaScript所需的功能
    //GeolocationModule.java 
    public class GeolocationModule extends ReactContextBaseJavaModule {
    	public GeolocationModule(ReactApplicationContext reactContext) {
        		super(reactContext);
    	}
    }
    
    • 实现getName方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块
    //GeolocationModule.java 
    public String getName() {
        return "MSLocationManager";
    }
    
    • 导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void
    //GeolocationModule.java 
    @ReactMethod
    public void startLocationWithCallback(Callback locationCallback) {
        //添加回调
        Logger.t(TAG).i("startLocationWithCallback");
        this.locationCallback = locationCallback;
        initLocationClient();
    }
    
    • 注册模块,在应用的Package类的createNativeModules方法中添加这个模块
    //MapViewPackage .java
    public class MapViewPackage implements ReactPackage {
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(new GeolocationModule(reactContext));
            return modules;
        }
    }
    
    • 对应JavaScript 模块调用
    //JS
    import {  NativeModules } from 'react-native'
    const LocationManager = NativeModules.MSLocationManager
    
    LocationManager.startLocationWithCallback((error, location) => {
      if (error) {
        logger.error(`get location error:${JSON.stringify(error)}`)
        ...
      } else {
       ...
      }
    })
    

    2.Android 百度地图SDK配置

    注意应用混淆

    集成地图SDK的应用,在打包混淆的时候,需要注意与地图SDK相关的方法不可被混淆。混淆方法如下:
    
    -keep class com.baidu.** {*;}
    -keep class vi.com.** {*;}    
    -dontwarn com.baidu.**
    保证百度类不能被混淆,否则会出现网络不可用等运行时异常
    
    
    • Android manifest.xml 配置如下

    <!-- 获取运营商信息,用于支持提供运营商信息相关的接口 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <!-- 访问网络,网络定位需要上网 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 用于读取手机当前的状态 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <!-- 这个权限用于进行网络定位 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- 这个权限用于访问GPS定位 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
           <service
                android:name="com.baidu.location.f"
                android:enabled="true"
                android:process=":remote" />
            <meta-data
                android:name="com.baidu.lbsapi.API_KEY"
                android:value="你申请的AK码" />
    
    • app build.gradle 配置如下

    android {
    .....
      defaultConfig {
      .....
     ndk {
        // 设置支持的SO库架构
        abiFilters 'armeabi' , 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
    }
     .....
      }
     sourceSets {
        main {
            jniLibs.srcDir 'libs'
            jni.srcDirs = []
         }
       }
       .....
    }
    
    
    

    3.MapView 加载

    • 3.1 根据React-Native桥接原生UI(前面已经讲过,具体参考步骤1.1),需要在createViewInstance 创建 View(BaiduMapView) ,代码如下
    //MapViewPackage.java
    @Override
    protected MapView createViewInstance(ThemedReactContext themedReactContext) {
        //在使用SDK各组件之前初始化context信息,传入ApplicationContext
        SDKInitializer.initialize(themedReactContext.getApplicationContext());
        //创建MapView对象
        mapView = new MapView(themedReactContext);
        mIconMarker = BitmapDescriptorFactory.fromResource(R.drawable.map_maker);
        this.themedReactContext = themedReactContext;
        initMap();//初始化mapView参数
        return mapView;
    }
    

    具体参考创建BaiduView文档

    • 3.2 在 MapView 上添加 Location(定位) 和 InfoWindow 布局,注意要在 MapLoaded后添加
    //MapViewPackage.java
    private void initMap() {
        mBaiduMap = mapView.getMap();
        mBaiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL);
        mapView.showZoomControls(true);
        //定义BaiduMapOptions对象
        BaiduMapOptions options = new BaiduMapOptions();
        options.scaleControlEnabled(false); // 不显示比例尺
        mBaiduMap.setMyLocationEnabled(true);
        mBaiduMap.setMaxAndMinZoomLevel(18, 7);//地图的最大最小缩放比例7-18
        mBaiduMap.setOnMapLoadedCallback(new BaiduMap.OnMapLoadedCallback() {
            @Override
            public void onMapLoaded() {
                setLocation();
                setInfoWindow();
            }
        });
        mBaiduMap.setOnMapClickListener(new BaiduMap.OnMapClickListener() {
            @Override
            public boolean onMapPoiClick(MapPoi arg0) {
                return false;
            }
    
            @Override
            public void onMapClick(LatLng arg0) {
                mBaiduMap.hideInfoWindow();
            }
        });
    }
    

    根据已经获取的经纬度,设置定位布局

    //MapViewPackage.java
    private void setLocation() {
        //获取定位
        LatLng latLng = new LatLng(mStopLatitude, mStopLongitude);
        //添加定位图标
        OverlayOptions overlayOptions = new MarkerOptions().position(latLng)
                .icon(mIconMarker).zIndex(3);
        Marker marker = (Marker) mBaiduMap.addOverlay(overlayOptions);
        Bundle bundle = new Bundle();
        bundle.putDoubleArray("location", new double[]{mStopLatitude, mStopLongitude});
        marker.setExtraInfo(bundle);
        MapStatus.Builder builder = new MapStatus.Builder();//地图状态构造器
        //设置地图中心点,设置地图缩放级别并创建地图状态对象
        MapStatus mapStatus = builder.target(latLng).zoom(14f).build()
        MapStatusUpdate mapStatusUpdate = MapStatusUpdateFactory.newMapStatus(mapStatus);
        //以动画方式更新地图状态,动画耗时 300 ms
        mBaiduMap.animateMapStatus(mapStatusUpdate);
    }
    

    设置 InfoWindow 布局,并监听布局点击状态

    //MapViewPackage.java
    void setInfoWindow() {
        Logger.t(TAG).i("setInfoWindow");
        //显示信息窗
        mBaiduMap.setOnMarkerClickListener(new BaiduMap.OnMarkerClickListener() {
            InfoWindow.OnInfoWindowClickListener infoWindow_ClickListener = new InfoWindow.OnInfoWindowClickListener() {
                @Override
                public void onInfoWindowClick() { // TODO Auto-generated method stub
                    mBaiduMap.hideInfoWindow();
                }
            };
            @Override
            public boolean onMarkerClick(Marker marker) {
                LatLng point = new LatLng(mStopLatitude, mStopLongitude);
                View infoWindowView = LayoutInflater.from(themedReactContext).inflate(R.layout.info_window, null);
                TextView posName = infoWindowView.findViewById(R.id.infoWindow_posName);
                TextView posAddress = infoWindowView.findViewById(R.id.infoWindow_posAddress);
                posName.setText(mPosName);
                posAddress.setText(mPosAddress);
                //根据一个 View 创建 Bitmap 描述信息, 当 view 为 null 时返回 null
                BitmapDescriptor infoWindow_bitmap = BitmapDescriptorFactory.fromView(infoWindowView);
                mInfoWindow = new InfoWindow(infoWindow_bitmap, point, -marker.getIcon().getBitmap().getHeight()/2, infoWindow_ClickListener);
                //显示信息窗
                Logger.t(TAG).i("showInfoWindow");
                mBaiduMap.showInfoWindow(mInfoWindow);
                return true;
            }
        });
    }
    

    infowwindow.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/popup"
        android:paddingTop="@dimen/map_info_window_padding_top"
        android:paddingBottom="@dimen/map_info_window_padding_bottom"
        android:paddingLeft="@dimen/map_info_window_padding_left"
        android:paddingRight="@dimen/map_info_window_padding_right"
        android:orientation="vertical">
    
        <TextView
            android:id="@+id/infoWindow_posName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal" />
    
        <TextView
            android:id="@+id/infoWindow_posAddress"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="@dimen/map_info_window_pos_name_text_size"/>
    
    </LinearLayout>
    
    • 3.3 桥接Android 原生模块(前面已经讲过,参考1.2),获取经纬度定位信息,通过 LocationClient 发起定位,通过 LocationClientOption 设置LocationClient相关参数
    //GeolocationModule.java
    private void initLocationClient() {
        LocationClientOption option = new LocationClientOption();//声明LocationClient类实例并配置定位参数
        option.setLocationMode(LocationMode.Hight_Accuracy);//可选,默认高精度,设置定位模式,高精度,低功耗,仅设备
        option.setCoorType("bd09ll");//可选,默认gcj02,设置返回的定位结果坐标系
        option.setIsNeedAddress(true);//可选,设置是否需要地址信息,默认不需要
        option.setIsNeedAltitude(true);//可选,默认false,设置定位时是否需要海拔信息,默认不需要,除基础定位版本都可用
        option.setIsNeedLocationDescribe(true);//可选,默认false,设置是否需要位置语义化结果,可以在BDLocation.getLocationDescribe里得到,结果类似于“在北京天安门附近”
        option.setOpenGps(true);//可选,默认false,设置是否使用gps
        option.setScanSpan(1000);//可选,默认0,即仅定位一次,设置发起连续定位请求的间隔需要大于等于1000ms才是有效的
        //定位初始化
        locationClient = new LocationClient(getReactApplicationContext()/*context.getApplicationContext()*/);
        locationClient.setLocOption(option);
        //开启地图定位图层
        locationClient.start();
    
        Log.i("locationClient", "locationClient");
        //注册LocationListener监听器
        MyLocationListener myLocationListener = new MyLocationListener();
        locationClient.registerLocationListener(myListener);
    }
    
    public class MyLocationListener extends BDAbstractLocationListener {
        private double latitude, longitude;
    
        /**
         * 实现定位监听 位置一旦有所改变就会调用这个方法
         * 可以在这个方法里面获取到定位之后获取到的一系列数据
         */
        @Override
        public void onReceiveLocation(BDLocation bdLocation) {
            latitude = bdLocation.getLatitude();
            longitude = bdLocation.getLongitude();
            Logger.t(TAG).i("onReceiveLocation");
            if (bdLocation != null) {
                StringBuffer buffer = new StringBuffer(256);
                buffer.append(latitude);
                buffer.append(",");
                buffer.append(longitude);
                location = buffer.toString();
                locationClient.stop();
                try {
                    GeolocationModule.this.locationCallback.invoke(null, location);
                } catch (IllegalViewOperationException e) {
                    locationCallback.invoke(e.getMessage(), null);
                }
            }
            Logger.t(TAG).i("onReceiveLocation:" + location
                    + "latitude:" + latitude
                    + "longitude:" + longitude);
        }
    }
    
    展开全文
  • React Native Get,Post请求

    2017-05-05 15:01:19
    React Native为我们提供了fetch框架进行网络请求。Fetch请求static async request(url, method, body, params) { DEBUG && console.log("#REQUEST NetUtil# [" + method + "] url=" + url + ",body=" + body); var...

    React Native为我们提供了fetch框架进行网络请求。

    Fetch请求

    static async request(url, method, body, params) {
            DEBUG && console.log("#REQUEST NetUtil# [" + method + "] url=" + url + ",body=" + body);
            var timestamp=new Date().getTime()/1000;//当前时间毫秒值
            var user=await AsyncStorage.getItem('user');//缓存本地的用户数据
            var token='';//用户token
            if(user!=null){
                token=JSON.parse(user).token;
            }else{
                token='';
            }
            var sign= await Sign.createSign(params,timestamp);//获取签名
            return new Promise((resolve, reject)=> {
                fetch(url, {
                    method: method,
                    body: body,
                    headers: new Headers({
                        'uid': '867909021770429',
                        'token':token,
                        'timestamp':timestamp,
                        'sign':sign,//签名
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'Connection': 'close'
                    }),
                })
                    .then((response)=> {
                        if(response.ok){
                            return response.json();
                        }else{
                            reject("服务器错误!");
                        }
                    })
                    .then((json)=> {
                        DEBUG && console.log("#RESPONSE# NetUitl ["+method+"] url = "+url+", body = "+body+",responseData="+json);
                        if(json.code==Constant.SUCCESS){
                            resolve(json.data);
                        }else{
                            reject(data.msg);
                        }
                    })
                    .catch((error)=> {
                        DEBUG && console.log("#RESPONSE# NetUitl ["+method+"] url = "+url+", body = "+body+", error="+error);
                        reject("服务器错误!");
                    });
            });
        }

    通过fetch()函数我们可以指定请求的url,请求的method,请求的body以及请求的headers,fetch函数返回一个Promist对象,请求成功后会返回请求的Response对象,response.ok会过滤掉404,500这些请求错误,通过response.json()方法我们就可以获得JOSN格式的服务器返回给我们的数据。

    Get请求

    //get请求 params为一个Map对象
        static get(url, params) {
            if (params) {
                let paramsArray = []
                for(var item of params.entries()){
                    paramsArray.push(item[0]+'='+encodeURIComponent(item[1]));
                }
                if (url.search(/\?/) === -1) {//拼接url
                    url += '?' + paramsArray.join('&')
                } else {
                    url += '&' + paramsArray.join('&')
                }
        }
            return this.request(url, 'get', undefined, params);
        }

    Post请求

    //pos请求
        static post(url, params) {
            var body = '';
            for (let item of params.entries()) {//拼接body
                body += item[0] + "=" + item[1] + "&";
            }
            body = body.substring(0, body.length - 1);
            return this.request(url, 'post', body, params);
        }

    使用

    • get方式
    var params = new Map();
            params.set('pageNo', 1);
            params.set('pageSize', 20);
            params.set('version', '1.0.1');
            params.set('platform', 'android');
            params.set('provinceCode', '310000');
            return dispatch=> {
                NetUtil.get(HttpUrl.QUERY_DYNAMIC_LIST, params)
                    .then((result)=> {
                        console.log(result);
                        dispatch({
                            type: ActionTypes.ACTION_DYNAMIC_LIST,
                            dynamicList: result,
                        })
                    }, (error)=> {
                        console.log('error' + error);
                    });
            }
    • post方式
    var params=new Map();//请求的参数
            params.set('username',this.state.username);
            params.set('password',this.state.password);
            params.set('cid','53b857f07b3ace9bdb2b99e0d65dc123');
            params.set('loDeviceInfo','AndroidA31');
            params.set('platfrom','Android');
            NetUtil.post(HttpUrl.LOGIN_URL,params)
                .then((result)=>{
                    AsyncStorage.setItem("user",JSON.stringify(result));//保存用户数据到本地
                    NavigatorRoute.replaceToMainScene(this.props.navigator);//跳转到主页
                },(error)=>{
                    ToastAndroid.show(error,ToastAndroid.SHORT);
                });

    源码

    展开全文
  • React前端开发总结

    2019-02-28 17:31:41
    第一次使用react,还是习惯性的去找了合适的移动开发框架,直接在此基础上迭代开发。 优点是能快速及时上手一个项目,边开发应用边学习,能做出一个可以用,可以看的东西。 缺点是不能系统的学习这个库,她的特性...

    第一次使用react,还是习惯性的去找了合适的移动开发框架,直接在此基础上迭代开发。
    优点是能快速及时上手一个项目,边开发应用边学习,能做出一个可以用,可以看的东西。
    缺点是不能系统的学习,她的特性不能系统全面地掌握以及与之相关的技术底层能用但是原理模糊。
    整个项目虽然功能实现,但是是在一知半解状态做出来,边开发边解决问题,边重构迭代,react的优势并没有完全发挥出来。
    希望系统学习之后能进行一次整体重构。

    开发工具选择

    开发工具

    Sublime Text 3

    使用这个就是因为以前用过,上手快,而且她快速,稳定,但是需要购买证书。
    

    Vscode

     速度较快,对超大文件读写速度飞快。
    

    Atom

    速度一般,更新快,占用资源较大。
    

    调试工具

    1. 在 Chrome 上可以安装 React 开发者工具插件,这样就能在浏览器的开发控制台里看到 React 渲染出来的组件树,调试查看挺方便。
    2. 微信web开发者工具。鉴于开发微信公众号应用,开发使用微信web开发者工具,可以提供公众号网页和小程序的开发,微信API里有提供下载链接。
      https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1455784140

    React全家桶

    react + redux + react-router + less/sass + ES6 + webpack
    react全家桶,react使用的标配,一个个做简单的说明。

    构建开发环境demo

    使用create-react-app构建React开发环境,node以及npm已配置。
    npm慢可以使用淘宝定制的cnpm,设置如下:

    npm install -g cnpm --registry=https://registry.npm.taobao.org
    npm config set registry https://registry.npm.taobao.org
    

    create-react-app demo安装

    create-react-app my-app
    cnpm install -g create-react-app
    cd my-app/
    npm start
    

    启动之后,http://localhost:3000/
    接下来可以在此基础上,学习或者实验react相关…

    react

    采用声明式,高效而且灵活的用来构建用户界面的框架。
    官方文档 https://react.docschina.org 
    

    虚拟dom

    react-dom提供操作DOM的扩展库。 
    

    JSX

    通过babel解析JSX语法代码转为纯JS语法代码的库。
    

    组件

    面向组件编程的(组件化编码开发),最后得到标签代码,同时组件之间可以进行通信,数据传递也非常灵活。
    关于组件通信方式,下面单独做了实验与总结。

    组件通讯方式

    组件通信方式汇总

    其他
    组件上加上prop-types进行验证

    optionalArray: PropTypes.array,
    optionalBool: PropTypes.bool,
    optionalFunc: PropTypes.func,
    optionalNumber: PropTypes.number,
    optionalObject: PropTypes.object,
    optionalString: PropTypes.string,
    optionalSymbol: PropTypes.symbol

    redux

    虽然使用的脚手架中整合了redux,但是在使用中,为了赶进度没有使用,重构的时候也把这部分也加上了。

    只有遇到 React 实在解决不了的问题,你才需要 Redux 。

    redux简单做了下总结。
    Redux简单汇总

    react-router

    用于构建路由,主要有Router,IndexRoute,Route,Link,IndexLink这几个组件,以及hashHistory,browserHistory。

    安装

    npm install --save react-router
    

    使用

    项目部署的时候,原配置createHashHistory出现一片白问题,后来更换了createBrowserHistory,重新部署后可访问。
    区别
    browserHistory 其实使用的是 HTML5 的 History API,浏览器提供相应的接口来修改浏览器的历史记录;
    hashHistory 是通过改变地址后面的 hash来改变浏览器的历史记录。

    import createHistory from 'history/createBrowserHistory'
    

    从 React Router 中导入

    import { BrowserRouter as Router, Route } from 'react-router-dom';	
    

    react-router嵌套路由

    Router包裹Route,
    当url变化的时候,Router将会匹配到指定的路由,然后渲染路由绑定的组件。
    Route用来显式地把路由映射到应用的组件结构上。 用path指定url,用component指定路由命中url后需要渲染的那个组件。

    如下:

    <Router basename={AppCfg.app.BaseName}>
    	<App>
      		<Route exact path="/" component={CreateComponent(Default)} />         
       		<Route path="/home" component={CreateComponent(Home)} />
       		<Route path="/userinfo" component={CreateComponent(UserInfo)} />
     	</App>
    </Router>
    

    通过Provider将redux绑定到react,同时用Provider包裹路由,路由控制器就可以访问store。

    <AppContainer warnings={false}>
      <Provider store={ApiClientStore} key="provider">
        <Router history={history}>
          {RootElement}
        </Router>
      </Provider>
    </AppContainer>
    

    Switch

    Switch的特点是从上往下读,只要有一个匹配成功,就不会往下读(Switch是由包容性变成排他性的一个重要组件)

    在具体页面跳转中使用switch

    import { Switch } from 'react-router-dom';
    
    const { location } = route || {};
    const { key } = location || {};
    <Switch key={key} location={location}>
      {this.props.children}
    </Switch>
    

    其他

    React Router 提供一个 routerWillLeave生命周期钩子,这使得 React 组件可以拦截正在发生的跳转,或在离开 route 前提示用户。routerWillLeave 返回值有以下两种:
    return false 取消此次跳转
    return 返回提示信息,在离开 route 前提示用户进行确认。

    import { Lifecycle } from 'react-router'
    
    const Home = React.createClass({
    
      // 假设 Home 是一个 route 组件,它可能会使用
      // Lifecycle mixin 去获得一个 routerWillLeave 方法。
      mixins: [ Lifecycle ],
    
      routerWillLeave(nextLocation) {
        if (!this.state.isSaved)
          return 'Your work is not saved! Are you sure you want to leave?'
      },
    
      // ...
    
    })
    

    Links
    Link在内部做了一个操作,把标签变成了a标签。

    <Link to="user" params={{userId: user.id}}>touser</Link>
    

    Redirect组件(重定向)
    from(使用绝对路径)
    exact(精准匹配)
    to(跳转路径)

    less/sass

    本次开发中使用sass,由于使用了react-weui样式使用较少了。
    SASS是一种CSS的开发工具,使CSS的开发,变得简单和可维护。
    可以查看阮一峰老师关于此教程。
    http://www.ruanyifeng.com/blog/2012/06/sass.html

    ES6

    声明方式
    var 、function 、let 、const 、import、 class。

    ES6中明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域,凡是在声明之前就使用这些变量就会报错。

    箭头函数
    相当于匿名函数,使用“箭头”(=>)定义函数。
    无参箭头函数

    setInterval(() => this.nextSlide(), 2000);
    

    一个参数箭头函数

    x => {console.log(x*x)}
    

    多个参数箭头函数
    参数需要用括号()括起来

     'use strict'
     var arr = [10, 20, 1, 2];
      arr.sort((x, y) => {
        if(x-y>0){
            return 1;
        }else{
            return -1;
        }
     });
     console.log(arr); // [1, 2, 10, 20]
    

    可变参数
    …rest

    (x, y, ...rest) => {
    	var i, sum = x + y;
    	 for (i=0; i<rest.length; i++) {
    	     sum += rest[i];
    	 }
    	 return sum;
    }
    

    其他

    1. this,箭头函数内部的this是词法作用域,由上下文确定。
    2. 函数体返回表达式的需要使用括号,例 x => ({ foo: x })

    Es6箭头函数说明挺全也很简明
    https://www.cnblogs.com/snandy/p/4403111.html

    ESLint

    ES6 ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。

    重构时候原来的代码都有问题,前后空格、无效的代码以及分号等等各种问题。
    一开始使用eslint确实会影响进度,但是对于规范代码以及语法有很好的引导,虽然麻烦点,最后的使用效果不错。
    类似如下错误,根据提示修改即可。
    (1) expected parenteses around arrow function argument having a body with curly braces arrow-parens
    箭头函数参数周围的预期括号具有带大括号的主体
    (2) Empty components are self-closing
    组件内部空时,不需要自己关闭,如下:

    
    <a href={link}>
     <div  className="slider__slide" //此处
        data-active={active}
        style={slideStyle}
        onTouchStart={(ev) => { this.clickCarousel(carouselId, ev); }}
        onTouchEnd={(ev) => { this.touchEnd(ev); }
        }></div> //此处
    </a>
    
    <a href={link}>
     <div 
        className="slider__slide" //换行
        data-active={active}
        style={slideStyle}
        onTouchStart={(ev) => { this.clickCarousel(carouselId, ev); }}
        onTouchEnd={(ev) => { this.touchEnd(ev); }
        }/> //空的div去除
    </a>
    

    webpack

    WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用

    webpack与Gulp的区别

    Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。
    Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。
    Webpack的处理速度更快更直接,能打包更多不同类型的文件

    这个不详细写了,要再实践总结下,但是这篇文章写得很好,值得学习。
    https://segmentfault.com/a/1190000006178770?utm_source=tag-newest

    react UI库

    UI库的选择上,找了好几个,鉴于微信公众号网页的兼容性,最后选择了react-weui。

    Material-UI

    Material-UI是一个实现了Google’s Material Design设计规范的react组件库。

    Ant-design

    蚂蚁金服开发的一个基于react的UI组件库

    react-weui

    API
    https://weui.github.io/react-weui/docs

    1.安装

    npm install weui@1.1.0 react-weui --save
    

    2.使用
    在App.js中引入模块。

        import 'weui';
        import 'react-weui/build/packages/react-weui.css';
    

    实际使用中,import 相应的组件即可,以首页的tab切换页为例,如下:

    import { Tab, TabBody, TabBar, TabBarItem, Article } from 'react-weui';
    

    详细页面如下

    <Tab>
     <TabBody>
        <Article style={{ display: this.state.tab == 0 ? null : 'none' }}>
          <h1>Page 1</h1>
          <section>
            <h2 className="title">Heading</h2>
            <section>
              <h3>1.1 Title</h3>
              <p>1111</p>
            </section>
          </section>
        </Article>
        <Article style={{ display: this.state.tab == 1 ? null : 'none' }}>
          <h1>Page 2</h1>
          <section>
            <h2 className="title">Heading</h2>
            <section>
              <h3>2.1 Title</h3>
              <p>222</p>
            </section>
          </section>
        </Article>
      </TabBody>
      <TabBar>
        <TabBarItem
          active={this.state.tab == 0}
          icon={<img src={this.state.urlmain} alt="购票主页" />}
          onClick={(e) => this.handleTab(e, 0)}
          label="主页"
        />
        <TabBarItem
          active={this.state.tab == 1}
          icon={<img src={this.state.urlmine} alt="个人中心" />}
          onClick={(e) => this.handleTab(e, 1)}
          label="个人中心"
        />
      </TabBar>
    </Tab>
    

    效果如下图

    初始化展示

    react-weui提供了一系列的组件,可以方便引入和使用,基本可以满足项目中的应用。

    支付宝支付

    其实一开始对微信里使用支付宝支付这个感觉有点喧宾夺主了。但是微信里还真的可以这么干,也挺有意思的。总之,用户需求至上。
    不过支付宝给出的解决方案里真正使用有点另起炉灶的意思。

    1.需要引入相关的js文件作为支撑ap.js。

    (function(){var b={};var a={};a.PADCHAR="=";a.ALPHA="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";a.makeDOMException=function(){var f,d;try{return new DOMException(DOMException.INVALID_CHARACTER_ERR)}catch(d){var c=new Error("DOM Exception 5");c.code=c.number=5;c.name=c.description="INVALID_CHARACTER_ERR";c.toString=function(){return"Error: "+c.name+": "+c.message};return c}};a.getbyte64=function(e,d){var c=a.ALPHA.indexOf(e.charAt(d));if(c===-1){throw a.makeDOMException()}return c};a.decode=function(f){f=""+f;var j=a.getbyte64;var h,e,g;var d=f.length;if(d===0){return f}if(d%4!==0){throw a.makeDOMException()}h=0;if(f.charAt(d-1)===a.PADCHAR){h=1;if(f.charAt(d-2)===a.PADCHAR){h=2}d-=4}var c=[];for(e=0;e<d;e+=4){g=(j(f,e)<<18)|(j(f,e+1)<<12)|(j(f,e+2)<<6)|j(f,e+3);c.push(String.fromCharCode(g>>16,(g>>8)&255,g&255))}switch(h){case 1:g=(j(f,e)<<18)|(j(f,e+1)<<12)|(j(f,e+2)<<6);c.push(String.fromCharCode(g>>16,(g>>8)&255));break;case 2:g=(j(f,e)<<18)|(j(f,e+1)<<12);c.push(String.fromCharCode(g>>16));break}return c.join("")};a.getbyte=function(e,d){var c=e.charCodeAt(d);if(c>255){throw a.makeDOMException()}return c};a.encode=function(f){if(arguments.length!==1){throw new SyntaxError("Not enough arguments")}var g=a.PADCHAR;var h=a.ALPHA;var k=a.getbyte;var e,j;var c=[];f=""+f;var d=f.length-f.length%3;if(f.length===0){return f}for(e=0;e<d;e+=3){j=(k(f,e)<<16)|(k(f,e+1)<<8)|k(f,e+2);c.push(h.charAt(j>>18));c.push(h.charAt((j>>12)&63));c.push(h.charAt((j>>6)&63));c.push(h.charAt(j&63))}switch(f.length-d){case 1:j=k(f,e)<<16;c.push(h.charAt(j>>18)+h.charAt((j>>12)&63)+g+g);break;case 2:j=(k(f,e)<<16)|(k(f,e+1)<<8);c.push(h.charAt(j>>18)+h.charAt((j>>12)&63)+h.charAt((j>>6)&63)+g);break}return c.join("")};b.pay=function(d){var c=encodeURIComponent(a.encode(d));location.href="pay.htm?goto="+c};b.decode=function(c){return a.decode(decodeURIComponent(c))};window._AP=b})();
    

    2.支付宝生成签名

    请求生成支付宝签名返回参数作为支付宝支付的参数。

    let params = new FormData();
    params.append('loginId', user.id);  
    params.append('orderNo', orderNo);  
    ApiClientUtil.post(ApiInfo.Alipay, params).then((json) => {  
      if(json.success) {
        const gotoUrl='https://openapi.alipay.com/gateway.do?'+ json.body;
        window._AP.pay(gotoUrl);
      }else {
        Alert.error('支付宝支付遇到问题,请重新尝试', {position: 'top',
          offset: 40,onRouteClose: true, timeout: 2000});
        return;
      }
    })
    

    3.跳转到pay.htm
    这个页面当用户选择支付宝账户点击确认支付时,页面跳转到此页面提示用户打开浏览器,进而完成支付的页面,在新的浏览器网页中完成支付宝支付。
    请在菜单中选择在浏览器中打开,以完成支付…

    <!DOCTYPE HTML>
    <html>
    <head>
        <meta charset="utf-8"
        ">
        <title>支付提示</title>
        <meta name="apple-mobile-web-app-capable" content="yes"/>
        <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
        <meta name="format-detection" content="telephone=no"/>
        <meta name="format-detection" content="email=no"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"/>
        <style>
            *, :before, :after {
                -webkit-tap-highlight-color: rgba(0, 0, 0, 0)
            }
    
            body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, form, fieldset, legend, input, textarea, p, blockquote, th, td {
                margin: 0;
                padding: 0
            }
    
            table {
                border-collapse: collapse;
                border-spacing: 0
            }
    
            fieldset, img {
                border: 0
            }
    
            li {
                list-style: none
            }
    
            caption, th {
                text-align: left
            }
    
            q:before, q:after {
                content: ""
            }
    
            input:password {
                ime-mode: disabled
            }
    
            :focus {
                outline: 0
            }
    
            html, body {
                -webkit-touch-callout: none;
                touch-callout: none;
                -webkit-user-select: none;
                user-select: none;
                -webkit-tap-highlight-color: transparent;
                tap-highlight-color: transparent;
                height: 100%;
                margin: 0;
                padding: 0;
                text-align: center;
                font-size: 15px;
                font-weight: 300;
                font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif
            }
    
            a {
                text-decoration: none
            }
    
            body {
                background: #F4F4F8
            }
    
            .weixin-tip {
                display: none;
                -webkit-box-sizing: border-box;
                box-sizing: border-box;
                position: absolute;
                top: 15px;
                right: 20px;
                width: 265px;
                padding: 55px 0 0;
                text-align: left;
                background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAACICAMAAABQgAwUAAAAMFBMVEUAAADY2NjY2NjY2NjY2NjY2NjY2NjY2NjX19fY2NjY2NjY2NjY2NjY2NjY2NjY2Njr/TvvAAAAD3RSTlMAxy89c9CdTRyG7lvcD7FzqbJAAAACFklEQVR42uWYy4rkMBAErZdlPdzx/3+7LAw0tH0Y2orDsnnyKQlSVaWytoc6xrEpigFoinUAIBnWABAE5woW9o6GPbGwI1jYGSzsgoV9goU9wMLe0bA7FnYCC7uBhV2wsE+wsAdY2AENGyzsBBZ2Q8MuWNgH94pLbgELO6Bhg4VdwcJuaNgTCzuChZ3Bwg5o2GBhV7CwdzTsjoUdwcLOYGEXLOwTLOwBFvaOht2xsBNY2I1f6lhaenvhrfpkAblab+k9b/OD0iuX2F9/x8D+7ZL2pmpbuj+6o3Vg//oWmPU9p65VkXL6+oIJ8S738nwj62Pb1lvHACH+fBs7sG59U3yrVD3rce3GVcp8qGkPAGTprQUYy6xfaE8i82b6S7/pfZnzdYQIHeOXdfYKpHoFcmrvWlM8RW+CDO8JMWoNM/+FeyB4UfMpL48g5qG1Iqc29YI3mqq2knXvEJu2onJoQy9ok4mkQZf/GjqitUvQyqN6SU8NOvOhHq25xNCWj6LFQdLiyKuaZWpxBC2OrFVHxdryElbQsVtBx6KN0qAd4a71yo610uxa2b0s5xg052I5p26d4MCqusZFwzrAnqQhSogSMnkNcr+GUS3kEKWS62NJFlNCToWLZpWMe14RReGqdjz2PfNECbkGbrQ/Nj5q5y7j8/HRTW5UhvHfA7Mdzitji8rfWsgX3gVZ91eO22odKed6LLf9A/sRnc74RV7lAAAAAElFTkSuQmCC) no-repeat right top;
                background-size: 45px 68px
            }
    
            .weixin-tip-img {
                display: none;
                padding: 110px 0 0
            }
    
            .weixin-tip-img::after {
                display: block;
                margin: 15px auto;
                content: ' ';
                background-size: cover
            }
    
            .weixin-tip-img.iphone::after {
                width: 150px;
                height: 150px;
                background-image: url(data:image/png;base64,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)
            }
    
            .weixin-tip-img.android::after {
                width: 173px;
                height: 240px;
                background-image: url(data:image/png;base64,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)
            }
        </style>
    </head>
    <body>
    <div class="J-weixin-tip weixin-tip">
        <div class="weixin-tip-content">
            请在菜单中选择在浏览器中打开,<br/>
            以完成支付
        </div>
    </div>
    <div class="J-weixin-tip-img weixin-tip-img"></div>
    
    <div>
        <br/>
        <a href="/" name="" style="font-size: 16px;color: green;">支付完成,点此返回主页</a>  
    </div>
            
    
    <script type="text/javascript" src="ap.js"></script>
    <script>
        if (location.hash.indexOf('error') != -1) {
            alert('参数错误,请检查');
        } else {
            var ua = navigator.userAgent.toLowerCase();
            var tip = document.querySelector(".weixin-tip");
            var tipImg = document.querySelector(".J-weixin-tip-img");
            if (ua.indexOf('micromessenger') != -1) {
                tip.style.display = 'block';
                tipImg.style.display = 'block';
                if (ua.indexOf('iphone') != -1 || ua.indexOf('ipad') != -1 || ua.indexOf('ipod') != -1) {
                    tipImg.className = 'J-weixin-tip-img weixin-tip-img iphone'
                } else {
                    tipImg.className = 'J-weixin-tip-img weixin-tip-img android'
                }
            } else {
                var getQueryString = function (url, name) {
                    var reg = new RegExp("(^|\\?|&)" + name + "=([^&]*)(\\s|&|$)", "i");
                    if (reg.test(url)) return RegExp.$2.replace(/\+/g, " ");
                };
                var param = getQueryString(location.href, 'goto') || '';
                location.href = param != '' ? _AP.decode(param) : 'pay.htm#error';
            }
        }
    </script>
    </body>
    </html>
    

    微信支付

    参考这个实现
    https://blog.csdn.net/wyk304443164/article/details/72845409

    首先,用户选择微信支付,传入订单号。

     let _that = this;
     Pay.done(order.bookNum, _that, 1, is_true => {
       if (is_true) {
         Alert.success('支付成功', {
           position: 'top',
           offset: 40,
           onRouteClose: true, 
           timeout: 1500,
           onClose: function () {
             Utility.toPage('order');
           }
         });
       } else {
         Alert.error('微信支付失败,请重新尝试', {position: 'top',
           offset: 40,onRouteClose: true, timeout: 2000});
         return;
       }
     });
    

    Pay组件支付实现
    支付过程中先由微信生成预支付信息,用户支付完成后执行成功提示等操作。同时,后台接口提供订单回调,修改订单状态业务等。

    import 'react-jweixin';
     
    let Pay = {};
    
    /**
     * 支付的回调
     */
    let payCallback;
    
    /**
     * 开始支付--这个方法其实是从后台请求微信支付签名。成功继续,失败回调
     * @param _order_id 传bookNum即可
     * @param openid 微信公众号openid
     * @param _thisObj 传this即可
     * @param _payType 支付类型--暂时只有微信
     * @param _callback 回调 true or false
     */
    Pay.done = function (_order_id, _thisObj, _payType, _callback) {
        let params = new FormData(); 
        params.append('searchParam.bookNum', _order_id); 
        params.append('searchParam.useFlag', 0); //0新商户
        let openid = sessionStorage.getItem('openid'); 
        params.append('searchParam.openid', openid);  
        if (typeof _callback === "function") {
            payCallback = _callback;
        }
    
        ApiClientUtil.post(ApiInfo.PaySign, params).then((data) => {
            if(data.head.success) {
                this.doneResponse(data.body);
            }
        }).catch(() => {
            payCallback(false);
        });
    };
    
    Pay.doneResponse = function (result) {
        this.callPay(result);
    };
    
    Pay.callPay = function (code) {
        if (typeof WeixinJSBridge === "undefined") {
            if (document.addEventListener) {
                document.addEventListener('WeixinJSBridgeReady', this.jsApiCall, false);
            } else if (document.attachEvent) {
                document.attachEvent('WeixinJSBridgeReady', this.jsApiCall);
                document.attachEvent('onWeixinJSBridgeReady', this.jsApiCall);
            }
        } else {
            this.jsApiCall(code);
        }
    };
    
    Pay.jsApiCall = function (code) {
        WeixinJSBridge.invoke('getBrandWCPayRequest', code, function (res) {
            if (!Utility.isNull(res) && !Utility.isNull(res.err_msg) && res.err_msg.indexOf('ok') > 0) {
                payCallback(true);
            } else {
                payCallback(false);
            }
        });
    };
    
    export { Pay as default }
    

    百度地图

    React-Bmap API
    https://huiyan-fe.github.io/react-bmap/examples/

    React-BMap只是利用了React组件的生命周期,来调用对应的百度地图JavaScript Api的方法,比如在componentDidMount和componentDidUpdate的时候在地图上添加覆盖物,componentWillUnmount的时候移除覆盖物,React对应的render渲染函数模块返回的是null。所以这里面地图相关的dom并不是react渲染的,真正创建地图之类的还是使用百度地图JavaScript Api,React-BMap只是利用了React组件的写法来封装百度地图JavaScript Api,使我们在使用React的时候能更方便的使用百度地图JavaScript Api。

    基本设置

    申请百度地图账号
    首先去百度地图开放平台官网注册成为开发者.

    http://lbsyun.baidu.com/

    创建应用,获取移动账户秘钥。

    安装

    npm install react-bmap
    

    使用

    首页引入api

    <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你的秘钥"></script>
    

    引入组件

    import { Map, Marker, Polyline, PointLabel, NavigationControl, MarkerList } from 'react-bmap';
    

    具体实现实例
    基本地图标记

        <Map center={center} zoom="13">
         {
            mapdata.map((item, index) => {
              if (isFlag==0) {//
                return (
                  <Marker key={index}  position={{lng: item.longitude,lat: item.latitude}} >
                    <img style={{height:'3rem',width:'3rem'}} src={require('./images/air.png')} />
                    <div style={{fontSize:'0.8rem',color:'red',fontWeight: 'bold', width:'30rem'}}>{item.num} </div>  
                  </Marker>
                )  
              }
            }) 
          } 
          <NavigationControl />
        </Map> 
    

    二、其他实例

    <Map center={center} zoom="17" style={{height: '44rem'}}>         
     {/*<PointLabel data={points} />*/} 
      {/*<MarkerList data={ points }  /> */} // 绘制标记点
      
      {
        markers.map((marker, index) => {
           return (
              <Marker position={{lng: marker.lng, lat: marker.lat}} offset={new window.BMap.Size(-15, -20)}>
                <img src={require('./images/start_pos.png')} onClick={(event) => this.handleClick(marker, event)}/>  
                <div id={'marker'+marker.porder} style={{fontSize:'0.8rem',color:'red', display:'none'}}>{marker.name} </div>            
              </Marker>  
            )
        })
      }
      <Polyline strokeColor='blue' path={ markers} /> //绘制线路
      <NavigationControl anchor="BMAP_ANCHOR_TOP_RIGHT"/> 
    </Map> 
             
    

    二维码组件

    API
    https://www.npmjs.com/package/qrcode-react

    安装

    npm install qrcode-react
    

    使用

    引入组件

    import QRCode from 'qrcode-react';
    

    具体使用

    <Dialog type="ios" show={this.state.showQR} buttons={this.state.passStyle.buttons} >
      <QRCode size={236} value={ qrstr } />
    </Dialog>
    

    可以设置logo,背景色,大小等。
    qrcode可设置属性

    react-s-alert组件

    react的提示组件,界面和样式可根据实际需要设置。

    安装

    npm install react-s-alert --save
    

    使用

    在APP中引入样式与库文件

        import Alert from 'react-s-alert';
        require('react-s-alert/dist/s-alert-default.css');
        require('react-s-alert/dist/s-alert-css-effects/genie.css');
    

    具体使用

    import Alert from 'react-s-alert';
    

    警告提示

    Alert.warning('请选择', {position: 'top',
                offset: 40,onRouteClose: false, timeout: 1500});
    

    错误提示

        Alert.error(json.head.msg, 
        	{position: 'top', offset: 40,onRouteClose: false, timeout: 2000,
            onClose: function () {
               Utility.toPage('order');
            }
        });
    

    onClose事件处理提示完之后的操作。

    react-confirm-alert

    react的确认提示框组件,也可以根据自己需要修改样式。

    安装

    npm install react-confirm-alert --save
    

    使用

    引入相关样式与组件

    import { confirmAlert } from 'react-confirm-alert'; // Import
    import 'react-confirm-alert/src/react-confirm-alert.css' // Import css
    

    以退票提示为例,在退票操作之前,询问用户是否确认退票(或者常见的删除前确认提示)。

    confirmAlert({
      title: '操作提示',
       message: '退票将产生['+fee+']元手续费,您确认执行退款操作吗?',
       buttons: [
         {
           label: '确认',
           onClick: () => this.refund() // 执行退票功能
         },
         {
           label: '取消',
           onClick: () => console.log('取消了操作...')
         }
       ]
    })
    

    关于提示的样式可以根据API来设置或者到源码里改改界面颜色。

    API
    https://www.npmjs.com/package/react-confirm-alert

    其他

    做完这一个项目,对react以及相关组件了解了个大概,但现在看代码还是很杂很乱,看着闹心。
    为了赶进度,一些react特性没有好好地应用,还是原始的JavaScript方式。
    这次基本总结的同时也开始进行了项目重构,希望可以发挥react的优势,优化代码,优化调用以及实现方式等,争取年前完成。

    文献资料
    从零搭建React全家桶框架教程
    https://github.com/brickspert/react-family
    https://react.docschina.org/docs/thinking-in-react.html

    展开全文
  • 最近在学习ReactNative,把手上正在做的项目里面比较简单的一个页面拿出来试着用RN实现了,分享一下  该页面是一个展示分类列表的页面,将从服务器端请求接口拿到的数据显示出来,实现后的UI如下图:  后台...

    最近在学习ReactNative,把手上正在做的项目里面比较简单的一个页面拿出来试着用RN实现了,分享一下


           该页面是一个展示分类列表的页面,将从服务器端请求接口拿到的数据显示出来,实现后的UI如下图:



     后台获取的json数据格式如下:

    {
        "result": "1",
        "data": [
            {
                "type": "1",
                "name": "语种",
                "child": [
                    {
                        "term_id": "0",
                        "name": "中文"
                    },
                    {
                        "term_id": "0",
                        "name": "英文"
                    },
                    {
                        "term_id": "0",
                        "name": "日语"
                    }, 
                    {
                        "term_id": "0",
                        "name": "其他语言"
                    }
                ]
            },
            {
                "type": "2",
                "name": "分类",
                "child": [
                    {
                        "term_id": "1",
                        "name": "小说"
                    },
                    {
                        "term_id": "21",
                        "name": "商业"
                    },
                    {
                        "term_id": "22",
                        "name": "科普"
                    },
                    {
                        "term_id": "40",
                        "name": "其它"
                    }
                ]
            },
            {
                "type": "3",
                "name": "标签",
                "child": [
                    {
                        "term_id": "42",
                        "name": "幽默"
                    },
                    {
                        "term_id": "43",
                        "name": "毒舌"
                    },
                    {
                        "term_id": "44",
                        "name": "人生"
                    },
                    {
                        "term_id": "45",
                        "name": "小说"
                    },
                    {
                        "term_id": "46",
                        "name": "情感"
                    },
                    {
                        "term_id": "47",
                        "name": "智慧"
                    }
                ]
            }
        ]
    }

    ReactNative 代码如下:


    function doHttpGet(listview){
      fetch('http://接口url地址',{
        method:'POST',
        headers:{
          'Content-Type': 'application/x-www-form-urlencoded',
        }  
      }).then((response)=>response.json())
      .then(responseJSON=>{
          //console.log(responseJSON);
          listview.setState({dataSource:listview.state.dataSource.cloneWithRows(responseJSON.data),loaded:true});
      })
      .catch((error) => {
           console.error(error);
         });
    
    }
    
    class ListViewTest extends Component{
        constructor(props){
          super(props);
          this.state ={
              dataSource:new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}),
              loaded:false
          };
    
        }
        componentDidMount (){
          setInterval(
            ()=>{
              doHttpGet(this);
            }
          ,1000);
        }
        render(){
          if (!this.state.loaded) {
            return <View style={{flex:1,justifyContent:'center',alignItems:'center'}}><Text>Loading</Text></View>
          }
          return(
            <View style={{flex:1,backgroundColor:'#f0f0f0'}}>
    
            <ListView dataSource={this.state.dataSource}
            renderRow={
              (rowData)=>
               <View style={{flexDirection:'column',marginTop:15}}>
                  <Text style={styles.title}>{rowData.name}</Text>
                  <ListRowView data={rowData}/>
              </View>
            } />
        </View>
    
          );
        }
    }
    
    class ListRowView extends Component{
    
      itemClick(termName){
        alert(termName);
      }
    
      render(){
        var arry = [];
        for(var i in this.props.data.child){
    
          if (i%3===0) {
            if((parseInt(i)+2)===(this.props.data.child.length)){
              //最后一行只有2条数据
              var sss = (<View style={styles.flex,{flexDirection:'row',alignItems:'center'}} key={i}>
                                    <View style={styles.textItem}>
                                           <Text  style={styles.itemText}  onPress={this.itemClick.bind(this,this.props.data.child[i].name)} >{this.props.data.child[i].name}</Text>
                                    </View>
                                    <View style={styles.textItemCenter}>
                                          <Text  style={styles.itemText}  onPress={this.itemClick.bind(this,this.props.data.child[parseInt(i)+1].name)} >
                                          {this.props.data.child[parseInt(i)+1].name}</Text>
                                    </View>
                                     <View style={styles.textItem}></View>
                         </View>);
              arry.push(sss);
            }else if((parseInt(i)+1)===(this.props.data.child.length)){
              //最后一行只有有1条条数据
              var sss = (<View style={styles.flex,{flexDirection:'row',alignItems:'center'}} key={i}>
              <View style={styles.textItem}>
                     <Text  style={styles.itemText} onPress={this.itemClick.bind(this,this.props.data.child[i].name)} >{this.props.data.child[i].name}</Text>
              </View>
                           <View style={styles.textItemCenter}></View>
                            <View style={styles.textItem}></View>
                         </View>);
              arry.push(sss);
            }else {
              var pos1 = this.props.data.child[i].name;
              var pos2 = this.props.data.child[parseInt(i)+1].name;
              var pos3 = this.props.data.child[parseInt(i)+2].name;
    
              var sss = (<View style={styles.flex,{flexDirection:'row',alignItems:'center'}} key={i} >
              <View style={styles.textItem} >
                    <Text style={styles.itemText} onPress={this.itemClick.bind(this,pos1)}  >{pos1}</Text>
              </View>
              <View style={styles.textItemCenter}>
                    <Text style={styles.itemText} onPress={this.itemClick.bind(this,pos2)}  >{pos2}</Text>
              </View>
              <View style={styles.textItem}>
                    <Text style={styles.itemText} onPress={this.itemClick.bind(this,pos3)} >{pos3}</Text>
              </View>
              </View>);
              arry.push(sss);
            }
          }
        };
       return (<View style={{backgroundColor:'#fff',
       flexDirection:'column',borderTopWidth:1,borderTopColor:'#ccc'}} key={this.props.data.term_id}>{arry}</View>);
      }
    }
    
    const styles = StyleSheet.create({
      flex:{
        flex: 1,
        justifyContent: 'center',
      },
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#ccc',
      },
      welcome: {
        fontSize: 20,
        textAlign: 'center',
        margin: 10,
      },
      instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5,
      },
    textItem:{
      flex:1,
      paddingLeft:15,
      justifyContent: 'center',
      height:40,
      borderBottomWidth:1,
      borderBottomColor:'#ccc',
    },
    itemText:{
        fontSize:15,
        color:'#505050',
    },
    textItemCenter:{
      borderLeftWidth:1,
      borderLeftColor:'#ccc',
      borderRightWidth:1,
      borderRightColor:'#ccc',
      flex:1,
      height:40,
      borderBottomWidth:1,
      borderBottomColor:'#ccc',
      justifyContent:'center',paddingLeft:15
    },
    title:{
      marginLeft:9,
      marginBottom:5
    }, 
    });
    
    AppRegistry.registerComponent('Test', () => ListViewTest);</span>

    每三条数据换行的处理方式一直觉得写的不好,没接触过react不熟悉语法,希望有了解的大神多指教



    展开全文
  • 因为现在的项目要求上传图片至...有问题请求留言。有关其他的一些引入的文件,我后续会补上,都是一些依赖! 首先看一下样式:未上传前: 上传后的样式: 其实样式还是蛮可以的对吧!!! 除render中的部...

    因为现在的项目要求上传图片至阿里云而且支持图片的校验。在网上扒了很多博客。。。很徒劳。基本都是互相抄,功能缺失严重,所以只能自己实现一个,放在博客上面,干货可以拿走!!有需求可以直接拿,别忘了点个赞!!!

     

    有问题请求留言。有关其他的一些引入的文件,我后续会补上,都是一些依赖!

    首先看一下样式:未上传前:

    上传后的样式: 

    其实样式还是蛮可以的对吧!!!

    除render中的部分传参逻辑需要自己定义外,其他完全可以直接使用。

    话不多说直接上代码,首先父组件中引入该组件

    //引入组件,路径按照自己定义的路径。
    import Confirm from './confirm/index.jsx';
    
    //引用该组件  参数自己按照自己的需求传递,用于上传图片之后重命名!
    <UploadDom
      userId={this.state.userId}
      userName={this.state.userName}
      groupId={this.state.groupId}
      extend={this.extend}
    />

    然后就是最关键的upload组件的实现! 

    
    import React, { Component, PureComponent } from 'react';
    import PropTypes from 'prop-types';
    import bindAll from 'lodash.bindall';
    import { Base64 } from 'js-base64';
    import plupload from 'plupload';
    import './Upload.css';
    import './lib/crypto1/crypto/crypto.js';
    import './lib/crypto1/hmac/hmac.js';
    import './lib/crypto1/sha1/sha1.js';
    
    let config = {
        name: '老男的demo',
        prefix: '',
        upload: 'yunweihuigaosunizenmepeizhi1',
        download: 'yunweihuigaosunizenmepeizhi2',
        accessid: 'yunweihuigaosunizenmepeizhi3',
        videoAccessid: 'yunweihuigaosunizenmepeizhi4',
        accesskey: 'yunweihuigaosunizenmepeizhi5',
        videoAccesskey: 'yunweihuigaosunizenmepeizhi6',
        policyText: {
            "expiration": "2020-01-01T12:00:00.000Z", //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
            "conditions": [
                ["content-length-range", 0, 1048576000] // 设置上传文件的大小限制
            ]
        },
        dirname: "laonan/",
        finished: true,
        userId: 1,
        userName: "texting",
        groupId: 1,
        uploadedList: [],
        files: {
            limit: 0,
            size: 120 * 1024, // Byte
            sizeMin: 40 * 1024,
            progress: false, // 进度条
            extensions: ["jpg", "png"],
            repetition: true, // 不允许重复(true 不允许)
            img: {
                size: [626, 413],
                limit: [">", ">"]
            }
        },
        needLogin: false,
        ossUrl: 'yunweihuigaosunizenmepeizhi7'
    }
    let fileName = '点击选择参赛者2寸证件照';
    const initFile = {
        fileName: '点击选择参赛者2寸证件照',
        fileState: '',
        filePercent: 0,
        fileErr: '',
    };
    class UploadDom extends Component {
        constructor(props) {
            super(props);
            bindAll(this, [
                'computeSize',
                'removeFile',
                'listKillFile',
                'getSuffix',
                'addFileFilterImg',
                'contrast'
            ]);
            this.state = {
                fileList: [],
                uploading: false,
                fileName: '您的浏览器不支持flash,Silverlight或者HTML5!',
                fileState: '',
                filePercent: 0,
                fileErr: '123',
                isOk: false,
                photoUrl: '',
                btnContent: '点击上传',
                btnStyle: 'upload-btn',
                uploader: {}
            };
        }
        componentWillMount(){
    
        }
        componentDidMount() {
            config.files.img && this.addFileFilterImg();
            this.state.uploader = new plupload.Uploader({
                runtimes: 'html5,flash,silverlight,html4',
                browse_button: `selectImage_recipt`,
                url: config.ossUrl,
                multi_selection: false,
                filters: {
                    mime_types: config.files.extensions.length ? [{ extensions: config.files.extensions.join() }] : undefined,
                    prevent_duplicates: config.files.repetition,
                    max_file_size: config.files.size,
                    min_file_size: config.files.sizeMin,
                    imgSize: config.files.img.size
                },
                flashSwfUrl: "plupload/js/Moxie.swf",
                init: {
                    PostInit: file => {
                        this.setState({
                            fileName: '点击选择参赛者2寸证件照',
                        });
                        document.getElementById(`uploadImage_recipt`).onclick = () => {
                            this.setUploadParam(this.state.uploader, '', false);
                            return false;
                        };
                    },
                    BeforeUpload: (up, file) => {
                        this.setUploadParam(up, file.name, true);
                    },
                    FileFiltered:(up, file) => {
                        if (false) {
                            if (
                                config.files.limit &&
                                up.files.length > config.files.limit
                            ) {
                                up.removeFile(file);
                            } else {
                                config.finished = false;
                            }
                        } else {
                            if (up.files.length > 1) {
                                this.listKillFile(up.files[0]);
                                this.removeFile(up.files[0]);
                            }
                            config.finished = false;
                        }
                    },
                    FilesAdded: (up, files) => {
                        plupload.each(files, file => {
                            this.setState({
                                ...initFile,
                                fileName: `${file.name}(${plupload.formatSize(file.size)})`,
                            });
                        });
                    },
                    UploadProgress: (up, file) => {
                        this.setState({
                            fileState: `${file.percent}%`,
                            filePercent: file.percent,
                        });
                    },
                    FileUploaded: (up, file, info) => {
                        if (info.status === 200) {
                            this.setState({
                                fileState: '成 功',
                                btnContent: '已上传',
                                btnStyle:'upload-btn-end'
                            });
                            // 这一块没什么用 可以删掉
                            let { isUp } = this.props
                            if (isUp === true) {
                                this.props.uploaded(`${config.download}${config.dirname}${fileName}`)
                            } else {
                                let filesData = {
                                    [this.props.tag]: `${
                                        this.props.uploadType && this.props.uploadType === 'video'
                                            ? config.videoDownload
                                            : config.download
                                        }${config.dirname}${fileName}`,
                                };
                            }
                        } else {
                            this.setState({
                                fileState: info.response,
                            });
                        }
                    },
                    Error: (up, err) => {
                        this.setState({
                            fileErr: `Error xml:${err.response}`,
                        });
                        switch (err.code) {
                            case -200:
                                this.setState({
                                    fileErr: `网络发生错误, error, 3000`,
                                });
                                console.log("网络发生错误", "error", 3000);
                                break;
                            case -300:
                                this.setState({
                                    fileErr: `磁盘读写错误, error, 3000`,
                                });
                                console.log("磁盘读写错误", "error", 3000);
                                break;
                            case -600:
                                this.setState({
                                    fileErr: `上传文件体积不能超过${this.computeSize(config.files.size)},error,3000`,
                                });
                                console.log(
                                    `上传文件体积不能超过${this.computeSize(config.files.size)}`,
                                    "error",
                                    3000
                                );
                                break;
                            case -601:
                                this.setState({
                                    fileErr: `选择的文件类型不符合要求, error, 3000`,
                                });
                                console.log("选择的文件类型不符合要求", "error", 3000);
                                break;
                            case -602:
                                this.setState({
                                    fileErr: `选取文件重复, error, 3000`,
                                });
                                console.log("选取文件重复", "error", 3000);
                                break;
                            default:
                                this.setState({
                                    fileErr: `Error xml:${err}`,
                                });
                                console.log(err);
                        }
                    },
                },
            });
            this.state.uploader.init();
        }
        computeSize(size) {
            let sizeStr = ``;
            if (size < 1024) {
                sizeStr = `${size}B`;
            } else if (size >= 1024 && size < 1024 * 1024) {
                sizeStr = `${(size / 1024).toFixed(1)}KB`;
            } else if (size >= 1024 * 1024 && size < 1024 * 1024 * 1024) {
                sizeStr = `${(size / 1024 / 1024).toFixed(1)}MB`;
            } else if (
                size >= 1024 * 1024 * 1024 &&
                size < 1024 * 1024 * 1024 * 1024
            ) {
                sizeStr = `${(size / 1024 / 1024 / 1024).toFixed(1)}GB`;
            }
            return sizeStr;
        }
        removeFile(file) {
            if (file.status === 2) {
                this.state.uploader.stop();
                console.log("上传已终止", "warning", 3000);
            }
            this.state.uploader.removeFile(file);
            config.finished = true;
            this.state.uploader.files.some((item) => {
                if ([1, 2].includes(item.status)) {
                    config.finished = false;
                    return true;
                }
            });
            this.listKillFile(file);
        }
        listKillFile(file) {
            config.uploadedList.some((item, i, arr) => {
                if (item.id === file.id) {
                    arr.splice(i, 1);
                    return true;
                }
            });
            this.state.fileList.some((item, i, arr) => {
                if (item.id === file.id) {
                    arr.splice(i, 1);
                    return true;
                }
            });
        }
        getSuffix(filename) {
            let pos = filename.lastIndexOf('.');
            let suffix = '';
            if (pos !== -1) {
                suffix = filename.substring(pos);
            }
            return suffix;
        }
        // 添加自定义 过滤类型
        addFileFilterImg() {
            plupload.addFileFilter("imgSize", (imgSize, file, cb) => {
                if (file.type.includes("image") && !file.type.includes("gif")) {
                    let img = new plupload.moxie.image.Image();
    
                    img.onload = () => {
                        let flag = true;
                        const msg = [];
                        const imgMsg = [
                            this.contrast(img.width, 0),
                            this.contrast(img.height, 1)
                        ];
                        imgMsg.forEach((item) => {
                            if (!item.flag) {
                                flag = false;
                            }
                            if (item.rule) {
                                msg.push(`图片${item.type}需${item.rule}${item.imgSize}像素`);
                            }
                        });
    
                        if (msg.length > 0) {
                            console.log(`${msg.join(",")}`, "error", 3000);
                        }
    
                        img.destroy();
                        img = null;
                        cb(flag);
                    };
                    img.onerror = () => {
                        console.log("imgOnError", img);
                        img.destroy();
                        img = null;
                        cb(false);
                    };
                    img.load(file.getSource());
                } else {
                    cb(true);
                }
            });
        }
        contrast(size, num) {
            let flag = false;
            let rule = "";
            const imgSize = config.files.img.size[num];
            let type = "宽";
            if (num === 1) {
                type = "高";
            }
            switch (config.files.img.limit[num]) {
                case "=":
                    if (size === imgSize) {
                        flag = true;
                    } else {
                        rule = "等于";
                    }
                    break;
                case "!=":
                    if (size !== imgSize) {
                        flag = true;
                    } else {
                        rule = "不等于";
                    }
                    break;
                case ">":
                    if (size > imgSize) {
                        flag = true;
                    } else {
                        rule = "大于";
                    }
                    break;
                case "<":
                    if (size < imgSize) {
                        flag = true;
                    } else {
                        rule = "小于";
                    }
                    break;
                case ">=":
                    if (size >= imgSize) {
                        flag = true;
                    } else {
                        rule = "大于等于";
                    }
                    break;
                case "<=":
                    if (size <= imgSize) {
                        flag = true;
                    } else {
                        rule = "小于等于";
                    }
                    break;
                default:
                    if (size === imgSize) {
                        flag = true;
                    } else {
                        rule = "等于";
                    }
            }
            return {
                flag,
                rule,
                type,
                imgSize
            };
        }
        setUploadParam(up, filename, ret) {
            fileName = this.props.userId+ '_'+ this.props.groupId+ '_'+ this.props.userName + this.getSuffix(filename);
            const policyBase64 = Base64.encode(JSON.stringify(config.policyText));
            const bytes = Crypto.HMAC(
                Crypto.SHA1,
                policyBase64,
                config.accesskey,
                {
                    asBytes: true,
                }
            );
            const wholeUrl =config.upload +'/'+ config.dirname + fileName;
            console.log(wholeUrl);
            this.setState({
                photoUrl: wholeUrl
            })
            this.props.extend(wholeUrl);
            const signature = Crypto.util.bytesToBase64(bytes);
            const new_multipart_params = {
                key: config.dirname + fileName,
                policy: policyBase64,
                OSSAccessKeyId: config.accessid,
                success_action_status: '200', //让服务端返回200,不然,默认会返回204
                signature: signature,
                Filename: 'console/',
                multi_selection: false,
            };
            up.setOption({
                url: config.upload,
                multipart_params: new_multipart_params,
            });
            if (typeof this.props.before === 'string') {
                up.start();
            }
        }
    
        render() {
            return (
                <div className="container">
                    <div className="upload-box">
                        <span id={`selectImage_recipt`} className="uploadImage">
                            {this.state.fileName}
                        </span>
                        <span id={`uploadImage_recipt`} className={this.state.btnStyle}>
                            {this.state.btnContent}
                        </span>
                    </div>
                    <p
                        className="schedule"
                        style={{
                            width: this.state.filePercent + '%',
                            height: this.state.filePercent > 0 ? 6 : 0,
                        }}
                    />
                    <div className="uoload-tips">用以制作考试证件,像素为626*413,格式为jpg或png,大小40KB-120KB</div>
                    <pre className="pre-upload">{this.state.fileErr}</pre>
                </div>
            );
        }
    }
    
    UploadDom.defaultProps = {
        before: '',
        isShowClear: false
    }
    
    export default UploadDom;
    

    css 就补贴不提出来了!后续可以用上面的链接下载完整代码 

     

    展开全文
  • 安卓的log系统初步探索,带你了解安卓log系统的整体框架,本文安卓框架,深入Linux驱动程序,让你知道安卓log系统的整体流程。
  • 摘自...主页面 require("./recordmp3.js"); let recorder = new MP3Recorder({ debug:true, funOk: function () { console.log(‘初始化成功’);...funCancel: funct...
  • 作者:神三元来源:https://juejin.im/post/5d1fe6be51882579db031a6d在线体验地址:http://47.105.150.105:...
  • 3.React Native单组件触摸响应 3.1申请成为响应者和释放响应者 3.2合成事件介绍-evt 4.嵌套组件事件传递 4.1正常事件响应顺序 4.2父视图拦截事件传递 5.手势识别-PanResponder 5.1PanResponder类介绍 5.2...
  • 在尝试把Django作为一个单纯的REST框架配合React进行web开发的时候遇到了使用axios发出post请求,但Django后端收不到数据的情况。 axios代码是很普通的样例代码: axios({ method: 'post', url: '/api/../...
  • Android okHttp网络请求之Get/Post请求  前言:  之前项目中一直使用的Xutils开源框架,从xutils 2.1.5版本使用到最近的xutils 3.0,使用起来也是蛮方便的,只不过最近想着完善一下app中使用的开源框架...
  • 地理位置 geolocation window.navigator.geolocation 复制代码 1.getCurrentPosition()获取当前位置信息 2.watchPosition()监视位置变化,和1参数一样 3.clearWatch()清除位置监视 ...getCurrentPosition(success,...
  • App有两个路子:一个是基于PhoneGap或者React Native的混合开发(天猫好像已经迁移到了React Native上);一种是使用JSPatch来为程序打补丁。不过就功能来看,还是React Native要强大一点,毕竟定位不一样。
  • Flutter:从入门到实践

    2019-07-02 03:04:31
  • 前端每周清单第 9 期:React Studio 1.0.2、ECharts GL 1.0 alpha发布;向jQuery用户介绍Vue 为InfoQ中文站特供稿件,首发地址为这里;如需转载,请与InfoQ中文站联系。从属于笔者的 Web 前端入门与工程实践的前端...
  • shell实例手册

    2019-03-31 14:55:49
    shell实例手册 1文件{ touch file # 创建空白文件 rm -rf 目录名 # 不提示删除非空目录(-r:递归删除 -f强制) dos2unix # windows文本转linux文本 unix2dos # linux文本转windows文本 enca filename ...
  • Tomcat的Server初始化及启动过程:[url]http://donald-draper.iteye.com/blog/2327060[/url] Tomcat的connector:[url]http://hill007299.iteye.com/blog/1757198[/url] 阻塞队列--LinkedBlockingQueue:[url]...
1 2 3 4
收藏数 75
精华内容 30
关键字:

pos请求t react-native