feat:初始提交uni-app项目

This commit is contained in:
2026-01-14 18:19:33 +08:00
commit 0dcbd340e6
515 changed files with 38560 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
<template>
<view class="classLayout pageBg">
<custom-nav-bar title="分类"></custom-nav-bar>
<view class="classify">
<theme-item v-for="item in classifyList"
:key="item._id"
:item="item">
</theme-item>
</view>
</view>
</template>
<script setup>
import {ref} from "vue"
import {apiGetClassify} from "@/api/apis.js"
import {onShareAppMessage,onShareTimeline} from "@dcloudio/uni-app"
const classifyList = ref([])
const getClassify = async ()=>{
let res = await apiGetClassify({
// select:true
pageSize:15
});
// console.log(res);
classifyList.value = res.data
}
getClassify();
//分享给好友
onShareAppMessage((e)=>{
//分享这里是需要有一个 “return” 的
return{
title:"hzb壁纸,精选分类",
path:"/pages/classfy/classfy"
}
})
//分享到朋友圈
onShareTimeline(()=>{
return{
// 标题
title:"hzb壁纸,精选分类",
// 分享时候的图片地址。可以本地也可以网络图
imageUrl:"/static/images/logo2.jpg"
}
})
</script>
<style lang="scss" scoped>
.classLayout{
.classify{
padding: 30rpx;
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 15rpx;
}
}
</style>

View File

@@ -0,0 +1,269 @@
<template>
<view class="classlist">
<!--
有图片的情况图片加载出来之后我们就要取消这个动画
没有图片的话我们也不能要这个加载动画然后还要显示没有更多了
刚进入页面时noData是false
classList.length也是0转换传给你false
然后请求完之后根据请求的内容当中有没有数据才改变noData的值
重点是页面执行的顺序问题有疑惑就思考这个
noData原本是false有数据之后就变成了true'”
-->
<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?id=' + item._id" class="item"
v-for="item in classList"
:key="item._id">
<image :src="item.smallPicurl" mode="aspectFill"></image>
</navigator>
</view>
<!-- 避免出现两个加载框的情况需要考虑这个v-if的条件
classList有值 或者 没有更多数据了noData变成true 之后,这个框 才可以出现
所以是或者关系 用 ||(或) -->
<!--
刚进入页面时noData是false
classList.length也是0转换传给你false
然后请求完之后根据请求的内容当中有没有数据才改变noData的值
重点是页面执行的顺序问题。有疑惑就思考这个
没有数据noData就变成了true显示“noMore”
有数据noDatashi false显示“loading” -->
<!-- <view class="loadingLayout" v-if="classList.length || noData">
<uni-load-more :status="noData?'noMore':'loading'"> </uni-load-more>
</view> -->
<!-- 或 表示 现在的页面有数据,但是点击的另一个标签没有数据 -->
<view class="loadingLayout bottom" v-if="classList.length || noData">
<uni-load-more :status="noData?'noMore':'loading'"> </uni-load-more>
<!-- <zero-loading type="sword" position="absolute"></zero-loading> -->
</view>
<!-- 程序使用时增加安全高度防止被home键挡住提高用户使用 -->
<view class="safe-area-inset-bottom"></view>
</view>
</template>
<script setup>
import {ref} from "vue"
import {apiGetClassList,apiGetHistoryList} from "@/api/apis.js"
import {onLoad,onUnload,onReachBottom,onPullDownRefresh} from "@dcloudio/uni-app"
// import {zero-loading} from "@/uni_modeules"
import {onShareAppMessage,onShareTimeline} from "@dcloudio/uni-app"
import { gotoHome } from "../../utils/common"
const classList = ref([])
const noData = ref(false)
//为了方便后期传入参数将queryParams方面外面要加属性的话直接一个 点方法(. 就可以了
//定义data参数
const queryParams = {
pageNum:1,
pageSize:12
}
let pageName;
//onLoad可以接收上个页面传入的参数
onLoad((e)=>{
// console.log(e);
//这里的type联系的是user页面的"我的下载""我的评分"这两个页面
let {id=null,name=null,type=null} =e;
// id是必定有的,但是这里的type是只有user页面的"我的下载""我的评分"传进来的
//如果页面没有传入id的话让让其返回主页面去
// if(!id){
// return gotoHome();
// }
// 如果上个页面传入的东西包含id的话,就给queryParams加一个id属性,另其等于上个页面传入的id
// 除了这两个判断外,其他东西都是不需要改变的
if(id){
//给queryParams加一个id属性
queryParams.id = id
}
if(type){
// 给查询参数queryParams添加一个type属性,令其等于这个其他页面传入的type(就是 download 或者 score )
queryParams.type = type
}
pageName = name
// console.log(id,name);
// 传入名字是为了动态改变导航栏标题
uni.setNavigationBarTitle({
title:name
})
getClassList();
})
//onUnload表示的是撤销页面时需要做的事情。撤销当前看的页面就remove掉缓存。提高性能
onUnload(() =>{
uni.removeStorageSync("storgClassList")
})
//下拉刷新
// onPullDownRefresh(()=>{
// queryParams.pageNum++;
// // getClassList();
// apiGetClassList({
// classid:queryParams.id,
// pageNum:queryParams.pageNum,
// pageSize:queryParams.pageSize
// // classid:"6524ace7213929cbcee72e4d"
// }).then(res =>{
// if (res.data.errCode === 0) {
// // 将新数据放在原有数据的前面(最新的内容在顶部)
// //这里res就是res图片是存在res.data.data里面与下面的用res接收Promise对象是不一样的
// classList.value = [...res.data.data, ...classList.value]
// // 显示成功提示
// uni.showToast({
// title: "刷新成功",
// icon: "success"
// })
// } else {
// uni.showToast({
// title: res.data.errMsg,
// icon: "none"
// })
// }
// }).catch(err => {
// uni.showToast({
// title: "刷新失败",
// icon: "none"
// })
// }).finally(() => {
// // 停止下拉刷新动画
// uni.stopPullDownRefresh()
// })
// })
//触底刷新的时候需要加载新的图片出来因此需要传入一个新的参数pageNum
onReachBottom(()=>{
//如果后面没有数据了,再触底就不发送网络请求了
if(noData.value){
return
}
queryParams.pageNum++;
getClassList();
// console.log(res);
// <uni-load-more :status="more"/>
})
const getClassList = async ()=>{
//需要先在外面声明一个res
let res;
//id参数由上面的queryParams.id提供,如果有id就进行有id的操作
if(queryParams.id){
res = await apiGetClassList({
// queryParams
classid:queryParams.id,
pageNum:queryParams.pageNum,
pageSize:queryParams.pageSize
// classid:"6524ace7213929cbcee72e4d"
});
}
//type参数由上面的queryParams.type提供,如果有type,就进行有type的操作
if(queryParams.type){
res = await apiGetHistoryList({
// queryParams
type:queryParams.type,
pageNum:queryParams.pageNum,
pageSize:queryParams.pageSize
});
}
classList.value = [...classList.value,...res.data]
//比较发送的长度 和 返回数据的长度。如果原来的大于发送来的,说明后面没数据了,所以将
if(queryParams.pageSize > res.data.length){
noData.value = true
}
//将数据保存到缓存里面
uni.setStorageSync("storgClassList",classList.value)
// console.log(res);
}
//分享给好友
onShareAppMessage((e)=>{
//分享这里是需要有一个 “return” 的
//在上面的onLoad中已经将列表的id赋给了queryParams的id属性
return{
title:"hzb壁纸-"+pageName,
// 必须传递id进去否则进不去内部页面
path:"/pages/classlist/classlist?id="+queryParams.id+"&name="+pageName
}
})
//分享到朋友圈
onShareTimeline(()=>{
return{
// 标题
title:"hzb壁纸~~~",
// 分享时候的图片地址。可以本地也可以网络图
// imageUrl:"/static/images/logo2.jpg"
//要想看朋友圈这个需要带的query参数
query:"id="+queryParams.id+"&name="+pageName
}
})
</script>
<style lang="scss" scoped>
.classlist{
.content{
display: grid;
// position: relative;
grid-template-columns: repeat(3,1fr);
gap: 5rpx;
padding: 5rpx;
.item{
// 没有加高度的话没有效果
height:440rpx ;
image{
width: 100%;
height: 100%;
//保证图片之间的空格是一致的。不然你行级块元素有空格
display: block;
}
}
}
}
</style>

370
pages/index/index.vue Normal file
View File

@@ -0,0 +1,370 @@
<template>
<view class="homeLayout pageBg">
<custom-nav-bar title="推荐"></custom-nav-bar>
<view class="banner">
<swiper :indicator-dots="true" :circular="true"
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#fff"
:autoplay="true">
<swiper-item v-for="item in bannerList" :key="item._id">
<!-- 这是跳转到小程序时的情况 -->
<!--
需要添加 target app-id
小程序的url时这样的 url: "/pages/index/index"
是其他小程序的主页
-->
<navigator v-if="item.target == 'miniProgram'"
class="like"
:url="item.url"
target="miniProgram"
:app-id="item.appid"
>
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
<!-- 这里的url是这种形式的 url: "id=6524adaffe975f09c72ce896&name=色彩艺术"-->
<!-- 这是跳转到分类页面的情况 -->
<navigator v-else
:url="`/pages/classlist/classlist?${item.url}`"
class="like">
<image :src="item.picurl" mode="aspectFill"></image>
</navigator>
</swiper-item>
</swiper>
</view>
<view class="notice">
<view class="left">
<uni-icons type="sound-filled" size="20" color="#28b389"></uni-icons>
<text class="text">公告</text>
</view>
<view class="center">
<swiper :vertical="true" :autoplay="true" interval="1500"duration="300":circular="true">
<swiper-item v-for="item in noticeList" :key="item._id">
<!-- 这里需要传入id表明是哪个具体的公告 -->
<navigator :url="'/pages/notice/detail?id='+item._id">
{{item.title}}
</navigator>
</swiper-item>
</swiper>
</view>
<view class="right">
<uni-icons type="forward" size="20" color="#28b389"></uni-icons>
</view>
</view>
<view class="select">
<common-title>
<template #name>每日推荐</template>
<template #custom>
<view class="date">
<uni-icons type="calendar" size="20" color="#28b389"></uni-icons>
<view class="text">
<uni-dateformat :date="Date.now()" format="dd日"></uni-dateformat>
</view>
</view>
</template>
</common-title>
<view class="content">
<scroll-view :scroll-x="true" >
<view class="box" v-for="item in randomList" :key="item._id"
@click="goPreview(item._id)">
<!-- mode = aspectFill 显示最短边 -->
<image :src="item.smallPicurl" mode="aspectFill"></image>
</view>
</scroll-view>
</view>
</view>
<view class="theme">
<common-title>
<template #name>专题精选</template>
<template #custom>
<!--
注意这里跳转的是tabBar页面
因此需要搭配open-type = switchbar/relaunch
-->
<navigator
url="/pages/classify/classify"
class="more"
open-type="reLaunch"
>More+</navigator>
</template>
</common-title>
<view class="content">
<theme-item :isMore="false" v-for="item in classifyList"
:key="item._id"
:item="item"></theme-item>
<theme-item :isMore="true"></theme-item>
</view>
</view>
</view>
</template>
<script setup>
import {ref} from "vue"
import {onShareAppMessage,onShareTimeline} from "@dcloudio/uni-app"
import {apiGetBanner,apiGetDayRandom,apiGetNotice,apiGetClassify} from "@/api/apis.js"
const bannerList = ref([])
const randomList = ref([])
const noticeList = ref([])
const classifyList = ref([])
// 获取Banner的图片
const getBanner = async ()=>{
//apiGetBanner()返回的结果是一个Promise对象,需要用await接收
let res = await apiGetBanner();
// console.log(res);
bannerList.value = res.data
}
// const getBanner = async () => {
// try {
// let res = await apiGetBanner();
// console.log(res);
// if (res.data.errCode === 0) {
// bannerList.value = res.data.data;
// } else {
// handleApiError(res.data.errMsg, '获取 Banner 失败');
// }
// } catch (error) {
// handleApiError(error, '获取 Banner 出现异常');
// }
// };
// 获取每日推荐的内容
const getDayRandom = async ()=>{
let res = await apiGetDayRandom();
// console.log(res);
randomList.value = res.data
}
// const getDayRandom = async () => {
// try {
// let res = await apiGetDayRandom();
// console.log(res);
// if (res.data.errCode === 0) {
// randomList.value = res.data.data;
// } else {
// console.error('获取每日推荐失败:', res.data.errMsg);
// }
// } catch (error) {
// console.error('获取每日推荐出现异常:', error);
// }
// };
//获取公告的内容
const getNotice = async ()=>{
let res = await apiGetNotice({select:true});
// console.log(res);
noticeList.value = res.data
}
// const getNotice = async () => {
// try {
// let res = await apiGetNotice();
// console.log(res);
// if (res.data.errCode === 0) {
// noticeList.value = res.data;
// } else {
// console.error('获取公告失败:', res.data.errMsg);
// }
// } catch (error) {
// console.error('获取公告出现异常:', error);
// }
// };
//预览大图
const goPreview = (id)=>{
//如果没有加下面这个东西,就会导致“每日推荐”的图片是看不了的。因为没有缓存.要缓存的数据randomList列表
uni.setStorageSync("storgClassList",randomList.value)
uni.navigateTo({
url:"/pages/preview/preview?id="+id
})
}
//分类列表内容
const getClassify = async ()=>{
let res = await apiGetClassify({
select:true
});
// console.log(res);
classifyList.value = res.data
}
//分享给好友
onShareAppMessage((e)=>{
//分享这里是需要有一个 “return” 的
return{
title:"hzb壁纸",
path:"/pages/index/index"
}
})
//分享到朋友圈
onShareTimeline(()=>{
return{
// 标题
title:"hzb壁纸~~~",
// 分享时候的图片地址。可以本地也可以网络图
imageUrl:"/static/images/logo2.jpg"
}
})
getBanner();
getDayRandom();
getNotice();
getClassify();
</script>
<style lang="scss" scoped>
.homeLayout{
.banner{
width: 750rpx;
padding: 30rpx 0;
swiper{
width: 750rpx;
height:340rpx;
swiper-item{
width:100%;
height: 100%;
padding: 0 30rpx;
.like{
width: 100%;
height: 100%;
image{
width: 100%;
height: 100%;
border-radius:10rpx;
}
}
}
}
}
.notice{
//使整体居中的方式
// 这个宽度使提前算好的因为你左右两边都是30rpx的间距
//整个大小是750750 —— 60 = 690
width: 690rpx;
margin: 0 auto;
height: 80rpx;
background: #f9f9f9;
//让文字显示一行
line-height: 80rpx;
//边框的弧度与高度一样就好看
border-radius: 80rpx;
//使内容位于同一行
display: flex;
.left{
//140rpx宽度的居中样式好看100rpx的居中样式不行
width: 140rpx;
// 细节处理 使这部分内容居中处理
display: flex;
align-items: center;
justify-content: center;
//细节处理 控制一下文字的样式
.text{
color: $text-font-color-1;
// 控制文字大小
font-size: 28rpx;
//控制文字的加粗程度
font-weight: 600;
}
}
.center{
// “flex1” 的意思是自动填充完剩下的内容
flex: 1;
swiper{
//继承父级的百分百这样这个swiper只会在框框的高度里面跳不会跃出高度
height: 100%;
swiper-item{
//继承父级的高度
height: 100%;
//细节处理 控制文字的大小
font-size: 30rpx;
color: #666;
//细节处理 文字太多了怎么办? --> 不能换行、还要有省略号
overflow: hidden;
white-space: nowrap;
//细节处理 文字超出部分用省略号
text-overflow: ellipsis;
}
}
}
.right{
width: 70rpx;
//细节处理 让内容居中
display: flex;
align-items: center;
justify-content: center;
}
}
.select{
padding-top: 50rpx;
.date{
color: $brand-theme-color;
display: flex;
align-items: center;
}
.content{
//750-30==720
width: 720rpx;
margin-left: 30rpx;
margin-top:30rpx;
scroll-view{
white-space: nowrap;
.box{
display: inline-block;
//宽高比大约是12.2左右
width: 200rpx;
height: 430rpx;
//如果你不给图片添加下面的宽度和高度你这个margin-right是没用用的
margin-right: 15rpx;
image{
//继承父级宽度和高度的100%
width: 100%;
height: 100%;
}
}
.box:last-child{
margin-right: 30rpx;
}
}
}
}
.theme{
padding: 50rpx 0;
.more{
font-size: 32rpx;
color: #888;
}
.content{
margin-top:30rpx;
padding: 0 30rpx;
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 15rpx;
}
}
}
</style>

116
pages/notice/detail.vue Normal file
View File

@@ -0,0 +1,116 @@
<template>
<view class="noticeLayout">
<view class="title">
<view class="tag">
<!-- <uni-tag text="置顶" :inverted="true" type="error" /> -->
<uni-tag text="置顶" :inverted="true" type="error" v-if="detail.select"></uni-tag>
</view>
<view class="font">{{detail.title}}</view>
</view>
<view class="info">
<view class="item">{{detail.author}}</view>
<view class="item">
<uni-dateformat :date="detail.publish_date" format="yyyy-MM-dd hh:mm:ss"></uni-dateformat>
</view>
</view>
<view class="content">
<!-- 内容区域 -->
<!-- 对于富文本<rich-text>我们不能直接在页面进行展示
需要利用组件<rich-text :node="富文本链接">进行展示 -->
<!-- <rich-text :nodes="detail.content"></rich-text> -->
<!-- 或者是利用插件商城的其他插件 -->
<mp-html :content="detail.content" />
</view>
<view class="count">
阅读数量:{{detail.view_count}}
</view>
</view>
</template>
<script setup>
import { apiNoticeDetail } from "@/api/apis.js"
import {ref} from "vue"
import {onLoad} from "@dcloudio/uni-app"
const detail = ref({})
const noticeId = ref('')
const noticeName = ref('')
// let noticeId
onLoad((e)=>{
console.log(e);
noticeId.value = e.id
noticeName.value = e.name
// 注意必须先获得这个id才能进行getNoticeDetail()操作
uni.setNavigationBarTitle({
title:noticeName.value
})
getNoticeDetail()
})
const getNoticeDetail = async () =>{
let res = await apiNoticeDetail({id:noticeId.value});
detail.value = res.data
console.log(res);
}
</script>
<style lang="scss" scoped>
.noticeLayout{
padding: 30rpx;
.title{
display: flex;
align-items: center;
font-size: 40rpx;
color: #111;
line-height: 1.6em;
padding-bottom: 30rpx;
.tag{
// // width: 50rpx;
// height: 100rpx;
// font-size: 20rpx;
// color: #e3a7a8;
// padding: 10rpx;
// border: 1px solid #e3a7a8;
//缩小0.8倍
transform: scale(0.8);
//缩小的起始点
transform-origin: left center;
flex-shrink: 0;
}
.font{
// font-size: 40rpx;
// color: #0b0b0b;
padding-left: 4rpx;
}
}
.info{
display: flex;
align-items: center;
color: #999;
font-size: 28rpx;
.item{
padding-left: 20rpx;
}
}
.content{
padding: 50rpx 0;
}
.count{
color: #999;
font-size: 28rpx;
}
}
</style>

13
pages/notice/notice.vue Normal file
View File

@@ -0,0 +1,13 @@
<template>
<view class="">
</view>
</template>
<script setup>
</script>
<style lang="scss" scoped>
</style>

707
pages/preview/preview.vue Normal file
View File

@@ -0,0 +1,707 @@
<template>
<view class="preview">
<swiper :circular="true" :current="currentIndex" @change="swiperChange">
<swiper-item v-for="(item,index) in classList" :key="item._id">
<!-- 下面的这个v-if是进行流量控制的看哪个页面就加载哪个页面
避免用户的流量消耗提高用户体验
-->
<!-- <image v-if="index===currentIndex" @click="maskChange" :src="item.picurl" mode="aspectFill"></image> -->
<!-- 也可以选择加载临近一至两张的图片 -->
<!-- 这是加载临近的左右图片 -->
<!-- <image v-if="Math.abs(index - currentIndex) <= 1" @click="maskChange" :src="item.picurl" mode="aspectFill"></image> -->
<image v-if="readImgs.includes(index)" @click="maskChange" :src="item.picurl" mode="aspectFill"></image>
</swiper-item>
</swiper>
<view class="mask" v-show="maskState">
<view class="goBack" @click="goBack" :style="{top:getPreviewBarHeight() + 'px'}">
<uni-icons type="back" color="#fff" size="20"></uni-icons>
</view>
<view class="count">{{currentIndex+1}} / {{classList.length}}</view>
<view class="time">
<uni-dateformat :date="Date.now()" format="hh:mm"></uni-dateformat>
</view>
<view class="date">
<uni-dateformat :date="Date.now()" format="MM月dd日"></uni-dateformat>
</view>
<view class="footer">
<view class="box" @click="clickInfo">
<uni-icons type="info" size="23"></uni-icons>
<view class="text">信息</view>
</view>
<view class="box" @click="clickScore">
<uni-icons type="star" size="23"></uni-icons>
<view class="text">{{currentInfo.score}}</view>
</view>
<view class="box" @click="clickDownload">
<uni-icons type="download" size="23"></uni-icons>
<view class="text">下载</view>
</view>
</view>
</view>
<uni-popup ref="infoPopup" type="bottom">
<view class="infoPopup">
<view class="popHeader">
<!-- 空占位区用于flex布局的分布 -->
<view></view>
<view class="title">壁纸信息</view>
<view class="close" @click="clickInfoClose">
<uni-icons type="closeempty" size="18" color="#999"></uni-icons>
</view>
</view>
<scroll-view :scroll-y="true">
<view class="content">
<view class="row">
<view class="label">壁纸ID</view>
<!-- 这里的内容是可选的所以要用text的 selectable属性 -->
<text selectable class="value">{{currentInfo._id}}</text>
</view>
<!-- 暂时没用到 -->
<!-- <view class="row">
<view class="label">分类</view>
<text class="value class">明星美女</text>
</view> -->
<view class="row">
<view class="label">发布者</view>
<text class="value">{{currentInfo.nickname}}</text>
</view>
<view class="row">
<text class="label">评分</text>
<view class="value roteBox">
<uni-rate :readonly="true" :touchable="false" :value="currentInfo.score" size="16" />
<!-- <uni-rate :readonly="true" :touchable="false" :value="5"/> -->
<text class="score">{{currentInfo.score}}</text>
</view>
</view>
<view class="row">
<text class="label">摘要</text>
<view class="value">
{{currentInfo.description}}
</view>
</view>
<view class="row">
<text class="label">标签</text>
<view class="value tabs">
<view class="tab" v-for="tab in currentInfo.tabs" :key="tab">
{{tab}}
</view>
</view>
</view>
<view class="copyright">
声明本图片来用户投稿非商业使用用于免费学习交流如侵犯了您的权益您可以拷贝壁纸ID举报至平台邮箱513894357@qq.com管理将删除侵权壁纸维护您的权益
</view>
<!-- 自己手动加一个安全距离 -->
<view class="safe-area-inset-bottom"></view>
</view>
</scroll-view>
</view>
</uni-popup>
<!-- :is-mask-click 表示与不允许你点击旁边的蒙版进行关闭弹窗 -->
<uni-popup ref="scorePopup" :is-mask-click="false">
<view class="scorePopup">
<!-- 头部 -->
<view class="popHeader">
<!-- 空占位区用于flex布局的分布 -->
<view></view>
<view class="title">{{isScore?'已经评分过了~':'壁纸评分'}}</view>
<view class="close" @click="clickScoreClose">
<uni-icons type="closeempty" size="18" color="#999"></uni-icons>
</view>
</view>
<!-- 中部内容-评分区 -->
<view class="content">
<!-- allowHalf 允许评分一半 -->
<uni-rate v-model="userScore" @change="onChange" :disabled="isScore" disabled-color="#FFCA3E"
allowHalf />
<text class="text">{{userScore}}</text>
</view>
<!-- 底部内容-确认按钮 -->
<view class="footer">
<button @click="submitScore" :disabled="!userScore || isScore" type="default" size="mini"
plain>确认评分</button>
</view>
</view>
</uni-popup>
<!-- <view class="">{{readImgs}}</view> -->
</view>
</template>
<script setup>
import {
ref
} from "vue"
import {
getPreviewBarHeight
} from "@/utils/system.js"
import {
onLoad
} from "@dcloudio/uni-app"
import {
apiGetSetScore,
apiWriteDownload
} from "@/api/apis.js"
// import { fail } from "assert";
import {apiDetailWall} from "@/api/apis.js"
import {onShareAppMessage,onShareTimeline} from "@dcloudio/uni-app"
const maskState = ref(true);
//信息弹窗
const infoPopup = ref(null)
//当前图片的具体信息内容
const currentInfo = ref({})
//评分弹窗
const scorePopup = ref(null)
//用户评分v-model双向绑定
const userScore = ref(0)
//用户是否评完分了
const isScore = ref(false)
//对应分类的图片列表
const classList = ref([])
//当前图片的id
const currentId = ref(null)
//通过当前图片id获得的当前图片的index
const currentIndex = ref(0)
//已经看过的图片的索引数组
const readImgs = ref([])
//读取缓存里面的内容
//后面加一个空数组的原因是为了防止缓存里面没数据时用map会报错.
const storgClassList = uni.getStorageSync("storgClassList") || [];
classList.value = storgClassList.map(item => {
// 如果直接return item 的话,item里面所有的属性都会不见
// ...item的意思是就是将item里面的所有东西原模原样的全部拷贝到我们新return出来的数据
return {
...item,
//注意 _small.webp是小图,要看大图的话,需要将后缀改变成.jpg
picurl: item.smallPicurl.replace("_small.webp", ".jpg")
}
console.log(classList.value);
})
//onLoad方法获得页面传入的参数
onLoad(async (e) => {
// console.log(e);
//当前图片的id
currentId.value = e.id
// 判断用户是不是通过分享页面进来的
if(e.type == 'share'){
// apiDetailWall()这个函数需要一个接收图片的id
let res = await apiDetailWall({id:currentId.value})
//将返回对象的内容全部复制给classList.value并修改picurl
classList.value = res.data.map(item =>{
return {
...item,
picurl:item.smallPicurl.replace("_small.webp", ".jpg")
}
})
}
// 注意:使用 _id 而不是 id并确保类型匹配
//根据id找在数组中的索引
currentIndex.value = classList.value.findIndex(item => item._id == currentId.value)
// 预先加载和去重
readImgsFun()
currentInfo.value = classList.value[currentIndex.value]
})
//swiper的页面滑动时会触发swiper的change事件,有对象传入
const swiperChange = (e) => {
currentIndex.value = e.detail.current
// console.log(e);
readImgsFun()
currentInfo.value = classList.value[currentIndex.value]
}
//普通状态就是加载临近左右的两张图片,包自己共三张
//必须考虑加载页面时 第一页要加载最后一页 和 最后一页 要加载第一页的情况。
const readImgsFun = () => {
readImgs.value.push(
currentIndex.value <= 0 ? classList.value.length - 1 : currentIndex.value - 1,
currentIndex.value,
currentIndex.value >= classList.value.length - 1 ? 0 : currentIndex.value + 1
)
//new Set() 可以保持数组的唯一性。但是new Set 后的结果是一个 new Set对象
//因此我们需要将其转换为数组对象。因此对其使用 [...]
// ... 是展开运算符。将对象所有可枚举的属性逐一展开并复制到新对象中
// [] 是将其转换为数组对象
readImgs.value = [...new Set(readImgs.value)]
}
//点击info弹窗
const clickInfo = () => {
infoPopup.value.open();
}
//点击关闭info弹窗
const clickInfoClose = () => {
infoPopup.value.close();
}
//评分弹窗
const clickScore = () => {
// 判断本地是否存在这个userScore来表明用户是否评过分了
if (currentInfo.value.userScore) {
isScore.value = true
//如果已经评完分了,将评过的分数赋值给展示的分数
userScore.value = currentInfo.value.userScore
}
scorePopup.value.open();
}
//点击关闭评分弹窗
const clickScoreClose = () => {
scorePopup.value.close();
userScore.value = 0
//复原思想将这个flag变回原来的信息展示的时候再变回来可以免去许多的bug
isScore.value = false
}
//确认评分
const submitScore = async () => {
// console.log("评分了");
uni.showLoading({
title: "加载中...",
})
//冒号的作用是将后面的名字作为当前名字的别名。两字名字都代表同个东西
let {
classid,
_id: wallId
} = currentInfo.value
// 用户的评分作为参数传入userScore是一个v-model
let res = await apiGetSetScore({
classid,
wallId,
userScore: userScore.value
})
uni.hideLoading();
// console.log(res);
if (res.errCode === 0) {
uni.showToast({
title: "评分成功",
icon: "none"
})
}
//classList.value[currentIndex.value] 表示找到这个数组里的这个对象
// .userScore 表示给这个对象加一个 userScore 属性
classList.value[currentIndex.value].userScore = userScore.value;
//然后将这个新的 classList 保存到本地
uni.setStorageSync("storgClassList", classList.value)
clickScoreClose()
}
//遮罩层状态
const maskChange = () => {
maskState.value = !maskState.value
}
//返回上一页
const goBack = () => {
uni.navigateBack({
success: () => {
},
fail:(err) => {
uni.reLaunch({
url:"/pages/index/index"
})
}
})
}
// 文件下载
const clickDownload = async () => {
// #ifdef H5
uni.showModal({
content: "请长按保存壁纸",
showCancel: false
})
// #endif
// #ifndef H5
try {
uni.showLoading({
title: "下载中...",
// 显示过程中不允许点击其他的东西
mask: true
})
let {
classid,
_id: wallId
} = currentInfo.value
let res = await apiWriteDownload({
classid,
wallId
})
/* != 表示5s内重复下载了
(人家接口设计的,5s内重复下载就提示这个东西5s内只能下载一次 */
if (res.errCode != 0) {
throw res
}
uni.getImageInfo({
// 这个getImageInfo是为了根据网络地址获得一个临时下载地址以便能够下载到相册里面
//单纯的使用 saveImageToPhotosAlbum 是不能够保存图片的
src: currentInfo.value.picurl,
success: (res) => {
uni.saveImageToPhotosAlbum({
filePath: res.path,
success: (res) => {
// console.log(res);
uni.showToast({
title:"保存成功,请到相册查看",
icon:"none"
})
},
fail: (err) => {
/* 下面这个情况是针对用户点击“下载”之后,但是没有保存时出现的
情况取消保存错误信息是fail cancel 没有授权是fail auth deny */
if (err.errMsg == 'saveImagePhotoAlbum:fail cancel') {
uni.showToast({
title: "保存失败,请重新点击下载",
icon: "none"
})
//此时return 是为了结束步骤。不能继续进行到下面的代码当中
return;
}
uni.showModal({
title: "授权提示",
content: "需要授权保存相册",
//success表示系统回复了的情况
success: res => {
//这里表示点击了“确认"的情况
if (res.confirm) {
uni.openSetting({
success: setting => {
console.log(
setting);
/* setting.authSetting['scope.writePhotosAlbum']
表示的是你 "是否授权的值true / false" */
if (setting
.authSetting[
'scope.writePhotosAlbum'
]) {
uni.showToast({
title: "获取授权成功",
icon: "none"
})
} else {
uni.showToast({
title: "获取权限失败",
icon: "none"
})
}
}
})
}
}
})
},
//complete表示无论成功还是失败都会做的事件
complete: () => {
uni.hideLoading();
}
})
}
})
} catch (err) {
uni.hideLoading()
}
// #endif
}
//分享给好友
onShareAppMessage((e)=>{
//分享这里是需要有一个 “return” 的
//在上面的onLoad中已经将列表的id赋给了queryParams的id属性
return{
title:"hzb壁纸-",
// 必须传递id进去否则进不去内部页面
path:"/pages/preview/preview?id="+currentId.value+"&type=share"
}
})
//分享到朋友圈
onShareTimeline(()=>{
return{
// 标题
title:"hzb壁纸~~~",
// 分享时候的图片地址。可以本地也可以网络图
// imageUrl:"/static/images/logo2.jpg"
//要想看朋友圈这个需要带的query参数
// type表示是分享传入的
query:"id="+currentId.value+"&type=share"
}
})
</script>
<style lang="scss" scoped>
@import '../../uni.scss';
// @include fix-position就是下面的这堆东西
// position: absolute;
// left: 0;
// right: 0;
// //单纯的给marginauto的话你的宽度会被拉长需要给一个固定宽度
// margin: auto;
// //宽度你给多少都不能准确到达中间位置给一个fit-content自动适应
// width:fit-content;
// color: #fff;
.preview {
width: 100%;
height: 100vh;
position: relative;
swiper {
width: 100%;
height: 100%;
image {
width: 100%;
height: 100%;
}
}
.mask {
.goBack {
@include fix-position;
width: 38px;
height: 38px;
background: rgba(0, 0, 0, 0.5);
left: 30rpx;
top: 0;
margin-left: 0;
border-radius: 100px;
backdrop-filter: blur(10rpx);
border: 1px solid rbga(255, 255, 255, 0.3);
display: flex;
justify-content: center;
align-items: center;
}
.count {
position: absolute;
top: 10vh;
left: 0;
right: 0;
//单纯的给marginauto的话你的宽度会被拉长需要给一个固定宽度
margin: auto;
//宽度你给多少都不能准确到达中间位置给一个fit-content自动适应
width: fit-content;
background: rgba(0, 0, 0, 0.3);
font-size: 28rpx;
color: #fff;
border-radius: 40rpx;
padding: 8rpx 28rpx;
backdrop-filter: blur(20rpx);
}
.time {
@include fix-position;
//看设计图上面的设计
top: calc(10vh + 80rpx);
font-size: 140rpx;
font-weight: 100rpx;
//去除行高
line-height: 1em;
//加上文字阴影,避免图片是白色是时候你看不到文字
text-shadow: 0 4rpx rgba(0, 0, 0, 0.3);
}
.date {
@include fix-position;
top: calc(10vh + 230rpx);
font-size: 34rpx;
//加上文字阴影,避免图片是白色是时候你看不到文字
text-shadow: 0 2rpx rgba(0, 0, 0, 0.3);
}
.footer {
@include fix-position;
background: rgba(255, 255, 255, 0.8);
bottom: 10vh;
width: 65vw;
height: 120rpx;
border-radius: 120rpx;
color: #000;
display: flex;
justify-content: space-around;
align-items: center;
box-shadow: 0 2rpx rgba(0, 0, 0, 0.1);
backdrop-filter: blur(20rpx);
//使文字和样式在同一列上
.box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
//给box加padding增大接触距离
padding: 2rpx 12rpx;
.text {
font-size: 26rpx;
color: $text-font-color-2;
}
}
}
}
.popHeader {
display: flex;
justify-content: space-between;
align-items: center;
.title {
color: $text-font-color-2;
font-size: 26rpx;
}
.close {
// 细节处理,增大图标接触面积,提高用户体验
padding: 6rpx
}
}
.infoPopup {
background: #fff;
padding: 30rpx;
border-radius: 30rpx 30rpx 0 0;
overflow: hidden;
scroll-view {
max-height: 60vh;
.content {
.row {
display: flex;
padding: 16rpx 0;
font-size: 32rpx;
// 行高控制
line-height: 1.7em;
.label {
color: $text-font-color-3;
width: 140rpx;
//文字靠右就是word里面的右对齐
text-align: right;
font-size: 30rpx;
}
.value {
//上面父级已经给了宽度了你子级就可以给一个flex1来让其自动分配
flex: 1;
//因为可能 value的值可能很多会把label挤压所以加一个兼容性写法宽度为0
width: 0;
font-size: 30rpx;
}
.roteBox {
display: flex;
align-items: center;
.score {
font-size: 26rpx;
color: $text-font-color-2 ;
padding-left: 10rpx;
}
}
.tabs {
display: flex;
white-space: 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: 0 10rpx 10rpx 0;
}
}
.class {
color: $brand-theme-color;
}
}
.copyright {
font-size: 28rpx;
padding: 20rpx;
background: #F6F6F6;
color: #666;
border-radius: 10rpx;
margin: 20rpx 0;
line-height: 1.5em;
}
}
}
}
.scorePopup {
background: #fff;
padding: 30rpx;
width: 70vw;
border-radius: 30rpx;
overflow: hidden;
.content {
padding: 30rpx 0;
display: flex;
justify-content: center;
align-items: center;
.text {
color: #FFCA3E;
padding-left: 10rpx;
width: 80rpx;
line-height: 1em;
text-align: right;
}
}
.footer {
display: flex;
justify-content: center;
align-items: center;
}
}
}
</style>

273
pages/search/search.vue Normal file
View File

@@ -0,0 +1,273 @@
<template>
<view class="searchLayout">
<!-- cancel是"取消",clear是那个"×" -->
<view class="search">
<uni-search-bar
@confirm="onSearch"
@cancel="onClear"
@clear="onClear"
:focus="true"
placeholder="搜索"
v-model="queryParams.keyword"
>
</uni-search-bar>
</view>
<!-- 当有图片的时候就不展示这些东西标签啥的 -->
<!-- 点击标签但是没有数据时noSearch会变成true也可以显示这些标签 -->
<view v-if="!classList.length || noSearch">
<!-- 这个最近搜索的展示是根据有没有历史记录来展示的
如果没有历史记录就不展示这一框
如果有历史记录就展示这一框
-->
<view class="history" v-if="historySearch.length">
<view class="topTitle" >
<view class="text">最近搜索</view>
<view class="icon" @click="removeHistory" >
<uni-icons type="trash" size="25"></uni-icons>
</view>
</view>
<view class="tabs">
<view class="tab" v-for="tab in historySearch" :key="tab"
@click="clickTab(tab)">{{tab}}</view>
</view>
</view>
<view class="recommend">
<view class="topTitle">
<view class="text">热门搜索</view>
</view>
<!-- 这里我本来的:key="index" ,但是人家却是:key="tab"-->
<view class="tabs">
<view class="tab" v-for="tab in recommendList" :key="tab"
@click="clickTab(tab)">{{tab}}</view>
</view>
</view>
</view>
<!-- 没有搜索结果的情况,这是一个插件 -->
<view class="noSearch" v-if="noSearch">
<uv-empty mode="search" ></uv-empty>
</view>
<!-- 有搜索结果的展示区域 -->
<view v-else>
<view class="list">
<!-- 这里你单独的进行传id也是不能够看到预览页面的 -->
<!-- 因为预览页面呢个够被看到是取决你有没有缓存这个预览页面的数据的 -->
<!-- 而缓存预览页面的数据我们可以在请求网络的数据的时候就进行存储 -->
<!--
:url是:url="`/pages/preview/preview?id=${item._id}`"
不是:url="`pages/preview/preview?id=${item._id}`"
前面还要有一个 /
-->
<!-- <navigator :url="`pages/preview/preview?id=${item._id}`" class="item"
v-for="item in classList" :key="item._id"> -->
<navigator :url="`/pages/preview/preview?id=${item._id}`" class="item"
v-for="item in classList" :key="item._id">
<image :src="item.smallPicurl" mode="aspectFill"></image>
</navigator>
</view>
<view v-if="noData || classList.length" class="loadingLayout">
<uni-load-more :status="noData?'noMore':'loading'"/>
</view>
</view>
</view>
</template>
<script setup>
import {ref} from "vue"
import {onReachBottom,onUnload,onLoad} from "@dcloudio/uni-app"
import {apiSearchData} from "@/api/apis.js"
//传递的参数
const queryParams = ref({
pageNum:1,
pageSize:12,
keyword:""
})
//搜索历史词
//获取历史搜索词,我们需要使其从缓存里面读取,保证刷新不会重新被赋值
// 当用户没有缓存时给一个空数组避免出现获取不到null的情况进而影响后续操作
const historySearch = ref(uni.getStorageSync("historySearch") || []);
//热门推荐词
const recommendList = ref(["美女","帅哥","宠物","卡通"])
//进到标签页面时可能发生的情况
//没有数据了
const noData = ref(false)
//没有搜索结果
const noSearch = ref(false)
//搜索结果列表 搜索的结果最终是要在preview页面进行查看的
// classList初始化为空只有在调用了searchData()函数之后才会被赋值
const classList = ref([])
//点击清除按钮
const onClear = ()=>{
initParams()
}
//点击标签进行搜索.需要一个参数接收传入的标签名
// 问题1.搜索框的值怎么表示? --》 v-model 绑定,监视:value和@input事件
// 2.怎么得到值后就自动”comfirm“
const clickTab = (value) =>{
/* 因为你在点击了标签的内容之后,还可以在搜索框继续进行搜索,那此时你新搜索的内容会跟之前一样,
被添加到原来的内容下面。因此你需要对其进行初始化并将keyword换成最新的关键词 */
initParams(value)
// 点击标签之后不是触发searchData(),因为这样不会这个点击的标签不会被添加到历史记录里面
// 点击标签之后要触发的函数是onSearch()
// searchData()
onSearch()
}
//清空最近搜索历史记录
const removeHistory = ()=>{
uni.showModal({
title:"是否清空历史搜索",
success:(res) =>{
if(res.confirm){
// console.log("确认删除");
uni.removeStorageSync("historySearch")
historySearch.value = []
}
}
})
}
//1.点击搜索,点击了之后,搜索记录里面要多加一个记录
const onSearch = ()=>{
//加一个加载页提高用户体验。那时候时候取消呢在searchData()获取数据那时候进行操作
uni.showLoading()
// console.log(queryParams.value.keyword);
// 第一种添加的方式就是直接加到前面,老的历史词就直接展开
// 第二种就是直接用数组的添加方法
//slice进行切片操作确保只有10个历史记录
historySearch.value = [...new Set([queryParams.value.keyword,...historySearch.value])].slice(0,10)
uni.setStorageSync("historySearch",historySearch.value)
/* 因为你在点击了标签的内容之后,还可以在搜索框继续进行搜索,那此时你新搜索的内容会跟之前一样,
被添加到原来的内容下面。因此你需要对其进行初始化并将keyword换成最新的关键词 */
initParams(queryParams.value.keyword)
// 只有点击“搜索”之后才要触发这个函数
searchData()
}
//搜索之后所有的要做的功能的合集
const searchData = async () =>{
// 加一个try、catch进行操作捕捉“拿不到数据”的情况。
try{
//使res等于请求过来的数据
let res = await apiSearchData(queryParams.value)
classList.value = [...classList.value,...res.data]
//将数据保存到缓存里面方便上面点击标签后要预览页面preview
uni.setStorageSync("storgClassList",classList.value)
//如果后面没有数据了就需要把noData变成true
if(queryParams.value.pageSize > res.data.length) noData.value = true
//下面这个是判断有没有搜索结果的
//如果先点击了有图片的标签,那么当你再点击没图片的标签时,就还是展示原来那个有图片的标签
//如果原本classList就是空的并且你点击的标签也没有图片时才会展示“没有搜索结果”
if(res.data.length == 0 && classList.value.length == 0) noSearch.value = true
// console.log(res);
}finally {
uni.hideLoading()
}
}
//初始化搜索框,清空原本的内容
//什么时候用呢?
// 1.当我们点击“取消”和"×"的时候进行调用
// 2.当我们点击新的标签时就要先初始化
const initParams = (value='')=>{
//清空原先已经展示的数据
classList.value = []
noData.value = false
noSearch.value = false
queryParams.value = {
pageNum:1,
pageSize:12,
// 如果有传入值就把这个值赋给keyword没有值的话就默认变成""
keyword:value || ""
}
}
//触底加载更多
onReachBottom(()=>{
//这个if表示如果后面没有数据了就直接return结束不要再进行请求数据了
if(noData.value) return
queryParams.value.pageNum++
searchData()
})
//关闭页面时将数据清除,能够提高性能
onUnload(()=>{
uni.removeStorageSync("storgClassList",classList.value)
})
</script>
<style lang="scss" scoped>
.searchLayout{
.search{
padding: 0 10rpx;
}
.topTitle{
display: flex;
justify-content: space-between;
align-items: center;
}
.history{
padding: 30rpx;
}
.recommend{
padding: 30rpx;
}
.tabs{
display: flex;
align-items: center;
flex-wrap: wrap;
padding-top: 20rpx;
.tab{
background: #F4F4F4;
font-size: 28rpx;
color: #333;
padding: 10rpx 28rpx;
border-radius: 50rpx;
margin-right: 20rpx;
margin-top: 20rpx;
}
}
.list{
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 5rpx;
padding: 20rpx 5rpx;
.item{
height: 440rpx;
image{
height: 100%;
width: 100%;
display: block;
}
}
}
}
</style>

239
pages/user/user.vue Normal file
View File

@@ -0,0 +1,239 @@
<template>
<!-- 注意如果userinfo被加载出来才会显示这个内容避免报错否则就显示loding -->
<view class="userLayout pageBg" v-if="userinfo">
<!-- 添加一个状态栏的高度避免被刘海屏挡住了 -->
<view :style="{height:getNavBarHeight() + 'px'}"></view>
<view class="userinfo">
<view class="avatar">
<image src="/static/images/xxmLogo.png" mode="aspectFill"></image>
</view>
<view class="ip">{{userinfo.IP}}</view>
<view class="address">来自于
<!-- 避免开代理获取不到城市和省份的情况外面考虑以下爱的内容最不其给你在中国 -->
<!-- 1. 安全访问操作符 (?.) 确保拥有该属性没回的返回undefined不会报错/如果都没有就默认是中国 -->
{{userinfo?.address?.city || userinfo?.address?.province || userinfo?.address?.country || '中国' }}
</view>
</view>
<!-- 菜单 -->
<view class="section">
<view class="list">
<!-- 区分页面表明这是通过user页面然后点击我的下载进入的 -->
<navigator url="/pages/classlist/classlist?name=我的下载&type=download">
<view class="row">
<!-- 这里分为左右两部分图标文字以及 (右箭头) -->
<view class="left">
<uni-icons type="download-filled" size="20" color="#28b389"></uni-icons>
<view class="text" >我的下载</view>
</view>
<view class="right">
<view class="text">{{userinfo.downloadSize}}</view>
<uni-icons type="forward" size="15" color="#aaa"></uni-icons>
</view>
</view>
</navigator>
<!-- 区分页面表明这是通过user页面然后点击我的评分进入的 -->
<navigator url="/pages/classlist/classlist?name=我的评分&type=score">
<view class="row">
<!-- 这里分为左右两部分图标文字以及 (右箭头) -->
<view class="left">
<uni-icons type="star-filled" size="20" color="#28b389"></uni-icons>
<view class="text" >我的评分</view>
</view>
<view class="right">
<view class="text">{{userinfo.scoreSize}}</view>
<uni-icons type="forward" size="15" color="#aaa"></uni-icons>
</view>
</view>
</navigator>
<view class="row">
<!-- 这里分为左右两部分图标文字以及 (右箭头) -->
<view class="left">
<uni-icons type="chatboxes-filled" size="20" color="#28b389"></uni-icons>
<view class="text" >联系客服</view>
</view>
<view class="right">
<view class="text"></view>
<uni-icons type="forward" size="15" color="#aaa"></uni-icons>
</view>
<!-- #ifdef MP -->
<button open-type="contact" class="contact">联系客服</button>
<!-- #endif -->
<!-- #ifdef WEB -->
<button @click="clickContact">拨打电话</button>
<!-- #endif -->
</view>
</view>
</view>
<view class="section">
<view class="list">
<!-- 订阅更新和常见问题的页面都是在公告列表里面的但是id变一下就可以了 -->
<navigator url="/pages/notice/detail?id=653507c6466d417a3718e94b&name=订阅更新">
<view class="row">
<!-- 这里分为左右两部分图标文字以及 (右箭头) -->
<view class="left">
<uni-icons type="notification-filled" size="20" color="#28b389"></uni-icons>
<view class="text" >订阅更新</view>
</view>
<view class="right">
<uni-icons type="forward" size="15" color="#aaa"></uni-icons>
</view>
</view>
</navigator>
<!-- 订阅更新和常见问题的页面都是在公告列表里面的但是id变一下就可以了 -->
<navigator url="/pages/notice/detail?id=6536358ce0ec19c8d67fbe82&name=常见问题">
<view class="row">
<!-- 这里分为左右两部分图标文字以及 (右箭头) -->
<view class="left">
<uni-icons type="flag-filled" size="20" color="#28b389"></uni-icons>
<view class="text" >常见问题</view>
</view>
<view class="right">
<uni-icons type="forward" size="15" color="#aaa"></uni-icons>
</view>
</view>
</navigator>
</view>
</view>
</view>
<!-- userinfo没有被加载出来就显示loading -->
<view class="loadingLayout" v-else>
<view :style="{height:getNavBarHeight()+'px'}"></view>
<uni-load-more status="loading"></uni-load-more>
</view>
</template>
<script setup>
import {getNavBarHeight} from "@/utils/system.js"
import {apiUserInfo} from "@/api/apis.js"
import { ref } from "vue";
const userinfo = ref(null)
const clickContact = ()=>{
uni.makePhoneCall({
phoneNumber: '114' //仅为示例
});
}
const getUserInfo = async ()=>{
let res = await apiUserInfo()
// console.log(res);
userinfo.value = res.data
}
getUserInfo()
</script>
<style lang="scss" scoped>
.userLayout{
.userinfo{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 50rpx 0;
.avatar{
width: 160rpx;
height: 160rpx;
// 对父组件设置border-radius和overflowhidden
border-radius: 50%;
overflow: hidden;
// 同时还需要image设置width和height 100%
image{
width: 100%;
height: 100%;
}
}
.ip{
font-size: 44rpx;
color: #333;
padding: 20rpx 0 5rpx;
}
.address{
font-size: 28rpx;
color: #aaa;
}
}
.section{
width: 690rpx;
// 父元素使其居中处理
margin: 50rpx auto;
border: 1px solid #eee;
border-radius: 10rpx;
box-shadow: 0 0 30rpx rgba(0,0,0,0.05);
.list{
.row{
display: flex;
justify-content: space-between;
align-items: center;
//细节处理,不要贴边
padding:0 30rpx;
height: 100rpx;
border-bottom: 1px solid #eee;
position: relative;
background: #fff;
//最后一个元素会有两个边框,所以我们需要去掉一个
//&&号表示同名,
//这里即指(.row 还要一个伪类选择器宣导最后一个元素
&:last-child{
border-bottom: 0;
}
.left{
display: flex;
justify-content: center;
align-items: center;
.text{
padding-left: 20rpx;
color: #666;
}
}
.right{
display: flex;
justify-content: center;
align-items: center;
.text{
font-size: 28rpx;
color: #aaa;
}
}
button{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
// display: flex;
// justify-content: center;
// align-items: center;
opacity: 0;
}
}
}
}
}
</style>