一般情况下用vue就可以了。如果是app且有部分场景vue页面的性能不满足你的需求时,这个页面可以改用nvue页面。如果app里同时存在同名的vue和nvue页面,在app端会优先执行nvue页面,而其他端仍然优先vue页面。
图标按钮组件封装
这个地方有一个坑:
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 进制值就不能写成 ,必须写成\ue682
这个问题通过查资料 + 反复实践大约耗时 30 分钟,因此记录一下这个坑。
3.3 封转头部导航组件
uni-app的普通组件中使用onLoad、onShow不生效?,要用created、mounted,为什么?
这个就要从uni-app的生命周期说起了。。。
uni-app有 3 类生命周期:
- 应用生命周期
- 页面生命周期
- 组件生命周期
应用生命周期:
重点是应用生命周期只能在App.vue中监听,其他页面监听无效,所以不要用错了。
页面生命周期:
需要注意的是:onLoad和onReady只会触发一次,这是官方没有说的,所以还是要多实践!
以上是常用的几个,想了解全部的参考官方文档
组件生命周期:
uni-app 组件支持的生命周期,与vue标准组件的生命周期相同。这里没有页面级的onLoad等生命周期:
以后编写组件的时候就要细心点,页面组件就用页面的生命周期,普通组件就用组件的生命周期,别乱搞给自己挖坑。
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 封装头像组件
free-avatar.vue 文件代码
<template>
<image
:src="src"
mode="widthFix"
:style="getStyle"
:class="type"
></image>
</template>
<script>
export default {
name: 'FreeAvatar',
components: {},
props: {
// 图像大小
size: {
type: [String, Number],
default: 90
},
// 图像地址
src: {
type: String,
default: ""
},
// 图像显示类型,是否圆角显示
type: {
type: String,
default: "rounded"
}
},
data () {
return {}
},
computed: {
getStyle () {
return `width: ${this.size}rpx;height: ${this.size}rpx;`
}
},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
index/index.vue 文件中使用
<free-avatar :src="item.avatar" size="92" />
3.6 badge组件封装
封装前的代码:
index/index.nvue 角标元素代码
<!-- 角标 -->
<text class="bg-danger text-white rounded-circle font-sm badge" >9</text>
角标css代码
.badge {
padding-left: 14rpx;
padding-right: 14rpx;
padding-bottom: 6rpx;
padding-top: 6rpx;
position: absolute;
right: 15rpx;
top: 15rpx;
}
封装后的代码:
free-badge.vue
<template>
<text
class="bg-danger text-white rounded-circle font-sm free-badge"
:class="badgeClass"
:style="badgeStyle"
>{{value}}</text>
</template>
<script>
export default {
name: 'FreeBadge',
components: {},
props: {
// 角标样式类名
badgeClass: {
type: String,
default: ""
},
// 角标行内样式
badgeStyle: {
type: String,
default: ""
},
// 角标内容
value: {
type: [String, Number],
default: ""
}
},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less">
.free-badge {
padding: 6rpx 14rpx;
}
</style>
使用badge
<free-badge value="1" badgeClass="position-absolute" badgeStyle="top: 15rpx;right:15rpx" />
3.7 封装聊天列表组件
需要注意view标签监听@longpress长按事件无法获取坐标值,此时换成 div 标签即可。
free-media-list.vue 文件代码
<template>
<view hover-class="bg-hover-light" v-if="item">
<!-- 列表 -->
<div class="flex" @click="onClick" @longpress="long">
<!-- 左侧 -->
<view class="flex align-center justify-center position-relative" style="width: 145rpx">
<free-avatar :src="item.avatar" size="92" />
<!-- 角标 -->
<free-badge :value="item.noreadnum" badgeClass="position-absolute" badgeStyle="top: 15rpx;right:15rpx" />
</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-sm text-ellipsis text-light-muted">{{item.data}}</text>
</view>
</div>
</view>
</template>
<script>
import FreeAvatar from '@/components/free-ui/free-avatar.vue'
import FreeBadge from '@/components/free-ui/free-badge.vue'
import $Time from '@/common/free-lib/time.js'
export default {
name: 'FreeMediaList',
components: {
FreeAvatar,
FreeBadge
},
props: {
item: Object,
index: Number
},
data() {
return {}
},
computed: {},
watch: {},
created() {},
mounted() {},
methods: {
onClick() {
this.$emit('click')
},
long(e) {
console.log(e)
}
},
filters: {
formatTime(value) {
return $Time.gettime(value)
}
}
}
</script>
<style scoped lang="less">
</style>
使用组件的代码
<!-- 列表 -->
<block v-for="(item, index) in list" :key="index">
<free-media-list
:item="item"
:index="index"
/>
</block>
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})
}
处理长按弹出菜单的边界问题。
- 计算屏幕宽高的边界最大值,通过API获取屏幕宽高 - 元素宽高
- 判断用户点击的x/y坐标是否大于该值,大于直接使用最大值,否则是否坐标值
给弹出层组件增加动画效果,代码结合上下文进行理解。
-
引入 weex 的 animation模块
// #ifdef APP-NVUE
const animation = weex.requireModule('animation')
// #endif
-
使用 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
<template>
<view>
<!-- 导航栏 -->
<free-nav-bar title titleValue="通讯录" fixed @openPopup="handleOpenPopup" />
<free-media-list />
<!-- 通讯录列表组件 -->
<view class="flex bg-white" v-for="item in 5">
<!-- 左侧图片 -->
<view class="flex justify-center align-center py-2 px-3" style="width: 132rpx; height: 104rpx;">
<image style="width: 76rpx; height: 76rpx;" src="/static/images/mail/friend.png" mode="widthFix"></image>
</view>
<!-- 右侧内容 -->
<view class="flex-1 flex align-center border-bottom">
<text class="font-md text-dark">新的朋友</text>
</view>
</view>
</view>
</template>
<script>
import FreeNavBar from '@/components/free-ui/free-nav-bar.vue'
export default {
components: {
FreeNavBar
},
data() {
return {
}
},
onLoad() {
},
methods: {
}
}
</script>
<style>
</style>
4.2 封装公共列表组件
free-list-item.vue
<template>
<view class="flex bg-white" hover-class="bg-light" @click="$emit('click')">
<!-- 左侧图片 -->
<view
class="flex justify-center align-center py-2 px-3"
style="width: 132rpx; height: 104rpx;"
>
<image v-if="cover" style="width: 76rpx; height: 76rpx;" :src="cover" mode="widthFix"></image>
</view>
<!-- 右侧内容 -->
<view class="flex-1 flex align-center border-bottom">
<text class="font-md text-dark">{{title}}</text>
</view>
</view>
</template>
<script>
export default {
name: 'FreeListItem',
components: {},
props: {
// 封面
cover: {
type: String,
default: ""
},
// 标题
title: {
type: String,
default: ""
}
},
data() {
return {}
},
computed: {},
watch: {},
created() {},
mounted() {
console.log(123)
},
methods: {}
}
</script>
<style scoped lang="less"></style>
页面组件中使用
<!-- 通讯录列表组件 -->
<free-list-item v-for="(item, index) in list" :key="index" :cover="item.cover" :title="item.title" />
4.3 完善通讯录列表
效果图:
mail/mail.nvue
<template>
<view>
<!-- 导航栏 -->
<free-nav-bar title titleValue="通讯录" fixed @openPopup="handleOpenPopup" />
<!-- 通讯录列表组件 -->
<free-list-item v-for="(item, index) in topList" :key="index" :cover="item.cover" :title="item.title" />
<!-- 通讯录列表 -->
<block v-for="(item, index) in list" :key="index">
<view v-if="item.data.length">
<view class="py-2 px-3 border-bottom bg-light">
<text class="font-sm text-dark">{{item.letter}}</text>
</view>
<free-list-item v-for="(item2, index2) in item.data" :key="index2" :title="item2" cover="/static/avatar.jpg" />
</view>
</block>
</view>
</template>
<script>
import FreeNavBar from '@/components/free-ui/free-nav-bar.vue'
import FreeListItem from '@/components/free-ui/free-list-item.vue'
export default {
components: {
FreeNavBar,
FreeListItem
},
data() {
return {
topList: [
{
title: "新的朋友",
cover: "/static/images/mail/friend.png",
event: ""
},
{
title: "群聊",
cover: "/static/images/mail/group.png",
event: ""
},
{
title: "标签",
cover: "/static/images/mail/tag.png",
event: ""
}
],
list: [{
"letter": "A",
"data": [
"阿苏",
"阿拉",
"阿勒",
"阿里",
"安庆",
"澳机"
]
}, {
"letter": "B",
"data": [
"保罗",
"包头",
"北海福成",
"北南苑",
"北都国际"
]
}, {
"letter": "C",
"data": [
"长山场",
"长国",
"常德",
"长花~",
"长王",
"常机",
"成双",
"赤峰"
]
}, {
"letter": "D",
"data": [
"大理爱你",
"大周哈哈",
"达河",
"丹浪",
"德芒",
"迪香里拉",
]
}, {
"letter": "E",
"data": [
"鄂多斯",
]
}, {
"letter": "F",
"data": [
"福乐",
]
}, {
"letter": "G",
"data": [
"固原六盘",
"广盘龙",
"广白机",
"桂江",
]
}, {
"letter": "H",
"data": [
"哈尔滨太平",
"哈密",
"海美兰",
"海拉尔",
"邯郸",
]
}, {
"letter": "I",
"data": []
}, {
"letter": "J",
"data": [
"鸡西兴",
"佳木斯",
"嘉峪",
]
}, {
"letter": "K",
"data": [
"克拉玛依",
"库车龟兹",
"库尔勒",
]
}, {
"letter": "L",
"data": [
"拉萨贡嘎",
"黎平",
"林芝米",
"柳州白莲",
]
}, {
"letter": "M",
"data": [
]
}, {
"letter": "N",
"data": [
]
}, {
"letter": "O",
"data": []
}, {
"letter": "P",
"data": [
"普洱思茅"
]
}, {
"letter": "Q",
"data": [
"齐齐哈尔",
"秦皇岛山",
"青岛流亭",
"衢州机场",
"泉州晋江机场"
]
}, {
"letter": "R",
"data": [
"日喀则和"
]
}, {
"letter": "S",
"data": [
"三亚凤凰",
"汕头",
"上海虹桥",
"上海浦东",
"深圳宝安",
"沈阳桃仙",
"石家庄正定",
"苏南硕放"
]
}, {
"letter": "T",
"data": [
"塔城",
]
}, {
"letter": "U",
"data": []
}, {
"letter": "V",
"data": []
}, {
"letter": "W",
"data": [
"文山",
"温永强",
"乌海",
"武汉天",
]
}, {
"letter": "X",
"data": []
}, {
"letter": "Y",
"data": []
}, {
"letter": "Z",
"data": [
"昭通",
"芷江",
"中卫",
"舟山",
]
}]
}
},
onLoad() {
},
methods: {
}
}
</script>
<style>
</style>
5.发现页开发
find/find.nvue
<template>
<view class="page">
<free-nav-bar title titleValue="发现" />
<!-- 列表 -->
<free-list-item title="朋友圈" rightIconShow>
<text slot="icon" class="iconfont font-lg main-text-color"></text>
<view slot="right" class="position-relative p-1">
<free-avatar src="/static/avatar.jpg" size="55"/>
<text
class="rounded-circle bg-danger position-absolute"
style="width: 20rpx;height: 20rpx;top: 0;right: 0;"
></text>
</view>
</free-list-item>
<free-divider />
<free-list-item title="扫一扫" rightIconShow>
<text slot="icon" class="iconfont font-lg"></text>
</free-list-item>
<free-list-item title="摇一摇" rightIconShow>
<text slot="icon" class="iconfont font-lg"></text>
</free-list-item>
<free-divider />
<free-list-item title="看一看" rightIconShow>
<text slot="icon" class="iconfont font-lg"></text>
</free-list-item>
<free-list-item title="搜一搜" rightIconShow>
<text slot="icon" class="iconfont font-lg"></text>
</free-list-item>
<free-divider />
<free-list-item title="购物" rightIconShow>
<text slot="icon" class="iconfont font-lg"></text>
</free-list-item>
</view>
</template>
<script>
import FreeNavBar from '@/components/free-ui/free-nav-bar.vue'
import FreeListItem from '@/components/free-ui/free-list-item.vue'
import FreeAvatar from '@/components/free-ui/free-avatar.vue'
import FreeDivider from '@/components/free-ui/free-divider.vue'
export default {
name: "FindIndex",
components: {
FreeNavBar,
FreeListItem,
FreeDivider,
FreeAvatar
},
data() {
return {
}
},
onLoad() {
},
methods: {
}
}
</script>
<style lang="less">
</style>
该业务针对free-list-item组件做了部分修改,添加了插槽,具体细节参考commit。
6.个人中心页开发
6.1 优化自定义导航栏功能
my.nvue
<template>
<view class="page">
<free-nav-bar bgColor="bg-white" >
<!-- <text slot="right" :iconValue="'\ue59117'"></text> -->
<free-icon-button slot="right" :iconValue="'\ue6ed'" />
</free-nav-bar>
</view>
</template>
<script>
import FreeNavBar from '@/components/free-ui/free-nav-bar.vue'
import FreeIconButton from '@/components/free-ui/free-icon-button.vue'
export default {
name: 'MyIndex',
components: {
FreeNavBar,
FreeIconButton
},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
使用iconfont图标的unicode编码动态赋值,需要进行格式转换:
例如将转换成\ue601,注意这里去掉了分号!
6.2 完善个人中心页
效果图:
my.nvue
<template>
<view class="page">
<free-nav-bar bgColor="bg-white" >
<free-icon-button slot="right" :iconValue="'\ue6ed'" />
</free-nav-bar>
<free-list-item cover="/static/images/demo/demo6.jpg" coverSize="120" title="张波" rightIconShow>
<!-- 中间文本内容 -->
<view class="flex flex-column">
<text class="text-dark font-lg font-weight-bold">伤心的瘦子</text>
<text class="text-light-muted font mt-2">叮咚号: Alexander3714</text>
</view>
<!-- 右侧图标 -->
<view slot="right">
<text class="iconfont font-md text-light-muted"></text>
</view>
</free-list-item>
<free-divider></free-divider>
<free-list-item title="支付" rightIconShow>
<text slot="icon" class="iconfont font-lg py-1"></text>
</free-list-item>
<free-divider></free-divider>
<free-list-item title="收藏" rightIconShow>
<text slot="icon" class="iconfont font-lg py-1"></text>
</free-list-item>
<free-list-item title="相册" rightIconShow>
<text slot="icon" class="iconfont font-lg py-1"></text>
</free-list-item>
<free-list-item title="表情" rightIconShow>
<text slot="icon" class="iconfont font-lg py-1"></text>
</free-list-item>
<free-divider></free-divider>
<free-list-item title="设置" rightIconShow>
<text slot="icon" class="iconfont font-lg py-1"></text>
</free-list-item>
</view>
</template>
<script>
import FreeNavBar from '@/components/free-ui/free-nav-bar.vue'
import FreeIconButton from '@/components/free-ui/free-icon-button.vue'
import FreeListItem from '@/components/free-ui/free-list-item.vue'
import FreeDivider from '@/components/free-ui/free-divider.vue'
export default {
name: 'MyIndex',
components: {
FreeNavBar,
FreeIconButton,
FreeListItem,
FreeDivider
},
props: {},
data () {
return {}
},
computed: {},
watch: {},
created () {},
mounted () {},
methods: {}
}
</script>
<style scoped lang="less"></style>
Typora太卡了。。。换一个文件写。。。
来源:https://www.cnblogs.com/alexander3714/p/14541481.html