虾米项目
分类列表页面
创建分类列表页面 classlist.vue
设置分类列表页面布局
配置navigate设置 跳转页面 (我的评分,我的下载,专题推荐,分类页面)
配置 navigate的 opentype 类型为 relaunch
壁纸预览页面
新建页面 preview.vue
使用swiper布局,设置custom通屏样式
搭建静态页面,返回,信息,评分,下载,浮动框。
宽度样式
/**定位元素设置居中**/
position:absolute;
left:0;
right:0;
width:fit-content; /**宽度根据内容自适应**/
margin:auto;
background:rgba(0,0,0,0.3); /**背景透明 **/
backdrop-filter:blur(10rpx); /**模糊效果 **/
/**使用calc计算的时候,中间需要加空格**/
top:calc(10vh + 80rpx);
/**使用子元素选择器**/
.mask{
&>{
}
.time{
text-shadow: /**文字阴影 **/
}
}
flex: 1; 和 width: 0; 一起使用时,通常是用来让该元素在一个弹性布局中扩展,自动调整大小以占据剩余的空间,但它的初始宽度是 0
.value {
flex: 1;
width: 0;
}
添加点击事件,隐藏遮罩层
使用 uni-popup 拓展组件
infoPopupRef.value.open()
infoPopupRef.value.close()
自己写信息弹窗的遮罩层的样式和布局
标题布局
内容布局
-使用scroll-view组件 滚动效果
配置评分组件
配置评分组件只读,可以点击,设置value
设置评分样式
配置版权信息样式,配置关闭按钮样式和功能
uni-rate组件点击评分配置
允许评分半分
<uni-rate v-model="userScore" allowHalf />
设置禁用状态
<button @click="submitScore" :disabled="!userScore" type="default" size="mini" plain>确认评分</button>
返回按钮的样式
<view class="goBack" @click="goBack" :style="{ top: getStatusBarHeight() + 'px' }">
<uni-icons type="back" color="#fff" size="20"></uni-icons>
</view>
.preview{
width: 100%;
height: 100vh;
position: relative;
.mask{
& > view {
position: absolute;
left: 0;
margin: auto;
color: #fff;
right: 0;
width: fit-content;
}
}
.goBack {
width: 38px;
height: 38px;
background: rgba(0, 0, 0, 0.5);
left: 30rpx;
margin-left: 0;
border-radius: 100px;
top: 0;
backdrop-filter: blur(10rpx);
border: 1rpx solid rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
}
}
标签样式
<view class="row">
<text class="label">标签:</text>
<view class="value tabs">
<view class="tab" v-for="item in 3">标签名</view>
</view>
</view>
.row {
display: flex;
padding: 16rpx 0;
font-size: 32rpx;
line-height: 1.7em;
.label {
color: $text-font-color-3;
width: 120rpx;
text-align: right;
font-size: 30rpx;
}
.value {
flex: 1;
width: 0;
}
.tabs {
display: flex;
flex-wrap: wrap;
.tab {
border: 1px solid $brand-theme-color;
color: $brand-theme-color;
font-size: 22rpx;
padding: 10rpx 30rpx;
border-radius: 40rpx;
line-height: 1em;
margin: 4rpx 10rpx 10rpx 4rpx;
}
}
}
rem和em的区别
em
相对当前元素的 font-size 计算
特点:会受到父元素影响,适用于局部相对调整
rem(root em)
相对 html 根元素 <html> 的 font-size 计算
自定义头部
计算导航栏高度示意图
抽离计算系统高度
新建utils目录,并同时创建system.js文件
使用全局API获取设备信息
定义一个计算状态栏高度函数,暴漏这个函数
export const functon(){}
定义一个计算标题栏高度函数,暴漏这个函数
export const functon(){}
在页面中导入,然后调用
分类页面的标题栏设置
把标题栏组件引入,传值
预览页面标题栏设置
引入封装计算状态栏方法,然后调用,设置返回按钮的高度
自定义NavBar
/src/components/custom-nav-bar.vue,创建这个公共组件之前,已经封装了一个工具类/utils/system.js,获取statusbar,titleBar,NavBar的高度。
<template>
<view class="layout">
<view class="navbar">
<view class="statusBar" :style="{ height: getStatusBarHeight() + 'px' }"></view>
<view class="titleBar" :style="{ height: getTitleBarHeight() + 'px', marginLeft: getLeftIconLeft() + 'px' }">
<view class="title">{{ title }}</view>
<navigator url="/pages/search/search" class="search" v-if="!noSearch">
<uni-icons class="icon" type="search" color="#888" size="18"></uni-icons>
<text class="text">搜索</text>
</navigator>
</view>
</view>
<!-- 填充 由于上边设置了定位 把后续的内容往后填充 -->
<view class="fill" :style="{height:getNavBarHeight()+'px'}"></view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { getStatusBarHeight, getTitleBarHeight, getNavBarHeight, getLeftIconLeft } from '/utils/system.js';
// 接收title
defineProps({
title: {
type: String,
default: '壁纸'
},
noSearch: {
type: Boolean,
default: false
}
});
</script>
<style lang="scss" scoped>
.layout {
.navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 10;
background: linear-gradient(to bottom, transparent, #fff 400rpx), linear-gradient(to right, #beecd8 20%, #f4e2d8);
.statusBar {
}
.titleBar {
display: flex;
align-items: center;
padding: 0 30rpx;
.title {
font-size: 22px;
font-weight: 700;
color: $text-font-color-1;
}
.search {
width: 220rpx;
height: 50rpx;
border-radius: 60rpx;
background: rgba(255, 255, 255, 0.4);
border: 1px solid #fff;
margin-left: 30rpx;
color: #999;
font-size: 28rpx;
display: flex;
align-items: center;
.icon {
margin-left: 5rpx;
}
.text {
padding-left: 10rpx;
}
}
}
}
}
</style>
首页推荐图片点击跳转到预览页面
首页精选图标点击跳转分类列表页面
首页精选更多图片点击跳转到分类页面
创建公告详情页面样式和布局
使用 uni.tag 组件 标签样式
设置公告跳转到公告详情页面
接口
获取首页banner数据
获取首页banner数据,渲染数据
获取每日推荐接口
获取首页每日推荐数据,渲染数据
获取壁纸公告列表
获取首页壁纸公告数据,渲染数据
封装请求
新建一个 api 目录,里边存放的是各种接口。
在utils中新建一个 request.js 文件,作用是封装uni 请求。
@/home/home.js
import request from "/utils/request.js";
export function apiGetBanner() {
return request({
url: "/homeBanner"
})
}
export function apiGetDayRandom() {
return request({
url: "/randomWall"
})
}
export function apiGetNotice(data = {}) {
return request({
url: "/wallNewsList",
data
})
}
request.js
// 可引入pinia
// 基础配置
const BASE_URL = 'https://tea.qingnian8.com/api/bizhi';
const request = (config) => {
// 请求配置项
let {
// 请求接口
url,
// 请求体
data = {},
// 请求方法
method = "GET",
// 请求头
header = {}
} = config
// 拼接url
url = BASE_URL + url
// 配置请求头
header['access-key'] = "sakuna@445"
// 返回一个Promise对象
return new Promise((resolve, reject) => {
uni.request({
url,
data,
method,
header,
// 成功回调
success: (res) => {
// 判断成功响应 代码是否为 0
if (res.data.errCode === 0) {
// 返回数据
resolve(res.data)
} else if (res.data.errCode === 400) { // 代码是否为 400
// 提示错误 模态弹窗
uni.showModal({
title: "错误提示",
content: res.data.errMsg,
showCancel: false
})
// 返回失败的数据状态
reject(res.data)
} else { // 代码为其他 404
// 消息提示框
uni.showToast({
title: res.data.errMsg,
icon: "none"
})
// 返回失败的数据状态
reject(res.data)
}
},
// 失败的回调
fail: err => {
reject(err)
}
})
})
}
export default request;
渲染 banner 图片并实现跳转
<!-- 判断图片的性质 跳转到其他 微信小程序-->
<navigator v-if="item.target == 'miniProgram'" :url="item.url" class="like" target="miniProgram" :app-id="item.appid">
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
<!-- 普通图片跳转在本程序内的其他页面 - 分类页面 -->
<navigator v-else :url="`/pages/classlist/classlist?${item.url}`" class="like">
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
渲染 推荐 图片并实现跳转
使用 参数 传递id
获取大分类列表数据
通过封装的request请求和api接口获取数据
// 分类
export const apiGetClassify = (data={}) => request({
url:"/classify",
data
})
获取数据/pages/index/index.vue
// 获取分类
const getClassify = async () => {
const data = {
select: true // 无需 pageNum和pageSize
};
await apiGetClassify(data)
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
时间戳工具类
// timestamp 发布者发布的时间戳
const compareTimestamp = (timestamp) => {
// 获取当前时间戳
const currentTime = new Date().getTime();
// 时间时间间隔
const timeDiff = currentTime - timestamp;
if (timeDiff < 60000) {
return '1分钟内';
} else if (timeDiff < 3600000) {
return Math.floor(timeDiff / 60000) + '分钟';
} else if (timeDiff < 86400000) {
return Math.floor(timeDiff / 3600000) + '小时';
} else if (timeDiff < 2592000000) {
return Math.floor(timeDiff / 86400000) + '天';
} else if (timeDiff < 7776000000) {
return Math.floor(timeDiff / 2592000000) + '月';
} else {
return null;
}
}
export {
compareTimestamp,
gotoHome
}
theme-item 组件
<template>
<view class="themeItem">
<!-- 图片跳转 -->
<navigator :url="'/pages/classlist/classlist?id=' + item._id + '&name=' + item.name" class="box" v-if="!isMore">
<image class="pic" :src="item.picurl" mode="aspectFill"></image>
<view class="mask">{{ item.name }}</view>
<view class="tab" v-if="compareTimestamp(item.updateTime)">{{ compareTimestamp(item.updateTime) }}前更新</view>
</navigator>
<!-- 最后一张图显示更多跳转到分类 -->
<navigator url="/pages/classify/classify" open-type="reLaunch" class="box more" v-if="isMore">
<image class="pic" src="../../common/images/more.jpg" mode="aspectFill"></image>
<view class="mask">
<uni-icons type="more-filled" size="34" color="#fff"></uni-icons>
<view class="text">更多</view>
</view>
</navigator>
</view>
</template>
<script setup>
import { compareTimestamp } from '@/utils/common.js';
defineProps({
isMore: {
type: Boolean,
default: false
},
item: {
type: Object,
default() {
return {
name: '默认名称',
picurl: '../../common/images/classify1.jpg',
updataTime: Date.now() - 1000 * 60 * 60 * 5
};
}
}
});
</script>
tabBar大分类页面
<template>
<view class="classLayout pageBg">
<custom-nav-bar title="分类"></custom-nav-bar>
<view class="classify">
<theme-item v-for="item in classifyList" :item="item" :key="item._id"></theme-item>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { apiGetClassify } from "@/api/home/home.js"
import { onReady } from '@dcloudio/uni-app';
const classifyList = ref([]);
// 获取分类
const getClassify = async () => {
const data = {
pageSize:15
};
await apiGetClassify(data)
.then((res) => {
classifyList.value = res.data
})
.catch((err) => {
console.log(err);
});
};
onReady(()=>{
// 获取分类请求
getClassify()
})
</script>
<style lang="scss" scoped>
.classify{
padding:30rpx;
display: grid;
grid-template-columns: repeat(3,1fr);
gap:15rpx;
}
</style>
大分类跳转到分类详情页面
通过请求参数 classid 获取 分类详情数据列表
分类详情页面通过 onLoad 生命周期获取 参数
onLoad(()=>{
从 参数中 解构出 id name type
设置导航栏的标题
调用发送网络请求
})
onLoad((value) => {
// type 指的是 ? 判断是历史浏览 还是 分类
// {id: "6524a48f6523417a8a8b825d", name: "可爱萌宠"}
const { id, name, type = null } = value;
if (type) {
queryParams.type = type;
}
if (id) {
queryParams.classid = id;
}
// 修改导航标题
uni.setNavigationBarTitle({
title: name
});
// 发送获取分类数据请求
getClassList();
});
大分类列表页面数据渲染
封装获取分类列表页面数据请求
发送请求
// 分类数据请求
const getClassList = async () => {
// 判断是 分类 来的 还是 历史记录来的
if (queryParams.classid) {
await apiGetClassList(queryParams)
.then((res) => {
classList.value = res.data
})
.catch((err) => {
console.log(err);
});
}
// 历史记录
if (queryParams.type) {
await apiGetHistoryList(queryParams)
.then((res) => {
console.log("历史列表",res);
})
.catch((err) => {
console.log(err);
});
}
};
获取数据,渲染数据
触底加载*
使用 生命周期函数 onReachBottom() 监听触底
自定义 触底加载
// 触底加载
onReachBottom(()=>{
// 判断是否后续还有数据,没有数据则不发送请求
if(noData.value){
return;
}
// 页面加一
queryParams.value.pageNum++;
// 发送请求
getClassList()
})
使用 z-paging 插件实现防抖
https://ext.dcloud.net.cn/search?q=z-paging
基本使用
https://z-paging.zxlee.cn/start/use.html#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8
在线演示
https://demo.z-paging.zxlee.cn/#/
更改配置项 ,例如每页 获取多少个
https://z-paging.zxlee.cn/api/props/common.html
自定义下拉刷新
下载 zip 包,然后打开 找到 demo. 中refresher插槽,再复制 custom-refresher中的组件到自己的项目
使用 loading插槽
<template #load>
加载中,使用uni中的加载中样式。
底部使用
<template>
<z-paging ref="paging" v-model="dataList" @query="queryList">
<!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
<!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
<view class="item" v-for="(item,index) in dataList" :key="index">
<view class="item-title">{{item.title}}</view>
</view>
</z-paging>
</template>
<script setup>
import { ref } from 'vue';
const paging = ref(null)
// v-model绑定的这个变量不要在分页请求结束中自己赋值,直接使用即可
const dataList = ref([])
// @query所绑定的方法不要自己调用!!需要刷新列表数据时,只需要调用paging.value.reload()即可
const queryList = (pageNo, pageSize) => {
// 此处请求仅为演示,请替换为自己项目中的请求
request.queryList({ pageNo,pageSize }).then(res => {
// 将请求结果通过complete传给z-paging处理,同时也代表请求结束,这一行必须调用
paging.value.complete(res.data.list);
}).catch(res => {
// 如果请求失败写paging.value.complete(false);
// 注意,每次都需要在catch中写这句话很麻烦,z-paging提供了方案可以全局统一处理
// 在底层的网络请求抛出异常时,写uni.$emit('z-paging-error-emit');即可
paging.value.complete(false);
})
}
</script>
onReachBottom(()=>{
pageNum++
})
通过 解构把 新数据和老数据进行拼接
classList.value = [...classList.value,...res.data]
判断如果返回的数据小于pageSize,则不再发送请求,因为最后一次请求的数据,已经小于pageSize了,说明后续已经没有数据了。
触底加载loading样式
使用 uni-loading 插件
在分类列表页面的 html 结构中添加 uni-load-more 标签
在 uni-load-more 标签定义 loading-layout 类名,并把这个类名放在 common 目录中的公共 style.css中
通过 v-if 和 v-else 控制loading加载的显示与隐藏
通过 定义一个 noData 响应式对象,当发送请求相应后,后续没有数据了,改变noData的状态为true
触底加载 和 骨架屏加载的 v-if 判断逻辑是相反的
骨架屏加载显示 条件:数据列表的长度为空,并且 noData标记 为false
触底加载显示 条件:数据列表长度不为空,或者 noDate 标记为 true
通过三元表达式控制 加载中还是没有显示更多
把底部安全区域定义为 通用css样式
在底部新建一个空标签 设置类名为 底部安全区域的类型
<template>
<view class="classlist">
<!-- 顶部加载样式 无数据时,并且后续有数据 显示-->
<view class="loadingLayout" v-if="!classList.length && !noData">
<uni-load-more status="loading"></uni-load-more>
</view>
<!-- 内容 -->
<view class="content">
<navigator url="/pages/preview/preview" class="item" v-for="item in classList" :key="item._id">
<image :src="item.smallPicurl" mode="aspectFill"></image>
</navigator>
</view>
<!-- 顶部加载样式 有数据,或者后续无数据 显示-->
<view class="loadingLayout" v-if="classList.length || noData">
<uni-load-more :status="noData ? 'noMore' : 'loading'"></uni-load-more>
</view>
</view>
</template>
骨架屏效果
使用 uv-skeletons 插件
在分类页面 使用本地存储 存储数据
使用本地存储 API存储 数据
uni.setStorageSync("")
在预览页面 使用本地存储 读取数据
uni.getStorageSync("")
// 读取数据后,然后处理数据
把数组中元素添加picurl属性,修改后缀
在预览页面修改 src 绑定的值
修改预览页面的 数量 显示
点击图片从当前图片显示
分类列表页面 通过 url的path 传递 id
预览页面通过 onLoad 函数读取 path中的id属性
使用 findIndex() 方法获取 元素对应的索引值
在swiper组件上设置current属性,对应当前 数据列表的索引值
在swiper组件上设置@change事件,创建change事件回调
const swiperChange = (e) => {
current.value = e.datail.current
}
自定义一个数组,实现swiper的懒加载的效果
在image标签上设置v-if属性,v-if通过判断仅显示当前索引值(存在问题,弃用)
创建一个数组对象,把看过的图索引值放在Set数组中,然后判断当前索引值是否包含在数组中,包含则返回true,不包含则返回false
预加载,向左和向右,
在数组对象中,把当前看过的图的索引的前一张和后一张,都放在数组中,实现友好交互。
// 设置预览页面数组对象为 set数组
readImgs.value = [...new Set(readImgs.value)]
渲染信息数据
点击信息按钮显示 壁纸信息
定义一个响应式数据
在onLoad生命周期函数中,把图片的信息赋值给 currentInfo响应式数据
评分的操作
点击评分按钮,弹出评分弹框
在确认评分的回调函数中,获取当前用户操作的评分
封装网络接口
发送网络请求
对相应做出反应,评分成功弹窗,评分失败弹窗,评分完成后关闭弹窗。
评分完成后,向分类列表的对象中添加一个userScore属性。
由于没有刷新,评分完成后,向缓存中重新存储分类列表
在评过分的壁纸上,再次点击评分,首先判断当前图片对象中是否存在 userScore属性,设置isScored响应式数据
然后把 userScore的属性值,更改为评分的双向绑定值。
根据isScored的状态,绑定确定评分按钮的禁用状态,同时绑定uni-rate组件的评分状态为disabled
点击确认评分时,发送网络请求时,加上加载状态
uni.showLoading({
})
返回数据时,加载完成状态。
解构同时设置别名
let {classid,_id:wallId} = currentInfo.value
在之后使用 wallId 就相当于使用 _id
图片下载
使用条件编译的语法,区别对待图片下载
const clickDownload = () => {
// #ifdef H5
H5逻辑
// #endif
// #ifndef H5
uni.saveImageToPhotosAlbum({
filePath: currentInfo.value.picurl
success:(res) => {
}
})
// #endif
}
设置 小程序的 downloadFile 合法域名
登陆小程序后台
使用 uni.getImageInfo 方法获取图片信息,小程序下获取网络图片信息需先配置download域名白名单才能生效。
uni.getImageInfo({
src:currentINfo.value.picurl,
success:(res)=>{
console.log(res)
}
})
uni.showloading({
title:'加载中',
mask:true // 遮罩
})
uni.saveImageToPhotosAlbum({
filePath:currentInfo.value.picurl,
success:(res)=>{
console.log(res)
},
fail:err=>{
// 如果没有保存成功
if(err.errMsg === '...'){
// 提示 保存失败,请重新点击下载
return;
}
// 再次提示,需要授权保存到相册
uni.showModal({
title:"提示",
content:"需要授权保存相册",
// 成功回调,点击了确认
success:res=>{
if(res.confirm){
// 打开手动授权
uni.openSetting({
// 成功的回调
success:(setting)=>{
if(setting.authSetting['scope.writePhotosAlbum']){
uni.showToast({
title:"",
})
}
else{
// 获取授权失败
}
}
})
}
}
complete:()=>{
// 设置加载效果隐藏
}
})
}
})
// 下载回调
const clickDown = async () => {
// 条件编译 H5 的语法
// #ifdef H5
uni.showModal({
content: '请长按保存壁纸',
showCancel: false
});
// #endif
// 非H5的语法
// #ifndef H5
try {
// 提示
uni.showLoading({
title: '下载中...',
mask: true
});
// 解构 classid 和 wallid
let { classid, _id: wallId } = currentInfo.value;
// 发送 下载 图片请求
let res = await apiWriteDownload({
classid,
wallId
});
// 判断如果响应代码 不为 0 抛出异常
if (res.errCode != 0){
throw res
}
// 使用 uni API
uni.getImageInfo({
// 图片路径
src: currentInfo.value.picurl,
// 获取图片地址成功时回调
success: (res) => {
console.log("获取图片地址成功时回调",res)
// 保存到 相册 API
uni.saveImageToPhotosAlbum({
filePath: res.path,
// 保存到相册成功时的回调
success: (res) => {
uni.showToast({
title: '保存成功,请到相册查看',
icon: 'none'
});
},
// 保存到相册失败的回调
fail: (err) => {
// 用户取消
if (err.errMsg == 'saveImageToPhotosAlbum:fail cancel') {
uni.showToast({
title: '保存失败,请重新点击下载',
icon: 'none'
});
return;
}
// 没有授权
uni.showModal({
title: '授权提示',
content: '需要授权保存相册',
// 提示成功弹出时的回调
success: (res) => {
if (res.confirm) {
// 打开设置 API
uni.openSetting({
// 打开设置成功的回调
success: (setting) => {
console.log(setting);
// 开启写入到相册权限
if (setting.authSetting['scope.writePhotosAlbum']) {
uni.showToast({
title: '获取授权成功',
icon: 'none'
});
} else {
uni.showToast({
title: '获取权限失败',
icon: 'none'
});
}
}
});
}
}
});
},
// 最终处理
complete: () => {
// 关闭加载样式
uni.hideLoading();
}
});
}
});
// 存在异常
} catch (err) {
console.log(err);
uni.hideLoading();
}
// #endif
};
设置小程序的 服务内容声明,用户隐私指引
设置用户隐私 拒绝 的回调处理
下载记录
封装下载记录请求接口
发送请求,对响应数据进行处理
使用try catch 捕获异常
try {
}catch(err){
// 取消加载效果
}
处理 v-for 中的key的报错
处理 生命周期,初始值报错
设置初始值,
设置key
设置v-if判断,数据存在时,再渲染
分享
分享小程序
onShareAppMessage(()=>{
return {
title:'',
path:'' + 响应式数据
}
})
分享朋友圈
onShareTimeline(()=>{
return {
title:'咸虾米'+ pageName,
query:'id=' + queryParmas.classid + "&name="+pageName
}
})
分享传递参数
onload((e)=>{
// 解构url中的参数
})
onShareAppMessage(()=>{
return {
title:'咸虾米'+ pageName,
path:'/pages/classlist/classlist?id=' + queryParmas.classid + "&name="+pageName
}
})
预览页面分享传递参数
在onLoad()的回调函数中获取,url中query参数,
判断query中的 type是否为 share,如果是share则代表是从别人分享过来的
于是需要封装接口,重新发送请求,获取壁纸列表,
并把壁纸列表的picurl处理为bigurl,即可实现需求
对分享后页面的返回按钮进行处理
如果是从分享过来的,进行单独处理
通过在 goBack的回调中
goBack(){
uni.navigateBack({
success:()=>{
},
fail:()=>{
// 跳转到首页
uni.reLaunch({
url:"/page/index/index"
})
}
})
}
在分类列表页面,对localStorage进行销毁
在 onUnLoad() 中,处理 localStorage的数据,进行销毁。
首页随机页面点击后跳转到预览页面
在跳转到预览页面的回调中
把随机页面存储到localStorage中
根据id,进行传参,显示那一张图片
处理popup底部弹窗空缺处理-安全区域处理-底部手势提示栏
在 uni-popup官方组件的源代码控制
/uni_modules/uni-popup/components/uni-popup.vue
uni-popup.vue 349行代码进行注释
paddingBottom: this.safeAreaInsets + 'px', 注释掉这行
如果需要参数的页面没有接收到参数,则提示无参数,选择回到首页弹窗
在 预览 页面,classList页面这种需要参数的页面,如果路由没有传递参数则弹窗,提示
把这个方法gotoHome写到 公共的库 中 utils/common.js
然后把这个方法导入到需要页面中
并在 onLoad 生民周期中 判断 如果某些参数咩有传递 则 调用这个方法
个人用户
布局个人用户页面,设置导航栏的高度
封装用户信息接口
获取用户数据,渲染用户数据
// 跳转到分类
const goClassListDown = () => {
uni.navigateTo({
url: `/pages/classlist/classlist?name=我的下载&type=download`
});
}
const goClassListScore = () => {
uni.navigateTo({
url: `/pages/classlist/classlist?name=我的评分&type=score`
});
}
// 获取用户信息
const getUserInfo = ()=>{
apiUserInfo().then(res=>{
console.log(res);
userInfo.value = res.data
})
}
onLoad(()=>{
// 获取用户信息
getUserInfo();
})
无数据时,使用空数据效果 classList页面
<!-- 顶部加载样式 有数据,或者后续无数据 显示-->
<view class="loadingLayout" v-if="classList.length || noData">
<uni-load-more :status="noData ? 'noMore' : 'loading'"></uni-load-more>
</view>
我的下载和我的评分页面
点击我的下载按钮,跳转到分类列表页面
在跳转时传递标题和type类型:download或scope
封装获取下载历史接口
封装获取评分的接口
用户登陆*
公告详情
封装公告详情接口
获取公告详情数据,并渲染数据。
富文本的渲染
渲染后端返回的html代码
默认会把html标签直接展出来
使用 rich-text 标签展示富文本。
rich-text :nodes=""
或使用第三方富文本插件 vue3
mp-text :content=""
在首页的公告 navegate 标签中设置 跳转的url 同时拼接 参数。
在公告详情页面使用 onLoad 函数接收 id。
在公告详情页面获取公告详情接口中接收id,发送请求
点击常见问题跳转到公告详情页面,同时传递 id 参数,发送请求
渲染数据
搜索页面
创建 搜索页面 search.vue ,布局,
首页 搜索标签 设置navigate便签 实现跳转
使用 uni-search-bar 组件 搜索框
在扩展组件中,安装uni-search-bar组件
插件市场,安装uv-empty组件,空状态,搜索为空时的样式
搜索框中的确认搜索 onSearch 回调中
获取搜索的内容
把搜索的内容添加到搜索历史的数组中,使用 new Set([]),去重处理
把搜索历史放置到localStorage中,历史搜索默认从 localStorage中读取,否则为空
搜索删除按钮 removeHistory 回调
弹窗提示 uni.showModal
删除缓存 uni.remove
删除响应式对象 searchHistory.value = []
若搜索历史为空,则不显示搜索删除按钮,和最近搜索标签
在 tab 标签的点击事件中 onClick 回调中
把tab标签中的内容,复制到搜索栏
最近搜索
热门搜索
封装搜索接口,发送网络请求,获取搜索数据,渲染数据
点击标签 tag 时,同时发送请求,获取数据
触底加载数据 onReachBottom 事件中
如果noDate为true,return
pageNum ++
处理响应结果,把老数据和新数据合并
需要把分类列表放在localStorage中
发送网络请求
加载样式进行处理,追加安全区域
如果后续没有数据,则 使用 noData 作为标记 为 true
使用 noSearch 标记判断 搜索内容和空状态
初始化 initParams
清空 classList
清空 noData标记 noSearch 标记
清空 queryParams 为初始化
在清空搜索和取消按钮 调用 init
在点击标签的回调总 调用init
搜索列表页面
没有 搜索列表页面 ,通过v-if 控制最近搜索的显示与隐藏
点击搜索列表的元素时,跳转到预览页面
<navigate :url = `/page/...?id=${item._id}`>
离开页面,清空localStorage中的classList
搜索框初始化,搜索记录限制条数,搜索中添加loading效果。
初始化函数中,传递value,如果输入框没有,则为空,存在则为value
在搜索 onSearch 回调中 添加搜索加载,
通过数组的 slice方法 截取 只获取10个内容
首页轮播图跳转和修复bug
swiper 标签中 添加 navigate 标签,同时修改 css样式
专题精选 更多按钮上 进行跳转。
设置swiper标签
<swiper circular indicator-dots indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#fff" autoplay>
<swiper-item v-for="item in bannerList" :key="item._id">
<!-- 判断图片的性质 跳转到其他 微信小程序-->
<navigator v-if="item.target == 'miniProgram'" :url="item.url" class="like" target="miniProgram" :app-id="item.appid">
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
<!-- 普通图片跳转在本程序内的其他页面 -->
<navigator v-else :url="`/pages/classlist/classlist?${item.url}`" class="like">
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
</swiper-item>
</swiper>
超过三个月之后返回null,进行修复,通过v-if进行修复
修改 /utils/common.js 中的代码
修改 /components/theme-item/theme-item.vue页面
打包
微信小程序
1. 注册微信小程序开发者
设置
服务类名 信息查询 图片处理
开发管理:
配置服务器域名:https:
downloadFile 合法域名:新增一个合法域名
unloadFile 合法域名:新增合法域名
2. manifest.json
设置appid,上传代码时自动压缩勾选
3. 发行
微信小程序
4. 打包目录
unpackage/dist/dev/mp-weixin
5. 开发者工具中
点击上传
6. 微信小程序的后台
版本管理-开发版本-提交审核
7. 快的审核 内容发布 新闻发布 审核的时间更长
抖音小程序
1. 运行到 抖音开发者工具
设置appId,设置域名域名
2. 注册抖音小程序开发者
抖音开放平台,快速入住,进入控制台,
使用 条件编译 隐藏标题栏 样式,仅在抖音小程序不显示。
备案
3. 发行,抖音,上传
4. 上传,
5. 配置域名,downloadFile合法域名
H5打包和发布:使用unicloud网页托管
1. 进入manifest.json文件
Web配置:
配置标题
配置路由模式
运行的基础路由 根./ 或者 /xxmwall/
配置定位和地图
2. 源码视图中配置跨域
3. 打包:发行-网站
配置名称
4. 打包路径
unpackage-dist-build-h5
5. 把目录名称为 xxmwall
6. 使用unicloud服务空间
使用前端网页托管
注意 目录名称 必须 与 manifest.json 名称一致。
参数配置,使用默认域名访问
配置跨域,跨域配置,把默认域名新增上
7. 草料二维码,将https链接转为二维码
8. 购买域名
安卓APP打包
https://hx.dcloud.net.cn/Tutorial/App/use-chrome-to-debug-android-apps?id=附录:android模拟器调试环境
1. 运行到手机模拟器
2. manifest.json 进行配置
应用名称 1.0.1 应用版本号 101
设置应用图标:
App启动界面,原生隐私提示框
App模块:
权限配置:
常用配置:
3. 打包自定义基座
4. 发行
原生APP-云打包,打正式包
打包
消息推送*
3.1-3.2-3.3 push推送更新
HBuilder连接夜神模拟器进行调试
夜神模拟器
编写文件注意后缀名为.bat
d:
cd D:\Program Files\Nox\bin
nox_adb connect 127.0.0.1:62001
nox_adb devicese
d:
cd D:\HBuilder
adb connect 127.0.0.1:62001
adb devices
输入命令:netstat -ano,列出所有端口的情况!
输入命令:netstat -aon|findstr “端口号”!
不通过可以尝试另换端口,或者检查路径是否准确!