uni-app高仿微信开源项目-叮咚

叮咚项目参考文档 v1.0

此项目是一款高仿微信APP的即时通讯应用,叫叮咚,是我的开源处女座,希望能与感兴趣的你交流 : )

项目技术栈:

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

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

为什么做这个项目?

  1. 在公司用uni-app写的都是比较简单的小程序项目,没有真正意义上从0到1实现过一个功能较复杂的跨端项目,借此机会,深度实践uni-app技术栈
  2. 我非常渴望拥有从0到1独立开发一款全栈应用的能力,因为我觉得Node.js太美了,太性感,对前端太友好了,不学习它简直就是对不起前端工程师这个 Title,借此机会彻底实践一次。
  3. 我想实现一款聊天应用,里面就只有我和我的女朋友,不用看到任何人、没有任何公众号、任何广告,这个世界里面只有我和她。

我想这是一个美好的开始,我也把它当作一种精进技术的方式,希望能在issue或企鹅中与你进行思想碰撞。

我的企鹅:1762458611

NVUE需要注意的点

  1. 在NVUE中编写css必须写完整,不能使用padding: 0 1 2 3这种写法,必须写成padding-top: 0px

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

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

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

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

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

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模式子组件使用插槽报错问题

需要恶补项目中的基础知识

以看懂free-lib/time.js这个库为目的展开复习下面的知识点:

  1. JavaScript的正则表达式
  2. Date日期时间对象知识

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 引入自定义图标库

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

<script>
	export default {
		onLaunch: function() {
			// #ifdef APP-NVUE
			// 加载公共图标库 只有在NVUE环境下才加载
			const domModule = weex.requireModule(‘dom‘)
			domModule.addRule(‘fontFace‘, {
				‘fontFamily‘: "iconfont",
				src: "url(‘https://at.alicdn.com/t/font_1365296_2ijcbdrmsg.ttf‘)"
			});
			// #endif
		},
		onShow: function() {
			console.log(‘App Show‘)
		},
		onHide: function() {
			console.log(‘App Hide‘)
		}
	}
</script>

<style>
	/*每个页面公共css */
	@import url("./common/free.css");
	@import url("./common/common.css");
	/* #ifndef APP-PLUS */
	@import url("./common/free-icon.css");
	/* #endif */
</style>

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

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 头部导航栏组件开发

<template>
	<view>
		<!-- 导航栏 -->
		<view class="bg-light">
			<!-- 状态栏 -->
			<view :style="‘height:‘+statusBarHeight+‘px‘"></view>
			<!-- 导航 -->
			<view class="w-100 flex align-center justify-between border" style="height: 90rpx">
				<!-- 左边标题部分 -->
				<view class="flex align-center">
					<text class="font-md ml-3">叮咚(10)</text>
				</view>
				<!-- 右边图标部分 -->
				<view class="flex align-center">
					<view class="flex align-center justify-center border" style="height: 90rpx;width: 90rpx;">
						<text class="iconfont font-md">&#xe6e3;</text>
					</view>

					<view class="flex align-center justify-center border" style="height: 90rpx;width: 90rpx;">
						<text class="iconfont font-md">&#xe682;</text>
					</view>
				</view>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				statusBarHeight: 0 // 状态栏高度
			}
		},
		onLoad() {
			this.statusBarHeight = plus.navigator.getStatusbarHeight()
		},
		methods: {

		}
	}
</script>

<style lang="less">
</style>

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

这个地方有一个坑:

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

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

<template>
<view
      class="flex align-center justify-center" 
      hover-class="bg-hover-light" @click="$emit(‘click‘)"
      style="height: 90rpx;width: 90rpx;">
    <text class="iconfont font-md">{{iconValue}}</text>
    </view>
</template>

<script>
    export default {
        name: ‘‘,
        components: {},
        props: {
            iconValue: {
                required: true
            }
        },
        data () {
            return {}
        },
        computed: {},
        watch: {},
        created () {
            console.log(this.iconValue)
        },
        mounted () {},
        methods: {}
    }
</script>

<style scoped lang="less"></style>

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

<free-icon-button @click="handleIconButtonClick" :iconValue="‘\ue682‘"/>

这里面还有一个细节:

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

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

3.3 封转头部导航组件

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

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

uni-app有 3 类生命周期:

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

应用生命周期:

uni-app高仿微信开源项目-叮咚

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

页面生命周期:

uni-app高仿微信开源项目-叮咚

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

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

组件生命周期:

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

uni-app高仿微信开源项目-叮咚

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

index/index.nvue 页面代码

<template>
	<view>
		<!-- 导航栏 -->
		<free-nav-bar title titleValue="叮咚(99+)" fixed />

		<!-- 列表 -->
		<view style="height: 2000rpx;">
			<text>好好学习,天天向上!</text>
		</view>
	</view>
</template>

<script>
	import FreeNavBar from ‘@/components/free-ui/free-nav-bar.vue‘
	export default {
		name: "IndexPage",
		components: {
			FreeNavBar
		},
		data() {
			return {}
		},
		methods: {}
	}
</script>

<style lang="less">
</style>

free-nav-bar.vue 组件代码

<template>
	<view>
		<!-- 导航栏 -->
		<view class="bg-light" :class="fixed?‘fixed-top‘:‘‘">
			<!-- 状态栏 -->
			<view :style="‘height:‘+statusBarHeight+‘px‘"></view>
			<!-- 导航 -->
			<view class="w-100 flex align-center justify-between border" style="height: 90rpx">
				<!-- 左边标题部分 -->
				<view class="flex align-center">
					<text v-if="title" class="font-md ml-3">{{titleValue}}</text>
				</view>
				<!-- 右边图标部分 -->
				<view class="flex align-center">
					<free-icon-button :iconValue="‘\ue6e3‘" />
					<free-icon-button :iconValue="‘\ue682‘"/>
				</view>
			</view>
		</view>
		<!-- 占位 -->
		<view v-if="fixed" :style="fixedStyle"></view>
	</view>
</template>

<script>
import FreeIconButton from ‘@/components/free-ui/free-icon-button.vue‘
export default {
  name: ‘FreeNavBar‘,
  components: {
		FreeIconButton,
	},
  props: {
		// 是否显示标题
		title: {
			type: Boolean,
			default: false
		},
		// 标题内容
		titleValue: {
			type: String
		},
		// 是否固定导航栏
		fixed: {
			type: Boolean,
			default: true
		}
	},
  data () {
    return {
			navBarHeight: 0,		// 状态栏高度+导航栏高度
			statusBarHeight: 0 // 状态栏高度
		}
  },

  computed: {
		fixedStyle() {
			return `height: ${this.navBarHeight}px`
		}
	},
  watch: {},
  created () {},
  mounted () {
		console.log("API获取:", uni.getSystemInfoSync().statusBarHeight)
		// NVUE环境下获取系统状态栏的高度
		// #ifdef APP-NVUE
			this.statusBarHeight = plus.navigator.getStatusbarHeight()
		// #endif
		/* 
			这里使用uni.upx2px的原因是因为我们获取的statusBarHeight是px单位,要进行相加
			需要转换成相同的单位才行.
		 */
		this.navBarHeight = this.statusBarHeight + uni.upx2px(90)
	},
  methods: {},
}
</script>

<style scoped lang="less">
</style>

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

3.4 开发聊天列表组件

index/index.nvue 文件代码

<template>
	<view>
		<!-- 导航栏 -->
		<free-nav-bar title titleValue="叮咚(99+)" fixed />

		<!-- 列表 -->
		<view class="flex align-center" v-for="(item, index) in list" :key="index">
			<!-- 左侧 -->
			<view class="flex align-center justify-center" style="width: 145rpx;">
				<image :src="item.avatar" style="width: 92rpx;height:92rpx;" mode="widthFix" class="rounded"></image>
			</view>
			<!-- 右侧 -->
			<view class="flex flex-column border-bottom flex-1 py-3 pr-3 border-light-secondary">
				<view class="flex align-center justify-between mb-1">
					<text class="font-md">{{item.nickname}}</text>
					<text class="font-sm text-light-muted">{{item.update_time | formatTime}}</text>
				</view>
				<text class="font text-ellipsis text-light-muted">{{item.data}}</text>
			</view>
		</view>
	</view>
</template>

<script>
	import FreeNavBar from ‘@/components/free-ui/free-nav-bar.vue‘
	import $Time from ‘@/common/free-lib/time.js‘
	export default {
		name: "IndexPage",
		components: {
			FreeNavBar
		},
		data() {
			return {
				list: [{
						avatar: "/static/avatar.jpg",
						nickname: ‘老婆‘,
						update_time: Date.now(),
						data: ‘今晚想吃什么都可以...‘
					},
					{
						avatar: "/static/avatar.jpg",
						nickname: ‘老婆2‘,
						update_time: Date.now(),
						data: ‘今晚想吃什么都可以...‘
					},
					{
						avatar: "/static/avatar.jpg",
						nickname: ‘老婆3‘,
						update_time: Date.now(),
						data: ‘今晚想吃什么都可以...‘
					},
					{
						avatar: "/static/avatar.jpg",
						nickname: ‘老婆4‘,
						update_time: Date.now(),
						data: ‘今晚想吃什么都可以...‘
					},
					{
						avatar: "/static/avatar.jpg",
						nickname: ‘老婆5‘,
						update_time: Date.now(),
						data: ‘今晚想吃什么都可以...‘
					}
				]
			}
		},
		methods: {},
		filters: {
			formatTime(value) {
				return $Time.gettime(value)
			}
		}
	}
</script>

<style lang="less">
</style>

3.5 封装头像组件

3.6 badge组件封装

3.7 封装聊天列表组件

3.8 封装全局mixin

文档完善中。。。我将夜以继日,用最快的速度把叮咚打造出来!

废话不多说、行动去了!

uni-app高仿微信开源项目-叮咚

上一篇:微信扫码关注登录


下一篇:taro编译微信小程序,报错“未找到setmap.json文件”