*
通配符思考:target
和 currentTarget
的区别
<text data-id="myId" @click="showId($event)">点我显示id</text>
showId(event) {
console.log(event.currentTarget.dataset.id)
}
.vue
文件(组件样式也在这里修改)-
,js中会当成减号)components: {}
中短横线形式,使用组件父组件
父组件中自定义属性mysrc
(体现传值),右边内容若为变量,则须在mysrc
前添加冒号
<my-yuyan :mysrc="xuanyan" mytitle="渲言"></my-yuyan>
<my-yuyan :mysrc="yuyan" mytitle="喻言"></my-yuyan>
data() {
return {
xuanyan: "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591198770665&di=3bdaf80f7bd15f03dfea55bf88e391a0&imgtype=0&src=http%3A%2F%2Fimglf5.nosdn0.126.net%2Fimg%2FbjZaRmVpWExkaE1ScFc3VzRjUW1INUJhcTcxVGNTcVF0VGpFYkxXelNiZWZnMW1SYjAzUFJBPT0.jpg%3FimageView%26thumbnail%3D500x0%26quality%3D96%26stripmeta%3D0%26type%3Djpg",
yuyan: "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1591200770069&di=a90535dc0ff79617758b4ec32484e1bd&imgtype=0&src=http%3A%2F%2Fwx2.sinaimg.cn%2Flarge%2F59853be1ly1gff7y14skvj20c80c8dna.jpg"
};
},
子组件
(父组件所传值)需要在子组件中声明:props
中当与父组件的自定义属性相同
<view>
<view>{{mytitle}}</view>
<image class="yuyan" :src="mysrc"></image>
</view>
export default {
props:['mysrc', 'mytitle']
}
绑定事件methods
部分触发自定义事件
在子组件上监听自定义事件methods
部分处理子组件传来的参数main.js
中定义
Vue.prototype.theme ="the 9"
.vue
组件中使用
onLoad() {
console.log(this.theme)
}
App.vue
文件定义
globalData: {
date: '2020-05-30'
}
获取
getApp().globalData.date
在父组件中使用到了子组件,在该页面的子组件内部插入标签,但是不知道具体的放置位置在哪,所以要在子组件定义的页面里用slot
标签来占位。
父组件
uni-app框架的生命周期结合了 vue 和 微信小程序的生命周期
全局的APP中 使用 onLaunch 表示应用启动时
页面中 使用 onLoad 或者 onShow 分别表示 页面加载完毕时 和页面 显示时
组件中使用 mounted 组件挂载完毕时
接口文档
项目名称:my-project → 默认模板 → 注意项目目录(C:\try\uniapp\my-project\dist\dev\mp-weixin
)→ AppId
可暂时忽略
全局安装
npm install -g @vue/cli
创建项目
vue create -p dcloudio/uni-preset-vue my-project
启动项目(微信小程序)
npm run dev:mp-weixin
微信小程序开发者工具导入项目
安装依赖
npm install sass-loader node-sass
vue组件中,添加属性 <style lang='scss'>
注意: 这里需要在vscode里编辑C:\try\uniapp\my-project
路径下的项目
app.js
文件 (微信小程序中)
"pages": [
"pages/index2/index",
"pages/index/index"
],
pages
数组中第一项表示应用启动页
pages.json
(vscode中)
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "uni-app"
}
},
{
"path": "pages/index2/index",
"style": {
"navigationBarTitleText": "uni-app"
}
}
]
官网配置
VM303:1 sitemap.json
Error: 未找到入口 sitemap.json 文件,或者文件读取失败,请检查后重新编译。
在vscode的微信小程序的根目录下新建sitemap.json
{
"rules":[{
"action": "allow",
"page": "*"
}]
}
uni-api
原生的微信小程序的api都是不支持promise
uni-app对大部分的小程序的原生api做了封装,使之支持promise
使用方式 :
wx.request(原生微信小程序)修改为uni.request (uni-api的方式 )
微信小程序:
wx.request({
url: '',
success(res) {
console.log(res)
}
})
uni-api:
uni.request({
url: ''
})
.then(res=> {
console.log(res)
})
uni-ui
uni-ui的使用方式
cnpm install @dcloudio/uni-ui
import {uniBadge} from '@dcloudio/uni-ui'
export default {
components: {uniBadge}
}
<uni-badge text="1"></uni-badge>
<uni-badge text="2" type="success" @click="bindClick"></uni-badge>
<uni-badge text="3" type="primary" :inverted="true"></uni-badge>
styles文件夹放入src路径下
在App.vue
文件中引入,注意不能使用@
<style>
@import "./styles/iconfont.wxss";
</style>
使用
<text class="iconfont iconvideocamera"></text>
pages.json
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "青春有你",
"navigationBarBackgroundColor": "#000"
}
tab栏:推荐、分类、最新、专辑
分段器的使用
文档中以下部分要修改:
import { uniSegmentedControl } from "@dcloudio/uni-ui";
onClickItem(e) {
if (this.current !== e.currentIndex) {
this.current = e.currentIndex;
}
}
button
改为text
<uni-segmented-control
style-type="text"
></uni-segmented-control>
从首页-推荐-热门
中的图片跳转到到图片详情
页面
图片数组
和图片索引
(组件传值,存储到全局数据中)<view class="img_wrap" v-for="(item, index) in hots" :key="item.id">
<go-detail :list="months.items" :index="index">
<image :src="item.thumb" mode="widthFix"></image>
</go-detail>
</view>
子组件
<template>
<view @click="clickHotImg">
图片详情组件
<slot></slot>
</view>
</template>
<script>
export default {
props:['list', 'index'],
methods:{
clickHotImg() {
getApp().globalData.imgList = this.list
getApp().globalData.imgIndex = this.index
uni.navigateTo({
url: '../../pages/imgDetail/index' -- 也可以用根路径/pages的形式,见图片分类
})
}
}
};
</script>
图片详情页面,需要在pages.json
中配置
<template>
<view>图片详情页面</view>
</template>
<script>
export default {
onLoad() {
console.log('缓存的数据', getApp().globalData)
}
};
</script>
overflow:hidden
<view class="wallpaper_image" v-for="(item, index) in wallpaper" :key="item.id">
<!-- :src="item.thumb+item.rule.replace('$',360)" -->
<go-detail :list="wallpaper" :index="index" class="wallpaperDetail">
<image :src="item.thumb+item.rule.replace('$',360)" mode="widthFix" ></image>
</go-detail>
</view>
.wallpaper {
display: flex;
flex-wrap: wrap;
.wallpaper_image {
width: 33.3%;
height: 130rpx;
border: 1rpx solid #fff;
.wallpaperDetail {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
overflow: hidden;
}
}
}
本地图片未加载成功的情况 Failed to load local image resource /pages/XXX/处理
<image
v-if="imgList[imgIndex].thumb+imgList[imgIndex].rule.replace('$',360)"
:src="imgList[imgIndex].thumb+imgList[imgIndex].rule.replace('$',360)" >
</image>
这样写太土了
data() {
return {
imgList: getApp().globalData.imgList,
imgIndex: getApp().globalData.imgIndex
}
}
```go
export default {
onLoad() {
const{imgList, imgIndex} = getApp().globalData
this.imgDetail = imgList[imgIndex]
console.log('缓存的数据', getApp().globalData)
},
data() {
return {
imgDetail: {}
}
}
};
fromNow()
实现显示为XX天/月/年前moment.locale('zh-cn')
使用中文语言(写在js里)this.commemtTime = moment(this.imgDetail.atime*1000).fromNow()
手指按下屏幕事件 touchstart
手指离开屏幕事件 touchend
手指在屏幕上的坐标 event.changedTouches[0].clientX 和 clientY
记录按下屏幕的时间
记录离开屏幕的时间
手指离开屏幕的时候根据坐标判断滑动的方向
子组件
<template>
<view @touchstart="handleTouchStart" @touchend="handleTouchEnd">
<slot></slot>
</view>
</template>
<script>
export default {
data() {
return {
//按下的时间
startTime: 0,
startX: 0,
startY: 0
};
},
methods: {
handleTouchStart(event) {
this.startTime = Date.now()
this.startX = event.changedTouches[0].clientX
this.startY = event.changedTouches[0].clientY
},
handleTouchEnd(event) {
const endTime = Date.now()
const endX = event.changedTouches[0].clientX
const endY = event.changedTouches[0].clientY
if (endTime - this.startTime > 2000) {
return
}
let direction = ''
if (Math.abs(endX - this.startX) > 10) {
direction = endX -this.startX > 0 ? 'right': 'left'
} else {
return
}
this.$emit('swipeAction', {direction})
}
}
};
</script>
父组件
<swipe-action @swipeAction="handleSwipeAction">
<image
v-if="imgUrl"
:src="imgUrl"
mode="scaleToFill">
</image>
</swipe-action>
handleSwipeAction (e) {
console.log(e)
}
if (Math.abs(endX - this.startX) > 10 && Math.abs(endY - this.startY) < 10) {
direction = endX -this.startX > 0 ? 'right': 'left'
} else {
return
}
downloadFile
下载远程文件到小程序的内存中saveImageToPhotosAlbum
将 图片从内存中下载到本地async handleClick() {
await uni.showLoading({
title: '下载中'
})
const res1 = await uni.downloadFile({url: this.imgDetail.img})
const {tempFilePath} = res1[1]
const res2 = await uni.saveImageToPhotosAlbum({ filePath: tempFilePath})
uni.hideLoading()
await uni.showToast({
title: '下载成功'
})
}
注意:在uni-app框架里,仍用v-for
指令
<view class="recommend_item" v-for="item in recommends" :key="item.id">
<image :src="item.thumb"></image>
</view
flex-wrap: nowrap | wrap | wrap-reverse;
两栏布局常见:
(限定了每一循环项的宽度为一半,并且换行)
width: 50%;
flex-wrap: wrap;
快速根据html结构,生成对应sass结构(选中代码,ctrl
+shift
+P
)
这种结构很不好啊,避免这种结构
块级元素和行内元素同时存在的情况,如何基线对齐
moment.js官方文档
优化:
由于月份是请求来的数据,防止在一开始渲染的时候,出现undefined
,需要改造组件:
<!-- 月份列表 开始 -->
<view class="months_wrap" v-if="Object.keys(months).length!==0" >
或者在最外层标签加:
<scroll-view v-if="recommends.length > 0" scroll-y class="recommend_view" @scrolltolower="handlerToLower" >
原先使用view
标签的情况(template
内第一个最外层的view
),不能冻结头部tab栏,因此替换为scroll-view
标签
添加scroll-y
属性
计算高度height: calc(100vh - 45px);
(屏幕的高减去头部固定高度)
绑定滚动条触底事件,@scrolltolower,当到底时,①skip+=limit
,②再次发起请求,③且hots做数据叠加,达到分页效果
如何叠加?使用es6语法this.hots = [...this.hots, ...res.res.vertical]
hasMore判断是否还有更多,当返回值.length为0,将hasMore置为false
头部的轮播图无须多次请求,当this.xx.length为0,才初始化
methods: {
getList() {
this.request({
url: "http://157.122.54.189:9088/image/v3/homepage/vertical",
data: this.params
})
.then((res)=> {
if(res.res.vertical.length === 0) {
uni.showToast({
title: '到底了宝贝儿:)',
icon: 'none'
})
this.hasMore = false
return
}
if(this.recommends.length === 0) {
// 首次请求
this.recommends = res.res.homepage[1].items
this.months = res.res.homepage[2]
this.months.MM = moment(this.months.stime).format("MM")
this.months.DD = moment(this.months.stime).format("DD")
}
this.hots = [...this.hots, ...res.res.vertical]
})
},
handlerToLower() {
if(this.hasMore) {
this.params.skip+=this.params.limit
this.getList()
} else {
uni.showToast({
title: '到底了:)',
icon: 'none'
})
}
}
}
加了分页代码后,小程序报错Cannot read property 'replace' of undefined
这可不是小程序的bug,定位到使用replace方法的地方
因此意味着item.rule可能是null/undefined,那么直接将src赋值为 item.thumb看看效果:
法宣循环多出了一个空白的image
,并且this.wallpaper
的首项是一个空数组,那一定是在分页请求,做数据叠加除了问题,定位到代码:
this.wallpaper = [this.wallpaper, ...res.res.wallpaper]
竟然是没有写解构,惊了(ಥ﹏ಥ)(ಥ﹏ಥ)(ಥ﹏ಥ),吸取教训,遇到问题冷静排查。
indicator-dots
面板指示圆点
autoplay
自动播放
circular
连续播放
<view class="album_swiper">
<swiper indicator-dots autoplay circular>
<swiper-item v-for="item in banner" :key="item.id">
<image :src="item.thumb" mode="widthFix"></image>
</swiper-item>
</swiper>
注意屏幕尺寸变化时,轮播图、图片应该随之变化:
在不设置的情况下,调整为大屏,图片显示不全。
swiper
组件默认高度150px,不能由内容撑开,宽度750rpxswiper
的高,使之与图片宽高等比calc
不要再写错了!不要有多余的样式,否者可能会不生效 swiper {
height: calc(750rpx / (640/284));
image {
height: 100%;
}
}
在每个组件的mounted
钩子中添加如下代码:
mounted() {
uni.setNavigationBarTitle({
title: '分类'
})
}
外容器:flex布局
左:flex: 1
→ 内image固定宽高
image:height+width: 200rpx; mode=“aspectFill”
右:flex: 2
当红框中内容是子绝父相定位到底部时,由于绝对定位宽度由内容撑开,因此子需要加width: 100%
使之占满整行,这样做justify-content: space-between
时才能两端对齐。
.bgInfo {
display: flex;
height: 80rpx;
align-items: center;
padding: 0 20rpx;
justify-content: space-between;
position: absolute;
width: 100%;
bottom: 10rpx;
color: #fff;
.albumName {
font-size: 40rpx;
}
.followBtn {
width: 152rpx;
height: 60rpx;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10rpx;
padding: 5 10rpx;
font-size: 30rpx;
background-color: $themeColor;
}
}
图片填满格子:
mode=“scaleToFill”
.wallpaper_image {
width: 33.3%;
height: 170rpx;
border: 3rpx solid #fff;
display: flex;
justify-content: center;
align-items: center;
image {
width: 100%;
height: 100%;
}
}
右:flex: 2
+ overflow: hidden
(否则会被文字撑开)
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
改造成navigator
组件,注意绑定的url是在" "内再使用模板字符串
<navigator :url="`/pages/album/index?id=${item.target}`" class="recommend_item" v-for="item in recommends" :key="item.id">
<image :src="item.thumb" mode="widthFix"></image>
</navigator>
在方法里的url又不用带双引号" "
getAlbumDetail() {
this.request({
url: `http://157.122.54.189:9088/image/v1/wallpaper/album/${this.id}/wallpaper`,
data: this.params
}).then(res => {
console.log('专辑详情', res)
})
}
在跳转到的页面打印来源页面所携带的参数(接收参数id):
export default {
onLoad(options) {
console.log(options)
}
}
设置分段器tab栏自带的current
属性
而pages.json文件中,pages数组的首项为项目启动首页
"pages": [
{
"path": "pages/album/index"
},
{
"path": "pages/home/index"
}
在view
标签里包裹一层text
即可
<view class="author_info"><text>{{album.desc}}</text></view>
后端返回:
"desc":"急先锋是一种精神,他们急流勇进,先锋夺人!\n急先锋是一份责任,他们上天入地,护你周全!"
和scroll-view
组件分页的区别:保持头部tab栏不动
专辑详情页面上拉加载:整个页面的加载更多分页
.imgName {
background-image: linear-gradient(to right top, rgba(0,0,0,.2),rgba(0,0,0,0));
}
<navigator class="img" v-for="item in category" :key="item.id" url='/pages/imgCategory/index'>
<image :src="item.cover" mode="aspectFill"></image>
<div class="imgName">{{item.name}}</div>
</navigator>
这里用url='/pages/imgCategory/index'
根路径/
由于最新和热门的数据渲染只需改变请求参数,因此修改分段器,将参数order
写入
items: [{ title: "最新", order:"new"}, { title: "热门", order:"hot"}]
同时分段器绑定的values
也要返回新的数组
<uni-segmented-control
:current="current"
:values="items.map(e=>e.title)"
@clickItem="onClickItem"
style-type="text"
active-color="#d4237a"
></uni-segmented-control>
同时,最新/热门只使用一个组件
<view class="category_tab_content">
<view class="cate_item" v-for="item in vertical" :key="item.id">
<image :src="item.thumb" mode="aspectFill"></image>
</view>
</view>
若未进行设置 enable-flex 属性以使 flexbox 布局生效
设置,flex效果失效
<scroll-view scroll-y enable-flex class="category_tab_content">
<view class="cate_item" v-for="item in vertical" :key="item.id">
<image :src="item.thumb" mode="aspectFill"></image>
</view>
</scroll-view>
onClickItem(e) {
if (this.current !== e.currentIndex) {
this.current = e.currentIndex
this.params.skip = 0
this.vertical = []
this.params.order = this.items[e.currentIndex].order
this.getCategoryDetail()
} else {
return
}
}
watch
监听参数变化)接口:
http://157.122.54.189:9088/videoimg/v1/videowp/featured
http://157.122.54.189:9088/videoimg/v1/videowp/category/59b25abbe7bce76bc834198a
http://157.122.54.189:9088/videoimg/v1/videowp/videowp
(改变参数的order
)http://157.122.54.189:9088/videoimg/v1/videowp/videowp
(改变参数的order
)http://157.122.54.189:9088/videoimg/v1/videowp/category
<view class="video_tab_content">
<view v-if="current < 4">
<video-main :urlobj="{url: items[current].url, params:items[current].params}"></video-main>
</view>
<view v-if="current === 4">
<video-category></video-category>
</view>
</view>
具体哪个组件需要做分页,就在子组件内部用scroll-view
标签
watch: {
urlobj() {
this.getVideoList()
this.content = []
}
},
onLoad
里拿到数据子组件
<view class="video_item" v-for="item in content" :key="item.id" @click="goVideo(item)">
<image :src="item.img" mode="aspectFill"></image>
<view>
goVideo(item){
getApp().globalData.video = item
uni.navigateTo({
url: '/pages/videoPlay/index'
})
}
详情页面
onLoad() {
console.log(getApp().globalData.video)
}
<video :src="video.video" objectFit="fill"></video>
video标签的muted
属性控制是否静音false/true
<video :src="video.video" objectFit="fill" :muted="isMuted"></video>
<view class="iconfont iconjingyin" @click="handleMute"></view>
button标签的open-type
属性设置为share
<view class="iconfont iconzhuanfa">
<button open-type="share" class="share"></button>
</view>
.iconzhuanfa {
position: relative;
.share {
position: absolute;
right: 0;
bottom: 0;
height: 80rpx;
width: 80rpx;
opacity: 0;
}
}
async downloadVideo(){
const res = await uni.downloadFile({ url: this.video.video })
console.log('下载临时路径', res)
}
async downloadVideo(){
await uni.showLoading({title: '下载中'})
const { tempFilePath } = (await uni.downloadFile({ url: this.video.video }))[1]
await uni.saveVideoToPhotosAlbum({
filePath: tempFilePath
})
uni.hideLoading()
await uni.showToast({title: '下载成功'})
}
.bgImg {
position: absolute;
height: 100vh;
width: 100vw;
filter: blur(20px);
z-index: -1;
}