三木

你是谁就遇到谁


  • 首页

  • 标签

  • 归档

npm 编写发布vue插件记录

发表于 2018-01-21 |

项目背景

本文记录自己学习npm编写vue插件,并导出包文件,发布到npm平台,供其他地方全局调用。

过程记录

首先使用vue-cli脚手架下载一个简版的vue框架webpack-simple,然后在src文件中新建lib文件夹用来存放插件代码,在lib中新建index.js和index.vue,在index.vue中写一个按钮,表示是我们的插件

index.vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="button">
<button @click="handler">这是一个按钮</button>
</div>
</template>

<script type="application/ecmascript">
export default {
name: 'test-button',
data() {
return {};
},
methods: {
handler() {
console.log( '点击按钮' );
}
}
};
</script>

<style type="text/less" lang="less" scoped>
.button {
width: 100px;
height: 20px;
text-align: center;
margin: 0 auto;
}
</style>

在index.js中将组件模块化,并导出组件

index.js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Index from './index.vue';

const button = {

//通过vue提供的install方法来将组件注册到vue全局中。
install( Vue ) {
Vue.component( Index.name, Index );
}
}

if ( typeof window !== 'undefined' && window.Vue ) {
window.Vue.use( Index );
}

// 将组件导出
export default button;

打包发布

插件已经编写完毕,我们只需要访问index.js文件就能使用该组件,接下来就是通过webpack将组件代码打包,在项目根目录新建一个webpack-library.js文件来编写webpack打包组件的配置,里面的配置跟webpack.config.js大致相同,但是需要修改文件打包入口出口。

1
2
3
4
5
6
7
8
9
10
11
12
13
//  webpack.config.js的入口是main.js文件,表示是整个项目的入口,但是我们这里只是要打包按钮组件,所以入口要改为lib下的index.js文件
entry: './src/lib/index.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
// 为了将组件打包出来的文件区分出来,我们要重命名打包后的js文件名
filename: 'test-button.js',
// 插件的名字
library: "TestButton",
// 不懂,反正写'umd'就是了
libraryTarget: "umd",
umdNamedDefine: true
},

接下来是package.json的配置:
声明一个包文件入口,表示别人下载了组件后,使用时的入口文件,其实就是上面配置后,生成的文件地址

1
"main": "./dist/test-button.js",

新建一个npm命令用来打包组件

1
2
3
4
5
"scripts": {
···
"publish": "webpack --config webpack-library.js"
···
},

然后运行npm run publish开始打包,再运行npm publish发布npm包,发布成功后,我们就可以在npm上看到自己写的包了,使用时,就运行npm install vue-plugin,然后在vue项目中引入,注册:

1
2
import TestButton from 'vue-plugin';
Vue.use( TestButton );

使用:

1
<test-button></test-button>

在项目中就可以看到自己编写的组件了。

(完)

vue 采坑总结

发表于 2017-12-31 |

项目背景

本文记录自己学习使用vue中所踩到的坑。

采坑记录

1、postcss配置
在项目中,如果是用rem单位来布局的话,css中最长用到的就是autoprefixer和px2rem,这两个都是postcss的插件,那么如何在webpack中配置postcss呢?就连webpack上的文档经过亲手尝试,也是报错,经过多重搜索,最后是配置这样的,在module中的rules选项中配置,因为是在解析vue文件时解析css,所以要配在匹配vue的文件下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
postcss: [
require( 'autoprefixer' )( {
browsers: [ 'last 10 versions', 'Firefox >= 20', '> 1%', 'iOS 4', 'android >= 2.0', 'and_uc > 1' ]
} ),
require( 'postcss-plugin-px2rem' )( {
rootValue: 10,
minPixelValue: 2,
} )
]
}
},

然后再加上自适应的方法,动态判断屏幕大小来改变字体大小,如果设计稿宽度是375px:

1
2
3
4
5
6
7
8
9
var response = function () {
var w = document.documentElement.clientWidth;
document.documentElement.style.fontSize = w / 37.5 + 'px';
};
window.onresize = function () {
response();
clearTimeout( this.responseTimer );
this.responseTimer = setTimeout( response, 300 );
};

2、webpack代码分割
webpack提供了代码分割功能,可以根据路由来动态加载所需模块,减小了初次加载的体积,在编写路由模块时,将路由component动态引入:

1
2
3
4
5
6
7
8
9
const Index = ( r ) => require.ensure( [], () => r( require( './index.vue' ) ) , 'moduleA');
import ChildrenA from './childrenA/router';

export default {
path: '/a',
name: 'module-a',
component: Index,
children: [ ChildrenA ]
}

第一行代码中,moduleA表示为该component命名,当webpack打包时,会将所有同名的组件打包到一个js文件中,当加载到/a的路由时,就会加载该js文件,这个文件中包含了所有名字为moduleA的模块。

3、vuex局部模块
vuex可以理解为vue中专门用来处理事务数据的,里面变量在全局可访问,当项目变大,变量变多时,vuex提供了modules属性,可以将数据以模块来划分。

1
2
3
4
5
6
7
8
9
10
11
import moduleA from '../moduleA/store';
import moduleB from '../moduleB/store';

const modules = {
moduleA,
moduleB
};

export default new Vuex.Store({
modules
})

moduleA为子模块,它又可以在内部继续划分为多个子模块:

1
2
3
4
5
6
7
8
9
10
export default {
namespaced: true,
modules: {
children: childrena
},
state,
types,
mutations,
actions
}

需要注意的是,当在内部划分成子模块时,要加上namespaced属性,这样子模块就可以继承父模块的命名空间,当在组件中调用moduleA下的子模块的action时,就要加上前缀:

1
2
3
4
...mapActions( {
'setmoduleA': 'moduleA/moduleAfn',
'setmoduleB': 'moduleB/children/moduleBfn'
} ),

4、webpack proxy代理
在devServer中添加proxy代理,要添加pathRewrite属性,这样,当我们请求/api/v2/api/Community/Navigations时,就会转到http://www.baidu.com/v2/api/Community/Navigations

1
2
3
4
5
6
7
8
9
proxy: {
'/api/': {
target: 'http://www.baidu.com/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}

5、webpack压缩问题
webpack自带的压缩功能uglifyjsplugin方法无法对ES6的语法进行压缩,要先在module中添加bable-loader,babel会把ES6转为ES5的语法,然后就可以压缩代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module:{
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
}
}
],
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
]

(未完待续)

vue-cli router history 服务端配置

发表于 2017-12-10 |

项目背景

最近在做一个项目,用vue-cli搭建,build之后,传到服务器上,手动输入二级子路由,页面显示404。

问题原因

手动输入链接,都是要走服务器http请求的,而vue build之后是一个单页应用,所有的路由配置都是通过index.html用js去解析,所以输入的子路由通过服务器解析后并未找到该文件,因此报错404。

解决方案

既然要通过index.html去跳转,我们就要把所有的手动输入子路由都要转向到index.html文件,由于我用的是apache,vue-router也说明了history模式需要后台去配置,因此我们在httpd.conf文件中要开启apache的rewrite模块,然后设置允许通过.htaccess文件来设置override规则。

httpd.conf文件:

1
2
3
4
5
6
7
<Directory "/home/sam">
···

AllowOverride All

···
</Directory>

然后在项目根目录中添加.htaccess文件,文件中写入vue-router文档中的配置:

1
2
3
4
5
6
7
8
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>

RewriteBase /指向的是服务器根目录,我的项目是在二级目录,即/jxvote/,因此要把RewriteBase设置为RewriteBase /jxvote/, RewriteRule . /index.html [L]设置为RewriteRule . /jxvote/index.html [L]。

最后再把router中的base设置为:base:'/jxvote/'。

这样就可以使用history模式下,手动输入子路由打开页面了。

(完)

微信大屏幕抽奖项目总结

发表于 2017-12-10 |

项目背景

商家要在led上投放个抽奖页面,就是用户扫描二维码进入h5页面报名签到,签到成功后,led上就要及时显示已签到用户信息。

实现过程

由于页面要及时显示已签到用户,那么就要后端轮询去查询数据库是否有新用户签到,如果有新用户,就发送给前端。后端我用的是php,于是就打算尝试一下SSE(Server Send Event),新插入的数据中,建立一个send字段来记录是否将已签到用户发送给前端,新插入已签到用户数据时,默认记录send字段值是-1,php去查询send=-1的数据,然后发送给前端,并将这些数据的send值设为1。php的SSE需要声明header('Content-Type: text/event-stream')来表示这是一个事件流脚本,并需要设置不缓存header("Cache-Control:no-cache,must-revalidate"),将数据组织好后,需要这样输出:

1
2
3
echo "retry: 3000\n\n";
echo "data:" . json_encode($responseinfo)."\n\n";
flush();

retry表示间隔时间是3000ms,输出的数据必须以data开头,以换行符\n结尾,前端收到的数据是字符串格式,所以需要后端组织成json,前端再把数据转成json。最后flush()表示再次执行。

(完)

手机端编辑器总结

发表于 2017-12-10 |

项目背景

由于公司业务是做书籍阅读类的。所以产品提出要在webapp的发帖页面中,能输入文本,插入图片,和插入书籍。

采坑过程

本来手机端做编辑器可以用现成的插件,文本和图片都是比较容易解决。但是为了插入书籍,只有自己来做这个功能。

坑 One

输入框用div的contenteditable属性来做,但是在输入框中输入enter键换行时,换行的文本无法获得焦点,是因为换行时会自动生成div,但生成的div并没有contenteditable属性,于是就监听enter键事件,阻止默认事件,动态重新创建contenteditable的div。

坑 Two

删除时,删到开头,要把光标移到上一个文本div。需要监听delete键,手动去删除空白的div,再把光标移动到上一个文本的末尾。iphone端表现良好,但是安卓端无法监听到delete键,所以就意识到用div还是不可行的。

于是,还是要尝试用原生的textarea,插入文本就是插入textarea,插入图片和书籍就是在文本节点下面插入图片和书籍节点,但在图片和书籍节点后面要再插入个空白的textarea可以让其继续输入。

但是textarea的缺点在于无法动态改变他的高度,但是在网上找到个方案,核心代码如下:

1
2
3
4
<div class="expandingArea" data-type="textarea">
<pre><span></span><br></pre>
<textarea data-type="text"></textarea>
</div>

每一个创建的textarea标签都需要这样包裹一下,为的是让其可以自适应高度。

以上代码在输入textarea时,监听input事件,把textarea的value复制给span标签,expandingArea用相对定位,expandingArea的高度由span标签撑开,span的高度可以跟随文本变化,span的文本又是跟textarea保持一致。这样便可以实现textarea的高度自适应了。但是要注意一点:textarea和pre里的字体样式必须保持一致

css代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.expandingArea {
position: relative;

/*textarea和pre里的字体样式必须保持一致*/
textarea,
pre {
margin: 0;
padding: 0;
outline: 0;
border: 0;
font-size: 15px;
line-height: 17px;
min-height: 15px;
color: #474e58;
width: 100%;
white-space: pre-wrap;
word-wrap: break-word;
}

textarea {
position: absolute;
top: 0;
left: 0;
height: 100%;
resize: none;
overflow: hidden;
}
pre {
display: block;
visibility: hidden;
}
}

插入书籍时,拿到书籍的json然后生成一个div放置在文本div下面,并在书籍div后生成一个新的空白文本div,这样就可以继续在书籍下面输入文本,当删除文本时,假如删到开头,就把当前文本的value附加给上一个文本div,再删除当前的文本节点。这样整个需求算是完成了。

(完)

设计模式学习之五-状态模式

发表于 2017-10-22 |

状态模式应用场景

这个模式在工作中也比较常见,就是点击按钮,根据按钮目前的状态来决定所要执行的操作,2个状态的比如开关按钮。我们普遍会拿if来做判断。但是状态比较多的情况下,多个if就比较low了。于是就应用到了状态模式

状态模式应用案例

比如我们在按灯泡开关的时候,灯泡的状态分为关灯,弱光,强光,超强光。

先上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var OffLightState = function(light){
this.light = light;
}

OffLightState.prototype.buttonWasPressed = function(){
console.log('弱光');
this.light.setState(this.light.weakLightState);
}

var WeakLightState = function(light){
this.light = light;
}

WeakLightState.prototype.buttonWasPressed = function(){
console.log('强光');
this.light.setState(this.light.strongLightState);
}


var StrongLightState = function(light){
this.light = light;
}

StrongLightState.prototype.buttonWasPressed = function(){
console.log('超强光');
this.light.setState(this.light.superstrongLightState);
}

var SuperStrongLightState = function(light){
this.light = light;
}

SuperStrongLightState.prototype.buttonWasPressed = function(){
console.log('关灯');
this.light.setState(this.light.offLightState);
}


var Light = function(){
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.superstrongLightState = new SuperStrongLightState(this);
this.button = null;
}

Light.prototype.init = function(){
var button = document.createElement('button'),
_self = this;

this.button = document.body.appendChild(button);
this.button.innerHTML = '开关';

this.currState = this.offLightState;

this.button.onclick = function(){
_self.currState.buttonWasPressed();
}
}

Light.prototype.setState = function(newState){
this.currState = newState;
}

var light = new Light();
light.init();

这段代码的核心思想就是为每个状态创建个对象,然后每个状态对象里都声明一个buttonWasPressed点击按钮方法,重要的是每个状态里都要显式的写出当前状态的下一个状态,然后切换到下一个状态,这样每次点击按钮,其实触发的是不同状态对象里的buttonWasPressed方法。

(完)

设计模式学习之四—职责链模式

发表于 2017-10-14 |

职责链者模式应用场景

该模式在业务中比较常见,常见场景是:检测用户是否满足A条件,不满足就检测是否满足B,再不满足就检测是否满足C,直到找到一个他满足的一个条件,但是从A-C是有优先级的,按照优先级一次降级检测。

职责链者模式应用案例

现在我们来模拟一个场景使用该模式:
用户来买手机,目前有三个优惠政策,分为优惠500,优惠200,无优惠,售罄。当然,优先级也是按照优惠大小来划分。当用户来买时,我们就要检测他满足哪种优惠。
随手来码代码的话:我们会用if判断来检测他满足哪种优惠,但是if层级太多的话,会显得代码非常乱,于是就用到职责连模式。
我们来看代码,首先列出三种优惠条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var order500 = function( orderType, pay, stock ){
if ( orderType === 1 && pay === true ){
console.log( '500 元定金预购,得到 100 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var order200 = function( orderType, pay, stock ){
if ( orderType === 2 && pay === true ){
console.log( '200 元定金预购,得到 50 优惠券' );
}else{
return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
}
};
var orderNormal = function( orderType, pay, stock ){
if ( stock > 0 ){
console.log( '普通购买,无优惠券' );
}else{
console.log( '手机库存不足' );
}
};

以上代码,把每个优惠政策分别声明,如果满足该优惠条件,则执行优惠,否则就传给下一节点,但是我们需要把这些节点关联起来才行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//  声明一个链
var Chain = function( fn ){
this.fn = fn;
this.successor = null;
};
// 设置下一节点
Chain.prototype.setNextSuccessor = function( successor ){
return this.successor = successor;
};
Chain.prototype.passRequest = function(){
// 检测当前节点是否满足条件
var ret = this.fn.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
// 如果不满足,就递交给下一节点来执行
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
// 如果满足当前节点条件,就返回当前节点的处理结果
return ret;
};

然后实例化各个节点,让他们串联起来:

1
2
3
4
5
6
7
8
//  实例化各个节点
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );

// 通过 setNextSuccessor方法跟下一个节点关联起来
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );

检验一下成果:

1
2
3
4
chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

(完)

python学习-图片转字符画

发表于 2017-10-14 |

前言

最近在学习python,惊讶于其处理数据方面的方便,以及目前机器学习潮流python占了不可动摇的地位,未来也是一股潮流,于是在熟悉python基本语法之后,决定先从小工具做起,慢慢练习,熟练掌握python的用法,目前觉得python在做一些日常小工具上还是挺方便的,这样对于自己来说,也不会只局限于“前端”,要往“软件工程师”方向发展。

原理

整个代码量不多,其中原理便是:颜色的色值区间为0-255,使用PIL的image模块读取图片的颜色值,例如(255,255,255,1),前三个数表示颜色值,第四个表示透明度。我们创建一个字符串数组,例如

1
2
3

把读取到的色值按从小到大取数组中的字符串,然后拼凑字符串,写入文件。把图片的像素转为色值的核心算法在于:
```gray = 0.2126 * r + 0.7152 * g + 0.0722 * b

我们需要获取在shell中输入的图片名,所以用argparse包来获取shell中的输入的变量。

以下是全部代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from PIL import Image
import argparse

# 灰度值小(暗)的用列表开头的符号,灰度值大(亮)的用列表末尾的符号。
# gray = 0.2126 * r + 0.7152 * g + 0.0722 * b

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
length = len(ascii_char)

# 颜色色值 0-255
MaxValue = 255

def get_char(r,g,b,alpha=256):
if alpha == 0:
return ' '

gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
return ascii_char[int(gray / MaxValue * length )]

parser = argparse.ArgumentParser()

parser.add_argument('file')
parser.add_argument('--output')
parser.add_argument('--width', type = int, default=80)
parser.add_argument('--height', type = int, default = 80)

args = parser.parse_args()

IMG = args.file
WIDTH = args.width
HEIGHT = args.height
OUTPUT = args.output or 'output.txt'


if __name__ == '__main__':

im = Image.open(IMG)
im = im.resize((WIDTH, HEIGHT), Image.NEAREST)

txt = ""

for i in range(HEIGHT):
for j in range(WIDTH):
txt += get_char(*im.getpixel((j, i)))
txt += '\n'

print(txt)

with open(OUTPUT, 'w') as f:
f.write(txt)

(完)

nginx反向代理和负载均衡配置

发表于 2017-09-03 |

nginx介绍

nginx作为静态服务器,只能解析静态文件,像php文件就属于动态文件,所以无法打开php文件,因为nginx要想解析php文件,须配合php-fpm使用,nginx当需要解析php文件的时候就会把解析任务交给php-fpm服务,php-fpm再把解析结果返给nginx,最终返回给用户。最近研究了下nginx,使用它的反向代理和负载均衡来解决一些常见问题,下面记录一下。

反向代理

现在越来越多的前后端分离已经不只是代码上的分离,就连服务器也各自使用,所以前后端通信就会有跨域的现象出现,以往解决跨域就是使用jsonp,但是目前越来越多的人使用nginx反向代理,即前端请求不直接发给后端,而是发到nginx服务器上,nginx再根据你的配置进行转发,由nginx来访问后端地址,相当于是一个中介。下面是配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server
{
#监听端口
listen 81;

#域名
server_name 120.27.104.77;

index index.html index.htm index.php;

#站点目录
root /home/nginx;

location /three/
{
proxy_pass http://120.27.104.77:80/three/;
index index.html index.php;
proxy_set_header Host $host;
proxy_buffer_size 64k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
access_log on;
access_log /usr/local/webserver/nginx/logs/www_access.log main;
}

location ~* \.php$
{
root /home/sam;
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi.conf;
access_log on;
access_log /usr/local/webserver/nginx/logs/www_phpproxy_access.log main;
}
}

上面的配置表示nginx监听81端口,我自己把80端口分给了apache,当访问81端口下的/three目录下的静态文件时,nginx把请求转给80端口下的three文件,其实81端口下是没有任何文件的,nginx只是起到了转发请求的作用。location匹配php文件,当访问的是php文件,就把这个文件请求转到home/sam文件夹下,home/sam是apache的根目录,这样,nginx就把php请求转给了apache。最终,当访问http://120.27.104.77:81/three/index.php,nginx就把请求转给http://120.27.104.77:80/three/index.php,代理完成。

负载均衡

负载均衡指的是当一个服务器上的项目有很多用户时,服务器压力增大,需要其他服务器来帮忙分担请求,但是用户输入的网址不能变,所以,就应用到了nginx的负载均衡,负载均衡还是通过反向代理的方式,把请求转发给其他服务器,但是需要每一台服务器上的文件同步。下面是配置示例:

在http模块下添加负载均衡配置(默认使用的是轮询方式,weight表示是权重,下面表示82端口和80端口请求次数是1:1):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream myserver {
server 120.27.104.77:82 weight=1;
server 120.27.104.77:80 weight=1;
}

server模块下添加location规则:

location ~*test.php {
proxy_pass http://myserver;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

80端口依然是apache,82端口是nginx服务器,当我访问81端口下的test.php,nginx就把请求转给82端口,再次刷新就把请求转给80端口,依次反复,这样就实现了负载均衡,相当于是有两台服务器在支撑。多设几个就有多个服务器支撑。

(完)

反馈页面项目总结

发表于 2017-08-20 |

项目背景

这个页面是用vue构建,用来内嵌APP中进行用户反馈交互,反馈页面是一个web聊天记录页面,也可以编辑内容上传图片发送对话。

踩到的坑

1、每一条对话数据需要显示数据时间,连续一分钟内的几条对话数据只显示第一条对话的时间。通过v-for循环列出数据时间,每一条循环通过index去判断当前item与index-1的item的时间差,但是第一条会报错,因为取不到第0条的数据。
解决方案:因为vue数据绑定属性只能写变量,不能写语句,所以就无法在标签中去判断是否是第一条数据。就算是用二元操作符也无法识别,所以用(dialogItems[index-1] && dialogItems[index-1].createDate) || 0)就可以筛选出第一条,并把他的上一条设为0.

2、后端返回的时间格式是2017-10-10 13:30:00的格式,所以用Date.parse()来转化成时间戳进行比较,但是在手机端无法识别-格式的时间,所以Date.parse()无效,需要用.replace(/-/g,'/')来转化成/格式,再用Date.parse()

3、对话页面需要一进入就把页面拉到最底部来显示最新对话,可以用scrollTo(0,列表高度)来让页面滚动到底部。

4、输入页面的输入框textarea需要根据输入文本高度增加而增加,可以用vue的input事件来监听textarea的scrollHeight,使他的高度等于scrollHeight即可动态变高。

5、编辑页面上传图片需要有删除功能,但是当点击删除时,src数据绑定的变量清空后,src并没有清空,需要再把src设为一个字符串,这样页面上的图片便可删除,也可以用vue的$nextTick的回调方法来清空。

6、上传图片使用formData来提交,但是一直提交不过去,后来得知,是项目有axios的config配置,把上传的内容给序列化了,在axios中设置header:multipart/form-data,然后在config中去匹配该header使其不序列化即可。

7、在对话列表页点击编辑需要跳转到反馈编辑页面并自动弹出手机软键盘,使用focus()方法可以弹出。但是需要在对话列表页点击按钮才弹出,直接进入编辑页面并不会弹出。猜测是手机监测用户交互行为所设的屏障。

8、上传完照片后,有个替换功能,可以重新选择照片,就是点击替换按钮去触发input事件,这里有的mouseEvent,

1
2
const evt = new MouseEvent( 'click', { bubbles: false, cancelable: true, view: window } );
this.$refs.fileBtn.dispatchEvent( evt );

这样就可以触发input file的点击事件

(完)

1234…8

三木

前端开发,web,javascript,技术博客,程序员

79 日志
17 标签
© 2023 三木
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4| 冀ICP备16017076号