
其实呢,我只是一个前端
最近做小程序,实在不想做。然后来吐槽下小程序的坑吧。也只做了这一个项目,有不对的地方请指正见谅。 一、tabbarapp.json里配置tabbar是原生层的,所以要实现比价奇怪的tabbar只有自己用view写了,比如:这样选中的按钮图标都超出了tabbar 的高度,只有的就自己定义一个组件吧 <template name="tabbar"> <view class="tabbar_box"> <block wx:for="{{tabbar.list}}" wx:for-item="item" wx:key="index"> <!-- 选中 --> <navigator class="tabbar_nav" url="{{item.pagePath}}" style="color:{{tabbar.selectedColor}}" open-type="switchTab" wx:if="{{item.selected}}"> <image class="tabbar_icon_on" src="{{item.selectedIconPath}}"></image> </navigator> <navigator class="tabbar_nav" url="{{item.pagePath}}" style="color:{{tabbar.color}}" open-type="redirect" wx:else> <image class="tabbar_icon" src="{{item.iconPath}}"></image> <text class="tabbar_text">{{item.text}}</text> </navigator> </block> </view> </template> js呢 就定义到app.js editTabBar: function () { var tabbar = this.globalData.tabbar, currentPages = getCurrentPages(), _this = currentPages[currentPages.length - 1], pagePath = _this.__route__; (pagePath.indexOf('/') != 0) && (pagePath = '/' + pagePath); for (var i in tabbar.list) { tabbar.list[i].selected = false; (tabbar.list[i].pagePath == pagePath) && (tabbar.list[i].selected = true); } _this.setData({ tabbar: tabbar }); }, globalData: { userInfo: null, tabbar: { list: [{ pagePath: "/pages/index/index", iconPath: "", selectedIconPath: "", text: "test" }, .........], } } 然后再对应页面里调用 onLoad: function () { //tabbar app.editTabBar(); } 再说个在app.json里配置 custom "window":{ "navigationStyle": "custom" }, 屏幕高度就是整个手机屏幕的高度了,看看编辑器显示布局信息,但是右边线程序的退出按钮不会消失,设计时必须注意啊,不要有按钮在右边!!!会重叠遮挡 二、cover-view如果调用了原生层,原生层有哪些·····map、video、canvas、camera, 想在上面布局就只有用cover-view、cover-image,这两个玩意儿不支持的东西太多了,overflow,zindex不支持,touch事件不支持,具体的不支持去官网看吧。然后你想想,当map或者vido作为全屏背景时,需要在上面实现其他元素布局也是蛋疼的 <cover-view class="red"></cover-vie> <cover-view class="blue"></cover-view> 如上,正常html顺序 red是红色框里的,blue是蓝色框里的。圈出来有重叠对吧,都是absolute定位。但是问题出在红色框重叠区域不能点击了,因为蓝色遮住了,所以换一个 <cover-view class="blue"></cover-view> <cover-view class="red"></cover-vie> 这样整个红色框按钮就都能点击了。cover-view设置固定高宽,里面的元素要是超过cover-view即使absolute 一样被隐藏。 三、父子组件子组件通过data 接受父组件数据 <view class="container"> <import src="../com/son/index.wxml"/> <template is="son" data="{{data: 'fuck', aa: '231', event: 'click'}}"/> </view > 如上事件传递也是这样,子组件不能再里面自己定义事件,只能通过父组件传递过去。 四、点击事件获取数据 <cover-view bindtap="click('sadas')"></cover-view> 想这么传递数据是不行的, 那就用dataset咯 <cover-view data-aa="sadas" bindtap="click" ></cover-view> click(event) { let tab = event.currentTarget.dataset.aa; }, 五、css方面 不支持伪类。。 待补充。。。。。
一提闭包,前端首先想到的肯定就是javascript的闭包,接着就是其特性,闭包里的变量常驻内存不会消失,外部函数可以访问内部函数的变量,似乎是摆脱了作用域的限制。 那么就先说说前端js的闭包,最简单的一个例子 function closure1() { var tmp = 'hello world'; return function() { return 'this say ' + tmp; } } //运行 var result1 = closure1(); //result1 等于 this say hello world 这样函数的局部变量就不限于函数体内,同时局部变量也不是唯一可以返回的,函数的参数也可以被返回捕获 //数组每一项乘以multiple, multiple被捕获 function closure2(multiple) { return function(ary) { return ary.map(function(item, i) { return item * multiple }) } } var multipleClosure = closure2(9); var result = multipleClosure([1,2,4,5,7]); //或者 result = closure2(9)([1,2,4,5,7]); console.log(result) //result == [9, 18, 36, 45, 63] multiple变量一直保存在返回的函数体内,必能在调用返回的multipleClosure是访问到。 基于对html元素的访问时闭包也是个好东西,尤其是通过getElementsByTagName或这个getElementsByClassName时 <p class="closure">closure1</p> <p class="closure">closure2</p> <p class="closure">closure3</p> <p class="closure">closure4</p> <p class="closure">closure5</p> function init() { var pAry = document.getElementsByClassName("closure"); for( var i = 0, item; item = pAry[i++]; ) { item.onclick = function() { console.log(i) // i 永远输出6 } } } init() 以上无论你点击哪个p元素都是输出6,这样情况下在事件上包裹一层匿名闭包,让i以局部变量方式访问到 function init() { var pAry = document.getElementsByClassName("closure"); for( var i = 0, item; item = pAry[i++]; ) { (function() { var tmp = i item.onclick = function() { console.log(tmp) } })() } } 有时需要实现一个类让其具有私有属性,并能对操作访问,比如实现一个Stack const items = new WeakMap(); class Stack { constructor() { items.set(this, []) } push(ele) { let s = items.get(this); s.push(ele) } pop() { let s = items.get(this); let r = s.pop(); return r; } } let stack = new Stack() stack.push(121) console.log(items) 这样做items不是私有属性任何一个方法都可以改动他,于是可以在外Stack用闭包包起来 let Stack = (function() { const items = new WeakMap(); class Stack { constructor() { items.set(this, []) } push(ele) { let s = items.get(this); s.push(ele) } pop() { let s = items.get(this); let r = s.pop(); return r; } getItems() { return items; } } return Stack; })(); let stack = new Stack() stack.push(121) console.log(stack.getItems()) 这样做有个弊端,扩展无法继承私有属性 前端或以为闭包只是js的专利,其实不然,闭包轻量级,短小,简洁的特性在其他语言中更具有优越性,尤其是在函数式编程中,可以避免代码冗长如下面java实现的闭包 public class closureTest { public static Runnable closure() { String string = "hello closure"; Runnable runnable = () -> System.out.println(string); System.out.println("exiting closure"); return runnable; } public static void main(String[] args) { Runnable runnable = closure(); System.out.println("go to closure"); runnable.run(); } } 上列中,closure 方法有一个局部变量 value,该变量的寿命很短:只要我们退出 closure,它就会消失。closure 内创建的闭包在其词法范围中引用了这个变量。在完成 closure 方法后,该方法将闭包返回给 main 中的调用方。在此过程中,它从自己的堆栈中删除变量 value,但是lambda 表达式将会执行。 exiting closurego to closurehello closure 有时需要数值计算,比如计算数值的能否被整除,和倍数 import static java.util.stream.Collectors.toList; import java.util.Arrays; import java.util.List; public class test { public static void call() { // numbers } public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 3, 5, 6, 8); List result = numbers.stream() .filter(e -> e % 2 == 0) //过滤出可以被2整除的 .map(e -> e * 2) //过滤出返回的 在乘以2 .collect(toList()); System.out.println(result); } } map接收的是一个单纯的lambda表达式,通常倍数是变化的,就需一种方式把变量传递给lambda表达式,那换成闭包如下 import static java.util.stream.Collectors.toList; import java.util.Arrays; import java.util.List; public class test { public static void call() { // numbers } public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 3, 5, 6, 8); int factor = 5; List result = numbers.stream() .filter(e -> e % 2 == 0) .map(e -> e * factor) //map接受一个闭包。 闭包接收参数e ,同时捕获携带 factor 变量的状态 .collect(toList()); System.out.println(result); } } 运行 javap -p test.class Compiled from "test.java" public class test { public test(); public static void main(java.lang.String[]); private static java.lang.Integer lambda$main$1(int, java.lang.Integer); private static boolean lambda$main$0(java.lang.Integer); } 可以看出java编译器为闭包创建一个 lambda$main$ 方法捕获一个int类型数据,在看看字节码javap -c -p test.class 来检查字节码 public static void main(java.lang.String[]); Code: 0: iconst_1 1: anewarray #2 // class java/lang/Integer 4: dup 5: iconst_0 6: bipush 6 8: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 11: aastore 12: invokestatic #4 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 15: astore_1 16: bipush 9 18: istore_2 19: aload_1 后面省略。。。。。 private static java.lang.Integer lambda$main$1(int, java.lang.Integer); Code: 0: aload_1 1: invokevirtual #15 // Method java/lang/Integer.intValue:()I 4: iload_0 5: imul 6: invokestatic #3 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 9: areturn 把变量9 int类型值存入局部变量2,从局部变量1中装载引用类型值,然后状态变量被lambda$main$1加载并传递到为闭包创建的函数中。可以看出闭包能捕获并携带状态,并将状态从定义上下文携带到执行点 再看看groovy的闭包实现 //计算1-n的和 def sum(n) { total = 0 for (int i = 1; i <= n; i++) { total += i } total } //输出3 println sum(2) //计算1-n的乘 def multi(n) { total = 1 for (int i = 1; i <= n; i++) { total *= i; } //return 可以省略 return total } //输出6 println multi(3) 上面的运算中,都有一个共同的循环,循环内部不一样,修改下我们把 def com(n, inside) { for (int i = 1; i <= n; i++) { inside(i) } } total = 0 com(2, { total += it }) println total 求和如此,那么求乘积如下 total = 1 com(3, { total *= it }) println total 可以看出,com方法是以一个函数作为参数,返回函数作为结果。以上方法把中在循环时,把i值传递给了匿名代码块。在Groovy中就称这类匿名代码块为闭包。可以换种写法 com(2) { total += it } 这个看着像不像前面js的 closure2()() inside中保存了一个指向闭包的引用,{}内部的代码又被传递给了inside 给闭包传递多个参数 def parames(closure) { closure 20, "dollars, so rich" } parames() { money, msg -> println "I have ${money} ${msg} " } 调用closure中,parames有一个数字,一个字符串参数,闭包分别用 money和msg引用指向它们输出 I have 20 dollars, so rich 复合闭包 def filterAryValue(closure) { def ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] def len = ary.size() - 1 for (int i = 0; i <= len; i++) { ary[i] = closure(ary[i]) } ary } def subtract = {it -> it - 1} def multi = {it -> it * 2 } println filterAryValue({it -> multi(subtract(it)) }) //[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] 第一次使用闭包subtract 返回的值再传递给multi闭包 闭包的特性也不止于此,包括函数科里化,递归中也有使用等
首先上图吧, 关于vue动态加入echarts时,宽度不能100%的问题,首先上图 如图,echarts中的canvas的width才500,显然不对呀,右边还有那么大的一片空白,需要说明下交互步骤 看看代码,按钮触发click方法 显示弹窗或者动态加载组件 <span class="btn" @click="showecharts">showeCharts</span> methods: { showecharts(){ this.$prompt('Test', resolve => require(['@/components/ShowEChart.vue'], resolve)) } } prompt 中的内容肯定是动态的 ShowEChart.vue <div> <EChart :chartData="chartData"/> </div> export default { components: { //EChart.vue图表内容 EChart: resolve => require(['@/components/EChart.vue'], resolve), }, } 这里应该echart能显示如上图了,但是宽度没打开,换成 imp 也是一样的 import EChart from '@/components/EChart.vue'; 思其原因还是prompt还没渲染完整紧着echart也加载,echart的宽度根据具父元素的宽度适应的渲染,给父元素一个确定宽度也可以撑开,也不是好办法,或者setTimeout也行 最好的还是利用vue的mounted 可以解决组件加载完成后 ShowEChart.vue 修改为 <div> <component :is="EChart"></component> </div> exprot defautl { data() { return { EChart: null } }, mounted() { this.$nextTick(() => { this.EChart= resolve => require(['@/components/EChart.vue''],resolve)} ); } } 来看下效果 ** 从此王子和公主完美的生活在一起 **
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
h5混合开发有时需要调用本地的代码,就是js和原生代码交互。当然rn的封装和调用都很方便,现在用下cordova封装自定义插件plugin,cordova和phonegap的关系自行百度吧,当然cordova的安装此处也省略。 首先以 js 调用安卓的Toast为例,显示Toast提示,同时android studio中Log 一下。 具体怎么做,下面然后我们来一件一件的抽肢剖解 当然新建一个android 工程,比如这样 ---------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------- 然后 导入CordovaLib 在建的工程里 在Cordova 新建的项目里有这个 复制出来导入到 android studio中就好了 package plugins.com.test; import android.util.Log; import android.widget.Toast; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.json.JSONArray; import org.json.JSONException; /** * Created by Administrator on 2018/1/18. */ public class Demo extends CordovaPlugin{ @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if (action.equals("demoToast")) { String string = args.getString(0); this.demoToast(string, callbackContext); return true; } return false; } private void demoToast(String str, CallbackContext callbackContext) { Log.i("demo", "demo_demo"); if (str != null || str.length() > 0) { Toast.makeText(cordova.getActivity(), str, Toast.LENGTH_LONG).show(); callbackContext.success(str); } else { callbackContext.error("str 不能为空~~~!!"); } } } 写到这里需要用plugman 打包插件了 先安装plugman npm install plugman -g plugman create --name 插件名 --plugin_id 插件ID --plugin_version 插件版本号 如下图 三个步骤 把java代码复制到 src目录下 新建android 目录 var exec = require('cordova/exec'); module.exports = { demoToast: function (arg0, success, error) { exec(success, error, 'Demo', 'demoToast', [arg0]); } } plugin.xml 配置 <?xml version='1.0' encoding='utf-8'?> <plugin id="plugins.com.demo" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> <name>plugins-com-demo</name> <js-module name="demo" src="www/plugins-com-demo.js"> <clobbers target="Demo" /><!--Demo 为 demo的类名 写错了 alert 提示err class not found --> </js-module> <platform name="android"> <config-file target="res/xml/config.xml" parent="/*"> <feature name="Demo"> <param name="android-package" value="plugins.com.demo.Demo"/> <!--Demo 类的 完成路径 --> </feature> </config-file> <source-file src="src/android/Demo.java" target-dir="src/plugins/com/demo"/><!--Demo 在android工程里 src下的全路径 --> </platform> </plugin> 插件就完成了,需要在cordova 新建的项目中加入此插件 新建一个 项目 步骤如下 cordova create test 进入test 目录 cd test cordova platform add android 加入插件如下图 cordova plugin add c:\插件路径\plugins-com-demo 在新建的 test 项目下 www\js\index.js中加入如下代码 onDeviceReady: function() { Demo.demoToast('ok, ok!!!', function(str) { alert(str + 'succc') }, function(err) { alert(err) }); this.receivedEvent('deviceready'); }, 同理设置点击 <h1 id="demo">click me click me</h1> onDeviceReady: function() { this.receivedEvent('deviceready'); document.getElementById('demo').addEventListener('click', function(e) { demo.demoToast('ok, ok!!!', function(str) { alert(str + 'succc') }, function(err) { alert(err) }); }) }, cordova run android 这样做不是目的,下面把视频播放用安卓代码播放 首先创建一个 activity_video.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="textPersonName" android:text="你好-new-activity" /> <VideoView android:layout_width="match_parent" android:layout_height="200dp" android:id="@+id/videView"/> </LinearLayout> 对应的VideoActivity.classpackage plugins.com.demo; import android.app.Activity; import android.net.Uri; import android.os.Bundle; import android.widget.MediaController; import android.widget.VideoView; /** * Created by Administrator on 2018/1/22. */ public class VideoActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_new); VideoView videoView = (VideoView)findViewById(R.id.videView); MediaController mc = new MediaController(this); videoView.setVideoURI(Uri.parse("http://127.0.0.1:8888/Test/2017.mp4")); videoView.setMediaController(mc); videoView.start(); } } MainActivity里唤起newActivity<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="plugins.com.demo.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" tools:layout_constraintTop_creator="1" tools:layout_constraintRight_creator="1" tools:layout_constraintBottom_creator="1" tools:layout_constraintLeft_creator="1" android:id="@+id/textView" /> <Button android:layout_width="291dp" android:layout_height="49dp" android:text="newActive" android:layout_marginStart="27dp" app:layout_constraintBaseline_toBaselineOf="@+id/textView" tools:layout_constraintBaseline_creator="1" tools:layout_constraintLeft_creator="1" app:layout_constraintLeft_toLeftOf="parent" android:layout_marginLeft="36dp" android:id="@+id/btnNewActivity"/> </android.support.constraint.ConstraintLayout> 记得加入网络读权限 同时加入js 可以调用的类VideoNewActivity.javapublic class VideoNewActivity extends CordovaPlugin{ @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { super.execute(action, args, callbackContext); if (action.equals("showVideo")) { Context ctx = cordova.getActivity().getApplicationContext(); Intent viodeIntent = new Intent(ctx, VideoActivity.class); this.cordova.getActivity().startActivity(viodeIntent); return true; } return false; } } 在安卓项目下完成组件的java代码,剩下的就是打包插件 同样的味道还是 plugman, 但是这样直接打包是不对的cordova不能直接识别findViewById, so改下android代码 public class VideoNewActivity extends CordovaPlugin{ @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { super.execute(action, args, callbackContext); if (action.equals("showVideo")) { Context ctx = cordova.getActivity().getApplicationContext(); Intent viodeIntent = new Intent(ctx, VideoActivity.class); this.cordova.getActivity().startActivity(viodeIntent); return true; } return false; } } public class VideoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String packageName = getApplication().getPackageName(); setContentView(getApplication().getResources().getIdentifier("activity_video", "layout", packageName)); int video = getApplication().getResources().getIdentifier("videView", "id", packageName); VideoView videoView = (VideoView)findViewById(video); MediaController mc = new MediaController(this); videoView.setVideoURI(Uri.parse("http://127.0.0.1:8888/Test/2017.mp4")); videoView.setMediaController(mc); videoView.start(); } } index.js 代码 onDeviceReady: function() { this.receivedEvent('deviceready'); document.getElementById('demo').addEventListener('click', function(e) { VideoNewActivity.playVideo('click', function(e) { alert(str + 'video-succc') }, function(err) { alert(err) }); }) }, plugins-com-video.js var exec = require('cordova/exec'); module.exports = { playVideo: function (arg0, success, error) { exec(success, error, 'VideoNewActivity', 'showVideo', [arg0]); } } plugin.xml配置如下 <?xml version='1.0' encoding='utf-8'?> <plugin id="plugins.com.VideoNewActivity" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> <name>VideoNewActivity</name> <js-module name="VideoNewActivity" src="www/plugins-com-video.js"> <clobbers target="VideoNewActivity" /> </js-module> <platform name="android"> <config-file target="res/xml/config.xml" parent="/*"> <feature name="VideoNewActivity"> <param name="android-package" value="plugins.com.demo.VideoNewActivity"/> </feature> </config-file> <source-file src="src/android/VideoNewActivity.java" target-dir="src/plugins/com/demo"/> <config-file target="AndroidManifest.xml" parent="/manifest/application"> <!-- /manifest/application activity 才能写入 application 里 --> <uses-permission android:name="android.permission.INTERNET" /> <activity android:label="VideoActivity" android:name="plugins.com.demo.VideoActivity"></activity> </config-file> <config-file parent="/*" target="AndroidManifest.xml"></config-file> <source-file src="src/android/VideoActivity.java" target-dir="src/plugins/com/demo"/> <source-file src="src/android/activity_video.xml" target-dir="res/layout"/> </platform> </plugin> 注意activity引入的位置 来看个效果图吧···如下···· ------------------------------------------------------------------------------ ------------------------------------------------------------------- ------------------------也可以是样--------------------------------------------------------------- 有不完善敬请更正··········
简单的说搞react开发的痛点之一,单向数据流的传递,redux统一管理数据,redux-saga又处理管理了异步调用。 要实现的内容如下,界面 目录结构 首先从请求接口入手,用axios封装请求接口,统一处理请求 axios.js import axios from 'axios' let defaultConfig = { timeout: 3000, } let instance = axios class Axios { constructor(props) { if (props && typeof props == 'object') { instance = axios.create(props) } else { instance = axios.create(defaultConfig); } //拦截 instance.interceptors.request.use((config) => { return config; }, (error) => { console.log(error) return Promise.reject(error); }); //日志 响应结果 instance.interceptors.response.use((response) => { return response.data; }, (error) => { console.log(error) return Promise.reject(error); }) } send(params) { if (!params || typeof params != 'object') { throw new Error('params is undefined or not an object') } if (params.method == 'GET') { return get(params.url) } else if (params.method == 'POST') { return post(params.url, params) } } } async function get(url, callback) { try { let response = await instance.get(url) return response } catch (e) { console.log(e) } } async function post(url, params, callback) { try { let response = await instance.post(url) //eturn callback(response) return response } catch (e) { console.log(e) } } export default Instance = new Axios();store.js 管理以及开发环境下的及时更新 const sagamiddleware = createSagaMiddleware(); export default function configureStore(initStore = {}) { const middlewares = [sagamiddleware]; if (__DEV__) { middlewares.push(logger) } const createStoreMiddleware = applyMiddleware(...middlewares)(createStore); const store = createStoreMiddleware( createReducer(), initStore ); store.runSaga = sagamiddleware.run; store.close = () => store.dispatch(END); // Make reducers hot reloadable, see http://mxs.is/googmo /* istanbul ignore next */ if (module.hot) { module.hot.accept(() => { const nextRootReducer = require('../reducers/index').default; //reducers 文件下的 index store.replaceReducer(createReducer(nextRootReducer)) }, ) } return store } reducers 文件下的 index.js, combineReducers各个模块的 reducer import { combineReducers } from 'redux'; import { latestNews } from './latestNewsReducer'; import { special } from "./specialReducer"; import { themes } from "./themesReducer"; export default createReducer = (injectedReducers) => { return combineReducers({ latestNews: latestNews, special: special, themes: themes, router: router, ...injectedReducers }) } 接下来就是 各个模块的 reducer了,接受action 返回的 state 或者data,由于都是get请求,各个模块的请求都大同小异,以最新模块为例, latestNewsReducer.js 如下import { RQUEST_LATESTNEWS, SUC_LATESTNEWS, DESTORY_LATESTNEWS } from '../actions/latestnews/types'; export const latestNews = (state = null, action) => { switch (action.type) { case RQUEST_LATESTNEWS: return state case SUC_LATESTNEWS: return Object.assign({}, state, { data: action.data }) case DESTORY_LATESTNEWS: return null default: return state; } } type 为常理单独写出来的 理应 单独新建 const 目录用于放各个模块的 type,图快就都挨着action放了 还是 以 最新模块 为例子 type.js //进入请求请求 export const FETCHED_LATESTNEWS = 'fetched_latestnews' //发送请求 export const RQUEST_LATESTNEWS = 'request_latestnews' //请求成功 返回数据 export const SUC_LATESTNEWS = 'suc_latestnews' //销毁 export const DESTORY_LATESTNEWS = 'destory_latestnews' //当离开当前页面时 返回此 置空stroe对应的值 latestNews的action 也很简单 import { RQUEST_LATESTNEWS, SUC_LATESTNEWS, FETCHED_LATESTNEWS, DESTORY_LATESTNEWS } from './types'; //初始请求 export const fetchedLatestNews = () => { return { type: FETCHED_LATESTNEWS } } //开始发送请求 export const requestLatestNews = () => { return { type: RQUEST_LATESTNEWS } } //请求成功 export const sucLatestNews = (data) => { return { type: SUC_LATESTNEWS, data } } //销毁 export const destoryLatestnews = () => { return { type: DESTORY_LATESTNEWS } } 现在开始sagas的编写 -------------------------------------------------嗯··--------------------------------------------- -------------------------------------------------辣眼睛--------------------------------------------- sagas目录下index 统一引入各个模块 对应的 请求方法 index.js import { all, takeEvery, fork } from 'redux-saga/effects'; import { FETCHED_LATESTNEWS } from '../actions/latestnews/types' import { getLatestNews } from './latestnews'; import { FETCHED_SPECICAL } from '../actions/special/types'; import { getSpecial } from './special'; import { FETCHED_THEMES } from '../actions/themes/types'; import { getThemes } from './themes'; export default function* rootSaga() { yield takeEvery(FETCHED_LATESTNEWS, getLatestNews); yield takeEvery(FETCHED_THEMES, getThemes); yield takeEvery(FETCHED_SPECICAL, getSpecial); } 还是以最新为例: import { put, call } from 'redux-saga/effects'; import { SUC_LATESTNEWS } from '../actions/latestnews/types'; import { repoLoadingError, requestLatestNews, sucLatestNews } from "../actions/latestnews/index"; import { GetLatestNews } from '../apis/latestnews/fetch'; export function* getLatestNews() { try { yield put(requestLatestNews()) const data = yield call(GetLatestNews); yield put({type: SUC_LATESTNEWS, data}); } catch (error) { yield put(repoLoadingError(error)); } } 现在到容器了 container, 对应的 最新模块的container latestnews.js 是嵌套在第个 tabNavigator 里的 ,和热门平级, tabNavigator 配置都大同小异 此处省略··· import React, { Component } from 'react'; import { View, Text } from 'react-native'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { requestLatestNews, sucLatestNews, fetchedLatestNews } from '../../actions/latestnews/index'; class LatestNews extends Component { componentDidMount() { this.props.fetchedLatestNews() } render() { let list = this.props.latestNews; return ( <View> {list ? <Text>{JSON.stringify(this.props.latestNews)} </Text> : <Text>LOADING</Text> } </View> ) } } const mapStateToProps = (state) => { return { latestNews: state.latestNews } } const matchDispatchToProps = (dispatch) => { return bindActionCreators({ fetchedLatestNews: fetchedLatestNews, }, dispatch) } export default connect(mapStateToProps, matchDispatchToProps)(LatestNews); 进行到这里,配置react-navigation, tabNavigator.js import React, { Component } from 'react'; import { View, Image, StyleSheet } from "react-native"; import { TabNavigator } from "react-navigation"; import News from '../containers/latestnews'; import Themes from '../containers/themes'; import Special from '../containers/special'; export default tabNavigator = TabNavigator( { newsTab: { screen: News, navigationOptions: { tabBarLabel: '最新', tabBarIcon: ({ tintColor, focused }) => ( <Image resizeMode='contain' source={require('../icon/icon_latestnews.png')} style={[style.footImage, {tintColor: tintColor}]} /> ) } }, Themes: { screen: Themes, navigationOptions: { tabBarLabel: '主题', tabBarIcon: ({ tintColor, focused }) => ( <Image resizeMode='contain' source={require('../icon/icon_themes.png')} style={[style.footImage, {tintColor: tintColor}]} /> ) } }, Special: { screen: Special, navigationOptions: { tabBarLabel: '专栏', tabBarIcon: ({ tintColor, focused }) => ( <Image resizeMode='contain' source={require('../icon/icon_special.png')} style={[style.footImage, {tintColor: tintColor}]} /> ) } }, }, { backBehavior: 'none', tabBarPosition: 'bottom', lazy: true, lazyLoad: true, initialRouteName: 'newsTab', tabBarOptions: { showIcon: true, pressOpacity: 0.8, style: { height: 45, backgroundColor: '#ffffff', zIndex: 0, position: 'relative' }, labelStyle: { fontSize: 12, paddingVertical: 0, marginTop: 0 }, iconStyle: { marginTop: -5 }, tabStyle: { backgroundColor: '#eeeeee', }, inactiveTintColor: '#212121', activeTintColor: '#0084ff' } } ) let style = StyleSheet.create({ footImage: { width: 24, height: 24 }, }); 三个大模块,最新,主题和专栏都放在 一个tabNavigator里,再配置到StackNavigator,最新里还嵌套一个 tabNavigator, 也可以把三个主题都放 stackNavigator里。放在stackNavigator,某些方面会更简单也更直观 import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { addNavigationHelpers, StackNavigator } from 'react-navigation'; import tabNavigator from './tabNavigator'; export const Navigator = StackNavigator({ tab: { screen: tabNavigator } },{ navigationOptions: { header: null, headerBackTitle: null, headerTintColor: '#333333', showIcon: false, swipeEnabled: false, animationEnabled: false, initialRouteName: 'tab', lazy: true, }, mode: 'card', lazy: true, }); export default Navigator; 当页面滑动离开当前模块 进入下一个模块 ,减小开支,可以选择把离开模块的store 清空,修改入口文件如下 调用onNavigationStateChange 方法 class Index extends Component { _onNavigationStateChange = (prevState, nextState) => { let prev = prevState.routes[0], preIndex = prev.index, preRouteName = prev.routes[preIndex].key, switch (preRouteName) { case 'newsTab': store.dispatch(destoryLatestnews()) break; case 'Themes': store.dispatch(destoryThemes()) break; default: store.dispatch(destorySpecial()) break; } } render() { return ( <Provider store={store}> <AppStackNavigator onNavigationStateChange={(prevState, currentState) => { this._onNavigationStateChange(prevState, currentState) }}/> </Provider> ); } } export default Index; 本来想在页面离开,组件销毁时 在componentWillUnmount 里调用 destoryLatesNews() 这些方法的,但是滑动切换的时候组件并不会销毁,那么只有更新周期函入手了。 --------------------------------------------------------------------- ps: 知道怎么在componentWillUnmount 里调用 destoryLatesNews 方法或者 进入到此函数的敬请指点,求告知呀 --------------------------------------------------------------------- 那么就需要修改一下 latestnews 进入到在更新周期函数中如下写入 shouldComponentUpdate(nextProps, nextState) { if (nextProps.navigation.state.routeName === 'newsTab' && this.props.latestNews == null) { this.props.fetchedLatestNews() return true; } return false; } 大体流程就这样了,当然redux-saga 各个函数方法的用途还需多理解,为什么 没进入 componentWillUnmount 是为什么呢·····哈哈哈哈· 等告知····· 有需要的交流的可以加个好友
项目中会遇到一个组件/方法, 在多个地方被调用,比较方便的的方式之一,this.$custom(agruments) 这样就比较方便 ,不然用组件引入的办法调用就就比较麻烦,每可能都需要这样调用 import coustom from './coustom' export default { components: { coustom } } <coustom :data="data" v-if="show"></coustom> 换个办法以自定义alert 为例 就这么一句就调用出来 this.$alert('哈哈哈'); alert.vue 如下 <template> <transition name="dialog-fade"> <div v-if="show" class="modal fade dialog-modal" id="alert" role="dialog" data-backdrop="false" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header row"> <h5 class="modal-title col-md-4">提示</h5> <button type="button" class="close" aria-label="Close" @click="close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="col-xs-offset-1">{{message}}</div> </div> <div class="modal-footer"> <button type="button" class="btn btn-primary" @click="close">确定</button> </div> </div> </div> </div> </transition> </template> <script> export default { name: 'Alert', methods: { close: function() { this.show = false } } } </script> 对然后将Alert 挂载到vue全局 index.js function install(Vue) { Object.defineProperty(Vue.prototype, '$alert', { get() { let div = document.createElement('div') document.body.appendChild(div); return (message) => { const Constructor = Vue.extend(Alert) let Instance = new Constructor({ data() { return { message: message, show: true } } }).$mount(div); }; } }); } export default install 最后vue.use 一下 import alert from 'index' Vue.use(alert) 就能直接调用了 当然前面有个坑 transition 的 vue 的过渡 alert的div不是一开始就加载到文档上的,通过后面的 document.body.appendChild(div); 动态写入,就造成 alert 显示时看不到transition效果,抛开vue来说也会遇到这样的情况 可以settimeout 下 给append的元素 addClass 同理在vue 中也可以,当然还有更好的办法暂时没想到。。。。 alert 只是纯的 传递一个param 但是需要 传递一个function 时,比如confirem this.$confirm('请确定你是傻逼', () => console.log('yes')}) 还是相同的味道,相同的道理 Confirm.vue <template> <transition name="dialog-fade"> <div v-if="show" class="modal fade" id="confirm" tabindex="-1" role="dialog" data-backdrop="false" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header row"> <h5 class="modal-title col-md-4">提示</h5> <button type="button" class="close" @click="close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div class="col-xs-offset-1">{{message}}</div> </div> <div class="modal-footer"> <button type="button" class="btn btn-info" @click="close">取消</button> <button type="button" class="btn btn-primary" @click="ConfirmSure">确定</button> </div> </div> </div> </div> </transition> </template> <script> export default { name: 'Confirm', methods: { close: function() { this.show = false }, ConfirmSure() { this.confirmSure()//确定关闭 由install 传入 this.close() } } } </script> import Confirm from './Confirm.vue' function install(Vue) { Object.defineProperty(Vue.prototype, '$confirm', { get() { let div = document.createElement('div') document.body.appendChild(div); return (message, confirmSure) => { const Constructor = Vue.extend(Confirm) const Instance = new Constructor({ data() { return { message: message, show: true } }, methods: { confirmSure: confirmSure //确定方法 } }).$mount(div); }; } }); } export default install 同样use 一下 import alert from 'index' Vue.use(alert) this.$confirm('你是猴子请来的唐僧么', () => console.log('yes,哈哈哈哈哈')) 传了两个arguments,累了吧,轻松点, 片分三级,嗯········参数也得 至少能传 三个。。。。 嗯,往哪里看呐···! 这里传递的params 才传递到第二个,才实现第二个功能,要么要实现第三个功能呢,dialog对话框内容,根据环境应用环境传递进去显示 如此中间的form 表单是动态传递进入的 <div class="midpass"> <div class="form-group form-group-inline flex" :class="errors.has('ans') ? 'has-error has-danger' : '' "> <label class="form-control-label">1+1=?</label> <div class="form-input-longer"> <input type="password" class="form-control form-control-title" name="ans" v-model="input.value" v-validate="'required|min:1'" placeholder="请输入答案"> <div class="help-block">请输入答案</div> </div> </div> </div> export default { name: 'oneaddone', data() { return { input: { value: null } } } } 用到了前端验证 vue veevalidate 这样传递进去 要调教数据时,触发验证,就是父组件调用子组件的方法 this.$children 即可 dialog.vue <template> <transition name="dialog-fade"> <div v-if="show" class="modal fade" id="popform" tabindex="-1" role="dialog" data-backdrop="false" aria-hidden="true"> <div class="hide" id="formpop-btn" data-toggle="modal" data-target="#popform"></div> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header row"> <h4 class="modal-title col-md-6 col-xs-4">{{message}}</h4> <button type="button" class="close col-md-1" aria-label="Close" @click="close"> <span aria-hidden="true">×</span> </button> </div> <form @submit.prevent="submit"> <div class="modal-body"> <keep-alive> <component :is="modalBody" ref="forms"></component> </keep-alive> </div> <div class="modal-footer"> <div class="center-block" style="width: 230px;"> <button type="button" class="btn btn-secondary" @click="close">取消</button> <button type="submit" class="btn btn-primary">保存</button> </div> </div> </form> </div> </div> </div> </transition> </template> <script> export default { name: 'dialog', data() { return { Submit: () => {} } }, methods: { close() { this.show = false }, setSubmit(dataKey, Submit) { this.submit = () => { //给submit btn 设置提交方法 this.$children.map(function (child) { let data = child.$data[dataKey] //data 的key 调用时传递的 data key 可以根据情景定义 child.$validator.validateAll().then((result) => { if (result) { Submit(data).then(res => { if (res) this.close() }) } }); }) } }, } } </script> 再来一遍 import dialog from './dialog.vue' function install(Vue) { Object.defineProperty(Vue.prototype, '$dialog', { get() { let div = document.createElement('div'); document.body.appendChild(div); return (message, modalBody) => { const Constructor = Vue.extend(dialog) const Instance = new Constructor({ data() { return { message: message, show: true, modalBody: modalBody } } }).$mount(div) return Instance.setSubmit //放回 一个方法用于 传递 自定义的数据和 submit 时方法 }; } }); } export default install Vue.use 同上 this.$dialog('请计算下面的数学题', resolve => require(['./oneaddone.vue'], resolve))('input', (data) => { this.$alert('哈哈哈,正确') console.log(data) return data } ) PS:这里需要注意的是 this.$dialog()(); 是这样的 因为里面返回的是一个方法,同时$mount 不能直接挂载在body 或者html下 必须在body的 子元素中 所以,createElement('div') 1+1 = 2 答案正确········ 有需要的交流的可以加个好友
最近搞vue,用的vue-cli,快速构建开发环境,当然核心还是集成的webpack。之前自己做react的webpack环境配置总觉得差强人意,于是就把vue-cli的迁移过来,感觉还是不错的。对应一般开发需要,下面需要修改的就在build和config目录下的几个文件中 从webpack.base.conf.js 文件开始,无论生产环境还是开发环境都以这个为基础的, module.exports = { entry: { app: ['./src/js/index.js'], //入口文件 babel: ['babel-polyfill'] //babel-polyfill 和redux 单独打包减小app.js 的打包体积 用于配合externals redux: ['redux', 'react-redux'], }, output: { path: config.build.assetsRoot, filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath, libraryTarget: 'umd' //用于外部引入的 react.js 等 }, resolve: { extensions: ['.js', '.json'], symlinks: false }, module: { rules: [ { test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test')], exclude:[resolve('node_modules')], //在node_modules的文件不被babel理会 query: { presets: ['react', 'stage-2'] } }, { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]'), } }, { test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } }, // { // test: /\.less$/, // use: ExtractTextPlugin.extract({ use: extractCssLoaders, fallback: 'style-loader' }), // } ] }, // 配置全局使用 plugins: [ new webpack.ProvidePlugin({ "React": "react", "ReactDOM": "react-dom", "_": "lodash", "classnames":"classnames" }), //extract css into its own file new ExtractTextPlugin({ filename: utils.assetsPath('css/[name].[contenthash].css') }), ], // 单独提取出 react 减小打包文件大小 externals: { 'react-router': { amd: 'react-router', root: 'ReactRouter', commonjs: 'react-router', commonjs2: 'react-router' }, react: { amd: 'react', root: 'React', commonjs: 'react', commonjs2: 'react' }, 'react-dom': { amd: 'react-dom', root: 'ReactDOM', commonjs: 'react-dom', commonjs2: 'react-dom' } } } 然后再 util.js 文件里,主要就在cssLoaders exports.cssLoaders = function (options) { options = options || {} var cssLoader = { loader: 'css-loader', options: { minimize: process.env.NODE_ENV === 'production', sourceMap: options.sourceMap, modules: true, localIdentName: '[local]--[hash:base64:6]', //class 名字 代替 } } // generate loader string to be used with extract text plugin function generateLoaders(loader, loaderOptions) { var loaders = [cssLoader]; if (loader) { loaders.push({ loader: loader + '-loader', options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // Extract CSS when that option is specified //(which is the case during production build) if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, publicPath: '../../', //解决 build css bg img 加载路径不对问题 fallback: 'react-style-loader' // 修改vue-style-loader }) } else { return ['react-style-loader'].concat(loaders) } } // https://vue-loader.vuejs.org/en/configurations/extract-css.html return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), sass: generateLoaders('sass', { indentedSyntax: true }), scss: generateLoaders('sass'), stylus: generateLoaders('stylus'), styl: generateLoaders('stylus') } } webpack.prod.conf 生产环境的修改,打包时,redux,和babel-polyfill 分离打包配置 配合webpack.base.conf.js中entry修改 new webpack.optimize.CommonsChunkPlugin({ name: ['app', 'redux', 'babel'], //单独提取打包 filename: './static/js/[name].js', minChunks: ({resource}) => { resource && /\.js$/.test(resource) && resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', chunks: ['app', 'redux', 'babel'] }), webpack.dev.conf.js和webpack.prod.conf.js 有同一个地方修要, 用于在inde.html上写入外部js new HtmlWebpackPlugin({ title: config.title, filename: 'index.html', template: 'index.ejs', //修改模板类型 为ejs inject: true, js: config.externalsJs_dev // 开发环境是config.externalsJs_prod }), 当然config.externalsJs_dev 配置到了 config/index.js 中 // 提取出的文件链接 externalsJs_dev:[ 'https://cdn.bootcss.com/react/16.0.0/umd/react.development.js', 'https://cdn.bootcss.com/react-dom/16.0.0/umd/react-dom.development.js', 'https://cdn.bootcss.com/react-router/4.2.0/react-router.js' ], externalsJs_prod: [ 'https://cdn.bootcss.com/react/16.0.0/umd/react.production.min.js', 'https://cdn.bootcss.com/react-dom/16.0.0/umd/react-dom.production.min.js', 'https://cdn.bootcss.com/react-router/4.2.0/react-router.min.js' ], title: 'react-redux-demo' //模板标题删除项目 根目录下的index.html 改为index.ejs, 内容如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <div id="root"></div> <% for (var i = 0, item; item = htmlWebpackPlugin.options.js[i++];) { %> <script type="text/javascript" src="<%= item %>"></script> <% } %> </body> </html>目录结构如下 包括打包 运行一下 npm run build --report=true 看看打包分析, 暂时未集成babel-runtime 是否要集成babel-runtime 需要修改 .babelrc 文件中 "plugins": ["transform-runtime"],取消此行的注释, 打包分析如下 package.json 内容 整个修改后的依赖 { "name": "wz", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "node build/dev-server.js", "start": "node build/dev-server.js", "build": "node build/build.js" }, "author": "", "license": "ISC", "dependencies": { "cross-env": "^5.0.5", "expect": "^1.20.1", "node-libs-browser": "^2.0.0", "node-sass": "^4.5.3", "npm": "^5.3.0", "react": "^15.6.1", "react-addons-test-utils": "^15.1.0", "react-dom": "^15.6.1", "react-redux": "^5.0.4", "redux": "^3.5.2", "redux-logger": "^3.0.1", "redux-promise": "^0.5.3", "redux-thunk": "^2.1.0", "shelljs": "^0.7.8", "style-loader": "^0.18.2" }, "devDependencies": { "autoprefixer": "^7.1.2", "babel-core": "^6.10.4", "babel-loader": "^7.1.2", "babel-plugin-react-transform": "^2.0.2", "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.9.1", "babel-preset-env": "^1.3.2", "babel-preset-react": "^6.11.1", "babel-preset-stage-0": "^6.5.0", "babel-preset-stage-2": "^6.22.0", "babel-register": "^6.9.0", "bower-webpack-plugin": "^0.1.9", "chalk": "^2.0.1", "chromedriver": "^2.27.2", "compression-webpack-plugin": "^1.0.0", "connect-history-api-fallback": "^1.3.0", "copy-webpack-plugin": "^4.1.1", "core-js": "^2.0.0", "cross-spawn": "^5.0.1", "css-loader": "^0.28.5", "cssnano": "^3.10.0", "eventsource-polyfill": "^0.9.6", "express": "^4.14.1", "extract-text-webpack-plugin": "^3.0.1", "file-loader": "^1.1.5", "friendly-errors-webpack-plugin": "^1.6.1", "html-webpack-plugin": "^2.29.0", "http-proxy-middleware": "^0.17.3", "less": "^2.7.2", "less-loader": "^4.0.3", "minimist": "^1.2.0", "open": "0.0.5", "opn": "^5.1.0", "optimize-css-assets-webpack-plugin": "^3.0.0", "ora": "^1.3.0", "postcss-import": "^10.0.0", "postcss-loader": "^2.0.6", "precss": "^2.0.0", "react-style-loader": "^1.0.1", "react-transform-hmr": "^1.0.4", "rimraf": "^2.6.0", "url-loader": "^0.6.2", "webpack": "^3.5.5", "webpack-bundle-analyzer": "^2.9.0", "webpack-dev-middleware": "^1.6.1", "webpack-dev-server": "^2.4.4", "webpack-hot-middleware": "^2.11.0", "webpack-merge": "^4.1.0" }, "engines": { "node": ">= 4.0.0", "npm": ">= 3.0.0" } } 以上可能有不准确的或者冗余地方 请酌情参考 有需要的交流的可以加个好友
用vue-cli 开发,要打包了,放到tomcat 上发现css 或者图片加载不出来,控制台一看是资源路径不对 资源是在assets 目录下的 彻底的解决办法 utils.js 文件中 if (options.extract) { return ExtractTextPlugin.extract({ use: loaders, publicPath: '../../', //解决 build css bg img 加载路径不对问题 fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } 如此就能尽情的 npm run build 了
form 表单 要限制 input 只能输入数字,首先想到的是 <input type="number"/> 不过问题来了 , 如图,能输入字母e , 而且用了 下面的正则也能输入 console.log 输入看看 e 就是也是代表数字 次方。。。 那还是老实的用 <input type="text"/> 外加正则咯 $(target).on('keyup', function() { var number = $(this).val().replace(/[^0-9]+/,''); $(this).val(number); });
我在react-navigation的组件StackNavigator 和TabNavigator组合使用在加上redux,出现如下问题 there is no route defined for key ***must be one of 这个类似的在 react-navigation的github也有,StackNavigator里嵌套一个TabNavigator, navreducer传递 AppNavigations就放入 <Provider>中 render 出来 显示了 const AppNavigations = ({ dispatch, nav }) => { return <Navigator navigation={addNavigationHelpers({ dispatch, state: nav })} /> } const mapStateToProps = state => ({ nav: state.navreducer, }); module.exports = connect(mapStateToProps)(AppNavigations); combineReducers 里还需要配置navReducer const allReducers = combineReducers({ nav: navreducers, }); 那么navreducers的配置就是重点了, 不注意 就把TabNavigator里面的route 配置进去了 export const nav = (state, action) => { switch (action.type) { //TabNavigator 里模块 case '****': return Navigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Tabchild' }), state ); case '**': //TabNavigator 里模块 return Navigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Tabchild' }), state ); case 'WebDetail': return Navigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'WebDetail' }), {...state, webViewURL: action.webViewURL } ); default: return Navigator.router.getStateForAction(action, state) || state; } } 上面做是不对的, 这样做了就报出上面的错误,正确如下,因为我们在给provider 里提供的就是StackNavigator,这样就对应了 export const navreducer = (state, action) => { switch (action.type) { case 'WebDetail': return Navigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'WebDetail' }), {...state, webViewURL: action.webViewURL } ); default: return Navigator.router.getStateForAction(action, state) || state; } } 加上redux的好处,比如,在切换页面时 就可以直接 dispatch 切换转跳页面了, 不然换个 同时吧 参数webURl传递给下一个将要展示的页面 <TouchableOpacity onPress={()=> { this.props.navigation.dispatch({ type: 'WebDetail', webViewURL: data.alt }); }}> </TouchableOpacity> webview 需要的参数通过state就能获取到 class WebView extends Component { render() { return <WebView source={{uri: this.props.url}}/> } } function mapStateToProps(state) { return { url: state.navreducer.webViewURL } } 当然可以用其他的办法用 navigate 方法切换 传递webViewURL <TouchableOpacity onPress={()=> { this.props.navigation.navigate('WebDetail', webViewURL); }}> </TouchableOpacity> 在 mapStateToProps输出看 这样就 得通过很多成了看着也觉得数据隐藏的太深了
react-native开发需时肯定有tab的切换,或者页面的转调,当然用RN自身的Navigator 也可以但是也不是那么方便react-navigation 就能满足很多大部分需求,如下图的三种切换方式,下面就说下TabNavigator 和StackNavigator的应用,才踏的一个坑,还是太年轻呀,横刀一撸!!!! 主要的界面 用tab 切换即是TabNavigator, 切换如下图 一共四个页面当然配置就如下咯 // 两个参数 routeConfigs: NavigationRouteConfigMap, config: TabNavigatorConfig = {} // 一个route对应的页面和tab图标, 一个切换的样式整个tab栏的样式 //tab export const AppNavigator = TabNavigator({ Hotshow: {screen: hotshow, navigationOptions: { tabBarLabel: '热映', tabBarIcon: ({ tintColor, focused }) => ( <Image resizeMode='contain' source={require('../icon/icon_hot.png')} style={[style.footImage, {tintColor: tintColor}]} /> ) }}, Usshow: {screen: usshow, navigationOptions: { tabBarLabel: '北美', tabBarIcon: ({ tintColor, focused }) => ( <Image style={[style.footImage, {tintColor: tintColor}]} resizeMode='contain' source={require('../icon/icon_us_normal.png')} /> ) }}, Soonshow: {screen: soonshow, navigationOptions: { tabBarLabel: '近期', tabBarIcon: ({ tintColor, focused }) => ( <Image style={[style.footImage, {tintColor: tintColor}]} resizeMode='contain' source={require('../icon/icon_soon_normal.png')} /> )} }, Nearcinemas: {screen: nearcinemas, navigationOptions: { tabBarLabel: '影院', tabBarIcon: ({ tintColor, focused }) => ( <Image style={[style.footImage, {tintColor: tintColor}]} resizeMode='contain' source={require('../icon/icon_near_normal.png')} /> )}, } }, { tabBarPosition: 'bottom', lazy: true, // 是否懒加载 initialRouteName: 'Hotshow', tabBarOptions: { showIcon: true, pressOpacity: 0.8, style: { height: 45, backgroundColor: '#ffffff', zIndex: 0, position: 'relative' }, labelStyle: { fontSize: 11, paddingVertical: 0, marginTop: -4 }, iconStyle: { marginTop: -3 }, tabStyle: { backgroundColor: 'rgb(230,69,51)', }, } }); TabNavigatorConfig更多具体的参数如下 /** * Tab Navigator */ export interface TabNavigatorConfig { tabBarComponent?: React.ReactElement<any>, tabBarPosition?: 'top'|'bottom', swipeEnabled?: boolean, animationEnabled?: boolean, lazy?: boolean, tabBarOptions?: { activeTintColor?: string, activeBackgroundColor?: string, inactiveTintColor?: string, inactiveBackgroundColor?: string, showLabel?: boolean, style?: ViewStyle, labelStyle?: TextStyle, // Top showIcon?: boolean, upperCaseLabel?: boolean, pressColor?: string, pressOpacity?: number, scrollEnabled?: boolean, tabStyle?: ViewStyle, indicatorStyle?: ViewStyle } initialRouteName?: string, order?: string[], paths?: any // TODO: better def backBehavior?: 'initialRoute'|'none' } 如上都配置好了,就需要在屏幕上显示 ,下面代码作为展示 主要的还是创建了AppWithNavigationState 然后export 出他, 在root下调用 class AppWithNavigationState extends Component { render() { return( <View style={{flex: 1}}> {this.props.fetchbool ? <Loading/> : <AppNavigator navigation={ addNavigationHelpers({dispatch: this.props.navigator, state: this.props.nav}) }/> } </View> ) } } function mapStateToProeps(state){ return { fetchbool: state.fetchload.data, nav: state.nav } }; function macthDispatchToProps(dispatch) { return bindActionCreators({ navigator: navigator, initHotshowAction: initHotshow, fetchLoading: fetchLoading }, dispatch); } let style = StyleSheet.create({ footImage: { width: 25, height: 25 }, }); export default connect(mapStateToProeps, macthDispatchToProps)(AppWithNavigationState); 结合了redux , nav就是通过state 传递的,在redux目录下创建了一个navigators/reducer import { NavigationActions } from 'react-navigation'; import { AppNavigator } from '../../navigators/AppNavigator'; const initialNavState = { index: 0, routes: [ { key: 'Hotshow', routeName:'Hotshow', }, { key: 'Usshow', routeName:'Usshow', }, { key: 'Soonshow', routeName:'Soonshow', }, { key: 'Nearcinemas', routeName:'Nearcinemas', }, ], }; export const nav = (state = initialNavState, action) => { let nextState; switch (action.type) { case 'Usshow': return AppNavigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Usshow' }), state ); case 'Soonshow': setate.index= 1 return AppNavigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Soonshow' }), state ); case 'Nearcinemas': return AppNavigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Nearcinemas' }), state ); default: //return AppNavigator.router.getStateForAction(action, state) || state; // return AppNavigator.router.getStateForAction( // state // ); return AppNavigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Hotshow' }), state ) || state ; } } 做到此处,是个tab的四个页面切换还是可以的,问题就在与当我切换到下一个页面时,就出现了状况, 在没有做进入下一个页面前,提前ajax请求,当进入了请求,当然能请求成功,但是请求成功后,刚显示的界面会还在显示等待时,尼玛返回上一个界面 这么说有点拗口,不解其意 额,,,, 清洗脱俗,惊鸿一瞥下就给直接返回A了, console.log(this.props.nav) 看看了 输出一次 nav.index = 0 然后 1 然后 0 ··········就这么又回到原点了,同时在AppWithNavigationState,解决办法想了一个在navigators/reducer里把nav传递的index固定了 export const nav = (state = initialNavState, action) => { let nextState; switch (action.type) { case 'Usshow': setate.index= 1 return AppNavigator.router.getStateForAction( NavigationActions.navigate({ routeName: 'Usshow' }), state );有次当然可以解决,但是 tab按钮不能点击咯,这真是尴尬的一幕, 当然还有个蛋疼的直接用TabNavigator 在AppWithNavigationState中的render 会运行四次,即第一个界面加载时, console.log 输出变知道 当然这样也有办法解决,react 的生命周期呀,最前面的两个 实例化 和存在期,就在存在期入手, shouldComponentUpdate(nextProps, nextState) { return **** ? false : true ; } componentWillUpdate(nextProps, nextState) { return *** ? false : true; } render()就减小了开销 问题是 tab还是不能点击啊!!!!!!!! 谜底是这样 StackNavigator 需要这个!!!! export const StackNavigator = StackNavigator( { Tab:{screen: AppNavigator}, // 就是整个tab切换的 AppNavigator Product:{screen: DtlWebView} }, { stackConfig:{ header: null, headerBackTitle:null, headerTintColor:'#333333', showIcon:true, swipeEnabled:false, animationEnabled:false, initialRouteName: 'Hotshow' }, mode:'card', } ); stackConfig 主要参数 TabNavigator, StackNavigator配合应用就很好区分开了前者模块页面之间,后者Stack由字面理解就是通过栈的方式进行切换,最新的就压入到栈顶显示 在App 里之前的就改为StackNavigator class App extends Component { render() { return ( <Provider store={ store }> <StackNavigator /> </Provider> ); } } 到此就结束咯 不对之处,敬请更正··········
react-native 的数据传递是父类传递给子类,子类通过this.props.** 读取数据,这样会造成组件多重嵌套,于是用redux可以更好的解决了数据和界面View之间的关系, 当然用到的是react-redux,是对redux的一种封装。 react基础的概念包括: 1.action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑,同时尽量减少在 action 中传递的数据 2. reducer是一个匹配函数,action的发送是全局的,所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。reducer里就是判断语句 3.Store 就是把以上两个联系到一起的对象,Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用reducer组合 而不是创建多个 store。 4.Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层 5.connect一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑mapDispatchToProps)再接受一个参数(将要绑定的组件本身)。mapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你需要的数据,组件里读取还是用this.props.* 6.container只做component容器和props绑定, 负责输入显示出来,component通过用户的要交互调用action这样就完整的流程就如此 来张图 流程如上,那么结构如下。 需要实现的效果如下, 顶部 一个轮播,下面listview,底部导航切换,数据来源豆瓣电影 程序入口 import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import App from './app/app'; export default class movies extends Component { render() { return( <App/> ); } } AppRegistry.registerComponent('moives', () => moives)store 和数据初始化 /** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-17 10:38:09 * @modify date 2017-05-17 10:38:09 * @desc [description] */ import { createStore, applyMiddleware, compose } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk' import React, { Component } from 'react'; import Root from './containers/root'; import allReducers from './reducers/allReducers'; import { initHotshow, fetchLoading } from './actions/hotshow-action'; const createStoreWithMiddleware = applyMiddleware(thunk)(createStore); const store = createStoreWithMiddleware(allReducers); //初始化 进入等待 首屏数据 ajax请求 store.dispatch(fetchLoading(true)); class App extends Component { constructor(props) { super(props); } render() { return ( <Provider store={ store }> <Root/> </Provider> ); } } module.exports = App;action纯函数,同时把action type单独写出来。在action同目录下文件types.js /** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-17 10:36:44 * @modify date 2017-05-17 10:36:44 * @desc [description] */ 'use strict'; //首页 正在上映 export const HOTSHOW_BANNER = 'HOTSHOW_BANNER'; export const HOTSHOW_LIST = 'HOTSHOW_LIST'; export const HOTSHOW_FETCH = 'HOTSHOW_FETCH'; export const ADDMORE = 'AddMORE';hotshow-action.js/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-12 04:56:43 * @modify date 2017-05-12 04:56:43 * @desc [description] */ import { HOTSHOW_BANNER, HOTSHOW_LIST, HOTSHOW_FETCH, ADDMORE } from './types'; import { hotshowFetch } from '../middleware/index-api'; export const addBanner = (data) => { return { type: HOTSHOW_BANNER, data } } //加载等待,true 显示 反之 export const fetchLoading = (bool) => { return { type: HOTSHOW_FETCH, bool } } export const addList = (data) => { return { type: HOTSHOW_LIST, data } } // 正在热映 初始请求 export const initHotshow = () => { return hotshowFetch(addList); } allReducers.js 整合所有reducer import { combineReducers } from 'redux'; import { HotShowList, Banner, fetchLoading } from './hotshow/reducers' const allReducers = combineReducers({ hotshows: HotShowList, // 首屏数据列表 listview banner: Banner, // 轮播 fetchload: fetchLoading, //加载中boo }); export default allReducers; hotshowreducer /** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-12 04:56:34 * @modify date 2017-05-12 04:56:34 * @desc [description] */ import { HOTSHOW_BANNER, HOTSHOW_LIST, HOTSHOW_FETCH } from '../../actions/types'; export const HotShowList = (state = {}, action) => { switch (action.type) { case HOTSHOW_LIST: return Object.assign( {} , state , { data : action.data }); default: return state; } } export const Banner = (state = {}, action) => { switch (action.type) { case HOTSHOW_BANNER: let subjects = action.data; let data = subjects.slice(0, 5);// 前五个 return Object.assign( {} , state , { data : data }); default: return state; } } export const fetchLoading = (state = {}, action) => { switch (action.type) { case HOTSHOW_FETCH: return Object.assign( {} , state , { data : action.bool }); default: return state; } } api 数据请求 /** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-16 08:34:36 * @modify date 2017-05-16 08:34:36 * @desc [description] */ //const hotshow = 'https://api.douban.com/v2/movie/in_theaters'; // const sonshow = 'https://api.douban.com/v2/movie/coming_soon'; // const usshow = 'https://api.douban.com/v2/movie/us_box'; // const nearcinemas = 'http://m.maoyan.com/cinemas.json'; const hotshow = 'http://192.168.×.9:8080/weixin/hotshow.json'; const sonshow = 'http://192.168.×.9:8080/weixin/sonshow.json'; const usshow = 'http://192.168.×.9:8080/weixin/usshow.json'; const nearcinemas = 'http://192.168.×.9:8080/weixin/nearcinemas.json'; import { initHotshow, fetchLoading } from '../actions/hotshow-action'; export function hotshowFetch(action) { return (dispatch) => { fetch(hotshow).then(res => res.json()) .then(json => { dispatch(action(json)); dispatch(fetchLoading(false)); }).catch(msg => console.log('hotshowList-err '+ msg)); } } containers\hotshow\index /** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-17 10:44:56 * @modify date 2017-05-17 10:44:56 * @desc [description] */ import React, { Component } from 'react'; import { View, ScrollView } from 'react-native'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { size } from '../../util/style'; import HotShowList from './hotshow-list'; import Loading from '../../compoments/comm/loading' import { fetchLoading, initHotshow } from '../../actions/hotshow-action'; class hotshow extends Component { componentWillMount() { let _that = this; let time = setTimeout(function(){ //请求数据 _that.props.initHotshowAction(); clearTimeout(time); }, 1500); } render() { return (<View > {this.props.fetchbool ? <Loading/> : <HotShowList/> } </View>); } } function mapStateToProps(state) { return { fetchbool: state.fetchload.data, hotshows: state.hotshows.data } } function macthDispatchToProps(dispatch) { return bindActionCreators({ initHotshowAction: initHotshow, }, dispatch); } export default connect(mapStateToProps, macthDispatchToProps)(hotshow); BannerCtn 轮播用的swiper 插件, 但swiper加入 listview 有个bug就是图片不显示,结尾做答import React, { Component } from 'react'; import { Text, StyleSheet, View, Image } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Swiper from 'react-native-swiper'; import { addBanner } from '../../actions/hotshow-action'; import { size } from '../../util/style'; class BannerCtn extends Component { render() { let data = this.props.banner.data; return ( <View style={{height: 200}}> { data !== undefined ? <Swiper height={200} autoplay={true}> { data.map((item, i) => { return ( <View key={i} style={{flex: 1, height:200}}> <Image style={{flex: 1}} resizeMode='cover' source={{uri: item.images.large}}/> <Text style={style.title}> {item.title} </Text> </View>) }) } </Swiper>: <Text>loading</Text> } </View> ); } } function mapStateToProps(state) { return { banner: state.banner } } let style = StyleSheet.create({ title: { position: 'absolute', width: size.width, bottom: 0, color: '#ffffff', textAlign: 'right', backgroundColor: 'rgba(230,69,51,0.25)' } }) export default connect(mapStateToProps)(BannerCtn);hotshow-list import React, { Component } from 'react'; import { Text, View, ListView, StyleSheet } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { addBanner } from '../../actions/hotshow-action'; import Loading from '../../compoments/comm/loading'; import Item from '../../compoments/hotshow/item'; import Banner from './banner-ctn'; import Foot from '../../compoments/comm/foot'; class HotShowList extends Component { constructor(props) { super(props); } componentWillMount() { //顶部轮播 let { hotshows, bannerAction } = this.props; let subs = hotshows.data.subjects; bannerAction(subs); } _renderList() { let { hotshows } = this.props; let ary = hotshows.data.subjects, subsAry = [], row=[]; row.push(<Banner/>); for(let i = 0, item; item = ary[i++];) { //一行两个 subsAry.push( <Item key={i} rank={i} data={item}/> ); if(subsAry.length == 2) { row.push(subsAry); subsAry = []; } } return row; } _renderRow(data) { return( <View style={{marginTop: 1, flexWrap:'wrap', flexDirection: 'row', justifyContent: 'space-between'}}>{data}</View> ); } render() { let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); let data = this._renderList(); this.state = { dataSource: ds.cloneWithRows(data), } //removeClippedSubviews 处理 banner 图片不显示 return ( <View> <View> <ListView removeClippedSubviews={false} dataSource={this.state.dataSource} renderRow={this._renderRow}/> </View> <Foot/> </View> ); } } function mapStateToProps(state) { return { hotshows: state.hotshows } } function macthDispatchToProps(dispatch) { return bindActionCreators({ bannerAction: addBanner}, dispatch); } let style = StyleSheet.create({ listbox: { marginBottom: 45, } }); export default connect(mapStateToProps, macthDispatchToProps)(HotShowList);剩下 便是foot tab 和单个item的编写 /** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-19 08:38:19 * @modify date 2017-05-19 08:38:19 * @desc [description] */ import React, { Component } from 'react'; import { Text, View, Image, StyleSheet } from 'react-native'; import { size } from '../../util/style'; const width = size.width/2-0.2; class item extends Component{ render() { let data = this.props.data; return( <View style={style.box}> <Image resizeMode='cover' style={style.avatar} source={{uri:data.images.large}}/> <View style={style.rank}> <Text style={style.rankTxt}>Top{this.props.rank}</Text> </View> <View style={style.msgbox}> <View style={style.msgrow}> <Text style={style.msgrowl}>{data.title}</Text> <Text style={style.msgrowr}>评分:{data.rating.average}</Text> </View> <View style={style.msgrow}> <Text style={style.msgrowl}> {data.genres.map((item, i)=> { if(i > 1) return; i == 1 ? null : item += ','; return item; })} </Text> <Text style={style.msgrowr}>观影人数:{data.collect_count}</Text> </View> </View> </View> ); } } let style = StyleSheet.create({ box: { width: width, paddingBottom: 1 }, avatar: { flex: 1, height: 260, }, rank: { position: 'absolute', top: 0, left: 0, backgroundColor: 'rgba(255,164,51,0.6)', paddingVertical: 1, paddingHorizontal: 3, borderBottomRightRadius: 4 }, rankTxt: { fontSize: 12, color: '#ffffff' }, msgbox: { position: 'absolute', bottom: 1, width: width, paddingHorizontal: 2, backgroundColor: 'rgba(230,69,51,0.5)', }, msgrow: { flex: 1, flexDirection: 'row', justifyContent: 'space-between', }, msgrowl: { fontSize: 12, color: '#ffffff' }, msgrowr: { fontSize: 13, color: '#ffffff' } }); module.exports = item; 到此一个react-native 配合redux 编程首屏显示就大体完成了, Swiper Image 在ListView不显示在,解决如下,测试手机微android 4.4.4,有把react-native升级为0.44.2 亲测无效 constructor(props) { super(props); this.state={ visibleSwiper: false } } render() { let data = this.props.banner.data; return ( <View style={{height: 200}}> { this.state.visibleSwiper ? <Swiper/>: <Text>LOADING</Text> } </View> ); } componentDidMount() { let time = setTimeout(() => { this.setState({ visibleSwiper: true }); clearTimeout(time); }, 200); } 关于初始ajax数据,可以在create store的时候获取数据构建初始状态,也可以在ComponentDidMount的时候去执行action发ajax, 上文写错了,github 纠正 github: https://github.com/helloworld3q3q/react-native-redux-demo 有需要的交流的可以加个好友
写这个么个题目好像,有点晦涩。直接来张gif图就知道,主要是是当手指触摸到元素时,元素的初始位置变成了left:0, top:0; js 设置监听事件 都是 一样的套路 touch.addEventListener('touchstart', function(evtend) {}, false); touch.addEventListener('touchmove', function(evtend) {}, false); touch.addEventListener('touchend', function(evtend) {}, false); 在事件里添加 进行位置的动态改变,首先就需要获得手指的初始位置,手指移动的位置,手机touch 当前元素的位置; var endpst = {}, //结束位置 elepst= {}, start={}; //初始位置 item.addEventListener('touchstart', function(event) { if(event.targetTouches.length > 1) return; var offset, touch = event.targetTouches[0], style = window.getComputedStyle(this, null);// 当前元素的css 样式 start = {x: touch.clientX, y: touch.clientY}; elepst = { x: parseFloat(style.getPropertyValue('left')), y: parseFloat(style.getPropertyValue('top')), }; }, false); item.addEventListener('touchmove', function(event) { if(event.targetTouches.length > 1) return; var touch = evtmv.targetTouches[0], offset = { x : touch.clientX - start.x, y : touch.clientY - start.y }; //手移动的 偏移位置 endpst['left'] = elepst.x + offset.x; endpst['top'] = .y + offset.y; this.style.left = endpst.left+ 'px'; this.style.top = endpst.top + 'px'; }, false); //移动结束 item.addEventListener('touchend', function(e) { if(e.targetTouches.length > 1) return; }, false); 这样写在安卓和电脑上是没问题的, 但到了水果上状况如上图,操作后,在第一次移动时,位置变成0,再次操作位置就是和手机的运动是一样的。 分析,初始化页面时,position,是读取css文件渲染的况且是百分比定位。第二次移动时,position在元素属性上 由此 最直接办法就是 给每个需要移动的 元素 用js 设置位置。 var ary = document.querySelectorAll(eles) for (var k = start , itm; itm = ary[k++];) { var left = itm.getBoundingClientRect().left, top = itm.getBoundingClientRect().top; itm.style.cssText = 'top:' + top + 'px;left:' + left +'px'; } 如此 当在水果上第一次触摸拖动元素时 ,位置就不会为0。 getBoundingClientRect有个尴尬的地方就是,eles 元素和父类元素 即所在分页吧,必须可见,才有值否则为0; 有需要的交流的可以加个好友
想学习点新东西就是开手写,就写了个简单的实现,利用node实现一个博客。主要的内容就在首页也能看到了。 话不多说,expressjs怎么创建项目选择ejs模板,之前的文章都写过了。 首先从用户注册开始,有了用户才能根据id查找文章。 <%- include("../layouts/header", {cssAry: ['/style/reg/index.css']}) %> <div class="body flex center"> <div class="index-main"> <div class="index-header"> <h1 class="logo"></h1> <p class="describe">一条大河波浪宽</p> </div> <div class="form-name flex center"> <a class="form-active" href="/reg">注册</a> <a href="/login">登录</a> </div> <div class="index-form"> <form method="post" action="/reg"> <input type="hidden" name="_csrf" value="<%= csrf %>"> <div class="form-item"> <input type="input" name="email" placeholder="邮箱"/> </div> <div class="form-item"> <input type="password" name="pwd" placeholder="密码(大于六位)"> </div> <div class="form-item"> <input type="password" name="repeatpwd" placeholder="确定密码"> </div> <div class="button-item"> <input class="btn btn-sure" type="submit" value="注册"> </div> </form> </div> </div> </div> <%- include("../layouts/footer", {jsAry: []}) %>include 传递参数时就把需要的css 和js 传递到head 和foot模块里 header.ejs <!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="renderer" content="webkit"> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <link rel="shortcut icon" type="image/x-icon" href="/images/favicon3.ico"> <meta name="csrf-token" content="<%= csrf %>"> <title><%= title %></title> <link rel='stylesheet' href='/style/main.css' /> <% for(var i = 0, item; item = cssAry[i++]; ) {%> <link rel='stylesheet' href='<%= item %>'/> <% } %> </head> <body>footer.ejs <script type="text/javascript" src="http://lib.sinaapp.com/js/jquery/2.2.4/jquery-2.2.4.min.js"></script> <script type="text/javascript" src="/javascripts/main.js"></script> <% for(var i = 0, item; item = jsAry[i++]; ) {%> <script type="text/javascript" src="/<%= item %>"></script> <% } %> </body> </html>route路由,编写对应再到reg.ejs的页面 //注册 router.get('/reg', function(req, res) { res.render('reg/index', { title: '注册', pwd_err: req.session.pwd_err }); }); //注册提交 router.post('/reg', function(req, res) { if(!util.isEqual(req.body.pwd, req.body.repeatpwd)) { req.session.pwd_err = true; return res.redirect('/reg'); } let pwd = util.mix(req.body.pwd); let User = { email: req.body.email, pwd: pwd } userDao.setUser(User, function(err){ if (err) { return res.redirect('/reg'); } req.session.email = req.body.email; return res.redirect('/login'); }); }); 同时需要user和数据库的交互创建userDao,并且继承自baseDao import { ObjectID } from 'mongodb'; import connect from '../../config/connect'; import BaseDao from './BaseDao'; import User from '../models/User'; import util from '../lib/util'; //继承Dao class UserDao extends BaseDao { //获取用户信息 登录等 getUser (user, callback) { this.query(user, 'users', callback); } //用户注册 玩家 和管理员 saveUser (user, col, callback) { let _that = this; let model = Object.assign(JSON.parse(User), user); this.query(model, col, function(err, u) { //用户已经存在 if (u !== null) { err = 'notnull'; return callback(err); } _that.save(model, col, callback); }); }; //普通用户注册 setUser (user, callback) { this.saveUser(user, 'users', callback); } updateUserOne(userUpdate, callback) { let ID = {}; if (util.isString(userUpdate.id) ) { ID = {_id: new ObjectID(userUpdate.id)}; } else { ID = userUpdate.id; } this.updateOne('users', ID, {$set: userUpdate.field}, callback); } // 加入postId updatePostId(userPost, callback) { let _that = this; this.updatePromise(userPost.id, 'postId').then(function(result) { let postId = result.data || []; postId.push(userPost.postId); let userUpdate = { id: result.id, field: { postId: postId } }; _that.updateUserOne(userUpdate, callback); }, function(err) { return err; }); } updatePromise(id, key) { let _that = this; let ID = {_id: new ObjectID(id)}; return new Promise(function(resolve, reject) { _that.query(ID, 'users', function(err, rtn) { if (err) { reject(err); } else { let result = {data: rtn[key], id: ID}; resolve(result); } }); }); } } module.exports = UserDao; import connect from '../../config/connect'; import { ObjectID } from 'mongodb'; class BaseDao { //查询 field查询字段, col 集合或表 query(field, col, callback) { connect.open(function(err, db) { if (err) { return callback(err); } //要查找的集合 db.collection(col, function(err, collection) { //要查找的字段 collection.findOne(field, function(err, result){ connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } //查找排序 query={field: field, orderby: orderby, } querySort(query, col, callback) { connect.open(function(err, db) { if (err) return callback(err); db.collection(col, function(err, collection) { if (query.limit) { collection.find(query.field).sort(query.orderby).limit(query.limit).toArray(function(err, result) { connect.close(); if (err) return callback(err); callback(null, result); }); } else { collection.find(query.field).sort(query.orderby).toArray(function(err, result) { connect.close(); if (err) return callback(err); callback(null, result); }); } }); }); } //查询首页全部 queryAll(col, query, callback) { connect.open(function(err, db) { if( err ) return callback(err); db.collection(col, function(err, collection) { collection.find().sort(query.sort).limit(query.limit).toArray(function(err, result) { connect.close(); if (err) return callback(err); callback(null, result); }); }); }); } //保存 新建 save(field, col, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { //if(field._id) delete field._id; collection.insert(field, { safe: true }, function(err, result) { connect.close(); if (err) { return callback(err); } callback(null, result); }); }); }); } //只修改一个 根据Id查找 updateOne(col, id, updateField, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { collection.updateOne(id, updateField, function(err, result) { connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } //更新 updates(col, field, updateFields, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { collection.update(field, updateFields, function(err, result) { connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } //删除一个表 removeOne(col, field, callback) { connect.open(function(err, db) { if (err) { return callback(err); } db.collection(col, function(err, collection) { collection.removeOne(field, function(err, result) { connect.close(); if (err) { return callback(err); } //成功 callback(null, result); }); }); }); } } module.exports = BaseDao; 工具类 import crypto from 'crypto'; class util { static isNull(str) { if (str.trim() == null || str.trim() == '') { //为空 return true; } } //密码加密混淆 static mix(str) { let sha1 = crypto.createHash('sha1'); return sha1.update(str).digest('hex'); } //字符串是否相等 static isEqual(str, str2) { if(str.trim() === str2.trim()) { return true; } } //未登录 1 static notLogin = function(req, res) { if(!req.session.user){ req.flash('isLogin', '1'); res.redirect('/login'); return true; } return false; } //已经登录 0 static login(req, res) { if(req.session.user){ if(req.session.user['_id'] === req.params._id) { req.flash('isLogin', '0'); return true; } else { return false; } } return false; } //json里出去空值 static mergeJson(basedata, newdata) { let merge = {}; for (let key1 in basedata) { merge[key1] = basedata[key1]; } for (let key in newdata) { if ( newdata[key] !== '' && newdata !== null) { merge[key] = newdata[key]; } } return merge; } //字符串 static isString(str) { if(typeof str === 'string' && str.constructor === String ) return true; } //图片路径 static getPath(str, aim) { var reg = new RegExp(aim + '\\/(\\S*)'); return str.match(reg)[1]; } //时间格式化 static format(str, mat) { let d, date = new Date(str), year = date.getFullYear(), month = date.getMonth() + 1, day = date.getDate(); switch(mat){ case 'year': d = year break; case 'month': d = month; break case 'day': d = day; default: d = year+'-'+month+'-'+day; } return d; } } module.exports = util; 做到此发觉痛点是和数据库mongodb交互用mongodb = require('mongodb'),很别扭,应该用mongoose,不过既然都这么用了,就都应该学习下。 接下来进入登录界面 登录的路由 //进入登录页面 router.get('/login', function(req, res) { let error_name = req.flash('error_name'); res.render('login/index', { title: '登录', error_name: req.flash('error_name'), error_pwd: req.flash('error_pwd'), isLogin: req.flash('isLogin'), email: req.session.email, }); }); //登录 router.post('/login', function(req, res) { if (util.isNull(req.body.email)) { req.flash('error_name', 'error_name'); return res.redirect('/login'); } if (util.isNull(req.body.pwd)) { req.flash('error_pwd', false); return res.redirect('/login'); } //用户查找 let pwd = util.mix(req.body.pwd); let User = { email: req.body.email, pwd: pwd } userDao.getUser(User, function(err, result) { if (err) { return res.redirect('/login'); } if (result === null) { return res.redirect('/reg'); } if (req.session.user && req.session.user['email'] === req.params.email) { delete req.session.user; } req.session.user = { _id: result._id, email: result.email, }; return res.redirect('/personal/'+ result._id); }); }); <%- include("../layouts/header", {cssAry: ['/style/login/index.css']}) %> <div class="body flex center"> <div class="index-main"> <div class="index-header"> <h1 class="logo"></h1> <p class="describe">一条大河波浪宽</p> </div> <div class="form-name flex center"> <a class="form-active" href="/login">登录</a> <a href="/reg">注册</a> </div> <div class="index-form"> <form method="post" action="/login"> <input type="hidden" name="_csrf" value="<%= csrf %>"> <div class="form-item"> <input type="input" name="email" placeholder="邮箱"/> </div> <div class="form-item"> <input type="password" name="pwd" placeholder="请输入密码"> </div> <div class="button-item"> <input class="btn btn-sure" type="submit" value="注册"> </div> </form> </div> </div> </div> <%- include("../layouts/footer", {jsAry: []}) %> 登录成功后进入个人中心页面personal,根据id进入同时用到 import async from 'async';当然不用的话可以用es6的promise(resolve,reject), resolve就是成功后的参数传递,reject就是错误异常时。 //个人中心 router.get('/personal/:_id', function(req, res) { let ID = req.params._id; let boo = util.login(req, res); async.waterfall([function(callback){ userDao.getUser({_id: new ObjectID(ID)}, function(err, result) { if(err) return false; callback(null, result) }); }, function(arg1, callback) { let options = { field: {id: ID}, orderby: {time: -1} } postDao.queryPostSort(options, function(err, rtn) { let json = { title: '个人中心', id: ID, usermsg: arg1.usermsg, postId: arg1.postId, postSize: arg1['postId'].length, postList: rtn, login: boo } callback(null, json); }); }], function(err, result) { res.render('personal/index', result); }); }); 用户主要的字段信息有如下 let user = { "email" : "", "pwd" : "", "postId" :[], "usermsg" : { "username" : "", "userwork" : "", "userdegree" : "", "userarea" : "", "usersex" : "", "userintr" : "", "userhead": "", }, "postclass":{ 0: "博文" }, //作者文章分类 } 当点击资料编辑时,进入资料编辑页面 //个人资料修改 router.get('/personal/edit/:_id', function(req, res) { let boo = util.notLogin(req, res); if(boo) return false; req.session.article = false; let ID = req.session.user['_id']; async.parallel([function(callback) { userDao.getUser({_id: new ObjectID(ID)}, function(err, result) { if(err) return false; callback(null, result.usermsg); }); }, function(callback) { //头像上传 mkdirs('/var/www/near/public/images/users/' + req.session.user['_id'] + '/avatar/', '0777'); callback(null, 'ok'); }], function(err, results){ res.render('personal/edit', { title: '个人资料修改', id: ID, usermsg: results[0], login: true }); }); }); router.post('/personal/edit/:_id', function (req, res) { let boo = util.notLogin(req, res); if(boo) return false; if(req.session.user['_id'] !== req.params._id) return res.redirect('login'); let Upload = upload.single("userhead"); //头像图片上传,未把头像图片单独做出来,选中后ajax提交 Upload(req, res, function(err) { if (err) { return ; } let id = req.session.user['_id']; userDao.updatePromise(id, 'usermsg').then(function(result) { let data = util.mergeJson(result.data, req.body); data['userhead'] = req.session.postcover; req.session['postcover'] = null; let userUpdate = { id: result.id, field: {usermsg: data} }; userDao.updateUserOne(userUpdate, function() { return res.redirect('/personal/'+ id); }) }, function(err) { return res.redirect('/reg'); }); }); import upload from '../lib/multer.config'; import multer from 'multer'; import fs from 'fs' ; var storage = multer.diskStorage({ destination: function (req, file, cb) { let newDestination = '/var/www/near/public/images/users/' + req.session.user['_id']; req.session.article ? (newDestination += '/posts/') : (newDestination += '/avatar/'); cb(null, newDestination); }, filename: function (req, file, cb) { let originalname = file.originalname, start = originalname.lastIndexOf('.'), len = originalname.length, type = originalname.substring(start+1, len), filename = Date.now() + '.' + type; req.session.postcover = filename; cb(null, filename); } }); let upload = multer( { limits: { fieldNameSize: 100, fileSize: 60000000 }, storage: storage } ); module.exports = upload;ejs<%- include("../layouts/header", {cssAry: ['/style/personal/edit.css']}) %> <div class="body"> <%- include("../layouts/main_head", {publishBtn: true}) %> <div class="main"> <form action="/personal/edit/<%= id %>?_csrf=<%= csrf %>" method="POST" id="userform" enctype="multipart/form-data"> <div class="form-item useravatar" id="useravatar"> <img src="/<%= usermsg.userhead ? ('images/users/' + id +'/avatar/' + usermsg.userhead) : 'images/default_avatar.jpg' %>"> <em class="cover" id="cover"></em> <input class="input-file item-val" type="file" id="input-file" name="userhead"> </div> <div class="form-item"> <div class="flex center"> <span class="profile-name"><%= usermsg.username || "暂未填写" %></span> <a class="modify-btn" data-type="center" href="javascript:;">修改</a> </div> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请填写昵称" name="username"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> <div class="form-item flex"> <div class="item-name">职业</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userwork || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请输入职业" name="userwork"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">学位</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userdegree || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请输入学位" name="userdegree"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">地区</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userarea || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <input class="modify-name item-val" type="text" placeholder="请填写地区" name="userarea"> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">性别</div> <div> <p class="modify-msg flex"> <span class="modify-file"><% if(usermsg.usersex) { %> <%= usermsg.usersex == 0 ? "女" : "男" %> <% } else { %> <%= "暂未填写" %> <% } %></span> <a class="modify-btn" data-type="radio" href="javascript:;">修改</a> </p> <div class="modify-content flex-center-h"> <label><input class="item-val" type="radio" name="usersex" value="1"> <em>男</em></label> <label><input class="item-val" type="radio" name="usersex" value="0"> <em>女</em></label> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> <div class="form-item flex"> <div class="item-name">介绍自己</div> <div> <p class="modify-msg flex"> <span class="modify-file"><%= usermsg.userintr || "暂未填写" %></span> <a class="modify-btn" data-type="com" href="javascript:;">修改</a> </p> <div class="modify-content modify-content-ta"> <textarea class="modify-name item-val" placeholder="请简单的介绍自己" name="userintr"></textarea> <a href="javascript:;" class="btn btn-cancel">取消</a> <a href="javascript:;" class="btn btn-sure">确定</a> </div> </div> </div> </form> </div> </div> <%- include("../layouts/footer", {jsAry: ['javascripts/personal/edit.js']}) %> 目录结构 有需要的交流的可以加个好友
表单提交时 form submit 直接就可以提交了,但是了防止跨站攻击,都可以加入CSRF来防御。 node下的配置 var csrf = require('csurf'); app.use(csrf()); app.use(function(req, res, next){ let _csrf = req.csrfToken(); res.locals.csrf = _csrf; res.cookie('XSRF-TOKEN', _csrf); return next(); });页面 <form id="localimage" action="/login" method="POST"> <input id="csrf" type="hidden" name="_csrf" value="<%= csrf %>"> <input id="test" name="test" class="test" type="text"> </form> 这么写当然没问题,但是当上传文件就不行了。 需要设置表单的enctype属性, 默认enctype 是对所有字符进行编码了的,同时csrf值就不能放到input 进行提交,从csrf源码来看我们传输csrf值可以放在以下几个地方 <form id="localimage" action="/login?_csrf=<%= csrf %>" method="POST" enctype="multipart/form-data"> <input id="imguploadinput" name="upfile" class="imguploadinput" type="file" accept="image/jpeg,image/gif,image/png,image/bmp"> </form>ajax异步提交,也是如此,但文件时也 new FormData 提交的,csrf 放在FormData 里也不行。最好的就是放在请求头里 xhr.setRequestHeader('csrf-token', _csrf); 在ueditor里就很容易做了,ueditor里 是有ajax 方法提交的,但是没有支持file的,使用ue的方法默认就对表单信息进行了编码 可以将此方法修改一下就行了 addEvent(imguploadinput, 'change', function() { // var localImage = document.getElementById('localimage'); // localImage.submit(); var ajax = UE.ajax; // 图片 var file = document.getElementById('imguploadinput').files[0]; var form = new FormData(); form.append('file', file); ajax.request('/ueditor/ue?action=uploadimage', { method: 'POST', timeout: 10000, async: true, data: form, _csrf: csrf.value, onsuccess: function ( xhr ) { console.log( xhr.responseText ); }, //请求失败或者超时后的回调。 onerror: function ( xhr ) { alert( 'Ajax请求失败' ); } }); }); ueditor 可以修改如下 成功提交后 返回如下 否则 没有设置header 时 如果设置了 xhr.setRequestHeader("Content-Type","multipart/form-data"); 也是不行的 写死后就出现找不到Boundary(用户分割不同的字段)的错误的错误 注意 使用jquery 的ajax 提交时 ,contentType 需要设置为false contentType:false 有需要的交流的可以加个好友
每一个程序员都有一颗全栈的心,node和es6满足了尤其是前端的憧憬。 node下用express框架,实现一个简单的mvc。当然用es6编程就涉及到es6到es5的转换。即使是node6 对es6实现了百分之九十四的支持也有那么一点没有实现,比如import等,所有就需要转换,用babel 安装babel npm install babel-core -g 或者 npm install --save-dev babel-core 在安装 npm install --save-dev babel-preset-es2015 当然有babel-preset-es2016 但是 使用 2016是 运行babel-node 就有有错误 improt 不支持 还是老实的用2015 需要在目录下面被子.babelrc 文件 { "presets": ["es2015", "stage-0"] }stage-0 有 0, 1, 2, 3 。stage-0包含了后面3个 class Base { base() { console.log(23); }; } module.exports = Base; import Base from './Base'; class app extends Base { son () { this.base() } } var a = new app(); a.son(); 如此就能正确输出了,当然最好需要转换成es5 运行命令 babel babel ./src --out-dir ./core 也可以放到 package.json 里 "scripts": { "build": "babel --watch=./src --out-dir ./core", "start": "node ./bin/www" },运行npm run build 这样做是很费劲的要是有文件新建或者修改,就要再次运行,最好的还是加入gulp对文件进行监听就好了,自动转换。 babel也有watch命令 测试发现对文件 目录不起作用,需要准的文件,如 babel --watch=./src/test.js --out-dir ./core 开发需要用到的工具 "devDependencies": { "babel-cli": "^6.22.2", "babel-preset-es2016": "^6.22.0", "babel-preset-stage-0": "^6.22.0", "browserify": "^14.0.0", "gulp": "^3.9.1", "gulp-babel": "^6.1.2", "gulp-plumber": "^1.1.0", "gulp-sourcemaps": "^2.4.0", "gulp-streamify": "^1.0.2", "gulp-strip-comments": "^2.4.3", "gulp-watch": "^4.3.11", "vinyl-source-stream": "^1.1.0" } var gulp = require('gulp'), babel = require('gulp-babel'), watch = require('gulp-watch'), //监听 plumber = require('gulp-plumber'), //错误管理 提示 sourcemaps = require('gulp-sourcemaps'), strip = require('gulp-strip-comments'), //删除注释 streamify = require('gulp-streamify'), //只支持 buffer 的插件直接处理 stream gulp 执行的 path = { src: { js: 'src/**/*.js' }, dist: { js: "core/" } }; gulp.task('6to5', function () { gulp.src(path.src.js) // 多个文件目录 参数为数组 .pipe(watch(path.src.js)) .pipe(plumber()) .pipe(sourcemaps.init()) .pipe(strip()) //去除注释 .pipe(streamify(babel())) .pipe(sourcemaps.write({addComment: false})) .pipe(plumber.stop()) .pipe(gulp.dest(path.dist.js)); }); 运行gulp watchnode gulp.task('watchnode', ['6to5'], function (){ gulp.watch([path.src.js], [babel]); }); 生成对应文件如下: 这样就可以畅爽的写es6了 BaseDao import connect from '../../config/connect'; class BaseDao { //查询 query(field, col, callback) { connect.open(function(err, db) { }); } //保存 新建 save(field, col, callback) { connect.open(function(err, db) { }); } } module.exports = BaseDao; 对用户操作 UserDaoimport nodeUtil from 'util'; import connect from '../../config/connect'; import BaseDao from './BaseDao'; import user from '../models/User'; //继承Dao class UserDao extends BaseDao { //获取用户信息 登录等 getUser(user, callback) { this.query(user, 'users', callback); } //普通用户注册 setUser (user, callback) { this.saveUser(user, 'users', callback); } } module.exports = UserDao; route 路由 routes.js import express, { Router } from 'express'; import csurf from 'csurf'; import util from '../lib/util'; import UserDao from '../dao/UserDao'; const router = Router(); const userDao = new UserDao(); //实例化UserDao router.post('/reg', function(req, res) { let pwd = util.mix(req.body.pwd); let User = { email: req.body.name, pwd: pwd } userDao.setUser(User, function(err, user){ console.log(err); }); }); 转换后的BaseDao 有需要的交流的可以加个好友
前端开发都需要对css ,js 打包压缩,less 编译,gulp简单的风格,可以完美的完成这项任务。 首先需要安装gulp npm install gulp -g npm install gulp --save-dev 安装gulp 需要的插件 如下 npm install gulp-*** --save-dev var uglify = require('gulp-uglify'); //js混淆 min var less = require('gulp-less'); // less 编译 var sourcemaps = require('gulp-sourcemaps'); //生成sourcemap文件 方便less 文件关系 var cssmin = require('gulp-minify-css') // css min var livereload = require('gulp-livereload'); // 自动刷新 免除f5 项目根目录下新建 gulifile.js 文件 首先 需要对less 编译 压缩 //定义一个guleLess任务(自定义任务名称) gulp.task('guleLess', function () { gulp.src('public/src/less/**/*.less') //该任务针对的文件 less .pipe(sourcemaps.init()) // less map 初始化 .pipe(streamify(less())) //该任务调用的模块 .pipe(cssmin()) //css 缩写 .pipe(sourcemaps.write({addComment: false})) .pipe(gulp.dest('public/style')); //将会在src/css下生成index.css }); 对应目录,如下 addComment: false 生产的css ,或者js 里是否有描述 gulp 启动 和 代码变化监听 //监听文件变化 gulp.task('watchs', function() { livereload.listen(); // 浏览器刷新 gulp.watch('public/src/*', ['guleLess', 'javascript']); }); // cmd gulp gulp.task('default',['watchs', 'guleLess', 'javascript']); js 压缩混淆 gulp.task('javascript', function() { gulp.src('public/src/javascripts/**/*.js') .pipe(sourcemaps.init()) .pipe(uglify()) .pipe(sourcemaps.write()) .pipe(gulp.dest('public/javascripts')); }); 当然可以把 要做webapp 时 就可以把 js 全部压缩到一个js里,同时需要gulp和browserify结合,添加如下包 var browserify = require('browserify'); var source = require('vinyl-source-stream'); //将Browserify的bundle()的输出转换为Gulp可用的一种虚拟文件格式流 var streamify = require('gulp-streamify'); //只支持 buffer 的插件直接处理 stream gulp.task('javascript', function() { var b = browserify(); //文件路径 files = ['public/src/javascripts/reg/index.js', 'public/src/javascripts/login/index.js']; files.forEach(function(item){ b.add(item); }); b.bundle().pipe(source('public/javascripts/packages.js')) .pipe(streamify(uglify())) .pipe(gulp.dest('./')); }); d.add 可以换成require 就是 压缩 需要的 模块插件到一个js里 修改如下 files = ['jquery']; files.forEach(function(item){ b.require(item); }); 新年第一天以一篇博结束,祝大家新年快。 有需要的交流的可以加个好友
快过年了,自己写了个android批量发送短信,短信前面添加人名,用来节日发短信,这样别人就不知道我是批量发送的了哈哈哈,当然写的很菜,刚开始玩这个敬请指教 短信发送后去短信列表查看 首先AndroidManifest.xml配置 软件的权限和需要用到达Activity类 <uses-sdk android:minSdkVersion="22" android:targetSdkVersion="22" /> <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.SEND_SMS" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="sendmsg.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name="receiver.SMSReceiver"></receiver> <activity android:name="sendmsg.ContactListActivity"></activity> <activity android:name="sendmsg.SendMsgActivity"></activity> </application> 创建联系人模型需要用的联系人的字段 package model; public class ContactBean { private int contactId; private String desplayName; private String phoneNum; private String sortKey; private Long phoneId; private String LookUpKey; private int selected = 0; private String formattedNumber; private String pinyin; public int getContactId() { return contactId; } public void setContactId(int contactId) { this.contactId = contactId; } public String getDesplayName() { return desplayName; } public void setDesplayName(String desplayName) { this.desplayName = desplayName; } public String getPhoneNum() { return phoneNum; } public void setPhoneNum(String phoneNum) { this.phoneNum = phoneNum; } public String getSortKey() { return sortKey; } public void setSortKey(String sortKey) { this.sortKey = sortKey; } public Long getPhoneId() { return phoneId; } public void setPhoneId(Long phoneId) { this.phoneId = phoneId; } public String getLookUpKey() { return LookUpKey; } public void setLookUpKey(String lookUpKey) { LookUpKey = lookUpKey; } public int getSelected() { return selected; } public void setSelected(int selected) { this.selected = selected; } public String getFormattedNumber() { return formattedNumber; } public void setFormattedNumber(String formattedNumber) { this.formattedNumber = formattedNumber; } public String getPinyin() { return pinyin; } public void setPinyin(String pinyin) { this.pinyin = pinyin; } } 联系人listview界面,全选于取消,List<ContactBean> list,存储联系人信息, 单list不为空时传递给下一个Activity, intentSendMsg.putExtra("user", userList.toString()); 只接收字符串 public class ContactListActivity extends Activity { private ContactListAdapter adapter; private ListView contactList; private List<ContactBean> list; private AsyncQueryHandler asyncQueryHandler; private Boolean cancel = true; private Map<Integer, ContactBean> contactIdMap = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.contact_list_view); contactList = (ListView) findViewById(R.id.contact_list); final Button selectAll = (Button) findViewById(R.id.select_all); Button sureBtn = (Button) findViewById(R.id.select_sure); asyncQueryHandler = new MyAsyncQueryHandler(getContentResolver()); init(); final Intent intentSendMsg = new Intent(this, SendMsgActivity.class); selectAll.setOnClickListener(new OnClickListener() { public void onClick(View v) { int len = list.size(); if (Boolean.valueOf(cancel)) { //全选 cancel = false; selectAll.setText("取消"); for (int i = 0; i < len; i++) { ContactListAdapter.getIsSelected().put(i, true); } } else { // 取消 cancel = true; selectAll.setText("全选"); for (int i = 0; i < len; i++) { ContactListAdapter.getIsSelected().put(i, false); } } adapter.notifyDataSetChanged(); } }); sureBtn.setOnClickListener(new OnClickListener() { private List<String> userList = new ArrayList<String>(); @Override public void onClick(View v) { Set<Entry<Integer, Boolean>> iterator = ContactListAdapter.getIsSelected().entrySet(); for (Entry<Integer, Boolean> entry : iterator) { if (entry.getValue()) { int key = entry.getKey(); ContactBean conBean = list.get(key); userList.add(conBean.getDesplayName()+":"+conBean.getPhoneNum()); } } if (!userList.isEmpty()) { intentSendMsg.putExtra("user", userList.toString()); startActivity(intentSendMsg); userList.clear(); } } }); } private void init() { Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI; String[] projection = { ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.DATA1, "sort_key", ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.PHOTO_ID, ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY }; asyncQueryHandler.startQuery(0, null, uri, projection, null, null, "sort_key COLLATE LOCALIZED asc"); } private class MyAsyncQueryHandler extends AsyncQueryHandler { public MyAsyncQueryHandler(ContentResolver cr) { super(cr); } @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { if (cursor != null && cursor.getCount() > 0) { contactIdMap = new HashMap<Integer, ContactBean>(); list = new ArrayList<ContactBean>(); cursor.moveToFirst(); int count = cursor.getCount(); for (int i = 0; i < count; i++) { cursor.moveToPosition(i); String name = cursor.getString(1); String number = cursor.getString(2); String sortKey = cursor.getString(3); int contactId = cursor.getInt(4); Long photoId = cursor.getLong(5); String lookUpKey = cursor.getString(6); if (!contactIdMap.containsKey(contactId)) { ContactBean contact = new ContactBean(); contact.setDesplayName(name); contact.setPhoneNum(number); contact.setSortKey(sortKey); contact.setPhoneId(photoId); contact.setLookUpKey(lookUpKey); list.add(contact); contactIdMap.put(contactId, contact); } } if(list.size() > 0) { setAdapter(list); } } super.onQueryComplete(token, cookie, cursor); } } private void setAdapter(List<ContactBean> list) { adapter = new ContactListAdapter(this, list); //listview contactList 设置适配器 contactList.setAdapter(adapter); } } listview 的适配器, item数据 通过 getIsSelected 向下一个Activity传递数据public class ContactListAdapter extends BaseAdapter { private LayoutInflater inflater; private List<ContactBean> list; private HashMap<String, Integer> alphaIndexer; private String[] sections; private Context ctx; private static HashMap<Integer, Boolean> isSelected; @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { //找到item布局文件 //listview 里 inflate 第二个参数 可以写成null 或者 第三个参数 false convertView = inflater.inflate(R.layout.contact_list_item, parent, false); holder = new ViewHolder(); holder.alpha = (TextView) convertView.findViewById(R.id.alpha); holder.name = (TextView) convertView.findViewById(R.id.name); holder.number = (TextView) convertView.findViewById(R.id.number); holder.cb = (CheckBox) convertView.findViewById(R.id.checkUser); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } ContactBean contact = list.get(position); String name = contact.getDesplayName(); String number = contact.getPhoneNum(); holder.name.setText(name); holder.number.setText(number); String currentStr = getAlpha(contact.getSortKey()); String previewStr = (position - 1) >= 0 ? getAlpha(list.get( position - 1).getSortKey()) : ""; if (!previewStr.equals(currentStr)) { holder.alpha.setVisibility(View.VISIBLE); holder.alpha.setText(currentStr); } else { holder.alpha.setVisibility(View.GONE); } final int pst = position; holder.cb.setChecked(getIsSelected().get(position)); //单个联系人选择 holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { getIsSelected().put(pst, isChecked); } }); return convertView; } public ContactListAdapter(Context context, List<ContactBean> list) { this.ctx = context; this.inflater = LayoutInflater.from(context); //创建视图 设置上下文 this.list = list; this.alphaIndexer = new HashMap<String, Integer>(); isSelected = new HashMap<Integer, Boolean>(); for (int i = 0; i < list.size(); i++) { String name = getAlpha(list.get(i).getSortKey()); if (!alphaIndexer.containsKey(name)) { alphaIndexer.put(name, i); } getIsSelected().put(i, false); } Set<String> sectionLetters = alphaIndexer.keySet(); ArrayList<String> sectionList = new ArrayList<String>(sectionLetters); Collections.sort(sectionList); sections = new String[sectionList.size()]; sectionList.toArray(sections); } //listview item 需要展示的数据 private static class ViewHolder { TextView alpha; TextView name; TextView number; CheckBox cb; } // 字母 private String getAlpha(String str) { if (str == null) { return "#"; } if (str.trim().length() == 0) { return "#"; } char c = str.trim().substring(0, 1).charAt(0); Pattern pattern = Pattern.compile("^[A-Za-z]+$"); if (pattern.matcher(c + "").matches()) { return (c + "").toUpperCase(); } else { return "#"; } } public static HashMap<Integer, Boolean> getIsSelected() { return isSelected; } public static void setIsSelected(HashMap<Integer, Boolean> isSelected) { ContactListAdapter.isSelected = isSelected; } } 短信发送界面 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <ListView android:id="@+id/usermsg_list" android:layout_width="wrap_content" android:layout_height="170dip" android:cacheColorHint="#000000" android:divider="#887d7d" android:dividerHeight="4dip" android:fadingEdge="none" android:scrollbars="vertical" android:scrollingCache="false" android:visibility="visible"/> <ScrollView android:id="@+id/sv" android:layout_width="match_parent" android:layout_height="149dip" android:layout_below="@id/usermsg_list" android:background="@drawable/stbottom" android:paddingTop="4dip" android:orientation="vertical" > <LinearLayout android:id="@+id/sms_linear" android:layout_height="wrap_content" android:layout_width="match_parent" android:orientation="vertical"/> </ScrollView> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:layout_alignParentBottom="true" android:background="#ffffff" > <EditText android:id="@+id/edit" android:layout_width="match_parent" android:layout_height="150dip" android:layout_marginTop="1dip" android:layout_marginBottom="1dip" android:padding="5dip" android:gravity="top" android:background="@drawable/phone_border"/> <Button android:id="@+id/send_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="发送" android:background="@drawable/sendbtn" android:textColor="#ffffff"/> </LinearLayout> </RelativeLayout> 短信发送界面listview中 item 用户名和电话 删除按钮 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:id="@+id/usermsg" android:layout_height="24dip" android:layout_width="wrap_content" android:layout_marginTop="4dip" android:layout_marginBottom="4dip" android:textSize="10pt"/> <Button android:id="@+id/user_del" android:layout_width="26dip" android:layout_height="26dip" android:background="@drawable/del" android:textColor="#d88822" android:textSize="6pt" android:gravity="center" android:text="X"/> </LinearLayout> main.xml里 注册的广播接 短信发送的状态 <receiver android:name="receiver.SMSReceiver"></receiver> public class SMSReceiver extends BroadcastReceiver{ public List<String> name = new ArrayList<String>(); private int id = 0; private ScrollView sv; private Context ctx; private LinearLayout stateList; private Handler mHandler = new Handler(); public SMSReceiver(ScrollView sv, Context ctx) { this.sv = sv; this.ctx = ctx; } public int getId() { return id; } public void setId(int id) { this.id = id; } public List<String> getName() { return this.name; } public void setName(String name) { this.name.add(name); } private void setTextView(LinearLayout list) { int id = getId(); // ScrollView item 设置 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.setMargins(0, 0, 10, 10); params.gravity = Gravity.END; TextView text = new TextView(ctx); text.setText("成功发送给:" + getName().get(id)); setId(id + 1); text.setBackgroundResource(R.drawable.msg_bg); text.setPadding(5, 5, 100, 5); text.setSingleLine(false); text.setLayoutParams(params); // 添加到了LinearLayout里 list.addView(text); mHandler.post(new Runnable() { @Override public void run() { // ScrollView 短信发送成功后向下滚动 sv.fullScroll(ScrollView.FOCUS_DOWN); } }); } @Override public void onReceive(Context ctx, Intent intent) { if (stateList == null) { stateList = (LinearLayout) sv.findViewById(R.id.sms_linear); } if (intent.getAction().equals("SMS_SEND_ACTIOIN")) { switch (getResultCode()) { //发送成功 case Activity.RESULT_OK: setTextView(stateList); break; default: break; } } } } 短信发送 public class SendSMS { private static String SMS_SEND_ACTIOIN = "SMS_SEND_ACTIOIN"; private SMSReceiver SendSMSReceiver; private Context ctx; private ScrollView sv; private String SMSShort; public SendSMS(Context ctx, ScrollView sv) { this.ctx = ctx; this.sv = sv; registerReceiver(); } public String getSMSShort() { return SMSShort; } public void setSMSShort(String sMSShort) { SMSShort = sMSShort; } //注册 public void registerReceiver() { IntentFilter filter = new IntentFilter(SMS_SEND_ACTIOIN); SendSMSReceiver = new SMSReceiver(sv, ctx); ctx.registerReceiver(SendSMSReceiver, filter); } //取消注册 public void unRegisterReceiver() { if (SendSMSReceiver != null) { ctx.unregisterReceiver(SendSMSReceiver); } } public int sendSMS(String sms, List<SendItemBean> list) { int size = 0; SmsManager smsManager = SmsManager.getDefault(); Iterator<SendItemBean> it = list.iterator(); Intent sendIT = new Intent(SMS_SEND_ACTIOIN); //注册广播 PendingIntent sendPI = PendingIntent.getBroadcast(ctx, 0, sendIT, 0); //遍历电话号码 while (it.hasNext()) { SendItemBean item = it.next(); String[] user = item.getUserMsg().split(":"); String name = user[0]; String strSms = name + sms; SendSMSReceiver.setName(strSms); //短信发送 smsManager.sendTextMessage(user[1], null, strSms, sendPI, null); size = item.getId() + 1; } return size; } } 发送短信Activity界面public class SendMsgActivity extends Activity{ private static final int OVER_SUCCESS = 0; private static final int OVER_FAILUER = 1; private SendMsgAdapter adapter; private List<SendItemBean> list; private ListView userMsgList; private SendSMS sendSMS; private Thread mThread; private Button sendBtn; private EditText ed; private String SMS; //Handler ui更新, 只能在子线程中 private Handler mHandler = new Handler(){ public void handleMessage(Message msg) { //根据消息, 设置ui样式 switch (msg.what) { case OVER_SUCCESS: sendBtn.setBackgroundColor(Color.parseColor("#f90000")); sendBtn.setText("发送"); sendBtn.setClickable(true); mHandler.removeCallbacks(runnable); break; default: sendBtn.setBackgroundColor(Color.parseColor("#968e8e")); sendBtn.setText("短信批量发送中"); sendBtn.setClickable(false); break; } } }; Runnable runnable = new Runnable() { @Override public void run() { mHandler.obtainMessage(OVER_FAILUER).sendToTarget(); // 联系人数组 传递给短信发送的方法 int size = sendSMS.sendSMS(SMS, list); try { Thread.sleep(2000); if (size == list.size()) { //发送短信状态 发送成功与否的 消息 mHandler.obtainMessage(OVER_SUCCESS).sendToTarget(); } } catch (Exception e) { mHandler.obtainMessage(OVER_FAILUER).sendToTarget(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.send_msg); ed = (EditText) findViewById(R.id.edit); sendBtn = (Button) findViewById(R.id.send_btn); userMsgList = (ListView) findViewById(R.id.usermsg_list); ScrollView scrollView = (ScrollView) findViewById(R.id.sv); Intent intent = getIntent(); CharSequence userStr = intent.getStringExtra("user"); sendSMS = new SendSMS(this, scrollView); list = new ArrayList<SendItemBean>(); // 联系人数组 String[] userAry = userStr.toString().replaceAll("\\[|\\]", " ").split(","); int len = userAry.length; for (int i = 0; i < len; i++) { SendItemBean sendItemBean = new SendItemBean(); sendItemBean.setId(i); sendItemBean.setUserMsg(userAry[i]); list.add(sendItemBean); } setAdapeter(list); sendBtnClick(); } //确定发送 public void sendBtnClick() { sendBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { SMS = ed.getText().toString().trim(); if (SMS.isEmpty()) { Toast.makeText(SendMsgActivity.this, "短信不能为空", Toast.LENGTH_SHORT).show(); } else { mThread = new Thread(runnable); mThread.start(); } } }); } private void setAdapeter(List<SendItemBean> list) { adapter = new SendMsgAdapter(this, list); //listview 适配器 userMsgList.setAdapter(adapter); } @Override protected void onPause() { super.onPause(); //注册的广播销毁 退出当前activity时 sendSMS.unRegisterReceiver(); list.clear(); adapter.notifyDataSetChanged(); } } 短信发送界面 联系人listview public class SendMsgAdapter extends BaseAdapter { private List<SendItemBean> list; private LayoutInflater inflater; private Context ctx; private static String[] userItem; @Override public int getCount() { return list.size(); } @Override public Object getItem(int position) { return list.get(position); } @Override public long getItemId(int position) { return position; } public void removeItem(int position) { list.remove(position); //联系人单个删除 后 为空是 退出当前Activity if (list.size() == 0) { /*Intent intent = new Intent(); intent.setClass(ctx, ContactListActivity.class); ctx.startActivity(intent);*/ ((Activity) ctx).finish(); } this.notifyDataSetChanged(); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (holder == null) { convertView = inflater.inflate(R.layout.send_msg_item, null); holder = new ViewHolder(); holder.userMsg = (TextView) convertView.findViewById(R.id.usermsg); holder.del = (Button) convertView.findViewById(R.id.user_del); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } SendItemBean sendItemBean = list.get(position); String userMsg = sendItemBean.getUserMsg(); holder.userMsg.setText(userMsg); final int pst = position; //联系人单个删除 holder.del.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { removeItem(pst); } }); return convertView; } public SendMsgAdapter(Context context, List<SendItemBean> list) { this.ctx = context; this.inflater = LayoutInflater.from(context); this.list = list; } private static class ViewHolder { TextView userMsg; Button del; } public static String[] getUserItem() { return userItem; } public static void setUserItem(String[] userItem) { SendMsgAdapter.userItem = userItem; } } 菜鸟一个,额,大致如上了, 有不对的 地方敬请指教,求调教哦!
做数据显示时总需要各种图表,显示的更形象,什么饼状图,柱状图的。常用的就是Chart.js。或者Highchartsjs写的 chartjs可以方便的绘制出各种图形,同时对数据进行切换。chartjs是canvas写的,所以 var ctx = document.getElementById("canvas"); Highchartsjs则是svg绘制。 以线形图为例 var datas = { labels: ["1月", "2月", "3月", "4月", "5月", "6月", "7月"], datasets: [{ label: "浏览UV", data: [2,34,34345,45,46345,6546], fill: false, backgroundColor:'#0084ff', pointBackgroundColor:'#0084ff', pointHoverBorderColor:'#0084ff', }, { label: '浏览PV', data: [324,34,4335,46346546], fill: false, backgroundColor:'#fe5551', pointBackgroundColor:'#fe5551', pointHoverBorderColor:'#fe5551' }] };labels 就是横坐标,datasets数据集合,data数组为每一项的,对应每个月的数据,y轴坐标显示根据每一项data计算显示出。 fill是否有填充,曲线下方和x轴之间是否有填充色, pointBackgroundColor数据点的背景色, pointHoverBorderColor鼠标覆盖时颜色,手机为点击时的样式, scales,中参数有 scales: { xAxes: [{ display: true, // X轴 上竖线是否显示 color: '#ffffff', //颜色 stacked: true, scaleLabel: { display: true, // x轴下面显示 x名字 是否显示 labelString: 'Month', //名字 }, gridLines: { color: '#aab5fd', // X轴 上竖线颜色 zeroLineColor: "#aab5fd" // 起点的颜色 }, ctx: { font: "18px Helvetica, Arial, sans-serif" } }], yAxes: [{ display: true, scaleLabel: { display: false, labelString: 'Value' }, ticks: { // 刻度线 suggestedMin: 0, suggestedMax: 250, }, gridLines: { color: '#aab5fd', zeroLineColor: "#aab5fd" } }], }, legend: { //表头顶部显示的信息 display: false, }, chartjs对数据的切换都是在数组里,对数据的数据datasets更换,然后重新绘制。 datasets.push(data); 同时需要update 才重新绘制 chart.update() 传入datasets里数据格式,同时需要对显示的数据图形赋值,线条颜色之类的。初次进入页面时,图形设置的初始化 var DataSets = (function () { var instance; function Init(args) { var args = args || {}; this.dataAry = args.dataAry; this.gbColor = args.gbColor; function setDataSets() { var datas = {}, datasetsAry = [], labelAry = dataAry[2][0], labelItem = [], Ary = dataAry[1]; datas['labels'] = dataAry[0]; labelAry.forEach(function (i) { labelItem.push(i); }); for (var i = 0, item; item = Ary[i++];) { var j = i - 1, color = gbColor[j]; var itemData = { label: labelItem[j], data: item, fill: false, backgroundColor: color, pointBackgroundColor: color, pointHoverBorderColor: color } datasetsAry.push(itemData); } datas['datasets'] = datasetsAry; console.log(datas) return datas; } return { getDataSets: function () { return setDataSets(); } } }; return { getInstance: function (args) { if (!instance) { instance = Init(args); } return instance; } }; })(); 初次进入页面绘制 ,gbColor 线条颜色 dataAry就是数据了 var dataUtil = DataSets.getInstance({ gbColor: ['#2e94f3', '#a81f12', '#095660', '#0e3eb7', '#d85f1d', '#6542be', '#ffc658', '#f99e8c', '#4bd2eb', '#c2b2ea'], dataAry: arr }); statistics = $('#canvas').statistics({ data: dataUtil.getDataSets(), color: ['#2e94f3', '#a81f12', '#095660', '#0e3eb7', '#d85f1d', '#6542be', '#ffc658', '#f99e8c', '#4bd2eb', '#c2b2ea'] }); 每次要显示其他数据重新传入数组就好了 statistics.setDataSets(arr[1], arr[0], arr[2]); arr[1] x轴坐标,arr[0],要展示的数据的值,里面就是多个数组。 arr[2] 鼠标覆盖上去显示的label 数据,即DataSets里 var itemData = { label: labelItem[j], ×××××× } chartjs 线形图jq封装如下 (function ($) { //初始加载 $.fn.statistics = function (_options) { var $this = $(this), $myStatistics = {}, dataset = [], currentIndex = null, defaults = { color: [], type: 'line', options: { responsive: true, title: { display: false, }, tooltips: { mode: 'label', callbacks: {} }, hover: { mode: 'dataset' }, scales: { xAxes: [{ display: true, color: '#ffffff', stacked: true, scaleLabel: { display: false, labelString: 'Month', }, gridLines: { color: '#aab5fd', zeroLineColor: "#aab5fd" }, ctx: { font: "18px Helvetica, Arial, sans-serif" } }], yAxes: [{ display: true, scaleLabel: { display: false, labelString: 'Value' }, ticks: { suggestedMin: 0, suggestedMax: 250, }, gridLines: { color: '#aab5fd', zeroLineColor: "#aab5fd" } }], }, legend: { display: false, }, } }, _options = $.extend(defaults, _options); var _optionsData = { type: _options.type, options: _options.options, data: _options.data }; var _initStats = function () { var max = 0; $.each(_options.data.datasets, function (i, dataset) { dataset.borderColor = '#ffffff'; dataset.borderWidth = 0.9; dataset.pointBorderColor = '#ffffff'; dataset.pointBorderWidth = 1; dataset.lineTension = 0; for(var x in dataset){ max = max > dataset[x] ? max : dataset[x]; } }); _options.options.scales.yAxes[0].ticks.suggestedMax = parseInt(max, 10) * 1.5 + 20; $myStatistics = new Chart($this, _optionsData); }; var _init = function () { _initStats(); }; var _addDataSets = function (index) { dataset = _rmDataSets(); if (currentIndex == index) { //同一元素被点击第二次,全部显示 _options.data.datasets = dataset; currentIndex = null; } else { $.each(dataset, function (i, n) { if (i == index) { _options.data.datasets.push(n); } }); currentIndex = index; } }; var _rmDataSets = function () { var len = _options.data.datasets.length; if (len == 1 && currentIndex == null) { return _options.data.datasets.splice(0); } if (len < 2) { _options.data.datasets.splice(0); return dataset; } return _options.data.datasets.splice(0); }; var _setDataAry = function (ary, titleAry) { var titleItem = []; titleAry.forEach(function (i) { titleItem.push(i); }); _options.data.datasets.splice(0); _dataUtil(ary); var max = 0; $.each(_options.data.datasets, function (i, n) { n.data = ary[i]; n.label = titleItem[i]; for(var x in ary[i]){ max = max > ary[i][x] ? max : ary[i][x]; } }); _options.options.scales.yAxes[0].ticks.suggestedMax = parseInt(max, 10) * 1.5 + 20; currentIndex = null; dataset = []; }; var _setLabels = function (labels) { _options.data.labels = labels; }; var _dataUtil = function (ary) { for (var i = 0, item; item = ary[i++];) { var j = i - 1; var itemData = { data: ary[i], fill: false, backgroundColor: _options.color[j], pointBackgroundColor: _options.color[j], pointHoverBorderColor: _options.color[j], borderColor: '#ffffff', pointBorderColor: '#ffffff', borderWidth: 0.9, pointBorderWidth: 1, lineTension:0 } _options.data.datasets.push(itemData); } }; var _changeCss = function (obj) { if(obj.attr('style')){ obj.removeAttr('style'); } else { var color = _options.color[obj.index()]; obj.css('color', color); obj.siblings().removeAttr('style'); } }; this.setDataSets = function (dataAry, labels, title) { _setDataAry(dataAry, title); _setLabels(labels); $myStatistics.update(); }; //单个 多个 切换 this.changeDataSets = function (obj) { var index = obj.index(); _addDataSets(index); _changeCss(obj); $myStatistics.update(); }; _init(); return this; }; })(jQuery); Highcharts如下 $(function() { chart = new Highcharts.Chart({ chart: { renderTo: 'chart_line', //图表放置的容器,DIV defaultSeriesType: 'line', //图表类型line(折线图), zoomType: 'x', //x轴方向可以缩放 plotBackgroundColor:'#ffffff', backgroundColor:'#ffffff', plotBorderWidth:0, borderWidth:0 }, credits: { enabled: false //右下角不显示LOGO }, title: { text: '' //图表标题 }, subtitle: { text: '' //副标题 }, xAxis: { //x轴 categories: [ '1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月' ], //x轴标签名称 gridLineWidth: 1, //设置网格宽度为1 lineWidth: 2, //基线宽度 labels:{y:20}, //x轴标签位置:距X轴下方26像素 gridLineColor: '#c6c5cf', gridLineDashStyle:'Dot', lineColor: '#423e5f', labels: { style: { color: '#423e5f', font: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' } } }, yAxis: { gridLineColor: '#c6c5cf', gridLineDashStyle:'Dot', labels: { style: { color: '#423e5f', font: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif' } }, lineColor: '#ffffff', minorTickInterval: null, tickColor: '#A0A0A0', tickWidth: 1, title: null }, plotOptions:{ //设置数据点 line:{ dataLabels:{ enabled:false //在数据点上显示对应的数据值 }, color: '#e93938', enableMouseTracking: false,//取消鼠标滑向触发提示框 cursor:"pointer" }, series: { allowPointSelect: true } }, legend: { //图例 layout: 'horizontal', //图例显示的样式:水平(horizontal)/垂直(vertical) backgroundColor: '#ffffff', //图例背景色 align: 'left', //图例水平对齐方式 verticalAlign: 'top', //图例垂直对齐方式 x: 100, //相对X位移 y: 70, //相对Y位移 floating: true, //设置可浮动 shadow: true , //设置阴影 enabled:false }, exporting: { enabled: false //设置导出按钮不可用 }, series: [{ //数据列 name: '一楼1号', data: [{ color: '#e93938', y: 0, marker: { symbol: 'circle', //数据点 图形样式设置 width:12, height:12, fillColor:'#ffffff', lineWidth:2, lineColor:'#000000' } }, { y: 5 }, { y: 9 }, { y: 9 }], }] }); }); 有需要的交流的可以加个好友
为了让react 实现本地语言,就需要i18n 当然首先就要npm install npm install react-intl --save 安装好intl,这个组件依赖react 版本为 0.14.0 以上 或者 15.0.0以上 如果是0.13.0 的 就要对react 升级,主要 0.14以后react 对组件进行了分离,分为 react 和react-dom 还有react-addons 正文开始 建立语言文件:data.json 汉字进行Unicode编码转换 { "en": { "BackManage": "Backstage Management", "POSTS": "POSTS", "Posts": "Posts", "Post Categories":"Post Categories", "GALLERIES": "GALLERIES", "Galleries": "Galleries", "ENQUIRIES": "ENQUIRIES", "Enquiries": "Enquiries", "YS": "YS", "Ys": "Ys", "OTHERS": "OTHERS", "TEST": "TEST", "Users": "Users", "Test": "Test", "Tests": "Tests" }, "zh": { "BackManage": "\u7ba1\u7406\u540e\u53f0", "POSTS": "\u6240\u6709\u535a\u6587", "Posts": "\u535a\u6587", "Post Categories":"\u535a\u6587\u5206\u7c7b", "GALLERIES": "\u6240\u6709\u753b\u5eca", "Galleries": "\u753b\u5eca", "ENQUIRIES": "\u67e5\u8be2\u6240\u6709", "Enquiries": "\u67e5\u8be2", "YS": "\u7ba1\u7406", "Ys": "\u7ba1\u7406", "OTHERS": "\u5176\u4ed6", "Users": "\u7528\u6237\u7ba1\u7406", "TEST": "\u6d4b\u8bd5", "Test": "\u6d4b\u8bd5", "Tests": "\u6d4b\u8bd5" } } 创建 Translate.js 组件 import { IntlProvider, addLocaleData } from 'react-intl'; 这个需要 intlprovider 用来传递 给子类 语言信息 import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { IntlProvider, addLocaleData } from 'react-intl'; import localeData from '../../translations/data.json'; import en from 'react-intl/locale-data/en'; import zh from 'react-intl/locale-data/zh'; //需要本地化的语言 addLocaleData([...en, ...zh]); //获取本地语言 const language = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage; const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0]; //messages data.json 里对应的 语言文本 const messages = localeData[languageWithoutRegionCode] || localeData[language] || localeData.zh; class Translate extends Component { constructor(props) { super(props); } render() { //this.props.Template 父级传来的 this.props.Template return ( <IntlProvider locale={ language } messages={ messages }> {this.props.Template} </IntlProvider> ); } } module.exports = Translate; 父级组件 import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import HomeDetail from './home-detail'; import Translate from './translate'; class HomeView extends Component { constructor(props) { super(props); } render() { return ( <Translate Template={<HomeDetail/>}/> ); } } ReactDOM.render(<HomeView />, document.getElementById('home-view')); 需要实现 本地化的 view 组件 引入 import { FormattedMessage } from 'react-intl';react-intl 还有其他很多 功能 时间 <FormattedMessage id={×××} /> id 值就是你要的显示的文字 当然还可以有其他属性 description='say hello todescription' defaultMessage='Hello, defaultMessage' import React, { Component } from 'react'; import { FormattedMessage } from 'react-intl'; export default class HomeDetail extends Component { constructor(props) { super(props); } renderFlatNav() { return index.lists.map((list) => { var href = list.external ? list.path : '/index/' + list.path; return ( <h3 key={list.path}> <a href={href}> <FormattedMessage id={list.label} /> </a> </h3> ); }); } renderGroupedNav() { return ( <div> {index.nav.sections.map((navSection) => { return ( <div className="nav-section" key={navSection.key}> <h4> <FormattedMessage id={navSection.label} /> </h4> <ul> {navSection.lists.map((list) => { var href = list.external ? list.path : '/index/' + list.path; return ( <li key={list.path}> <a href={href}> <FormattedMessage id={list.label}/> </a> </li> ); })} </ul> </div> ); })} {(() => { if (!index.orphanedLists.length) return; return ( <div className="nav-section"> <h4> <FormattedMessage id={'OTHERS'} /> </h4> <ul> {index.orphanedLists.map((list) => { return ( <li key={list.path}> <a href={'/index/' + list.path}> <FormattedMessage id={list.label}/> </a> </li> ); })} </ul> </div> ); })()} </div> ); } render() { return ( <div> <div className="page-header"> <h1> <FormattedMessage id={'BackManage'} /> </h1> </div> <div className="index-lists">{index.nav.flat ? this.renderFlatNav() : this.renderGroupedNav()}</div> </div> ); } } 直接传递字符串时 需要通过defineMessages 来对字符进行转换 import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; const messages = defineMessages({ placeholder: {id: 'inputfilter'}, }); var test = React.createClass({ getInitialState () { return { formatMessage: this.props.intl['formatMessage'], }; }, render() { return ( <FormInput placeholder={this.state.formatMessage(messages.placeholder)} /> ) } }); module.exports = injectIntl(test); 有需要的交流的可以加个好友
一、 首先配置环境 当然是node 下用npm npm install -g react-native-cli然后创建项目 react-native init react1 cd react1 react-native run-android 新开一个cmd 启动项目 react-native start 在电脑上启动的安卓虚拟机 不能摇一摇,所以还需要在 cmd 里输入 adb shell input keyevent 82 或者 adb -s emulator-5554 shell input keyevent 82 在笔记本下启动的虚拟机会比较卡可以设置如下,会稍微好点 二、需要实现的界面和功能如下 新建一个AppNavigator.js 文件 用于首页和详情页的跳转 'use strict' import React, { Component } from 'react'; import { StyleSheet, Navigator } from 'react-native'; import ViewContainer from '../views/indexView'; import DetailContainer from '../views/Detail'; class AppNavigator extends Component { _renderScene(route, navigator) { let globalNavigatorProps = { navigator }; switch(route.ident){ case 'indexView': return( <ViewContainer {...globalNavigatorProps} /> ) case 'detail': return( <DetailContainer {...globalNavigatorProps} /> ) default: return( <ViewContainer {...globalNavigatorProps} /> ) } } render() { return ( <Navigator initialRoute={this.props.initialRoute} ref="AppNavigator" renderScene={this._renderScene}/> ); } } module.exports = AppNavigator;index.android.js 首先展示首页 'use strict' import React, { Component } from 'react'; import { AppRegistry, StatusBar } from 'react-native'; import AppNavigator from './app/common/AppNavigator'; class react1 extends Component { render() { this._setStatusBar(); return ( <AppNavigator initialRoute={{ident: 'indexView'}}/> ); } //状态栏的颜色 _setStatusBar() { StatusBar.setBackgroundColor('#af3329', true); } } AppRegistry.registerComponent('react1', () => react1); reactjs 写样式和传统的css 有一定的区别,驼峰和没有简写如(margin: 0 auto) 是没有的,同时安卓机下是不能显示出阴影的首页中的 卡片布局如下 'use strict' import React, { Component } from 'react'; import { StyleSheet, Text, View, AsyncStorage, Image } from 'react-native'; import util from '../common/util'; class Card extends Component { constructor(props) { super(props); } render() { let subject, posterImage, image; // 大图 this.props.subject ? subject = this.props.subject : subject = this.props; posterImage = subject.images.large; if(posterImage != '' && posterImage != null) { image = <Image resizeMode="stretch" style={styles.posterImage} source={{uri: posterImage}}/>; }else{ image = <Text>{this.props.subject.title}</Text>; } return ( <View style={[styles.cardBox, this.props.CardPosition ? { position: 'absolute', top: this.props.CardTop} : null ]}> <View style={styles.posterWrap}> {image} {this.props.new ? <Text style={styles.newTop}>新上榜</Text> : null } {this.props.rank ? <Text style={styles.topNumber}>Top {this.props.rank}</Text> : null} </View> <View style={styles.cinemaMsg}> <View style={styles.cinemaMsgItem}> <Text style={styles.title} numberOfLines={1} >{subject.title} { this.props.box ? <Text style={styles.cast}> {subject.casts[0].name}...</Text> : null} </Text> <Text style={styles.average}>评分:{subject.rating.average}</Text> </View> <View style={styles.cinemaMsgItem}> <Text style={[styles.arrivedMsg, styles.flex2]} numberOfLines={1}>{subject.original_title} ({subject.year})</Text> <Text style={styles.boxOffice} numberOfLines={1}>{ this.props.box ? '票房:' + this.props.box/1000 : subject.casts[0].name}</Text> </View> <View style={styles.cinemaMsgItem}> <Text style={styles.arrivedMsg}>类型:{subject.genres.join('\/')}</Text> <Text style={styles.directors}>导演:{subject.directors[0].name}</Text> </View> </View> </View> ); } } const styles = StyleSheet.create({ flex2: { overflow: 'hidden', }, cardBox: { borderRadius: 5, borderWidth: 2, marginTop: 2, width: 310, marginHorizontal: (util.size.width - 310) / 2 , borderColor: '#e1e2da', backgroundColor: '#ffffff', }, posterWrap: { width: 310, borderColor: '#e1e2da', }, posterImage: { height: 340, width: 310, }, newTop: { position: 'absolute', top: 0, right: 0, fontSize: 12, color: '#ffffff', backgroundColor: 'rgba(230,69,51,0.65)', paddingVertical: 1, paddingHorizontal: 3, }, topNumber: { position: 'absolute', top: 0, left: 0, fontSize: 12, color: '#ffffff', paddingVertical: 1, paddingHorizontal: 3, backgroundColor: 'rgba(255,164,51,0.7)', }, cinemaMsg: { width: 300, padding: 2, flexDirection: 'column', }, cinemaMsgItem: { flex: 1, justifyContent: 'space-between', flexDirection: 'row', }, title: { flex: 2, fontSize: 15, color: '#1d1d1d', textAlign: 'left', }, cast: { fontSize: 12, }, average: { flex: 1, fontSize: 15, color: '#e64533', textAlign: 'right', }, arrivedMsg: { fontSize: 13, textAlign: 'left', }, boxOffice: { flex:1, fontSize: 12, color: '#e64533', textAlign: 'right', }, directors: { fontSize: 12, textAlign: 'right', color: '#1d1d1d', }, }); module.exports = Card; 卡片滑动swipe 效果 npm install -g react-native-swipe-cards import SwipeCards from 'react-native-swipe-cards'; render() { let data = (this.props.dataCinema ? JSON.parse(this.props.dataCinema) : null); return ( <View style={styles.box}> { data ? <SwipeCards cards={data} style={styles.swipeCards} loop={true} renderCard={(cardData) => <Card {...cardData} />} handleYup={this.handleYup} renderNope={this.renderNope} renderYup={this.renderYup} handleNope={this.handleNope} cardRemoved={this.cardRemoved}/> : null } </View> ); } 首页抽屉效果 npm install -g react-native-drawer render() { return ( <Drawer ref={(ref) => this._drawer = ref} type="static" content={ <LeftControlPanel closeDrawer={this.closeDrawer}/> } styles={{main: {shadowColor: '#000000', shadowOpacity: 0.3, shadowRadius: 15}}} captureGestures={true} acceptTap={true} acceptPan={true} negotiatePan={false} useInteractionManager={false} tweenDuration={100} panThreshold={0.08} panOpenMask={0.03} panCloseMask={0} disabled={this.state.drawerDisabled} openDrawerOffset={(viewport) => { return 80 }} panOpenMaskY={50} side="left" tweenHandler={Drawer.tweenPresets.parallax} > <Drawer type="static" ref={(ref) => this._drawer2 = ref} content={ <RightControlPanel closeDrawer={this.closeDrawer2} /> } captureGestures={true} acceptTap={true} acceptPan={true} negotiatePan={false} useInteractionManager={false} tweenDuration={100} panThreshold={0.08} panOpenMask={0.03} panCloseMask={0} openDrawerOffset={(viewport) => { return 80 }} side="right" tweenHandler={Drawer.tweenPresets.parallax} > <Main topItem={this.state.topItem} navigator={this.props.navigator}/> </Drawer> </Drawer> 以上所用插件我都有改动 符合demo的需求···· 通过这次的demo学习对react 有进一步的认识与体会, 组件的生命周期 componentWillReceiveProps(nextProps){} 接收新的数据 shouldComponentUpdate(nextProps, nextState){ return boo} 必须有返回值, 返回 false componentWillUpdate()不会被调用 render()也再不执行 ,这样根据需要可以禁止页面的更新。 react native事件捕获 onStartShouldSetPanResponderCapture, onMoveShouldSetPanResponderCapture连个方法都有返回值, 返回true 时,事件就不再传递,被当前组件劫持并调用当前组件的onResponderStart或者onResponderRlase等 数据请求利用 facth 比传统ajax 更简洁 当然属于es6 的 fetch(url).then((response) => response.text()) .then((responseText) => { successCallback(JSON.parse(responseText)); }).catch(function(err){ failCallback(err); }); state 更新 用setState 当然也可以用 如 this.state['cards'] = JSON.parse(nextProps.dataCinema); this.forceUpdate(); forceUpdate就是重新render。有变量不在state上,缺又想更新state,刷新render;或者state里的某个变量层次太深,更新的时候没有自动触发render。这些时候都可以手动调用forceUpdate自动触发render。 跳转到github 有需要合作的,可以加个好友
我用的是ionic start 命令生成项目 ,用 cordova create 也是可以的。都会在index.html 有这么一行 <script type="text/javascript" src='cordova.js'></script> 但在浏览器运行时 提示 deviceready has not fired after 5 seconds. Channel not fired: onCordovaInfoReady 当然浏览器下这么浏览肯定是有问题的,但是 当然在 执行 ionic emulate 或者 ionic run 时 安卓机上打开程序也是 什么都没有显示,可以断定浏览器提示肯定是有原因的。用 ripple emulate 试调 也没都能正常加载的,同时还能出发controlle里 的事件 如 backbutton 事件。 盲目的 谷歌了很多种办法都没解决 有 如下的: 或者 或者 只用 cordova 命令 Content Security Policy 用来定义 页面可以加载那些域名下的资源 图片 css js 等。 github 或者stackoverflow 上也没找到 解决的办法,也是因为英语太差。 然后运行 ionic platform add android 可以指定好版本 ionic platform add android@5.0.0 生成如下目录, 看到这 就自然的把 android\assets 里的 文件都copy到服务器上去了 然后 ionic emulate android,或者浏览器 ,问题就来了。 其实这么copy是错误的,打包成apk 是 html,js,css 等都在apk里面了于是呢,运行的时候,安卓就首先加载本地的文件 js等,而从服务器上加载资源,不知道什么时候资源请求完成,事件的绑定就是个问题了,同时js是需要调用java代码的,放服务器端js就涉及到权限问题了,对本地数据的读写。而请求json数据是从服务器请求$http.get('http://182....',function(data){}); 所以在 apk 里打包有的文件,无服务端都不用放了。特别是cordova.js , cordova_plugins.js, plugins等。 最底层的加载机制 还有些没搞懂,请大神们指导呢,谢谢哦。
fileapi,加载图片,并且显示。 先new 一个fileReader。 主要方法 // try sending var reader = new FileReader(); reader.onloadstart = function() { console.log("onloadstart"); } reader.onprogress = function(p) { console.log("onprogress"); } reader.onload = function() { console.log("load complete"); } reader.onloadend = function() { }form表单<input type="file" id=“imagemain”> 选择文件时 document.getElementById('imagemain').addEventListener('change', function (event){});点击按钮添加图片 <form method="post" enctype="multipart/form-data"> <div id="formitem-bigpic"> <div class="bigpic bigpic-add"> 添加<br>图片 <input type="file" id="imagemain" name="image"> </div> </div> </form> 利用formData提交表单数据 var formData = new FormData(); formData.append('key1', 'value1'); formData.append('key2', 'value2'); formData.get('key1'); //返回value1 formData.has('123'); // Returns false formData.delete('key1');//删除 var data = new FormData(); data.append('key1',21321); console.log(data); for(let i of data.entries()) { console.log(i[0]+ ', '+ i[1]); } console.log('-------delete -------'); data.delete('key1'); for(let i of data.entries()) { console.log(i[0]+ ', '+ i[1]); } console.log('-----delete------'); data.set('key1',43232); // data.append('key1',21321); data.append('key1','213opop'); console.log(data.get('key1')); //获得 首先设置的值 console.log(data.getAll('key1')); //图片添加 fileApi var handleFileSelect = (function () { var readerInstance; function inInt(args) { var args = args || {}, fileJson = {}, xhr = new XMLHttpRequest(), reader = new FileReader(); this.fatherEle = args.fatherEle; this.sonClz = args.sonClz; this.Index = args.Index; this.maxSize = args.maxSize; this.inputImage = args.inputImage; this.inputItem = args.inputItem; this.addMethod = args.addMethod; this.delMethod = args.delMethod; function loadFile(evt, callback) { var file = evt.target.files[0]; //取消时 if (file === undefined) { return false; } if (!file.type.match('image.*')) { errMsgBox('图片格式请选择为png,jpg,jpeg等'); return false; } if ((file.size / 1024) > maxSize) { errMsgBox('图片大小不能超过' + (maxSize / 1024).toFixed(2) + 'M'); return false; } //是否存在 /* var boo = isExists(file); if(boo) return false;*/ //开始选择 reader.onloadstart = (function () { loadStart(); fileOnload(file, callback); })(file, callback); }; //load complete function fileOnload(file, callback) { reader.onload = (function () { return function (e) { createEle(e.target.result, file.name, file.type); } })(file); reader.readAsDataURL(file); //上传 返回ajaxEnd var endFunc = ajaxPost(file, ajaxEnd); //上传结束 成功 或失败 return endFunc(loadEnd, callback); }; function ajaxPost(file, fn) { var oData = formDataFunc(); //oData.append('fileImage',file); oData.append('fileImage', file); xhr.open("POST", addMethod, true); var endFn = fn(); xhr.send(oData); return endFn; }; function ajaxDel(id,fn) { xhr.open('POST', delMethod, true); var oData = formDataFunc(); oData.append('id',id); xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if (xhr.status == 200) { inputImage.value = ''; fn.call(this, JSON.parse(xhr.responseText)); }; } }; xhr.send(oData); }; //上传结果返回 function ajaxEnd() { return function (fn, callback) { xhr.onreadystatechange = function () { if (xhr.readyState == 4) { inputImage.value = ''; fn.call(this, JSON.parse(xhr.responseText), callback); } }; }; }; // formdata 数据添加 function formDataFunc() { var oData = new FormData(); for (var i = 0, j; j = inputItem[i++];) { oData.append(j.name, j.value); } return oData; }; //显示图片上传div function loadStart() { var head = document.getElementsByClassName('m-head')[0]; var child = document.getElementById('sure-disabled'); if (child === null) { child = document.createElement('div'); child.setAttribute('class', 'sure-disabled'); child.setAttribute('id', 'sure-disabled'); head.appendChild(child); } else { child.style.display = 'block'; } inputImage.setAttribute('disabled', 'disabled') }; //上传结束 function loadEnd(data, callback) { var sure = document.getElementById('sure-disabled'); sure.style.display = 'none'; if (data.status != 0) { var child = fatherEle.getElementsByClassName('bigpic'); var idx = child.length - 2; fatherEle.removeChild(child[idx]); } var clear = setTimeout(function(){ inputImage.removeAttribute('disabled'); clearTimeout(clear); clear = null; },200) callback.call(this, data); }; //判断图片是否已经添加 function isExists(fileObj) { var fileName = fileObj.name; //不为空 未添加过 if (Object.getOwnPropertyNames(fileJson).length === 0) { fileJson[fileName] = file.type; return false; } if (fileJson[fileName] != fileObj.type) { fileJson[fileName] = file.type; return false; } else { alert('该图片已经上传'); return true; } }; // 创建并添加元素 function createEle(srcStr, fileName, fileType) { var sonEle = fatherEle.getElementsByClassName(sonClz), sonLen = sonEle.length, tagEle = sonEle[sonLen - Index], div = document.createElement('div'); div.setAttribute('class', sonClz); div.innerHTML = '<img src="' + srcStr + '"/><em class="delete_pic" data-msg="' + fileName + ':' + fileType + '"></em>'; fatherEle.insertBefore(div, tagEle); }; return { //添加图片 fn 定义服务器返回 loadFileFunc: function (e, callback) { loadFile(e, callback); }, //删除图片 delFileFunc: function (param, fn) { ajaxDel(param, fn) }, //清空 已经添加的图片信息 cleanFile: function () { fileJson = {}; }, //删除一张 图片信息 cleanOneFile: function (data) { var dataAry = data.split(':'), fileName = dataAry[0], flieType = dataAry[1], tmpJson = {}; for (var i in fileJson) { if (!(i == fileName && flieType == fileJson[i])) { tmpJson[i] = fileJson[i]; } } fileJson = tmpJson; }, //添加Form 信息 inputItem 固定参数补充 addInputItem: function (ary) { inputItem = inputItem.concat(ary); }, //点击确定后 添加图片 showLoad: function () { var clz = fatherEle.getAttribute('class'); if (clz.indexOf('formitem-top') < 0) { var clz = clz + ' formitem-top'; fatherEle.setAttribute('class', clz); } } }; }; return { getReaderInstance: function (args) { return readerInstance; } }; })(); var flieFunc = handleFileSelect.getReaderInstance({ inputImage: document.getElementById('imagemain'), //inputfile 按钮 inputItem: [document.getElementById('comm')], //固定参数 fatherEle: document.getElementById('formitem-bigpic'), //容器 addMethod: addMethod, //添加 图片请求路径 delMethod: delMethod, //删除 图片请求路径 sonClz: 'bigpic', Index: 1, //追加图片的位置 maxSize: 10300// 图片最大 大小限制 }); 设置监听事件 document.getElementById('imagemain').addEventListener('change', function (event) { flieFunc.addInputItem([document.getElementById('comment_id')]); flieFunc.loadFileFunc(event, function (rtn) { console.log(rtn + ' 上传成功后返回数据'); }); }, false);
ionic.css 布局是基于flex的,虽然没有bootstrap那么丰富,但基本的布局还是满足的。提供了字体的图标,可以自定义颜色。还是能基本满足icon需求吧。。。当然还是需要自己定义很多css 或者 覆盖原来的。入门的写下,也不知道对不对。。。。。 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no,width=device-width,height=device-height"> <link rel="stylesheet" type="text/css" href="../lib/css/ionic.min.css"/> <link rel="stylesheet" type="text/css" href="bm.css"/> </head> <body> <header class="bar bar-header"> <a href="javascript:;" class="icon-right ion-arrow-down-b button button-clear">成都</a> <label class="item item-input stable-dark icon ion-search"> <input type="search" class="dark" placeholder="search"/> </label> </header> <div class="content has-header"> <div class="list banner"> <a class="item item-image"> <img src="aa.jpg"> </a> </div> <a href="#" class="addr list"> <span class="item item-icon-right"> 以父之名 <i class="icon ion-navigate">店铺导航 </i> </span> </a> <div class="list play u-list"> <div class="u-title"><em class="prefix">参</em>参加报名</div> <div class="item item-image"> <img src="aa.jpg"/> </div> </div> <div class="list u-list"> <div class="u-title"><em class="prefix">周</em>龙卷风</div> <div class="row item"> <a href="#" class="col-33"><img src="th.jpg"/><p>静静悄悄</p></a> <a href="#" class="col-33"><img src="th.jpg"/><p>静静悄悄</p></a> <a href="#" class="col-33"><img src="th.jpg"/><p>静静悄悄</p></a> </div> <div class="row item"> <a href="#" class="col-33"><img src="th.jpg"/><p>静静悄悄</p></a> <a href="#" class="col-33"><img src="th.jpg"/><p>静静悄悄</p></a> <a href="#" class="col-33"><img src="th.jpg"/><p>静静悄悄</p></a> </div> </div> </div> <div class="tabs tabs-icon-top"> <div class="tab-item"> <i class="icon ion-ios-home-outline"></i>首页 </div> <div class="tab-item"> <i class="icon ion-ios-keypad-outline"></i> 分类 </div> <div class="tab-item"> <i class="icon ion-ios-cart-outline"></i> 购物车 </div> <div class="tab-item"> <i class="icon ion-ios-person-outline"></i> 会员 </div> <div class="tab-item"> <i class="icon ion-ios-more-outline"></i> 更多 </div> </div> </body> </html> html, body { font-family: "SimHei","Helvetica Neue",Arial,"Droid Sans", sans-serif; background-color: #f0f0f0; } html{ overflow: visible; } a{ text-decoration: none; } .u-list{ margin-top: 5px; margin-bottom: 0; background-color: #ffffff; } .u-title{ padding: 4px 2.66666667%; } .u-list .row{ padding: 0; } .u-list .col-33{ margin: 0 0.5%; } .u-list .col-33 img{ max-width: 100%; } .u-list .col-33{ text-align: center; } .row.item{ border: 0; } .prefix{ display: block; font-size: 12px; height: 14px; width: 14px; color: #fff; text-align: center; line-height: 14px; float: left; margin: 3px 4px 0 0; background-color: #4198f7; } .bar-header{ background-color: #ff332a; } .bar-footer{ background-color: #333333; } .bar-header .button-clear.button{ font-size: 13px; color: #ffffff; } .bar.bar-header .button.button-clear:before{ font-size: inherit; } .bar-header .item-input{ border: 1px solid #dcdcdc; border-radius: 5px; width: 70%; margin-left: 5%; } .bar-header .ion-search:before{ margin-right: 5px; color: inherit; color: #b2b2b2; } .banner.list{ padding: 0; margin-bottom: 0; } .banner .item{ border-width: 0; } .tabs{ background-color: #333333; color: #ffffff; } .tabs-icon-top.tabs .tab-item{ font-size: 12px; line-height: 11px; } .tab-item .icon:before{ color: #inherit; font-size: 32px; } .addr .item .icon{ font-size: 16px; } .addr .item{ padding-top: 8px; padding-bottom: 8px; } .addr .ion-navigate:after { text-align: center; display: block; content: "\f2a3"; font-family: Ionicons; } .addr .ion-navigate:before{ display: none; }
安装工具 以上两个的版本号 得一样。。。。。 vargrant 命令 vagrant box add BOX_NAME BOX_URL 添加一个 box BOX_NAME 为要添加的 box 指定一个名字, 可任意. 命令 `vagrant init` 将使用这个名字进行初始化. BOX_URL 指定 box 的地址, 可以是远程或本地 box 文件. 建议可先将远程 box vagrant box addBOX_URL --name BOX_NAME 参数 --name 是 box 的别名, 可任意. vagrant box list 查看本地已添加的 box vagrant box remove BOX_NAME 删除本地已添加的 box vagrant init BOX_NAME 初始化, 将在当前工作目录下创建 Vagrantfile 配置文件. vagrant up 启动虚拟机 vagrant halt 关闭虚拟机 vagrant reload 重启虚拟机, 修改了配置文件 Vagrantfile 后, 可以使用这个命令重新加载配置. vagrant status 查看虚拟机当前状态 vagrant ssh 进入虚拟环境 vagrant destroy 销毁虚拟机 操作如上,步骤如下: 目录下生成 Vagrantfile 文件打开进行配置 然后启动linux 启动成功。 接下来进行nodejs安装 先安装这些东西,有的可能已经有了。 sudo apt-get update sudo apt-get install python sudo apt-get install build-essential sudo apt-get install gcc sudo apt-get install g++ node官网下载 node-v4.4.4.tar.gz 安装包 先解压 命令依次: tar -xvf node-v4.4.4.tar.gz 执行 ./configure --prefix=/usr/local/bin/node然后 make && make install 设置npm node 链接 ln -s sudo ln -s /usr/local/bin/node/lib/node_modules/npm/bin/npm-cli.js /usr/bin/npm sudo ln -s /usr/local/bin/node/bin/node /usr/bin/node 安装 express sudo ln -s /usr/local/bin/node/lib/node_modules/express-generator/bin/express /usr/bin/express sudo npm install -g express-generator sudo npm install -g express 生成 express 项目 express testapp -e npm install 安装 debugging工具 npm install node-inspector -g 安装时报错 解决办法 步骤: 1.npm config get prefix 2. sudo chown -R vagrant /usr/local/ 3.sudo chown -R vagrant /usr/local/bin/ 4. sudo chown -R vagrant /usr/local/share/ 或者 mkdir npm-global cd npm global npm config set prefix /usr/vagrant/npm-global ps:vagrant 用户名 whoami 启动: node --debug bin/www 新窗口: node-inspector & 指定端口 node-inspector--web-port=3000 vagrant 配置文件得 配置对应端口 有需要的交流的可以加个好友
</pre>用iscroll做滑动时,里面的元素需要绑定click 事件,但是,明明只是绑定了一次,却触发了两次。<p></p><p></p><pre name="code" class="html"><div id="wrapper" style="display:none"> <div id="scroller"> <ul> <li>Pretty row 1</li> <li>Pretty row 2</li> <li>Pretty row 3</li> <li>Pretty row 4</li> <li>Pretty row 5</li> <li>Pretty row 6</li> <li>Pretty row 7</li> <li>Pretty row 8</li> <li>Pretty row 9</li> <li>Pretty row 10</li> </ul> <p class="btn"> hide</p> </div> </div> <div id="footer"> show</div> <script type="text/javascript"> var myScroll; $('.btn').on('click',function(){ $('#wrapper').hide(); }); $('#footer').on('click',function(){ myScroll = new IScroll('#wrapper', { mouseWheel: true,click:true }); $('#wrapper').show(); }) $('li').on('click',function(){ console.log(3213); }); </script> 当wrapper显示,初始化iscorll,再点击一次 li元素是console.log只打印一次。但是,但点击btn元素时,wrapper隐藏,在点击footer元素,wrapper显示后,再点击一次li元素时 console.log只打印两次,循环操作时,每循环一次,console.log只打印次数+1。 可以在页面加载完成时,就初始化iscorll ,同时加上destory(); <script type="text/javascript"> var myScroll = new IScroll('#wrapper', {click:true }); $('.btn').on('click',function(){ console.log('sdsadsad'); myScroll.destroy(); $('#wrapper').hide(); }); $('#footer').on('click',function(){ $('#wrapper').show(); }) $('li').on('click',function(){ console.log(3213); }); </script>这样点击li时 console.log只打印了一次,但是点击btn时,第一次点击就打印两遍 隐藏显示,在点击btn时,只打印出一次。第一种写法 是点击一次footer 就在li元素和btn上添加一次事件绑定,所以在每次隐藏时都需要对iscorll销毁。myScroll.destroy(); 这样做同样会也有多次触发的结果。 原因便是 在$btn.on('click')下有myScroll.destroy(),影响的同时 浏览器为未设置成手机模式,改为手机模式就没问题。 为了彻底避免这些情况iscorll 有一个tap事件,用tap代替click var $wrapper = document.getElementById('wrapper'),$btn = $('.btn'),$footer = $('#footer'),$li = $('.lia'); var myScroll = new IScroll($wrapper, { click:true,tap:true}); $btn.on('tap',function(){ myScroll.destroy(); $wrapper.style.display = 'none'; }); $footer.on('click',function(){ $wrapper.style.display = 'block'; myScroll.refresh(); }); $li.on('tap',function(){ console.log(3213); }); foot不在wrapper里 对其绑定tap 无效的。 关于Event 对象 // createEvent(eventType) 该方法将创建一种新的事件类型,该类型由参数 eventType 指定。注意,该参数的值不是要创建的事件接口的名称,而是定义那个接口的 DOM 模块的名称。 var event = document.createEvent('Event'); // event.initEvent(eventType,canBubble,cancelable) //eventType 字符串值。事件的类型。 //canBubble 事件是否起泡。 //cancelable 是否可以用 preventDefault() 方法取消事件。 event.initEvent('build', true, true); // Listen for the event. elem.addEventListener('build', function (e) { // e.target matches elem }, false); // dispatchEvent() 方法给节点分派一个合成事件。 //evt必需传入。要分派的 Event 对象。 elem.dispatchEvent(event); 有需要的交流的可以加个好友
移动端写css3 时 发现在safari 上 一个元素使用了 transform:rotateY(19deg); 显示有问题。 .class{ transform: scale(0.85) rotateY(10deg); -moz-transform: scale(0.85) rotateY(10deg); -o-transform: scale(0.85) rotateY(10deg); -webkit-transform: scale(0.85) rotateY(10deg); }这样 class 元素就看不到了, 然后挨着试在class 元素上添加 transform-style: perspective-3d, 或者visibility:visible , perspective:1000, 也都无济于事, transform-style 两个值 flat 即默认 2d,perspective-3d 即 所有子元素在3d空间中展示, perspective : w3c 上看了 一段 试着 在父类元素上添加 perspective 小图标果断显示出来了 .faterclass{ transform: perspective(1000); -moz-transform: perspective(1000); -o-transform: perspective(1000); -webkit-transform: perspective(1000); } 绕Y轴旋转 效果。。。。。 再 说下 如果 元素使用动画 用了 transfrom 属性 .class { animation: dh 2.5s linear infinite alternate; -webkit-animation: dh 2.5s linear infinite alternate; } @keyframes dh { form { transform: scale(0.7) rotateY(150deg); -moz-transform: scale(0.7) rotateY(150deg); -o-transform: scale(0.7) rotateY(150deg); -webkit-transform: scale(0.7) rotateY(150deg); } to { transform: scale(0.95) rotateY(360deg); -moz-transform: scale(0.95) rotateY(360deg); -o-transform: scale(0.95) rotateY(360deg); -webkit-transform: scale(0.95) rotateY(360deg); } }这样 手机上是不会显示动画效果的 需要在.class 里加上初始的 效果 .class { -moz-transform: scale(0.85) rotateY(10deg); -o-transform: scale(0.85) rotateY(10deg); -webkit-transform: scale(0.85) rotateY(10deg); animation: dh 2.5s linear infinite alternate; -webkit-animation: dh 2.5s linear infinite alternate; }
Web存储机制,为了克服服由cookie 带来的一些限制,当数据需要被严格控制在客户端上时,无须持续地将数据发回服务器。提供一种在cookie 之外存储会话数据的途径和另一种存储大量可以跨会话存在的数据的机制,即sessionStorage 和globalStorage。后来在h5 修订时将 globalStorage 废弃换成了localStorage,与globalStorage 不同,不能给localStorage 指定任何访问规则;规则事先就设定好了。要访问同一个localStorage 对象,页面必须来自同一个域名(子域名无效),使用同一种协议,在同一个端口上。 主要方法 localStorage.getItem,localStorage.setItem,localStorage.removeItem var localStorageFunc = { //获取 localStorage getStorage:function(storage){ var str = localStorage.getItem(storage); if(str != null && str != ''){ var obj = JSON.parse(JSON.parse(str)); return obj; } }, //设置 localStorage 未嵌套 setStorage:function(storage,key,val){ var obj = this.getStorage(storage); var saveObj = this.utilStorage(obj,key,val); localStorage.setItem(storage,saveObj); }, //设置 localStorage 嵌套 secondStorage:function(storage,tagKey,key,val){ var obj = this.levelStorage(storage,tagKey,key,val); this.setStorage(storage,tagKey,obj); }, //清除 localStorage clearStorage:function(storage){ localStorage.removeItem(storage); }, //json 整理格式化 utilStorage:function(obj,key,val){ if(typeof obj == 'object'){ obj[key] = val; var str = JSON.stringify(obj); str = str.replace(/\\/g,''); obj = JSON.stringify(str); }else{ var item = '{"'+key+'":"' + val + '"}'; obj = JSON.stringify(item); } return obj; }, //次级json 整理格式化 levelStorage:function(storage,tagObj,key,val){ var tag = this.getStorage(storage); var tagItem = tag[tagObj]; if(typeof tagItem == 'object'){ tagItem[key] = val; }else if(tagItem == undefined){ tagItem = '{"'+key+'":"' + val + '"}'; tagItem = JSON.parse(tagItem); } return tagItem; } }; json 嵌套
放假无聊,五行缺撸,索性就这么撸篇博客咯,聊以自慰。 css3 中flex布局 即为弹性布局,在实现响应式,是非常好的。当然对于传统的position,float布局,如果完全只考虑移动端很大程度上能做到去float化了。但是pc端就不行,需要考虑到ie6,ie7,ie8。。。。具体看自己公司咯。这么要求,真像一个现代穿着比基尼的大胸女人,却还要要求她裹脚。。扯远了···· flex是用在父元素上的,即display:flex; 当然子元素水平居中,垂直中,或者完全居中的样式也都写在父元素上。对于子元素 可单独控制子元素大小。 问题是直接这么用flex 是由问题的就是前缀问题。-webkit-,-moz-, -ms-, -o-,而且还有 09年出的标准(box),11年后的标准(flexbox,ie10特有),和12年后至今的(flex)。所以呢,需要一个公共类,或者工具类了 .df{ display: -webkit-box; /* Chrome 4+, Safari 3.1, iOS Safari 3.2+ */ display: -moz-box; /* Firefox 17- */ display: -webkit-flex; /* Chrome 21+, Safari 6.1+, iOS Safari 7+, Opera 15/16 */ display: -moz-flex; /* Firefox 18+ */ display: -ms-flexbox; /* IE 10 */ display: flex; /* Chrome 29+, Firefox 22+, IE 11+, Opera 12.1/17/18, Android 4.4+ */ } 容器还有属性: flex-direction 子元素的排列方式 水平竖直 flex-wrap 是否换行 flex-flow flex-direction属性和flex-wrap属性的简写形式 justify-content 水平方向的排列 align-items 垂直方向的排列 align-content 多列或多行的对齐方式 子元素属性: order 排列顺序。数值越小,排列越靠前,默认为0 flex-grow 放大比例 flex-shrink 缩小比例 flex-basis 根据每项设置的基准值,按比率伸缩剩余空间 flex flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto align-self 脱离容器的对齐方式 单独定义 以上每个属性都要 涉及到前缀,产生的css就比较多了,所以每一个属性都可以定一个公共的类 减少代码量 如flex 天朝的 移动端 浏览器内核都是webkit 就可以只考虑 webkit了,,连opeara 都放弃自家内核 转头 内核都换webkit了 这行为 当时让多少粉丝 很不情愿。。。。 .flex1{ -webkit-box-flex:1; -webkit-flex: 1; -ms-flex: 1; flex: 1; } 要是用less(或者sass)开发,还可以这么定义一个 .flex(@val){ -webkit-box-flex:@val; -webkit-flex: @val; -ms-flex: @val; flex: @val; }某一个子类(项目) item{ .flex(2) }这样能减少写的时间 了。 好了 ,该扯蛋点具体的布局了, 我们需要 并排的几个元素 一般用float ,或者display:inline-block,一个要清除浮动 clear 或者overflow:hidden, 而 display:inline-block 容器需要设置 font-size:0;vertical-align:top; e 用 flex 了在容器(父元素)上一个 <div class="xxxxx df"> <div class="item"></div> <div class="item"></div> <div class="item"></div> <div> 移动端 为了自适应 百分比 .item{ width:33.33333333333333% }换个办法 也可以 <div class="xxxxx container df"> <div class="item flex1"></div> <div class="item flex1"></div> <div class="item flex1"></div> <div> 很nice的完成了。 对于居中 水平垂直同时居中吧, <div class="xxxxx container"> <div class="item"></div> </div> .container{ position:relative; top:100px; left:100px; width:200px; height:200px; background-color:gray; } .item{ position:absolute; width:20px; height:20px; background-color:red; top: 50%; left:50%; margin:-10px 0 0 -10px; } 加入css3 也能搞定 .item{ position:absolute; width:20px; height:20px; background-color:red; top: 50%; left:50%; transform:translate(-10px,-10px); }这样的弊端就是 你需要确切知道 item 的 width ,height 大小,利用 flex 就不用这么顾虑 <div class="xxxxx container"> <div class="item">fuckfuck</div> </div> .container{ top:100px; left:100px; width:200px; height:200px; background-color:gray; /* 水平居中*/ -webkit-box-align: center; -moz-box-align: center; -ms-flex-pack:center;/* IE 10 */ -webkit-justify-content: center; -moz-justify-content: center; justify-content: center;/* IE 11+,Firefox 22+,Chrome 29+,Opera 12.1*/ /* 垂直居中 */ -webkit-box-pack: center; -moz-box-pack: center; -ms-flex-align:center;/* IE 10 */ -webkit-align-items: center; -moz-align-items: center; align-items: center } .item{background-color:red;} 文字单行居中 很简单的 height 和line-height 一样就行了 多行的 话 就用padding 如今用 align-items: center 就可以了 如下这样居中呢 还是给容器使用: align-items: center 诸如此类 利用flex:1; 可以放弃使用float,但此项目不是 块元素的 需要dispaly:block ,国内有些浏览器就是这么蛋疼的。 多列的等高布局, 传统下 item1,item2, height都是atuo 需要给1,2设定不同的背景色,意淫的办法就是给容器设置左右border 颜色和 width,同时relative。tiem1,2 就absolute,定位 flex 布局下用 .item{ aling-items:stretch; } 同理 为三行时也是这样做法。 。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。 。。。。。。。。。。。 。。。。。。。。。 。。。。。。。 。。。。。 。。。 。 扯淡完毕。
最近做滚动效果,用到IScroll,当然就直接使用IScroll5了,在移动端用的iscroll-lite,轻而小但是 没翻页无限加载的功能 主要是 用到了在IScroll 里面在嵌入 一个IScroll ,在IScroll4 里还是有决绝的办法,IScroll5就无能为力了(ps:应该有办法,只是本人没找到),最后只把两个IScroll 做为同一级的元素,实现的。 iScroll5 事件使用 var myScroll = new IScroll(‘#wrapper‘); myScroll.on('api事件',function(){}); iScroll4 事件使用 var myScroll =new iScroll("wrapper",{ onRefresh: function(e){...}, onScrollMove: function(e){...}, onScrollEnd: function(e)...{} } 所以 iscroll4 里 子类 iScroll 可以这样阻止 事件冒泡 onBeforeScrollStart :function(e){ e.stopPropagation(); 在 iScroll5里 myScroll.on(‘beforeScrollStart',function(e){e.stopPropagation()}); 却是不能使用的 使用 iscroll 时 需要用到 click 事件的 click:true; 就ok了 当然 对于移动端 要是讲究都不会用默认的click 事件可以自定义tap 事件
angular 中 $cacheFactory.Cache 可以用来进行数据传递,主要是不同controller之间的传递,其方法有以下几个 put(key, value); get(key); removeAll(); destroy(); info(); app.controller('controller01',['$scope','$cacheFactory',function($scope,$cacheFactory){ var cache = $cacheFactory('cache01'); cache.put('name','sdsadas'); cache.put('sdsadas','sdsadas'); var name = cache.info(); console.log(name); }]); app.controller('controller02',['$scope','$cacheFactory',function($scope,$cacheFactory){ var cache = $cacheFactory.get('cache01'); var name = cache.get('name'); console.log(name); }]);
npm config set msvs_version 2012 --global 这样就可以了
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">我们可以在app.js里随便写一段 代码</span> app.get('/list',function(req,res){ res.render('list',{ title:'li', items:[213,23,3213,2321,2131,324] }); }); views list.ejs : <ul><%- partial('listitem', items) %></ul> 同时listitem.ejs :<li><%= listitem %></li>但是在express 后就不能使用的了 因为partial被放弃了 于是 改用include ejs文件分别对应 <% items.forEach(function(listitem){ %> <%= title%> <% include listitem %> <% }) %> <li><%= listitem %></li>
app.helpers 和app.dynamicHelpers 是express2.X使用的 分别为静态/动态 视图助手通过其注册函数, 例如 app.helpers({ <span style="white-space:pre"> </span>inspect: function(obj) { <span style="white-space:pre"> </span>return util.inspect(obj, true); } }); 但是在express 3.x后 这两个方法被废止, 3.x 使用的是 app.locals({ key2: value1, key2: value2 }) 就有如下: app.locals({ inspect: function(obj) { return util.inspect(obj, true); } }); 但是express4.x后变化就很大了 app.locals.key1 = value1; app.locals.key2 = value2; 如下: app.locals.inspect=function(obj){ return util.inspect(obj, true); }
css文本溢出显示省略号, 单行文 <div class="test"> asadsadsadsasadasdsadadasa</div> <pre name="code" class="html"> .test{ width: 50px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }省略号要和文字底部对齐 得是微软雅黑,宋体不行的,默认字体,firefox是下对齐,chrome不是 多行文本 <div class="test1"> asadsadsadsasadasdsadadasaasadsadsa dsasadasdsadadasaasadsadsadsasadasdsadadasaasadsadsadsasadasdsadadasaa sadsadsadsasadasdsadadasaasadsadsadsasadasdsadadasa</div> .test1{ height: 60px; width: 50px; display: -webkit-box; display: -moz-box; overflow: hidden; text-overflow: ellipsis; word-break: break-all; -webkit-box-orient: vertical; -webkit-line-clamp:3; } line-clamp firefox占不支持 适合用于移动端
web页面的自适应开发,要求就是跨平台,跨浏览器,一般mobile+pc,前几天写了几个pc端全屏页面,用的是百分比,在手机上看了下效果 相去甚远。这么看来mobile+pc 的自适应 有些时候就是个伪命题。 那对于移动端的自适应就 一般的宣传页面全屏滑动那种,用百分比,若复杂了就肯定不行,仔细看了下某淘的处理用的rem为单位,就学习在这个方法吧首先对于设计图,width 一般是640的。 rem:CSS3新增了一个相对单位rem(root em,根em),这样rem 就应该设定在html{font-size:1rem;}, 某淘对此的设定是根据手机宽度设定的, 必不可少的这句:<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">由js 控制的 , 首先是在苹果上不一样,苹果6就是<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no" 这样设置可以用 window.devicePixelRatio 设备像素比 window.clientWitdh*window.devicePixelRatio/10 ,这样就得出了font-size大小, 而andorid上有大部分就是、 <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> font-size就是 window.clientWitdh/10; 淘*里使用的代码: !function(J, I) { function H() { var d = E.getBoundingClientRect().width; d / B > 540 && (d = 540 * B); var e = d / 10; E.style.fontSize = e + "px", z.rem = J.rem = e; } var G, F = J.document, E = F.documentElement, D = F.querySelector('meta[name="viewport"]'), C = F.querySelector('meta[name="flexible"]'), B = 0, A = 0, z = I.flexible || (I.flexible = {}); if (D) { console.warn("将根据已有的meta标签来设置缩放比例"); var y = D.getAttribute("content").match(/initial\-scale=([\d\.]+)/); y && (A = parseFloat(y[1]), B = parseInt(1 / A)) } else { if (C) { var x = C.getAttribute("content"); if (x) { var w = x.match(/initial\-dpr=([\d\.]+)/), v = x.match(/maximum\-dpr=([\d\.]+)/); w && (B = parseFloat(w[1]), A = parseFloat((1 / B).toFixed(2))), v && (B = parseFloat(v[1]), A = parseFloat((1 / B).toFixed(2))) } } } if (!B && !A) { var u = (J.navigator.appVersion.match(/android/gi), J.navigator.appVersion.match(/iphone/gi)), t = J.devicePixelRatio; B = u ? t >= 3 && (!B || B >= 3) ? 3 : t >= 2 && (!B || B >= 2) ? 2 : 1 : 1, A = 1 / B } if (E.setAttribute("data-dpr", B), !D) { if (D = F.createElement("meta"), D.setAttribute("name", "viewport"), D.setAttribute("content", "initial-scale=" + A + ", maximum-scale=" + A + ", minimum-scale=" + A + ", user-scalable=no"), E.firstElementChild) { E.firstElementChild.appendChild(D) } else { var s = F.createElement("div"); s.appendChild(D), F.write(s.innerHTML) } } J.addEventListener("resize", function() { clearTimeout(G), G = setTimeout(H, 300) }, !1), J.addEventListener("pageshow", function(b) { b.persisted && (clearTimeout(G), G = setTimeout(H, 300)) }, !1), "complete" === F.readyState ? F.body.style.fontSize = 12 * B + "px": F.addEventListener("DOMContentLoaded", function() { F.body.style.fontSize = 12 * B + "px" }, !1), H(), z.dpr = J.dpr = B, z.refreshRem = H, z.rem2px = function(d) { var c = parseFloat(d) * this.rem; return "string" == typeof d && d.match(/rem$/) && (c += "px"), c }, z.px2rem = function(d) { var c = parseFloat(d) / this.rem; return "string" == typeof d && d.match(/px$/) && (c += "rem"), c } } (window, window.lib || (window.lib = {}));
<pre name="code" class="html">竖直滚动 jquery 文字 图片 .box4{height:200px;overflow:hidden;} <pre name="code" class="html"> .box4 ul{height:auto;} <div class="box4"> <ul> <li> </li> <ul> </div> $('.box4').myScroll({ speed:500, scroll_speed:500, see:8 }); (function($){ $.fn.myScroll = function(options){ var defaults = { speed:40, see:4, rowHeight:35, scroll_speed:200 }; var opts = $.extend({}, defaults, options),Increase=0; var _self = $(this), speeds = opts['speed'], $son = $(this).find('li'), Reduce=scroll_li = $son.length - opts['see'], son_ht = $son.innerHeight(), scroll_spd = opts.scroll_speed; function marquee(obj,step){ obj.animate({ scrollTop: step+'px' },scroll_spd,function(){ intervalFun(); }); } function intervalFun(){ var interval = setInterval(function(){ var _step =0; if(scroll_li > Increase){ ++Increase; _step = son_ht*Increase; marquee(_self,_step); clearInterval(interval); }else if(Reduce != 0){ --Reduce; _step = son_ht*Reduce; marquee(_self,_step); if(Reduce < 1){ Reduce = scroll_li; Increase=0; } clearInterval(interval); } },speeds); } intervalFun(); } })(jQuery);
ajax 区别: xmlHttp.open(method, url, async); async:布尔值,用来说明请求是否为异步模式。async是很重要的,因为它是用来控制JavaScript如何执行该请求。 当设置为true时,将以异步模式发送该请求,JavaScript代码将继续执行而不再等待响应,且必须使用一个事件处理函数来监控请求的响应。 如果将async设置为false,则将以同步模式发送该请求, JavaScript将等接收到响应后再继续执行剩余代码。 这意味着如果响应时间很长,则用户在浏览器收到响应之前是将无法与其交互的。 基于这个原因,Ajax应用程序开发的最佳实践是,使用异步请求来实现数据获取,使用同步请 求来实现与服务器之间发送和接收简单的消息。 用同步模式来发送该请求(将open()方法的第三个参数设置为false),可以使你在调用send()方法之后马上对其响应进行处理。 这对于想让用户交互等待响应,或希望只接收很少的数据(例如,小于1KB)的应用场景是很有用的。而对于通常的数据量或较大的数据量而言,最好还是使用异步调用。
之前看到了 这么一段代码 子类中 出现了 和父类 成员同名的 成员变量后的取值问题 代码如下 class SuperClass { private int number; public SuperClass() { this.number = 0; } public SuperClass(int number) { this.number = number; } public int getNumber() { number++; return number; } } class SubClass1 extends SuperClass { public SubClass1(int number) { super(number); } } class SubClass2 extends SuperClass { public SubClass2(int number) { super(number); } } package learn01; public class SubClass extends SuperClass { private int number; public SubClass(int number) { super(number); } public int getNumber() { number++; return number; } public static void main(String[] args) { SuperClass s = new SubClass(20); SuperClass s1 = new SubClass1(20); SuperClass s2 = new SubClass2(20); System.out.println(s.getNumber()); System.out.println(s1.getNumber()); System.out.println(s2.getNumber()); } } 输出的 结果 : 1 3 3 开始 也觉得奇怪了 断点调试 再看看 出现了 两个 number 而 s1.getNumber() 只有一个 调试进去 发现 number=20 传递给了父类, 子类还是为0的 SubClass 类里 重写 SubClass(int number) 调用了 父类构造 值传递到父类 父类使用了 父类的 成员变量 子类使用 子类的 成员变量
今天 写js 时 要动态获取 window 的 高度,在ie 和 chrome 下都能获取 正确的高度 但firefox 就是要高出300 px 左右 先用的jquery 的 $(window).height(); 得到的结果是不对的 改成 window.screen.availWidth 也高出了 用qq截图 看出 firefox是把浏览器 工具栏的高度 也算上了 设计就是一个满屏的 于是 给新加了css html,body{ height:100%; } firefox 就能 获得正确的高度 了
console.log(typeof document.createElement); 在ie9 以下不包括ie9 返回的是 object 儿ie9 以上返回的是 function; 这样是因为: DOM对象是宿主对象,IE 及更早版本中的宿主对象是通过COM 而非JScript 实现的。因此,document.createElement()函数确实是一个COM 对象,所以 typeof 才会返回"object" 宿主对象不是引擎的原生对象,而是由宿主框架通过某种机制注册到JavaScript引擎中的对象。