uni-app全栈仿微信开源项目系列(一)


叮咚项目参考文档 v1.0

项目技术栈:

?前端:uni-app + nvue 实现原生页面渲染、同时兼容多端。

后端:Egg.js + MySQL + Redis 实现后端API服务。

不使用第三方组件库,自己写一套。

NVUE需要注意的点

  1. 在NVUE中引入字体图标需要参考Weex的引入规则 点我查看

  2. NVUE模式下的页面默认是Flex布局

  3. iconfont图标应该放在text标签中包裹,不能直接使用view标签包裹

  4. NVUE中的屏幕都是以750像素为基准

  5. text组件换行问题,text组件中的内容如果有换行,显示的效果也会换行

  6. Weex是从上到下进行渲染的,如果你的元素有定位之类的脱离文档流的需求,元素最好按顺序写,否则z-index可能也救不了你。

  7. 目前仅 iOS 支持 box-shadow 属性,Android 暂不支持,可以使用图片代替。每个元素只支持设置一个阴影效果,不支持多个阴影同时作用于一个元素。

uni-app中 vue 和 nvue 的区别:

uni-app是逻辑和渲染分离的。渲染层,在app端提供了两套排版引擎:小程序方式的webview渲染,和weex方式的原生渲染。
两种渲染引擎可以自己根据需要选。vue文件走的webview渲染,nvue走的原生渲染。
组件和js写法是一样的,css不一样,原生排版的能用的css必须是flex布局,这是web的css的子集。当然什么界面都可以用flex布出来。不懂flex布局就自己学。

一般情况下用vue就可以了。如果是app且有部分场景vue页面的性能不满足你的需求时,这个页面可以改用nvue页面。如果app里同时存在同名的vue和nvue页面,在app端会优先执行nvue页面,而其他端仍然优先vue页面。

区别和适用场景这文档里写的很清楚:https://uniapp.dcloud.io/nvue-outline

记录一些踩过的坑

  1. 【报Bug】2.2.5版本纯nvue的uniapp模式子组件使用插槽报错问题

1.环境搭建和项目创建

需要安装的插件:

  1. 内置浏览器
  2. App真机运行
  3. uni-app App调试
  4. less编译
  5. scss/sass编译
  6. stylus编译
  7. es6编译

创建项目:

项目类型为:uni-app,使用默认模版。

开启原生渲染:

uni-app在App端,支持vue页面和nvue页面混搭、互相跳转。也支持纯nvue原生渲染。

启用纯原生渲染模式,可以减少App端的包体积、减少使用时的内存占用。因为webview渲染模式的相关模块将被移除。

在manifest.json源码视图的"app-plus"下配置"renderer":"native",即代表App端启用纯原生渲染模式。此时pages.json注册的vue页面将被忽略,vue组件也将被原生渲染引擎来渲染。

如果不指定该值,默认是不启动纯原生渲染的。

 // manifest.json    
    {    
         // ...    
        /* App平台特有配置 */    
        "app-plus": {    
            "renderer": "native", //App端纯原生渲染模式
        }    
    }

使用uni-app编译模式:

 // manifest.json    
    {    
         // ...    
        /* App平台特有配置 */    
        "app-plus": {    
            "renderer": "native", //App端纯原生渲染模式
            "nvueCompiler" : "uni-app",
        }    
    }

2.全局配置

2.1 引入全局样式

将封装好的free.css库引入到项目中。

2.2 引入自定义图标库

全局加载自己的字体图标库并且做多端适配:





如果对跨端兼容和条件编译语法不熟悉,可以参考官方文档

2.3 配置tabbar底部导航

修改package.json配置文件、添加tabbar配置。

这里的 tabbar 的 icon 图标大小为 81*81。

{
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
			"path": "pages/tabbar/index/index",
			"style": {}
		},
		{
			"path": "pages/tabbar/mail/mail",
			"style": {}
		},
		{
			"path": "pages/tabbar/find/find",
			"style": {}
		},
		{
			"path": "pages/tabbar/my/my",
			"style": {}
		}
	],
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "叮咚",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"tabBar": {
		"color": "#000000",
		"selectedColor": "#08C261",
		"borderStyle": "black",
		"backgroundColor": "#F7F7F7",
		"list": [{
				"iconPath": "static/tabbar/index.png",
				"selectedIconPath": "static/tabbar/index-select.png",
				"pagePath": "pages/tabbar/index/index",
				"text": "首页"
			},
			{
				"iconPath": "static/tabbar/mail.png",
				"selectedIconPath": "static/tabbar/mail-select.png",
				"pagePath": "pages/tabbar/mail/mail",
				"text": "通讯录"
			},
			{
				"iconPath": "static/tabbar/find.png",
				"selectedIconPath": "static/tabbar/find-select.png",
				"pagePath": "pages/tabbar/find/find",
				"text": "发现"
			},
			{
				"iconPath": "static/tabbar/my.png",
				"selectedIconPath": "static/tabbar/my-select.png",
				"pagePath": "pages/tabbar/my/my",
				"text": "我的"
			}
		]
	}
}

配置细节参考官方文档

2.4 配置globalStyle

取消APP端下的原生导航栏、滚动条。

"globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "叮咚",
    "navigationBarBackgroundColor": "#F8F8F8",
    "backgroundColor": "#F8F8F8",
    "app-plus":{
        "titleNView":false,
        "scrollIndicator":"none"
    }
}

3.聊天列表页开发

3.1 头部导航栏组件开发







3.2 [*]图标按钮组件封装

这个地方有一个坑:

NVUE 中如果使用 iconfont 的话就必须使用text标签进行包裹,如果要封装成组件,通过slot动态传递iconfont 的 16 进制值的话就会报错,因为 slot 会转换成text标签,又因为text标签里面不能再次嵌套text标签,所以报错,这个也是近期才发现的,以前看别人写没有问题,怎么解决呢?通过 props 传参。

封装的组件: free-icon-button.vue







调用组件的文件 index/index.nvue


这里面还有一个细节:

通过props方式传参的话 iconfont 的 16 进制值就不能写成 ,必须写成\ue682

这个问题通过查资料 + 反复实践大约耗时 30 分钟,因此记录一下这个坑。

3.3 封转头部导航组件

uni-app的普通组件中使用onLoad、onShow不生效?,要用created、mounted,为什么?

这个就要从uni-app的生命周期说起了。。。

uni-app有 3 类生命周期:

  1. 应用生命周期
  2. 页面生命周期
  3. 组件生命周期

应用生命周期:

重点是应用生命周期只能在App.vue中监听,其他页面监听无效,所以不要用错了。

页面生命周期:

需要注意的是:onLoadonReady只会触发一次,这是官方没有说的,所以还是要多实践!

以上是常用的几个,想了解全部的参考官方文档

组件生命周期:

uni-app 组件支持的生命周期,与vue标准组件的生命周期相同。这里没有页面级的onLoad等生命周期:

以后编写组件的时候就要细心点,页面组件就用页面的生命周期,普通组件就用组件的生命周期,别乱搞给自己挖坑。

index/index.nvue 页面代码







free-nav-bar.vue 组件代码







这里使用计算属性配合动态计算 状态栏 + 导航栏的高度,这个高度给占位的view标签用,防止列表被导航栏覆盖。

3.4 开发聊天列表组件

index/index.nvue 文件代码







3.5 封装头像组件

free-avatar.vue 文件代码







index/index.vue 文件中使用


3.6 badge组件封装

封装前的代码:

index/index.nvue 角标元素代码


9

角标css代码

.badge {
    padding-left: 14rpx;
    padding-right: 14rpx;
    padding-bottom: 6rpx;
    padding-top: 6rpx;
    position: absolute;
    right: 15rpx;
    top: 15rpx;
}

封装后的代码:

free-badge.vue







使用badge


3.7 封装聊天列表组件

需要注意view标签监听@longpress长按事件无法获取坐标值,此时换成 div 标签即可。

free-media-list.vue 文件代码







使用组件的代码



    

3.8 封装全局mixin

例如有以下场景:

多个组件内需要对日期时间进行格式化处理,这时我们已经在某个组件内定义了filters过滤器,其他组件也需要使用这个过滤器,难道我们一个个 CV 过去吗?显然太low,而且以后这个filters发生变动你其他引用了这个filters的组件代码也得跟着改,也就会造成大量的重复代码冗余,维护起来极其不方便,此时就可以用 Vue 的mixin特性来解决这个问题,当然有人会说 "我用全局过滤器也可以啊",是的,但我就要用mixin

官方的解释和demo已经很详细了,参考官方文档

mixin/free-base.js 文件代码

import $Time from '@/common/free-lib/time.js'
export default {
	filters: {
		formatTime(value) {
			return $Time.gettime(value)
		}
	}
}

使用mixin

import freeBase from '@/common/mixin/free-base.js'

export default {
    mixins: [freeBase],
    props: {
    },
    data() {
        return {}
    },
    computed: {},
    watch: {},
    created() {},
    mounted() {},
    methods: {}
}

到这里就已经把处理时间的过滤器混入到组件中了,直接在模版中使用即可。

3.8 开发弹出层组件

通过 API 动态获取的参数都为 px,如果需要与 rpx 单位进行计算,要提前使用 uni.upx2px方法将 rpx 转换成px。

mounted() {
    // 获取系统信息
    let info = uni.getSystemInfoSync()
    this.maxX = info.windowWidth - uni.upx2px(this.bodyWidth) - 30
    this.maxY = info.windowHeight - uni.upx2px(this.bodyHeight) - 30
},

长按事件的跨端兼容问题。

@longpress事件在nuve的原生APP环境和微信小程序环境下获取的参数属性不同,因此需要写2套代码兼容多端。

long(e) {
    let x = 0
    let y = 0
    // #ifdef APP-NVUE
    if(Array.isArray(e.changedTouches) && e.changedTouches.length > 0) {
        x = e.changedTouches[0].screenX
        y = e.changedTouches[0].screenY
    }
    // #endif

    // #ifdef MP-WEIXIN
    x = e.target.x
    y = e.target.y
    // #endif

    this.$emit('long', {x,y})
}

处理长按弹出菜单的边界问题。

  1. 计算屏幕宽高的边界最大值,通过API获取屏幕宽高 - 元素宽高
  2. 判断用户点击的x/y坐标是否大于该值,大于直接使用最大值,否则是否坐标值

给弹出层组件增加动画效果,代码结合上下文进行理解。

  1. 引入 weex 的 animation模块

    // #ifdef APP-NVUE
    const animation = weex.requireModule('animation')
    // #endif
    
  2. 使用 animation API 实现动画

    show(x = -1, y = -1) {
        this.x = (x > this.maxX) ? this.maxX : x
        this.y = (y > this.maxY) ? this.maxY : y
        this.status = true
        // #ifdef APP-NVUE
        this.$nextTick(_=>{
            animation.transition(this.$refs.popup, {
                styles: {
                    transform: 'scale(1,1)',
                    transformOrigin: 'left top',
                    opacity: 1
                },
                duration: 200,	// 单位:ms
                timingFunction: 'ease'
            })
        })
        // #endif
    },
    

3.9 开发导航栏的弹出菜单

直接看 commit

3.10 删除当前聊天会话

直接看 commit

3.11 设置和取消聊天置顶

直接看 commit

4.通讯录页开发

4.1 通讯录列表组件开发

mail/mail.nvue







4.2 封装公共列表组件

free-list-item.vue







页面组件中使用



4.3 完善通讯录列表

效果图:

mail/mail.nvue







5.发现页开发

find/find.nvue







该业务针对free-list-item组件做了部分修改,添加了插槽,具体细节参考commit。

6.个人中心页开发

6.1 优化自定义导航栏功能

my.nvue







使用iconfont图标的unicode编码动态赋值,需要进行格式转换:

例如将转换成\ue601,注意这里去掉了分号!

6.2 完善个人中心页

效果图:

my.nvue







Typora太卡了。。。换一个文件写。。。